Java的灵魂——反射

反射

1.什么是反射

Reflection(反射) 是 Java 程序开发语言的特征之一,是一种根据类的元数据创建对象,访问对象成员的技术。它允许运行中的 Java 程序对自身进行检查,可以获取任意类中的方法和属性;也可以调用任意对象中的方法和属性。

2.反射可以做什么

1.创建对象
2.访问对象的属性
3.访问对象的方法

3 反射中的常用类及常用方法

用途
Class描述类的类
ConStructot描述构造函数的类
Field描述字段(属性)的类
Method描述方法的类
Parameter描述方法参数的类

常用方法:

//获取包名、类名
clazz.getPackage().getName()//包名
clazz.getSimpleName()//类名
clazz.getName()//完整类名
 


//获取成员变量定义信息
getFields()//获取所有公开的成员变量,包括继承变量
getDeclaredFields()//获取本类定义的成员变量,包括私有,但不包括继承的变量
getField(变量名)
getDeclaredField(变量名)
 
//获取构造方法定义信息
getConstructor(参数类型列表)//获取公开的构造方法
getConstructors()//获取所有的公开的构造方法
getDeclaredConstructors()//获取所有的构造方法,包括私有
getDeclaredConstructor(int.class,String.class)
 
//获取方法定义信息
getMethods()//获取所有可见的方法,包括继承的方法
getMethod(方法名,参数类型列表)
getDeclaredMethods()//获取本类定义的的方法,包括私有,不包括继承的方法
getDeclaredMethod(方法名,int.class,String.class)
 
//反射新建实例
clazz.newInstance();//执行无参构造创建对象
clazz.newInstance("10086","李明",18);//执行有参构造创建对象
clazz.getConstructor(String.class, String.class, int.class)//获取构造方法
 
//反射调用成员变量
clazz.getDeclaredField(变量名);//获取变量
clazz.setAccessible(true);//使私有成员允许访问
f.set(实例,);//为指定实例的变量赋值,静态变量,第一参数给null
f.get(实例);//访问指定实例变量的值,静态变量,第一参数给null
 
//反射调用成员方法
Method m = Clazz.getDeclaredMethod(方法名,参数类型列表);
m.setAccessible(true);//使私有方法允许被调用
m.invoke(实例,参数数据);//让指定实例来执行该方法

4. 通过反射实例化对象

4.1 获取类对应的字节码的对象

要想解剖一个类,必须先要获取到该类的字节码文件对象。解剖使用的是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.
Class类对象有三种实例化模式:

方式备注
对象.getClass()
类.class
Class.forName(包名.类名)静态

以下分别举例实现:

  1. 对象.getClass()
    调用某个类的对象的getClass()方法,即:对象.getClass()

    public final native Class<?> getClass();

    示例:

Student stu = new Student();
class<?> clazz = stu.getClass();  //?代表通配符,当不清楚获取到的对象的类型可使用,此处也可换成Student
  1. 类.class
    调用类的class属性来获取该类对应的Class对象
    示例:
Class<?> clazz1 = Student.class;
  1. Class.forName(包名加类名)
    使用Class类中的forName()静态方法来获取该对应的Class对象。此方法最安全,性能最好。

public static Class<?> forName(String className) throws ClassNotFoundException

示例:

  		Class<?> clazz = null;
    	try {
            clazz = Class.forName("包名.类名");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

通常采用第三种方式。
注:在运行期间,一个类只有一个Class对象产生。

4.2 实例化对象

通过三种创建Class对象的方式,我们可以发现只有getClass()方法会实例化对象之外,其他的两种不会产生实例化对象,所以取得Class类对象的一个最直接的好处就是通过反射实例化对象。有两种方法可以实例化对象。第一种是使用Class类对象.newInstance,但在java 9后便不建议使用。第二种是使用构造函数来创建对象。

注:newInstance()方法内部实际上调用了无参数构造方法,必须保证无参构造存在才可以。
否则会抛出java.lang.InstantiationException异常。

示例:
1.创建一个学生类
创建包: com.reflection
创建类: Student.java*

package com.reflection;
public class Student {
    private String id;
    private String name;
    private int age;
    public Student() {}
    public Student(String id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
   
  	 public String toString() {
        return "Student{" +
        		"id='" + this.id + '\'' +
                "name='" + this.name + '\'' +
                ", age=" + this.age +
                '}';
    }
}

创建测试类
由于经常使用的是第三种方法,所以以下使用第三种。

创建包: com.reflection
创建类: TestReflect.java

public class test {
    public static void main(String[] args) throws Exception{
       //1.获取字节码对象
        Class<?> clazz = Class.forName("com.reflection.Student");
        
        //2.通过反射技术创建目标类的对象,注意抛出异常
        /*反射创建对象方案1:
            使用 目标类 的 无参构造 创建对象
        */
        Object o = clazz.newInstance();
        System.out.println(o);//这一步已经获取到了对象Student{id='null',name='null', age=0}
 
        /*反射创建对象方案2:
            使用 目标类 的 全参构造 创建对象
        * 思路:
        * 1.先获取指定的构造函数对象,注意需要指定构造函数的参数,传入的是.class字节码对象
        * 2.通过刚刚获取到的构造函数对象创建Student目标类的对象,并且给对象的属性赋值
        * */
 
        //3.获取目标类中指定的全参构造
        Constructor<?> c = clazz.getConstructor(String.class,String.class, int.class);
        //System.out.println(c);
        //3.打印数组,查看数组中的元素
        System.out.println(Arrays.toString(s));
        //4.通过获取到的构造函数:创建对象 + 给对象的属性赋值
        Object o2 = c.newInstance("10086", "赵六", 26);
        System.out.println(o2);
    }
    }
}

5. 获取对象的属性和方法

5.1获取成员变量

public class TestReflect {
    public void getStu() {
        //1.创建Student类的3个对象
        Student s1 = new Student("001","张三", 3);
        Student s2 = new Student("001","李四", 4);
        Student s3 = new Student("001","王五", 5);
        //2.创建数组将刚刚的3个对象存入数组中
        Student[] s = {s1, s2, s3};
        //3.直接打印数组,查看数组中的元素
        System.out.println(Arrays.toString(s));
        //4.遍历学生数组,拿到每一个学生对象,做进一步的操作
        for (Student stu : s) {
            stu.print();
        }
    }
 
	//获取Student类中的成员变量
    public void getFie() throws ClassNotFoundException {
        //1.获取Student类对应的字节码对象
        Class<?> clazz = Class.forName("com.reflection.Student");
        //2.通过Student类对应的字节码对象获取Student类中的成员变量们
        Field[] fs = clazz.getFields();
        //3.遍历数组,获取Student类中的每个成员变量的具体信息
        /*注意!目前成员变量的修饰符必须是public的才能获取到*/
        for(Field f : fs){
            System.out.println(f.getName());//通过本轮循环到的字段对象获取字段名
            System.out.println(f.getType());//通过本轮循环到的字段对象获取字段的类型
        }
 
    }
}

5.2 获取类的成员方法

public class TestReflect {
    //获取Student类中的成员方法
    public void getFunction() {
        //1.获取Student类对应的字节码对象
        Class<?> clazz = Class.forName("com.reflection.Student");
        //2.通过Student类对应的字节码对象获取Student类中的成员方法们
        Method[] ms = clazz.getMethods();
        //3.通过高效for循环遍历数组,拿到每一个方法对象
        for (Method m : ms) {
            System.out.println(m);//直接打印遍历到的方法对象
            System.out.println(m.getName());//通过方法对象获取方法名
            Class<?>[] pt = m.getParameterTypes();//通过方法对象获取方法所有参数的数组
            System.out.println(Arrays.toString(pt));//打印方法参数的数组
        }
 
    }
}

5.3 获取类的构造方法

public class TestReflect {
    //获取Student类中的构造方法
    public void getCons() {
        //1.获取字节码对象
        Class<?> clazz = Class.forName("com.reflection.Student");
        //2.通过字节码对象获取目标类Student的构造方法们
        Constructor<?>[] cs = clazz.getConstructors();
        //3.通过高效for循环遍历数组
        for(Constructor c : cs){
            System.out.println(c.getName());//打印本轮遍历到的构造方法的名字
            Class[] pt = c.getParameterTypes();//通过本轮遍历到的构造函数对象获取构造函数的参数类型
            System.out.println(Arrays.toString(pt));//打印参数类型
        }
    }
}

6. 反射的优缺点

6.1 优点

1、增加程序的灵活性,可以在运行的过程中动态对类进行修改和操作。

2、提高代码的复用率,比如动态代理,就是用到了反射来实现。

3、可以在运行时轻松获取任意一个类的方法、属性、并且还能通过反射进行动态调用。

6.2 缺点

1、反射会涉及到动态类型的解析,所以JVM无法对这些代码进行优化,导致性能要比非反射调用更低。

2、使用反射后,代码的可读性会下降

3、反射可以绕过一些限制访问的属性或是方法,可能会导致破坏了代码本身的抽象性,而且可能会导致一些安全性问题。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值