Java基础 - 反射


程序中的反射完成的是通过一个实例化对象映射到类,这样在程序运行期间就可以获取类的信息。类的结构都是开发者自定义的,它的信息完全可以看到,为什么还要通过反射来获取呢?注意这里讲的通过反射获取类信息是在程序运行期间,可以直接看到的类结构是静态的,而程序运行起来是动态的,两者是不同的两个概念。我们要做的就是在运行期间获取类的结构然后完成某些特定功能。一句话来简单理解反射:常规情况下是通过类来创建实例化对象的,反射就是将这一过程进行反转,通过实例化对象来获取对应的类信息。

Class类

Class类是反射的基础,反射就是通过对象来获取类的信息。Java是面向对象的编程语言,因此可以将通过反射获取的类信息抽象成对象。

既然是对象,那么就一定有对应的类,这个类就是Class,所以Class类可以理解为专门用来描述其他类的类,Class类的每一个实例对象对应的都是其他类的结构特征。其定义如下。

public final class Class<T> implements java.io.Serializable,GenericDeclaration,Type,AnnotatedElement{}

在外部不能通过构造函数来实例化Class对象,因为Class类只有一个私有的构造函数,如下所示:

private Class(ClassLoader loader, Class<?> arrayComponentType){
    classLoader = loader;
    componentType = arrayComponentType;
}

创建Class实例化对象来描述其他类的结构的方法有三种,

  • 方式一:调用Class的静态方法 forName(String className)创建,将目标类的全限定类名作为参数传入,即可获取对应的Class对象。全限定类名是指包含所在包信息的类名全称,例如 java.lang.String。

  • 方式二:通过目标类的class创建,Java中的每一个类都可以调用类.class 这里的class不是属性,而是叫做 “类字面量”,其作用是获取内存中目标类型class对象的引用。

  • 方式三:通过目标类实例化对象的getClass()方法创建。getClass()方法定义在Object类中,被所有类继承,通过实例化对象获取内存中该类class对象的引用。

三种创建Class实例对象的代码如下

public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        Class c = Class.forName("java.lang.String");
        System.out.println(c);
        System.out.println("------------");
        Class c1 = String.class;
        System.out.println(c1);
        System.out.println("------------");
//        String str = "h";
        String str = new String("h");
        Class c2 = str.getClass();
        System.out.println(c2);
    }
}

// 输出
class java.lang.String
------------
class java.lang.String
------------
class java.lang.String

可以看到结果是打印了3次“class java.lang.String”,其原因是在打印某个对象时,会自动调用它的 toString()方法将该对象转为一个字符串。toString()是定义在Object类中的方法,所有的类都可以继承并进行重写,显然Class类就对toString()方法进行了重写。

在上面的代码中,获取到了三个Class对象,那么这个三个对象是否相等,在内存中创建了几个对象?通过下面的代码进行验证。

public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        Class c = Class.forName("java.lang.String");
        System.out.println(c);
        System.out.println("------------");
        Class c1 = String.class;
        System.out.println(c1);
        System.out.println("------------");
        String str = new String("h");
        Class c2 = str.getClass();
        System.out.println(c2);
        System.out.println("***********************");
        System.out.println(c.hashCode());
        System.out.println(c1.hashCode());
        System.out.println(c2.hashCode());
        System.out.println(c == c1);
        System.out.println(c == c2);
        System.out.println(c1 == c2);
    }
}

// 输出
class java.lang.String
------------
class java.lang.String
------------
class java.lang.String
***********************
1956725890
1956725890
1956725890
true
true
true

从结果可以看到,这三个Class对象是相等的,内存中只有一份。Class的实例化对象是用来描述某个目标类的,而每个目标类在运行时,类在内存中只有一份,所以对应的 Class 对象也只有一份。

获取类结构

反射相关的操作大部分是基于对Class对象的操作,即获取目标类的信息都是通过调用Class的相关方法来完成的。类的信息包括其内部的成员变量、方法、构造函数、继承的父类和实现的接口等。Class类的常用方法如下:

方法 描述
public native boolean isInterface() 判断该类是否为接口
public native boolean isArray() 判断该类是否为数组
public boolean isAnnotation() 判断该类是否为注解
public String getName() 获取该类的全限定类名
public ClassLoader getClassLoader() 获取类加载器
public native Class<? super T> getSuperclass() 获取该类的直接父类
public Package getPackage() 获取该类所在的包
public String getPackageName() 获取该类所在包的名称
public Class<?>[] getInterfaces 获取该类的全部接口
public native int getModifiers() 获取该类的访问权限修饰符
public Field[] getFields() throws SecurityException 获取该类的全部公有成员变量,包括继承父类的和自定义的
public Field[] getDeclaredFields() throws SecurityException 获取该类的所有自定义成员变量
public Field getField(String name) throws NoSuchFieldException,SecurityException 通过名称获取该类的公有成员变量,包括继承自父类的和自定义的
public Field getDeclaredField(String name) throws NoSuchFieldException,SecurityException 通过名称获取该类的自定义成员变量
public Method[] getMethods() throws SecurityException 获取该类的全部公有方法,包括继承自父类的和自定义的
public Method[] getDeclaredMethods() throws SecurityException 获取该类的所有自定义方法
public Method getMethod(String name, Class<T>… parameterTypes) throws NoSuchFieldException,SecurityException 通过名称和参数信息(方法名)获取该类的公有方法,包括继承自父类的和自定义的
public Method getDeclaredMethod(String name, Class<T>… parameterTypes) throws NoSuchFieldException,SecurityException 通过名称和参数信息(方法名)获取该类的自定义方法
public Constructor<?>[] getConstructors() throws SecurityException 获取该类的公有构造函数
public Constructor<?>[] getDeclaredConstructors() throws SecurityException 获取该类的全部构造函数
public Constructor<T> getConstructor(Class<?>… parameterTypes) throws NoSuchFieldException,SecurityException 通过参数信息获取该类的公有构造函数
public Constructor<T> getDeclaredConstructor(Class<?>… parameterTypes) throws NoSuchFieldException,SecurityException 通过参数信息获取该类的私有构造函数

获取类的接口

Class提供了 getInterfaces() 方法来获取目标类的接口。getInterfaces() 方法的返回值是一个 Class类型的数组。一个类是可以同时实现多个接口的,所以需要用数组来保存返回值。

public class People implements Serializable,Comparable<People> {
    @Override
    public int compareTo(People o) {
        return 0;
    }
}
public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        Class c = Class.forName("com.test.a.People");
        Class[] classes = c.getInterfaces();
        for (Class cc : classes){
            System.out.println(cc);
        }
    }
}

// 输出
interface java.io.Serializable
interface java.lang.Comparable

获取父类

Class 提供了getSuperclass()方法来获取目标类的父类。

public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        Class c = Class.forName("com.test.a.People");
        Class superClass = c.getSuperclass();
        System.out.println("直接父类是:" + superClass);
    }
}
// 输出
直接父类是:class java.lang.Object

获取构造函数

Class提供了四个方法获取构造函数,分别是 getConstructors()、getConstructor(Class<?>… parameterTypes)、getDeclaredConstructors()、getDeclaredConstructor(Class<?>… parameterTypes)

public class People extends Main {
    public People(){}
    public People(int id){}
    private People(String str){}
}
public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        Class c = Class.forName("com.test.a.People");
        Constructor[] constructor = c.getConstructors();
        System.out.println("----- 公有构造函数 -----");
        for (Constructor con : constructor){
            System.out.println(con);
        }
        System.out.println("----- 全部构造函数 -----");
        Constructor[] constructors = c.getDeclaredConstructors();
        for (Constructor con : constructors){
            System.out.println(con);
        }
        System.out.println("----- 公有无参构造函数 -----");
        Constructor constructors1 = c.getConstructor(null);
        System.out.println(constructors1);
        System.out.println("----- 私有带参构造函数 -----");
        Constructor declaredCon = c.getDeclaredConstructor(null);
        System.out.println(declaredCon);
    }
}
// 输出
----- 公有构造函数 -----
public com.test.a.People()
public com.test.a.People(int)
----- 全部构造函数 -----
public com.test.a.People()
public com.test.a.People(int)
private com.test.a.People(java.lang.String)
----- 公有无参构造函数 -----
public com.test.a.People()
----- 私有带参构造函数 -----
public com.test.a.People()

获取方法

Class提供了四个方法获取类中的方法,分别是 getMethods()、getMethod(String name, Class<T>… parameterTypes)、getDeclaredMethods()、getDeclaredMethod(String name, Class<T>… parameterTypes)

public class User{
    public void setName(){}
    public String setName(String str){
        return "";
    }
    private String setName(long id){
        return "";
    }
}
public class People extends User {
    public void setId(int id){}
    public int setId(long id){
        return 0;
    }
    private int setId(){
        return 0;
    }
}
public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        Class c = Class.forName("com.test.a.People");
        Constructor[] constructor = c.getConstructors();
        System.out.println("----- 公有方法 -----");
        Method[] methods = c.getMethods();
        for (Method m : methods){
            System.out.println(m);
        }
        System.out.println("----- 本类方法 -----");
        Method[] methods1 = c.getDeclaredMethods();
        for (Method m : methods1){
            System.out.println(m);
        }
        System.out.println("----- 公有方法 包括父类 -----");
        Method method = c.getMethod("setId", long.class);
        Method method1 = c.getMethod("setId", int.class);
        Method method2 = c.getMethod("setName");
        System.out.println(method);
        System.out.println(method1);
        System.out.println(method2);
        System.out.println("----- 本类方法 -----");
        Method method3 = c.getDeclaredMethod("setId");
        System.out.println(method3);
    }
}

获取成员变量

Class提供了四个方法获取成员变量,分别是

public class User{
    public int auage;
    private String augender;
}
public class People extends User {
    public int apid;
    private String apname;
}
public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class c = Class.forName("com.test.a.People");
        System.out.println("----- 公有成员变量 -----");
        Field[] fields = c.getFields();
        for (Field f : fields){
            System.out.println(f);
        }
        System.out.println("----- 本类成员变量 -----");
        Field[] fields1 = c.getDeclaredFields();
        for (Field f : fields1){
            System.out.println(f);
        }
        System.out.println("----- 所有本类和父类公有成员变量 -----");
        Field field = c.getField("apid");
        System.out.println(field);
        System.out.println("----- 所有本类成员变量 -----");
        Field field1 = c.getDeclaredField("apname");
        System.out.println(field1);
    }
}

// 输出
----- 公有成员变量 -----
public int com.test.a.People.apid
public int com.test.a.User.auage
----- 本类成员变量 -----
public int com.test.a.People.apid
private java.lang.String com.test.a.People.apname
----- 所有本类和父类公有成员变量 -----
public int com.test.a.People.apid
----- 所有本类成员变量 -----
private java.lang.String com.test.a.People.apname

反射的应用

反射调用方法

常规情况下,需要先创建实例化对象,然后调用该对象的方法,操作的是实例化对象。反射就是将常规方法进行反转,首先创建实例化对象,然后该对象的方法也抽象成对象,被称之为方法对象。调用方法对象的invoke方法来实现业务需求,并将实例化对象作为参数传入invoke方法中。

public class User{
    public int id;
    private String name;
    // setter、getter方法

    public void showInfo(){
        System.out.println("id: " + this.id);
        System.out.println("姓名: " + this.name);
    }
}
public class Main {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        user.setId(1);
        user.setName("Tom");
        Class c = user.getClass();
        Method method = c.getDeclaredMethod("showInfo", null);
        method.invoke(user, null);
    }
}

// 输出
id: 1
姓名: Tom

从上面的代码结果可以看到,通过invoke()方法来完成目标方法调用,invoke是Method类中定义的方法,该方法的作用是通过反射来执行目标方法。

invoke()方法的参数包括两类,一类是一个Object类型的参数,另一类是一组Object类型的参数。前者表示调用该方法的对象,因为虽然是通过反射机制来执行,但是 Java 中非静态方法的调用都是由对象来完成的。后者是一组可变参数,表示调用该方法需要传入的参数。

invoke()方法的返回值为Object类型,这里用到了多态,因为方法定义时并不知道开发者需要调用的方法返回值是什么类型,所以这里定义为Object,可以指代任意数据类型。上面代码中调用的方法没有返回值,下面演示有返回值的方法。

public class Main {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        user.setId(1);
        user.setName("Tom");
        Class c = user.getClass();
        Method method = c.getDeclaredMethod("getName",null);
//        Object invoke = method.invoke(user, null);
        String invoke = (String) method.invoke(user, null);
        System.out.println(invoke);
    }
}

// 输出
Tom

反射访问成员变量

通过反射机制,可以在程序运行期间访问成员变量,并获取成员变量的相关信息,如名称、数据类型、访问权限等。

public class Main {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Class c = User.class;
        Field[] fields = c.getDeclaredFields();
        for (Field f : fields){
            int modifiers = f.getModifiers();
            Class typeClass = f.getType();
            String name = f.getName();
            System.out.println("成员变量 " + name + " 的数据类型是:" + typeClass.getName() + ",访问权限是:" + getModifiers(modifiers));
        }
    }
    public static String getModifiers(int num){
        String result = "";
        switch(num){
            case 0:
                result = "";
                break;
            case 1:
                result = "public";
                break;
            case 2:
                result = "private";
                break;
            case 4:
                result = "protected";
                break;
        }
        return result;
    }
}
// 输出
成员变量 id 的数据类型是:int,访问权限是:public
成员变量 name 的数据类型是:java.lang.String,访问权限是:private

通过调用 Field 的 set()方法可以对成员变量的值进行修改。public void set(Object obj, Object value),obj表示被修改的对象,value表示修改之后的值。

public class Main {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Class c = User.class;
        Field[] fields = c.getDeclaredFields();
        User user = new User();
        for (Field field : fields){
            if (field.getName().equals("id")){
                field.set(user, 1);
            }
            if (field.getName().equals("name")){
                field.setAccessible(true);
                field.set(user,"Bob");
            }
        }
        System.out.println(user.getId());
        System.out.println(user.getName());
    }
}

field.setAccessible(true);这行代码的作用是使得类中private属性也可以被赋值修改。 私有成员变量在外部是无法访问的,修改私有变量的值会抛出异常,因此可以使用setAccessible(boolean flag)方法进行暴力修改,所谓的暴力修改是指修改private成员变量的访问权限,设置为true时,表示可以修改,false表示不能修改,会抛出异常,默认值为false。

反射调用构造函数

通过调用Constructor类的 newInstance(Object … initargs)来完成对象的创建。

public class Main {
    public static void main(String[] args) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
        Class c = User.class;
        // 获取无参构造函数
        Constructor<User> constructor = c.getConstructor(null);
        User user = constructor.newInstance(null);
        user.showInfo();
        // 获取有参构造函数
        Constructor<User> constructor1 = c.getConstructor(int.class,String.class);
        User user1 = constructor1.newInstance(5,"Alice");
        user1.showInfo();
    }
}

// 输出
id: 0
姓名: null
id: 5
姓名: Alice

动态代理

Java中的动态代理是反射的一个重要应用。在Java程序中,对象所具有的能力都封装成了接口,Java中代理模式的特点是委托类和代理类实现了同样的接口,代理类可以代替委托类完成一些核心业务以外的工作,例如消息预处理、过滤消息以及事后处理消息等。

代理类与委托类之间通过依赖注入进行关联,即在设计程序时需要将委托类定义为代理类的成员变量。代理类本身并不会去执行业务逻辑,而是通过委托类的方法来完成。

代理模式分为静态代理和动态代理。两者的区别在于静态代理需要预先写好代理类的代码,在编译期代理类的class文件就已经生成了;动态代理是指在编译期并没有确定具体的代理类,在程序运行期间根据java代码的指示动态地生成的方式。简单地理解静态代理是预先写好代理类,动态代理是程序运行期间动态生成代理类。很显然动态代理的方式更加灵活,可以很方便对代理类的方法进行统一的处理,而不需要逐一修改每个代理类中的方法。其中静态代理跟反射没有什么直接联系,动态代理是运用反射机制来实现的。

静态代理

public interface Phone {
    public String salePhone();
}
public class Apple implements Phone{
    @Override
    public String salePhone() {
        return "销售apple手机";
    }
}
public class Mi implements Phone{
    @Override
    public String salePhone() {
        return "销售小米手机";
    }
}
public class PhoneProxy implements Phone{
    private Phone phone;
    public PhoneProxy(Phone phone){
        this.phone = phone;
    }
    @Override
    public String salePhone() {
        System.out.print("代理模式 -- ");
        return this.phone.salePhone();
    }
}
public class Main {
    public static void main(String[] args) {
        PhoneProxy phoneProxy = new PhoneProxy(new Apple());
        System.out.println(phoneProxy.salePhone());
        PhoneProxy phoneProxy1 = new PhoneProxy(new Mi());
        System.out.println(phoneProxy1.salePhone());
    }
}

// 输出
代理模式 -- 销售apple手机
代理模式 -- 销售小米手机

上述代码完成了静态代理,其优势在于如果要完成业务扩展,不需要修改委托类,只需要修改代理类中的方法即可,这在分离不同业务的同时保证代码的整洁。同理,可以添加一个代理汽车的程序。

如果现在需要创建一个厂商,可以同时代理销售手机和代理销售汽车。那么使用静态代理是不行的。因为无论那一个代理,都只能代理手机或者只能代理汽车。使用动态代理就可以完成这个需求,前面提到过动态代理是指在程序运行时动态生成代理类,那么这个动态生成的功能是谁来完成的呢?java.lang.reflect包中提供了InvocationHandler接口,通过该接口可以在程序运行期间动态生成代理类。首先,自定义一个类 MyInvocationHandler,实现 InvocationHandler接口,这个类就是动态代理类的模版。

public class MyInvocationHandler implements InvocationHandler {
    private Object object = null;
    public Object bind(Object obj){
        this.object = obj;
        return Proxy.newProxyInstance(MyInvocationHandler.class.getClassLoader(),obj.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.print("厂商代理模式 -- ");
        Object result = method.invoke(object,args);
        return result;
    }
}

MyInvocationHandler类中定义的委托类成员变量的数据类型为Object,它可以接收任意数据类型的委托类,即这个代理商什么样的产品都可以代理,这里用到了多态的机制。MyInvocationHandler类的 bind()方法的作用是返回一个代理对象供外部调用,这个代理对象是通过Proxy类的newProxyInstance()方法来创建的。

newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHander h)方法中参数列表的loader是类加载器,在程序运行期将动态生成的代理类加载到内存中。interfaces是委托类的接口,动态代理机制需要获取到委托类的所有接口信息,以便让动态代理类也具备相同的功能。h表示当前的InvocationHandler对象。

MyInvocationHandler类中还有一个invoke()方法,在该方法中可以获取到委托类的方法对象Method,然后通过反射机制来调用委托对象的业务方法,同时可以添加其他的业务逻辑代码。

public class Main {
    public static void main(String[] args) {
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
        Phone phone = (Phone) myInvocationHandler.bind(new Mi());
        System.out.println(phone.salePhone());
        Car car = (Car) myInvocationHandler.bind(new Suv());
        System.out.println(car.saleCar());
    }
}

// 输出
厂商代理模式 -- 销售小米手机
厂商代理模式 -- 销售suv汽车

文章作者: zerollone
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 zerollone !
  目录