Java学习的第二十八天(JavaSE最终篇之Java反射机制)

一、什么是反射??

1.反射机制的概念引入

package com.bianyiit.cast;

public class FanSheJiZhiDemo {
    public static void main(String[] args) throws ClassNotFoundException {
        Person p=new Person();
        //通过对象调用属性
        p.name;  //直接报错,不能调用私有化的属性
        p.run(); //直接报错,不能调用私有化的方法
        //类不能直接去操作类中的私有成员,Java提供了反射机制(反封装机制)直接去操作私有成员
        //反射就是可以获取类中所有的成员的一种技术
    }
}
//Person类不定义get和set方法
class Person{
    private String name;
    private Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    //自定义两个方法
    public void eat(){
        System.out.println("吃饭..");
    }
    private void run(){
        System.out.println("跑步..");
    }
}

2.反射的概念

2.1 类不能直接去操作类中的私有成员,Java提供了反射机制(反封装机制)直接去操作私有成员
2.2 反射就是可以获取类中所有的成员的一种技术(包括私有成员属性和方法)

二、如何使用反射去操作成员呢??

1.如何去使用反射

所谓的反射,第一步就是获取类的字节码对象
那么什么是字节码对象??
为什么会存在字节码对象??

2.反射机制、Class类和字节码对象图解如下所示

3.什么是字节码对象??

虚拟机把字节码文件加载进内存的方法区中,所有的字节码文件就封装成了一个Class类,每一个具体的字节码文件就对应了一个字节码对象

4.为什么会存在字节码对象

因为存在了字节码对象就可以获取到字节码文件,然后就可以直接获取类中的所有的属性

5.如何获取字节码对象---Java提供了三种方式

//第一种方式:直接通过类名.class获取字节码对象
Class<Person> class1 = Person.class;

//第二种方式:通过类的对象.getClass()获取字节码对象
Class<? extends Person> class2 = p.getClass();

//第三种方式:通过字节码类Class.forName("类的全类名")用的最多的
Class<?> class3 = Class.forName("com.bianyiit.cast.Person");

6.方法区中一个字节码文件会生成几个字节码对象---有且只有一个字节码对象

System.out.println(class1==class2);
System.out.println(class1==class3);
//输出结果:都是true

解释:因为字节码文件只会加载一次到内存的方法区中

三、构造方法的反射

1.通过字节码对象获取构造方法对象常用方法

1.1 Constructor<T> getConstructor(Class<?>... parameterTypes)
	返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。
1.2 Constructor<?>[] getConstructors()
	返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法。
1.3 Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
	返回一个 Constructor 对象,该对象反映此 Class 对象所表示的类或接口的指定构造方法。
1.4 Constructor<?>[] getDeclaredConstructors()
	返回 Constructor 对象的一个数组,这些对象反映此 Class 对象表示的类声明的所有构造方法。

2.代码演示

package com.bianyiit.fanshe;

import java.lang.reflect.Constructor;

public class ConstuctorDemo2 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        //先要获取字节码对象
        Class<?> class1 = Class.forName("com.bianyiit.fanshe.Person1");
        //通过字节码对象获取构造方法对象
        //method1(class1);
        method2(class1);
    }

    private static void method2(Class<?> class1) throws NoSuchMethodException {
        //获取私有和非私有的无参构造
        Constructor<?> con5 = class1.getDeclaredConstructor();
        System.out.println(con5);
        //获取一个属性的私有和非私有的构造方法
        Constructor<?> con6 = class1.getDeclaredConstructor(String.class);
        System.out.println(con6);
        //获取所有属性的私有和非私有的构造方法
        Constructor<?> con7 = class1.getDeclaredConstructor(String.class, Integer.class);
        System.out.println(con7);
        //一次性获取所有的私有的和非私有的构造方法
        Constructor<?>[] con8 = class1.getDeclaredConstructors();
        for (Constructor<?> constructor : con8) {
            System.out.println(constructor);
        }
    }

    private static void method1(Class<?> class1) throws NoSuchMethodException {
        //getConstructor()---获取公共的无参构造
        Constructor con1 = class1.getConstructor();
        System.out.println(con1);
        //给一个参数获取只有一个属性的构造方法---"属性的字节码对象"
        Constructor con2 = class1.getConstructor(String.class);
        System.out.println(con2);
        //获取两个参数的构造方法
        Constructor con3 = class1.getConstructor(String.class,Integer.class);
        System.out.println(con3);
        //一次性获取所有的公共的构造方法
        Constructor[] con4 = class1.getConstructors();
        for (Constructor constructor : con4) {
            System.out.println(constructor);
        }
    }
}
class Person1{
    private String name;
    private Integer age;

    private Person1() {
        System.out.println("这是无参的构造方法");
    }
    private Person1(String name){
        System.out.println("这是只有一个参数的构造方法");
    }

    private Person1(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
    //自定义两个方法
    public void eat(){
        System.out.println("吃饭..");
    }
    private void run(){
        System.out.println("跑步..");
    }
}
//输出结果:---构造方法对象
	private com.bianyiit.fanshe.Person1()
	private com.bianyiit.fanshe.Person1(java.lang.String)
	private com.bianyiit.fanshe.Person1(java.lang.String,java.lang.Integer)

3.利用构造方法对象调用newInstance()去创建对象

T newInstance(Object... initargs)---使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。 
package com.bianyiit.fanshe;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class ConstructorDemo3 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //获取字节码对象
        Class<?> clazz = Class.forName("com.bianyiit.fanshe.Person2");
        //获取到了公有化的构造方法对象
        Constructor<?> con1 = clazz.getConstructor();
        //利用构造方法对象调用newInstance()去创建对象
        Person2 p1 = (Person2) con1.newInstance();

        //获取到了私有的构造方法对象,还有对私有对象进行权限设置
        Constructor<?> con2 = clazz.getDeclaredConstructor();
        con2.setAccessible(true);
        //利用构造方法对象调用newInstance()去创建对象
        Person2 p2 = (Person2) con2.newInstance();

        //获取私有化的含有两个属性的构造方法
        Constructor<?> con3 = clazz.getDeclaredConstructor(String.class, Integer.class);
        con3.setAccessible(true);
        //通过构造方法对象类创建类的对象
        Person2 p3 = (Person2) con3.newInstance("张三",18);
        p3.eat();  //java.lang.IllegalAccessException:

        //创建对象---简便的方式,要求无参构造必须是公共的
        Person2 p4 = (Person2) clazz.newInstance();
    }
}
class Person2{
    private String name;
    private Integer age;

    public Person2() {
        System.out.println("这是无参的构造方法");
    }
    private Person2(String name){
        System.out.println("这是只有一个参数的构造方法");
    }

    public Person2(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
    //自定义两个方法
    public void eat(){
        System.out.println(name+"正在吃饭中..");
    }
    private void run(){
        System.out.println(name+"正在跑步中..");
    }
}

注意:获取到了私有的构造方法对象,还有对私有对象进行打开权限的设置---con2.setAccessible(true)

扫描二维码关注公众号,回复: 8578023 查看本文章
获取到了私有的构造方法对象,还有对私有对象进行权限设置
Constructor<?> con2 = clazz.getDeclaredConstructor();
con2.setAccessible(true);
利用构造方法对象调用newInstance()去创建对象
Person2 p2 = (Person2) con2.newInstance();

四、成员属性的反射

1.通过字节码对象获取成员属性对象的常用方法

1.1 Field getField(String name)
	返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。
1.2 Field[] getFields()
	返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段。
1.3 Field getDeclaredField(String name)
	返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。
1.4 Field[] getDeclaredFields()
	返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段。

2.代码演示

package com.bianyiit.fanshe;

import java.lang.reflect.Field;

public class FiledDemo4 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        //创建字节码对象
        Class<?> clazz = Class.forName("com.bianyiit.fanshe.Person3");
        //method1(clazz);
        //method2(clazz);
    }

    private static void method2(Class<?> clazz) throws NoSuchFieldException {
        //获取私有的和非私有的属性对象
        Field name = clazz.getDeclaredField("name");
        System.out.println(name);
        //获取所有的私有的和非私有的属性对象
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println(declaredField);

        }
    }

    private static void method1(Class<?> clazz) throws NoSuchFieldException {
        //通过属性名获取字节码文件中的公共的属性对象
        Field name = clazz.getField("name");
        System.out.println(name);
        //所有的公共的属性对象
        Field[] fields = clazz.getFields();
        for (Field field : fields) {
            System.out.println(field);
        }
    }
}
class Person3{
    private String name;
    private Integer age;

    public Person3() {
        System.out.println("这是无参的构造方法");
    }
    private Person3(String name){
        System.out.println("这是只有一个参数的构造方法");
    }

    public Person3(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
    //自定义两个方法
    public void eat(){
        System.out.println(name+"正在吃饭中..");
    }
    private void run(){
        System.out.println(name+"正在跑步中..");
    }
}

注意:

获取私有的和非私有的属性对象
Field name = clazz.getDeclaredField("name");
作为参数传入的name其实就是成员属性的这个name---private String name;

3.利用成员属性对象调用set和get方法去给属性赋值和取值

3.1 void set(Object obj, Object value)---将指定对象变量上此 Field 对象表示的字段设置为指定的新值
3.2 Object get(Object obj)---返回指定对象上此 Field 表示的字段的值。 
package com.bianyiit.fanshe;

import java.lang.reflect.Field;

public class FiledDemo5 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        //如何操作属性对象
        Class<?> clazz = Class.forName("com.bianyiit.fanshe.Person4");
        Field name = clazz.getField("name");
        //System.out.println(name);
        //要设置属性对象的属性值,首先要有一个对象
        Person4 p1=new Person4("张三",20);
        name.set(p1,"李四");
        Object o = name.get(p1);
        System.out.println(o);

        //更改age的值
        //获取age的属性对象
        Field age = clazz.getDeclaredField("age");
        age.setAccessible(true);
        age.set(p1,120);
        Object o1 = age.get(p1);
        System.out.println(o1);
        //不建议这样去做

        //获取country属性对象
        //静态属性是不需要指定的具体的对象的,赋一个空值
        Field country = clazz.getDeclaredField("COUNTRY");
        country.setAccessible(true);
        country.set(Person4.class,"中国");
        Object o2 = country.get(null);
        System.out.println(o2);
    }
}
class Person4{
    public String name;
    private Integer age;
    private static String COUNTRY;

    public Person4() {
        System.out.println("这是无参的构造方法");
    }
    private Person4(String name){
        System.out.println("这是只有一个参数的构造方法");
    }

    public Person4(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    //自定义两个方法
    public void eat(){
        System.out.println(name+"正在吃饭中..");
    }
    private void run(){
        System.out.println(name+"正在跑步中..");
    }
}

五、自定义方法的反射

1.通过字节码对象获取自定义方法对象的常用方法

1.1 Method getMethod(String name, Class<?>... parameterTypes)
	返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
1.2 Method[] getMethods()
	返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。
1.3 Method getDeclaredMethod(String name, Class<?>... parameterTypes)
	返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。
1.4 Method[] getDeclaredMethods()
	返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。 */

2.代码演示

package com.bianyiit.fanshe;

import java.lang.reflect.Method;

public class ZiDingYiMethodDemo6 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        //创建字节码对象
        Class<?> clazz = Class.forName("com.bianyiit.fanshe.Person5");
        //通过指定方法名字获取方法对象
        Method eat = clazz.getMethod("eat");
        System.out.println(eat);
        //通过指定方法名字获取私有和非私有对象
        Method run = clazz.getDeclaredMethod("run");
        run.setAccessible(true);
        System.out.println(run);
        //获取的是本类中字节码文件中所有的公共方法和Object公共方法
       /* Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }*/
       //获取的是本类中的字节码文件中所有的公共方法
        /*Method[] declaredMethods = clazz.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println(declaredMethod);
        }*/
    }
}
class Person5{
    public String name;
    private Integer age;
    private static String COUNTRY;

    public Person5() {
        System.out.println("这是无参的构造方法");
    }
    private Person5(String name){
        System.out.println("这是只有一个参数的构造方法");
    }

    public Person5(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
    //自定义两个方法
    public void eat(){
        System.out.println(name+"正在吃饭中..");
    }
    private void run(){
        System.out.println(name+"正在跑步中..");
    }
}

注意:

1.获取的是本类中字节码文件中所有的公共方法和Object公共方法
 Method[] methods = clazz.getMethods();
 for (Method method : methods) {
     System.out.println(method);
 }
2.获取的是本类中的字节码文件中所有的公共方法
 Method[] declaredMethods = clazz.getDeclaredMethods();
 for (Method declaredMethod : declaredMethods) {
     System.out.println(declaredMethod);
 }

3.利用成员属性对象调用invoke方法获取自定义方法中的内容

Object invoke(Object obj, Object... args)---对带有指定参数的指定对象调用由此 Method 对象表示的底层方法
package com.bianyiit.fanshe;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ZiDingYiMethodDemo7 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        //创建字节码对象
        Class<?> clazz = Class.forName("com.bianyiit.fanshe.Person6");
        Method eat = clazz.getMethod("eat");
        Person6 p1=new Person6();
        eat.invoke(p1);

        //获取无参数的私有化自定义方法
        Method run = clazz.getDeclaredMethod("run");
        run.setAccessible(true);
        run.invoke(p1);

        //获取有参数的方法
        Method eat1 = clazz.getMethod("eat", String.class);
        eat1.invoke(p1,"张三");

    }
}
class Person6{
    public String name;
    private Integer age;
    private static String COUNTRY;

    public Person6() {
        System.out.println("这是无参的构造方法");
    }
    private Person6(String name){
        System.out.println("这是只有一个参数的构造方法");
    }

    public Person6(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
    //自定义两个方法
    public void eat(){
        System.out.println(name+"正在吃饭中..");
    }
    private void run(){
        System.out.println(name+"正在跑步中..");
    }
    public void eat(String name){
        System.out.println(name+"正在盛饭中...");
    }
}

六、反射结合Properties(配置文件)使用

案例需求:

需求:给一个配置文件:Properties.txt,里面有数据(类名为key,全类名当做value)
Person=com.bianyiit.fanshe.Person
Student=com.bianyiit.fanshe.Student
....
要求通过配置文件去创建对象
将成员属性、构造方法、自定义方法全部私有化,在配置文件中只有一个全类名,要求去创建对象,去给属性赋值,去调用私有化自定义方法

结合反射去完成这个案例

//配置文件内容
Person=com.bianyiit.fansheanli.Person
package com.bianyiit.fansheanli;

public class Person {
        private String name;
        private Integer age;
        private static String COUNTRY;

        private Person() {
            System.out.println("这是无参的构造方法");
        }
        private Person(String name){
            System.out.println("这是只有一个参数的构造方法");
        }

        private Person(String name, Integer age) {
            this.name = name;
            this.age = age;
        }

        //自定义两个方法
        private  void eat(){
            System.out.println(name+"正在吃饭中..");
        }
        private  void run(){
            System.out.println(name+"正在跑步中..");
        }
        private  void eat(String name){
            System.out.println(name+"正在盛饭中...");
        }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
package com.bianyiit.fansheanli;

import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;

public class PropertiesDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        //创建一个Properties.txt文件
        //FileOutputStream fileOutputStream = new FileOutputStream("properties.txt");
        //创建Properties集合对象
        Properties properties = new Properties();
        //通过流对象与文件建立连接
        properties.load(new FileInputStream("properties.txt"));
        //通过键key获取值value---全类名
        String person =(String) properties.get("Person");
        System.out.println(person);
        //将全类名作为反射的参数输入构造字节码对象clazz---1
        Class<?> clazz = Class.forName(person);
        //利用字节码对象获取私有化的含有两个属性的构造方法对象con1---2
        Constructor<?> con1 = clazz.getDeclaredConstructor(String.class, Integer.class);
        //打开私有化构造方法的权限
        con1.setAccessible(true);
        //利用构造方法对象调用newInstance()去创建对象----3
        Person p1 = (Person) con1.newInstance("张三",18);
        System.out.println(p1.toString());
        //获取私有的和非私有的属性对象----4
        Field name = clazz.getDeclaredField("name");
        name.setAccessible(true);
        Field age = clazz.getDeclaredField("age");
        age.setAccessible(true);
        //要设置属性对象的属性值,首先要有一个对象---5
        //Person2 p1 = (Person2) con1.newInstance();------注意这里已经创建了构造方法对象
        name.set(p1,"李四");
        Object o1 = name.get(p1);
        System.out.println(o1);
        age.set(p1,20);
        Object o2 = age.get(p1);
        System.out.println(o2);
        //通过指定方法名字获取私有和非私有对象---6
        Method run = clazz.getDeclaredMethod("run");
        run.setAccessible(true);
        //Person2 p1 = (Person2) con1.newInstance();------注意这里已经创建了构造方法对象
        run.invoke(p1);
    }
}
//输出结果:
	com.bianyiit.fansheanli.Person
	Person{name='张三', age=18}
	李四
	20
	李四正在跑步中..

七、使用反射的优缺点

1.优点

1.1 就算构造方法私有化,通过反射也能去创建对象
1.2 结合properties(配置文件)使用,反射可以直接操作配置文件,不需要操作类中的内容,大大提高了程序的灵活性

2.缺点

2.1 反射会破坏封装
2.2 反射会降低执行效率
发布了73 篇原创文章 · 获赞 11 · 访问量 2462

猜你喜欢

转载自blog.csdn.net/weixin_43908333/article/details/103316640