泛型学习笔记

简单实用

泛型就是参数化类型,可以将类型当作参数传递给一个类或者是方法。

public class Cache {
    Object value;

    public Object getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value;
    }
}

假设 Cache 能够存取任何类型的值:

Cache cache = new Cache();
cache.setValue(134);
int value = (int) cache.getValue();
cache.setValue("hello");
String value1 = (String) cache.getValue();

使用泛型:

public class Cache<T> {
    T value;

    public Object getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }

}
Cache<String> cache1 = new Cache<String>();
cache1.setValue("123");
String value2 = cache1.getValue();

Cache<Integer> cache2 = new Cache<Integer>();
cache2.setValue(456);
int value3 = cache2.getValue();

泛型除了可以将类型参数化外,而参数一旦确定好,如果类似不匹配,编译器就不通过。

结论

  1. 与普通的 Object 代替一切类型这样简单粗暴而言,泛型使得数据的类别可以像参数一样由外部传递进来。它提供了一种扩展能力。它更符合面向抽象开发的软件编程宗旨。

  2. 当具体的类型确定后,泛型又提供了一种类型检测的机制,只有相匹配的数据才能正常的赋值,否则编译器就不通过。所以说,它是一种类型安全检测机制,一定程度上提高了软件的安全性防止出现低级的失误。

  3. 泛型提高了程序代码的可读性,不必要等到运行的时候才去强制转换,在定义或者实例化阶段,因为 Cache<String> 这个类型显化的效果,程序员能够一目了然猜测出代码要操作的数据类型。

泛型的定义和使用

泛型安扎使用情况可以分为3种。

  1. 泛型类

  2. 泛型方法

  3. 泛型接口

泛型类

简单定义一个泛型类

public class Test<T> {
    T field1;
}

尖括号 <> 中的 T 被称作是类型参数,用于指代任何类型。事实上,T只是一种习惯性写法,如果你愿意。你可以这样写。

public class Test<Hello> {
    Hello field1;
}

但出于规范的目的,Java 还是建议我们用单个大写字母来代表类型参数。常见的如:

  1. T 代表一般的任何类。
  2. E 代表 Element 的意思,或者 Exception 异常的意思。
  3. K 代表 Key 的意思。
  4. V 代表 Value 的意思,通常与 K 一起配合使用。
  5. S 代表 Subtype 的意思,文章后面部分会讲解示意。

如果一个类被 <T> 的形式定义,那么它就被称为是泛型类。

泛型类的使用:

Test<String> test1 = new Test<>();
Test<Integer> test2 = new Test<>();

对泛型类创建实例的时候,在尖括号中赋值相应的类型便是。T 就会被替换成对应的类型,如 String 或者是 Integer。当一个泛型类被创建时,内部自动扩展成下面的代码。

public class Test<String> {
    String field1;
}

泛型类还可以这样接受多个类型参数。

public class MultiType <E,T>{
    E value1;
    T value2;

    public E getValue1(){
        return value1;
    }

    public T getValue2(){
        return value2;
    }
}

泛型方法

public class Test1 {

    public <T> void testMethod(T t){

    }
}

泛型方法与泛型类稍有不同的地方是,类型参数也就是尖括号那一部分是写在返回值前面的。<T> 中的 T 被称为类型参数,而方法中的 T 被称为参数化类型,它不是运行时真正的参数。

当然,声明的类型参数,其实也是可以当作返回值的类型的。

public  <T> T testMethod1(T t){
        return null;
}

泛型类与泛型方法共存

public class Test1<T>{

    public  void testMethod(T t){
        System.out.println(t.getClass().getName());
    }
    public  <T> T testMethod1(T t){
        return t;
    }
}

上面代码中,Test1<T> 是泛型类,testMethod 是泛型类中的普通方法,而 testMethod1 是一个泛型方法。而泛型类中的类型参数与泛型方法中的类型参数是没有相应的联系的,泛型方法始终以自己定义的类型参数为准

所以,针对上面的代码,我们可以这样编写测试代码。

Test1<String> t = new Test1();
t.testMethod("generic");
Integer i = t.testMethod1(new Integer(1));

避免混淆,如果在一个泛型类中存在泛型方法,那么两者的类型参数最好不要同名。比如,Test1<T> 代码可以更改为这样

public class Test1<T>{

    public  void testMethod(T t){
        System.out.println(t.getClass().getName());
    }
    public  <E> E testMethod1(E e){
        return e;
    }
}

泛型接口和泛型类类似

public interface Iterable<T> {
}

通配符 ?

除了用 <T> 表示泛型外,还有 <?> 这种形式。 被称为通配符。

class Base{}

class Sub extends Base{}

Sub sub = new Sub();
Base base = sub;            

上面代码显示,Base 是 Sub 的父类,它们之间是继承关系,所以 Sub 的实例可以给一个 Base 引用赋值,那么

List<Sub> lsub = new ArrayList<>();
List<Base> lbase = lsub;

Sub 是 Base 的子类,不代表 List<Sub>List<Base> 有继承关系。

通配符有 3 种形式。

1. <?> 被称作无限定的通配符。

2. <? extends T> 被称作有上限的通配符。

3. <? super T> 被称作有下限的通配符。

无限定通配符

public void testWildCards(Collection<?> collection){
}

上面的代码中,方法内的参数是被无限定通配符修饰的Collection对象,它隐略地表达了一个意图或者可以说是限定,那就是 testWidlCards()这个方法内部无需关注Collection中的真实类型,因为它是未知的。所以只能调用 Collection 中与类型无关的方法。

image
我们可以看到,当 <?> 存在时,Collection 对象丧失了 add() 方法的功能,编译器不通过。 

在看如下代码

List<?> wildlist = new ArrayList<String>();
wildlist.add(123);// 编译不通过

有人说,<?> 提供了只读的功能,也就是它删减了增加具体类型元素的能力,只保留与具体类型无关的功能。它不管装载在这个容器内的元素是什么类型,它只关心元素的数量、容器是否为空?

<? extends T>

<?> 代表着类型未知,但是我们的确需要对于类型的描述再精确一点,我们希望在一个范围内确定类别,比如类型 A 及 类型 A 的子类都可以。

public void testSub(Collection<? extends Base> para){

}

上面代码中,para这个collection接收Base及Base的之类的类型

但是,它仍然丧失了写操作的能力。也就是说

para.add(new Sub());//失败
para.add(new Base());//失败

仍然是编译不通过的,因为para是继承自base的一个子类(现在并不清楚具体是什么)

<? super T>

这个和<? extends T>相对应,代表T以及T的超类

public void testSuper(Collection<? super Sub> para){
}

<? super T>神奇的地方在于,它拥有一定程度的写操作的能力

 public void testSuper(Collection<? super Sub> para){
    para.add(new Sub());//编译通过,para是sub的一个父类,可以将子类对象引用给他,实际就是java的多态(性质相同)
    para.add(new Base());//编译不通过
}

通配符与参数类型的区别

一般而言,通配符能干的事情可以用类型参数替换

比如

public void testWildCards(Collection<?> collection){}

可以被

public <T> void test(Collection<T> collection){}

取代

如果用泛型方法来取代通配符,那么上面代码中collection是能够进行写操作的。只不过要进行强制转换。

public <T> void test(Collection<T> collection){
    collection.add((T)new Integer(12));
    collection.add((T)"123");
}

类型参数适用于参数之间的类别依赖关系:

public class Test2 <T,E extends T>{
    T value1;
    E value2;
}
public <D,S extends D> void test(D d,S s){

    }

E 类型是 T 类型的子类,显然这种情况类型参数更适合。

有一种情况是,通配符和类型参数一起使用。

public <T> void test(T t,Collection<? extends T> collection){

}

如果一个方法的返回类型依赖于参数的类型,那么通配符也不行。

public T test1(T t){
    return value1;
}

类型擦除,看了好几遍(尚不能完全理解,先做个记录)

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

这是因为,泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除

泛型类和普通类在 java 虚拟机内是没有什么特别的地方。回顾文章开始时的那段代码

List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();

System.out.println(l1.getClass() == l2.getClass());

打印的结果为 true 是因为 List<String>List<Integer> 在 jvm 中的 Class 都是 List.class

泛型信息被擦除了。

答案是泛型转译。

public class Erasure <T>{
    T object;
    
    public Erasure(T object) {
        this.object = object;
    }
}

Erasure 是一个泛型类,查看它在运行时的状态信息可以通过反射。

Erasure<String> erasure = new Erasure<String>("hello");
Class eclz = erasure.getClass();
System.out.println("erasure class is:"+eclz.getName());

结果如下:

erasure class is:com.alguojian.test.Erasure

Class 的类型仍然是 Erasure 并不是 Erasure<T> 这种形式,再看泛型类中 T 的类型在 jvm 中是什么类型。

Field[] fs = eclz.getDeclaredFields();
for ( Field f:fs) {
    System.out.println("Field name "+f.getName()+" type:"+f.getType().getName());
}

打印结果:

Field name object type:java.lang.Object

改变代码如下:

public class Erasure <T extends String>{
//  public class Erasure <T>{
    T object;
    
    public Erasure(T object) {
        this.object = object;
    }
}

打印结果:

Field name object type:java.lang.String

在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如 <T> 则会被转译成普通的 Object 类型,如果指定了上限如 <T extends String>则类型参数就被替换成类型上限。

所以在反射中:

public class Erasure <T>{
    T object;

    public Erasure(T object) {
        this.object = object;
    }

    public void add(T object){

    }
}

add()方法对应的Method的签名应该是Object.class

Erasure<String> erasure = new Erasure<String>("hello");
Class eclz = erasure.getClass();
System.out.println("erasure class is:"+eclz.getName());

Method[] methods = eclz.getDeclaredMethods();
for ( Method m:methods ){
    System.out.println(" method:"+m.toString());
}

打印结果如下:

 method:public void com.frank.test.Erasure.add(java.lang.Object)

也就是说,如果要在反射中找到 add 对应的 Method,应该调用 getDeclaredMethod("add",Object.class) 否则程序会报错,提示没有这么一个方法,原因就是类型擦除的时候,T 被替换成 Object 类型了。

类型擦除的局限性

类型擦除,是泛型能够与之前的java版本代码兼容共存的原因,但也因为类型擦除,会抹掉很多继承相关的特性

利用类型擦除的原理,用反射的手段就绕过了正常开发中编译器不允许的操作限制。

注意地方

泛型类或者泛型方法中,不接受8种基本数据类型

List<int> li = new ArrayList<>();
List<boolean> li = new ArrayList<>();

需要使用包装类

List<Integer> li = new ArrayList<>();
List<Boolean> li1 = new ArrayList<>();

参考文章

设计模式番外篇之泛型
Java泛型详解
Java泛型详解,通俗易懂只需5分钟

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容

  • 泛型类定义与使用 定义泛型//定义 class Point<T>{// 此处可以随便写标识符号 pr...
    陌上疏影凉阅读 304评论 0 0
  • 所谓泛型,就是变量类型的参数化。泛型是java1.5中引入的一个重要特征,通过引入泛型,可以使编译时类型安全,运行...
    cvmars阅读 206评论 0 2
  • 开发人员在使用泛型的时候,很容易根据自己的直觉而犯一些错误。比如一个方法如果接收List作为形式参数,那么如果尝试...
    时待吾阅读 992评论 0 3
  • object 变量可指向任何类的实例,这让你能够创建可对任何数据类型进程处理的类。然而,这种方法存在几个严重的问题...
    CarlDonitz阅读 884评论 0 5
  • 成长的个中滋味,长大的人深有体会~ 我成长了,但还没有长大;我成年了,但并没有因此变得成熟;我想要经济独立,但依旧...
    未知jianghu阅读 164评论 0 0