Java反射笔记
Class的获取
1、 Object.getClass()
Car car = new Car();
Class clazz = car.getClass();
这种方式不适合基本数据类型比如 int float等
2、 String.class
Class clazz = Car.class;
Class cls1 = int.class;
Class cls2 = String.class;
3、 Class.forName()
Class clz = Class.forName("com.frank.test.Car");
“com.frank.test.Car”
就是 Car 这个类的全限定名称,它包括包名+类名。
Class 的名字
这里放一个 Car 类,做 Demo
package cn.smartsean;
public class Car {
private String mBand;
private Color mColor;
public enum Color {
RED,
WHITE,
BLACK,
BLUE,
YELLOW
}
public Car() {
super();
}
public Car(String band) {
mBand = band;
}
public void drive(){
System.out.println("滴滴滴滴,开车了");
}
@Override
public String toString() {
return "Car [mBand=" + mBand + ", 车的颜色是=" + mColor + "]";
}
}
测试类:
Class clz = Car.class;
System.out.println(clz.getName());
Class clz1 = float.class;
System.out.println(clz1.getName());
Class clz2 = Void.class;
System.out.println(clz2.getName());
Class clz3 = new int[]{}.getClass();
System.out.println(clz3.getName());
Class clz4 = new Car[]{}.getClass();
System.out.println(clz4.getName());
1、 Class.getName();
- 当 Class 代表一个引用时
getName()
方法返回的是一个二进制形式的字符串 比如 cn.smartsean.Car
- 当 Class 代表一个基本数据类型
getName()
方法返回的是他们的关键字,比如 int.class
返回的是 int
- 当 Class 代表的是基础数据类型的数组时
比如 int[][][]
这样的 3 维数组时, getName()
返回的是 [[[I
这样的字符串
这种情况的规则:
最后上面的测试类运行结果:
cn.smartsean.Car
float
java.lang.Void
[I
[Lcn.smartsean.Car;
2、 Class.getSimpleName();
顾名思义,就是获取简易名字的。
上面测试类运行结果:
cn.smartsean.Car
Car
float
float
java.lang.Void
Void
[I
int[]
[Lcn.smartsean.Car;
Car[]
需要注意的是对于匿名内部类。返回的是一个空的字符串(注意不是null是”“)
3、 Class.getCanonicalName();
Canonical 是官方、标准的意思,那么 getCanonicalName() 自然就是返回一个 Class 对象的官方名字,这个官方名字 canonicalName 是 Java 语言规范制定的,如果 Class 对象没有 canonicalName 的话就返回 null。
getCanonicalName() 是 getName() 和 getSimpleName() 的结合。
- getCanonicalName() 返回的也是全限定类名,但是对于内部类,不用 $ 开头,而用 .。
- getCanonicalName() 对于数组类型的 Class,同 simplename 一样直接在后面添加 [] 。
- getCanonicalName() 不同于 simplename 的地方是,不存在 canonicalName 的时候返回 null 而不是空字符串。
- 局部类和匿名内部类不存在 canonicalName。
上面测试类的运行结果:
getName():cn.smartsean.Car
getSimpleName():Car
getCanonicalName():cn.smartsean.Car
getName():float
getSimpleName():float
getCanonicalName():float
getName():java.lang.Void
getSimpleName():Void
getCanonicalName():java.lang.Void
getName():[I
getSimpleName():int[]
getCanonicalName():int[]
getName():[Lcn.smartsean.Car;
getSimpleName():Car[]
getCanonicalName():cn.smartsean.Car[]
Class 的修饰符
通常情况下,Java 类的修饰符有以下几种:
- 限定作用域的 public private protected
- 用来限制子类必须复写的 abstract
- 用来标记静态的 static
- 注解
Class clz = Test.class;
System.out.println("修饰符的id:" + clz.getModifiers());
System.out.println("修饰符的实际值:" + Modifier.toString(clz.getModifiers()));
修饰符的id:1025
修饰符的实际值:public abstract
可以看出,通过 Modifier.toString()
方法可以获得实际的修饰符
一个类定义的时候可能会被多个修饰符修饰,为了一并获取,所以 Java 工程师考虑到了位运算,用一个 int 数值来记录所有的修饰符,然后不同的位对应不同的修饰符,这些修饰符对应的位都定义在 Modifier 这个类当中。
Modifier 还提供了一系列的静态工具方法用来对修饰符进行操作:
比如 isPublic、isPrivate、isProtected、isStatic、isFinal、isSynchronized、isVolatile、isTransient、isNative、isInterface、isAbstract、isStrict等
获取 Class 的成员
一个类的成员包括属性(有人翻译为字段或者域)、方法、构造方法(也是方法)。对应到 Class 中就是 Field、Method、Constructor。
获取 Filed
获取指定名字的属性
获取指定名字的属性有 2 个 API
1、 getDeclaredField(String name)
getDeclaredField() 获取的是 Class 中被 private 修饰的属性
2、 getField(String name)
getField() 方法获取的是非私有属性,并且 getField() 在当前 Class 获取不到时会向祖先类获取
获取所有属性
1、getDeclaredFields()
获取所有的属性, 比如private、protected、public 和 default 修饰的属性,但不包括从父类继承下来的属性
2、getFields()
获取自身的所有的 public 属性(仅获取被 public 修饰的),包括从父类继承下来的。
上面两个都是返回 Field[] 数组,存放所有获取到的值。
测试代码:
public class Father {
private String father;
protected String father1;
public String father2;
}
public class Son extends Father {
private String son;
protected String son1;
public String son2l;
}
Class clz1 = Son.class;
System.out.println("getDeclaredFields:");
System.out.println();
for (Field field : clz1.getDeclaredFields()) {
System.out.println(field.getName());
}
System.out.println();
System.out.println("getFields:");
System.out.println();
for (Field field : clz1.getFields()) {
System.out.println(field.getName());
}
运行结果:
getDeclaredFields:
son
son1
son2l
getFields:
son2l
father2
获取 Method
和上面的获取 Field 基本类似
//获取指定方法
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
//获取指定方法
public Method getMethod(String name, Class<?>... parameterTypes)
//获取**所有**方法
public Method[] getDeclaredMethods() throws SecurityException
//获取**所有**方法
public Method[] getMethods() throws SecurityException
获取 Constructor
Java 反射把构造器从方法中单独拎出来了,用 Constructor 表示
Constructor 不能从父类继承,所以就没有办法通过 getConstructor() 获取到父类的 Constructor
//获取指定的构造方法
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
//获取指定的构造方法
public Constructor<T> getConstructor(Class<?>... parameterTypes)
//获取**所有**的构造方法
public Constructor<?>[] getDeclaredConstructors() throws SecurityException
//获取**所有**的构造方法
public Constructor<?>[] getConstructors() throws SecurityException
Field 的操控
Field 要么是 8 种基础类型 int、long、float、double、boolean、char、byte 和 short。或者是引用,所有的引用都是 Object 的后代。
Field 类型的获取
获取 Field 的类型,通过 2 个方法:
//能够获取到泛型类型,比getType更加的详细
public Type getGenericType() {}
public Class<?> getType() {}
测试代码:
public class Test {
private Car mCar;
private int[] mInts;
private List<String> mStrings;
private Map<String, Car> mMap;
}
Class clz = Test.class;
for (Field field : clz.getDeclaredFields()) {
System.out.println("name :" + field.getName() + " getType() :" + field.getType() + " getGenericType() :" + field.getGenericType());
}
输出:
name | type() | genericType() |
---|---|---|
mCar | class cn.smartsean.Car | class cn.smartsean.Car |
mInts | class [I | class [I |
mStrings | interface java.util.List | java.util.List |
mMap | interface java.util.Map | java.util.Map |
Field 修饰符的获取
同 Class 一样,Field 也有很多修饰符。通过 getModifiers() 方法就可以轻松获取。
和获取 Class 的修饰符一样
Field 内容的读取与赋值
这个是反射机制中对 Field 最主要的目的。
Field 这个类定义了一系列的 get 方法来获取不同类型的值
Field 也定义了一系列的 set 方法用来对其自身进行赋值。与上面的基本对应。
其中Object 是类的实例引用。
Class 本身不对成员进行储存,它只提供检索,所以需要用 Field、Method、Constructor 对象来承载这些成员,所以,针对成员的操作时,一般需要为成员指定类的实例引用。如果难于理解的话,可以这样理解,班级这个概念是一个类,一个班级有几十名学生,现在有A、B、C 3 个班级,将所有班级的学生抽出来集合到一个场地来考试,但是学生在试卷上写上自己名字的时候,还要指定自己的班级,这里涉及到的 Object 其实就是类似的作用,表示这个成员是具体属于哪个 Object。这个是为了精确定位。
下面以实例说明对 Filed 的操控(设置一个String的值)
public class Test1 {
private String mString = "father";
}
Test1 test1 = new Test1();
Class clz1 = test1.getClass();
Field field = clz1.getDeclaredField("mString");
System.out.println(field.getName());
//反射中访问了 private 修饰的成员(String),如果要消除异常的话,需要添加一句代码,如果是其他修饰符可以不加这一句
field.setAccessible(true);
System.out.println("mString的值="+field.get(test1));
field.set(test1,"son");
System.out.println("====修改 father 为 son====");
System.out.println("mString的值="+field.get(test1));
结果为:
mString
mString的值=father
====修改 father 为 son====
mString的值=son
Method 的操控
方法由下面几个要素构成:
- 方法名
- 方法参数
- 方法返回值
- 方法的修饰符
- 方法可能会抛出的异常
Method 获取方法名
getName() 方法
Method 获取方法参数
public Parameter[] getParameters() {}
返回的是一个 Parameter 数组,在反射中 Parameter 对象就是用来映射方法中的参数。经常使用的方法有:
Parameter 常用方法
// 获取参数名字
public String getName() {}
// 获取参数类型
public Class<?> getType() {}
// 获取参数的修饰符
public int getModifiers() {}
有时候我们不需要参数的名字,只要参数的类型就好了,通过 Method 中下面的方法获取。
// 获取所有的参数类型,不包括泛型
public Class<?>[] getParameterTypes() {}
// 获取所有的参数类型,包括泛型
public Type[] getGenericParameterTypes() {}
Method 获取返回值类型
// 获取返回值类型
public Class<?> getReturnType() {}
// 获取返回值类型包括泛型
public Type getGenericReturnType() {}
Method 获取修饰符
public int getModifiers() {}
然后通过 Modifier 类去获取
Method 获取异常类型
public Class<?>[] getExceptionTypes() {}
public Type[] getGenericExceptionTypes() {}
Method 方法的执行
是整个反射机制的核心内容了,很多时候运用反射目的其实就是为了以常规手段执行 Method。
Method 调用 invoke() 的时候,存在许多细节:
invoke() 方法中第一个参数 Object 实质上是 Method 所依附的 Class 对应的类的实例,如果这个方法是一个静态方法,那么 ojb 为 null,后面的可变参数 Object 对应的自然就是参数。
invoke() 返回的对象是 Object,所以实际上执行的时候要进行强制转换。
在对 Method 调用 invoke() 的时候,如果方法本身会抛出异常,那么这个异常就会经过包装,由 Method 统一抛出 InvocationTargetException。而通过 InvocationTargetException.getCause() 可以获取真正的异常。
Constructor 的操控
Constructor 同 Method 差不多,但是它特别的地方在于,它能够创建一个对象。
在 Java 反射机制中有两种方法可以用来创建类的对象实例:
- Class.newInstance()
- Constructor.newInstance()。
官方文档建议开发者使用后面这种方法,下面是原因。
- Class.newInstance() 只能调用无参的构造方法,而 Constructor.newInstance() 则可以调用任意的构造方法。
- Class.newInstance() 通过构造方法直接抛出异常,而 Constructor.newInstance() 会把抛出来的异常包装到 InvocationTargetException 里面去,这个和 Method 行为一致。
- Class.newInstance() 要求构造方法能够被访问,而 Constructor.newInstance() 却能够访问 private 修饰的构造器。
下面实例说明:
public class TestConstructor {
private String self;
public TestConstructor() {
self = " Frank ";
}
public TestConstructor(String self) {
this.self = self;
}
@Override
public String toString() {
return "TestConstructor [self=" + self + "]";
}
}
public class Main {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class clz = TestConstructor.class;
TestConstructor test1 = (TestConstructor) clz.newInstance();
System.out.println(test1.toString());
Constructor constructor = clz.getConstructor(String.class);
TestConstructor testConstructor = (TestConstructor) constructor.newInstance("smrtsean");
System.out.println(testConstructor.toString());
}
}
结果:
TestConstructor [self= Frank ]
TestConstructor [self=smrtsean]
反射中的数组
数组的类型
数组本质上是一个 Class,而在 Class 中存在一个方法用来识别它是否为一个数组
Class.java
public native boolean isArray();
测试代码:
public class ShuZu {
private int[] array;
private Car[] cars;
}
public class Main {
public static void main(String[] args){
ShuZu shuZu = new ShuZu();
Class clz = shuZu.getClass();
Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
Class c = field.getType();
if (c.isArray()){
System.out.println("type是:"+c.getName());
System.out.println("ComponentType type is :"+c.getComponentType());
}
}
}
}
结果:
type是:[I
ComponentType type is :int
type是:[Lcn.smartsean.Car;
ComponentType type is :class cn.smartsean.Ca
反射中动态创建数组
反射创建数组是通过 Array.newInstance() 这个方法。
Array.java
public static Object newInstance(Class<?> componentType, int... dimensions)
throws IllegalArgumentException, NegativeArraySizeException {}
第一个参数指定的是数组内的元素类型,后面的是可变参数,表示的是相应维度的数组长度限制。
比如,我要创建一个 int[2][3] 的数组。
Array.newInstance(int.class,2,3);
Array 的读取与赋值
反射中的枚举 Enum
反射中的枚举 Enum
同数组一样,枚举本质上也是一个 Class 而已,但反射中还是把它单独提出来了
在 Java 反射中,可以把枚举看成一般的 Class,但是反射机制也提供了 3 个特别的的 API 用于操控枚举。
// 用来判定 Class 对象是不是枚举类型
Class.isEnum()
// 获取所有的枚举常量
Class.getEnumConstants()
// 判断一个 Field 是不是枚举常量
java.lang.reflect.Field.isEnumConstant()
枚举的获取与设定
因为等同于 Class,所以枚举的获取与设定就可以通过 Field 中的 get() 和 set() 方法。
需要注意的是,如果要获取枚举里面的 Field、Method、Constructor 可以调用 Class 的通用 API。
实例:
public enum State {
IDLE,
DRIVING,
STOPPING,
test();
int test1(){
return 1;
}
}
public class Meiju {
private State state = State.DRIVING;
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
}
public class Main {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Class clz = State.class;
if (clz.isEnum()) {
System.out.println(clz.getName() + " is Enum");
System.out.println(Arrays.asList(clz.getEnumConstants()));
Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
if (field.isEnumConstant()) {
System.out.println(field.getName() + "is EnumConstant");
} else {
System.out.println(field.getName() + "is not EnumConstant");
}
}
}
Class cMeiJu = Meiju.class;
Meiju meiju = new Meiju();
Field field = cMeiJu.getDeclaredField("state");
field.setAccessible(true);
State state = (State) field.get(meiju);
System.out.println("state:====="+state);
field.set(meiju, State.STOPPING);
System.out.println("State current is "+meiju.getState());
}
}
结果:
cn.smartsean.enumreflect.State is Enum
[IDLE, DRIVING, STOPPING, test]
IDLEis EnumConstant
DRIVINGis EnumConstant
STOPPINGis EnumConstant
testis EnumConstant
$VALUESis not EnumConstant
state:=====DRIVING
State current is STOPPING
反射总结
1、Java 中的反射是非常规编码方式。
2、Java 反射机制的操作入口是获取 Class 文件。 有 Class.forName()、 .class 和 Object.getClass() 3 种。
3、获取 Class 对象后还不够,需要获取它的 Members,包含 Field、Method、Constructor。
4、Field 操作主要涉及到类别的获取,及数值的读取与赋值。
5、Method 算是反射机制最核心的内容,通常的反射都是为了调用某个 Method 的 invoke() 方法。
6、通过 Class.newInstance() 和 Constructor.newInstance() 都可以创建类的对象实例,但推荐后者。因为它适应于任何构造方法,而前者只会调用可见的无参数的构造方法。
7、数组和枚举可以被看成普通的 Class 对待。
最后,需要注意的是。
反射是非常规开发手段,它会抛弃 Java 虚拟机的很多优化,所以同样功能的代码,反射要比正常方式要慢,所以考虑到采用反射时,要考虑它的时间成本。另外,就如无人驾驶之于汽车一样,用着很爽的同时,其实风险未知。