反射
什么是反射
Java的反射机制是指代码(程序)在运行状态中,对于任意一个类,都能够知道并获得这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取类的信息以及动态调用对象的方法的功能称为java的反射机制。
要想动态获取一个类的信息,必须先要获取到该类的字节码文件(Class)对象。如图是类的正常加载过程:反射的关键就在于编译加载后的Class文件.
类的加载机制(双亲委派机制)
JVM中提供了三层的ClassLoader:
- Bootstrap classLoader:主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader。
- ExtClassLoader:主要负责加载jre/lib/ext目录下的一些扩展的jar。
- AppClassLoader:主要负责加载应用程序的主函数类
- 自定义加载器(比如:自定义CustomClassLoader, extends java.lang.ClassLoader 一般不会自定义)
当一个字节码文件要被加载时。不考虑我们自定义类加载器,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了。如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法。父类中同理也会先检查自己是否已经加载过,如果没有再往上。注意这个类似递归的过程,直到到达Bootstrap classLoader之前,都是在检查是否加载过,并不会选择自己去加载。直到BootstrapClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException.
这种设计有个好处是,如果有人想替换系统级别的类:String.java。篡改它的实现,在这种机制下这些系统的类已经被Bootstrap classLoader加载过了(为什么?因为当一个类需要加载的时候,最先去尝试加载的就是BootstrapClassLoader),所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入,也避免了类的重复加载.
SPI 破坏双亲委派机制
SPI是Service Provider Interface的缩写,它是Java提供的一套用于被第三方开发或者实现的API接口,可以用于模块化、解耦,插件机制等场景。SPI具体的使用方式是:首先定义一个接口,然后在项目的src/main/resources/META.INF/services目录下创建一个同名的文件,文件内容是实现该接口的具体实现类。然后在程序运行时,JVM会查找并加载这些实现类。
加载后的Class文件中,几乎包含了所有类相关的信息,如:类名、包名、属性、方法等等。那么,Class对象其实就是我们获得的某一个类型字节码的引用对象。Class文件中包含如下主要内容:
那么如何利用反射去创建一个实例对象?
创建对象的过程,通过下图我们能够看出来,获得对象实例无论是使用new对象的方式,还是使用反射的方式,大致都是需要以下3步的,即:
① 加载Class文件(获得Class的方式有几种?)
② 查找入参匹配的构造函数;
③ 通过构造函数创建实例对象;
反射相关的方法和操作:
public class Student {
public String name;
protected int age;
char sex;
private String phoneNum;
//---------------构造方法-------------------
//(默认的构造方法)
Student(String name) {
System.out.println("(默认)的构造方法 name = " + name);
}
//无参构造方法
public Student() {
System.out.println("调用了公有、无参构造方法执行了。。。");
}
//有一个参数的构造方法
public Student(char sex) {
System.out.println("性别:" + sex);
}
//有多个参数的构造方法
public Student(String name, int age) {
System.out.println("姓名:" + name + "年龄:" + age);
}
//受保护的构造方法
protected Student(String phoneNum) {
System.out.println("受保护的构造方法 phoneNum = " + phoneNum);
}
//私有构造方法
private Student(int age) {
System.out.println("私有的构造方法 年龄:" + age);
}
//**************成员方法***************//
public void show1(String name){
System.out.println("调用了:公有的,String参数的show1(): name = " + name);
}
protected void show2(){
System.out.println("调用了:受保护的,无参的show2()");
}
void show3(){
System.out.println("调用了:默认的,无参的show3()");
}
private String show4(int age){
System.out.println("调用了,私有的,并且有返回值的,int参数的show4(): age = " + age);
return "abcd";
}
}
- 获取类的Class对象。(在运行期间,一个类只有一个Class对象产生,常用第三种)
public static void main(String[] args) {
//第一种方式获取Class对象
Student stu1 = new Student();//这一new 产生一个Student对象,一个Class对象。
Class stuClass = stu1.getClass();//获取Class对象
System.out.println(stuClass.getName());
//第二种方式获取Class对象
Class stuClass2 = Student.class;
System.out.println(stuClass == stuClass2);//判断第一种方式获取的Class对象和第二种方式获取的是否是同一个
//第三种方式获取Class对象
try {
Class stuClass3 = Class.forName("com.zjg.Student");//注意此字符串必须是真实路径,就是带包名的类路径,包名.类名
System.out.println(stuClass3 == stuClass2);//判断三种方式是否获取的是同一个Class对象
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
- 通过构造函数创建实例对象。(通过私有构造函数创建对象,可以破坏单例模式)
public static void main(String[] args) throws Exception {
//1.加载Class对象
Class clazz = Class.forName("com.zjg.Student");
//2.获取所有公有构造方法
System.out.println("***所有公有构造方法***");
Constructor[] conArray = clazz.getConstructors();
for(Constructor c : conArray){
System.out.println(c);
}
System.out.println("***所有的构造方法(包括:私有、受保护、默认、公有)***");
conArray = clazz.getDeclaredConstructors();
for(Constructor c : conArray){
System.out.println(c);
}
System.out.println("***获取公有、无参的构造方法***");
Constructor con = clazz.getConstructor(null);
//1>、因为是无参的构造方法所以类型是一个null,不写也可以:
// 这里需要的是一个参数的类型,切记是类型
//2>、返回的是描述这个无参构造函数的类对象。
System.out.println("con = " + con);
//调用构造方法
Object obj = con.newInstance();
// System.out.println("obj = " + obj);
// Student stu = (Student)obj;
System.out.println("***获取私有构造方法,并调用***");
con = clazz.getDeclaredConstructor(char.class);
System.out.println(con);
//调用构造方法
con.setAccessible(true);//暴力访问(忽略掉访问修饰符)
obj = con.newInstance('男');
}
- 通过反射获得类的属性值。
public static void main(String[] args) throws Exception {
//1.获取Class对象
Class stuClass = Class.forName("com.zjg.Student");
//2.获取字段
System.out.println("***获取所有公有的字段***");
Field[] fieldArray = stuClass.getFields();
for(Field f : fieldArray){
System.out.println(f);
}
System.out.println("***获取所有的字段(包括私有、受保护、默认的)***");
fieldArray = stuClass.getDeclaredFields();
for(Field f : fieldArray){
System.out.println(f);
}
System.out.println("***获取公有字段并调用***");
Field f = stuClass.getField("name");
System.out.println(f);
//获取一个对象
Object obj = stuClass.getConstructor().newInstance();//产生Student对象--》Student stu = new Student();
//为字段设置值
f.set(obj, "刘德华");//为Student对象中的name属性赋值--》stu.name = "刘德华"
//验证
Student stu = (Student)obj;
System.out.println("验证姓名:" + stu.name);
System.out.println("***获取私有字段并调用***");
f = stuClass.getDeclaredField("phoneNum");
System.out.println(f);
f.setAccessible(true);//暴力反射,解除私有限定
f.set(obj, "18888889999");
System.out.println("验证电话:" + stu);
}
- 通过反射获得类的方法。
public static void main(String[] args) throws Exception {
//1.获取Class对象
Class stuClass = Class.forName("fanshe.method.Student");
//2.获取所有公有方法
System.out.println("***获取所有的公有方法***");
stuClass.getMethods();
Method[] methodArray = stuClass.getMethods();
for(Method m : methodArray){
System.out.println(m);
}
System.out.println("***获取所有的方法,包括私有的***");
methodArray = stuClass.getDeclaredMethods();
for(Method m : methodArray){
System.out.println(m);
}
System.out.println("***获取公有的show1()方法***");
Method m = stuClass.getMethod("show1", String.class);
System.out.println(m);
//实例化一个Student对象
Object obj = stuClass.getConstructor().newInstance();
m.invoke(obj, "刘德华");
System.out.println("***获取私有的show4()方法***");
m = stuClass.getDeclaredMethod("show4", int.class);
System.out.println(m);
m.setAccessible(true);//解除私有限定
Object result = m.invoke(obj, 20);//需要两个参数,一个是要调用的对象(获取有反射),一个是实参
System.out.println("返回值:" + result);
}
泛型
什么是泛型
泛型主要是为了让开发人员在开发时可以在某个场景中定义使用统一的某种类型, 使代码更规范更安全. 早期Java是使用Object来代表任意类型的,但是向下转型有强转的问题,这样程序并不安全。针对List、Set、Map等集合类型,它们对存储的元素类型是没有任何限制的。假如向List中存储A类型的对象,但是有人把B类型对象也存储到这个List中了,那么在编译上是没有任何语法错误的,但在运行时可能就会出现问题。所以所有使用泛型参数的地方都被统一化,保证类型一致。如果未指定具体类型,默认是Object类型。Java从1.5版本开始有了泛型的概念, 集合体系中的所有类都增加了泛型,泛型也主要用在集合。
泛型类
泛型类就是把泛型定义在类上,用户使用该类的时候,才把类型明确下来。这样的话,用户明确了什么类型,该类就代表着什么类型,用户在使用的时候就不用担心强转的问题,和运行时转换异常的问题了,如下所示:
泛型方法
除了在类上使用泛型,我们可能就仅仅在某个方法上需要使用泛型,外界仅仅是关心该方法,不关心类其他的属性,这样的话,我们在整个类上定义泛型,未免就有些大题小作了。那么此时,我们可以采用泛型方法:
泛型类子类
前面我们已经定义了泛型类,泛型类是拥有泛型这个特性的类,它本质上还是一个Java类,那么它就可以被继承或实现。这里分两种情况:
我们创建一个Inter接口,在该接口上使用泛型
- 子类明确泛型类的类型参数变量
- 子类不明确泛型类的类型参数变量
泛型通配符
List<?>表示元素类型未知的List,它可以匹配任何类型的元素。声明List<?>list后,不能向集合中添加元素,因为无法确定集合的元素类型,唯一例外的是null。
泛型的上限和下限
泛型的上限: <? extends 父类A>
只能接受类型A及其子类, 上限就是A
泛型的下限: <? super 子类B>
只能接受类型B及其父类, 下限就是B
泛型擦除
泛型是提供给java编译器使用的,它可以作为类型的限制,让编译器在源代码级别上,挡住非法类型的数据。但是在JDK1.5之前没有泛型的概念,为了能够与之前版本代码兼容,编译器编译完带有泛型的java程序后,生成的class字节码文件中将不再带有泛型信息,这个过程称之为“泛型擦除”, 也就是Java的泛型其实是伪泛型.
Java使用了桥接方法来实现1.5前后版本泛型字节码文件兼容的,它是由编译器自动生成的。可用 method.isBridge() 判断method是否是桥接方法。