Java笔记八——反射

Class类

class是JVM在执行过程中动态加载的。每加载一种class,创建一个class类型(这个类型的名字就是class)的唯一对象(实例/instance),并关联起来,该实例保存着该class的所有信息,包括类名、报名、父类、实现的接口、所有方法、字段等,可以通过该实例获得class对应的所有信息,这就叫反射,实例反射出class信息。

获取class的class实例

  • 通过一个class的静态变量class获取
    Class cls = String.class;
  • 通过实例变量提供的getClass()方法获取:
String s = "Hello";//String类的实例s
Class cls = s.getClass();
  • 知道完整类名(包名.类名),使用静态方法Class。forName()获取
    Class cls = Class.forName("java.lang.String");
    JDBC常用此方法加载数据库驱动
    Class.forName(driver);
    注意一下Class实例比较和instanceof的差别:
Integer n = new Integer(123);

boolean b1 = n instanceof Integer; // true,因为n是Integer类型
boolean b2 = n instanceof Number; // true,因为n是Number类型的子类

boolean b3 = n.getClass() == Integer.class; // true,因为n.getClass()返回Integer.class
boolean b4 = n.getClass() == Number.class; // false,因为Integer.class!=Number.class

instanceof不但匹配指定类型,还匹配该类型的父类。==判断class实例是精确判断数据类型,通常用instanceof判断数据类型,因为面向抽象编程就不关心具体子类型。

动态加载

JVM在执行Java程序
的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载。

小结
JVM为每个加载的class及interface创建了对应的Class实例来保存class及interface的所有信息;
获取一个class对应的Class实例后,就可以获取该class的所有信息;
通过Class实例获取class信息的方法称为反射(Reflection);
JVM总是动态加载class,可以在运行期根据条件来控制加载class。

访问字段

Class类提供了以下几个方法来获取字段:以String类的value字段为例

Field getField(name):根据字段名获取某个public的field(包括父类)
Field f = String.class.get
Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
Field[] getFields():获取所有public的field(包括父类)
Field[] getDeclaredFields():获取当前类的所有field(不包括父类)

publpublic class Main {
    public static void main(String[] args) throws Exception {
        Class stdClass = Student.class;
        // 获取public字段"score":
        System.out.println(stdClass.getField("score"));
        // 获取继承的public字段"name":
        System.out.println(stdClass.getField("name"));
        // 获取private字段"grade":
        System.out.println(stdClass.getDeclaredField("grade"));
    }
}
class Student extends Person {
    public int score;
    private int grade;
}
class Person {
    public String name;
}
/*运行结果
public int Student.score
public java.lang.String Person.name
private int Student.grade
*/

获取字段值

对于一个Person实例,我们可以先拿到name字段对应的Field,再获取这个实例的name字段的值:

// reflection
import java.lang.reflect.Field;

public class Main {

    public static void main(String[] args) throws Exception {
        Object p = new Person("Xiao Ming");
        Class c = p.getClass();
        Field f = c.getDeclaredField("name");
        f.setAccessible(true);//因为main类无法访问Person的private字段,所以写这句
        Object value = f.get(p);
        System.out.println(value); // "Xiao Ming"
    }
}

class Person {
    private String name;
    public Person(String name) {
        this.name = name;
    }
}

设置字段值

通过Field.set(Object, Object)实现

Person p = new Person("Xiao Ming");
Class c = p.getClass();
Field f = c.getDeclareField("name");
f.setAccessible(true);
f.set(p, "Xiao Hong");

调用方法

通过Class实例获取所有的Method信息

  • Method getMethod(name, Class…):获取某个public的Method(包括父类)
  • Method getDeclaredMethod(name, Class…):获取当前类的某个Method(不包括父类)
  • Method[] getMethods():获取所有public的Method(包括父类)
  • Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)

一个Method对象包含一个方法的所有信息:
getName():返回方法名称,例如:“getScore”;
getReturnType():返回方法返回值类型,也是一个Class实例,例如:String.class;
getParameterTypes():返回方法的参数类型,是一个Class数组,例如:{String.class, int.class};
getModifiers():返回方法的修饰符,它是一个int,不同的bit表示不同的含义。

用反射来调用substring方法

import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        // String对象:
        String s = "Hello world";
        // 获取String substring(int)方法,参数为int:
        Method m = String.class.getMethod("substring", int.class);
        // 在s对象上调用该方法并获取结果:
        String r = (String) m.invoke(s, 6);
        // 打印调用结果:
        System.out.println(r);//输出world
    }
}

调用静态方法

调用静态方法无需指定实例对象,以Interger.parsenInt(String)为例

import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        // 获取Integer.parseInt(String)方法,参数为String:
        Method m = Integer.class.getMethod("parseInt", String.class);
        // 调用该静态方法并获取结果:
        Integer n = (Integer) m.invoke(null, "12345");//第一个参数为null
        // 打印调用结果:
        System.out.println(n);
    }
}

调用非public方法

和Field类似,对于非public方法,我们虽然可以通过Class.getDeclaredMethod()获取该方法实例,但直接对其调用将得到一个IllegalAccessException。为了调用非public方法,我们通过Method.setAccessible(true)允许其调用:

import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        Person p = new Person();
        Method m = p.getClass().getDeclaredMethod("setName", String.class);
        m.setAccessible(true);//反射打破了封装性,平时开发不提倡
        m.invoke(p, "Bob");
        System.out.println(p.name);
    }
}

class Person {
    String name;
    private void setName(String name) {
        this.name = name;
    }
}

调用构造方法

  • 使用new创建新的实例
    Person p = new Person();
  • 通过反射创建新实例,Class提供的newInstance()方法(只能调用该类的public无参构造方法)
    Person p = Person.class.newInstance();
  • Constructor对象
    通过Class实例获取Constructor的方法如下:
    getConstructor(Class…):获取某个public的Constructor;
    getDeclaredConstructor(Class…):获取某个Constructor;
    getConstructors():获取所有public的Constructor;
    getDeclaredConstructors():获取所有Constructor。
import java.lang.reflect.Constructor;

public class Main {
    public static void main(String[] args) throws Exception {
        // 获取构造方法Integer(int):
        Constructor cons1 = Integer.class.getConstructor(int.class);
        // 调用构造方法:
        Integer n1 = (Integer) cons1.newInstance(123);
        System.out.println(n1);

        // 获取构造方法Integer(String)
        Constructor cons2 = Integer.class.getConstructor(String.class);
        Integer n2 = (Integer) cons2.newInstance("456");
        System.out.println(n2);
    }
}

Constructor对象封装了构造方法的所有信息;

通过Class实例的方法可以获取Constructor实例:getConstructor(),getConstructors(),getDeclaredConstructor(),getDeclaredConstructors();

通过Constructor实例可以创建一个实例对象:newInstance(Object… parameters); 通过设置setAccessible(true)来访问非public构造方法。

获取继承关系

获取父类的Class

Class i = Interger.class;
Class n = i.getSuperclass();
System.out.println(n);//class java.lang.Number

获取interface

通过Class可以查询类实现的接口(一个类可能实现一个或者多个接口),如查询Integer实现的接口:getInterfaces()只返回当前类实现的接口,不包括父类实现的接口类型

import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        Class s = Integer.class;
        Class[] is = s.getInterfaces();
        for (Class i : is) {
            System.out.println(i);
        }
    }
}

/*结果
java.lang.Comparable
java.lang.constant.Constable
java.lang.constant.ConstantDesc
*/

获取接口的父接口要用getInterfaces()

通过Class对象可以获取继承关系:
Class getSuperclass():获取父类类型;
Class[] getInterfaces():获取当前类实现的所有接口。
通过Class对象的isAssignableFrom()方法可以判断一个向上转型是否可以实现。

动态代理(Dynamic Proxy)

有实现类但是在运行期动态创建了一个接口对象的方式,称为动态代码
一个动态代理实现如下:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Main {
    public static void main(String[] args) {
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println(method);
                if (method.getName().equals("morning")) {
                    System.out.println("Good morning, " + args[0]);
                }
                return null;
            }
        };
        Hello hello = (Hello) Proxy.newProxyInstance(
            Hello.class.getClassLoader(), // 传入ClassLoader
            new Class[] { Hello.class }, // 传入要实现的接口
            handler); // 传入处理调用方法的InvocationHandler
        hello.morning("Bob");
    }
}

interface Hello {
    void morning(String name);
}

在运行期动态创建一个interface实例的方法如下:

定义一个InvocationHandler实例,它负责实现接口的方法调用;
通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:
使用的ClassLoader,通常就是接口类的ClassLoader;
需要实现的接口数组,至少需要传入一个接口进去;
用来处理接口方法调用的InvocationHandler实例。
将返回的Object强制转型为接口。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值