类型信息--深入理解反射

什么是RTTI

RTTI(Run-Time Type Identification)运行时类型识别,其作用是在运行时识别一个对象的类型和类信息,运行时识别一个对象的类型。主要有两种方式,一种是“传统的”的RTTI,另一种是反射机制,它允许我们在运行时发现和使用类的信息。

abstract class Shape {

    void draw() {
        System.out.println(this + ".draw()");
    }

    @Override
    abstract public String toString();
}
public class Circle extends Shape {
    @Override
    public String toString() {
        return "circle";
    }
}
public class Square extends Shape {
    @Override
    public String toString() {
        return "square";
    }
}
public class Shapes {

    public static void main(String[] args) {
        List<Shape> list = Arrays.asList(new Circle(),new Square());
        list.stream().forEach(System.out::println);
    }
}

 当从数据中取元素的时候,这种容器实际上它将所有的的事物都当做Object持有,会自动将结果转回Shape,这是RTTI最基本的使用方式,因为在Java中,所有的类型转换都是在运行中进行正确性检查的。这也是RTTI名字的含义:在运行时,识别一个对象的类型。

 在这个例子中,RTTI类型的转换并不彻底:Object被转型为Shape,而不是转型为Circle,Square,这是因为目前我们只知道这个List保存的都是Shape,在编译时,将有容器和Java泛型系统来强制确保这一点,而在运行时,由类型转换操作来确保这一点。

Class 对象

 类是程序的一部分,每个类都有一个Class对象。换言之,每当编写并且编写一个新类,就会产生一个Class对象并且这个Class对象会被保存在同名.class文件里(编译后的字节码文件保存的就是Class对象),那为什么需要这样一个Class对象呢?是这样的,当我们new一个新对象或者引用静态成员变量时,Java虚拟机(JVM)中的类加载器子系统会将对应Class对象加载到JVM中,然后JVM再根据这个类型信息相关的Class对象创建我们需要实例对象或者提供静态变量的引用值。需要特别注意的是,手动编写的每个class类,无论创建多少个实例对象,在JVM中都只有一个Class对象,即在内存中每个类有且只有一个相对应的Class对象.
 声明普通的Class对象,在编译器并不会检查Class对象的确切类型是否符合要求,如果存在错误只有在运行时才得以暴露出来。但是通过泛型声明指明类型的Class对象,编译器在编译期将对带泛型的类进行额外的类型检查,确保在编译期就能保证类型的正确性.

public class ClassDemo {
    public static void main(String[] args){
        //没有泛型
        Class intClass = int.class;

        //带泛型的Class对象
        Class<Integer> integerClass = int.class;

        integerClass = Integer.class;

        //没有泛型的约束,可以随意赋值
        intClass= double.class;

        //编译期错误,无法编译通过
//        integerClass = double.class;
    }
}

 到这我们也就可以得出以下几点信息:

  • Class类也是类的一种,与class关键字是不一样的。

  • 手动编写的类被编译后会产生一个Class对象,其表示的是创建的类的类型信息,而且这个Class对象保存在同名.class的文件中(字节码文件),比如创建一个Shapes类,编译Shapes类后就会创建其包含Shapes类相关类型信息的Class对象,并保存在Shapes.class字节码文件中。

  • 每个通过关键字class标识的类,在内存中有且只有一个与之对应的Class对象来描述其类型信息,无论创建多少个实例对象,其依据的都是用一个Class对象。

  • Class类只存私有构造函数,因此对应Class对象只能有JVM创建和加载。

  • Class类的对象作用是运行时提供或获得某个对象的类型信息。

Class.forName与.class的区别

* .class称为Class字面常量,使用* .class获得得对类的引用不会引发初始化,Class.forName获得得对类的引用会引发初始化。


public class Initialization2 {

    static int staticNonFinal2 = 147;
    static {
        System.out.println("init  staticNonFinal2 " + staticNonFinal2);

    }
}
public class Initialization {
    static final int staticFinal = 47;
    static final int staticFinal2 = ClassInitialization.random.nextInt(1000);
    static {
        System.out.println("init  Initialization2 " + staticFinal);
        System.out.println("init  Initialization2 ,random " + staticFinal2);
    }
}
public class ClassInitialization {
    public static Random random = new Random(47);
    public static void main(String[] args) throws ClassNotFoundException {
        Class initable = Initialization2.class;
        Class initale2 = Class.forName("think.in.java.chapter14.Initialization");
    }
}

运行结果如下:

init  Initialization2 47
init  Initialization2 ,random 258

可以发现Class.forName引发了类的初始化。

泛化Class的引用

采用泛化Class的引用构建一个注册工厂

public interface Factory<T> {
    T create();
}
public class FuelFilter extends Part {
    public static class Factory implements think.in.java.chapter14.Factory<FuelFilter> {
        @Override
        public FuelFilter create() {
            return new FuelFilter();
        }
    }
}

public class AirFilter extends Part {
    public static class Factory implements think.in.java.chapter14.Factory<AirFilter> {

        @Override
        public AirFilter create() {
            return new AirFilter();
        }
    }
}
public class Part {
    @Override
    public String toString() {
        return getClass().getSimpleName();
    }
    static List<Factory<? extends Part>> partFactories = new ArrayList<>();
    static {
        partFactories.add(new FuelFilter.Factory());
        partFactories.add(new AirFilter.Factory());
    }
    public static void main(String[] args) {
        partFactories.stream().forEach(f -> System.out.println(f.create()));
    }
}

运行结果如下:

FuelFilter
AirFilter

什么是反射技术

 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。一直以来反射技术都是Java中的闪亮点,这也是目前大部分框架(如Spring/Mybatis等)得以实现的支柱。在Java中,Class类与java.lang.reflect类库一起对反射技术进行了全力的支持。在反射包中,我们常用的类主要有Constructor类表示的是Class 对象所表示的类的构造方法,利用它可以在运行时动态创建对象、Field表示Class对象所表示的类的成员变量,通过它可以在运行时动态修改成员变量的属性值(包含private)、Method表示Class对象所表示的类的成员方法,通过它可以动态调用对象的方法(包含private),下面将对这几个重要类进行分别说明。

Constructor

 Constructor类存在于反射包(java.lang.reflect)中,反映的是Class 对象所表示的类的构造方法。获取Constructor对象是通过Class类中的方法获取的,Class类与Constructor相关的主要方法如下:

方法返回值方法签名方法说明
static Class<?>forName(String className)返回与带有给定字符串名的类或接口相关联的 Class 对象。
ConstructorgetConstructor(Class<?>… parameterTypes)返回指定参数类型、具有public访问权限的构造函数对象
Constructor<?>[]getConstructors()返回所有具有public访问权限的构造函数的Constructor对象数组
ConstructorgetDeclaredConstructor(Class<?>… parameterTypes)返回指定参数类型、所有声明的(包括private)构造函数对象
Constructor<?>[]getDeclaredConstructor()返回所有声明的(包括private)构造函数对象
TnewInstance()创建此 Class 对象所表示的类的一个新实例。
ClassgetDeclaringClass()返回 Class 对象,该对象表示声明由此 Constructor 对象表示的构造方法的类,其实就是返回真实类型(不包含参数)
Type[]getGenericParameterTypes()按照声明顺序返回一组 Type 对象,返回的就是 Constructor对象构造函数的形参类型。
StringgetName()以字符串形式返回此构造方法的名称。
Class<?>[]getParameterTypes()按照声明顺序返回一组 Class 对象,即返回Constructor 对象所表示构造方法的形参类型
TnewInstance(Object… initargs)使用此 Constructor对象表示的构造函数来创建新实例
StringtoGenericString()返回描述此 Constructor 的字符串,其中包括类型参数。

下面通过一个简单例子来演示这些方法

package think.in.java.chapter14.constructor;

enum Sex {
    man, women;
}


public class Person {

    public int age;
    private String name;

    public Sex sex = Sex.man;

    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }


    private Person(int age, String name) {
        this.age = age;
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }


    public void draw() {
        System.out.println("person draw :");
    }

    public void draw(int age, String name) {
        System.out.println("person draw :" + age + " name: " + name);
    }

    private String sing(String musicName) {
        System.out.println("--- person sing");
        return musicName;
    }
}

package think.in.java.chapter14.constructor;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
import java.util.Arrays;

public class ConstructorDemo {

    public static void main(String[] args) throws ClassNotFoundException
            , NoSuchMethodException, IllegalAccessException,
            InvocationTargetException, InstantiationException {

        Class<?> clazz = Class.forName("think.in.java.chapter14.constructor.Person");
        Constructor constructor = clazz.getConstructor(String.class);

        System.out.println("----- Constructor new Instance");
        Person person1 = (Person) constructor.newInstance("zhouchen");
        person1.setAge(12);
        System.out.println(person1.toString());


        System.out.println("----- Constructor getDeclaredConstructor setAccessible");
        Constructor constructor2 = clazz.getDeclaredConstructor(int.class, String.class);
        constructor2.setAccessible(true);
        Person person2 = (Person) constructor2.newInstance(26, "lingxiao");
        System.out.println(person2.toString());

        System.out.println("----- Constructor getDeclaredConstructors getParameterTypes");
        Constructor<?> cons[] = clazz.getDeclaredConstructors();
        Arrays.asList(cons).forEach(con -> {
            Class<?> clazzs[] = con.getParameterTypes();
            for (int i = 0; i < clazzs.length; i++) {
                System.out.println(clazzs[i].getName());
            }
        });

        System.out.println("----- Constructor getGenericParameterTypes");
        Type[] types = constructor2.getGenericParameterTypes();
        Arrays.asList(types).forEach(type -> System.out.println(type.getTypeName()));

        Type[] types2 = constructor2.getParameterTypes();
        System.out.println("----- Constructor getParameterTypes");
        Arrays.asList(types2).forEach(type -> System.out.println(type.getTypeName()));

        System.out.println("----- Constructor getDeclaringClass");
        System.out.println(constructor2.getDeclaringClass().getName());

        System.out.println("----- Constructor toGenericString");
        System.out.println(constructor2.toGenericString());
    }
}

输出结果

----- Constructor new Instance
Person{age=12, name='zhouchen'}
----- Constructor getDeclaredConstructor setAccessible
Person{age=26, name='lingxiao'}
----- Constructor getDeclaredConstructors getParameterTypes
int
java.lang.String
java.lang.String
----- Constructor getGenericParameterTypes
int
java.lang.String
----- Constructor getParameterTypes
int
java.lang.String
----- Constructor getDeclaringClass
think.in.java.chapter14.constructor.Person
----- Constructor toGenericString
private think.in.java.chapter14.constructor.Person(int,java.lang.String)

Field
方法返回值方法名称方法说明
FieldgetDeclaredField(String name)获取指定name名称的(包含private修饰的)字段,不包括继承的字段
Field[]getDeclaredField()获取Class对象所表示的类或接口的所有(包含private修饰的)字段,不包括继承的字段
FieldgetField(String name)获取指定name名称、具有public修饰的字段,包含继承字段
Field[]getField()获取修饰符为public的字段,包含继承字段
voidset(Object obj, Object value)将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
Objectget(Object obj)返回指定对象上此 Field 表示的字段的值
Class<?>getType()返回一个 Class 对象,它标识了此Field 对象所表示字段的声明类型。
booleanisEnumConstant()如果此字段表示枚举类型的元素则返回 true;否则返回 false
StringtoGenericString()返回一个描述此 Field(包括其一般类型)的字符串
StringgetName()返回此 Field 对象表示的字段的名称
Class<?>getDeclaringClass()返回表示类或接口的 Class 对象,该类或接口声明由此 Field 对象表示的字段
voidsetAccessible(boolean flag)将此对象的 accessible 标志设置为指示的布尔值,即设置其可访问性
package think.in.java.chapter14.constructor;

import java.lang.reflect.Field;

/**
 * @author: <a href="mailto:lingxiao@2dfire.com">凌霄</a>
 * @time: Created in 下午3:11 2019/4/7
 * @desc
 */
public class FieldDemo {

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
        Class clazz = Person.class;
        Person person = (Person) clazz.newInstance();

        Field[] fields = clazz.getDeclaredFields();

        for (Field field : fields) {
            System.out.println(field.getName());
        }

        System.out.println("------Field getName");
        Field ageField = clazz.getDeclaredField("age");
        System.out.println(ageField.getName());

        System.out.println("------Field getDeclaredField setAccessible");
        Field nameField = clazz.getDeclaredField("name");
        nameField.setAccessible(true);
        nameField.set(person, "lx");
        System.out.println(person.toString());

        System.out.println("------Field getType");
        System.out.println(nameField.getType());
        System.out.println("------Field toGenericString");
        System.out.println(nameField.toGenericString());
        System.out.println("------Field getDeclaringClass");
        System.out.println(nameField.getDeclaringClass());

        System.out.println("------Field isEnumConstant");
        Field sexField = Sex.class.getField("man");
        System.out.println(sexField.toGenericString());
        System.out.println("-- is enum " + sexField.isEnumConstant());

        System.out.println("------Field toGenericString isSynthetic");
        Field sexField2 = clazz.getField("sex");
        System.out.println(sexField2.toGenericString());
        System.out.println("-- is syn " + sexField2.isSynthetic());

        System.out.println("------Field  getFields");
        Field[] fields2 = clazz.getFields();

        for (Field field : fields2) {
            System.out.println(field.getName());
        }
    }
}

运行结果如下:


----- Constructor new Instance
Person{age=12, name='zhouchen'}
----- Constructor getDeclaredConstructor setAccessible
Person{age=26, name='lingxiao'}
----- Constructor getDeclaredConstructors getParameterTypes
int
java.lang.String
java.lang.String
----- Constructor getGenericParameterTypes
int
java.lang.String
----- Constructor getParameterTypes
int
java.lang.String
----- Constructor getDeclaringClass
think.in.java.chapter14.constructor.Person
----- Constructor toGenericString
private think.in.java.chapter14.constructor.Person(int,java.lang.String)
Method

 Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息,所反映的方法可能是类方法或实例方法(包括抽象方法)。下面是Class类获取Method对象相关的方法:

方法返回值方法名称方法说明
MethodgetDeclaredMethod(String name, Class<?>… parameterTypes)返回一个指定参数的Method对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。
Method[]getDeclaredMethod()返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
MethodgetMethod(String name, Class<?>… parameterTypes)返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
Method[]getMethods()返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。
Objectinvoke(Object obj, Object… args)对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。
Class<?>getReturnType()返回一个 Class 对象,该对象描述了此 Method 对象所表示的方法的正式返回类型,即方法的返回类型
TypegetGenericReturnType()返回表示由此 Method 对象所表示方法的正式返回类型的 Type 对象,也是方法的返回类型。
Class<?>[]getParameterTypes()按照声明顺序返回 Class 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型。即返回方法的参数类型组成的数组
Type[]getGenericParameterTypes()按照声明顺序返回 Type 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型的,也是返回方法的参数类型
StringgetName()以 String 形式返回此 Method 对象表示的方法名称,即返回方法的名称
booleanisVarArgs()判断方法是否带可变参数,如果将此方法声明为带有可变数量的参数,则返回 true;否则,返回 false。
StringtoGenericString()返回描述此 Method 的字符串,包括类型参数。
package think.in.java.chapter14.constructor;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Arrays;

public class MethodDemo {

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {

        Class clazz = Person.class;
        Person person = (Person) clazz.newInstance();

        System.out.println("------Method invoke");
        Method method = clazz.getMethod("draw",int.class, String.class);
        method.invoke(person,28,"zc");

        System.out.println("------Method getMethods");
        Method[] methods = clazz.getMethods();
        Arrays.asList(methods).forEach(method1 -> System.out.println(method1.getName()));


        System.out.println("------Method getDeclaredMethods");
        Method[] methods2 = clazz.getDeclaredMethods();
        Arrays.asList(methods2).forEach(method1 -> System.out.println(method1.getName()));

        System.out.println("------Method getDeclaredMethod");
        Method singMethod = clazz.getDeclaredMethod("sing",String.class);
        singMethod.setAccessible(true);
        singMethod.invoke(person, "july");

        System.out.println("------Method getReturnType");
        System.out.println(singMethod.getReturnType());

        System.out.println("------Method getParameterTypes");
        Type[] types = method.getParameterTypes();
        Arrays.asList(types).forEach(type -> type.getTypeName());

    }
}

运行结果如下:

------Method invoke
person draw :28 name: zc
------Method getMethods
toString
getName
setName
getAge
setAge
draw
draw
wait
wait
wait
equals
hashCode
getClass
notify
notifyAll
------Method getDeclaredMethods
toString
getName
setName
getAge
setAge
draw
draw
sing
------Method getDeclaredMethod
--- person sing
------Method getReturnType
class java.lang.String
------Method getParameterTypes

 通过上面的输出可以看出,在通过getMethods方法获取Method对象时,会把父类的方法也获取到,如上的输出结果,把Object类的方法都打印出来了。而getDeclaredMethod/getDeclaredMethods方法都只能获取当前类的方法。我们在使用时根据情况选择即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值