Java反射笔记

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 虚拟机的很多优化,所以同样功能的代码,反射要比正常方式要慢,所以考虑到采用反射时,要考虑它的时间成本。另外,就如无人驾驶之于汽车一样,用着很爽的同时,其实风险未知。

猜你喜欢

转载自blog.csdn.net/Sean_css/article/details/79719098