Java反射学习总结

Java反射

一、反射是什么?

1.Java反射机制

在程序运行时动态加载类并获取类的详细信息(属性,构造方法,方法等),并且可以操作类或对象的属性和方法。

本质:JVM得到class对象,再通过class对象进行反编译,从而获取对象的各种信息。

反射最重要的用途就是开发各种通用框架。搞懂了反射以后,可以帮助理解框架的一些原理。所以说有一句很经典的话:反射是框架设计的灵魂

2.Class类

在Java中,一切皆对象。每个具体的类的对象也有很多信息,这些类型信息就通过Class对象表示。其实创建的实例对象就是通过Class对象创建的。

每一种类都有一个Class对象,每当编译产生一个新类,就产生一个Class对象,基本类型有Class对象,数组有Class对象(数组的Class对象:具有相同元素类型和维数的数组都共享一个Class对象,并没有说元素个数)。

Class对象对应java.lang.Class类。

如果说类是对对象的抽象和集合的话,Class类就是对类的抽象和集合。

Class对象没有公共的构造方法,Class对象是在类加载的时候有JVM及通过调用类加载器中的defineClass方法自动构造的,所以不能显式的声明一个Class对象。

二、反射的优缺点

  1. 优点:在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。
  2. 缺点:由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。另外,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。

三、如何获得Class对象

1.Class.forName(“类路径”)

try {
     Class<?> clazz2=Class.forName("java.lang.String");
     System.out.println(clazz2.getName());
} catch (ClassNotFoundException e) {
     e.printStackTrace();
}

Class.forName方法是Class类的一个静态方法,forName在执行的过程中发现如果类String还没有被加载,那么JVM就会调用类加载器去加载String类,并返回加载后的Class对象。Class对象和其他对象一样,我们可以获取并操作它的引用。

这种是最常用的,第二种都已经产生实例对象了,第三种需要导入包,依赖性强。

2.实例对象.getclass()

String s=new String();
Class<?> clazz=s.getClass();
System.out.println(clazz.getName());

3.类名.class

Class<?> clazz1=String.class;
System.out.println(clazz1.getName());

四、获取类实例及成员

1.创建类的实例对象

  1. 通过Class对象的newInstance()方法创建
Object str=clazz.newInstance();//已经弃用
  1. 先通过Class对象获取Constructor对象,再调用Constructor对象的newInstrance()方法创建对象
try {
	//可以指定构造函数,只需要传入相应的Class对象参数
     Constructor constructor=clazz.getConstructor(String.class);
     //newInstance方法传入构造函数的初始化形参
     Object str2=constructor.newInstance("hello reflection");
     System.out.println(str2);
} catch (NoSuchMethodException e) {
     e.printStackTrace();
} catch (IllegalAccessException e) {
     e.printStackTrace();
} catch (InstantiationException e) {
     e.printStackTrace();
} catch (InvocationTargetException e) {
     e.printStackTrace();
}

2.获取类的构造方法并调用

  1. 获取所有的public的构造方法
    public Constructor[] getConstructors()
  2. 获取所有的构造方法(包括private、public、protected、默认)
    public Constructor[] getDeclaredConstructors()
  3. 获取单个构造方法
    public Constructor getConstructor(Class… parameterTypes)//公有
    public Constructor getDeclaredConstructor(Class… parameterTypes)//公有、私有、受保护、默认
  4. 调用(或暴力调用)构造方法
    通过setAccessible(true)方法忽略掉访问修饰符,再通过Constructor对象的newInstance()创建实例

测试代码:

//(1)、(2)、(3),String类的Class对象
//所有公有方法
        System.out.println("----------所有公有构造方法----------");
        Constructor[] constrArray=clazz.getConstructors();
        for(Constructor constructor:constrArray){
            System.out.println(constructor);
        }

        System.out.println("----------所有构造方法----------");
        Constructor[] constrArray2=clazz.getDeclaredConstructors();
        for(Constructor constructor:constrArray2){
            System.out.println(constructor);
        }

        try {
            System.out.println("----------指定参数公有构造方法----------");
            Constructor constructor=clazz.getConstructor(byte[].class);
            System.out.println(constructor);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
//结果
----------所有共有构造方法----------
public java.lang.String(byte[])
public java.lang.String(byte[],int,int)
public java.lang.String(byte[],java.nio.charset.Charset)
public java.lang.String(byte[],java.lang.String) throws java.io.UnsupportedEncodingException
public java.lang.String(byte[],int,int,java.nio.charset.Charset)
public java.lang.String(java.lang.StringBuilder)
public java.lang.String(java.lang.StringBuffer)
public java.lang.String(char[],int,int)
public java.lang.String(char[])
public java.lang.String(java.lang.String)
public java.lang.String()
public java.lang.String(byte[],int,int,java.lang.String) throws java.io.UnsupportedEncodingException
public java.lang.String(byte[],int)
public java.lang.String(byte[],int,int,int)
public java.lang.String(int[],int,int)
----------所有构造方法----------
public java.lang.String(byte[])
public java.lang.String(byte[],int,int)
public java.lang.String(byte[],java.nio.charset.Charset)
public java.lang.String(byte[],java.lang.String) throws java.io.UnsupportedEncodingException
public java.lang.String(byte[],int,int,java.nio.charset.Charset)
java.lang.String(char[],int,int,java.lang.Void)
java.lang.String(java.lang.AbstractStringBuilder,java.lang.Void)
public java.lang.String(java.lang.StringBuilder)
public java.lang.String(java.lang.StringBuffer)
java.lang.String(byte[],byte)
public java.lang.String(char[],int,int)
public java.lang.String(char[])
public java.lang.String(java.lang.String)
public java.lang.String()
public java.lang.String(byte[],int,int,java.lang.String) throws java.io.UnsupportedEncodingException
public java.lang.String(byte[],int)
public java.lang.String(byte[],int,int,int)
public java.lang.String(int[],int,int)
----------指定参数公有构造方法----------
public java.lang.String(byte[])
//(4),构建Demo类,并访问其私有构造方法
class Demo{
    private String name;
    private Demo(String name){
        this.name=name;
        System.out.println(name);
    }
}

public class Test {
    public static void main(String[] args){
        Class<?> cls=Demo.class;
        try {
            Constructor constructor=cls.getDeclaredConstructor(String.class);
            constructor.setAccessible(true);//否则报错java.lang.IllegalAccessException
            Object obj=constructor.newInstance("私有");
            System.out.println(obj);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

3.获取类的属性并设置

  1. 获取所有的public的属性
    public Filed[] getFileds()
  2. 获取所有的属性(包括private、public、protected、默认)
    public Filed[] getDeclaredFileds()
  3. 获取单个属性
    public Filed getFiled(String filedname)//公有
    public Filed getDeclaredFiled(String filedname)//公有、私有、受保护、默认
  4. 设置(或暴力设置)属性值
    通过setAccessible(true)方法忽略掉访问修饰符,再通过Constructor对象的set方法设置
    public void set(Object obj,Object value):
    参数说明:1.obj:要设置的字段所在的对象;2.value:要为字段设置的值;

测试代码:

class Demo{
    private String name;
    public Demo(String name){
        this.name=name;
        System.out.println(name);
    }

    @Override
    public String toString() {
        return name;
    }
}

public class Test {
    public static void main(String[] args){
        Class<?> cls=Demo.class;
        try {
            Demo obj=(Demo)cls.getConstructor(String.class).newInstance("设置前");
            Field field=cls.getDeclaredField("name");
            field.setAccessible(true);
            field.set(obj,"设置后");
            System.out.println(obj);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

4.获取类的成员方法并调用

  1. 获取所有的public的方法(包含父类)
    public Method[] getMethods()
  2. 获取所有的成员方法(包括private、public、protected、默认)
    public Method[] getDeclaredMethods()
  3. 获取单个成员方法
    public Method getMethod(String name,Class<?>… parameterTypes)//公有
    public Method getDeclaredMethod(String name,Class<?>… parameterTypes)//公有、私有、受保护、默认
    1.name:方法名;2.parameterTypes:方法形参的Class对象
  4. 调用(或暴力调用)成员方法
    通过setAccessible(true)方法忽略掉访问修饰符,再通过Constructor对象的invoke方法调用
    public Object invoke(Object obj,Object… args)
    1.obj:要调用方法的对象;2.传入方法的参数;

测试代码:

class Son{
    public Son(){
        
    }
    private void fun3(){
        System.out.println("子类私有方法");
    }
    public void fun4(){
        System.out.println("子类公有方法");
    }
}
public class Test1 {
    public static void main(String[] args){
        Class clazz=Son.class;
        try {
            Object obj=clazz.getConstructor().newInstance();
            Method method=clazz.getMethod("fun4");
            method.setAccessible(true);
            method.invoke(obj,null);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

4.反射子类信息时父类信息如何获取?
对Method、Field、Constructor的获取是类似的。
拿Method举例:

  • getMethods()获取所有公有方法,包括父类的公有方法
  • getDeclaredMethods()获取子类的所有方法,但不包括父类的

如果想获取父类所有信息,可以通过getSuperClass()获取父类Class对象,再通过它反射获取信息。
测试代码:

class Father{
    public int x;
    private int y;
    private void fun1(){
        System.out.println("父类私有方法");
    }
    public void fun2(){
        System.out.println("父类公有方法");
    }
}
class Son extends Father{
    public Son(){

    }
    private void fun3(){
        System.out.println("子类私有方法");
    }
    public void fun4(){
        System.out.println("子类公有方法");
    }
}
public class Test1 {
    public static void main(String[] args){
        Class clazz=Son.class;
        Class cla=clazz.getSuperclass();
        System.out.println("----------公有方法---------");
        Method[] methods=clazz.getMethods();
        for(Method method:methods){
            System.out.println(method);
        }

        System.out.println("----------所有方法---------");
        Method[] methods2=clazz.getDeclaredMethods();
        for(Method method:methods2){
            System.out.println(method);
        }
    }
}

结果:

----------公有方法---------
public void Son.fun4()
public void Father.fun2()
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
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()
----------所有方法---------
private void Son.fun3()
public void Son.fun4()

五、反射的其它使用

1.创建数组
Java中数组由一个Object引用。

	try {
            Class clazz=Class.forName("java.lang.String");
            Object array= Array.newInstance(clazz,10);
            Array.set(array,0,"0");
            Array.set(array,1,"1");
            Array.set(array,2,"2");
            Array.set(array,3,"3");
            System.out.println(Array.get(array,2));
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

2.调用main方法

  • 1.5:public Object invoke(Object obj,Object…args)
  • 1.4:public Object invoke(Object obj,Object[] args)

JDK1.4和JDK1.5的invork方法有一些区别,所以在反射类似于main(String[] args)这种参数为数组的方法时,又有特殊处理。

当把一个字符串数组作为参数传递给invoke方法时,jdk1.5肯定要兼容jdk1.4的语法,会按jdk1.4的语法进行处理
在jdk1.4中当传入数组时,会把这个数组拆成单个作用为参数,就会报错

java.lang.IllegalArgumentException: argument type mismatch

解决方法:

  1. method.invoke(null,new Object[]{new String[]{“xxx”}});
    按jdk1.4语法拆开后仍然是String数组,类型就匹配了。

  2. method.invoke(null,(Object)new String[]{“xxx”});
    相当于传入参数是一个对象,不会拆开

class Demo2{
    public static void main(String[] args){
        System.out.println("reflect main");
    }
}

public class Test2 {
    public static void main(String[] args){
        try {
            Class clazz=Class.forName("Demo2");
            Method method=clazz.getMethod("main",String[].class);
            //invoke第一个参数是实例对象,由于是static方法,所以可以是null
            method.invoke(null,(Object)new String[]{"aaa","bbb"});
            method.invoke(null,new Object[]{new String[]{"aaa","bbb"}});
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

3.通过反射越过泛型检查
由于在编译后,List< String>中的String会擦除为它的父类,这里就是Object,所以在运行时,可以通过反射获取add方法并添加Integer对象。

//在List<String>中添加Integer类实例
		List<String> list=new ArrayList<>();
        list.add("1");
        list.add("2");

        Class cla=list.getClass();
        try {
            Method m=cla.getMethod("add",Object.class);
            m.invoke(list,123);
            for(Object obj:list)
                System.out.println(obj);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

六、参考

https://www.jianshu.com/p/58976c8bf1e1
https://blog.csdn.net/mcryeasy/article/details/52344729
https://blog.csdn.net/qq_32452623/article/details/54025185
https://blog.csdn.net/ju_362204801/article/details/90578678
https://www.sczyh30.com/posts/Java/java-reflection-1/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值