① 使用迭代器==Iterator==
② foreach循环(或增强for循环):==内部仍然调用的是迭代器==
==Iterable==:接口,规定实现类或子接口必须要有提供迭代器的能力
==Iterator==:迭代器的类型,迭代器使用Iterable接口中iterator方法返回的
java.utils包下定义的迭代器接口:Iterator
==支持删除行为==:删除当前遍历到的元素(迭代器对象.remove()),迭代器在迭代的时候不支持集合本身的修改行为(add|remove),否则,会引发java.util.ConcurrentModificationException
并发修改异常
Iterator iterator = coll.iterator(); //hasNext():判断是否还下一个元素 while(iterator.hasNext()){ //next():①指针下移 ②将下移以后集合位置上的元素返回 System.out.println(iterator.next()); }
==存储的数据特点:存储有序的、可重复的数据。==
add(Object obj)
remove(int index) / remove(Object obj)
set(int index, Object ele)
get(int index)
add(int index, Object ele)
size()
底层:底层是一个Object类型的数组,初始的默认长度0,在第一次add时,容量变为10,也可以指定长度,初始长度如果满了,底层进行自动扩容,扩容为原来的1.5倍oldCapacity + (oldCapacity >> 1)
。10—->15—->22,如果对集合中的元素个数可以预估,那么建议预先指定一个合适的初始容量new ArrayList(20);
优点:查找效率高,向末尾添加元素也可以
缺点:增加 、删除牵扯到数组的扩容和移动,效率低
底层:是一个链表(双向)结构,不是线性存储。
优点:增加、删除效率高
缺点:查找效率低
底层:是一个Object类型的数组,初始的默认长度为10,扩容的时候扩容为原来的2倍 ,如果自己指定扩容的长度,那么就是就容量加指定的,如果没有指定,就是旧容量的2倍。
优点:线程安全,通过synchronized同步锁实现
缺点:效率低
==添加的对象,所在的类要重写equals()方法==
==存储的数据特点:无序的、不可重复的元素(如果hashCode返回值一样和equals为true,则是重复元素)==
以HashSet为例说明:
==Set接口中没额外定义新的方法,使用的都是Collection中声明过的方法。==
**底层:**HashSet的底层是一个HashMap,只是将HashMap中的值设置为一个常量,用所有的键组成了一个HashSet
优点:==可以存储null值==
缺点:线程不安全
LinkedHashSet 是 HashSet 的子类
底层:是一个LinkedHashMap,底层维护了一个数组 + 双向链表
存储对象所在类的要求:
向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()
TreeSet 是 SortedSet(继承于Set)接口的实现类,TreeSet 可以确保集合元素处于排序状态。
==不可以放null对象==
存储对象所在类的要求:
底层:所有的键构成了一个HashSet,整体是数组+链表/红黑树
优点:效率高,可以存储null值、null键
缺点:线程不安全
底层:==HashMap的子类==;在原的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
优点:保证在遍历map元素时,可以照添加的顺序实现遍历。对于频繁的遍历操作,此类执行效率高HashMap。
缺点:线程不安全
底层:==数组+链表/红黑树==
优点:保证照添加的key-value对进行排序,实现按照键排序遍历(自然排序或定制排序)
缺点:
==键不可以为null,值可以为null==
底层:数组+链表/红黑树
优点:线程安全
缺点:效率低;不能存储null的key和value
不可以放入null值、null键
底层:Hashtable的子类,数组+链表/红黑树
优点:一般用来存储配置文件
缺点:==key和value都是String类型==
int newCapacity = (oldCapacity << 1) + 1
作用:操作Collection和Map的工具类
Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作, 还提供了对集合对象设置不可变、对集合对象实现同步控制等方法
如果使用ArrayList多个线程同时添加和读取,可能会出现并发修改异常ConcurrentModificationException
使用synchronized关键字,对修改、添加、删除等方法进行修饰,实际开发中,我们很少使用,强同步性能低
可以使用Collections中的静态方法synchronizedXXX将XXX类型的集合转为线程安全版本
List list = new CopyOnWriteArrayList();
使用写时复制技术,也就是读的时候可以并发进行读,但是写,只允许一个线程写,复制一份和当前集合相同的集合,然后在写完以后,进行合并
使用Lock可重入锁进行实现
和List的使用方法一样
和List的使用方法一样
使用synchronizd强同步,效率低,不推荐
和List的使用方法一样
Map map = new ConcurrentHa
I:input ; O : output
如:InputStream、Reader、OutputStream、Writer
节点流与具体节点相连接,直接读写节点数据
.mp3,.avi,.rmvb,mp4,.jpg,.doc,.ppt
.txt,.java,.c,.cpp
等语言的源代码。尤其注意.doc,excel,ppt
这些不是文本文件。InputStream(字节流) 和 Reader **(字符流)**是所有输入流的基类。
程序中打开的文件 IO 资源不属于内存里的资源,垃圾回收机制无法回收该资源,所以应该==显式关闭文件 IO 资源(使用close方法)。==
FileInputStream 从文件系统中的某个文件中获得输入字节。FileInputStream 用于读取非文本数据之类的原始字节流。要读取字符流,需要使用 FileReader
**OutputStream(字节流)和Writer(字符流)**是所有输出流的基类。
FileOutputStream(file)
,则目录下有同名文件将被覆盖。FileOutputStream(file,true)
,则目录下的同名文件不会被覆盖, 在文件内容末尾追加内容。因为字符流直接以字符作为操作单位,所以 Writer 可以用字符串来替换字符数组, 即以 String 对象作为参数
==显示关闭IO资源,需要使用flush和close方法==
FileOutputStream 从文件系统中的某个文件中获得输出字节。FileOutputStream 用于写出非文本数据之类的原始字节流。要写出字符流,需要使用 FileWriter
public static void copy(String copyBy,String copyTo) throws IOException { //输入流 InputStream inputStream = new FileInputStream(new File(copyBy)); //输出流 OutputStream outputStream = new FileOutputStream(new File(copyTo)); //创建缓冲数组 byte[] bytes = new byte[1024]; int i; //循环写入缓冲,然后从缓冲数组中输出到目标文件 while ((i = inputStream.read(bytes)) != -1){ outputStream.write(bytes,0,i); } //释放资源 outputStream.flush(); inputStream.close(); outputStream.flush(); }
BufferedInputStream 、BufferedOutputStream、BufferedReader、BufferedWriter
优点:提供流的读取、写入的速度
提高读写速度的原因:内部提供了一个缓冲区。默认情况下是8kb
客户端/浏览器端 <----> 后台(java,GO,Python,Node.js,php) <----> 数据库
要求前前后后使用的字符集都要统一:UTF-8.
ObjectInputStream、OjbectOutputSteam
用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
补充:
ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量
多线程
一个Java应用程序java.exe,其实至少三个线程:
main( )
)gc( )
)问题一:我们启动一个线程,必须调用start(),不能调用run()的方式启动线程。
问题二:如果再启动一个线程,必须重新创建一个Thread子类的对象,调用此对象的start().
开发中:==优先选择:实现Runnable接口的方式==
public class Thread implements Runnable
如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
java.lang.Thread
MAX_PRIORITY:10
MIN _PRIORITY:1
NORM_PRIORITY:5 -->默认优先级
getPriority()
:获取线程的优先级
setPriority(int p)
:设置线程的优先级
说明:高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只当高优先级的线程执行完以后,低优先级的线程才执行。
JDK中用Thread.State类定义了线程的几种状态
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:
通过同步机制,来解决线程的安全问题。
synchronized(同步监视器){ //需要被同步的代码 }
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。
关于同步方法的总结:
可以重复使用的锁,锁的创建只有一次,可以重复调用lock和unlock
Lock可重入锁代表实现类为:ReentrantLock
公平锁的效率比非公平锁低
公平锁总是可以保证让所有线程中等待时间最长的线程先执行
在new ReentrantLock(true)
时,参数为true,创建的就是公平锁,不传参,默认是非公平锁
ReadWriteLock读写锁代表实现类为:ReentrantReadWriteLock
两个线程都是写锁:互斥,同步执行
两个线程一写一读:互斥,同步执行
两个线程都是读锁:共享,异步执行
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
说明:
1、调整锁的顺序,避免可能出现的死锁
2、调整锁的范围,避免在一个同步代码块中使用另一个同步代码块
3、使用可重入锁ReentrantLock
注意:wait方法等待的线程,在哪等待,被唤醒后,就从哪里开始执行,可能会出现虚假唤醒的问题,所以建议wait循环使用在while循环中
Condition condition = lock.newCondition();
可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据(可以存放线程范围内的局部变量)
1、在javaweb项目里面,我们做MyBatis的工具类的时候,为了可以满足多个线程中,各个线程关闭自己的SqlSession对象的需求,而不会产生线程安全的问题,所以,我们需要将SqlSession对象存入到ThreadLocal对象中
用于向ThreadLocal对象中存值
用于向ThreadLocal对象中取值
用于从ThreadLocal对象中删除值
例如,一个int类型的变量,在高并发的情况下进行加减,通常会导致线程不安全,数值不准确的问题,例如
public class Test1 { static int age = 0; public static void main(String[] args) { for (int i = 0; i < 20; i++) { new Thread(new Runnable() { @Override public void run() { for (int i1 = 0; i1 < 2000; i1++) { age++; } } }).start(); } //判断除了java默认的主线程和垃圾回收线程都执行完了 while(Thread.activeCount()>2){ Thread.yield(); } //代码到了此处,线程数量只剩2个,上面的循环代码已经执行完成了 System.out.println(age); } } //此时的age小于40000,因为出现了线程不安全的现象
缺点是,该方式是同步的,必须一个线程执行完,才会执行另一个线程,效率低下
public class Test1 { static int age = 0; public static void main(String[] args) { for (int i = 0; i < 20; i++) { new Thread(new Runnable() { @Override public void run() { synchronized (Test1.class){ for (int i1 = 0; i1 < 2000; i1++) { age++; } } } }).start(); } //等代码代码运行完毕之后,age是多少 //判断线程数量还剩下几个 while(Thread.activeCount()>2){ Thread.yield(); } //代码到了此处,线程数量只剩2个,上面的循环代码已经执行完成了 System.out.println(age); } }
通过AtomicInteger,可以在线程提交的时候,用自己工作空间中的数值,与现在主存中的数值进行比对,如果一致的话,就压入主存,如果数值已经发生改变的话,那么会进行自旋,也就是重新进行运算,再进行比较
public class Test1 { static AtomicInteger age = new AtomicInteger(); public static void main(String[] args) { for (int i = 0; i < 20; i++) { new Thread(new Runnable() { @Override public void run() { for (int i1 = 0; i1 < 2000; i1++) { age.getAndIncrement(); } } }).start(); } //等代码代码运行完毕之后,age是多少 //判断线程数量还剩下几个 while(Thread.activeCount()>2){ Thread.yield(); } //代码到了此处,线程数量只剩2个,上面的循环代码已经执行完成了 System.out.println(age); } } //此时的结果一定是40000
但是,这样的方法,可能会产生ABA的问题,
比如当前主存的值是100. 并发: 线程A: 从主存中拿到100,放入自己的工作空间中。 线程B: 从主存中拿到100,放入自己的工作空间中。 线程A: 100+1,并且刷新了主存,主存中的值101. 线程C: 从主存中拿到101,减1,并且把100放入主存。 线程B: 执行+1动作,比对CAS,竟然相同,由于线程C对数据的操作,认为没有人改过,直接提交成功了。
在CAS的思想上,利用版本戳的思想,从原始数据开始,给每次的数据都加上一个版本,每次对数据发生修改,版本也会进行迭代
具体的实现类,AtomicStampedReference,这个类在实例化时,第二个参数为版本号
public class Test3 { public static void main(String[] args) throws InterruptedException { AtomicStampedReference atomicStampedReference = new AtomicStampedReference(100,0); Thread thread1 = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } atomicStampedReference.compareAndSet( 100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp()+1); atomicStampedReference.compareAndSet( 101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp()+1); } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { int version = atomicStampedReference.getStamp(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(atomicStampedReference.compareAndSet( 100, 101, version, version + 1)); } }); thread1.start(); thread2.start(); Thread.sleep(2000); System.out.println(atomicStampedReference.getReference()+" "+atomicStampedReference.getStamp()); } }
JDK7提供了7个阻塞队列。分别是:
线程池使用的也是阻塞队列
1、用户提交任务,判断核心线程是否正在处理,如果核心线程有空闲,直接使用核心线程执行
2、如果核心线程没有空闲,判断队列是否满了,如果没有满,就把认为放进队列中
3、如果队列已经满了,那么判断线程池中线程+新任务线程是否大于最大线程数,如果大于,抛出异常,拒绝执行
4、如果小于等于最大线程数,创建新的线程,执行任务
/* 参数说明 * 参数1:线程池创建后,核心线程数 * 参数2:最大线程数 * 参数3:核心线程外,新创建的线程空闲时,最大存活时间 * 参数4:最大存活时间的单位 * 参数5:用于存放任务的阻塞队列 * 参数6:用于创建线程的线程工厂 * 参数7:任务被拒绝后的策略 */ ThreadPoolExecutor executor = new ThreadPoolExecutor(1,2,3, TimeUnit.SECONDS,new LinkedBlockingDeque<>(3),Executors.defaultThreadFactory(), defaultHandler);
Funture.get()
可以使用Executors的静态方法,来创建线程池
创建方法 | 线程池 | 阻塞队列 | 使用场景 |
---|---|---|---|
newSingleThreadExecutor() | 单线程的线程池 | LinkedBlockingQueue | 适用于串行执行任务的场景,⼀个任务⼀个任务地执行 |
newFixedThreadPool(n) | 固定数目线程的线程池 | LinkedBlockingQueue | 适用于处理CPU密集型的任务,适用执行长期的任务 |
newCachedThreadPool() | 可缓存线程的线程池 | SynchronousQueue | 适用于并发执行大量短期的小任务 |
newScheduledThreadPool(n) | 定时、周期执行的线程池 | DelayedWorkQueue | 周期性执行任务的场景,需要限制线程数量的场景 |
public static void main(String[] args) { // 创建一个固定大小的线程池: ExecutorService es = Executors.newFixedThreadPool(4); Future<Integer> future = es.submit(new Task("a")); System.out.println(future); // 关闭线程池: es.shutdown(); }
阿里巴巴Java开发手册规定线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险
IP的理解
此类的一个对象就代表着一个具体的IP地址
正在计算机上运行的进程。
要求:不同的进程不同的端口号
范围:被规定为一个 16 位的整数 0~65535。
端口号与IP地址的组合得出一个网络套接字:Socket
/** * 服务器端 */ public class ServerSocketTest { public static void main(String[] args) throws IOException { //1、创建服务器端的ServerSocket,指明自己的端口号 ServerSocket serverSocket = new ServerSocket(9999); //2、调用accept()表示接收来自于客户端的socket Socket socket = serverSocket.accept(); //3、获取输入流 InputStream inputStream = socket.getInputStream(); ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); //4、获取输出流 OutputStream outputStream = socket.getOutputStream(); ObjectOutputStream outputStream1 = new ObjectOutputStream(outputStream); //5、循环收发 Scanner scanner = new Scanner(System.in); while (true){ System.out.println("客户端说:" + objectInputStream.readUTF()); System.out.println("请发送你要和客户端说的话:"); String s = scanner.next(); if (s.equals("exit")){ break; } outputStream1.writeUTF(s); outputStream.flush(); outputStream1.flush(); } //5、关闭资源 inputStream.close(); objectInputStream.close(); socket.close(); serverSocket.close(); } }
/** * 客户端 */ public class SocketTest { public static void main(String[] args) throws IOException { //1、创建Socket对象,指明服务器端的ip和端口号 Socket socket = new Socket("localhost",9999); //2、获取一个输出流,用于输出数据 OutputStream outputStream = socket.getOutputStream(); ObjectOutputStream outputStream1 = new ObjectOutputStream(outputStream); //3、获取输入流,用于接收服务器端数据 InputStream inputStream = socket.getInputStream(); ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); //4、循环收发 Scanner scanner = new Scanner(System.in); while (true){ System.out.println("请发送你要和服务器端说的话:"); String s = scanner.next(); if (s.equals("exit")){ break; } outputStream1.writeUTF(s); outputStream.flush(); outputStream1.flush(); System.out.println("服务器端说:" + objectInputStream.readUTF()); } //5、关闭资源 outputStream.close(); outputStream1.close(); socket.close(); } }
所谓泛型,就是==允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型==。这个类型参数将在使用(例如,继承或实现这个接口,用这个类型声明变量、创建对象时确定(即传入实际的类型参数,也称为类型实参)。
==JDK1.5引入==
泛型的定义中,不可以使用基本数据类型,可以使用对应的包装类进行替换
1、类型不安全,导入数据存入混乱,添加泛型,在编译时就会进行类型检查,保证了数据安全
2、避免了强制类型转换时,出现异常ClassCastException
List<String> li = new ArrayList<String>();
1、在实例化集合类时,可以指明具体的泛型类型
2、指明完泛型以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。
3、如果实例化时,没指明泛型的类型。默认类型为java.lang.Object类型。
4、泛型可以嵌套使用
5、JDK7新特性,类型推断,可以写成:
List<String> li = new ArrayList<>();
修饰符 class 类名<泛型形参1,泛型形参2,......> { 在类中使用泛型形参对类型进行占位; }
泛型形参名称,一般由单个大写字母组成,常用的:E、T、V、N、K
如果泛型类在实例化的时候没有指明泛型的类型,那么默认的类型就是Object类型,如果实例化的类是带有泛型的,那么建议在实例化的时候指明类的泛型
修饰符 interface 类名<泛型形参> { 在接口中使用泛型形参对类型进行占位; }
泛型方法是指在方法中出现了泛型的结构,泛型参数与类的泛型参数没任何关系和类是不是泛型类也没有关系。
可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。
public <E> List<E> 方法名(E[] arr){ 方法体; }
类A是类B的父类,**G<A>
和G<B>
**不具备子父类关系,属于并列关系
==通配符:?==
类A是类B的父类,G<A>
和G<B>
是没关系的,二者共同的父类是:G<?>
以List<?>数据的读写为例:
l 读取List的对象list中的元素时,永远是数据安全的,因为不管list的元素真实类型是什么,它都是Object类型。
l 对于写入list中的元素时,由于因为我们不知道c的元素类型,我们不能向其中添加对象。
l 在写入元素时,唯一的例外是null,它是所有类型的成员
//? extends A:相当于小于等于,可以放A和A的子类,所以在接收的时候,可以使用A或A的父类进行接收 G<? extends A> 可以作为G<A>和G<B>的父类,其中B需要是A的子类 //? super A:相当于大于等于,可以放A和A的父类,所以,在接收的时候,也只可以使用Object类型接收 G<? super A> 可以作为G<A>和G<B>的父类,其中B需要是A的父类
? extends A:不可以进行写入,因为写入的类型无法确定,继承关系的限制。
? super A:可以写入A以及A的子类,因为其代表A或A的父类,所以父类肯定可以指向子类
Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
==框架 = 反射 + 注解 + 设计模式。==
1.类的加载过程:
程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。
接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。
2.换句话说,Class的实例只对应着加载到内存中的一个运行时类。
3.加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。
4.一个加载的类在 JVM 中只会有一个Class实例
方式一:调用运行时类的属性:.class
Class class1 = 类名.class;
方式二:通过运行时类的对象,调用getClass()
Person p1 = new Person(); Class class1 = p1.getClass();
方式三:调用Class的静态方法:forName(String classPath),可能抛出ClassNotFoundException异常
Class class1 = Class.forName("路径");
方式四:使用类的加载器
ClassLoader cl = this.getClass().getClassLoader(); Class clazz4 = cl.loadClass("类的全类名");
方式一:new + 构造器
方式二:要创建Xxx类的对象,可以考虑:Xxx、Xxxs、XxxFactory、XxxBuilder类中查看是否有静态方法的存在。可以调用其静态方法,创建Xxx对象。
方式三:通过反射
//可以通过Class对象的getPackage()方法进行获取 Class c = 类名.class; Package p = c.getPackage();
数组的Class实例中,只要类型和维度一样,那么两个Class实例相等
int[] a = new int[10]; int[] b = new int[100]; a.class == b.class //结果为true
Class<Person> clazz = Person.class; Person obj = clazz.newInstance();
newInstance():调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。
使用此方法,要求:
==不是!==只要在操作的时候明确的调用类中的构造器,并将参数传递进去之后,才可以实例化操作。
1)通过Class类的getDeclaredConstructor(Class … parameterTypes)
取得本类的指定形参类型的构造器
2)向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。
3)通过Constructor实例化对象。
//一、获取类的Class实例、并获得运行时类的实例 Class clazz = Person.class; Person p = (Person) clazz.newInstance(); //二、获取需要调用的方法,getDeclaredMethod(): //参数1 :指明获取的方法的名称 参数2:指明获取的方法的形参列表 Method show = clazz.getDeclaredMethod("show", String.class); //三、保证当前方法可以访问 show.setAccessible(true); //四、调用方法的invoke() //参数1:方法的调用者 参数2:给方法形参赋值的实参 //invoke()的返回值即为对应类中调用的方法的返回值。 Object returnValue = show.invoke(p,"CHN");
Class clazz = Person.class; //创建运行时类的对象 Person p = (Person) clazz.newInstance(); //1. getDeclaredField(String fieldName):获取运行时类中指定变量名的属性 Field name = clazz.getDeclaredField("name"); //2.保证当前属性是可访问的 name.setAccessible(true); //3.设置指定对象的此属性值 name.set(p,"Tom"); //4、获取指定对象的此属性值 System.out.println(name.get(p));
Class clazz = Person.class; //1.获取指定的构造器 //getDeclaredConstructor():参数:指明构造器的参数列表 Constructor constructor = clazz.getDeclaredConstructor(String.class); //2.保证此构造器是可访问的 constructor.setAccessible(true); //3.调用此构造器创建运行时类的对象,此时才会进行类加载 Person per = (Person) constructor.newInstance("Tom");
关于setAccessible方法的使用
什么是注解
注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
==注释==:用来解释说明,是给我们程序员看的
==注解==:用来解释说明,是给程序看的
编写文档:通过代码里标识的注解生成文档,如:生成文档doc文档
代码分析:通过代码里标识的注解对代码进行分析,如:使用反射
编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查,如:@Override
@注解名 public void show() {} @注解名 int i; @注解名 class user { public void show() {} }
定义好的注解,常用的有:
@Override:检测是否是方法的重写
@Deprecated:检测方法是否已过时
@SuppressWarnings:压制警告
注意:一般情况下,我们会在@SuppressWarnings中的参数里传递"all",代表压制所有情况的警告
注解只有成员变量,没有方法体
public @interface 注解名{ public String 属性名(); public String 属性名() default 默认值; String 属性名() default 默认值; //注解的权限修饰符可以省略,默认也是public }
public interface MyAnnotation1 extends java.lang.annotation.Annotation {}
通过反编译可以了解到,注解的本质是默认继承java.lang.annotation.Annotation接口的接口
由于注解本质就是接口,所以在接口可以定义抽象方法,在接口中叫抽象方法,在注解中就叫做属性
在注解中,属性的返回值类型可以写以下几种数据类型:'
用来标记注解的注解
用于指定注解可以保留多长时间
RetentionPolicy.SOURCE:如果该注解的值为这个,表示被该元注解所标注的注解生存时长只会保留在.java的时候
RetentionPolicy.CLASS:如果该注解的值为这个,表示被该元注解所标注的注解生存时长只保留在.java的时候和字节码文件的时候,但不会进内存中
RetentionPolicy.RUNTIME:如果该注解的值为这个,表示被该元注解所标注的注解生存时长保留在.java的时候和字节码文件的时候,也会进内存JVM中
指定注解用于修饰哪些程序元素
ElementType.TYPE:如果target注解的值为这个,就说明被target注解所标注的注解只能放在类、接口的上面
ElementType.FIELD:如果target注解的值为这个,就说明被target注解所标注的注解只能放在属性的上面
ElementType.METHOD:如果target注解的值为这个,就说明被target注解所标注的注解只能放在方法的上面
在使用javadoc命令生成文档后,被该元注解修饰了将会消失
指定注解具有继承性
就是指使用反射技术,获取注解的属性值。
注意:如果想要使用反射来解析注解,前提条件,该注解一定要有元注解@Retention,而且其值一定要为RetentionPolicy.RUNTIME,否则属性不进内存,会有空指针异常
//获取运行时类的Class对象 Class cls = 带有注解的类的类名.getClass(); //判断是否有注解 if(cls.isAnnotationPresent(注解.class)){ //获取注解对象 注解 a = (注解)cls.getAnnotation(注解.class); //获取属性值 a.属性名(); }
//获取运行时类的Class对象 Class cls = 带有注解的类的类名.getClass(); Method m = cls.getMethod("方法名"); //判断是否有注解 if(m.isAnnotationPresent(注解.class)){ //获取注解对象 注解 a = (注解)m.getAnnotation(注解.class); //获取属性值 a.属性名(); }
用来加载.class文件
可以将本地磁盘上的字节码文件加载进JVM的方法区,生成字节码文件对象
//1、由new关键字创建一个类的实例,在由运行时刻用 new 方法载入 Person person = new Person(); //2、使用Class.forName(),通过反射加载类型,并创建对象实例 Class clazz = Class.forName("Person"); Object person =clazz.newInstance(); //3、使用某个ClassLoader实例的loadClass()方法,通过该 ClassLoader 实例的 loadClass() 方法载入。应用程序可以通过继承 ClassLoader 实现自己的类装载器。 Class clazz = ClassLoader.getSystemClassLoader().loadClass("Person"); Object person =clazz.newInstance();
==引导类加载器(根类加载器、启动类加载器,BootstrapClassLoader)==
==扩展类加载器(ExtClassLoader)==
==系统类加载器(AppClassLoader)==
简单理解,就是引导类加载器加载Java的类,扩展类加载器加载导入的外部类,系统类加载器加载自定义类
上层:引导类加载器
中层:扩展类加载器
下层:系统类加载器
1.先获取类的字节码文件对象 Class clazz = Person.class; 2.通过字节码文件对象获取类加载器对象 ClassLoader classLoader = clazz.getClassLoader(); 3.获取类加载器的上一层类加载器 ClassLoader classLoader1 = classLoader.getParent();
// 在src的路径下,有一个jdbc.properties的配置文件 Properties p = new Properties(); p.load(new FileInputStream("src/jdbc.properties")); String driver = p.getProperty("driver"); System.out.println(driver);
// 获取类的字节码文件对象 Class clazz = Demo2.class; // 获取类加载器的对象 ClassLoader classLoader = clazz.getClassLoader(); // 使用类加载器对象读取配置文件 InputStream is = classLoader.getResourceAsStream("jdbc.properties"); Properties p = new Properties(); p.load(is); String driver = p.getProperty("driver"); System.out.println(driver); //注意:使用类加载器的方式读取配置文件,默认的根目录是相对于classpath目录
// 获取类的字节码文件对象 Class clazz = Demo1.class; // 获取类加载器对象 ClassLoader classLoader = clazz.getClassLoader(); // 使用类加载器对象读取配置文件 URL url = classLoader.getResource("jdbc.properties"); String path = url.getPath(); FileInputStream fis = new FileInputStream(path); Properties p = new Properties(); p.load(fis); String driver = p.getProperty("driver"); System.out.println(driver); /注意:使用类加载器的方式读取配置文件,默认的根目录是相对于classpath目录