Java基础深度总结:反射

我自是年少,韶华倾负。

1.Java反射机制
  • 动态语言:是在程序运行时可以改变其结构的语言。例如:C#,JavaScript,Python等。

  • 静态语言:是在运行时结构不可变的语言。例如:Java,C,C++。

Java是静态语言,但是因为有了反射机制的存在,使得Java有了类似动态语言的特性。

反射指应用程序访问、检测、修改自身状态与行为的能力。Java的反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。

Java反射主要提供以下功能:

  • 在运行时判断任意一个对象所属的类。
  • 在运行时构造任意一个类的对象。
  • 在运行时判断任意一个类所具有的成员变量和方法。
  • 在运行时调用任意一个对象的方法。
2.Class对象

在这里插入图片描述
上面的图片说明了Java程序在计算机中经历的阶段。Class类包含了一个类的完整的结构信息,每个类的运行时的类型信息就是用Class对象来表示的,它包含了与类有关的信息,事实上我们实例对象就是通过Class对象来创建的。

每个类都有一个Class对象,当任何一个类被加载时,即将类的.class文件读入内存时,类加载器都会自动为之创建一个java.lang.Class对象。Class类没有公共构造方法。Class类对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass 方法自动构造的。

3.获取Class对象

Java中有三种获取Class对象的方式:

3.1 Class.forName()

当知道类的全路径名时,可使用Class.forName静态方法来获得Class对象。当类的全路径名错误时会抛出ClassNotFoundException。

 Class dog = Class.forName("cu.edu.xupt.acat.reflex.User");
3.2 通过类字面量:Object.class

通过“类名.class”获得,前提条件是在编译前就能够拿到对应的类。

Class clz = User.class;

Tip:

  • 使用类字面量获取Class对象更加方便、安全(编译时会检查所以不会抛出异常)也更高效。
  • int.class 和 Integer.class 是不一样的,但是在包装类中有个一个TYPE字段,TYPE字段是一个引用,指向对应的基本数据类型的Class对象,如下所示,左右两边相互等价:
    在这里插入图片描述
3.3 通过对象的getClass方法
User user = new User();
Class clz = user.getClass();
3.4 三种获取Class对象方式的区别(重点)
package cu.edu.xupt.acat.reflex;

public class GetClassInstanceTest {

    public static void test1(){
        Class<?> clz = Person.class;
    }

    public static void test2() throws ClassNotFoundException {
        Class<?> clz = Class.forName("cu.edu.xupt.acat.reflex.Person");
    }

    public static void test3() {
        Class<?> clz = new Person().getClass();
    }

    public static void main(String[] args) throws ClassNotFoundException{

        Class<?> clz1 = Person.class;
        System.out.println("---------------");
        Class<?> clz2 = Class.forName("cu.edu.xupt.acat.reflex.Person");
        System.out.println("---------------");
        Class<?> clz3 = new Person().getClass();
        System.out.println("---------------");
        System.out.println(clz1 == clz2);
        System.out.println(clz2 == clz3);
    }
}


class Person {

    static {
        System.out.println("Person:静态代码块");
    }

    {
        System.out.println("Person:构造代码块");
    }

    public Person(){
        System.out.println("Person:构造方法");
    }
}
输出:
---------------
Person:静态代码块
---------------
Person:构造代码块
Person:构造方法
---------------
true
true

所有类在对其第一次使用时,都会被动态加载进JVM,类加载过程:
在这里插入图片描述
区别:

  • 通过类字面量Object.class : JVM将使用类装载器,将类装入内存(前提是:类还没有装入内存),不做类的初始化工作,返回创建好的Class对象。

  • Class.forName(“类名字符串”):装入类,并做类的静态初始化,返回Class的对象。

  • 实例对象.getClass():对类进行静态初始化、非静态初始化;返回引用运行时真正所指的对象所属的类的Class的对象(即返回变量动态类型的对象所属的类的Class的对象)。

解析:

  • 执行Person.class时,Person类只完成Load,还没有静态初始化,因此没有输出。
  • Class.forName(),Person类完成类加载的整个过程,因此执行了静态代码块的内容。
  • 静态代码块只执行一次,在new Person().getClass()之前, Person类已经加载完毕,所以没有输出静态代码块中的内容,创建对象时,执行了构造代码块和构造方法。

每一个类都有且仅有一个Class对象。 因此不论以哪种方式创建Class对象,在堆中只有一个该类的Class实例。

4.通过Class对象创建实例对象

可以通过Class对象的newInstance()方法和通过Constructor 对象的newInstance()方法创建类的实例对象。

4.1 Class对象.newInstance();
Class clz = User.class;
User user = (User) clz.newInstance();
4.2 Constructor对象.newInstance();
Class clz = Class.forName("cu.edu.xupt.acat.reflex.User");
Constructor constructor = clz.getConstructor();
User user = (User) constructor.newInstance();

Tip:
其中Constructor对象.newInstance()方法创建类对象可以选择特定构造方法,而通过 Class对象只能调用无参数构造方法,因此若使用Class对象.newInstance()创建实例,该类必须提供无参构造。

Class clz = User.class;
Constructor constructor = clz.getConstructor(String.class);
User user = (User) constructor.newInstance("zhangsan");
5.获取属性和方法

通过Class对象,可以轻易地获取属性和方法。

5.1属性
  • Field getField(String name):获得某个公有的属性对象,包括从父类中继承来的公有的属性。
  • Field[] getFields():获得所有公有的属性对象,包括从父类中继承来的公有的属性。
  • Field getDeclaredField(String name):获得声明的某个属性对象,不包含从父类中继承来的属性。
  • Field[] getDeclaredFields():获得声明的所有属性对象,不包含从父类中继承来的属性。

获取到Field对象后,可以使用 get() 和 set() 方法读取和修改 Field 对象关联的字段。

5.2 方法
  • Method getMethod(String name, Class…<?> parameterTypes):获得该类某个公有的方法,包括从父类中继承的方法。
  • Method[] getMethods():获得该类所有公有的方法,包括从父类中继承的方法。
  • Method getDeclaredMethod(String name, Class…<?> parameterTypes):获得该类声明的某个方法,不包括从父类中继承的方法。
  • Method[] getDeclaredMethods():获得该类所有声明的方法,不包括从父类中继承的方法。

获取到Method对象后,可以使用 invoke() 方法调用与 Method 对象关联的方法。

6.暴力反射

修改私有成员变量或调用私有方法是不被允许的,但是可以在修改私有成员变量或调用私有方法之前,通过设置 setAccessible(true) 来暴力执行。

public class ViolenceReflexTest {

    public static void main(String[] args) throws Exception {

        Woman woman = new Woman("超超超级美丽的巨兔", 30);
        Class<Woman> clz = Woman.class;
        Method method = clz.getDeclaredMethod("getAge");
        method.setAccessible(true); //暴力反射
        method.invoke(woman);

        System.out.println("--------");

        Field age = clz.getDeclaredField("age");
        age.setAccessible(true);
        age.set(woman, 18);
        method.invoke(woman);
    }
}

class Woman {

    public String name;
    private int age;

    public Woman(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void getName() {
        System.out.println("name = " + name);
    }

    private void getAge() {
        System.out.println("age = " + age);
    }
}
输出:
age = 30
--------
age = 18
7.反射的优缺点

优点:

  • 增加了程序的灵活性、扩展性。
  • 降低耦合,提高程序自适应能力。

缺点:

  • 反射涉及了动态类型的解析,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。
  • 使用反射技术要求程序必须在一个没有安全限制的环境中运行。
  • 由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用,这可能导致代码功能失调并破坏可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值