反射
- 摆脱只能在编译时执行面向对象类型的操作的限制
- 自省是指程序在运行时检查对象类型或属性的能力。它通常用于获取关于对象的信息,如类名、方法名、属性等,而不改变对象的行为。
-
反射是指程序在运行时创建、检查和修改其自身行为的能力。与自省相比,反射不仅能获取对象的信息,还能在运行时改变对象的属性和行为。
-
核心功能
- 在运行时判断任意一个对象所属的类;
- 在运行时构造任意一个类的对象;
- 在运行时判断任意一个类所具有的成员变量和方法;
-
在运行时调用任意一个对象的方法;
-
向上转型可以自动发生,但是向下转型必须显示指出,并且如果这个对象并转型到目标并不是向下转型(不是其父类),会抛出ClassCastException
- 通过反射可以获取实际类型并且进行安全的向下转型
Animal animal = getAnimal(); // getAnimal()返回一个 Animal 对象 if (animal instanceof Dog) { Dog dog = (Dog) animal; // 在这里,你可以安全地调用 Dog 类的方法 }
Class对象¶
- Class对象包含了与类相关的信息,Class对象被用来创建常规对象,Java使用Class对象执行反射
- 每个类都有一个Class对象,存储在同名的class文件中
- 类在首次使用时才会被动态加载到JVM,类加载器检查是否加载了类型的Class对象,如果没有就定位到对应的class文件并校验后加载,Class对象加载到内存后会用于创建类的所有对象
- 类第一次被加载时会调用类的静态初始化块
获取class对象¶
- 获取Class对象
Class.forName("classname");
- 需要使用包含包名称的完全限定类型
-
找不到则抛出ClassNotFoundException
-
如果有对应类型的对象,可以通过对象获取
xx.getClass()
-
使用类字面常量获取Class
ClassName.class'
编译时检查们不用放在try- 包装类使用
TYPE
关键字
- 包装类使用
- 不一定会立即导致初始化,等到首次使用静态方法再初始化,对于static final的编译时常量不会导致初始化
常用方法¶
getName()
获取完整类名(含包路径),外部类内部类用$分隔getCanonicalName()
获取规范名,外部类内部类用.分隔,可能返回null(比如匿名内部类)
isInterface()
getSimpleName()
getInterfaces()
返回Class数组,即所有实现的接口类型getSuperClass()
查询直接基类Method[] getDeclaredMethods()
返回当前Class
对象所表示的类或接口的所有声明的方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。Annotation[] getDeclaredAnnotations()
获取全部注解-
newInstance()
得到一个Object类型的引用,类型必须要有无参构造器(非public类要手动提供一个public的无参构造) -
创建对象
//无参构造 try { return type.getConstructor().newInstance(); } catch(Exception e) { throw new RuntimeException(e); } //有参构造 Class <?> clazz = MyClass.class; Constructor <?> constructor = clazz.getConstructor(String.class, int.class); MyClass myObject = (MyClass) constructor.newInstance("example", 123);
-
结合泛型
Class<Integer>
只接受特定类型的Class-
通配
Class<?>
(实际上\<?>可以省略)Class<? extends Number>
可以存储整数/浮点数Class <FancyToy> ftc = FancyToy.class; // Produces exact type: FancyToy fancyToy = ftc.getConstructor().newInstance(); Class <? super FancyToy> up = ftc.getSuperclass(); // 虽然知道 Toy 是基类,但编译不通过,只能是 FancyToy 的某个基类 // Class <Toy> up2 = ftc.getSuperclass(); //不确定具体类型,使用 Object 引用 Object obj = up.getConstructor().newInstance();
-
cast方法
- 使用Class引用类型转换
- 用于将对象转换为由
Class
实例表示的类或接口 -
这个方法在运行时动态地执行类型转换,与强制类型转换类似,但更加灵活,因为它允许在编译时不知道具体类的情况下进行转换。
Class <String> clazz = String.class; Object obj = "Hello"; String str = clazz.cast(obj); //str = (String)obj
-
类方法提取器
-
可以查看一个类(包含其基类在内)的全部可用方法
//全部方法 Method [] methods = c.getMethods(); //全部构造方法 Constructor [] ctors = c.getConstructors();
-
方法Method
getName()
: 返回方法的名称。getReturnType()
: 返回方法的返回类型。getParameterTypes()
: 返回方法参数的类型数组。getModifiers()
: 返回方法的修饰符,如public
,private
等。getExceptionTypes()
: 返回方法声明抛出的异常类型数组。- 动态调用方法
method.invoke(Object obj, Object... args)
: 允许你动态地调用方法。obj
是要调用其方法的对象,args
是方法调用时传递的参数。
- 动态调用方法
getAnnotation(Class<T> annotationClass)
: 返回该方法上指定类型的注解。getAnnotations()
: 返回该方法上所有注解的数组。getGenericReturnType()
: 返回方法的泛型返回类型。-
getGenericParameterTypes()
: 返回方法的泛型参数类型数组。 -
字段Field
- 获取类或接口的全部字段
getDeclaredFields()
getName()
:返回字段的名称。getType()
:返回一个Class
对象,该对象表示字段的类型。get(Object obj)
:返回指定对象上此字段的值。set(Object obj, Object value)
:将指定对象上此字段的值设置为指定的新值。getModifiers()
:返回此字段的Java语言修饰符,这可以用来检查字段是否为public
,private
,protected
,static
等。getDeclaredAnnotations()
:返回此元素上直接存在的所有注解。
转型前检查¶
- 类型检查
x instanceof Dog
返回布尔值,是否是特定类型的对象 -
在向下转型之前进行检查,防止遇到ClassCastException
-
动态isInstance
Class.isInstance()
-
可以使用Class数组方便的使用
Class对象.isInstance(待判断类型的对象)
-
instanceof和isInstance的比较是等价的
- 行同类型/基类都满足比较的条件
-
但是使用==或equal则表示要求类型完全相同
-
isAssignableFrom
- 判断一个类是否相同或是另一个类的超类或接口。F
parentClass.isAssignableFrom(childClass);
代理模式¶
接口和类型信息¶
- 把一个实现接口的类型赋值给接口类型,可以隐藏许多信息,只保留接口规定的方法,但是通过反射可以发现这一点并进行强制转换,从而破坏限制
- 将实现接口的类声明为包作用域可以避免,因为包外根本没有这个类。但是虽然不能转型,如果知道方法的名字,仍然可以访问!
callHiddenMethod(Object a, String methodName) throws Exception { //获取方法 Method g = a.getClass().getDeclaredMethod(methodName); //设置为允许访问 g.setAccessible(true); g.invoke(a); }
- 无论是使用private方法还是匿名内部类都无法阻止