导图社区 Delei-Java基础核心(公开)
Java基础的核心内容,主要包含了Java基础知识,部分涉及到源码, 原理,JVM等。
编辑于2022-04-01 23:09:36Java基础核心
基础
面向对象
三大基本特征
封装(Encapsulation)
所谓封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏
一个类就是一个封装了数据以及操作这些数据的代码的逻辑实体
在一个对象内部,某些代码或某些数据可以是私有的,不能被外界访问。通过这种方式,对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分
继承(Inheritance)
让某个类型的对象获得另一个类型的对象的属性的方法
可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展
通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”
继承的过程,就是从一般到特殊的过程
多态(Polymorphism)
指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口
多态应该是一种运行期的状态
动态多态
有类继承或者接口实现
子类要重写父类的方法
父类的引用指向子类的对象
VS 面向过程
面向过程
“面向过程”(Procedure Oriented)是一种以过程为中心的编程思想,是一种自顶而下的编程模式
把问题分解成一个一个步骤,每个步骤用函数实现,依次调用即可
面向对象
面向对象程序设计在某种程度上通过强调可重复性解决了这一问题
面向对象是一种将事务高度抽象化的编程模式
将问题分解成一个一个步骤,对每个步骤进行相应的抽象,形成对象,通过不同对象之间的调用,组合解决问题。
要把属性、行为等封装成对象,然后基于这些对象及对象的能力进行业务逻辑的实现
基础数据类型
种类
常量池
[-128,127]
包装类Byte、Short、Integer、Long默认创建了相应类型的缓存数据
技术规范JLS7 5.1.7
性能和资源之间的权衡,例如IntegerCache类的实现
[0,127]
包装类Character
源码
查看class文件字节码,发现调用了相应包装内的valueOf方法
部分valueOf方法直接规定了缓存范围大小
部分调用了相应包装内的Cache类,例如IntegerCache,ShortCache等
IntegerCache中,最大值high可以修改,默认是127
虚拟机参数:java.lang.Integer.IntegerCache.high
Integer i=10相当于Integer i = Integer.valueOf(10);
自动装箱与拆箱
概念
装箱(autoboxing)
将基本类型用它们对应的引用类型包装起来
拆箱(unboxing)
将包装类型转换为基本数据类型
自动
基础数据类型于相对应的包装类进行运算时,编译器会自动进行转换
触发情况
进行=赋值操作时,可能存在装箱或拆箱
进行+,-,*,/缓和运算时,进行拆箱
进行大小<,>,==比较运算时,进行拆箱
进行equals比较比较时,进行装箱
部分集合类(如ArrayList,HashMap等)在添加基础数据类型时,进行装箱
实现
调用相应包装类的valueOf方法进行装箱
调用相应包装类的**Value方法进行拆箱
问题
包装对象的数值比较,不能简单的使用 ==,虽然 -128 到 127 之间的数字可以,但是这个范围之外还是需要使用 equals 比较
如果一个 for 循环中有大量拆装箱操作,会浪费很多资源
三目运算NPE
Integer a = 1;Integer b = 2;Integer c = null;Boolean flag = false; // a*b的结果时int类型,那么c会强制拆箱为int类型,抛出NPEInteger result = flag ? a * b : c;
由于自动拆箱,如果包装类对象为 null ,那么自动拆箱时就有可能抛出 NPE
条件运算符是右结合的,也就是说,从右向左分组计算
当第二位和第三位表达式都是包装类型的时候,该表达式的结果才是该包装类型,否则,只要有一个表达式的类型是基本数据类型,则表达式得到的结果都是基本数据类型。如果结果不符合预期,那么编译器就会进行自动拆箱
抽象类和接口
抽象类(Abstract Class)
定义
使用abstract 修饰符修饰的类
如果一个类中没有包含足够多的信息来描述一个具体的对象,这样类即为抽象类
特点
抽象类无法用来创建对象,无法实例化
需要子类继承抽象类才能实例化其子类
类中的抽象方法只需声明,不实现,由继承它的子类来实现
接口(Interface)
定义
接口是抽象类的延伸
JDK8之前可以看成是一个完全抽象的类,JDK8开始支持接口有默认的方法实现
区别
抽象类满足里氏替换原则,即子类对象必须能够替换掉所有父类对象
接口并不要求接口和实现接口的类满足里氏替换原则
一个类可以实现多个接口,但不能继承多个抽象类
接口的字段只能是static和final类型,抽象类的字段无此类限制
接口的成员只能是public,抽象类成员可以有多种访问权限
抽象类可以有构造方法,接口中不能有构造方法
抽象类中的抽象方法类型可以是任意修饰符,Java 8 之前接口中的方法只能是 public 类型,Java 9 支持 private 类 型
语法糖(Syntactic Sugar)
概念
语法糖(Syntactic Sugar),也称糖衣语法,是由英国计算机学家 Peter.J.Landin 发明的一个术语,指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。简而言之,语法糖让程序更加简洁,有更高的可读性
语法糖的存在主要是方便开发人员使用。但其实,Java虚拟机并不支持这些语法糖。这些语法糖在编译阶段就会被还原成简单的基础语法结构,这个过程就是解语法糖
解析语法糖实现
javac命令在com.sun.tools.javac.main.JavaCompiler的源码中,compile()中有一个步骤就是调用desugar(),这个方法就是负责解语法糖的实现
常见的语法糖
switch 支持 String
编译后case的比较实际调用的String 的hashCode()方法
为了避免 hashcode 冲突,内部调用 equals 方法进行二次比对,保证安全检查
自动装箱与拆箱
装箱时编译后为调用相应包装类的 valueOf 方法
方法变长参数
数组
for-each
for-each的实现原理其实就是使用了普通的for循环和迭代器
try-with-resource
编译器实现关闭资源的操作
Object 类
Object 类是一个特殊的类,是所有类的父类
方法
public final native Class<?> getClass()
native方法,用于返回当前运行时对象的Class对象,使用了final关键字修饰,故不允许子类重写
public native int hashCode()
native方法,用于返回对象的哈希码,主要使用在哈希表中,比如HashMap
public boolean equals(Object obj) { return (this == obj); }
用于比较2个对象的内存地址是否相等
protected native Object clone() throws CloneNotSupportedException
naitive方法,用于创建并返回当前对象的一份拷贝
一般情况下,对于任何对象 x,表达式 x.clone() != x 为true,x.clone().getClass() == x.getClass() 为true
Object本身没有实现Cloneable接口,所以不重写clone方法并且进行调用的话会发生CloneNotSupportedException异常
public String toString()
返回类的名字 @实例的哈希码的16进制的字符串
其中 @ 后面的数值为散列码的无符号十六进制表示
建议Object所有的子类都重写这个方法
public final native void notify()
native方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个
public final native void notifyAll()
native方法,并且不能重写。跟notify一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程
public final native void wait(long timeout) throws InterruptedException
native方法,并且不能重写。暂停线程的执行
注意:Thread#sleep方法没有释放锁,而wait方法释放了锁 。timeout是等待时间
public final void wait(long timeout, int nanos) throws InterruptedException
多了nanos参数,这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。 所以超时的时间还需要加上nanos毫秒
public final void wait() throws InterruptedException
跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念
protected void finalize() throws Throwable { }
实例被垃圾回收器回收的时候触发的操作
@since 9 后标记为@Deprecated
深拷贝和浅拷贝
深拷贝(Deep Clone)
拷贝对象和原始对象的引用类型引用不同的对象
每个引用类型属性内部都重写clone方法
利用序列化,实现Serializable接口,序列化产生的是两个完全独立的对象
浅拷贝(Shallow Clone)
拷贝对象和原始对象的引用类型引用同一个对象
实现Cloneable接口,重写clone方法
重载和重写
定义
重载(Overload)
同一个类中,函数或者方法有同样的名称,但是参数列表不相同的情形,这样的同名不同参数的函数或者方法之间,互相称之为重载函数或者方法
条件
被重载的方法必须改变参数列表(例如顺序,个数等)
被重载的方法可以改变返回类型,访问修饰符
被重载的方法可以声明新的或更广的检查异常
方法能够在同一个类中或者在一个子类中被重载
重写(Override)
在Java的子类与父类中有两个名称、参数列表都相同的方法的情况。由于他们具有相同的方法签名,所以子类中的新方法将覆盖父类中原有的方法
条件
参数列表,返回类型必须完全与被重写方法相同
访问级别的限制性一定不能比被重写方法的强
重写方法一定不能抛出新的检查异常或比被重写的方法声明的检查异常更广泛的检查异常
不能重写被标示为final的方法
如果不能继承一个方法则不能重写这个方法
比较
重载是一个编译期概念、重写是一个运行期间概念
重载遵循所谓“编译期绑定”,即在编译时根据参数变量的类型判断应该调用哪个方法
重写遵循所谓“运行期绑定”,即在运行的时候,根据引用变量所指向的实际对象的类型来调用方法
因为在编译期已经确定调用哪个方法,所以重载并不是多态
重写是多态。重载只是一种语言特性,是一种语法规则,与多态无关,与面向对象也无关
比较相等
== 和 equals
==
对于基础类型,比较的是值是否相同,基本类型没有 equals() 方法
对于引用类型,比较的是引用是否相同
equals
超级父类 Object.equals 本质上就是 ==
public boolean equals(Object obj) { return (this == obj); }
类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象
类覆盖了 equals() 方法
一般都需要覆盖 equals() 方法来比较两个对象的内容是否相等;若它们的内容相等,则返回 true (即认为这两个对象相等)
例如String 和 Integer 重写了 equals 方法,把它变成了值比较
hashCode()和equals()
规则说明
一个类如果覆写了equals(),就必须覆写hashCode()
equals()相等的两个对象他们的hashCode()肯定相等,也就是用equals()对比是绝对可靠的
hashCode()相等的两个对象他们的equals()不一定相等,也就是hashCode()不是绝对可靠的
equals()
自反性
x.equals(x); // true
对称性
x.equals(y) == y.equals(x); // true
传递性
if (x.equals(y) && y.equals(z)) x.equals(z); // true;
一致性
多次调用 equals() 方法结果不变
与 null 的比较
对任何不是 null 的对象 x 调用 x.equals(null) 结果都为 false
x.equals(null); // false;
其他
只要生成一个hash值进行比较,效率很高,但是 hash 值会有冲突可能(不同对象 hash 值相同,hash 冲突等)
使用相关 Hash 结构的类,例如HashMap,HashTable,HashSet 等时
Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须重写这两个方法
关键字
static
简介
方便在没有创建对象的情况下来进行调用(方法/变量)
被static修饰的成员变量和成员方法独立于该类的任何对象。也就是说,它不依赖类特定的实例,被类的所有实例共享
用于修饰类的成员方法、类的成员变量,编写static代码块等
修饰成员方法
可以直接通过类名调用,任何的实例也都可以调用
静态方法是没有this的,因为它不依附于任何对象
在静态方法中不能访问类的非静态成员变量和非静态成员方法
非静态成员方法/变量都是必须依赖具体的对象才能够被调用
在非静态成员方法中是可以访问静态成员方法/变量
static方法独立于任何实例,因此static方法必须被实现,而不能是抽象的abstract
static代码块
表示静态代码块,在类中独立于类成员的static语句块,可以置于类中的任何地方
在类中独立于类成员的static语句块,可以有多个。如果有多个,JVM将按照它们在类中出现的先后顺序依次执行它们
每个代码块只会在类加载的时候执行一次
可以用来优化程序性能
静态内部类
成员内部类可以看做外部类中的一个成员,所以可以用static修饰,这种用static修饰的内部类我们称作静态内部类,也称作嵌套内部类.
要创建嵌套类的对象,并不需要其外围类的对象
不能使用外部类的非static成员变量和成员方法
即使没有外部类对象,也可以创建静态内部类对象,而外部类的非static成员必须依赖于对象的调用,静态成员则可以直接使用类调用,不必依赖于外部类的对象
静态导入
从Java 5开始引入静态导入语法(import static)
用import static代替import静态导入包
是为了减少字符输入量,提高代码的可阅读性,以便更好地理解程序
可以导入某个类里的静态方法,可以使用通配符*,也可以指定导入的某个静态方法
注意事项
提防含糊不清的命名static成员
方法名是具有明确、清晰表象意义的工具类
可以在static对象引用、常量(记住,它们是static 或final)和static方法上进行静态导入
与final一起使用
修饰变量
一旦赋值就不可修改,并且通过类名可以访问
实质上类似为"全局变量"
修饰方法
不可覆盖,并且可以通过类名直接访问
final
简介
用于表示所修饰的内容一旦赋值之后就不会再被改变
当final修饰的是一个基本数据类型数据时, 这个数据的值在初始化后将不能被改变
当final修饰的是一个引用类型数据时, 也就是修饰一个对象时, 引用在初始化后将永远指向一个内存地址, 不可修改. 但是该内存地址中保存的对象信息, 是可以进行修改的
可以修饰类、方法和变量(包括成员变量和局部变量)
修饰变量
类成员变量
必须要在静态初始化块中指定初始值或者声明该类变量时指定初始值,而且只能在这两个地方之一进行指定
实例变量
必要要在非静态初始化块,声明该实例变量或者在构造器中指定初始值,而且只能在这三个地方进行指定
final修饰的变量会指向一块固定的内存, 这块内存中的值不能改变
修饰方法
方法不能被子类重写
把方法锁定,以防任何继承类修改它的含义
在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化
修饰类
该类不能被继承
该类所有成员方法都将被隐式修饰为final方法
该类的对象在堆中分配内存后地址不可变
修饰引用
引用无法改变,对于基本类型,无法修改值
对于引用,虽然不能修改地址值,但是可以对指向对象的内部进行修改
字符串
全局字符串池(String Pool)
字符串字面量
String string1 = "abcd"
字符串对象
String string2 = new String("abcd")
图示
String Pool存的是引用值而不是具体的实例对象,具体的实例对象是在堆中开辟的一块空间存放的
实现原理
在HotSpot VM里实现的String Pool功能的是一个StringTable类,它是一个哈希表,里面存的是驻留字符串(也就是我们常说的用双引号括起来的)的引用(而不是驻留字符串实例本身)
String类
特点
Immutable不可变类,不可被继承,方法不能被重写,属性不能修改
不可变好处
可以缓存hash值,只需进行一次hash计算
String Pool的需要
安全性
经常作为参数,String 不可变性可以保证参数不可变
线程安全
不可变性天生具备线程安全,可以在多个线程中安全地使用
结构
类
final class String
类不可变,不可被继承
变量
final byte[] value
在JDK8中使用的是char[]来存储
final byte coder
编码格式
@since 9
int hash
hash code
static final boolean COMPACT_STRINGS
是否开启字符串压缩
在static 静态初始化块中初始化为 true
@Native static final byte LATIN1 = 0
@Native static final byte UTF16 = 1
操作
native String intern()
注释描述:如果常量池中存在当前字符串, 就会直接返回当前字符串. 如果常量池中没有此字符串, 会将此字符串放入常量池中后, 再返回
native底层调用StringTable的intern方法
boolean isLatin1()
JDK11中,因引入 coder 后,与JDK8不同的是,很多方法都先调用isLatin1()判断是否为LATIN-1
COMPACT_STRINGS && coder == LATIN1
int hashCode()
调用isLatin1()判断是否为LATIN-1
如果是,调用StringLatin1.hashCode方法
如果不是,调用StringUTF16.hashCode
底层hashCode 的相关逻辑基本一致
遍历byte[],使用31作为乘数进行计算
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
为什么是31
31 是一个奇质数,如果选择偶数会导致乘积运算时数据溢出,因为乘二相当于移位运算
在二进制中,2个5次方是32,那么也就是 31*i==(i<<5)-i。这主要是说乘积运算可以使用位移提升性能,同时目前的 JVM 虚拟机 也会自动支持此类的优化
哈希值冲突率
boolean equals()
判断对象instanceof String
JDK11 增加了判断对象的 coder 是否相同,调用coder()
JDK11根据 isLatin1方法判断来调用相应编码类的equals方法
判断byte[]的 length 是否相同
for循环比较两个对象的 byte 数组是否相同
JDK8中类似,只是没有编码判断,直接用的 while 循环遍历 char[]比较
int length()
JDK8
value.length
JDK11(@since 9)
value.length >> coder()
如果是LATIN-1就是右移0位,如果是UTF-16就右移1位
byte coder()
COMPACT_STRINGS ? coder : UTF16;
boolean isEmpty()
判断字符串是否为空
无法判断 null,会抛出 NPE
value.length==0
存储的byte数组大小
boolean isBlank()
@since 11新增
判断字符串是空白、空格
根据 isLatin1方法判断来调用相应编码类的indexOfNonWhitespace(byte[] value)
过滤空格" ","\t"等
无法判断 null,会抛出 NPE
String trim()
调用isLatin1()判断是否为LATIN-1
如果是,调用StringLatin1.trim方法
该方法去掉的不仅仅是空格,而是所有不大于 32 的字符
如果不是,则和 JDK8一样,只是调用了StringUTF16.trim,逻辑与 JDK8时一致
其他
coder编码
编码格式的标识,使用LATIN1还是UTF-16
LATIN1
ISOLatin-1,是ISO-8859-1的别名
ISO-8859-1编码是单字节编码,向下兼容ASCII
java.lang.StringLatin1
UTF16
Unicode标准
UTF-16也是一种变长的编码方式
UTF-16使用的是1个到2个16bits来表示相应的字符
java.lang.StringUTF16
value 字段计算方式
所有字符都小于 0xFF 时,采用 Latin1 Character Encoding 来保存 Unicode code point,也就是每个字符都用一个 byte 来保存
0xFF是16进制的表达方式,F是15;十进制为:255,二进制为:1111 1111
否则采用 UTF-16 Character Encoding 来保存,也就是每个字符都用 2 个或者 4 个 byte 来保存
字符串优化
压缩字符串
JEP 254: Compact Strings
http://openjdk.java.net/jeps/254
由char变为byte
减少空间,char为2个字节,byte只需1个字节
判断是不是只有Latin-1字符,如果是则按照1字节/字符,如果不是则按照2字节/字符(UTF-16编码),提高内存使用率
经过大量的 dump 分析等实践总结,大部分的字符串均为英文数字组合,使用 byte 存储能够减少空间占用,复杂字符串(例如中文等)则选择UTF16编码来存储
可以使用参数-XX:-CompactStrings来禁用紧凑字符串功能,默认为开启,禁用即强制使用UTF-16编码
字符串连接
JEP 280: Indify String Concatenation
http://openjdk.java.net/jeps/280
JDK9之前
通过反编译class,字符串拼接使用StringBuilder.append
JDK9开始
通过反编译class,开始使用InvokeDynamic指令,它会调用java.lang.invoke.StringConcatFactory 类中的makeConcatWithConstants方法
makeConcatWithConstants策略,java.lang.invoke.StringConcatFactory类内部枚举Strategy
BC_SB
字节码生成器,调用java.lang.StringBuilder
BC_SB_SIZED
字节码生成器,调用java.lang.StringBuilder
估计所需的存储空间
BC_SB_SIZED_EXACT
字节码生成器,调用java.lang.StringBuilder
精确计算所需的存储空间
MH_SB_SIZED
基于MethodHandle的生成器,最终调用java.lang.StringBuilder
尝试估计所需的存储空间
MH_SB_SIZED_EXACT
基于MethodHandle的生成器,最终调用java.lang.StringBuilder
精确计算所需的存储空间
MH_INLINE_SIZED_EXACT
基于MethodHandle的生成器,从参数构造自己的byte[]数组。它精确地计算所需的存储空间。
默认策略:final Strategy DEFAULT_STRATEGY = Strategy.MH_INLINE_SIZED_EXACT
长度限制
编译期
字符串字面量直接定义过长的字符串
constant string too long
javac 编译
Javac中定义了MAX_STRING_LENGTH = 0xFFFF;即转成十进制为65535
Javac 源码中做的限制是((String)constValue).length() < Pool.MAX_STRING_LENGTH)
即最大允许为65534
字符串常量池中
CONSTANT_Utf8_info { u1 tag; u2 length; u1 bytes[length];}
u2是无符号的16位整数,因此理论上允许的的最大长度是2^16=65536
java class 文件是使用一种变体UTF-8格式来存放字符的,null 值使用两个字节来表示,因此只剩下 65536-2 = 65534个字节
在非 Javac 编译器中,无此限制可以编译通过,比如 Eclipse 的 JDT 编译器
运行期
最大为Integer.MAX_VALUE,约为4G 大小
String、StringBuffer、StringBuilder类
StringBuffer、StringBuilder类结构
两者均extends AbstractStringBuilder
两者均为final修饰的类
大部分的方法实现调用父类AbstractStringBuilder的相关方法实现
内部数据存储使用AbstractStringBuilder的 byte[]
AbstractStringBuilder
结构
类
abstract class AbstractStringBuilder
成员变量
byte[] value
存储数据
byte coder
编码方式
@since 9
int count
操作
AbstractStringBuilder append(String str)
处理扩容,调用ensureCapacityInternal方法
putStringAt方法,将 str 放入数组中
void ensureCapacity(int minimumCapacity)
最终调用void ensureCapacityInternal(int minimumCapacity)
调用newCapacity处理扩容
调用 Arrays.copyOf 完成数据处理
int newCapacity(int minCapacity): 扩容
int newCapacity = (oldCapacity << 1) + 2
在JDK11中value.length 同样需要处理 coder,与 String.length()类似
三者区别
StringBuffer 的相关改变结构的方法使用了synchronized关键字,线程安全
StringBuilder 不是线程安全的
StringBuffer、StringBuilder不可以赋空值,String 可以
String 不可变,StringBuffer、StringBuilder类可变,因为内部存储的 byte[]不是 final 修饰
字符串拼接效率
String + 拼接的效率最慢
String.concat()次之
StringBuilder 和 StringBuffer 没有绝对的效率优先,看实际的数据量和处理性能,但整体相差不大
使用总结
操作少量的数据: 适用 String
单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer
泛型
定义
Java泛型( generics) 是JDK 5中引⼊的⼀个新特性, 允许在定义类和接口的时候使⽤类型参数( type parameter)
声明的类型参数在使⽤时⽤具体的类型来替换
作用
参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)
提⾼代码的复⽤性
类型擦除(type erasue)
概念
通过类型参数合并,将泛型类型实例关联到同一份字节码上。编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上
实现
Java编译器通过Code sharing方式为每个泛型类型创建唯一的字节码表示,并且将该泛型类型的实例都映射到这个唯一的字节码表示上。将多种泛型类形实例映射到唯一的字节码表示是通过类型擦除(type erasue)实现
Java 采取了“伪泛型”的策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”(Type Erasure),将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型),就像完全没有泛型一样
Code sharing
对每个泛型类只生成唯一的一份目标代码;该泛型类的所有实例都映射到这份目标代码上,在需要的时候执行类型检查和类型转换
原则
消除类型参数声明,即删除<>及其包围的部分
根据类型参数的上下界推断并替换所有的类型参数为原生态类型:如果类型参数是无限制通配符或没有上下界限定则替换为Object,如果存在上下界限定则根据子类替换原则取类型参数的最左边限定类型(即父类)
为了保证类型安全,必要时插入强制类型转换代码
自动产生“桥接方法”以保证擦除类型后的代码仍然具有泛型的“多态性”
编译无法通过
不能使用类型参数访问构造函数
基本类型不能作为泛型类型
不能使用 clone 方法
不能创建泛型数组
不能抛出也不能捕获泛型类的对象
静态方法和静态变量不可以使用泛型类所声明的泛型类型参数
上下限
? extends XClass
上界通配符(Upper Bounds Wildcards):参数化类型可能是 XClass 或 XClass 的子类
? super XClass
下界通配符(Lower Bounds Wildcards):参数化类型可能是 XClass 或 XClass 的超类
PECS原则
PECS(Producer Extends Consumer Super)
频繁往外读取内容的,适合用上界Extends
经常往里插入的,适合用下界Super
如果既要存又要取,那么就不要使用任何通配符
常见命名类型
E - Element (在集合中使用,因为集合中存放的是元素)
T - Type(Java 类)
K - Key(键)
V - Value(值)
N - Number(数值类型)
? - 通配符,表示不确定的java类型
引起的问题
先检查,再编译以及编译的对象和引用传递问题
自动类型转换
类型擦除与多态的冲突
泛型类型变量不能是基础数据类型
编译时集合的instanceof
泛型在静态变量和静态类中的问题
异常
层次结构图示
异常和错误
异常(Exception)
程序本身可以处理的异常,可以通过 catch 来进行捕获。
错误(Error)
Error 属于程序无法处理的错误 ,我们没办法通过 catch 来进行捕获
这类 Error发生时,Java 虚拟机(JVM)一般会选择线程终止
异常类型
受检异常(Checked Exceptions)
除了RuntimeException及其子类以外,其他的Exception类及其子类都属于受检查异常
非运行时异常(编译异常)
代码在编译过程中,如果受检查异常没有被 catch/throw 处理的话,就没办法通过编译
举例
ClassNotFoundException
SQLException
StringConcatException
FileNotFoundException
EOFException(输入期间意外到达文件末尾或流末尾的信号)
非受检异常(Un-Checked Exceptions)
RuntimeException 及其子类都统称为非受检查异常
运行时异常
代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译
举例
NullPointerException(空指针异常)
IndexOutOfBoundsException(下标越界异常)
NumberFormatException
ClassCastException(对象强制转换异常)
ArithmeticException(算术计算异常)
ConcurrentModificationException
DateTimeException(日期计算异常)
InternalException(意外的内部错误)
NoSuchElementException
IllegalArgumentException(非法参数异常)
异常的捕获
try-catch
可以捕获多个异常类型,并对不同类型的异常做出不同的处理
try-catch-finally
try-finally
可用在不需要捕获异常的代码,可以保证资源在使用后被关闭
try-with-resource
@since 7 语法糖
异常实践
只针对不正常的情况才使用和捕获异常,比如不应该捕获 NPE
程序中尽量避免捕获异常后,调用异常的printStacktrace打印,建议记录日志中,并有相应的异常处理逻辑
涉及到资源开关闭场景(如IO)在 finally 块中清理资源或者使用 try-with-resource 语句
尽量使用标准的异常,例如参数异常,可以IllegalArgumentException
不要捕获类似 Exception 之类的异常,而应该捕获类似特定的异常
不要忽略异常,如果有使用catch块,则需要有相应的处理逻辑,哪怕是直接打印输出
不要捕获 Throwable 类
不要记录并抛出异常,如果异常已处理或记录日志,则无需继续 throw
封装自定义异常时不要抛弃原始的异常
不要使用异常来实现控制程序的走向或逻辑流程
不要在finally块中使用return
try块中的return语句执行成功后,并不马上返回,而是继续执行finally块中的语句,如果此处存在return语句,则在此直接返回,无情丢弃掉try块中的返回点
类加载
Class类文件
概述
Class文件本质上是一个以8位字节为基础单位的二进制流
文件结构
图示
描述
魔数(Magic Number)
每个 Class 文件的头四个字节称为魔数(Magic Number),它的唯一作用是确定这个文件是否为一个能被虚拟机接收的 Class 文件
u4 magic; //Class 文件的标志
Class 文件版本
Class 文件的版本号:第五和第六是次版本号,第七和第八是主版本号
高版本的 Java 虚拟机可以执行低版本编译器生成的 Class 文件,但是低版本的 Java 虚拟机不能执行高版本编译器生成的 Class 文件
u2 minor_version;//Class 的小版本号 u2 major_version;//Class 的大版本号
常量池(Constant pool)
常量池计数器是从1开始计数的,将第0项常量空出来是有特殊考虑的,索引值为0代表“不引用任何一个常量池项”
常量池可以理解成Class文件中的资源仓库。主要存放的是两大类常量:字面量(Literal)和符号引用(Symbolic References)。字面量类似于java中的常量概念,而符号引用则属于编译原理方面的概念
字面量
文本字符串
final 常量值
基本数据类型的值
其他
符号引用
类和接口的全限定名(Fully Qualified Name)
字段的名称和描述符号(Descriptor)
方法的名称和描述符
访问标志
用于识别一些类或者接口层次的访问信息,包括:这个 Class 是类还是接口,是否为 public 或者 abstract 类型,如果是类的话是否声明为 final 等等
当前类索引,父类索引与接口索引集合
类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名
由于 Java 语言的单继承,所以父类索引只有一个,除了 java.lang.Object 之外
接口索引集合用来描述这个类实现了那些接口,这些被实现的接口将按 implements (如果这个类本身是接口的话则是extends) 后的接口顺序从左到右排列在接口索引集合中
字段表集合
字段表(field info)用于描述接口或类中声明的变量。字段包括类级变量以及实例变量,但不包括在方法内部声明的局部变量
结构
access_flags
字段的作用域(public ,private,protected修饰符),是实例变量还是类变量(static修饰符),可否被序列化(transient 修饰符),可变性(final),可见性(volatile 修饰符,是否强制从主内存读写)
name_index
对常量池的引用,表示的字段的名称
descriptor_index
对常量池的引用,表示字段和方法的描述符
attributes_count
一个字段还会拥有一些额外的属性,attributes_count 存放属性的个数
attributes[attributes_count]
存放具体属性具体内容
方法表集合
与字段表结构类似,不过方法表描述的是方法的类型,作用域等
属性表集合
在 Class 文件,字段表,方法表中都可以携带自己的属性表集合,以用于描述某些场景专有的信息
与 Class 文件中其它的数据项目要求的顺序、长度和内容不同,属性表集合的限制稍微宽松一些,不再要求各个属性表具有严格的顺序,并且只要不与已有的属性名重复,任何人实现的编译器都可以向属性表中写 入自己定义的属性信息
类加载过程/生命周期
图示
详解
加载(Loading)
通过一个类的全限定名来获取其定义的二进制字节流
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口
loadClass文件的方式
从本地系统中直接加载
通过网络下载.class文件
从zip,jar等归档文件中加载.class文件
从专有数据库中提取.class文件
将Java源文件动态编译为.class文件
验证(Verification)
确保被加载的类的正确性,确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
各阶段的检验动作
文件格式验证
验证字节码是否符合 Class 文件格式规范
元数据验证
对字节码描述的信息进行语义分析(注意: 对比Javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求
字节码验证
通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的
符号引用验证
确保解析动作能正确执行
准备(Preparation)
为类的静态变量分配内存,并将其初始化为默认值
注意事项
仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在 Java 堆中
初始值"通常情况"下是数据类型默认的零值(如0、0L、null、false等), fianl 关键字修饰的变量值
解析(Resolution)
虚拟机将常量池内的符号引用替换为直接引用的过程。
解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符7类符号引用进行
初始化(Initialization)
为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化
初始化阶段是执行初始化方法 <clinit> ()方法的过程
使用(Using)
类访问方法区内的数据结构的接口, 对象是Heap区的数据。
卸载(Unloading)
卸载类即该类的Class对象被GC
在JVM生命周期类,由jvm自带的类加载器加载的类是不会被卸载的。但是由我们自定义的类加载器加载的类是可能被卸载的
虚拟机结束生命周期的情况
执行了System.exit()方法
程序正常执行结束
程序在执行过程中遇到了异常或错误而异常终止
由于操作系统出现错误而导致Java虚拟机进程终止
卸载类的要求
该类的所有的实例对象都已被GC,也就是说堆不存在该类的实例对象
该类没有在其他任何地方被引用
该类的类加载器的实例已被GC
类加载器
类加载器(ClassLoader)
启动类加载器(Bootstrap ClassLoader)
最顶层的加载类,由C++实现,是虚拟机自身的一部分
负责加载 <JAVA_HOME>/lib目录下的jar包和类或者或被 -Xbootclasspath参数指定的路径中的所有类
启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器去处理,那直接使用 null 代替即可
由 Java 语言实现的类加载器
这些类加载器都由 Java 语言实现,独立存在于虚拟机外部,并且全都继承自抽象类 java.lang.ClassLoader
内置加载器在JDK9之前主要由sun.misc.Launcher类实现,JDK9之后,由jdk.internal.loader.ClassLoaders实现
类型
扩展类加载器(Extension Class Loader)
主要负责加载目录 <JRE_HOME>/lib/ext 目录下的jar包和类,或被 java.ext.dirs 系统变量所指定的路径下的jar包
此类库中存放具有通用性的扩展类库,且允许用户自行添加,即扩展机制
JDK9引入模块化之后,该加载器被PlatformClassLoader加载器替换
JDK9之前
由sun.misc.Launcher$ExtClassLoader实现,
JDK9之后
由jdk.internal.loader.ClassLoaders$PlatformClassLoader替换
平台类加载器(Platform Class Loader)
JDK9之后引入模块化系统,不再保留 <JRE_HOME>/lib/ext 目录,用于替换ExtClassLoader
应用程序类加载器(Application Class Loader)
负责加载当前应用ClassPath下的所有jar包和类
JDK9之前
由sun.misc.Launcher$AppClassLoader实现
JDK9之后
由jdk.internal.loader$AppClassLoader实现
自定义类加载器(User Defined ClassLoader)
用户自定义实现的 ClassLoader,一般都是继承自 ClassLoader 类
遵守双亲委派模型
继承ClassLoader,并重写findClass方法
破坏双亲委派模型
继承ClassLoader,并重写loadClass方法
想要打破即重写的时候让自己去加载不让父加载器去加载
用途
在执行非置信代码之前,自动验证数字签名
动态地创建符合用户特定需要的定制化构建类
从特定的场所取得java class,例如数据库中和网络中
父子关系
双亲委派模型中,类加载器之间的父子关系一般不会以继承(Inheritance)的关系来实现,而是都使用组合(Composition)关系来复用父加载器的代码
public abstract class ClassLoader { // ... // 类加载器之间是组合(Composition)关系,非继承(Extends) private final ClassLoader parent; // ...}
JDK9之后
JDK 9 基于模块化进行构建(原来的 rt.jar 和 tools.jar 被拆分成数十个 JMOD 文件), 其中的 Java 类库就已天然地满足了可扩展的需求,那自然无须再保留 <JAVA_HOME>\lib\ext 目录,此前使用这个目录或者 java.ext.dirs 系统变量来扩展 JDK 功能的机制不再存在
平台类加载器和应用程序类加载器都不再继承自 java.net.URLClassLoader,启动类加载器、平台类加载器、应用程序类加载器全都继承于 jdk.internal.loader.BuiltinClassLoader
双亲委派模型(The Parent-Delegation Model)
图示
如果一个类加载器收到了类加载的请求,它不会加载自己尝试加载此类,而是委派请求给父类加载器进行加载。只有父加载器无法加载这个类的时候,才会由当前这个加载器来负责类的加载
过程
流程图示
描述
当【应用程序类加载器】加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器【平台及应用程序类加载器】去完成
当【平台及应用程序类加载器】加载一个class时
JDK9之前
它首先也不会自己去尝试加载这个类,而是把类加载请求委派给【启动类加载器】去完成
JDK9之后
在委派给父加载器加载前,要先判断该类是否能够归属到某一个系统模块中,如果可以找到这样的归属关系,就要优先委派给负责那个模块的加载器完成加载
如果【启动类加载器】加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用【平台及应用程序类加载器】来尝试加载
若【平台及应用程序类加载器】也加载失败,则会使用【应用程序类加载器】来加载,如果【应用程序类加载器】也加载失败,则会报出异常ClassNotFoundException
实现
java.lang.ClassLoader的loadClass()
目的作用
防止重复加载同一个Class。加载过了,就不用再加载一遍。保证数据安全
保证了 Java 的核心 API 不被篡改
判定两个Class对象是否相同的依据
class字节码是否相同
ClassLoader是否相同
破坏双亲委托模式
因为类加载器受到加载范围的限制,在某些情况下父类加载器无法加载到需要的文件,这时候就需要委托子类加载器去加载class文件
方式
继承ClassLoader并重写loadClass方法
想要打破即重写的时候让自己去加载不让父加载器去加载
使用线程上下文类加载器
Thread#getContextClassLoader获取的类加载器去加载类
JVM破坏
第一次
JDK1.2之前,还没有双亲委派模型,不过已经有了 ClassLoader 抽象类
用户可以继承这个抽象类,重写 loadClass 方法来实现用户自定义类加载器
JDK1.2开始,要引入双亲委派模型,为了向前兼容, loadClass 这个方法还得保留着使之得以重写,新引入了findClass方法,并呼吁遵守双亲委派模型,呼吁大家不要重写 loadClass 只要重写 findClass
子主题
第二次
SPI机制
ServiceLoader类是通过引导类加载器加载的,而其内部调用的方法要通过Class#forName方法加载META-INF/services/自定义接口名文件中非JDK核心库中的类
ServiceLoader#load方法通过Thread.currentThread().getContextClassLoader()获取的类加载器去加载类
JNDI、JDBC等需要加载SPI接口实现
线程上下文类加载器
通过Thread#setContextClassLoader()默认情况就是应用程序类加载器,然后利用Thread.current.currentThread().getContextClassLoader()获得类加载器来加载
第三次
OSGI为代表的热替换机制
第四次
JDK9引入模块化系统
在JDK9中,整个JDK都基于模块化进行构建,以前的rt.jar, tool.jar被拆分成数十个模块,编译的时候只编译实际用到的模块,同时各个类加载器各司其职,只加载自己负责的模块
经过破坏后的双亲委派模型更加高效,减少了很多类加载器之间不必要的委派操作
案例
JDBC
JDBC 使用Class.forName加载驱动程序,JDBC4.0后无需使用Class.forName,使用 SPI自动加载
JDBC的Driver接口定义在JDK中,其实现由各个数据库的服务商来提供
DriverManager 类中要加载各个实现了Driver接口的类,然后进行管理,但是DriverManager位于 JAVA_HOME中jre/lib/rt.jar 包,由BootStrap类加载器加载
Tomcat
每个Tomcat的webappClassLoader加载自己的目录下的class文件,不会传递给父类加载器
不同项目间需要进行隔离,将多个项目中共同依赖的包抽出来让JVM只加载一次
HotSwap热部署和热加载
类加载器
类加载架构
CommonLoader
Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问
CatalinaLoader
Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见
SharedLoader
各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见
WebappClassLoader
各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见
注解
概念
Java 注解(Annotation) 又称为元数据,它为我们在代码中添加信息提供了一种形式化的方法
Annotation(注解)是一个接口,程序可以通过反射来获取指定程序中元素的Annotation对象,然后通过该 Annotation 对象来获取注解中的元数据信息
注解用来描述包、类、成员变量、方法或者参数的元数据
作用
生成文档,通过代码里标识的元数据生成javadoc文档
编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证
编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码
运行时动态处理,运行时通过代码里标识的元数据动态处理,例如使用反射注入实例
原理
注解本质是一个继承了Annotation 的特殊接口,其具体实现类是Java 运行时生成的动态代理类
通过反射获取注解时,返回的是Java 运行时生成的动态代理对象$Proxy1
通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler 的invoke 方法。该方法会从memberValues 这个Map 中索引出对应的值。而memberValues 的来源是Java 常量池
https://blog.csdn.net/qq_20009015/article/details/106038023
分类
自带的标准注解
@Override
表示当前的方法定义将覆盖父类中的方法
无任何成员
@Deprecated
表示代码被弃用,如果使用了被@Deprecated注解的代码则编译器将发出警告
String since() default ""
boolean forRemoval() default false
@SuppressWarnings
表示关闭编译器警告信息
String[] value():被禁止的警告名
@FunctionalInterface
@since 1.8
表明这个方法是一个函数式接口
@SafeVarargs
@since 1.7
在声明可变参数的构造函数或方法时,Java 编译器会报 unchecked 警告。使用 @SafeVarargs 可以忽略这些警告
元注解(meta-annotations)
java.lang.annotation
@Target
描述注解的使用范围(即:被修饰的注解可以用在什么地方)
ElementType枚举类
TYPE
类、接口、枚举类
FIELD
成员变量(包括:枚举常量)
METHOD
成员方法
PARAMETER
方法参数
CONSTRUCTOR
构造方法
LOCAL_VARIABLE
局部变量
ANNOTATION_TYPE
注解类
PACKAGE
可用于修饰:包
TYPE_PARAMETER
@since 1.8
类型参数
TYPE_USE
@since 1.8
使用类型的任何地方
MODULE
@since 9
模块描述
@Retention
描述注解保留的时间范围(即:被描述的注解在它所修饰的类中可以被保留到何时)
RetentionPolicy枚举类
SOURCE
源文件保留
CLASS
编译期保留,默认值
RUNTIME
运行期保留,可通过反射去获取注解信息
@Documented
描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息
@Inherited
被它修饰的Annotation将具有继承性
如果某个类使用了被@Inherited修饰的Annotation,则其子类将自动具有该注解
默认不是 true
@Repeatable
@since 1.8
允许在同一申明类型(类,属性,或方法)的多次使用同一个注解
@Native
@since 1.8
使用 @Native 注解修饰成员变量,则表示这个变量可以被本地代码引用,常常被代码生成工具使用
自定义注解
@interface 接口定义
可以定义成员变量
添加默认值
确定相关元注解和设置
结合反射或切面来获取注解信息
动态代理
代理模式
图示
概念
给某一个对象提供一个代理,并由代理对象来控制对真实目标对象的访问。代理模式是一种结构型设计模式
参与的角色
Subject(抽象目标对象接口)
定义代理类和真实目标类的公共对外方法,也是代理类代理目标对象的方法
RealSubject(代理的目标对象类)
真正实现业务逻辑的类
Proxy(代理类)
用来代理和封装目标类
静态代理
在程序开发阶段,程序运行前就已存在代理类,在编译器就已确定好的
用途
控制真实对象的访问权限:通过代理对象控制真实对象的使用权限
避免创建大对象:通过使用一个代理小对象来代表一个真实的大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度
增强真实对象的功能:通过代理可以在调用目标对象的方法的前后增加额外的功能
缺点
无法适配所有代理场景,如果有新的需求,需要修改代理类,不符合软件工程的开闭原则
动态代理
动态代理中的代理类并不要求在编译期就确定,而是可以在运行期动态生成,从而实现对目标对象的代理功能
反射是动态代理的一种实现方式
常见的实现方式
JDK动态代理(JDK Proxy)
概念
java.lang.reflect 包中的Proxy类和InvocationHandler接口提供了生成动态代理类的能力
通过实现java.lang.reflect.InvocationHandler接口并使用java.lang.reflect.Proxy.newProxyInstance()方法来生成代理对象
实现原理
拿到被代理对象的引用,然后获取它的接口
Proxy.getInstance方法
JDK代理重新生成一个类,同时实现我们给的代理对象所实现的接口
把被代理对象的引用拿到了,重新动态生成一个class字节码
然后编译
CGLib动态代理
概念
CGLib (Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展
CGLIB 可以代理没有实现接口的 Java 类
实现
CGLIB 代理中有两个核心的类:MethodInterceptor接口 和 Enhancer类,前者是实现一个代理工厂的根接口,后者是创建动态代理对象的类
javassist代理
JDK对比CGLIB
JDK Proxy只能代理接口的类,CGLIB可以代理大部分的类
CGLIB 采用动态创建被代理类的子类实现方法拦截,子类内部重写被拦截的方法,所以 CGLIB 不能代理被 final 关键字修饰的类和方法
应用
Spring AOP
缓存
日志环绕,Logger
事务处理@Transactional注解
反射
概念
在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法
对于任意一个对象,都能够知道调用它的任意属性和方法,这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制
作用
在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时判断任意一个类所有的成员变量和方法
在运行时调用任意一个对象的方法
反射获取
java.lang.Class类
获取Class实例方式
根据类名:
类名.class
根据对象
对象.getClass()
根据全限定类名
Class.forName()
Class类常用方法
String getName()
获取全限定的类名(包含package名的完整类名)
String getSimpleName()
获取类名
String getCanonicalName()
获取全限定的类名
大多数情况下和getName一样,但是在内部类、数组等类型的表示形式就不同了
native boolean isInterface()
判断Class对象是否是表示一个接口
Class<? super T> getSuperclass()
返回Class对象,表示Class对象所引用的类所继承的直接基类
T newInstance()
返回一个Oject对象,是实现“虚拟构造器”的一种途径
使用该方法创建的类,必须带有无参的构造器
@since 9 被标记为@Deprecated已过时
建议使用clazz.getDeclaredConstructor().newInstance()替代
Field[] getFields()
获得某个类的所有的公共(public)的字段
包括继承自父类的所有公共字段
Field[] getDeclaredFields()
获得某个类的自己声明的字段,即包括public、private和proteced
默认但是不包括父类声明的任何字段
java.lang.reflect.Constructor类
Class 对象所表示的类的构造方法
主要信息
构造方法的访问修饰符(private、public、protected..)
构造方法的参数
参数数据类型
参数名称
标注在参数上的注解
java.lang.reflect.Field类
有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段
主要信息
标注在属性字段的注解
字段名
字段的数据类型
字段的访问修饰符
java.lang.reflect.Method类
提供关于类或接口上单独某个方法(以及如何访问该方法)的信息,所反映的方法可能是类方法或实例方法(包括抽象方法)
主要信息
同Constructor类中的相同
方法返回值类型
场景
Spring实例化对象
当程序启动时,Spring 会读取配置文件applicationContext.xml并解析出里面所有的 标签实例化到IOC容器中
反射+工厂模式
通过反射消除工厂中的多个分支,如果需要生产新的类,无需关注工厂类,工厂类可以应对各种新增的类,反射可以使得程序更加健壮
JDBC连接数据库
使用JDBC连接数据库时,指定连接数据库的驱动类时用到反射加载驱动类
优劣势
增加程序的灵活性
运行期类型的判断,提高代码灵活度
增加类的安全隐患,破坏类的封装性
性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的 java 代码要慢很多