零基础学习java------20---------反射

1. 反射和动态代理

参考博文:https://blog.csdn.net/sinat_38259539/article/details/71799078

1.0 什么是Class:

  我们都知道,对象可以用类来描述,但是类应该用什么来描述呢。类描述对象是将对象的公共部分抽离出来。同理,描述类的话也是讲类中公共的部分抽离出来这个用来描述类的事物叫做Class(实质也是一个类),如下图

1.1  反射(reflect)概述

  java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取信息以及动态调用对象的方法的功能称为java语言的反射机制。

   要想解剖一个类,必须先要获取到该类的字节码文件的对象。而解剖使用的就是Class类中的方法,所以先要获取到每一个字节码文件对应的Class类型的对象。

如下图是类的正常加载过程:反射的原理在于Class对象,Class对象的由来是将class文件读入内存,并为之创建一个Class对象

反射的通俗理解:反射是先得到某个类的Class对象(包括了某个类中的所有信息),进而通过Class对象获知到该类中包含的所有信息,反过来,我们就能将这个类的对象创建出来,从而得到这个对象的信息

 

由上可知,反射的必要条件是获取一个Class对象,那么怎么获取Class对象呢?

1.2 反射的使用

Person类(以下例子中Persn结为此)

public class Person extends SuperPerson {
    public String name;
    public int age;
    private char gender;
    public Person() {
        
    }
    private Person(int age) {
        this.age = age;
    }
    protected Person(String name) {
        this.name = name;
    }
    Person(String name,int age){
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + ", gender=" + gender + "]";
    }
    
    public void show() {
        System.out.println("show");
    }
    
    private int getSum(int a,int b) {
        return a+b;
    }
    protected void test() {
        System.out.println("test");
    }
    
}
class SuperPerson{
    public String a;
    public int b;
    public void haha() {
        System.out.println("哈哈");
    }
}

interface A{}

interface B{}
View Code

1.2.1. 获取Class对象(字节码文件,即.class文件)的三种方式  

(1)第一种:Object------>getClass()

(2)任何数据类型(包括基本数据类型)都有一个“静态”的class属性======》 类名.class

(3)通过Class类的静态方法:forName(String className)    此方法最常用

案例:

public class ReflectDemo {
    public static void main(String[] args) {
        //  第一种
        Person p = new Person();
        Class<? extends Person> class1 = p.getClass();
        System.out.println(class1);//class com._51doit.javase.day20.Person
        
        // 第二种
        Class<?> class2 = Person.class;
        System.out.println(class2);
        
        // 第三种
        try {
            Class<?> class3 = Class.forName("com._51doit.javase.day20.Person");
            System.out.println(class3);// class com._51doit.javase.day20.Person
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

由结果可知,Person类只有一个Class对象

1.2.2  获取方法

System.out.println(class1.getName());//获取全类名
System.out.println(class1.getSimpleName()); // 获取类名
System.out.println(class1.getPackage()); // 获取包名
System.out.println(class1.getSuperclass());// 获取父类的Class对象
System.out.println(class1.getInterfaces());//获取父接口的Class,得到的是数组(Class[]),一个类可以实现多个接口

1.2.3 反射获取构造方法

(1)public Constructor[] getConstructors(); 获取所有用public 修饰的构造方法
(2)public Constructor getConstructor(Class…args); 获取单个的用public 修饰构造方法
(3)public Constructor[] getDeclaredConstructors (); 获取所有的构造方法
(4)public Constructor getDeclaredConstructor (Class…args); 获取单个构造方法

案例(此处只写了第一种方法,其他方法是类似的)

public class ReflectDemo1 {
    public static void main(String[] args) {
        try {
            //1. 获取Class对象
            Class clazz = Class.forName("com._51doit.javase.day20.reflect.Person");
            //2. 利用Class对象获取构造方法
            Constructor[] cs = clazz.getConstructors(); //只能获取类中用public修饰的构造方法
            for (Constructor constructor : cs) {
                System.out.println(constructor);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

 得到两个用public修饰的构造方法

1.2.4 使用构造方法创建对象

格式:构造方法.newInstance();  根据构造方法传参

这里需要注意下私有构造方法创建对象

public class ReflectDemo2 {
    public static void main(String[] args) {
        try {
            //1 使用构造方法创建对象
            Class<?> clazz = Class.forName("com._51doit.javase.day20.reflect.Person");
            Constructor<?> c = clazz.getDeclaredConstructor(String.class);
            Object o = c.newInstance("张三");
            System.out.println(o);//Person [name=张三, age=0, gender=
            
            //2 使用私有构造方法创建对象
            Constructor c1 = clazz.getDeclaredConstructor(int.class);// 默认是无法访问私有的构造方法的
            c1.setAccessible(true);
            Object o1 = c1.newInstance(18);
            System.out.println(o1); // Person [name=null, age=18, gender=
            
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

1.2.5 反射获取成员变量

Field[] fs       getFields()                // 得到所有用public修饰的变量(包括父类中变量)       
Field[] fs       getDeclaredFields()        // 得到类中所有的变量(成员,静态),不包括父类中的变量
Field            getField(Class....args)    // 只能获得单个用public修饰的变量
Field            getDeclaredField(Class...args) // 获得单个变量(成员,静态),不包括父类中的变量

 案例(只列出了一种,其他类似)

public class ReflectDemo3 {
    public static void main(String[] args) {
        try {
            Class<?> clazz = Class.forName("com._51doit.javase.day20.reflect.Person");
            Field[] fields = clazz.getFields();
            for (Field field : fields) {
                System.out.println(field);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果:

1.2.6 反射给变量赋值

public class ReflectDemo3 {
    public static void main(String[] args) {
        try {
            Class<?> clazz = Class.forName("com._51doit.javase.day20.reflect.Person");
            // 获取具体的某一个变量
            Field f = clazz.getField("name");
            System.out.println(f);
            Field f1 = clazz.getDeclaredField("age");
            System.out.println(f1);
            // 给变量赋值,先要创建一个对象
            Object o = clazz.getDeclaredConstructor(String.class,int.class).newInstance("张三",20);
            f.set(o, "老王");
            f1.set(o, 22);
            System.out.println(o);
            // 给私有变量赋值
            Field f2 = clazz.getDeclaredField("gender");
            f2.setAccessible(true);
            f2.set(o,'');
            System.out.println(o);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果:

1.2.7 反射获取成员方法

(1)Method[]      getMethods()              //得到本类中和父类中所有用public修饰的方法    
(2)Method []     getDeclaredMethods()      //得到本类中所有的方法
(3)Method        getMethod()               //得到本类或父类中某一个用public修饰的方法                                  
(4)Method        getDeclaredMethod()       //得到类(父类也行)中某一个方法(可以是私有方法)

 案例

1.

public class ReflectDemo4 {
    public static void main(String[] args) {
        try {
            Class<?> clazz = Class.forName("com._51doit.javase.day20.reflect.Person");
            Method[] m = clazz.getMethods();
            for (Method method : m) {
                System.out.println(method);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

运行结果(可知,得到本类中和父类中所有用public修饰的方法):

 2.

Method[] m1 = clazz.getDeclaredMethods();
for (Method method : m1) {
    System.out.println(method);

运行结果:

 3

// 得到本类中的show方法
Method m2 = clazz.getMethod("show");
// 得到父类中的haha方法
Method m3 = clazz.getMethod("haha");
System.out.println(m2);
System.out.println(m3);

运行结果

 4

// 获取私有方法getSum
Method m4 = clazz.getDeclaredMethod("getSum",int.class,int.class);
//  获取父类方法haha
Method m5 = clazz.getDeclaredMethod("haha");
System.out.println(m4);
System.out.println(m5);

1.2.8 方法的调用

格式:

  方法.invoke(对象,参数)

// 调用方法:成员需有对象
Object o = clazz.getConstructor().newInstance();
// 用o对象调用m2这个方法,没有参数
m2.invoke(o);  // show
// 调用有参数,也有返回值的私有方法
m4.setAccessible(true);
Object re = m4.invoke(o, 11,20);
System.out.println(re);  // 31

练习:给你一个ArrayList<String>的一个对象,我想在这个集合中添加一个整数型数据,如何实现呢?

public class ExerDemo1 {
    public static void main(String[] args) {
        ArrayList<String> array = new ArrayList<>();
        array.add("你好,明天");
        try {
            //1 获取Class对象(还可以通过array.getClass()获取Class对象)
            Class<?> clazz = Class.forName("java.util.ArrayList");
            //2 得到add方法,参数为泛型的类型,在运行中参数泛型被擦除掉,变为默认的Object类型
            Method m = clazz.getMethod("add",Object.class);
            //3. 调用add方法,并传参
            m.invoke(array , 100);
            System.out.println(array);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

1.3 反射中配置文件的使用

需求,原来有个Teacher类,想在想升级一下,将Teacher改为superTeacher,直接点的方法就是改程序的代码,将Teacher类改成super类,但这样不利于程序的维护,这个时候就可以通过修改配置文件并且不修改原程序代码的方式达到这种需求,利于程序的维护。,如下图

Teachable接口

public interface Teachable {
    public void teach();
}

Teacher类

public class Teacher implements Teachable {
    public void teach() {
        System.out.println("教学的很水。。。。。");
    }
}

SuperTeacher类

public class SuperTeacher implements Teachable {
    public void teach() {
        System.out.println("教的非常好。。。。");
    }
}

测试类

public class TeacherTest {
    public static void main(String[] args) {
        try {
            //1 创建一个Properties对象
            Properties p = new Properties();
            //2 使用p对象,加载配置文件
            p.load(TeacherTest.class.getClassLoader().getResourceAsStream("config.properties"));
            //3 使用p对象获取配置文件中的内容
            String value = p.getProperty("className");
            
            //4 获取需要修改成类的Class对象
            Class<?> clazz = Class.forName(value);
            //此处的o就是Teacher类(Teacher,SuperTeacher,此处不是指数据类型,所以需要用Object类型来接收),所以下面才能将之向上转型为Teachable
            Object o = clazz.getConstructor().newInstance();
            // 此处o转成Teachable接口比较好,这样就实现了多态的调用
            Teachable t = (Teachable) o;
            t.teach();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

配置文件中就可以通过配置全类名,用来获取其Class对象,若配置成Teacher类的全类名,执行的就是Teacher类中的功能,置成SuperTeacher类的全类名,执行的就是SuperTeacher类中的功能,这样程序维护就很方便

上面的例子中的全类名配置的是SuperTeacher类,最终运行结果为:教的非常好

原文地址:https://www.cnblogs.com/jj1106/p/11439360.html