欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【Java SE】反射 Reflect

程序员文章站 2022-06-09 20:39:59
...

反射,它就像是一种魔法,引入运行时自省能力,赋予了 Java 语言令人意外的活力,通过运行时操作元数据或对象,Java 可以灵活地操作运行时才能确定的信息。而动态代理,则是延伸出来的一种广泛应用于产品开发中的技术,很多繁琐的重复编程,都可以被动态代理机制优雅地解决。

反射一般很少使用,基本都是在封装框架的时候会用到,我们可以根据反射获取对象内容信息(属性,方法),从而生成API文档,既然可以获取所有对象的内容,我们可以做插件功能,获取里面的方法,随意调用,当然也有缺点,本该有的封装性会被破坏。

  • 提问:什么是反射?
    反射就是【动态加载对象】,并对对象进行剖析。
    在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
    对于任意一个对象,都能够调用它的任意一个方法;
    这种动态获取信息以及动态调用对象方法的功能成为Java反射机制。

  • 提问:反射的优缺点?
    优点:
    反射提高了程序的灵活性和扩展性,在底层框架中用的比较多,业务层面的开发过程中尽量少用。
    缺点:
    性能不好
    反射是一种解释操作,用于字段和方法接入时要远慢于直接代码;
    程序逻辑有影响
    使用反射操作会模糊化程序的内部逻辑,从代码的维护角度来讲,我们更希望在源码中看到程序的逻辑,反射相当于绕过了源码的方式,因此会带来维护难度比较大的问题。

  • 提问:反射的使用场景有哪些?
    实现RPC框架;
    实现ORM框架;
    拷贝属性值(BeanUtils.copyProperties);
    ——————————————————————

例子:

public class Student {
    private long id;
    private String name;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

获取类中的所有方法

public static void main(String[] args) {
    try {
        Class<?> clz = Class.forName("fs.Student");
        Method[] methods = clz.getMethods();
        for (Method method : methods) {
            System.out.println("方法名:" + method.getName());
        }
    } catch (ClassNotFoundException e) {
            e.printStackTrace();
    }
}

如果只需要获取加载类中的方法,不要父类的方法,可以使用下面的代码:
Method[] methods = clz.getDeclaredMethods();

通过反射来【调用方法】:

try {
    Class<?> clz = Class.forName("fs.Student");
    Student stu = (Student) clz.newInstance();
    System.out.println(stu.getName());
    Method method = clz.getMethod("setName", String.class);
    method.invoke(stu, "名字");
    System.out.println(stu.getName());
} catch (Exception e) {
    e.printStackTrace();
}

获取类中的所有属性

Class<?> clz = Class.forName("fs.Student");
Field[] fields = clz.getFields();
for (Field field : fields) {
    System.out.println("属性名:" + field.getName());
}


Class类的使用

  • 1.在面向对象的世界里,万事万物皆对象

但在Java中,有两项事物不是对象,他们是:
(1).普通数据类型;
比如int i = 5不是对象;但是他有对应包装类Integer来弥补这个缺陷;
(2).静态成员;

提问 - 类是不是对象?如果是,是谁的对象?
类是对象!是java.lang.Class类的实例对象;
These is a class named Class.

【重要】
比如说有个类叫Foo.class
他有【实例对象】foo1Foo foo1 = new Foo();
他也有【类类型】c1Class c1 = Foo.class;

【类类型】:
因为万事万物皆为对象;
所以类也是对象,是java.lang.Class的对象(类类型);

类类型有三种创建方法:
1.Class c1 = Foo.class;
2.Class c2 = foo1.getClass();
3.需要加上try catch语句块;

Class c3 = null;
c3 = Class.forName("com.xxx.xxx.Foo")

——————————————————————————
例子:

class Foo{
    void print(){
        System.out.println("foo...");
    }
}
public class Test {
    public static void main(String[] args) {
        Foo foo1 = new Foo();

        // 方法1  -> 任何一个类都有一个隐含的静态成员变量class
        Class c1 = Foo.class;

        // 方法2 -> 应知道该类的对象通过getClass方法
        Class c2 = foo1.getClass();

        System.out.println(c1 == c2);
        // 一个类只有一个类类型,类类型的实例对象都是相等的;返true

        // 方法3 ->

        Class c3 = null;
        try {
            c3 = Class.forName("com.example.springtest.Controller.Foo");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        System.out.println(c2 == c3);
        // 返true
    }
}

class Foo{}

另外,我们完全可以通过类的类类型创建该类的实例对象!
即,通过c1 c2 c3来创建Foo的实例对象;
【前提:必须要有无参数的构造方法;】

public class Test {
    public static void main(String[] args) {

        Class c1 = Foo.class;

        try {
            Foo foo = (Foo)c1.newInstance();
            foo.print();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

输出:
foo...

动态类加载

  • 提问:什么是动态加载?静态加载?
    Class,forName("类的全程")
    不及表示了类的类类型,还代表了动态加载类;

  • 提问:编译、运行的区别?
    (高级工具帮我们区分了,导致我们可能忽视了这一块,但这很重要)
    【编译时刻】:加载类是【静态加载类】;
    【运行时刻】Test:加载类是【动态加载类】;

——————————————————————————————

我们用记事本和Terminal操作以下步骤(不用IDE)

class Office{
	public static void main(String[] args){
	    if("Word".equals(args[0])){
	        Word w = new Word();
	        w.start();
	    }
	    if("Excel".equals(args[0])){
	        Excel e = new Excel();
	        e.start();
	    }
	}
}

编译:javac Office.java
报错:
找不到Word和Excel这两个类!
两个start()找不到!

我们试一试,把Word类创建出来;

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

编译:javac Word.java
javac Office.java
只报两个错了:Excel找不到和start()

我们可能理所当然觉得这很正常;
但是!只有Excel不存在,而Word已经存在的!
假设我只想用Word,程序用不了;

因为程序现在new对象是【静态加载】;
在【编译时刻】需要加载所有可能使用到的类,不管你用不用;

然后实际中我们希望如果Word存在,我们就能用;
假设程序有100个功能,只要有一个功能,其他99个都用不了,这是不希望发生的;
我们希望用哪个就加载哪个,不用就不加载!!!
———————————————
通过【动态加载类】可以解决以上问题;

public class OfficeBetter{
	public static void main(String[] args){
	    try{
	        Class c = Class.forName(args[0]);
	    }catch(Exception e){
	        e.printStackTrace();
	    }
	}
}

javac OfficeBetter.java
OK
运行里面的Excel类(不存在Excel类时)java OfficeBetter Excel
报错:ClassNotFoundException

说明运行动态加载,不会管编译,只管运行;

往里面用类类型创建Excel对象就可以了;
但是如果运行了Word怎么办?

public class OfficeBetter{
	public static void main(String[] args){
	    try{
	        Class c = Class.forName(args[0]);
	        OfficeAble oa = (OfficeAble)c.newInstance();
	        oa.start();
	    }catch(Exception e){
	        e.printStackTrace();
	    }
	}
}
interface OfficeAble{
	public void start();
}

制定一个接口标准;
若要运行Word,让Word类implements OfficeAble。。。如下;

class Word implements OfficeAble{
	public void start(){
	    System.out.println("Word...starts...");
	}
}

javac Word.java
javac OfficeBetter.java
java OfficeBetter Word
返回:Word…starts…

动态加载;
比如升级QQ新版本,老版本不需要重新编译;
短期升级用动态加载;

功能性的类尽量使用动态加载;
——————————————————————————————


获取 方法信息

基本的数据类型、void关键字都存在类类型;
注意:double Double这样的两种数据类型代表的意义完全不一样;

public class Test {
    public static void main(String[] args) {
        Class c1 = int.class;
        System.out.println(c1.getName());

        Class c2 = String.class;
        System.out.println(c2.getName());
        System.out.println(c2.getSimpleName());

        Class c3 = double.class;

        Class c4 =  Double.class;

        Class c5 = void.class;
        System.out.println(c5.getName());
    }
}

输出:
int
java.lang.String
String
void

public class Test {
    public static void printClassMessage(Object obj){
        Class c = obj.getClass();
        System.out.println("类的名称是:"+c.getName());
        Method[] ms = c.getMethods();
        // .getDeclaredMethods()获取的是自己声明的方法

        for(int i = 0;i<ms.length;i++){
            // 得到方法的返回值的类类型
            Class returnType = ms[i].getReturnType();
            System.out.print(returnType.getName()+" ");
            // 得到方法名称
            System.out.print(ms[i].getName());
            // 获取参数类型 得到的是参数列表的类型的类类型
            Class[] paramTypes = ms[i].getParameterTypes();
            for (Class class1:paramTypes){
                System.out.print("("+class1.getName()+",");
            }
            System.out.println(")");
        }
    }
}

public class Test2 {
    public static void main(String[] args) {

        // 拿到String的所有方法

        String s = "hello";
        Test.printClassMessage(s); // 调用Test类的printClassMessage()方法
    }
}
输出:
类的名称是:java.lang.String
boolean equals(java.lang.Object,)
java.lang.String toString)
int hashCode)
int compareTo(java.lang.Object,)
int compareTo(java.lang.String,)
int indexOf(java.lang.String,(int,)
int indexOf(int,)
int indexOf(java.lang.String,)
int indexOf(int,(int,)
。。。。。。

获取 成员变量构造函数信息

成员变量也是对象;
java.lang.reflect.Field类封装了关于成员变量的操作;

getFields()方法获取的是所有public的成员变量的信息;
getDeclaredFields()获取的是自己声明的成员变量信息(包括私有、公有);

Test类中:

    public static void printFieldMessage(Object obj) {
        Class c = obj.getClass();
        Field[] fs = c.getDeclaredFields();
        for(Field field:fs){
            Class fieldType = field.getType();
            String typeName = fieldType.getName();
            String fieldName = field.getName();
            System.out.println(typeName+","+fieldName);
        }
    }
public class Test3 {
    public static void main(String[] args) {
        Test.printFieldMessage("hello"); // 或者写 (new Integer(1));
    }
}
输出:
[C,value  // [C 是数组反射的内容
int,hash
long,serialVersionUID
[Ljava.io.ObjectStreamField;,serialPersistentFields
java.util.Comparator,CASE_INSENSITIVE_ORDER

——————————————————————————————

——————————————————————————————


方法反射 的基本操作

1.如何获取某个方法
方法的名称和方法的参数列表才能为一决定某个方法;

2.方法反射的操作
method.invoke(对象,参数列表)
——————————————————————————————

public class MethodDemo1 {
    public static void main(String[] args) {
    
        // 1.获取print方法;
        //   获取方法就是获取类的信息;获取类的信息首先要获取类的类类型
        A a1 = new A();
        Class c = a1.getClass();
        
        // 2.获取方法
        //   要先获取:名称+参数列表
        //   getMethod 获取public方法;getDeclaredMethod 获取公共和私有方法
        try {
            Method m = c.getMethod("print",new Class[]{int.class,int.class});
            // 因为第二个参数有...所以是可变的,也可以写成:int.class,int.class

            // 3.方法的反射操作
            //   如果不用反射,可以这么写:a1.print(10,20);
            //   反射操作:用m对象来进行方法调用;和a1.print调用效果完全相同;
            Object o = m.invoke(a1,new Object[]{10,20});
            // 也可以写:Object o = m.invoke(a1,10,20);

            System.out.println("========================");

            Method m1 = c.getMethod("print",String.class,String.class);
            o = m1.invoke(a1,"hello","WORLD");

            System.out.println("========================");

            Method m2 = c.getMethod("print");
            o = m2.invoke(a1);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class A{

    public void print(int a,int b){
        System.out.println(a+b);
    }

    public void print(String a,String b){
        System.out.println(a.toUpperCase()+b.toLowerCase());
    }

    public void print(){
        System.out.println("helloworld");
    }
}

——————————————————————————————
语法上会更困难和麻烦,但是需要先记下来,才能发现它的好处;


通过反射了解 集合范型 的本质

通过Class、Method认识范型的本质;

public class MethodDemo4 {
    public static void main(String[] args) {
        ArrayList list1 = new ArrayList();
        ArrayList<String> list2 = new ArrayList<>();

        list2.add("hello");

        Class c1 = list1.getClass();
        Class c2 = list2.getClass();

        System.out.println(c1 == c2);  // 返true
        // 反射操作都是编译后的操作
        // 返true 说明:编译后集合的范型是【去范型化】的
        // 结论:Java中集合的范型 是防止错误输入的,只在编译阶段有效;绕过编译就无效了
        // 验证:通过方法的反射来操作,绕过编译

        // 本来c2是有范型的,不能添加int参数
        // 之前添加了一个String参数,现在试试反射后添加一个int:20,看能否添加成功
        try{
            Method m = c2.getMethod("add",Object.class);
            m.invoke(list2,20); // 绕过了范型
            System.out.println(list2.size());
            System.out.println(list2);
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}
true
2
[hello, 20]