关于java:Java反射说得透彻一些

39次阅读

共计 15029 个字符,预计需要花费 38 分钟才能阅读完成。

[TOC]

很多时候咱们会遇到他人问一个问题:你给我讲一下反射,到底是什么货色?怎么实现的?咱们能用反射来做什么?它有什么优缺点?上面咱们会围绕着这几个问题开展:

一、反射机制是什么?

反射是什么?什么是反?什么是正射?
有反就有正,咱们晓得失常状况,如果咱们心愿创立一个对象,会应用以下的语句:

Person person = new Person();

其实咱们第一次执行下面的语句的时候,JVM 会先加载 Person.class,加载到内存完之后,在办法区 / 堆中会创立了一个Class 对象, 对应这个 Person 类。这里有争议,有人说是在办法区,有些人说是在堆。个人感觉应该 JVM 标准说是在办法区,然而不是强制要求,而且不同版本的 JVM 实现也不一样。具体参考以下链接,这里不做解释:
https://www.cnblogs.com/xy-nb…
而下面失常的初始化对象的办法,也能够说是“正射”, 就是应用 Class 对象创立出一个 Person 对象。

而反射则相同,是依据 Person 对象,获取到 Class 对象,而后能够获取到 Person 类的相干信息,进行初始化或者调用等一系列操作。

运行状态时 ,能够结构任何一个类的对象,获取到任意一个对象所属的类信息,以及这个类的成员变量或者办法,能够调用任意一个对象的属性或者办法。能够了解为具备了 动静加载对象 以及 对对象的根本信息进行分析和应用 的能力。

提供的性能包含:

  • 1. 在运行时判断一个对象所属的类
  • 2. 在运行时结构任意一个类的对象
  • 3. 在运行时获取一个类定义的成员变量以及办法
  • 4. 在运行时调用任意一个对象的办法
  • 5. 生成动静代理

灵便,弱小,能够在运行时拆卸,无需在组件之间进行源代码链接,然而使用不当效率会有影响。所有类的对象都是 Class 的实例。
既然咱们能够对类的全限定名,办法以及参数等进行配置,实现对象的初始化,那就是相当于减少了 java 的可配置性。

这里特地须要明确的一点:类自身也是一个对象,办法也是一个对象,在 Java 外面万物皆可对象,除了根底数据类型 …

二、反射的具体应用

2.1 获取对象的包名以及类名

package invocation;
public class MyInvocation {public static void main(String[] args) {getClassNameTest();
    }
    
    public static void getClassNameTest(){MyInvocation myInvocation = new MyInvocation();
        System.out.println("class:" + myInvocation.getClass());
        System.out.println("simpleName:" + myInvocation.getClass().getSimpleName());
        System.out.println("name:" + myInvocation.getClass().getName());
        System.out.println("package:" +
                "" + myInvocation.getClass().getPackage());
    }
}

运行后果:

class: class invocation.MyInvocation
simpleName: MyInvocation
name: invocation.MyInvocation
package: package invocation

由下面后果咱们能够看到:
1.getClass(): 打印会带着 class+ 全类名
2.getClass().getSimpleName():只会打印出类名
3.getName():会打印全类名
4.getClass().getPackage(): 打印出 package+ 包名

getClass()获取到的是一个对象,getPackage()也是。

2.2 获取 Class 对象

在 java 中,所有皆对象。java 中能够分为两种对象,实例对象和 Class 对象。这里咱们说的获取 Class 对象,其实就是第二种,Class 对象代表的是每个类在运行时的类型信息,指和类相干的信息。比方有一个 Student 类,咱们用 Student student = new Student()new 一个对象进去,这个时候Student 这个类的信息其实就是寄存在一个对象中,这个对象就是 Class 类的对象,而 student 这个实例对象也会和Class 对象 关联起来。
咱们有三种形式能够获取一个类在运行时的 Class 对象,别离是

  • Class.forName(“com.Student”)
  • student.getClass()
  • Student.class

实例代码如下:

package invocation;

public class MyInvocation {public static void main(String[] args) {getClassTest();
    }
    public static void getClassTest(){
        Class<?> invocation1 = null;
        Class<?> invocation2 = null;
        Class<?> invocation3 = null;
        try {
            // 最罕用的办法
            invocation1 = Class.forName("invocation.MyInvocation");
        }catch (Exception ex){ex.printStackTrace();
        }
        invocation2 = new MyInvocation().getClass();
        invocation3 = MyInvocation.class;
        System.out.println(invocation1);
        System.out.println(invocation2);
        System.out.println(invocation3);
    }
}

执行的后果如下, 三个后果一样:

class invocation.MyInvocation
class invocation.MyInvocation
class invocation.MyInvocation

2.3 getInstance()获取指定类型的实例化对象

首先咱们有一个 Student 类,前面都会沿用这个类,将不再反复。

class Student{
    private int age;

    private String name;

    public Student() {}
    public Student(int age) {this.age = age;}

    public Student(String name) {this.name = name;}
    
    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }

    public int getAge() {return age;}

    public void setAge(int age) {this.age = age;}

    public String getName() {return name;}

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

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }

咱们能够应用 getInstance() 办法结构出一个 Student 的对象:

    public static void getInstanceTest() {
        try {Class<?> stduentInvocation = Class.forName("invocation.Student");
            Student student = (Student) stduentInvocation.newInstance();
            student.setAge(9);
            student.setName("Hahs");
            System.out.println(student);

        }catch (Exception ex){ex.printStackTrace();
        }
    }
    
    
输入后果如下:Student{age=9, name='Hahs'}

然而如果咱们勾销不写 Student 的无参构造方法呢?就会呈现上面的报错:

java.lang.InstantiationException: invocation.Student
    at java.lang.Class.newInstance(Class.java:427)
    at invocation.MyInvocation.getInstanceTest(MyInvocation.java:40)
    at invocation.MyInvocation.main(MyInvocation.java:8)
Caused by: java.lang.NoSuchMethodException: invocation.Student.<init>()
    at java.lang.Class.getConstructor0(Class.java:3082)
    at java.lang.Class.newInstance(Class.java:412)
    ... 2 more

这是因为咱们重写了构造方法,而且是有参构造方法,如果不写构造方法,那么每个类都会默认有无参构造方法,重写了就不会有无参构造方法了,所以咱们调用 newInstance() 的时候,会报没有这个办法的谬误。值得注意的是,newInstance()是一个无参构造方法。

2.4 通过构造函数对象实例化对象

除了 newInstance() 办法之外,其实咱们还能够通过构造函数对象获取实例化对象,怎么了解?这里只构造函数对象,而不是构造函数,也就是构造函数其实就是一个对象,咱们先获取构造函数对象,当然也能够应用来实例化对象。

能够先获取一个类的所有的构造方法,而后遍历输入:

    public static void testConstruct(){
        try {Class<?> stduentInvocation = Class.forName("invocation.Student");
            Constructor<?> cons[] = stduentInvocation.getConstructors();
            for(int i=0;i<cons.length;i++){System.out.println(cons[i]);
            }

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

输入后果:

public invocation.Student(int,java.lang.String)
public invocation.Student(java.lang.String)
public invocation.Student(int)
public invocation.Student()

取出一个构造函数咱们能够获取到它的各种信息,包含参数,参数个数,类型等等:

    public static void constructGetInstance() {
        try {Class<?> stduentInvocation = Class.forName("invocation.Student");
            Constructor<?> cons[] = stduentInvocation.getConstructors();
            Constructor constructors = cons[0];
            System.out.println("name:" + constructors.getName());
            System.out.println("modifier:" + constructors.getModifiers());
            System.out.println("parameterCount:" + constructors.getParameterCount());
            System.out.println("结构参数类型如下:");
            for (int i = 0; i < constructors.getParameterTypes().length; i++) {System.out.println(constructors.getParameterTypes()[i].getName());
            }
        } catch (Exception ex) {ex.printStackTrace();
        }
    }

输入后果,modifier是权限修饰符,1 示意为public,咱们能够晓得获取到的构造函数是两个参数的,第一个是 int,第二个是 String 类型,看来获取进去的程序并不一定是咱们书写代码的程序。

name: invocation.Student
modifier: 1
parameterCount: 2
结构参数类型如下:int
java.lang.String

既然咱们能够获取到构造方法这个对象了,那么咱们可不可以通过它去结构一个对象呢?答案必定是能够!!!
上面咱们用不同的构造函数来创建对象:

    public static void constructGetInstanceTest() {
        try {Class<?> stduentInvocation = Class.forName("invocation.Student");
            Constructor<?> cons[] = stduentInvocation.getConstructors();
            // 一共定义了 4 个结构器
            Student student1 = (Student) cons[0].newInstance(9,"Sam");
            Student student2 = (Student) cons[1].newInstance("Sam");
            Student student3 = (Student) cons[2].newInstance(9);
            Student student4 = (Student) cons[3].newInstance();
            System.out.println(student1);
            System.out.println(student2);
            System.out.println(student3);
            System.out.println(student4);

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

输入如下:

Student{age=9, name='Sam'}
Student{age=0, name='Sam'}
Student{age=9, name='null'}
Student{age=0, name='null'}

结构器的程序咱们是必须一一针对的,要不会报一下的参数不匹配的谬误:

java.lang.IllegalArgumentException: argument type mismatch
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at invocation.MyInvocation.constructGetInstanceTest(MyInvocation.java:85)
    at invocation.MyInvocation.main(MyInvocation.java:8)

2.5 获取类继承的接口

通过反射咱们能够获取接口的办法,如果咱们晓得某个类实现了接口的办法,同样能够做到通过类名创建对象调用到接口的办法。

首先咱们定义两个接口,一个InSchool:

public interface InSchool {public void attendClasses();
}

一个AtHome:

public interface AtHome {public void doHomeWork();
}

创立一个实现两个接口的类Student.java

public class Student implements AtHome, InSchool {public void doHomeWork() {System.out.println("I am a student,I am doing homework at home");
    }

    public void attendClasses() {System.out.println("I am a student,I am attend class in school");
    }
}

测试代码如下:

public class Test {public static void main(String[] args) throws Exception {Class<?> studentClass = Class.forName("invocation.Student");
        Class<?>[] interfaces = studentClass.getInterfaces();
        for (Class c : interfaces) {
            // 获取接口
            System.out.println(c);
            // 获取接口外面的办法
            Method[] methods = c.getMethods();
            // 遍历接口的办法
            for (Method method : methods) {
                // 通过反射创建对象
                Student student = (Student) studentClass.newInstance();
                // 通过反射调用办法
                method.invoke(student, null);
            }
        }
    }
}

后果如下:

能够看出其实咱们能够获取到接口的数组,并且外面的程序是咱们继承的程序,通过接口的Class 对象,咱们能够获取到接口的办法,而后通过办法反射调用实现类的办法,因为这是一个无参数的办法,所以只须要传 null 即可。

2.6 获取父类相干信息

次要是应用 getSuperclass() 办法获取父类,当然也能够获取父类的办法,执行父类的办法, 首先创立一个Animal.java:

public class Animal {public void doSomething(){System.out.println("animal do something");
    }
}

Dog.java继承于Animal.java

public class Dog extends Animal{public void doSomething(){System.out.println("Dog do something");
    }
}

咱们能够通过反射创立 Dog 对象,获取其父类 Animal 以及创建对象,当然也能够获取 Animal 的默认父类Object

public class Test {public static void main(String[] args) throws Exception {Class<?> dogClass = Class.forName("invocation02.Dog");
        System.out.println(dogClass);
        invoke(dogClass);

        Class<?> animalClass = dogClass.getSuperclass();
        System.out.println(animalClass);
        invoke(animalClass);

        Class<?> objectClass = animalClass.getSuperclass();
        System.out.println(objectClass);
        invoke(objectClass);
    }

    public static void invoke(Class<?> myClass) throws Exception {Method[] methods = myClass.getMethods();
        // 遍历接口的办法
        for (Method method : methods) {if (method.getName().equalsIgnoreCase("doSomething")) {
                // 通过反射调用办法
                method.invoke(myClass.newInstance(), null);
            }
        }
    }
}

输出如下:

2.7 获取以后类的私有属性和公有属性以及更新

创立一个 Person.java, 外面有动态变量,非动态变量,以及publicprotected,private 不同润饰的属性。

public class Person {

    public static String type ;

    private static String subType ;

    // 名字(公开)public String name;

    protected String gender;

    private String address;

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

应用 getFields() 能够获取到 public 的属性,包含 static 属性,应用 getDeclaredFields() 能够获取所有申明的属性,不论是 publicprotected,private 不同润饰的属性。

批改 public 属性, 只须要 field.set(object,value) 即可,然而 private 属性不能间接 set,否则会报以下的谬误。

Exception in thread "main" java.lang.IllegalAccessException: Class invocation03.Tests can not access a member of class invocation03.Person with modifiers "private"
    at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
    at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
    at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
    at java.lang.reflect.Field.set(Field.java:761)
    at invocation03.Tests.main(Tests.java:21)

那么须要怎么做呢?private 默认是不容许外界操作其值的,这里咱们能够应用field.setAccessible(true);,相当于关上了操作的权限。

那 static 的属性批改和非 static 的一样,然而咱们怎么获取呢?
如果是 public 润饰的,能够间接用类名获取到,如果是 private 润饰的,那么须要应用 filed.get(object), 这个办法其实对下面说的所有的属性都能够的。
测试代码如下

public class Tests {public static void main(String[] args) throws Exception{Class<?> personClass = Class.forName("invocation03.Person");
        Field[] fields = personClass.getFields();
        // 获取公开的属性
        for(Field field:fields){System.out.println(field);
        }
        System.out.println("=================");
        // 获取所有申明的属性
        Field[] declaredFields = personClass.getDeclaredFields();
        for(Field field:declaredFields){System.out.println(field);
        }
        System.out.println("=================");
        Person person = (Person) personClass.newInstance();
        person.name = "Sam";
        System.out.println(person);

        // 批改 public 属性
        Field fieldName = personClass.getDeclaredField("name");
        fieldName.set(person,"Jone");

        // 批改 private 属性
        Field addressName = personClass.getDeclaredField("address");
        // 须要批改权限
        addressName.setAccessible(true);
        addressName.set(person,"东风路 47 号");
        System.out.println(person);

        // 批改 static 动态 public 属性
        Field typeName = personClass.getDeclaredField("type");
        typeName.set(person,"人类");
        System.out.println(Person.type);

        // 批改动态 private 属性
        Field subType = personClass.getDeclaredField("subType");
        subType.setAccessible(true);
        subType.set(person,"黄种人");
        System.out.println(subType.get(person));
    }
}

后果:

从后果能够看出,不论是 public,还是protectedprivate 润饰的,咱们都能够通过反射对其进行查问和批改,不论是动态变量还是非动态变量。
getDeclaredField()能够获取到所有申明的属性,而 getFields() 则只能获取到 public 的属性。对于非 public 的属性,咱们须要批改其权限能力拜访和批改:field.setAccessible(true)

获取属性值须要应用 field.get(object),值得注意的是: 每个属性,其自身就是对象

2.8 获取以及调用类的私有 / 公有办法

既然能够获取到私有属性和公有属性,那么我想,执行私有办法和公有办法应该都不是什么问题?

那上面咱们一起来学习一下 …

先定义一个类,蕴含各种修饰符,以及是否蕴含参数,是否为静态方法,Person.java:

public class Person {
    // 非动态私有无参数
    public void read(){System.out.println("reading...");
    }

    // 非动态私有无参数有返回
    public String getName(){return "Sam";}

    // 非动态私有带参数   
    public int readABookPercent(String name){System.out.println("read"+name);
        return 80;
    }

    // 公有有返回值
    private String getAddress(){return "东方路";}

    // 私有动态无参数无返回值
    public static void staticMethod(){System.out.println("static public method");
    }

    // 私有动态有参数
    public static void staticMethodWithArgs(String args){System.out.println("static public method:"+args);
    }

    // 公有静态方法
    private static void staticPrivateMethod(){System.out.println("static private method");
    }
}

首先咱们来看看获取外面所有的办法:

public class Tests {public static void main(String[] args) throws Exception {Class<?> personClass = Class.forName("invocation03.Person");
        Method[] methods = personClass.getMethods();
        for (Method method : methods) {System.out.println(method);
        }

        System.out.println("=============================================");
        Method[] declaredMethods = personClass.getDeclaredMethods();
        for (Method method : declaredMethods) {System.out.println(method);
        }
    }
}

后果如下:

咦,咱们发现 getMethods() 的确能够获取所有的私有的办法,然而有一个问题,就是他会把父类的也获取到,也就是下面图片绿色框外面的,咱们晓得所有的类默认都继承了 Object 类,所以它把 Object 的那些办法都获取到了。
getDeclaredMethods 的确能够获取到私有和公有的办法,不论是动态还是非动态,然而它是获取不到父类的办法的。

那如果咱们想调用办法呢?先试试调用非静态方法:

public class Tests {public static void main(String[] args) throws Exception {Class<?> personClass = Class.forName("invocation03.Person");
        Person person = (Person) personClass.newInstance();
        Method[] declaredMethods = personClass.getDeclaredMethods();
        for (Method method : declaredMethods) {if(method.getName().equalsIgnoreCase("read")){method.invoke(person,null);
                System.out.println("===================");
            }else if(method.getName().equalsIgnoreCase("getName")){System.out.println(method.invoke(person,null));
                System.out.println("===================");
            }else if(method.getName().equalsIgnoreCase("readABookPercent")){System.out.println(method.invoke(person,"Sam"));
                System.out.println("===================");
            }
        }

    }
}

后果如下,能够看出 method.invoke(person,null); 是调用无参数的办法,而 method.invoke(person,"Sam") 则是调用有参数的办法,要是有更多参数,也只须要在外面多加一个参数即可,返回值也同样能够获取到。

那么 private 办法呢?咱们照着来试试,试试就试试,who 怕 who?

public class Tests {public static void main(String[] args) throws Exception {Class<?> personClass = Class.forName("invocation03.Person");
        Person person = (Person) personClass.newInstance();
        Method[] declaredMethods = personClass.getDeclaredMethods();
        for (Method method : declaredMethods) {if(method.getName().equalsIgnoreCase("getAddress")){method.invoke(person,null);
            }
        }

    }
}

后果报错了:

Exception in thread "main" java.lang.IllegalAccessException: Class invocation03.Tests can not access a member of class invocation03.Person with modifiers "private"
    at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
    at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
    at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
    at java.lang.reflect.Method.invoke(Method.java:491)
    at invocation03.Tests.main(Tests.java:13)

一看就是没有权限,小局面,不要慌,我来操作一波, 只有加上

method.setAccessible(true);

哦豁,完满解决了 …

那么问题来了,下面说的都是非动态的,我就想要调用动态的办法。
当然用下面的办法,对象也能够间接调用到类的办法的:

一点问题都没有,为什么输入后果有几个 null, 那是因为这函数是无返回值的呀,笨蛋 …

如果我不想用遍历办法的形式,再去判断怎么办?能不能间接获取到我想要的办法啊?那答案必定是能够啊。

public class Tests {public static void main(String[] args) throws Exception {Class<?> personClass = Class.forName("invocation03.Person");
        Person person = (Person) personClass.newInstance();
        Method method = personClass.getMethod("readABookPercent", String.class);
        method.invoke(person, "唐诗三百首");
    }
}

后果和下面调用的齐全一样,图我就不放了,就一行字。要是这个办法没有参数呢?那就给一个 null 就能够啦。或者不给也能够。

public class Tests {public static void main(String[] args) throws Exception {Class<?> personClass = Class.forName("invocation03.Person");
        Person person = (Person) personClass.newInstance();
        Method method = personClass.getMethod("getName",null);
        System.out.println(method.invoke(person));
    }
}

三、反射的优缺点

3.1 长处

反射能够在不晓得会运行哪一个类的状况下,获取到类的信息,创建对象以及操作对象。这其实很不便于拓展,所以反射会是框架设计的灵魂,因为框架在设计的时候,为了升高耦合度,必定是须要思考拓展等性能的,不能将类型写死,硬编码。

升高耦合度,变得很灵便,在运行时去确定类型,绑定对象,体现了多态性能。

3.2 毛病

这么好用,没有毛病?怎么可能!!!无利就有弊,事物都是有双面性的。
即便性能很弱小,然而反射是须要动静类型的,JVM没有方法优化这部分代码,执行效率绝对间接初始化对象较低。个别业务代码不倡议应用。

反射能够批改权限,比方下面拜访到 private 这些办法和属性,这是会毁坏封装性的,有安全隐患,有时候,还会毁坏单例的设计。

反射会使代码变得复杂,不容易保护,毕竟代码还是要先写给人看的嘛,逃~

此文章仅代表本人(本菜鸟)学习积攒记录,或者学习笔记,如有侵权,请分割作者删除。人无完人,文章也一样,文笔稚嫩,在下不才,勿喷,如果有谬误之处,还望指出,感激不尽~

技术之路不在一时,山高水长,纵使迟缓,驰而不息。

公众号:「秦怀杂货店」

正文完
 0