反射(Reflection)
类加载后,在堆内存中的方法区中间产生了一个Class类型的对象
(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息
。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,称之为:反射
反射机制
允许程序在运行期间获取任何类的内部信息
(比如类名,类的接口,类的方法,字段,属性…),并且能够直接操作任意对象的内部属性及方法
。
反射提供的功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
- …
反射的优缺点
优点:
可以实现动态创建对象和编译,体现出很大的灵活性
缺点:
对性能有影响,使用反射基本上是一种解释操作,我们可以告诉JVM 我们希望做什么并且它满足我们的需求。这类操作总是慢于 直接执行相同的操作。
1.Class类
通过对象反射出类的名称就是反射
Class类的常用方法
获得Class类的方式
a)如果已有具体的类,通过类的class属性获取,最为安 全可靠且性能最高的方法。
Class 变量=XXX.class;
b)已知某个类的实例,调用此实例的getClass()方法获取Class对象。
Class 变量=XXX.getClass();
c)已知一个类的全名且在类路径下,可以通过Class类的静态方法forName()获取,需要处理异常ClassNotFoundException
Class 变量=Class.forName("类的全限定名");
d)内置基本数据类型可以直接使用类名.Type
e)还可以用ClassLoader(之后讲解)
测试:
public class TestClass {
public static void main(String[] args) throws ClassNotFoundException {
Student student=new Student();
//a.
Class class1=Student.class;
//b.
Class class2=student.getClass();
//c.
Class class3=Class.forName("com.kuang.reflect.Student");
//d.
Class class4=Integer.TYPE;
//获得父类Class
Class class5=class1.getSuperclass();
System.out.println(class1.hashCode());
System.out.println(class2.hashCode());
System.out.println(class3.hashCode());
System.out.println(class4);
System.out.println(class5);
}
}
class Person{}
class Student extends Person{}
哪些类型可以有Class对象?
- class:外部类、成员(成员内部类、静态内部类),局 - 部内部类,匿名内部类。
- interface:接口
- []:数组
- enum:枚举
- annotation:注解 (也是一个类型)
- primitive type:基本数据类型
- void
测试:
public class Test {
public static void main(String[] args) {
Class class1=Object.class;//类
Class class2=Serializable.class;//接口
Class class3=String[].class;//一维数组
Class class4=int[][].class;//二维数组
Class class5=Override.class;//注解
Class class6=ElementType.class;//枚举
Class class7=double.class;//基本数据类型
Class class8=void.class;//void (代表空类型)
Class class9=Class.class;//Class
System.out.println(class1);
System.out.println(class2);
System.out.println(class3);
System.out.println(class4);
System.out.println(class5);
System.out.println(class6);
System.out.println(class7);
System.out.println(class8);
System.out.println(class9);
}
}
2.类加载内存分析
java内存分析
类的加载过程
测试:
public class Test05 {
public static void main(String[] args) {
A a =new A();
System.out.println(A.m);
/**
* 1.加载到内存,会产生一个对应Class对象
* 2.链接, 链接结束后 m=0
* 3.初始化
<clint>(){
System.out.println("A类静态代码块初始化");
m=300;
m=100;
}
*/
}
}
class A{
//无参构造方法
public A(){
System.out.println("A类的无参构造初始化");
}
//静态代码块
static {
System.out.println("A类静态代码块初始化");
m=300;
}
/**
* m=300
* m=100 (覆盖了上面的值)
*/
//静态变量
static int m=100;
}
1.加载阶段,将编译好的class文件加载到内存中(方法区)
2.然后会生成一个代表这个类的Class对象
3.链接阶段,会为静态变量分配内存并设置默认值
4.开始执行main方法,实例化一个A对象放在堆内存中,然后去找自己的Class类(图上4有误),拿到所有属性,方法等
5.初始化阶段,赋值
分析类的初始化
1.主动引用
public class Test06 {
static {
System.out.println("Main类被加载");
}
public static void main(String[] args) throws ClassNotFoundException {
//1.主动引用
// Son son=new Son();
//反射也会产生主动引用
Class.forName("com.javacto.reflection.Son");
}
}
class Father{
static int b=2;
static {
System.out.println("父类被加载");
}
}
class Son extends Father{
static {
System.out.println("子类被加载");
}
static int m=100;
static final int M =1; //常量
}
2.被动引用
public class Test06 {
static {
System.out.println("Main类被加载");
}
public static void main(String[] args) throws ClassNotFoundException {
//System.out.println("==========不会发生类的初始化===============");
//不会产生类的引用的方法
//1.通过子类引用父类的静态变量,不会导致子类初始化 因为static在链接阶段的时候已经存在了
//System.out.println(Father.b);
//2.只是一个数组 命了一个名和开辟了空间而已
//Son [] array= new Son[5];
//System.out.println(array.getClass());
//3.常量不会引起父类与子类的初始化,因为所有的常量以及静态变量,都是在链接阶段都已经赋了一个值了,初始化的时候已经存在了
//System.out.println(Son.M);
}
}
class Father{
static int b=2;
static {
System.out.println("父类被加载");
}
}
class Son extends Father{
static {
System.out.println("子类被加载");
}
static int m=100;
static final int M =1; //常量
}
3.类加载器
java平台核心库:rt.jar 包
扩展类加载器 :ExtClassLoader:
系统类加载器: System Classloder ,也有的地方叫 AppClassLoader
java的运行环境jre -> lib 目录 运行所有的jar包都在这里面
测试获取加载器:
public class Test07 {
public static void main(String[] args) throws ClassNotFoundException {
//获取系统的类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println("系统类加载器:"+systemClassLoader);
//获取系统类加载的父类加载器--> 扩展类加载器
ClassLoader parent = systemClassLoader.getParent();
System.out.println("扩展类加载器:"+parent);
//获取扩展类加载器的父类加载器--> 根加载器 (c/c++ 写的 读取不到 返回 null)
ClassLoader parent1 = parent.getParent();
System.out.println("根加载器:"+parent1);
//测试当前类是哪个加载器加载的
ClassLoader classLoader = Class.forName("com.javacto.reflection.Test07").getClassLoader();
System.out.println("当前类加载器:"+classLoader);
//测试jdk 内置的类是谁加载的
classLoader=Class.forName("java.lang.Object").getClassLoader();
System.out.println("JDK内置类的加载器:"+classLoader); //根加载器加载的
}
}
可以看到 jdk内置类的加载器 就是java平台核心库,跟加载器加载的 因为用c++编写的的,无法直接获取 所有显示null。
了解什么是双亲委派机制:
如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己才想办法去完成。
优点:避免重复加载 + 避免核心类篡改
采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。
4.获取运行时类的完整结构
Field(字段)、Method(方法)、Constructor(构造器)、Superclass(父类)、Interface(接口)、Annotation(注解)
//获得类的信息
public class Test08 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class c1 = Class.forName("com.javacto.reflection.User");
System.out.println("=========获得类的名字=============");
//类的名字
System.out.println(c1.getName());//包名+类名
System.out.println(c1.getSimpleName());//类名
/*
打印输出结果
com.javacto.reflection.User
User
*/
System.out.println("==============获得类的属性===================");
//获得类的属性
Field[] field1=c1.getFields();//只能找到public属性
for (Field f :field1) {
System.out.println(f); //因为没有定义public属性所以为无
}
Field[] field2=c1.getDeclaredFields();//找到全部的属性
for (Field f :field2) {
System.out.println(f);
}
/*
输出结果
private java.lang.String com.javacto.reflection.User.name
private int com.javacto.reflection.User.id
private int com.javacto.reflection.User.age
*/
//获得指定的属性
Field name=c1.getDeclaredField("name");
System.out.println("指定:"+name);
/*
输出结果
指定:private java.lang.String com.javacto.reflection.User.name
*/
//获得类的方法
System.out.println("==============获得类的方法===================");
Method[] methods=c1.getMethods();//获得本类及其父类的全部public方法
for (Method method:methods) {
System.out.println("正常的:"+method);
}
methods=c1.getDeclaredMethods();//获得本类的所有方法,包括私有的 不包括父类
for (Method method:methods) {
System.out.println("DeclaredMethods:"+method);
}
/*
打印输出结果
正常的:public java.lang.String com.javacto.reflection.User.toString()
正常的:public java.lang.String com.javacto.reflection.User.getName()
正常的:public int com.javacto.reflection.User.getId()
正常的:public void com.javacto.reflection.User.setName(java.lang.String)
正常的:public int com.javacto.reflection.User.getAge()
正常的:public void com.javacto.reflection.User.setId(int)
正常的:public void com.javacto.reflection.User.setAge(int)
正常的:public final void java.lang.Object.wait() throws java.lang.InterruptedException
正常的:public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
正常的:public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
正常的:public boolean java.lang.Object.equals(java.lang.Object)
正常的:public native int java.lang.Object.hashCode()
正常的:public final native java.lang.Class java.lang.Object.getClass()
正常的:public final native void java.lang.Object.notify()
正常的:public final native void java.lang.Object.notifyAll()
DeclaredMethods:public java.lang.String com.javacto.reflection.User.toString()
DeclaredMethods:public java.lang.String com.javacto.reflection.User.getName()
DeclaredMethods:public int com.javacto.reflection.User.getId()
DeclaredMethods:public void com.javacto.reflection.User.setName(java.lang.String)
DeclaredMethods:private void com.javacto.reflection.User.test()
DeclaredMethods:public int com.javacto.reflection.User.getAge()
DeclaredMethods:public void com.javacto.reflection.User.setId(int)
DeclaredMethods:public void com.javacto.reflection.User.setAge(int)
*/
//获得指定方法只需要在()中添加参数(方法名,方法参数)
//添加参数 实则是因为考虑到了 重载
System.out.println("============获得指定方法=============");
Method getName = c1.getMethod("getName", null);
Method setName = c1.getMethod("setName", String.class);
System.out.println(getName);
System.out.println(setName);
/*
打印输出结果
public java.lang.String com.javacto.reflection.User.getName()
public void com.javacto.reflection.User.setName(java.lang.String)
*/
System.out.println("=============获得指定的构造器===================");
//获得指定构造器
Constructor[] constructors=c1.getConstructors();//获得public方法
for (Constructor constructor : constructors) {
System.out.println("public:"+constructor);
}
constructors=c1.getDeclaredConstructors();//获得本类所有方法
for (Constructor c :constructors) {
System.out.println("全部:"+c);
}
//获得指定构造器 (String name, int id, int age)
Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
System.out.println("指定构造器:"+declaredConstructor);
/*
打印输出结果:
public:public com.javacto.reflection.User(java.lang.String,int,int)
public:public com.javacto.reflection.User()
全部:public com.javacto.reflection.User(java.lang.String,int,int)
全部:public com.javacto.reflection.User()
指定构造器:public com.javacto.reflection.User(java.lang.String,int,int)
*/
}
}
5.通过反射动态创建对象测试
public class Test09 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
//获得Class对象
Class c1 = Class.forName("com.javacto.reflection.User");
//构造一个对象
/* User user = (User)c1.newInstance(); //本质上调用了类的无参构造器
System.out.println(user); //User{name='null', id=0, age=0}*/
//通过构造器创建对象
Constructor constructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
User user2 = (User) constructor.newInstance("小明", 1, 18);
System.out.println(user2); //User{name='小明', id=1, age=18}
//通过反射调用普通方法
User user3= (User) c1.newInstance();
//通过反射获取一个方法
Method setName = c1.getMethod("setName", String.class);
//invoke: 激活的意思 (对象, "方法需要的参数")
setName.invoke(user3,"小明3");
System.out.println(user3.getName());
System.out.println("========通过反射操作属性===========");
//通过反射操作属性
User user4= (User) c1.newInstance();
Field name = c1.getDeclaredField("name");
//不能直接操作私有属性, 我们需要关闭程序的安全检查,属性或者方法的 setAccessible(true)
name.setAccessible(true);
name.set(user4,"小明4"); //修改属性值
System.out.println(user4.getName());//setAccessible 默认为false 如果没有关闭将会报没有访问private的权限
//can not access a member of class com.javacto.reflection.User with modifiers "private"
}
}
invoke(对象,"参数")
:激活方法
setAccessible
:关闭安全检查
6.获取泛型信息
Java采用泛型擦除的机制来引入泛型,Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换的问题,但是,一旦编译完成,所有和泛型有关的类型全部擦除。
思考该怎么获得? (之前有说过,类加载的时候就产生了Class对象,故class对象里面应该是有保留的)
public class Test11 {
/**
* 通过泛型传参
* @param map
* @param list
*/
public void test01(Map<String,User> map, List <User> list){
System.out.println("test01");
}
/**
* 通过泛型返回值
* @return
*/
public Map<String,User> test02(){
System.out.println("test02");
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
//获得参数类型
Method method = Test11.class.getMethod("test01", Map.class, List.class);
//获取参数类型 即Map和 List
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println("1:"+genericParameterType);
//判断genericParameterType参数类型 是否属于 ParameterizedType 参数化类型
if (genericParameterType instanceof ParameterizedType){
//如果属于参数化类型,获得他的真实类型 getActualTypeArguments
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
//再次输出真实的泛型信息
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("2:"+actualTypeArgument);
}
}
}
//获得返回值类型
method = Test11.class.getMethod("test02",null);
Type genericReturnType = method.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType){
//如果genericReturnType返回值类型属于参数化类型,获得他的真实类型 getActualTypeArguments
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
//再次输出真实的泛型信息
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("3:"+actualTypeArgument);
}
}
}
}
7.获取注解信息
public class Test12 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class c1 = Class.forName("com.javacto.reflection.Student2");
//通过反射获取注解
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//获得注解的value的值 获取指定注解值
MyTable myTable =(MyTable) c1.getAnnotation(MyTable.class);
String value = myTable.value();
System.out.println(value);
//获得类指定的注解
System.out.println("=====获得类指定的注解======");
Field f= c1.getDeclaredField("name");
MyField annotation = f.getAnnotation(MyField.class);
System.out.println(annotation.columnName());
System.out.println(annotation.type());
System.out.println(annotation.length());
}
}
@MyTable("db_students")
class Student2{
@MyField(columnName = "db_id",type = "int",length = 10)
private int id;
@MyField(columnName = "db_age",type = "int",length = 10)
private int age;
@MyField(columnName = "db_name",type = "varchar",length = 50)
private System name;
public Student2() {
}
public Student2(int id, int age, System name) {
this.id = id;
this.age = age;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public System getName() {
return name;
}
public void setName(System name) {
this.name = name;
}
@Override
public String toString() {
return "Student2{" +
"id=" + id +
", age=" + age +
", name=" + name +
'}';
}
}
//类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyTable{
String value();
}
//属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyField{
String columnName(); //列名
String type(); //类型
int length(); //长度
}