1.反射的概念
1.1反射的出现背景
Java程序中,所有的对象都有两种类型:编译时类型和 运行时类型,而很多时候对象的编译时类型和运行时类型不一致。 Object obj= new String(“hello”);Object编译时类型 String运行时类型
例如:某些变量或形参的声明类型是Object类型,但是程序却需要调用该对象运行时类型的方法,该方法不是object中的方法,那么如何解决呢?
解决这个问题,有两种方案:
方案1:在编译和运行时都完全知道类型的具体信息,在这种情况下,我们可以直接先使用instanceof 运算符进行判断,再利用强制类型转换符将其转换成运行时类型的变量即可。
方案2:编译时根本无法预知该对象和类的真实信息,程序只能依靠 运行时信息来发现该对象和类的真实信息,这就必须使用反射。
希望动态的获取obj类的类型要
1.2反射概述
Reflection(反射)是被视为动态语言 的关键,反射机制允许程序在运行期间 借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
加载完类之后,在堆内存的方法区中就产生了一个class类型的对象(一个类只有一个class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。
1.3 Java反射机制研究及应用
Java反射机制是供的功能:
在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时判断任意一个类所具有的成员变量和方法
在运行时获取泛型信息
在运行时调用任意一个对象的成员变量和方法
在运行时处理注解
生成动态代理
1.4反射相关的主要API
java.lang.Class:代表一个类
java.lang.reflect.Method:代表类的方法
java.lang.reflect.Field:代表类的成员变量
java.lang.reflect.Constructor: 代表类的构造器
…
1.5反射的优缺点
优点:
提高了Java程序的灵活性和扩展性,降低了耦合性,提高自适应能力
允许程序创建和控制任何类的对象,玩需提前 硬編码 目标类
缺点:
反射的性能较低。
反射机制主要应用在对灵活性和扩展性要求很高的系统框架上
反射会模糊 程序内部逻辑,可读性较差。
1.6 class类的理解
针对于编写好的.java源文件进行编译(使用javac.exe),会生成一个或多个.class字节码文件。接着,我们使用java.exe命令对指定的.class文件进行解释运行。这个解释运行的过程中,我们需要将.class字节码文件加载(使用类的加载器)到内存中(存放在方法区)。加载到内存中的.class文件对应的结构即为Class的一个实例。
(通过.Class字节码文件创建实例)
比如:加载到内存中的Person类或String类或User类 ,都作为Class的一个一个的实例
Class clazz1 = Person.class;
Class clazz2 = String.class;
Class clazz3 = User. class;
运行时类在内存中会缓存,只能加载一次(地址永远都一样)
通过使用反射前后的例子的对比
1.面向对象中创建对象,调用指定结构(属性、方法)等功能,可以不使用反射,也可以使用反射。
区别是什么?
不使用反射,需要考虑封装性。比如:出了Perosn类之后,就不能调用Person类中的私有的结构
使用反射,可以调用运行时类任意的构造器、属性、方法。包括私有的属性、方法、构造器。
2.以前创建对象并调用方法的方式,与现在通过反射创建对象并调用方法的方式对比有什么区别?哪种用的多?
从程序可读性,开发者角度,开发中主要是完成业务代码,对于相关的对象方法调用都是确定的,所以使用非反射的方式极多。
因为反射体现了动态性,(可以在运行时动态的获取对象所属的类,动态的调用相关的方法)所以在设计框架时,会大量的运用反射。
框架 = 注解 +反射+ 设计模式
3.单例模式的饿汉式和懒汉式,私有化类的构造器!此时通过反射,可以创建单例模式中类的多个对象么
public class SingletonEager {
// 私有静态成员变量,直接初始化
private static final SingletonEager instance = new SingletonEager();
// 私有构造方法,防止外部实例化
private SingletonEager() {
}
// 公共方法,提供全局访问点
public static SingletonEager getInstance() {
return instance;
}
}
public class SingletonLazy {
// 私有静态成员变量,volatile 关键字确保多线程可见性
private static volatile SingletonLazy instance;
// 私有构造方法,防止外部实例化
private SingletonLazy() {
}
// 公共方法,使用双重检查锁定(Double-Checked Locking)确保多线程安全
public static SingletonLazy getInstance() {
if (instance == null) {
synchronized (SingletonLazy.class) {
if (instance == null) {
instance = new SingletonLazy();
}
}
}
return instance;
}
}
是的!(其实也可以避免,看情况)
4.通过反射,可以调用类中私有的结构,是否与面向对象的封装性有冲突?是不是JAVA语言设计存在的BUG?
不存在bug
封装性:体现的是是否建议调用内部api的问题,比如private声明的结构,意味着不建议调用。
反射:体现的是我们能否调用的问题。因为类的完整性都加载到内存中,所以我们就有能力调用。
调用指定的属性(步骤)
步骤1.通过Class实例调用getDeclaredField(String fieldName),获取运行时类指定名的属性步骤2. setAccessible(true):确保此属性是可以访问的步骤
步骤3.通过Filed类的实例调用get(Object obj)(获取的操作)
或 set(Object obj,Object value)(设置的操作)进行操作。
调用指定的方法
步骤1.通过CLass的实例调用getDecLaredMethod(String methodName,Class …args),获取指定的方法
步骤2. setAccessible(true):确保此方法是可访问的
步骤3.通过Method实例调用invoke(Object obj, Object…objs),即为对Method对应的方法的调用。
invoke()的返回值即Method对应的方法的返回值
特别的:如果Method对应的方法的返回值类型为void,则invoke()返回值为null
3.3 调用指定的构造器(步骤)
步骤1.通过Class的实例调用getDeclaredConstructor(Class …args),获取指定参数类型的构造器
步骤2.setAcceSsible(true):确保此构造器是可以访问的
步骤3.通过Constructor实例调用newInstance(Object …objs〕,返回一个运行时类的实例。|
@Test
public void test4() throws ClassNotFoundException {
//1.调用运行时类的静态属性
Class<Person>pC1 = Person.class;
//2.运行时类的对象的getClass();
Person person = new Person();
Class<? extends Person> pC2 = person.getClass();
System.out.println(pC2.equals(pC1));//true
//3.调用Class的静态方法forName(string className)
// com.example.Person 全类名
Class<?> pC3 = Class.forName("com.example.Person");
//4.使用类的加载器方式
//通过调用系统加载器的方式
Class<?> pC4 = ClassLoader.getSystemClassLoader().loadClass(
"com.example.Person");
}