Java 泛型详解

介绍

JDK 5.0 引入的语言特性

通常在容器或容器的子接口中使用

编译器在编译时可以检查程序的类型正确性

为什么需要泛型

  • 在编译时进行更强的类型检查

    Java 编译器对泛型代码应用强类型检查,并在代码违反类型安全时发出错误。 修复编译时错误比修复运行时错误更容易,后者很难发现。

  • 消除类型转换

  • 实现通用算法

    通过使用泛型,程序员可以实现适用于不同类型集合的泛型算法,并且类型安全且易于阅读。

泛型与泛型子类型

泛型类型声明并不会因为泛型的不同而存在多个副本,泛型类型声明只会编译一次,并编译成单个 class 文件,就像普通的类或接口声明一样。

一般来说,如果 Foo 是 Bar 的子类型(子类或子接口),而 G 是某种泛型类型声明,则 G<Foo> 不是 G<Bar> 的子类型。

证明(反证法):

// 假设 Driver(司机) extends Person(普通人)
Collection<Driver> drivers = new ArrayList<>();
Collection<Person> persons = new ArrayList<>();
persons = drivers; // 编译时会报错,假设编译可以通过
persons.add(new Person()); 
// persons 容器添加一个 Person 记录或者 Person 子类记录, persons = drivers; 引用的是同一个副本, 因此插入一个非 Driver 元素会破坏 drivers 的结构,读取记录的时候会报 ClassCastException。因此编译器会在编译时禁止 persons = drivers 这种赋值。  

通配符

通配符引入

Java5.0 之前打印一个集合的所有元素

void printCollection(Collection c) {
    Iterator i = c.iterator();
    for (k = 0; k < c.size(); k++) {
        System.out.println(i.next());
    }
}

Java5.0 之后使用泛型和新的泛型语法打印一个集合的所有元素

void printCollection(Collection<Object> c) {
    for (Object e : c) {
        System.out.println(e);
    }
}

Java5.0 之前的版本可以使用任何类型的集合作为参数调用,Java5.0之后接受 Collection<Object>,因为 Collection<Xxx extends Object> 不是 Collection<Object> 的子类型。反而接收的范围还更小了。

那么各种集合的超类型是什么呢? 它被写成 Collection<?>,Collection<?> 代表匹配任何类型的集合。它被称为通配符类型。

void printCollection(Collection<?> c) {
    for (Object e : c) {
        System.out.println(e);
    }
}
通配符集合的读取与写入
通配符集合的读取

给定一个 List<?>,调用 get() 方法,返回结果类型是未知类型,但这个结果始终是一个 Object。 因此,将 get() 的结果赋值给 Object 类型的变量是类型安全的。

通配符集合的写入
Collection<?> c = new ArrayList<String>();
c.add(new Object()); // 编译错误

因为我们不知道集合 c 的元素类型代表什么,所以我们不能向它添加对象。

Collection#add() 方法接受类型为 E 的参数,即集合的元素类型。 当实际类型参数为 ? 时,代表某种未知类型。 传递给 add 的任何参数都必须是这种未知类型的子类型。 因为我们不知道那是什么类型,所以我们不能传入任何东西。唯一例外是 null,它是所有类型的成员。

Collection<Driver> drivers = new ArrayList<>();
Collection<?> c = new ArrayList<>();
c = drivers; // 编译通过
c.add(new Driver()); // 编译报错 // c 并不知道 ? 允许添加的类型是什么.
c.add(new Object()); // 编译报错 // c = drivers; 引用的是同一个副本, 因此插入一个非 Driver元素会破坏 drivers 的结构,读取 drivers 记录的时候会报 ClassCastException。
// 因此 Collection<?> 不允许添加任何类型,除了null。

有界通配符

  • 上限通配符: 语法 ? extends T

    要求一个可以绘制矩形和圆形等形状的简单绘图应用程序。 要在程序中表示这些形状,可以定义这样一个类层次结构:

    public abstract class Shape {
        public abstract void draw(Canvas c);
    }
    
    public class Circle extends Shape {
        private int x, y, radius;
        public void draw(Canvas c) {
            ...
        }
    }
    
    public class Rectangle extends Shape {
        private int x, y, width, height;
        public void draw(Canvas c) {
            ...
        }
    }
    
    public class Canvas {
        public void draw(Shape s) {
            s.draw(this);
       }
    }
    

    一个复合图形一般都会包含许多形状。 假设将这些形状表示为一个列表,那么需要在 Canvas 中定义一个方法将它们全部绘制出来。

    // 不使用泛型通配符,List<Circle>、List<Rectangle> 是不允许传入的
    public void drawAll(List<Shape> shapes) {
        for (Shape s: shapes) {
            s.draw(this);
       }
    }
    

    要允许传入 Shape 或 Shape 子类型的集合,需要使用泛型通配符。? extends Shape,Shape表示通配符的上限。

    public void drawAll(List<? extends Shape> shapes) {
        ...
    }
    

    ? extends E 上限泛型通配符不允许插入元素

    public void addRectangle(List<? extends Shape> shapes) {
        // 编译错误
        shapes.add(0, new Rectangle());
    }
    
  • 下限的通配符。 语法 ? super T

    ? super T 表示一个未知类型,它是 T 的超类型(或 T 本身;记住超类型关系是自反的)。

    • Collection<? super T> 可以写入 T 和 T 的子类型
    • Collection<? super T> 读取到的元素类型都是 Object

通常,如果 API 仅使用类型参数 T 作为参数,则其使用应利用下限通配符 (? super T)

如果 API 只返回 T,可以通过使用上限通配符 (? extends T) 为客户端提供更大的灵活性

PECS

 /**
  * Upper Bound Wildcard <? extends T> as Producer({@link Supplier#get()}): 只读; 写入元素编译错误(null 除外)
  * <p>
  * Lower Bound WildCard <? super T> as Consumer({@link Consumer#accept(T t)}): 可以写入 T 和 T 的子类型; 读取到的元素类型都是 Object
  */
 public <T> void pecs(List<? extends T> producer, List<? super T> consumer) {
     T t = producer.get(0);
     consumer.add(t);
 }

泛型方法

类型推断:根据调用传入的类型推断泛型类型

原始类型与泛型类型

Collection 是不是 Collection<Object> ? ==> 不是

Collection<String> ls = new ArrayList<>(); // 1

Collection lo = ls;  // 2

如果 Collection 是 Collection<Object>,那么第 2 行代码会编译错误,当然第 2 行代码是不会编译出错的。那么为什么编译器不禁止这种赋值行为呢?因为旧代码中会存在使用原始类型 Collection 的情况,在新旧代码混用的情况下不可避免的会将一个泛型类型赋值给原始类型,为了兼容这种情况,编译器不会报错。

Collection 是不是 Collection<?> ? ==> 不是

Collection c = new ArrayList<String>(); // 1

c.add("hello"); // 2

如果 Collection 是 Collection<?>,那么第 2 行代码会编译错误,当然第 2 行代码是不会编译出错的。编译器不禁止这种赋值行为同样的是为了兼容原始类型的旧代码。

所以原始类型用起来非常像通配符类型,但它们没有那么严格的类型检查。 这是一个深思熟虑的设计决策,以允许泛型与使用原始类型的遗留代码互相兼容。

当然混合遗留代码和泛型代码是类型不安全,请密切注意编译器提示的警告。并尽可能的保证运行时类型安全。

泛型擦除

Generics were introduced to the Java language to provide tighter type checks at compile time and to support generic programming. To implement generics, the Java compiler applies type erasure to:

  • Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods.
  • Insert type casts if necessary to preserve type safety.
  • Generate bridge methods to preserve polymorphism in extended generic types.

Type erasure ensures that no new classes are created for parameterized types; consequently, generics incur no runtime overhead.

泛型擦除: 兼容 jdk<1.5 代码

Casts and InstanceOf

// Unchecked warning
Collection<String> cstr = (Collection<String>) cs;
Collection cs = new ArrayList<String>();
// 编译错误
if (cs instanceof Collection<String>) { ... }

泛型数组

  • 声明具体类型泛型数组编译时报错
  • 声明通配符泛型数组编译通过
// 具体类型泛型数组
// 编译报错,假设编译通过
List<String>[] lsa = new List<String>[10];
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
// Unsound, but passes run time store check
oa[1] = li;

// 运行时强制类型转换错误,但在编译时由于泛型具体类型是确定的,所以编译器不会有任何警告和要求强制类型转换(不允许声明 具体类型泛型数组 的原因)
String s = lsa[1].get(0);
// 通配符泛型数组
// OK, array of unbounded wildcard type.
List<?>[] lsa = new List<?>[10];
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
// Correct.
oa[1] = li;
// 运行时错误,但强制类型转换是明确的(允许声明 通配符泛型数组 的原因)
String s = (String) lsa[1].get(0);

Class 泛型(Class<T>)

如String.class的类型是Class<String>,Serializable.class的类型是Class<Serializable>。 这可用于提高反射代码的类型安全性。

Class c1 = /***/;
Object instance1 = c2.newInstance(); // 不能确定具体类型

Class<String> c2 = /***/;
String instance2 = c2.newInstance(); // 能确定具体类型
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值