Java程序中,所有的对象都有两种类型:编译时类型和运行时类型,而很多时候对象的编译时类型和运行时类型不一致。例如:
Object obj = new String("hello");
某些变量或形参的类型在编译时是Object,但是程序需要调用该对象运行时类型的方法,该方法不是Object中的方法,那么怎么办呢?
解决这个问题,有两种方案:
在编译和运行时都完全知道类型的具体信息,在这种情况下,我们可以直接使用instanceof
运算符进行判断,利用强类型转换符将其转换成运行时类型的变量即可。
编译时根本无法预知该对象和类的真实信息,程序只能依靠运行时信息来发现该对象和类的真实信息,这就必须使用反射。
Reflection(反射)是被视为动态语言的关键,反射机制允许程序在运行期间借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。
这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。
实例
创建person类
package apply; public class Person { private String name; public int age; public Person() { System.out.println("person()"); } public Person(String name) { this.name = name; } public Person(int age) { this.age = age; } public Person(String name, int age) { this.name = name; this.age = age; } public void show() { System.out.println("person"); } private String showNation(String nation) { return "nation"+nation; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
package apply; import org.junit.Test; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class RelectionTest { @Test public void test1(){ // 创建person的对象 Person p1 = new Person(); // 调用属性和方法 p1.age = 10; System.out.println(p1.age); // 调用方法 p1.show(); } // 使用反射完成上述操作 @Test public void test2() throws InstantiationException, IllegalAccessException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException { //创建实例 Classclazz = Person.class; Person p1 = clazz.newInstance(); System.out.println(p1); Field ageField = clazz.getField("age"); ageField.set(p1, 10); System.out.println(ageField.get(p1)); Method showMethod = clazz.getMethod("show"); showMethod.invoke(p1); } //通过反射调用person类中的私有属性、方法 @Test public void Test02() throws Exception { // 1.调用私有构造器,创建对象 Class clazz = Person.class; Constructor cons = clazz.getDeclaredConstructor(String.class,int.class); cons.setAccessible(true); Person p1 = (Person) cons.newInstance("tom",12); System.out.println(p1); // 调用私有属性 Field nameFiled = clazz.getDeclaredField("name"); nameFiled.setAccessible(true); nameFiled.set(p1, "jerry"); System.out.println(nameFiled.get(p1)); // 调用私有方法 Method showNation = clazz.getDeclaredMethod("showNation", String.class); showNation.setAccessible(true); String p2 = (String) showNation.invoke(p1, "CHN"); System.out.println(p2); } }
2.以前创建对象并调用方法的方式,与现在通过反射创建对象并调用方法的方式对比的话,哪种用的多?场景是什么?
从我们作为程序员开发者的角度来讲,我们开发中主要是完成业务代码,对于相关的对象、方法的调用都是确定的。所以,我们使用非反射方式多一些。
因为反射体现了动态性,(可以在运行时动态的获取对象所属的类,动态的调用相关的方法)所以我们在设计框架的时候,会大量使用反射
框架 = 注解 + 反射 + 设计模式
通过反射,可以调用类中私有的结构,是否与面向对象的封装性有冲突?是不是Java语言设计存在Bug? 不存在bug!
这个描述指出,尽管反射机制允许访问和操作类的私有成员(字段、方法等),这并不意味着Java语言设计存在缺陷。实际上,这是Java语言设计的一部分,允许在特定情况下绕过封装性原则。
面向对象编程的封装性原则主张隐藏对象的内部状态和实现细节,只通过公共接口暴露功能。然而,反射提供了一种机制,允许在运行时检查或修改类的私有成员,这在某些高级场景中非常有用,比如:
测试框架需要访问私有字段或方法来设置测试数据或验证内部状态。
框架或库可能需要访问或修改对象的私有成员来实现某些功能,例如对象映射、依赖注入等。
在某些情况下,开发者可能需要在运行时动态地访问或修改类的私有成员,尽管这通常不推荐作为常规做法。
Java反射机制的原理基于Java虚拟机(JVM)在运行时对类的内部信息的访问能力。以下是反射机制工作原理的几个关键点:
在Java中,每个类在加载到JVM时,都会生成一个对应的Class
对象,这个对象包含了类的元数据信息,如类名、方法、字段、构造函数等。这些信息被存储在方法区(Method Area)中。
每个类都有一个唯一的Class
对象,可以通过类名.class
、对象.getClass()
或Class.forName("类名")
的方式获取。Class
对象是反射机制的核心,它允许程序在运行时动态地访问和操作类的信息。
通过Class
对象,可以使用反射API来访问和操作类的内部结构:
获取类信息:使用getFields()
, getMethods()
, getConstructors()
等方法获取类的字段、方法和构造函数。
创建对象:使用getConstructor().newInstance()
或getDeclaredConstructor().newInstance()
创建类的实例。
访问和修改字段:使用getField()
, getDeclaredField()
获取字段,并使用setAccessible(true)
访问私有字段,然后使用set()
和get()
方法来修改和获取字段值。
调用方法:使用getMethod()
或getDeclaredMethod()
获取方法,并使用invoke()
方法调用它们。
处理注解:使用getAnnotation()
等方法获取类、方法或字段上的注解信息。
反射的动态性体现在它允许程序在运行时检查和修改类的结构,而不需要在编译时确定。这使得反射非常适合于需要高度灵活性的场景,如框架开发、依赖注入、对象映射等。
虽然反射提供了强大的功能,但它也有一些缺点。反射操作通常比直接代码访问要慢,因为它需要在运行时解析类型信息,并且可能需要处理安全权限检查。因此,在性能敏感的应用中,应谨慎使用反射。
反射可以绕过Java的访问控制,访问和修改私有成员。这虽然提供了灵活性,但也可能破坏封装性原则,增加代码的复杂性和维护难度。因此,使用反射时应确保有充分的理由,并且要小心处理安全和封装问题。
Java反射机制提供的功能:
在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时判断任意一个类所具有的成员变量和方法
在运行时获取泛型信息
在运行时调用任意一个对象的成员变量和方法
在运行时处理注解
生成动态代理
Java反射机制的优缺点如下:
1.动态性:
反射允许在运行时动态地创建对象、访问和修改属性、调用方法,这为程序提供了极大的灵活性。
可以在不知道具体类的情况下操作对象,这对于编写通用代码(如框架和库)非常有用。
2.解耦:
反射可以减少代码之间的耦合,因为它允许程序在运行时决定调用哪个类或方法,而不是在编译时就固定下来。
3.通用性:
反射使得某些通用功能(如对象序列化、ORM框架、依赖注入等)的实现成为可能,这些功能需要在运行时动态地处理类和对象。
4.扩展性:
可以在不修改现有代码的情况下,通过反射调用新的方法或访问新的属性,这对于插件系统和模块化设计特别有用。
1.性能开销:
反射操作通常比直接代码访问要慢,因为它需要在运行时解析类型信息,并且可能需要处理安全权限检查。
反射调用方法或访问字段时,需要通过方法句柄进行间接调用,这比直接调用要慢。
2.安全问题:
反射可以绕过访问控制,访问和修改私有成员,这可能导致安全漏洞,尤其是当反射被用于不恰当的场景时。
3.代码复杂性:
使用反射的代码通常比直接代码更难以理解和维护,因为反射操作的动态性使得代码的意图不够明显。
调试时也更困难,因为反射调用可能隐藏在多层间接调用之后。
4.编译时检查缺失:
由于反射是在运行时解析类型信息,编译器无法对反射代码进行类型检查,这可能导致运行时错误。
5.封装性破坏:
反射破坏了面向对象编程中的封装性原则,因为它允许访问和修改类的私有成员
在Java中,Class
类是所有类的根类,它用于表示Java程序运行时的类型信息。每个类在Java虚拟机(JVM)中都有一个对应的Class
对象,这个对象包含了类的元数据信息,如类名、字段、方法、构造函数等。Class
类是Java反射API的核心,它允许程序在运行时动态地访问和操作类的信息。
Class
对象1.通过类名直接获取:
Class> clazz = MyClass.class;
2.通过实例获取:
MyClass myObject = new MyClass(); Class> clazz = myObject.getClass();
3.通过类的全名获取
try { Class> clazz = Class.forName("完整的类名"); } catch (ClassNotFoundException e) { e.printStackTrace(); }
Class
对象的用途创建对象实例:
Class> clazz = MyClass.class; MyClass myObject = (MyClass) clazz.newInstance();
获取类的成员信息:
获取字段:getFields()
, getField(String name)
, getDeclaredFields()
, getDeclaredField(String name)
获取方法:getMethods()
, getMethod(String name, Class>... parameterTypes)
, getDeclaredMethods()
, getDeclaredMethod(String name, Class>... parameterTypes)
获取构造函数:getConstructors()
, getConstructor(Class>... parameterTypes)
, getDeclaredConstructors()
, getDeclaredConstructor(Class>... parameterTypes)
访问和修改字段:
Field field = clazz.getField("fieldName"); Object value = field.get(myObject); field.set(myObject, newValue);
调用方法:
Method method = clazz.getMethod("methodName", 参数类型1.class, 参数类型2.class); Object result = method.invoke(myObject, 参数1, 参数2);
处理注解:
Annotation[] annotations = clazz.getAnnotations();
使用反射时,需要处理NoSuchFieldException
, NoSuchMethodException
, IllegalAccessException
, InvocationTargetException
等异常。
反射操作通常比直接代码访问要慢,因此在性能敏感的应用中应谨慎使用。
反射可以绕过访问控制,访问和修改私有成员,这可能导致安全问题。
1.Class类的理解(掌握) (如下以Java类的加载为例说明) 针对编写好的.java源文件进行编译(使用javac.exe),会生成一个或多个.class字节码文件。接着,我们使用java.exe命令对指定的.class文件进行解释运行。这个解释运行的过程中,我们需要将.class字节码文件加载(使用类的加载器)到内存中(存放在方法区)。加载到内存中的.class文件对应的结构即为Class的一个实例。
比如,加载到内存中的Person类或String类或User类,都作为Class的一个一个的实例
Class clazz1 = Person.class; Class clazz2 = String.class; Class clazz3 = User.class; Class clazz4 = Comparable.class;
1.类的装载(loading)
将类的class文件读入内存,并为之创建一个Java.long.class对象,此过程由类加载器完成
2.连接(linking)
验证(verify):确保加载的类信息符合JVM规范
准备(prepare):正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配.
解析(resolve):虚拟机常量池的符号引用(常量名) 替换为直接引用(地址)的过程
3.初始化(Initialization)
执行类构造器
类构造器
类构造器:负责类的加载,并对应于一个class的对象
bootstrapclassloader:启动类加载器,引导类加载器
使用c/c++语言编写,不能通过Java代码获取实例
负则加载Java核心库
继承于 classloader的类加载器
extensionclassloader:扩展类加载器
负责加载java.ext.dirs系统属性所指定的目录中加载类库
或者从jdk的安装目录的jre/lib/ext子目录下加载
systemclassloader/applicationclassloader:系统类加载器.应用程序类加载器
我们自定义的类,默认使用的类的加载器
用户自定义类的加载器
实现应用隔离,数据加密