Java从入门到精通!第十四天,重点!(反射)
二、反射
1. 反射(Reflection):
是被视为动态语言的关键,反射机制允许程序在执行期间借助于 Reflection 的 API 取得任何类的内部信息,并能直接操作任意对象的内部属性和方法。
2. 反射的工作原理:
javac 在编译了 java 源文件之后会生成 .class 文件,然后 JVM 会通过类加载器加载 .class 文件,加载完毕之后,在堆内存的方法区中就会产生一个 Class 类型的对象(注意:一个类只会有一个 Class 对象),这个 Class 对象就是反射源,包含了完整的类的结构信息,我们可以通过它看到类的内部结构。Class 对象就像一个镜子,通过镜子可以看到类的内部结构,所以形象的称为“反射”。
3. 产生对象的不同方式
(1)正常产生对象
(2)反射机制
反射就类似于在解剖一个类
4. 反射能够提供的功能
(1)在运行时判断任意一个对象所属的类
(2)在运行时构造任意一个类的对象
(3)在运行时判断任意一个类所具有的属性和方法
(4)在运行时获取泛型信息
(5)在运行时调用任意一个对象的成员变量和方法
(6)在运行时处理注解
(7)生成动态代理
5. 反射相关的API
6. 获取反射的源头:Class类
在 Object 类中定义了一个方法:public final Class getClass(),这意味着所有的 Java 类都可以调用该方法区获取反射的源头 Class。
在使用 Class 类时注意事项:
(1)对象反射之后可以得到的信息有:某个类的属性,方法,构造器,某个类实现了哪个接口,对于每个类而言,JVM 都为其保留了一个不变的 Class 类型的对象,一个 Class 类型的对象包含了特定的结构(这些结构可以是:class/interface/enum/annotation/primitive type(基本数据类型)/void/[])的有关信息:
① Class 本身就是一个类
② Class 类不是由程序员生成,而只能由系统生成
③ 一个加载在 JVM 中的类只会有一个 Class 对象
④ 一个 Class 对象就对应了加载在 JVM 中的 .class 文件
⑤ 通过 Class 对象可以得到类中被加载在 JVM 中的完整信息
(2)获取Class类的方式:
示例:
7. Class 反射源常用的方法:
示例:
前导知识:导出 jar 包和导入 jar 包:
导出 jar 包(jar包:Java 特有一种压缩包):
保持默认值一直下一步:然后直接点 Finish 完成
可以看到 jar 包的本质就是包含了 .class 文件(java源代码编译后的字节码文件)的压缩包:
工程中导入 jar 包:
在工程上新建一个 lib 目录,将 jar 包拷贝到 lib 目录中:
在这个 jar 包上右键,添加到工程的类路径中(classpath),以便当前工程能够识别这个 jar 包:
添加之后就可以找到了:
示例代码:反射
8. 类的加载和 ClassLoader 类(就是用于加载类的一个类)的理解
(1)类的加载过程
当程序主动使用某个类的时候,如果该类还没有被加载到内存中,则系统通过以下三个步骤加载类:
以上过程分析:
1)加载:将 .class 文件的字节码内容加载到内存中,并将这些静态数据转换为方法区的数据结构,然后生成一个表示该类的 Class 对象,作为方法区中类数据的访问入口(引用地址),即使用 Class 引用变量指向方法区中的数据结构,加载过程需要类加载器参与。
2)链接:将 Java 类的二进制代码合并到 JVM 的运行状态之中的过程
a. 验证:确保加载的类符合 JVM 规范
b. 准备:将类变量(static)分配内存并设置类变量的默认值,这些内存都是在方法区中分配的。
c. 解析:虚拟机常量池的常量名替换为直接引用的过程。
3)初始化
a. 执行类构造器(<clinit>,底层C++的方法),类构造器的作用就是完成类变量和静态代码语句按顺序合并。
b. 当初始化一个类的时候,如果发现其父类没有初始化,则初始化父类
c. 虚拟机保证一个类的类构造器方法能够在多线程环境中正确的加锁和同步。
(2)类加载说明
类加载的作用是把类(.class)装进内存,JVM规范定义了如下类型的类加载器:
9. 通过反射获取运行时类的完整结构
(1)获取的完整结构主要包含
(2)构造函数 Constructor
(3)成员变量 Field
示例:
package com.edu.reflect;
import java.lang.reflect.Field;
public class ReflectDemo3 {
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
Class clazz = UserDemo.class;
//获取所有的成员变量,包含私有的
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {
System.out.println(field);
}
System.out.println();
//通过属性名获取属性
Field name = clazz.getDeclaredField("name");
/**
* 通过 Class 的 newInstance() 方法产生对象,注意该方法回去调用被反射类的默认构造方法
*/
Object obj = clazz.newInstance();
//由于 name 是 private,外部要访问需要进行如下设置:
name.setAccessible(true);
//给name属性设置属性值:
name.set(obj, "小明");//给 obj 对象的 name 属性设置值为 "小明"
//获取 obj 对象的 name 属性值
Object value = name.get(obj);
System.out.println(value);
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class UserDemo {
private String name;
private int age;
private int c;
public UserDemo() {
// TODO Auto-generated constructor stub
}
public UserDemo(String name, int age, int c) {
super();
this.name = name;
this.age = age;
this.c = c;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getC() {
return c;
}
public void setC(int c) {
this.c = c;
}
@Override
public String toString() {
return "UserDemo [name=" + name + ", age=" + age + ", c=" + c + "]";
}
}
(4)通过反射调用指定的方法
示例:
package com.edu.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectDemo4 {
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
Class<?> clazz = Class.forName("com.edu.reflect.Emp");
//获取所有的方法,包含私有的
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method method : declaredMethods) {
System.out.println(method);
}
System.out.println();
//获取 setName 方法
Method setName = clazz.getDeclaredMethod("setName", String.class);
//创建对象:换种方式:通过反射获取构造器来产生对象
Constructor<?> constructor = clazz.getDeclaredConstructor(null);//不传参数表示获取默认构造函数
//通过 constructor 产生对象
Object obj = constructor.newInstance(null);//null表示没有实参传递给构造函数
//调用setName 方法,参数1:obj对象,参数2:传给方法的实参,res表示方法返回值,这里没有返回值,所以肯定为null
Object res = setName.invoke(obj, "张三");
//获取 getName 方法
Method getName = clazz.getDeclaredMethod("getName", null);
//调用 getName 方法
Object name = getName.invoke(obj, null);
System.out.println(name);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class Emp {
private int id;
private String name;
private double sal;
public Emp() {
// TODO Auto-generated constructor stub
}
public Emp(int id, String name, double sal) {
super();
this.id = id;
this.name = name;
this.sal = sal;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
@Override
public String toString() {
return "Emp [id=" + id + ", name=" + name + ", sal=" + sal + "]";
}
}
(5)Annotation(注解)相关
① getAnnotation(Class AnnotationClass):获取对应的注解(可以是Class,Constructor,Field,Method等等)
② getDeclaredAnnotations():获取所有的注解
(6)程序包相关
① Package getPackage():获取程序包
示例:参考注解部分的示例
10. 泛型擦除
思考,将已存在的 ArrayList<Integer> 集合中添加一个字符串数据,如何实现?
解答:其实程序编译后产生的 .class 文件中是没有泛型约束的,这种现象我们称为泛型的擦除。那么,我们可以使用反射的技术,来完成向有约束的集合中添加任意类型的元素。
package com.edu.reflect;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ReflectDemo5 {
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(30);//自动装箱
list.add(40);
list.add(50);
System.out.println(list);
//反射
Class<?> c = Class.forName("java.util.ArrayList");
//获取 add 方法
Method add = c.getMethod("add", Object.class);
//执行 add 方法
add.invoke(list, "哈哈,字符串也添加进来了");
System.out.println(list);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}