Java泛型和反射

前言

本文重点介绍反射的概念,以及反射之于泛型的应用,如果对泛型不清楚的同学可以查看我的Java泛型程序设计一文

一、概述

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的是Class类中的方法,所以先要获取到每一个字节码文件对应的Class类型的对象。Class对象的由来是将class文件读入内存,并为之创建一个Class对象。

反射就是把Java的各种成分映射成相应的Java类,通过Class对象,我们可以去找到各种成分对应的Java类,从而在运行状态中就可以找到目标成分。

类型描述
Class类代表一个类
Field类代表类的成员变量(类的属性)
Method类代表类的方法
Constructor类代表类的构造方法
Array类提供了动态创建数组,以及访问数组的元素的静态方法

反射机制允许 Java 程序在运行时调用Reflection API取得任何类的内部信息(比如成员变量、构造器、成员方法等),并能操作类的实例对象的属性以及方法。

在Java 程序中,JVM 加载完一个类后,在堆内存中就会产生该类的一个 Class 对象,一个类在堆内存中最多只会有一个 Class 对象,这个Class 对象包含了该类的完整结构信息,我们通过这个 Class 对象便可以得到该类的完整结构信息。

二、泛型Class类

首先我们了解下Class类:

public final class Class<T> extends Object implements Serializable, GenericDeclaration, Type, AnnotatedElement

Class类没有公共构造方法,Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass 方法自动构造的。也就是这不需要我们自己去处理创建,JVM已经帮我们创建好了。

现在,Class类是泛型的。例如,String.class实际上是一个Class< String >类的对象。(事实上,是唯一的对象)
类型参数十分有用,这是因为它允许Class < T > 方法的返回类型更加具有特定性。Class < T > 的以下方法就使用了类型参数:

T newInstance()  //返回无参数构造器构造的 一个新实例
T cast ( Object obj) //如果obj为null或有可能转换成类型T,则返回obj ;否则抛出一个BadCastException 异常
T[] getEnumConstants() //如果T是枚举类型,则返回所有值组成的数组,否则返回null 。
Class<? super T> getSuperclass()//返回这个类的超类。如果T不是一个类或Object 类,则返回null。
Constructors getConstructor ( C1ass... parameterTypes)
Constructors getDeclaredConstructor ( Class... parameterTypes)
//获得公共构造器,或者有给定参数类型的构造器。

获取类的Class对象:

  • 使用类的.class属性:Class<?> clazz = MyClass.class;
  • 使用Class.forName()方法:Class<?> clazz = Class.forName(“com.example.MyClass”); com.example.MyClass为类实际的路径。
  • 使用对象的.getClass()方法:Class<?> clazz = myObject.getClass();
package fanshe;
/**
 * 获取Class对象的三种方式
 * 1 Object ——> getClass();
 * 2 任何数据类型(包括基本数据类型)都有一个“静态”的class属性
 * 3 通过Class类的静态方法:forName(String  className)(常用)
 *
 */
public class Fanshe {
        public static void main(String[] args) {
                //第一种方式获取Class对象  
                Student stu1 = new Student();//这一new 产生一个Student对象,一个Class对象。
                Class stuClass = stu1.getClass();//获取Class对象
                System.out.println(stuClass.getName());
                
                //第二种方式获取Class对象
                Class stuClass2 = Student.class;
                System.out.println(stuClass == stuClass2);//判断第一种方式获取的Class对象和第二种方式获取的是否是同一个
                
                //第三种方式获取Class对象
                try {
                        Class stuClass3 = Class.forName("fanshe.Student");//注意此字符串必须是真实路径,就是带包名的类路径,包名.类名
                        System.out.println(stuClass3 == stuClass2);//判断三种方式是否获取的是同一个Class对象
                } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                }
                
        }
}

所以Java中共有四种方法实例化对象:

  • new
  • clone
  • 序列化
  • 反射
//利用反射创建对象
public class People {
    private String name;
    private String age;
    public void say(){
        System.out.println("my name is "+name+"\t"+age+" years old");
    }
}

try {
            //方式1
            People people = (People) Class.forName("com.eryi.People").newInstance();
            //方式2
//           Constructor<People> constructor=People.class.getConstructor();
//            People people= constructor.newInstance();
            people.say();
        } catch (Exception e) {
            e.printStackTrace();
        }

获取类的信息:(获取类信息之前需要先获取到class对象)

  • 获取类的名称:String className = clazz.getName();
  • 获取类的修饰符:int modifiers = clazz.getModifiers();
  • 获取类的父类:Class<?> superClass = clazz.getSuperclass();
  • 获取类的接口:Class<?>[] interfaces = clazz.getInterfaces();
  • 获取类的共有构造函数:Constructor<?>[] constructors = clazz.getConstructors();
  • 获取类的共有方法:Method[] methods = clazz.getMethods();
  • 获取类的共有字段:Field[] fields = clazz.getFields();
  • 获取类的所有构造函数:Constructor<?>[] constructors =clazz.getDeclaredConstructors();
  • 获取类的所有方法:Method[] methods = clazz.getDeclaredMethods();
  • 获取类的所有字段:Field[] fields = clazz.getDeclaredFields();
//获取类对象
Class<?> classType=Class.forName("reflection.emplee");
                
//调带参构造实例化
Constructor<?> constructor2=classType.getConstructor(new Class[]{String.class,int.class});
emplee e3=(emplee)constructor2.newInstance(new Object[]{"张三",30});
                
//获得指定方法
Method method1=classType.getDeclaredMethod("toString",new Class[]{});
String result1=(String)method1.invoke(e3, new Object[]{});
System.out.println(result1);
                
//获得全部方法(包括私有)
Method[] methods=classType.getDeclaredMethods();
for (Method m : methods) {
     System.out.println("方法名:"+m.getName()+"\t"+"返回值类型:"+m.getReturnType());
}
                
//调用私有方法
Method method2=classType.getDeclaredMethod("getName",new Class[]{});

//关闭安全检查
method2.setAccessible(true);
String result2=(String)method2.invoke(e3, new Object[]{});
System.out.println(result2);

三、虚拟机中泛型类型信息

Java泛型的突出特性之一是在虚拟机中擦除泛型类型。令奇怪的是,擦除的类仍然保留原先泛型的微弱记忆。例如,原始的Pair类知道它源于泛型类Pair < T >,尽管 一个Pair类型的对象无 法区分它是构造为Pair < String > 还是Pair < Employee > 。

public static <T extends Comparable<? super T>> T min(T[] t){}这是一个泛型方法,在虚拟机运行时会泛型擦除为public static Comparable min(Comparable t){},可以使用反射来确定:

  • 这个泛型方法有一个T的类型参数
  • 这个类型参数有一个子类限定,其自身又是一个泛型类型
  • 这个限定类型有一个通配符参数
  • 这个通配符参数参数有一个超类型限定
  • 这个泛型方法有一个泛型数组参数

为获取上述类型,javaSE在5.0中提供一个新接口Type来确定上述类型,这个接口包括以下子类型:

  • Class类,描述具体类型
  • TypeVariable接口,描述类型变量(如T extends Comparable<? super T>
  • WildcardType接口,描述通配符(如? super T)
  • ParameterizedType接口,描述泛型类或者接口类型(如Comparable<? super T>)
  • GenericArrayType接口,描述数组类型(如T[])

下图给出了继承层次。注意,最后4个子类型是接口,虚拟机将实例化实现这些接口的适当的类
在这里插入图片描述

/**
获取泛型超类或者接口相关信息
先定义一个超类和一个实现类:
public class Point<T> {}
public class PointImpl extends Point<Integer>{}
使用Type相关Api获取Point泛型信息:
*/
private static void getGenericClassType() {
   //获取父类的泛型类型,如果这个类型为Object或者不是一个类类型(class Type)则返回为null;
   Type type = PointImpl.class.getGenericSuperclass(); //--->Point

   if (type instanceof ParameterizedType) {
       //获取父类类型的泛型参数列表--->Integer
       Type[] pType = ((ParameterizedType) type).getActualTypeArguments();
       for (Type t : pType) {
           Class clazz = (Class) t;//因为t为Class类型,可以直接类型转换
           System.out.println(clazz.getName());
       }
       //获取父类的原始类型即 Point.class
       Type rType = ((ParameterizedType) type).getRawType();
       //因为知道为Class类型,可以直接类型转换
       Class supperClass = (Class) rType;
       System.out.println(supperClass.getName());
   }
}

/**
获取泛型接口的信息
定义一个接口,以及接口的实现类型
public interface PointInterface<T,U> {}
public class PointImpl2 extends Point<Integer> implements PointInterface<String,Double> {}
*/
 private static void getGenericInterfaceType() {
 //获取被声明这个类型的接口的泛型类型,若没实现接口,返回一个长度为0的数组-->[PointInterface<String,Double)]
  Type[] types = PointImpl2.class.getGenericInterfaces();
  for (Type type : types) {
      if (type instanceof ParameterizedType) {
          Type[] pType = ((ParameterizedType) type).getActualTypeArguments();
          for (Type t : pType) {
              Class pClazz = (Class) t;
              System.out.println("接口的填充类型:" + pClazz.getName());
          }
          //表示声明此类型的类或接口。  --->PointInterface.class
          Type iType = ((ParameterizedType) type).getRawType();
          Class iClazz = (Class) iType;
          System.out.println("声明此接口的类型为:" + iClazz.getName());
      }
  }
}

/**
获取类型变量信息(TypeVariable)
定义一个实现类:

public class PointImpl3<T extends Number & Serializable> implements PointInterface<T,Integer> {}
获取TypeVarable周边信息
*/
 //TypeVariable 类型变量 : Comparable<? Supper T>
private static void getGenericVariableType() {
   //获取此类的接口列表--->PointInterface<T,Integer>
   Type[] types = PointImpl3.class.getGenericInterfaces();
   //Class<?>[] interfaces = PointImpl3.class.getInterfaces();

   for (Type type : types) {
       if (type instanceof ParameterizedType) {
           //获取接口列表上的泛型参数列表--T,integer
           Type[] pType = ((ParameterizedType) type).getActualTypeArguments();
           for (Type t : pType) {
               if (t instanceof TypeVariable) { //T--->TypeVariable 类型变量
                   Type[] bounds = ((TypeVariable) t).getBounds();//获取上边界数组--Number,Serializable
                   for (Type bound : bounds) {
                       Class clazz = (Class) bound;
                       System.out.println("bounds为:" + clazz.getName());
                   }
               }
               if (t instanceof Class) { //Integer
                   System.out.println("此接口的类型为:" + ((Class) t).getName());
               }
           }
       }
   }
}

/**
获取通配符(WildcardType)周边信息
重新定义一个接口和一个实现类:

public interface PointInterface2<T> {}
public class PointWildcardImpl implements PointInterface2<Comparable<? extends Number>> {}
获取这个通配符的相关信息:
*/
public static void getWildcardType() {
    //此时的type对应PointArrayInterface<Comparable<? extends Number>>
    Type[] types = PointWildcardImpl.class.getGenericInterfaces();

    for (Type type : types) {
       if (type instanceof ParameterizedType) {
            //得到填充PointSingleInterface的具体参数,即:Comparable<? extends Number>,仍然是一个ParameterizedType
            Type[] pT = ((ParameterizedType) type).getActualTypeArguments();

            for (Type t : pT) {
                if (t instanceof ParameterizedType) {
                    //对Comparable<? extends Number>再取填充参数,得到的type对应<? extends Number>,WildcardType
                    Type[] ppT = ((ParameterizedType) t).getActualTypeArguments();
                    for (Type tt : ppT) {
                        if (tt instanceof WildcardType) {
                            //  ((WildcardType) tt).getLowerBounds();//下边界
                            Type[] bT = ((WildcardType) tt).getUpperBounds(); //获取上边界
                            for (Type b : bT) {
                                Class<?> bClazz = (Class) b;
                                System.out.println("bounds为:" + bClazz.getName());

                            }
                        }

                    }
                }
            }
        }
    }
}

/**
获取泛型数组(GenericArrayType)相关信息
定义一个实现带有泛型数组的实现类:
public class PointArrayImpl implements PointInterface2<Integer[]> {}
获取这个泛型数组类型的周边信息:
*/
//GenericArrayType泛型数组: T[]
public static void getGenericArrayType() {
    Type[] types = PointArrayImpl.class.getGenericInterfaces();

    for (Type type : types) {
        if (type instanceof ParameterizedType) {

            Type[] pType = ((ParameterizedType) type).getActualTypeArguments();

            for (Type t : pType) {

                if (t instanceof GenericArrayType) {
                    //获取泛型数组的泛型类型---Integer
                    Type aType = ((GenericArrayType) t).getGenericComponentType();

                    System.out.println("数组类型为:" + aType.getClass().getName());
                }
            }
        }
    }
}

四·、类加载

反射机制是 Java实现动态语言的关键,也就是通过反射实现类的动态加载。

  • 静态加载:编译时就加载相关的类,如果程序中不存在该类则编译报错,依赖性太强。
  • 动态加载:运行时加载相关的类,即使程序中不存在该类,但如果运行时未使用到该类,也不会编译错误,依赖性较弱。

类加载的时机:

静态加载

  • 当新创建一个对象时(new),该类会被加载;
  • 当调用类中的静态成员时,该类会被加载;
  • 当子类被加载时,其超类也会被加载;

动态加载

  • 通过反射的方式,在程序运行时使用到哪个类,该类才会被加载;

五、动态代理

我们先来比较 Java 的类class和接口interface的区别:

1.可以实例化类class(非abstract);
2.不能实例化接口interface。

所有接口interface类型的变量总是通过某个实现了接口的类的对象向上转型再赋值给接口类型的变量

CharSequence cs = new StringBuilder();

有没有可能不编写实现类,直接在运行期创建某个interface的实例呢?

这是可能的,因为 Java 标准库提供了一种动态代理(Dynamic Proxy)的机制:可以在运行期动态创建某个interface的实例。

什么叫运行期动态创建?听起来好像很复杂。所谓动态代理,是和静态相对应的。

我们来看静态代理代码怎么写:

//1.定义接口
public interface Hello {
    void morning(String name);
}
//2.编写实现类
public class HelloWorld implements Hello {
    public void morning(String name) {
        System.out.println("Good morning, " + name);
    }
}
//3.创建实例,转型为接口并调用
Hello hello = new HelloWorld();
hello.morning("Bob");

还有一种方式是动态代码,我们仍然先定义了接口Hello,但是我们并不去编写实现类,而是直接通过 JDK 提供的一个Proxy.newProxyInstance()方法创建了一个Hello接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代理。JDK 提供的动态创建接口对象的方式,就叫动态代理。

public class Main {
    public static void main(String[] args) {
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println(method);
                if (method.getName().equals("morning")) {
                    System.out.println("Good morning, " + args[0]);
                }
                return null;
            }
        };
        Hello hello = (Hello) Proxy.newProxyInstance(
            Hello.class.getClassLoader(), // 传入ClassLoader
            new Class[] { Hello.class }, // 传入要实现的接口
            handler); // 传入处理调用方法的InvocationHandler
        hello.morning("Bob");
    }
}
 
interface Hello {
    void morning(String name);
}

在运行期动态创建一个interface实例的方法如下:

1.定义一个InvocationHandler实例,它负责实现接口的方法调用;
2.通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:
>使用的ClassLoader,通常就是接口类的ClassLoader;
>需要实现的接口数组,至少需要传入一个接口进去;
>用来处理接口方法调用的InvocationHandler实例。
3.将返回的Object强制转型为接口。

动态代理实际上是JVM在运行期动态创建class字节码并加载的过程,它并没有什么黑魔法,把上面的动态代理改写为静态实现类大概长这样

public class HelloDynamicProxy implements Hello {
    InvocationHandler handler;
    public HelloDynamicProxy(InvocationHandler handler) {
        this.handler = handler;
    }
    public void morning(String name) {
        handler.invoke(
           this,
           Hello.class.getMethod("morning", String.class),
           new Object[] { name });
    }
}

其实就是 JVM 帮我们自动编写了一个上述类(不需要源码,可以直接生成字节码),并不存在可以直接实例化接口的黑魔法。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全栈游戏开发

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值