Java-泛型

Java泛型(generics)是JDK5中引入的一个新特性,泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数类型,也就是说所操作的数据类型被指定为一个参数。为了向下兼容,虚拟机其实是不支持泛型,所以Java实现的是一种伪泛型机制,也就是说Java在编译期擦除了所有的泛型信息,这样Java就不需要产生新的类型到字节码,所有的泛型类型最终都是一种原始类型,在Java运行时根本就不存在泛型信息。

为什么使用泛型?

泛型可以增强编译时错误检测,减少因类型问题引发的运行时异常(只要编译期没有警告,那么运行期就不会出现ClassCastException)
泛型具有更强的类型检查
泛型可以避免类型转换
泛型可以泛型算法,增加代码复用性

Java中的泛型

泛型类
泛型接口
泛型方法

泛型类格式:class name<T1, T2, ..., Tn>
定义格式:private <K,V> boolean compare(Pair<K,V> p1, Pair<K,V> p2)
调用格式:Util.<K, V>compare(p1,p2)

常见类型变量名称

最常见的类型变量名有:

E:元素(在Java集合框架中有广泛的应用)
K:键
N:数字
T:类型
V:值
S,U,V 等:第二,第三,第四个类型

参数化类型

  • 参数化类型:

把类型当参数一样传递
<数据类型>只能是引用类型(泛型的副作用)

  • 举个例子:

Plate<T>中的”T”称为类型参数
Plate<Banana>中的”Banana”称为实际类型参数
Plate<T> 整个称为泛型类型
Plate<Banana>整个称为参数化的类型ParameterizedType

类型参数VS类型实参

“类型参数”与“类型变量”的不同
Foo<T>中的T为类型参数
Foo<String>中的String为类型变量

The Diamond钻石运算符

JDK7以下版本
Box<Integer> integerBox = new Box<Integer>();
JDK7及以上版本
Box<Integer> integerBox1 = new Box<>();// The Diamond(菱形) 类型推断

原始类型

缺少实际类型变量的泛型就是一个原始类型
举例:

class Box<T>{} 
Box b = new Box(); //这个Box就是Box<T>的原始类型

泛型擦除

ArrayList<Int> arr1 = new ArrayList();
ArrayList<String> arr2 = new ArrayList();

//result true
System.out.println(arr1.getClass() == arr2.getClass()); // true

由此可见通过运行时获取的类信息是完全一致的,泛型类型被擦除了。擦除后只留下原始类型,这里也就是ArrayList

  • 代码查看:
  • 示例:

ConditionalPlate

package com;

import java.util.ArrayList;
import java.util.List;


public class ConditionalPlate<T> implements Plate<T> {

    private List<T> items = new ArrayList<T>(10);

    public ConditionalPlate(){

    }

    @Override
    public void set(T t) {
        items.add(t);
    }

    @Override
    public T get(){
        int index = items.size() -1;
        if(index>= 0){
            return items.get(index);
        }else{
            return null;
        }
    }

    @Override
    public String toString() {
        return "Plate{" +
                "items=" + items +
                '}';
    }

//    @Override
//    public boolean equals(T t) {
//        return super.equals(t);
//    }

    @Override
    public boolean equals(Object obj) {
        return super.equals(obj);
    }
}

Plate

package com;


public interface Plate<T> {


    public void set(T t);
    public T get();
}

  • javac编译源文件
  • javap -c 查看生成的字节码
PS E:\project\com> javac .\ConditionalPlate.java .\Plate.java
PS E:\project\com> javap -c .\ConditionalPlate.class
Compiled from "ConditionalPlate.java"
public class com.ConditionalPlate<T> implements com.Plate<T> {
  public com.ConditionalPlate();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: new           #2                  // class java/util/ArrayList
       8: dup
       9: bipush        10
      11: invokespecial #3                  // Method java/util/ArrayList."<init>":(I)V
      14: putfield      #4                  // Field items:Ljava/util/List;
      17: return

  public void set(T);
    Code:
       0: aload_0
       1: getfield      #4                  // Field items:Ljava/util/List;
       4: aload_1
       5: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      10: pop
      11: return

  public T get();
    Code:
       0: aload_0
       1: getfield      #4                  // Field items:Ljava/util/List;
       4: invokeinterface #6,  1            // InterfaceMethod java/util/List.size:()I
       9: iconst_1
      10: isub
      11: istore_1
      12: iload_1
      13: iflt          27
      16: aload_0
      17: getfield      #4                  // Field items:Ljava/util/List;
      20: iload_1
      21: invokeinterface #7,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
      26: areturn
      27: aconst_null
      28: areturn

  public java.lang.String toString();
    Code:
       0: aload_0
       1: getfield      #4                  // Field items:Ljava/util/List;
       4: invokedynamic #8,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/util/List;)Ljava/lang/String;
       9: areturn

  public boolean equals(java.lang.Object);
    Code:
       0: aload_0
       1: aload_1
       2: invokespecial #9                  // Method java/lang/Object.equals:(Ljava/lang/Object;)Z
       5: ireturn
}

1: invokespecial #1 // Method java/lang/Object."<init>":()V
可以看到ConditionalPlate构造函数里显示的类型已经是object

功能:保证了泛型不在运行时出现

  • 类型消除应用的场合:

编译器会把泛型类型中所有的类型参数替换为它们的上(下)限,如果没有对类型参数做出限制,那么就替换为Object类型。因此,编译出的字节码仅仅包含了常规类,接口和方法。
在必要时插入类型转换以保持类型安全。
生成桥方法以在扩展泛型时保持多态性

  • Bridge Methods 桥方法

当编译一个扩展参数化类的类,或一个实现了参数化接口的接口时,编译器有可能因此要创建一个合成方法,名为桥方法。它是类型擦除过程中的一部分

  • 示例代码查看桥方法:

BananaPlate

package com;

import java.util.ArrayList;
import java.util.List;

public class BananaPlate implements Plate<Banana> {

    private List<Banana> items = new ArrayList<>(10);

    @Override
    public void set(Banana banana) {
        items.add(banana);
    }

    @Override
    public Banana get() {
        return items.get(0);
    }
}

javap -c 查看字节码

PS E:\project\com> javap -c .\BananaPlate.class
Compiled from "BananaPlate.java"
public class com.BananaPlate implements com.Plate<com.Banana> {
  public com.BananaPlate();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: new           #2                  // class java/util/ArrayList
       8: dup
       9: bipush        10
      11: invokespecial #3                  // Method java/util/ArrayList."<init>":(I)V
      14: putfield      #4                  // Field items:Ljava/util/List;
      17: return

  public void set(com.Banana);
    Code:
       0: aload_0
       1: getfield      #4                  // Field items:Ljava/util/List;
       4: aload_1
       5: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      10: pop
      11: return

  public com.Banana get();
    Code:
       0: aload_0
       1: getfield      #4                  // Field items:Ljava/util/List;
       4: iconst_0
       5: invokeinterface #6,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
      10: checkcast     #7                  // class com/Banana
      13: areturn

  public java.lang.Object get();
    Code:
       0: aload_0
       1: invokevirtual #8                  // Method get:()Lcom/Banana;
       4: areturn

  public void set(java.lang.Object);
    Code:
       0: aload_0
       1: aload_1
       2: checkcast     #7                  // class com/Banana
       5: invokevirtual #9                  // Method set:(Lcom/Banana;)V
       8: return
}

查看字节码可以看到有两个get函数,一个是自定义返回值的get,一个是object默认的get

 public com.Banana get();
    10: checkcast     #7                  // class com/Banana

通过字节码可以看到这里的返回值类型被强转了

泛型擦除的残留

查看class文件的时候泛型是没有擦除的,还是能看到泛型

这里看到的其实是签名而已,还保留了定义的格式,这样子,对于分析字节码是有好处的

泛型方法的类型擦除

消除方法:同对泛型类的处理
无限制:替换为Object
有限制:替换为第一受限类型

Java编译器具体是如何擦除泛型的?

  1. 检查泛型类型,获取目标类型
  2. 擦除类型变量,并替换为限定类型
    如果泛型类型的类型变量没有限定(<T>),则用Object作为原始类型
    如果有限定(<T extends XClass>),则用XClass作为原始类型
    如果有多个限定(T extends XClass1&XClass2),则使用第一个边界XClass1作为原始类
  3. 在必要时插入类型转换以保持类型安全
  4. 生成桥方法以在扩展时保持多态性

不可具体化类型

可具体化类型和不可具体化类型的定义:
可具体化类型:就是一个可在整个运行时可知其类型信息的类型。
包括:基本类型、非泛型类型、原始类型和调用的非受限通配符。
不可具体化类型:无法整个运行时可知其类型信息的类型,其类型信息已在编译时被擦除:
例如:List<String>List<Number>,JVM无法在运行时分辨这两者

堆污染:
发生时机:当一个参数化类型变量引用了一个对象,而这个对象并非此变量的参数化类型时,堆污染就会发生。
分模块对代码进行分别编译,那就很难检测出潜在的堆污染,应该同时编译

带泛型的可变参数问题:
T...将会被翻译为T[],根据类型擦除,进一步会被处理为Object[],这样就可能造成潜在的堆污染
避免堆污染警告
@SafeVarargs:当你确定操作不会带来堆污染时,使用此注释关闭警告
@SuppressWarnings({"unchecked", "varargs"}):强制关闭警告弹出(不建议这么做)

泛型,继承和子类型

给定两种具体的类型A和B(例如Fruit和Apple),
无论A和B是否相关,
MyClass<A>MyClass<B>都没半毛钱关系,
它们的公共父对象是Object

受限的类型参数

功能:对泛型变量的范围作出限制
格式:
单一限制:<U extends Number>
多种限制:<U extends A & B & C>
extends表达的意义:这里指的是广义上“扩展”,兼有“类继承”和“接口实现”之意
多种限制下的格式语法要求:如果上限类型是一个类,必须第一位标出,否则编译错误

泛型算法实现的关键:利用受限类型参数

通配符

泛型中的问号符“?”名为“通配符”
受上下限控制的通配符
通配符匹配

通配符的适用范围:

参数类型
字段类型
局部变量类型
返回值类型(但返回一个具体类型的值更好)

非受限通配符

两个关键使用场合:

写一个方法,而这方法的实现可以利用Object类中提供的功能时
泛型类中的方法不依赖类型参数时
List.size()方法,它并不关心List中元素的具体类型

List<XXX>List<?>的一个子类型
理解List<Object>List<?>的不同:差在NULL处理,前者不支持,而后者却可接受一个null入表

受上限控制的通配符

语法格式:<? extends XXX>
优点:扩大兼容的范围

List<XXX>要比List<? extends XXX>更加严格,因为前者仅能匹配XXX列表,然而后者却可同时匹配XXX及其子类的列表

关键词:及其

缺点:

不能set任何元素,但是可以set(null)可以
get类型也不是随便一个子类都能接收

Plate<? extends Fruit> fruitPlate = xiaoLiMa.getSnack(applePlate);
//这时候小明再从盘子里面那苹果吃,发现不行了
xiaoMing.eat((Apple) fruitPlate.get());
//实际上
Fruit fruit = fruitPlate.get();
Object object = fruitPlate.get();

但是这种不是严格的限制,反射可破

    public  Plate<? extends Fruit> getSnack(Plate<Apple> applePlate){
        Plate<? extends Fruit> fruitPlate =  applePlate;
        //不能存放任何元素
        try{
            Method set = fruitPlate
                    .getClass()
                    .getMethod("set",Object.class);
            set.invoke(fruitPlate,new Banana());
            //set.invoke(fruitPlate,new Beef());//什么都能放了 安全没法保证
        }catch(Exception e){}

//        fruitPlate.set(new Apple());
//        fruitPlate.set(new Banana());
        //放null还是可以
        fruitPlate.set(null);
        return fruitPlate;
    }

<? extends T>上界通配符 相当于”只读“,但是通过反射可以写数据进去
反射破坏了泛型的特性了,当拿出来的时候不能转成T就会报错了

有下限通配符

功能:限定了类型的下限,也就它必须为某类型的父类
格式:<? super A>

List<XXX>List<? super XXX>要更加严格。因为前者仅仅兼容XXX类型的列表,而后者却兼容XXX及其任何XXX超类的列表

关键词:及其

缺点:

只能set数据,不能get数据

    public static void scene03() {
        Plate<? super Fruit> lowerfruitPlate = new AIPlate<Food>();
        lowerfruitPlate.set(new Apple());
        lowerfruitPlate.set(new Banana());
//        lowerfruitPlate.set(new Food());

//        Fruit newFruit1 = lowerfruitPlate.get();
//        Apple newFruit3 = lowerfruitPlate.get();
        Object newFruit2 = lowerfruitPlate.get();
    }

可以把Plate<Fruit>以及它的基类Plate<Food>转成Plate<? super Fruit>它可以存数据但是取出来后 泛型信息丢失了,只能用Object存放

<?>

不能存也不能取

    public static void scene05() {
        //<?> == <? extends Object>
        Plate<?> fruitPlate = new AIPlate<Apple>();
//        Fruit fruit = fruitPlate.get();
//        fruitPlate.set(new Apple());

        fruitPlate.toString();
        Object object = fruitPlate.get();
        fruitPlate.set(null);

    }

Plate<?>其实就是Plate<? extends Object>

Java泛型PECS原则

如果你只需要从集合中获得类型T , 使用<? extends T>通配符
如果你只需要将类型T放到集合中, 使用<? super T>通配符
如果你既要获取又要放置元素,则不使用任何通配符。例如List<Apple>
PECS即 Producer extends Consumer super, 为了便于记忆。

  • 为何要PECS原则?

提升了API的灵活性

示例:

    public static void scen07() {
        List<Apple> src = new ArrayList<>();
        src.add(new Apple(1));
        List<Apple> dest = new ArrayList<>(10);
        dest.add(new Apple(2));
        System.out.println(dest);
        copy(dest,src);
        System.out.println(dest);


        List<Banana> src1 = new ArrayList<>();
        src1.add(new Banana(1));
        List<Banana> dest1 = new ArrayList<>(10);
        dest1.add(new Banana(2));
        copy1(dest1,src1);

        List<Fruit> dest2 = new ArrayList<>(10);
        dest2.add(new Banana());
        
        //        List<Apple> src = new ArrayList<>();
        //        List<Food> dest2 = new ArrayList<>(10);
        //        Test1.<Food>copy2(dest2,src);
        
        Test1.<Fruit>copy3(dest2,src1);
    }

    public static void copy(List<Apple> dest, List<Apple> src) {
         Collections.copy(dest,src);
    }

    public static <T> void copy1(List<T> dest, List<T> src) {
        Collections.copy(dest,src);
    }

    public static <T> void copy2(List<? super T> dest, List<T> src) {
        Collections.copy(dest,src);
    }

    public static <T> void copy3(List<? super T> dest, List<? extends T> src) {
        Collections.copy(dest,src);
    }

通配符捕获

    void foo(List<?> i) {
        fooHelper(i);
    }

    /**
     * 在此示例中,代码正在尝试执行安全操作,那么如何解决编译器错误?
     * 你可以通过编写捕获通配符的私有帮助器方法来修复它。在这种情况下,
     * 你可以通过创建私有帮助器方法fooHelper来解决此问题
     *
     * 由于使用了辅助方法,编译器在调用中使用推断来确定T是CAP#1(捕获变量)。该示例现在可以成功编译。

     * 按照约定,辅助方法通常命名为originalMethodNameHelper
     */

    private <T> void fooHelper(List<T> l) {
        l.set(0, l.get(0));
    }

泛型不是被擦除了吗? 那为何还与反射有关?


import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Map;

/**
 * ParameterizedType
 * 具体的范型类型, 如Map<String, String>
 * 有如下方法:
 *
 * Type getRawType(): 返回承载该泛型信息的对象, 如上面那个Map<String, String>承载范型信息的对象是Map
 * Type[] getActualTypeArguments(): 返回实际泛型类型列表, 如上面那个Map<String, String>实际范型列表中有两个元素, 都是String
 * Type getOwnerType(): 返回是谁的member.(上面那两个最常用)
 */
public class TestType {
    Map<String, String> map;
    //擦除 其实在类常量池里面保留了泛型信息
    public static void main(String[] args) throws Exception {
        Field f = TestType.class.getDeclaredField("map");
        System.out.println(f.getGenericType());                               // java.util.Map<java.lang.String, java.lang.String>
        System.out.println(f.getGenericType() instanceof ParameterizedType);  // true
        ParameterizedType pType = (ParameterizedType) f.getGenericType();
        System.out.println(pType.getRawType());                               // interface java.util.Map
        for (Type type : pType.getActualTypeArguments()) {
            System.out.println(type);                                         // 打印两遍: class java.lang.String
        }
        System.out.println(pType.getOwnerType());                             // null
    }
}

泛型约束

  • 无法利用原始类型来创建泛型

解决方法:使用它们的包装类

  • 无法创建类型参数的实例

变通方案:利用反射就是可以

  • 无法创建参数化类型的静态变量

原因:静态变量是类所有,共享决定了其必须能确定。但多个类型作为参数传入此类的多个实例时,就会无法确定究竟赋予此静态变量哪个实例对象所传入的参数了

  • 无法对参数化类型使用转换或者instanceof关键字 ,但需要注意通配符的情况

  • 无法创建参数化类型的数组

  • 无法创建、捕获或是抛出参数化类型对象 ,但却可以在throw后使用类型参数

  • 当一个方法的所有重载方法的形参类型擦除后,如果它们具有了相同的原始类型,那么此方法不可重载

原因:此情境下,类型擦除会产生两个同签名的方法

使用泛型以及泛型擦除带来的影响(副作用)

泛型类型变量不能使用基本数据类型

比如没有ArrayList<int>,只有ArrayList<Integer>.当类型擦除后,ArrayList的原始类中的类型变量(T)替换成Object,但Object类型不能存放int值

不能使用instanceof 运算符

ArrayList<String> strings = new ArrayList<>();
if(strings instanceof ArrayList<?>){}  //ArrayList<?>可以

// if(strings instanceof ArrayList<String>){ }  ArrayList<String> 不可以

因为擦除后,ArrayList<String>只剩下原始类型,泛型信息String不存在了,所有没法使用instanceof

泛型在静态方法和静态类中的问题

class Test2<T>{
//    public static T one; //不可以
//    public static T test(T t){} //不可以

    public static <T> T test1(T t){return t;}
}

因为泛型类中的泛型参数的实例化是在定义泛型类型对象(比如ArrayList<Integer>)的时候指定的,而静态成员是不需要使用对象来调用的,所有对象都没创建,如何确定这个泛型参数是什么

泛型类型中的方法冲突

//    @Override
//    public boolean equals(T t) {
//        return super.equals(t);
//    }

    @Override
    public boolean equals(Object obj) {
        return super.equals(obj);
    }

因为擦除后两个equals方法变成一样的了

没法创建泛型实例

因为类型不确定

//2. Cannot Create Instances of Type Parameters 无法创建类型参数的实例
class Test02 {
    //你无法创建一个类型参数的实例。例如,下面代码就会引起编译时错误:
    public static <E> void append(List<E> list) {
//        E elem = new E();  // compile-time error
//        list.add(elem);
    }
    //通过反射创建一个参数化类型的实例
    public static <E> void append(List<E> list, Class<E> cls) throws Exception {
        E elem = cls.newInstance();   // OK
        list.add(elem);
    }
}

没有泛型数组

因为数组是协变,擦除后就没法满足数组协变的原则

    public  static <T> void scene05() {
//        Plate<Apple>[] applePlates = new Plate<Apple>[10];//不允许
//        T[] arr = new T[10];//不允许
       Apple[] apples = new Apple[10];
       Fruit[] fruits = new Fruit[10];
        System.out.println(apples.getClass());
        //class [Lcom.zero.genericsdemo02.demo02.Apple;
        System.out.println(fruits.getClass());
        //class [Lcom.zero.genericsdemo02.demo02.Fruit;
       fruits = apples;
       // fruits里面原本是放什么类型的? Fruit or Apple
        // Apple[]
       fruits[0] = new Banana();//编译通过,运行报ArrayStoreException
        //Fruit是Apple的父类,Fruit[]是Apple[]的父类,这就是数组的协变
        //如果加入泛型后,由于擦除机制,运行时将无法知道数组的类型
        Plate<?>[] plates = new Plate<?>[10];//这是可以的
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,869评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,716评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,223评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,047评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,089评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,839评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,516评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,410评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,920评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,052评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,179评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,868评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,522评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,070评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,186评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,487评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,162评论 2 356

推荐阅读更多精彩内容

  • JAVA-泛型 sschrodinger 2018/11/15 简介 泛型是Java SE 1.5的新特性,泛型的...
    sschrodinger阅读 518评论 0 2
  • Java1.5版本中增加了泛型。在没有泛型之前,从集合中读取到的每一个对象都必须进行转换。如果不小心插入了错误类型...
    塞外的风阅读 822评论 0 0
  • 泛型 1.为什么要有泛型(存在的意义) 对于集合,他可以存储各种类型的对象,正因为它的抽象,导致使用时会出现混乱 ...
    controler阅读 178评论 0 0
  • - 1.泛型定义 - 2.泛型使用 - 3.泛型上下边界 - 4.JVM如何实现的泛型?   1、定义 Jdk1....
    风拂吾心阅读 278评论 0 0
  • 1、泛型概念 java 泛型,是java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被...
    有腹肌的豌豆Z阅读 206评论 0 1