⭐代码和思维导图详见仓库:Sivan_Xin的代码仓库—Java语法。如果觉得有帮助的话,可以帮作者点一个star⭐哦~
🎉 关于Java语法部分的知识体系主要分三篇文章进行讲解:
🎉【基础语法篇】【面向对象篇】【高级语法篇】
🎉大家可以在日常的学习生活中进行参考,笔记内容并不是绝对权威,所以请保持自己独立思考的能力。
本文共16000字,读完约需30分钟。
- 思维导图以其中一张为例:
多线程
基本概念
- 程序、进程、线程
程序:静态的代码块。
进程:进程作为资源分配的单位,也就是运行起来的程序,存在生命周期。
线程:进程进一步细化为线程,是程序内部的一条执行路径。若一个进程同一时间并行执行多个线程,就是支持多线程。每个进程有不同的内存区域,而线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器。
多个线程可以对内存进行操作,这时就可能会出现一些问题。一个Java程序至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。
并行与并发:
并行:多个CPU同时执行多个任务。
并发:一个CPU同时执行多个任务。比如:秒杀、多个人做同一件事。
问:何时需要多线程?
- 程序需要同时执行两个或多个任务。
- 程序需要实现一些需要等待的任务,如用户输入、文件读写等。
- 需要后台运行的程序。
线程的创建和使用
创建多线程方式一:使用Thread类。
1. 创建一个继承Thread类的子类。
2. 重写Thread类的run方法 -->将线程执行的操作声明在run中。
3. 创建Thread类的子类的对象。
4. 通过对象调用start方法。
start方法的作用:1. 启动线程 2. 调用run方法。
创建多线程方式二:实现Runnable接口。
1. 实现Runnable接口
2. 重写run()方法
3. 创建实现接口的类的对象
4. 使用Thread,将对象传入构造器中,new新的线程对象
5. 使用线程对象调用start()方法
- 两者比较
开发中:优先选择实现Runnable接口的方式。
原因:- 没有类的单继承性的局限性
- Runnable更适合来处理多个线程有共享数据的情况。
相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
- Thread类中的常用方法:
1. start():启动当前线程;调用当前线程的run()
2. run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
3. currentThread():静态方法,返回执行当前代码的线程
4. getName():获取当前线程的名字
5. setName():设置当前线程的名字
6. yield():释放当前cpu的执行权
7. join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。
8. stop():已过时。当执行此方法时,强制结束当前线程。
9. sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态。
10. isAlive():判断当前线程是否存活
- 线程的调度
时间片策略; 抢占式:高优先级抢占低优先级的线程。
- 线程的优先级
-
线程的三大优先级
MAX_PRIORITY
:10MIN _PRIORITY
:1NORM_PRIORITY
:5 -->默认优先级
高优先级的线程要抢占低优先级线程cpu的执行权。 -
如何获取和设置当前线程的优先级:
getPriority()
:获取线程的优先级
setPriority(int p)
:设置线程的优先级
说明: 只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。
线程的通信
线程与线程之间可以进行通信交互。
wait()
方法:使当前线程阻塞,并将锁释放。与sleep不同,sleep只是将线程休眠,并没有释放锁。
notify()
方法:释放正在wait()的线程。
notifyAll()
方法:释放所有正在wait()的线程,按照优先级释放。
- 注意
- 这三个方法只能出现在同步代码块或同步方法中。
- 调用者必须是同步代码块或同步方法中的同步监视器。
- 三者是定义在java.lang.Object类中的。
线程的生命周期
两张图片:
Thread.State枚举类中定义了线程的几种状态:
- 新建 :当一个Thread类或子类对象被声明创建时。
- 就绪:处于新建状态的线程被start()后,进入线程队列等待CPU时间片,此时已经具备运行条件。
- 运行:线程被调度,run()方法定义了线程的操作和功能。
- 阻塞:线程被人为挂起或执行输入输出操作时。
- 死亡:线程完成工作或线程被提前强制性中值。
线程的同步
- 为什么使用线程的同步?
多个线程对账本的共享,会造成操作的不完整性,会破坏数据。
Java中会通过同步机制,解决线程安全问题。
synchronized
- 方式一:同步代码块
synchronized(同步监视器){
//说明:1. 操作共享数据的代码,即为需要被同步的代码。
// 2. 共享数据:多个线程共同操作的变量。
// 3. 同步监视器(锁):任意一个类的对象,但是多个线程必须共用同一把锁。
// 在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
}
- 方式二:同步方法
- 如果操作共享数据的代码完整的声明在一个方法中,不妨将此方法声明为同步的。
- 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
- 非静态的同步方法,同步监视器是:this [使用Runnable]
静态的同步方法,同步监视器是:当前类本身 [使用Thread]
-
同步的好处与局限性
同步解决了线程的安全问题,但是在操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低。 -
线程的死锁问题
不同的线程分别占据对方需要的资源(同步监视器对象)不放弃,就形成了线程的死锁。出现死锁后,不会出现异常,不会出现提示,只是线程都处于阻塞状态,无法继续。
Lock(锁)
从JDK5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。ReentrantLock类实现了Lock接口,在实现线程安全的控制中,比较常用的是ReentrantLock。
JDK5.0新增线程创建方式
- 新增方式一:实现Callable接口
- 相比run()方法,可以有返回值。
- 方法可以抛出异常。
- 支持泛型的返回值。
- 需要借助FutureTask类,比如获取返回结果等。
另外的,Java5提供了Future接口来代表Callable接口里call()方法的返回值,并且为Future接口提供了一个实现类FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口。
- 新增方式二:使用线程池
背景:对于经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池,使用时直接获取,使用完返回池中。
好处:
- 提高响应速度(减少创建新线程所需的时间)
- 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
- 便于线程管理
- 线程池相关API:
ExecutorService
类、Executors
工具类、ThreadPoolExecutor
类。
推荐使用ThreadPoolExecutor
类来创建线程池,避免使用ExecutorService
类造成的OOM。
Java集合框架
概述
Java 集合框架主要包括两种类型的容器:
- 单列集合框架:
Collection
接口,在该接口下还有两个子接口List
和Set
。
其中,List存储有序、可重复元素(动态数组)。Set存储无序、不可重复元素。 - 双列集合框架:
Map
,存储(key—value)映射。
通用语句:ArrayList<E> objectName = new ArrayList<>;
Collection接口
Collection的使用
- 常用API
参考官方文档/代码即可。 - 集合<——>数组
集合—>数组:toArray
方法。
数组—>集合:Arrays.asList
方法。官方文档中是这么介绍的:返回由指定数组支持的固定大小的列表。此方法与Collection.toArray()
结合使用,作为基于数组和基于集合的API之间的桥梁。
补充知识:
- ArrayList,HashSet,HashMap等都可以用sout进行输出。因为在类的方法中重写了toString方法。
- 可变形参
语法:数据类型···变量名。
在Arrays.asList()
方法中传入的是一个可变形参。表示传入的参数可以是一个或多个数/数组。
iterator(迭代器)
iterator可用于迭代实现了Collection接口的集合。
- 迭代器执行原理
我们这里可以把iterator对象看成一个指针。
调用next()
1. 指针下移 2. 返回下移后的元素。
调用hasNext()
往下看还有没有其他元素。
补充:for:each循环遍历集合就是内部调用迭代器来实现的。
子接口
List接口
List接口:存储有序的、可重复的数据——动态数组。
接口的具体实现类:ArrayList
、LinkedList
、Vector。
-
常用API
参考官方文档/代码即可。 -
LinkedList的源码分析
- 使用无参构造器
LinkedList list = new LinkedList()
—> 内部声明了Node类型的first和last属性。 - 添加操作,创建了一个Node对象,存入数据。
- Vector的源码分析
Vector的源码分析:jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。
在扩容方面,默认扩容为原来的数组长度的2倍。
Set接口
主要存储无序的、不可重复的数据,Set中没有新的方法,都是从Collection继承而来的。
向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()。
- 理解无序和不可重复
- 无序性:存储在数据的底层数组中并非按照数组的索引来添加,而是根据数据的哈希值决定。
- 不可重复性:保证添加相同的元素时,使用
equals()
判断不能返回true。
Set的具体实现类:
HashSet、LinkedHashSet、TreeSet
。
-
具体实现类
-
HashSet
Set的主要实现类,线程不安全,可以存储null值。基于哈希表实现,支持快速查找,但不支持有序性操作。并且失去了元素的插入顺序信息,也就是说使用 Iterator 遍历 HashSet 得到的结果是不确定的。 -
LinkedHashSet
HashSet的子类。在添加数据的同时,每个数据还维护了pre和next两个引用。LinkedHashSet的底层是一个数组+双向链表。意义和好处是LinkedHashSet中的元素顺序可以保证,也就是说遍历序和插入序是一致的。 -
TreeSet
向TreeSet中添加数据,要求是相同类的对象。TreeSet的底层使用红黑树。
- TreeSet的两种排序方式
自然排序(需要实现Comparable接口,重写compareTo()方法) 和 定制排序(构造器传参Comparator)
如果使用定制排序,就向TreeSet的构造函数中传入一个Comparator对象。
- 自然排序中,比较两个对象是否相同的标准为:compareTo()返回0。不再是equals()。
- 定制排序中,比较两个对象是否相同的标准为:compare()返回0.不再是equals()。
Map
Map存储双列数据(key—value)(以HashMap为例)
key使用Set存储,无序,不能重复。——>存储自定义的类的对象时,要重写equals和hashCode。
value使用Collection存储可重复,无序的数据。——>存储自定义的类的对象时,要重写equals方法。
Entry用Set容器存储,无序,不可重复。调用put方法时放进去的就是Map.Entry对象。(key和value)
- 具体实现类
- HashMap:是Map的主要实现类,线程不安全,效率高。可以存储null的数据。
- LinkedHashMap:在遍历map元素时,可以按照添加的顺序来遍历 。
- TreeMap:保证按照添加的key—value进行排序,实现排序遍历。此时考虑key的自然排序和定制排序。底层使用红黑树。
- Hashtable:作为古老的实现类,线程安全,效率低。不能存储null的key—value。
- Properties:properties类继承于Hashtable类,实现了Map等接口。常用来处理配置文件。key和value都是String类型。配置文件后缀:properties。
Map常用API
可查阅文档/代码仓库。
Collections工具类
Collections类直接继承于Object接口,是集合类的一个工具类。这个类不能被实例化(私有构造器),所有的方法都是静态的(可通过类名调用)。包含有关集合操作的静态方法,可以实现对各种集合的搜索、排序、线程安全等。
泛型
概述
- 泛型是什么?
元素类型不确定,所以设计一个参数,这个参数叫做泛型。
例如ArrayList< E>
,< E>就是传入一个泛型的意思。
总之,就是通过一个标识,影响方法的参数类型。 - 使用泛型的作用?
1.编译时会进行类型检查,保证数据的安全。
2.避免了类型转换异常出现的情况。 - 使用泛型的注意?
- 使用泛型,不可以用基本数据类型,必须用其包装类。
- 泛型只是一个标签,不一定传入的都是基本数据类型的包装类,也可以是其他类。
- Java集合接口和集合类jdk1.5之后都被修改为带泛型的结构。
- 子类继承父类时,父类指明了泛型类型,子类实例化对象时不需要使用泛型。
- 子类继承父类时,父类没有指明泛型类型,子类实例化对象时需要使用泛型。
- 异常类不能是泛型的。
- 静态方法中不能使用泛型。
- 泛型的嵌套(HashMap构造迭代器):
Map<String,Integer> map = new HashMap<>();
Iterator <Map.Entry<String,Integer>> iterator = map.entrySet().iterator();
while(iterator.hasNext()){
Map.Entry<String, Integer> e = iterator.next();
String key = e.getKey();
Integer value = e.getValue();
System.out.println(key + "----" + value);
}
自定义泛型类、泛型方法、泛型继承
- 自定义泛型程序举例:
//定义带泛型的类
public class Student<T> {
int age;
String name;
T describe;
public Student(int age, String name, T describe) {
this.age = age;
this.name = name;
this.describe = describe;
}
}
//1. 继承父类时,子类指定泛型的类型,此时子类不是泛型。例:
public class Generic extends fatherGeneric<String>
//接口类似。如果一个类实现的接口给出了具体的泛型,该类不需要写出泛型,反之该类需要给出泛型。
class class -name <type-param-list> implements interface -name <type-arg-list> {
//2. 继承父类时,子类没有指定泛型的类型,此时子类是泛型。
public class Generic <T>extends Student<T>{
//泛型类的构造器不用加<>
public Generic(int age, String name, T describe) {
super(age, name, describe);
}
//泛型方法:
//泛型方法与所在类是否是泛型无关。
//泛型方法可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。
public static <E> List toArray(E []e){
List<E> list=new ArrayList<>();
for(E e1 :e){
list.add(e1);
}
return list;
}
//静态方法中,不能使用泛型。泛型是在实例化对象的时候(调用构造器)使用的,
// 而静态方法在加载类的时候就优先加载了(静态优先,构造随后)。
public static int Demo(T t){ //错误
}
public static void main(String[] args) {
//实例化对象时,要使用泛型。
Generic<String> generic=new Generic<>(12,"Wang","good");
List<Integer> list=Generic.toArray(new Integer[]{1,2,3});
System.out.println(list);
}
}
- 继承中的泛型
- 虽然类A是类B的父类,但是G< A> 和G< B >二者不具备子父类关系,二者是并列关系。
例如:
List<Object> list1 = null;
List<String> list2 = new ArrayList<>();
//编译不通过
// list1 = list2;
- 类A是类B的父类,A
和B 二者是子类和父类的关系。
例如:
List<String> list1 = null;
ArrayList<String> list2 = null;
//编译通过
list1 = list2;
泛型的通配符
- 通配符
类A是类B的父类,G< A>和G< B>是没有关系的,二者共同的父类是:G<?>
//通配符的使用举例
ArrayList<Object> list=new ArrayList<>();
ArrayList<String> arrayList=new ArrayList<>();
ArrayList<?> a=new ArrayList<>();
//G<?>是G<A> G<B>的父类,故可以父类引用指向子类对象。
a=list;
//添加(写入):对于Arraylist<?>就不能向其内部添加数据。
//查找(读取):对于Arraylist<?>可以读取数据,返回Object类型。
-
有限制条件的通配符
<? extends Person>
表示?可以接受小于等于Person的类。(-无穷 ,Person],例如Student等…<? super Person>
表示?可以接受大于等于Person的类。[Person,+无穷),例如Object等… -
添加操作和查找操作
添加操作(以List.add为例)
当执行添加操作时,只可以添加小于等于当前区间的子类。(添加Student、Person;不可以添加Object)
查找操作(以List.get为例)
当执行查找操作时,使用当前区间的最大类来接受值。(用Object类来接收)
IO流
File文件类
作用: Java中使用File类的对象来充当文件夹/文件目录。
注意: 在File类中,只是对文件进行创建、删除、重命名等操作,并未进行读取和修改文件内容(IO流)。
- 文件引用路径:
- 相对路径:相对于当前包的位置,文件在当前包下创建。
- 绝对路径:包含盘符在内的文件或文件目录的路径。
相对路径写在main方法下,会对应着当前工程,进而创建文件在当前整体工程下。并非当前Moudle下。
IO流原理及流的分类
-
IO流原理
IO是input/output的缩写,用于处理设备之间的数据传输。在Java程序中,对于数据的输入/输出操作是以“流”的形式进行的。 -
流的分类
- 四个抽象基类(按操作的数据分):
- 字节流:InputStream/OutputStream。(图片,101010)
- 字符流:Reader/Writer。(文字,字节的基础上编码)
- 按流的角色分为:
- 节点流(文件流):FileInputStream/FileOutputStream、FileReader/FileWriter。
- 处理流:除了节点流的其他流。
节点流
节点流等同于流的基础,所有其他处理流都是建立在节点流的基础之上的。
FileInputStream/FileOutputStream(字节)FileReader/FileWriter(字符)
注意:写入到文件中时(write),文件不需要实际存在,会自动创建。
- 流的通用操作
- 实例化文件对象。
- 创建节点流。
- 创建处理流(如有需求)。
- 具体操作。
- 关闭流。
处理流
缓冲流
BufferedInputStream、BufferedOutputStream/BufferedReader、BufferedWriter。
- 作用
提高流的读取、写入的速度。
提高读写速度的原因:内部提供了一个缓冲区。
使用flush方法可以强制将缓冲区的内容全部写入到输出流。
常用BufferedReader提供的readLine方法来读取一行字符。
转换流
解码(破解):字节——>字符数组、字符串
编码:字符数组——>字节
InputStreamReader:将字节的输入流转换为字符的输入流(解码)
OutputStreamWriter:将字符的输出流转换为字节的输出流(编码)
- 作用
字节流和字符流之间的转换,可以实现文件编码的转换。
对象流
对象的序列化
- 序列化机制
对象序列化机制允许把内存中的Java对象转换成二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。
简单来说,序列化就是对象转换成二进制流,保存在内存中。
反序列化就是将二进制流转换为对象输出。
- Java对象可序列化应满足的条件
- 实现接口:Serializable
- 当前类提供一个全局常量:serialVersionUID(标识作用)。
- 保证当前类的内部也需要实现可序列化(基本数据类型本身就是可序列化的)。
对象流的使用
ObjectInputStream(反序列化)和ObjectOutputStream(序列化)
- 作用
用于存储和读取基本数据类型或对象的处理流,可以把Java中的对象写入到数据中,也可以把对象从数据源中还原。
ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量。
其他流
标准输入输出流
字节流。
System.in
:标准的输入流,默认从键盘输入。
System.out
:标准的输出流,默认从控制台输出。
System.exit(int status)
:终止当前正在运行的Java虚拟机,这个status表示退出的状态码,非零表示异常终止。(可以返回给其他进程的调用者一个调用的返回码,以根据返回码采取不同的策略。)
注意:不管status为何值程序都会退出,和return 相比有不同的是:return是回到上一层,而System.exit(status)是回到最上层。
打印流
字符流。
PrintStream、PrintWriter提供了一系列重载的print() 和 println()。
数据流
字节流。
DataInputStream 和 DataOutputStream
作用:用于读入或写出基本数据类型的变量或字符串。
注意:想要写出文件内容,不可以双击打开,应该使用DataOutputSream来写出文件。
随机存取文件流
直接继承Object类,实现了DataInput和DataOutput接口。
- 构造器和方法
调用RandomAccessFile构造器会传入两个参数,一个是File文件,另一个是mod:
r:只读; rw:既可以读入,也可以写出。
方法:write():文件覆盖的操作; seek(pos):调整当前指针pos的位置。 - 作用
既可以输入,又可以输出的流。
RandomAccessFile:写入文件时,对于原有文件会从头开始覆盖。如果文件不存在,会自动创建文件。可以较方便地实现文件的插入操作。
NIO.2
NIO将以更加高效的方式进行文件的读写操作。
Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套是网络编程NIO。
- Path可以看成是File类的升级版本,实际引用的资源也可以不存在。
- Paths 类提供的静态 get() 方法用来获取 Path 对象。
- Files 用于操作文件或目录的工具类。
- 在以前IO操作都是这样写的:
import java.io.File;
File file = new File(“index.html”); - 但在Java7 中,我们可以这样写:
import java.nio.file.Path;
import java.nio.file.Paths;
Path path = Paths.get(“index.html”);
网络编程
网络编程概述
在开始介绍网络编程之前,先介绍一些基本的网络概念:
IP: 唯一的标识 Internet 上的计算机(通信实体)
IPv4:4个字节组成,如192.168.0.1。
IPv6:16个字节组成。
域名: www.baidu.com等,可以通过域名来访问IP地址。
DNS: 域名解析服务器,可以解析域名。
本地回路地址: 127.0.0.1 域名:localhost
端口号: 标识正在计算机上运行的进程(程序)。被规定一个为16位的整数。(0~65535)。不同的进程应该有不同的端口号。
Socket: 端口号和IP地址组合得出一个网络套接字。
TCP协议: 传输前:采用“三次握手”的方式,点对点通信,形成数据通道,保证可靠。
发送结束:释放已建立的链接,但是效率比较低,“四次挥手”断开链接。
UDP协议: 不需要建立连接,不可靠的链接,发送结束无需释放资源,开销小速度快。
URL: 统一资源定位符,对应着互联网的某一资源地址。
一个URL类就表示一个URL。具体方法见仓库。
- URL格式:
http://localhost:8080/examples/beauty.jpg?username=Tom
协议 主机名 端口号 资源地址 参数列表
拓展:端口分类:
- 公认端口:被预先定义的通信服务占用。比如HTTP占用端口80。(0~1023)
- 注册端口:分配给用户进程或应用程序。比如MySQL占用端口3306。比如tomcat的端口号为8080。(1024~49151)
- 动态/私有端口:(49152~65535)
网络编程目的:
直接或间接通过网络协议与其他计算机实现数据交换,进行通讯。
- 如何准确定位主机,与主机上特定的应用?——通信双方地址:IP和端口号
- 找到主机后如何可靠的进行数据传输?
——网络通信协议:TCP/IP参考模型
- 通信要素1:IP和端口号
InetAddress类的对象,就表示一个IP地址。一个IP地址标识互联网上的一台主机。
具体方法见仓库。 - 通信要素2:网络协议
传输层:TCP/UDP协议。网络层:IP协议。
TCP、UDP、URL网络编程
TCP网络编程练习一:客户端发送信息给服务端,服务端将数据显示在控制台上。
客户端:
- 创建InetAddress对象和Socket对象,指明服务器端(现在是本地)IP地址和端口号。
- 使用Socket对象获取输出流。(输出流:将文字从程序输出到网络。)
- 关闭流。
服务端:
- 创建服务端ServerSocket对象,指明自己端口号。
- 创建Socket对象,ServerSocket对象调用accept()方法接受来自客户端的socket。
- 使用Socket对象获取输入流。(输入流:将网络中的信息读取到程序。)
- 创建输出流。(输出流:进一步将程序中的信息写入到内存中。)
注:这里比较特殊,使用ByteArrayOutputStream流读取文字信息到内存。- 关闭流。
TCP网络编程练习二:客户端向服务端发送一个图片,并且服务端保存图片到本地。
TCP网络编程练习三:从客户端发送文件给服务端,服务端保存到本地。并返回“发送成功”给客户端。
与练习一类似,详情参见代码仓库即可。
UDP网络编程练习:客户端发送信息给服务端,服务端将数据显示在控制台上。
客户端:使用DatagramSocket的方法【send】发送DatagramPacket的packet。
服务端:使用DatagramSocket的方法【receive】接收DatagramPacket的packet。
URL网络编程练习:
- 创建URL对象。
- 打开HttpsURLConnection类的对象的链接。
- 使用HttpsURLConnection类的对象打开连接。
- 使用HttpsURLConnection类的对象获取输入流,将URL资源从网络输入到程序。
- 创建输出流,将URL资源输出到内存中。
Java反射机制
Java反射机制概述
- 初识反射
Java Reflection 被视为动态语言的关键,允许程序在执行期借助于Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性方法(包括声明为私有的属性和方法)。
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。类也是一种Class的实例,可见Java中万事万物皆对象。
体会反射的动态性:在编译时,无法确定具体所需的类的对象,只有在运行时才可以确定,这里就可以用到反射。动态,通俗点来说就是在程序运行时代码可以根据某些条件改变自身结构。 - 与反射相关的API
java.lang.Class :代表一个类
java.lang.reflect.Method:代表类的方法
java.lang.reflect.Field:代表类的成员变量
java.lang.reflect.Constructor:代表类的构造器
获取Class类的实例以及创建运行时类的对象
- 获取Class类的实例的三种方式
- 使用类名+.class
- 通过运行时类的对象,调用getclass()
- 调用Class的静态方法 forName(String classPath)
- 创建运行时类的对象
使用Class类的方法newInstance()
可以创建运行时类的对象。其内部调用了运行时类的空参构造器。
要想此方法正常的创建运行时类的对象,要求:- 运行时类必须提供空参的构造器。
- 空参的构造器的访问权限足够,通常为public。
获取运行时类属性、方法
-
获取一个运行时类的属性操作大体如下:
- 获取Class的实例(使用 .class)。
- 获取运行时类的对象(调用newInstance)。
- 获取指定属性(调用getDeclaredField)。
- 保证当前属性可访问(调用setAccessible)。
- 设置指定对象属性(调用set方法)。
-
常见的第三步的其他操作:
属性:getDeclaredField(String)
—获取指定属性,String为属性名。
方法:getDeclaredMethod(String,参数的Class实例)
—获取指定方法,String为方法名。 -
常见的第五步的其他操作:
属性:set(obj)
—设置对象属性值、get()
—获取对象属性值。
方法:invoke(obj,args)
—表示给方法传参,args就是传递的参数,obj表示该方法属于哪个运行时类的对象。invoke的返回值为方法的返回值。
获取运行时类的完整结构
关于运行时类,我们可以通过一些方法来获取类的结构,比如:
-
获取当前运行时类的属性(Field)——权限修饰符 数据类型 变量名
-
获取当前运行时类的方法(Method)——注解 权限修饰符 返回值类型 方法名 参数类型 异常类型
-
获取当前运行时类的构造器(Constructor)、父类(Superclass)、带泛型的父类(GenericSuperclass)的泛型(ActualTypeArauments)、接口(Interfaces)、所在的包(Package)、类声明的注解(Annotations)。
此内容仅了解即可,不要求掌握,因为在上文调用运行时类的指定结构中用到了。
ClassLoader的理解
- 类的加载
- 程序经过javac.exe命令编译以后,会生成一个或多个字节码文件(.class结尾,一个类就有一个.class文件)。
- 接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中,此过程就称为类的加载。
加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。也可以说,Class的实例就对应着一个运行时类。
- 类的加载过程
当程序使用某个类时,会进行以下初始化:
类的加载——> 类的链接——> 类的初始化
将class文件加载到内存中 ->为类变量分配内存,设置默认初始值 -> 给类变量进行赋值
类加载器的作用:把类装载进内存。
了解类加载器以及读取配置文件的两种造流方式:代码见仓库。
反射的应用:动态代理
- 代理模式
代理模式:代理模式是Java中使用较多的一种设计模式,代理设计就是为其他对象提供一种代理以控制对这个对象的访问,也就是通过代理类来调用其他对象的方法。
应用场景:
安全代理:屏蔽对真实角色的直接访问。
远程代理:通过代理类处理远程方法调用(RMI)。
……
分类:
静态代理:专门针对某一接口来处理的代理。
动态代理:在程序运行时根据需要动态创建目标类的代理对象。
动态代理面向切面编程(AOP)到框架的时候再深入学习。
动态代理的应用:Spring 的 AOP 、加事务、加权限、加日志。
⭐码字不易,求个关注⭐
⭐点个收藏不迷路哦~⭐