【Java基础】反射与泛型

Reflect

能够分析类能力的程序称之为反射(reflective)。

反射库(reflection library)提供了一个丰富且精巧的工具集,可以用来编写能够动态操控Java代码的程序。

主要作用有:

  • 在运行时分析类的能力。
  • 在运行时检查对象。
  • 实现泛型数组操作。
  • Method对象。

获取类对象的三种方式

java 复制代码
实例.getClass();

Class.forName("java.lang.Object");

类.class;

利用反射分析类的能力

Class

所有类运行时的抽象。

java 复制代码
Field[] getFields():返回这个类与超类的公共字段;
Field[] getDeclaredFields():返回这个类的所有字段,否则返回一个长度为0的数组;

Method[] getMethods():返回一个包含Method对象的数组,该数组反映由这个class对象表示的类或接口的所有公共方法,包括由类或接口声明的方法以及从超类和超接口继承的方法;
Method[] getDeclaredMethods():返回一个包含Method对象的数组,该数组反映由这个class对象表示的类或接口的所有声明方法,包括公共、受保护、默认(包)访问和私有方法,但不包括继承的方法;

Constructor<?>[] getConstructors():返回一个包含构造函数对象的数组,该构造函数对象反映由此class对象表示的类的所有公共构造函数;
Constructor<?>[] getDeclaredConstructors():全部构造器函数;

String getPackageName():得到包含这个类型的所有包名;如果是一个基本类型,则返回 "java.lang";
...

Field

类的字段。

java 复制代码
Object get(Object obj):返回obj这个类此字段的值;

void set(Object obj, Object newValue):修改此对象该字段的值;

Method

类的方法。

java 复制代码
Object invoke(Object obj, Object... args);

Constructor

类的构造器。

java 复制代码
Object newInstance(Object... params);

Modifier

分析修饰符。

java 复制代码
public static boolean isPublic(int mod);
public static boolean isPrivate(int mod);
public static boolean isAbstract(int mod);
public static boolean isNative(int mod);
public static boolean isVolatile(int mod);
public static boolean isSynchronized(int mod);
public static boolean isStrict(int mod);
...

此外反射包下还有ArrayAccessibleObject等类,也有很强的能力。

利用反射获取泛型

java.lang.Class<T>

java 复制代码
TypeVariable<Class<T>>[] getTypeParameters():如果这个类型声明为泛型类型,则获得泛型类型变量,否则获得一个长度为为0的数组;

Type getGenericSuperclass():获得这个类型所声明超类的泛型类型;如果这个类型是Obejct或不是类类型(class type),则返回null;

Type[] getGenericInterfaces():获得这个类型所声明接口的泛型类型(按照声明的顺序),否则,如果这个类型没有实现接口,返回长度为0的数组;

java.lang.reflect.Method

java 复制代码
TypeVariable<Method>[] getTypeParameters():如果这个方法被声明为一个泛型方法,则获得泛型类型变量,否则返回长度为0的数组;

Type getGenericReturnType():获得这个方法声明的泛型返回类型;

Type[] getGenericParameterTypes():获得这个方法声明的泛型参数类型,如果这个方法没有参数,则返回一个长度为0的数组;

java.lang.reflect.TypeVariable

java 复制代码
String getName():获得这个类型变量的名字;

Type[] getBounds():获得这个类型变量的子类限定,否则,如果该类没有子类限定,返回长度为0的数组;

java.lang.reflect.wildcardType

java 复制代码
Type[] getUpperBounds():获得这个类型变量的子类限定,否则,如果没有子类限定,返回长度为0的数组;

Type[] getLowerBounds():获得这个类型变量的超类限定,否则,如果没有超类限定,返回长度为0的数组;

java.lang.reflect.ParameterizedType

java 复制代码
Type getRawType():获得这个参数化类型的原始类型;

Type[] getActualTypeArguments():获得这个参数化类型声明的类型参数;

Type getOwnerType():如果是内部类型,则返回外部类类型;如果是一个顶级类型,则返回null;

java.lang.reflect.GenericArrayType

java 复制代码
Type getGenericComponentType():获得这个数组类型所声明的泛型元素类型;

Generic

Java的泛型的本质是参数化类型(Parameterized Type)或者参数化多态(Parametric Polymorphism)的应用,即一种既可以将操作数据类型指定为方法签名中的一种特殊参数,这种参数类型能够用在类、接口和方法的创建中,分别构成泛型类、泛型接口、泛型方法,以增强语言类型系统以及抽象能力。也是一种语法糖

这种泛型实现方式称之为"类型擦除式泛型(Type Erasure Generics)",与C#的"具现化式泛型(Reified Generics)"相对。

类型擦除

编译期间Java编译器会将类型参数替换为其上界(类型参数中extends子句的类型),如果上界没有定义,则默认为Object,这就叫做类型擦除。

裸类型

裸类型被视为所有类型泛型化的共同父类型。

泛型擦除前:

java 复制代码
Map<String, String> map = new HashMap<>();
map.put("a", "b");
System.out.println(map.get(a));

泛型擦除后:

java 复制代码
Map map = new HashMap();
map.put("a", "b");
System.out.println(map.get(a));

类型擦除的利弊

弊:

  • 字节码Code属性中擦除了泛型信息,在元数据中保留,只能通过反射获取,运行效率低下。
  • 不支持基本类型的泛型,会发生频繁的拆箱与装箱。

利:

  • 实现简单,兼容以前的代码。

桥方法

Java中的桥接方法(Bridge Method)是一种为了实现某些Java语言特性而由编译器自动生成的方法。

我们可以通过Method类的isBridge方法来判断一个方法是否是桥接方法。

在字节码文件中,桥接方法会被标记为ACC_BRIDGEACC_SYNTHETIC:

  • ACC_BRIDGE用于表示该方法是由编译器产生的桥接方法;

  • ACC_SYNTHETIC用于表示该方法是由编译器自动生成。

什么时候生成桥方法?

为了实现哪些Java语言特性会生成桥接方法?最常见的两种情况就是协变返回值类型类型擦除,因为它们导致了父类方法的参数和实际调用的方法参数类型不一致。下面我们通过两个例子更好地理解一下。

注意:静态方法与升级修饰符不会生成桥接方法。

协变返回类型

协变返回类型是指子类方法的返回值类型不必严格等同于父类中被重写的方法的返回值类型,而可以是更 "具体" 的类型。

在Java 1.5添加了对协变返回类型的支持,即子类重写父类方法时,返回的类型可以是子类方法返回类型的子类。下面看一个例子:

java 复制代码
public class Parent {
    Number get() {
        return 1;
    }
}
java 复制代码
public class Child extends Parent {

    @Override
    Integer get() {
        return 1;
    }
}

Child类重写其父类Parent的get方法,Parent的get方法返回类型为Number,而Child类中get方法返回类型为Integer。

将这段代码进行编译,再反编译:

java 复制代码
javac Child.java
javap -v -c Child.class

结果如下:

java 复制代码
public class Child extends Parent
......省略部分结果......
  java.lang.Integer get();
    descriptor: ()Ljava/lang/Integer;
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: iconst_1
         1: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         4: areturn
      LineNumberTable:
        line 5: 0

  java.lang.Number get();
    descriptor: ()Ljava/lang/Number;
    flags: ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokevirtual #3                  // Method get:()Ljava/lang/Integer;
         4: areturn
      LineNumberTable:
        line 1: 0

从上面的结果可以看到,有一个方法java.lang.Number get() , 在源码中是没有出现过的,是由编译器自动生成的,该方法被标记为ACC_BRIDGEACC_SYNTHETIC,就是我们前面所说的桥接方法。

这个方法就起了一个桥接的作用,它所做的就是把对自身的调用通过invokevirtual指令再调用方法java.lang.Integer get()

**编译器这么做的原因是什么呢?**因为在JVM方法中,返回类型也是方法签名的一部分,而桥接方法的签名和其父类的方法签名一致,以此就实现了协变返回值类型。

类型擦除

泛型是Java 1.5才引进的概念,在这之前是没有泛型的概念的,但泛型代码能够很好地和之前版本的代码很好地兼容,这是为什么呢?

这是因为,在编译期间Java编译器会将类型参数替换为其上界(类型参数中extends子句的类型),如果上界没有定义,则默认为Object,这就叫做类型擦除。

当一个子类在继承(或实现)一个父类(或接口)的泛型方法时,在子类中明确指定了泛型类型,那么在编译时编译器会自动生成桥接方法,例如:

java 复制代码
public class Parent<T> {    
    void set(T t) {
    } 
}
java 复制代码
public class Child extends Parent<String> {

    @Override
    void set(String str) {
    }
}

Child类在继承其父类Parent的泛型方法时,明确指定了泛型类型为String,将这段代码进行编译,再反编译:

java 复制代码
public class Child extends Parent<java.lang.String>
......省略部分结果......
  void set(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags:
    Code:
      stack=0, locals=2, args_size=2
         0: return
      LineNumberTable:
        line 5: 0

  void set(java.lang.Object);
    descriptor: (Ljava/lang/Object;)V
    flags: ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: checkcast     #2                  // class java/lang/String
         5: invokevirtual #3                  // Method set:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 1: 0

从上面的结果可以看到,有一个方法void set(java.lang.Object) , 在源码中是没有出现过的,是由编译器自动生成的,该方法被标记为ACC_BRIDGEACC_SYNTHETIC,就是我们前面所说的桥接方法。

这个方法就起了一个桥接的作用,它所做的就是把对自身的调用通过invokevirtual指令再调用方法void set(java.lang.String)

**编译器这么做的原因是什么呢?**因为Parent类在类型擦除之后,变成这样:

java 复制代码
public class Parent<Object> {

    void set(Object t) {
    }
}

编译器为了让子类有一个与父类的方法签名一致的方法,就在子类自动生成一个与父类的方法签名一致的桥接方法。

相关推荐
七星静香24 分钟前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
Jacob程序员25 分钟前
java导出word文件(手绘)
java·开发语言·word
ZHOUPUYU25 分钟前
IntelliJ IDEA超详细下载安装教程(附安装包)
java·ide·intellij-idea
stewie628 分钟前
在IDEA中使用Git
java·git
Elaine20239144 分钟前
06 网络编程基础
java·网络
G丶AEOM1 小时前
分布式——BASE理论
java·分布式·八股
落落鱼20131 小时前
tp接口 入口文件 500 错误原因
java·开发语言
想要打 Acm 的小周同学呀1 小时前
LRU缓存算法
java·算法·缓存
镰刀出海1 小时前
Recyclerview缓存原理
java·开发语言·缓存·recyclerview·android面试
阿伟*rui3 小时前
配置管理,雪崩问题分析,sentinel的使用
java·spring boot·sentinel