Java 泛型

泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类.

泛型类

我们定义一个简单的泛型类, T称为泛型参数, G被称为泛型化了

class G<T> {

}

接着我们在内部定义一个泛型变量

class G<T> {
    T t;
}

然后我们再添加一个泛型方法泛型方法

class G<T> {
    T t;

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

下来我们来使用一下这个泛型类

G<String> g = new G<>();
g.setValue("value");

泛型方法

在一个非泛化的类中我们也可以直接定义泛化的方法

类型擦除

说到java中的泛型就不得不提泛型参数的类型擦除. 当java源码文件被编译成class文件的时候,编译器会将泛型中的类型参数擦除掉(其实class文件中还是会保留部分的泛型信息, 具体参考java虚拟机规范).

类定义的泛型参数T会被替换成具体类型, 一般为Object. 而<T>信息则会被擦除掉, 例如G就会替换成

class G {
    Object t;

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

而在引用该类型的时候则会擦除成

G g = new G();
g.setValue("value");

因此,java里的泛型类型安全是由编译器保证的, 在运行期是无法保证类型的安全的.

泛型类的继承关系

如果类被泛型化之后, 会对类本身的继承关系造成影响

public class TestGeneric {

    public static void main(String[] args) {
        SuperParam superParam = new SuperParam();

        G<SuperParam> g = new G<>();
        g.setValue(superParam);

        SubG1 subG1 = new SubG1();
        subG1.setValue(1);
        subG1.printValue("SubG1 printValue");
        subG1.print();

        SubG2 subG2 = new SubG2();
        subG2.setValue("SubG2 setValue");
        subG2.printValue("SubG2 printValue");
        subG2.print();

        SubG3<Integer> subG3 = new SubG3<>();
        subG3.setValue("SubG3 setValue");
        subG3.printValue("SubG3 printValue");
        subG3.print(3);

        SubG4<Integer> subG4 = new SubG4();
        subG4.setValue("SubG4 setValue");
        subG4.printValue("SubG4 printValue");
        subG4.print(4);
    }
}

class G<T> {
    T t;

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

    public void printValue(T t) {
        System.out.println(t);
    }

    public T getT() {
        return t;
    }
}

// SubG1继承了G的泛型参数. 但是SubG1本身是没有泛化的
class SubG1 extends G {
    public void print() {
        System.out.println(t);
    }
}

// 强制指定继承过来的T的类型为String
class SubG2 extends G<String> {
    public void print() {
        System.out.println("Super:" + t);
    }
}

// G的类型由继承过来的方法指定, SubG3则再次由本类自己指定类型
class SubG3<T> extends G {
    public void print(T t1) {
        System.out.println("Super:" + t + ". this:" + t1);
    }
}

// 指定G的类型为String, SubG4则仍然由本类进行泛型化
class SubG4<T> extends G<String> {
    public void print(T t1) {
        System.out.println("Super:" + t + ". this:" + t1);
    }
}

class SuperParam {
    public String toString() {
        return "SuperParam";
    }
}

结果为

SubG1 printValue
1
SubG2 printValue
Super:SubG2 setValue
SubG3 printValue
Super:SubG3 setValue. this:3
SubG4 printValue
Super:SubG4 setValue. this:4

需要特别指出的是, 在SubG1对象分别调用setValue()printValue()方法时分别使用了IntegerString俩个类型, 但是却没有产生任何异常信息.

泛型参数的继承关系

public class TestGeneric {

    public static void main(String[] args) {
        SuperParam superParam = new SuperParam();
        Param param = new Param();

        G<SuperParam> gSuperParam = new G<>();
        gSuperParam.setValue(superParam);
        gSuperParam.setValue(param);
        SuperParam t = gSuperParam.getT();

        G<Param> gParam = new G<>();
        gParam.setValue(param);
        gParam.setValue(superParam);    // compile error

        SubParam subParam = new SubParam();
        gParam.setValue(subParam);

    }
}

class G<T> {
    T t;

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

    public T getT() {
        return t;
    }
}

class SuperParam {}

class Param extends SuperParam {}

class SubParam extends Param {}

从这一行gParam.setValue(subParam);我们可以看到类型参数的继承结构和普通类型的继承结构的规则是一样的.

泛化在方法中的应用

public class TestGeneric {

    public static void main(String[] args) {

        print(new G<>());
        print(new G<SuperParam>());     // compile Error
        print(new SubG1());
        print(new SubG2());
        print(new SubG3());
        print(new SubG3<SuperParam>());
        print(new SubG4());
        print(new SubG4<SuperParam>());

        printSuperParam(new G<>());
        printSuperParam(new G<SuperParam>());
        printSuperParam(new G<Param>());        // compile Error
    }

    public static void print(G<String> gs) {}

    public static void printSuperParam(G<SuperParam> gs) {}
}

从上面的例子中我们可以看出, 泛化的类的泛型参数并没有对其类型判断造成影响, 子类化的参数仍然是编译通过的. 但是类型参数的继承再传递到方法时, 却被认为不是相同的类型.

通配符

?在泛型参数中作为通配符存在, 它一般和extendssuper关键字一起使用. 它表示不确定的一组类型, 例如和extends关键字一起使用就是表示继承自某个类的所有类型

extends

extends关键字是用来定义泛型参数的继承关系. 它表示我们的泛型参数继承自某个类型, 也被我们称为上界符.

我们修改一下G的类型定义,我们引入extends关键字

public class TestGeneric {

    public static void main(String[] args) {

        SuperParam superParam = new SuperParam();

        G<SuperParam> g = new G<>();
        g.setValue(superParam);

        SubG1 subG1 = new SubG1();
        subG1.setValue(1);
        subG1.printValue("SubG1 printValue");
        subG1.print();

    }
}

class G<T extends SuperParam> {
    T t;

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

    public void printValue(T t) {
        System.out.println(t);
    }

    public T getT() {
        return t;
    }
}

当使用extends关键字之后, 我们就将这个类的泛化信息固定了下来, 在实例化的时候, 其类型参数必须是继承自某类的子类型

如果我们在实例化的时候指定extends会发生什么呢?

public class TestGeneric {

    public static void main(String[] args) {
        SuperParam superParam = new SuperParam();

        G<? extends SuperParam> g = new G<>();
        g.setValue(superParam);     // compile error
        g.setValue(new Param());        // compile error
    }
}

class G<T> {
    T t;

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

class SuperParam {}

class Param extends SuperParam {}
class Param1 extends SuperParam {}

不推荐这种用法, 因为这种情况下如果我们可以对其使用Param或者Param1的类型, 那么这就和不使用泛型是一样的, 会引起类型转化异常.

但是我们却可以在另外一种情况下使用这个关键字

public class TestGeneric {

    public static void main(String[] args) {
        SuperParam superParam = new SuperParam();

        print(new G<>());   // 默认的是SuperParam类型
        print(new G<Param>());
    }

    public static void print(G<? extends SuperParam> g) {
        SuperParam t = g.getT();
    }
}

class G<T> {
    T t;

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

    public T getT() {
        return t;
    }
}

class SuperParam {}

class Param extends SuperParam {}

super

super作为一种下界符存在. 也就在具体使用时的参数都必须是泛型参数的父类才行.

G<? super SuperParam> g = new G<>();
g.setValue(new SuperParam());
g.setValue(new Param());

同样我们可以在方法中如此使用

public class TestGeneric {

    public static void main(String[] args) {
        print(new G<SuperParam>());
        print(new G<>());   // 默认是Param
    }

    public static void print(G<? super Param> g) {
        Object t = g.getT();
    }
}

class G<T> {
    T t;

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

    public T getT() {
        return t;
    }
}

class SuperParam {}

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

推荐阅读更多精彩内容

  • 开发人员在使用泛型的时候,很容易根据自己的直觉而犯一些错误。比如一个方法如果接收List作为形式参数,那么如果尝试...
    时待吾阅读 1,050评论 0 3
  • 泛型的好处 使用泛型的好处我觉得有两点:1:类型安全 2:减少类型强转 下面通过一个例子说明: 假设有一个Tes...
    德彪阅读 1,122评论 0 0
  • 我们知道,使用变量之前要定义,定义一个变量时必须要指明它的数据类型,什么样的数据类型赋给什么样的值。 假如我们现在...
    今晚打肉山阅读 980评论 0 1
  • Java泛型总结# 泛型是什么## 从本质上讲,泛型就是参数化类型。泛型十分重要,使用该特性可以创建类、接口以及方...
    kylinxiang阅读 913评论 0 1
  • (看完后如果觉得还可以,可以分享到朋友圈,让更多的家长受益) 1 孩子正在写作业时,您在干什么? 错误做法:一边看...
    博慧bohui阅读 474评论 0 1