Java-反射

73 阅读7分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

一.反射的引入

1.先了解反射的作用

对于任意一个类,可以知道这个类有哪些属性和方法

对于任意一个对象,可以知道调用它的任意一个方法

动态获取类的信息和动态调用对象的方法的功能。

可以在运行时检查类,接口,方法和变量等信息,无需知道类的名字,方法名等

还可以在运行时实例化新对象,调用方法,以及设置和获取变量值。

2.反射的引入

通过一个案例来引入反射

1.有这样一个需求

美团外卖与支付宝,微信,招商银行等厂商合作,这些厂商提供付款功能,那么需要美团来制定标准,其他厂商来实现。

定义美团支付的接口,厂商提供实现类去继承

2.实现代码

美团的接口
public interface Mtwm {
    void payOnline();
}


微信的支付实现类
public class WeChat  implements Mtwm{
    @Override
    public void payOnline() {
        System.out.println("使用微信支付");
    }
}

支付宝的支付实现类
public class AliPay implements Mtwm{
    @Override
    public void payOnline() {
        System.out.println("使用支付宝支付");
    }
}

银行卡的支付实现类
public class BankCard implements Mtwm{
    @Override
    public void payOnline() {
        System.out.println("银行卡支付");
    }
}

假设str是选择支付方式时传进来的值
public class Test {
    public static void main(String[] args) {
        String str = "微信";

        if("微信".equals(str)){
            pay(new WeChat());
        }
        if("支付宝".equals(str)){
            pay(new AliPay());
        }
        if("银行卡".equals(str)){
            pay(new BankCard());
        }
    }

    public static void pay(WeChat wc){
        wc.payOnline();
    }
    public static void pay(AliPay wc){
        wc.payOnline();
    }
    public static void pay(BankCard wc){
        wc.payOnline();
    }
}

这样的不好之处是当需要再添加厂商时,需要手动去写支付的方法和if的判断,拓展性不好。

所以利用多态可以改进支付方法。

public class Test {
    public static void main(String[] args) {
        String str = "微信";

        if("微信".equals(str)){
            pay(new WeChat());
        }
        if("支付宝".equals(str)){
            pay(new AliPay());
        }
        if("银行卡".equals(str)){
            pay(new BankCard());
        }
    }
    public static void pay(Mtwm m){
        m.payOnline();
    }
}

还有最后一个缺点,就是继续提高拓展性,不想再通过添加if来判断。

这就体现了反射的作用。

public class Test {
    public static void main(String[] args) throws Exception{
        String str = "introduce.WeChat";

        Class cls = Class.forName(str);
        Object o = cls.newInstance();
        Method method = cls.getMethod("payOnline");
        method.invoke(o);
    }
}

通过反射来解决拓展性的问题。

二.反射学习

1.反射的概念

​编辑

 ​编辑

 一个字节码文件对应一个Class对象,通过Class对象能获取到这个字节码文件的全部信息,这个Class对象就像一面镜子,光线经过Class对象,反射到了字节码文件上,看到了字节码文件的信息,所以称之为反射。

反射是动态的,因为只有程序运行起来,才会在JVM中加载出Class对象

2.Class类的理解

​编辑

想要获取红色:电脑类->张三的电脑->红色

想要获取方法:Class类->具体实例或对象->方法

所以Class类就是向上的抽取。

3.读取字节码信息的四种方式

用到的代码,为了阅读方便,将不同Java文件的代码放到了一起。

public class Person {
    private int age;
    public String name;

    private void eat() {
        System.out.println("PersonEat");
    }
    public void sleep(){
        System.out.println("PersonSleep");
    }
}

public class Student extends Person{
    private int sno;
    double height;
    protected double weight;
    public double score;

    public String showInfo(){
        return "我是好学生";
    }
    private void work(){
        System.out.println("工作");
    }
    public Student(){
        System.out.println("空构造器");
    }
    private Student(int sno){
        this.sno = sno;
    }
    Student(int sno,double weight){
        this.sno = sno;
        this.weight = weight;
    }

}


public class Test {
    public static void main(String[] args) throws Exception{
        //方式1
        Person p = new Person();
        Class class1 = p.getClass();

        //方式2
        Class class2 = Person.class;


        //方式3
        Class class3 = Class.forName("learn2.Person");

        //方式4
        ClassLoader classLoader = Test.class.getClassLoader();
        Class class4 = classLoader.loadClass("learn2.Person");
    }
}

方式1和方式2不常用,因为获取Class对象的目的是获取具体实例再获取属性方法等,既然已经知道了类或者该类的具体实例,就能直接获取属性和方法了。

方式3最常用。

方式4通过获取类加载器,所以字节码的加载都需要经过类加载器,所以任何类都可以获得到类加载器,再通过类加载器去获取Class对象。

4.Class类的具体实例

1.类:外部类,内部类

2.接口

3.注解

4.数组

5.基本数据类型

6.void

public class Test {
    public static void main(String[] args) throws Exception{
        Class c1 = Test.class;
        Class c2 = Comparable.class;
        Class c3 = Override.class;
        int []arr1 = {1,2,3};
        int []arr2 = {4,5,6};
        Class c4 = arr1.getClass();
        Class c5 = arr2.getClass();
        System.out.println(c4==c5);//true
        Class c6 = int.class;
        Class c7 = void.class;
    }
}

三.利用反射

1.获取构造器并创建对象

@MyAnnotation(value = "hello")
public class Student extends Person implements MyInterface{
    private int sno;
    double height;
    protected double weight;
    public double score;

    @MyAnnotation(value = "himethod")
    public String showInfo(){
        return "我是好学生";
    }
    public String showInfo(int a,int b){
        return "重写方法,我是好学生";
    }
    private void work(){
        System.out.println("工作");
    }
    void happy(){
        System.out.println("开心");
    }
    protected int getSno(){
        return sno;
    }

    public Student(){
        System.out.println("空构造器");
    }
    public Student(double weight,double score){
        this.weight = weight;
        this.score = score;
    }
    private Student(int sno){
        this.sno = sno;
    }
    Student(int sno,double weight){
        this.sno = sno;
        this.weight = weight;
    }
    protected Student(int sno,double height,double weight){
        this.sno = sno;
    }

    @MyAnnotation(value = "hellomethod")
    @Override
    public void myMethod() {
        System.out.println("重写myMethod");
    }

    @Override
    public String toString() {
        return "Student{" +
                "sno=" + sno +
                ", height=" + height +
                ", weight=" + weight +
                ", score=" + score +
                '}';
    }
}

//获取构造器并创建对象
public class Test {
    public static void main(String[] args) throws Exception{

        Class studentClass = Student.class;

        //通过字节码信息获取构造器
        //只能获取被public修饰的构造器
        Constructor[] constructors = studentClass.getConstructors();

        for(Constructor c : constructors){
            System.out.println(c);
        }
        System.out.println("----------我是分割线-----------");
        //得到所有被修饰的构造器
        Constructor[] declaredConstructors = studentClass.getDeclaredConstructors();
        for(Constructor c : declaredConstructors){
            System.out.println(c);
        }
        System.out.println("----------我是分割线-----------");
        //获取指定构造器
        //什么参数都不传得到空构造器
        Constructor constructor = studentClass.getConstructor();
        System.out.println(constructor);
        System.out.println("----------我是分割线-----------");
        //得到两个参数的有参构造器
        Constructor constructor1 = studentClass.getConstructor(double.class, double.class);
        System.out.println(constructor1);
        //获取private修饰的构造器
        Constructor constructor2 = studentClass.getDeclaredConstructor(int.class);
        System.out.println(constructor2);


        //有了构造器就能创建对象了
        Object o = constructor.newInstance();
        System.out.println(o);

        Object o1 = constructor1.newInstance(100, 100);
        System.out.println(o1);
    }
}

获取构造器时

1.用public修饰的构造器用getConstructor()或getConstructors()获取

2.不用public修饰的构造器用getDeclaredConstructor()或getDeclaredConstructors()获取

3.上面方法的参数要根据具体构造器的参数传入对于的class

创建对象时

用newInstance(),参数要传具体的数据。

2.获取属性并赋值

public class Test02 {
    public static void main(String[] args) throws Exception{
        Class cls = Student.class;
        //获取运行时类和父类的public修饰的属性
        Field[] fields = cls.getFields();
        for(Field f : fields){
            System.out.println(f);
        }
        System.out.println("-----------------------------");
        //获取运行时类所有属性
        Field[] declaredFields = cls.getDeclaredFields();
        for(Field f : declaredFields){
            System.out.println(f);
        }
        System.out.println("-----------------------------");
        //获取具体的public修饰的属性
        Field score = cls.getField("score");
        System.out.println(score);
        //获取具体的非public修饰的属性
        Field sno = cls.getDeclaredField("sno");
        System.out.println(sno);
        //属性的具体结构
        //获取修饰符的数量,
        int modifiers = sno.getModifiers();
        System.out.println(modifiers);//输出2
        System.out.println(Modifier.toString(modifiers));//输出private
        //获取数据的具体类型
        Class type = sno.getType();
        System.out.println(type);
        //获取属性的名字
        System.out.println(sno.getName());

        //给属性赋值
        //要有对象和属性对应的值
        Field score1 = cls.getField("score");
        Object object = cls.newInstance();
        score1.set(object,98);
        System.out.println(object);
    }

3.获取方法和调用方法

public class Test03 {
    public static void main(String[] args) throws Exception {
        Class cls = Student.class;

        //获取方法
        //获取被public修饰的运行时类的方法和所有父类中的方法
        Method[] methods = cls.getMethods();
        for(Method m : methods){
            System.out.println(m);
        }
        System.out.println("------------------------");

        //获取运行时类中所有方法
        Method[] methods1 = cls.getDeclaredMethods();
        for(Method m : methods1){
            System.out.println(m);
        }
        System.out.println("------------------------");
        //获取指定public修饰无参方法
        Method method = cls.getMethod("showInfo");
        System.out.println(method);
        //获取指定public修饰有参方法
        Method method1 = cls.getMethod("showInfo", int.class, int.class);
        System.out.println(method1);
        //获取非public修饰的无参方法
        Method method2 = cls.getDeclaredMethod("work");
        System.out.println(method2);

        //获取方法的具体结构
        //获取方法的名字
        System.out.println(method2.getName());
        //获取修饰符
        int modifiers = method2.getModifiers();
        System.out.println(Modifier.toString(modifiers));
        //获取返回值
        System.out.println(method2.getReturnType());
        //获取参数的类型
        Class[] parameterTypes = method2.getParameterTypes();
        for(Class c : parameterTypes){
            System.out.println(c);
        }
        //获取方法的注解
        //只能获取到RUNTIME的注解
        Method mymethod = cls.getMethod("myMethod");
        Annotation[] annotation = mymethod.getAnnotations();
        for(Annotation annotation1 : annotation){
            System.out.println(annotation1);
        }

        //获取异常
        Class[] exceptionTypes = mymethod.getExceptionTypes();
        for(Class c : exceptionTypes){
            System.out.println(c);
        }
        //调用方法
        //说明这个方法是哪个对象的方法和方法的参数
        Object o = cls.newInstance();
        mymethod.invoke(o);

        //调用有参数的方法
        System.out.println(method1.invoke(o, 12, 25));

    }
}

4.获取类的接口,所在包,注解

public class Test04 {
    public static void main(String[] args) {
        //获取字节码信息的对象
        Class cls = Student.class;

        //获取运行时类的接口
        Class[] interfaces = cls.getInterfaces();
        for(Class c : interfaces){
            System.out.println(c);
        }

        //得到父类的接口:先得到父类的字节码信息->再得到父类的接口
        Class[] interfaces1 = cls.getSuperclass().getInterfaces();

        //获取运行时类所在的包
        Package aPackage = cls.getPackage();
        System.out.println(aPackage);
        //直接获取包名
        System.out.println(aPackage.getName());

        //获取运行时类的注解
        Annotation[] annotations = cls.getAnnotations();
        for(Annotation a : annotations){
            System.out.println(a);
        }
    }
}

【小白也能听懂的】Java反射与自定义注解底层原理,(从入门到入坟)_哔哩哔哩_bilibili

\