Java泛型的基本使用与原理

110 阅读4分钟

泛型:Generics

泛型快速开始

泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法

泛型类
class Notepad<K,V>{       // 此处指定了两个泛型类型  
    private K key ;     // 此变量的类型由外部决定  
    private V value ;   // 此变量的类型由外部决定  
} 
public class GenericsDemo09{  
    public static void main(String args[]){  
        Notepad<String,Integer> t = null ;        // 定义两个泛型类型的对象  
        t = new Notepad<String,Integer>() ;       // 里面的key为String,value为Integer  
    }  
}
// 类后加尖括号,内部放泛型
泛型接口
interface Info<T>{        // 在接口上定义泛型  
    public T getVar() ;  
}  
class InfoImpl<T> implements Info<T>{
     private T var ;  
}

可以看到,上述方式都是在类/接口名后加 <> 表示泛型

泛型方法

泛型方法有点小区别了,必须在返回值前边加<>,来声明这是一个泛型方法

public <T> T function(Class<T> c){}
// Class<T>表示泛型的具体类型 c可以用来创建泛型类的对象(反射 c.newInstance)
// 就是你调用这个泛型方法,首先得指明泛型是什么具体类型(此时才知道了泛型具体是什么)
// 可以用Class.forName("完全类名")来表示泛型

泛型方法实例:

public <T> void printArray(T[] array) {
    for (T element : array) {
        System.out.println(element);
    }
}
// 调用泛型方法
Integer[] intArray = { 1, 2, 3, 4, 5 };
printArray(intArray);
​
String[] stringArray = { "Hello", "World" };
printArray(stringArray);
// 也可以自己显式指定
<String>printArray(stringArray);
子/父泛型

Java允许泛型元素是某个类的子类,或者父类,表达方式如下:

class Info<T extends Number>{
    // 此处表示泛型只能是数字类型 可以通过这种手段进行泛型上下边界的限制
    // extends表示T必须是E本身或者是E的子类
}
<? super E>
    // 此处表示泛型必须是E本身或者是E的父类
    
 // 这种约束可以拼接   
<T extends Staff & Passenger>
给泛型起名:通配符

一般通配符代表的含义:

  • E: Element (在集合中使用,因为集合中存放的是元素)
  • T:Type(Java 类)
  • K: Key(键)
  • V: Value(值)
  • N: Number(数值类型)
  • ?: 表示不确定的java类型
如何实例化泛型
T test = new T(); // ERROR

这样是会报错的,因为即便T泛型是某个对象,它会被泛型擦除为Object,因此JVM不知道该如何在堆上为它分配内存。但我们可以通过反射做到这一点:

static <T> T newTclass (Class < T > clazz){
    T obj = clazz.newInstance();
    return obj;
}
获取泛型类型
        // 泛型类的对象  这里泛型是  java.lang.String
        GenericType<String> genericType = new GenericType<String>();
        // 获取泛型的Superclass
        Type superclass = genericType.getClass().getGenericSuperclass();
        //getActualTypeArguments 返回确切的泛型参数, 
        //如Map<String, Integer>返回[String, Integer]
        Type type = ((ParameterizedType) superclass).getActualTypeArguments()[0]; 
        System.out.println(type);//class java.lang.String

泛型本质

泛型是前端编译的一种语法糖。

编译期检查

Java编译器是通过先检查代码中泛型的类型,然后在进行类型擦除,再进行编译。

List<String> list = new ArrayList<String>(); 
list.add("hello"); 
list.add(123); // 报错

隐式类型转换

没有泛型,需要手动转换成自己期望的类型

List list = new ArrayList(); 
list.add("hello"); 
String s = (String) list.get(0); 

有泛型,自动转换

List<String> list = new ArrayList<String>(); 
list.add("hello"); 
String s = list.get(0); // no cast

泛型实现原理

泛型本质是Java的一种语法糖,在JDK 1.5引入,由前端编译器帮助完成(javac)

  • Java的泛型的实现方式采用了:类型擦除式泛型
  • C#的泛型采用:具现化式泛型

泛型擦除

就是在编译阶段将泛型替换。

  • 将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。
  • 移除所有的类型参数。

在C#中,List<int>List<string>就是两个不同的类型

而在Java中,ArrayList<Integer>ArrayList<String>其实是同一个类型,在编译后的字节码文件中 ,全部泛型都被替换为原来的裸类型。

裸类型/原始类型

裸类型:所有该类型泛型化实例的共同父类型

  • 如果类型参数是无限制通配符或没有上下界限定则替换为Object;
  • 如果存在上下界限定则根据子类替换原则取类型参数的最左边限定类型(即父类)

举例来说:

一般的泛型:

有界的泛型:

泛型与静态

泛型类中的静态方法和静态变量不可以使用泛型类所声明的泛型类型参数

因为泛型类的泛型是在创建对象的时候确定的,因此static不行

// 但是这个是可以的,因为是个泛型方法
public class Test2<T> {    
    public static <T>T show(T one){ //这是正确的    
        return null;    
    }    
}

也就是说:泛型与静态,只有被static修饰的泛型方法才有所关联。

静态变量和泛型没关系,没办法被联系到一起。

参考文献

Java 基础 - 泛型机制详解

Java泛型详解&泛型的优点、方法及相关细节