2021-05-15

泛型的使用

集合没有泛型的时候,集合存放数据时都会丢失原来的类型,全部改为Object。这样可以获得良好的通用性。但是取出的时候,就需要做类型转换,如果类型写错了,转换就会出现异常。为了有更好的安全性和可读性,Java在JDK1.5的时候加入了泛型。

泛型的应用非常重要,在教学中,务必让学生学会基本的使用:

  1. 在集合(List、Set、Map)上使用泛型
  2. 在通用类或者接口上使用泛型
  3. 在方法上使用泛型
  4. 明白什么是泛型擦除

泛型的作用

使用泛型机制编写的程序代码要比那些杂乱地使用Object 变量 ,然后再进行强制类型转换的代码具有更好的安全性和可读性 。泛型对于集合类尤其有用 ,例如 ,ArrayList就是一个无处不在的集合类

没有泛型的代码:

List list = new ArrayList();

list.add(123);
list.add("abc");
list.add(1>2);
list.add('E');
list.add(890.12);

for (int i = 0; i < list.size(); i++) {
    System.out.println( list.get(i) );
}

输入什么类型,就输出什么类型。但是我希望list里面存放的数据类型只有字符串怎么办 ?没有代码的情况下代码是这样的:

List list = new ArrayList();

list.add("123.A");
list.add("abc.def");

for (int i = 0; i < list.size(); i++) {
    String str = (String) list.get(i);
    System.out.println( str.split("\\.")[0] ) ;
}

但是list中如果有其他类型呢?

List list = new ArrayList();
        
list.add("123.A");
list.add(123.123);  // 这行数据就是一个浮点型
list.add("abc.def");

for (int i = 0; i < list.size(); i++) {
    String str = (String) list.get(i);
    System.out.println( str.split("\\.")[0] ) ;
}

[图片上传失败...(image-51749-1621087260272)]

发生了类型转换错误~~

为了解决这类问题,使用泛型是不二之选。通过泛型可以限制集合中数据的类型,只有符合的类型才能放到集合中

格式

在声明的集合类型后面跟上一对尖括号,实现的类型构造器的小括号前面跟上一对尖括号。里面写上需要存放的数据类型

[图片上传失败...(image-2bf134-1621087260272)]

可以看到,在定义泛型后,添加 123.123 浮点数时编译器就开始报错了。在使用泛型后,代码也不需要做类型强转了。

List<String> list = new ArrayList<String>();

list.add("123.A");
list.add("abc.def");

for (int i = 0; i < list.size(); i++) {
    String str = list.get(i);
    System.out.println( str.split("\\.")[0] ) ;
}

Map的演示

Map<String, String> data = new HashMap<>();

data.put("name", "张三");
data.put("age", "11岁");
data.put("sex", "男");

for (Map.Entry<String, String> entry: data.entrySet()) {
    System.out.println( entry.getKey() +":"+ entry.getValue());
}

简单的泛型类

在定义类 Pair 时,在类名后跟上 <T>

public class Pair<T> {
    private T first;
    private T second;

    public Pair() {
        first = null;
        second = null;
    }

    public  Pair(T first , T second){ 
        this.first = first; 
        this.second = second; 
    }
    
    public void setFirst(T newValue) {
        first = newValue;
    }

    public void setSecond(T newValue) {
        second = newValue;
    }

    public T getFirst() {
        return first;
    }

    public T getSecond() {
        return second;
    }
    
    @Override
    public String toString() {
        return "Pair [first=" + first + ", second=" + second + "]";
    }
}

使用

public static void main(String[] args) {

    System.out.println(new Pair<Integer>(1, 2));
    //输出:   Pair [first=1, second=2]
    
    System.out.println(new Pair<String>("诸葛", "孔明"));
    //输出:   Pair [first=诸葛, second=孔明]
}

使用泛型,就如同将原来类定义的 T 替换为了指定的类型版本一样,比如:

public class Pair {
    private Integer first;
    private Integer second;

    public Pair() {
        first = null;
        second = null;
    }

    public  Pair(Integer first , Integer second){ 
        this.first = first; 
        this.second = second; 
    }
    //....
}

类型参数就跟在方法或构造函数中普通的参数一样。就像一个方法有形式参数(formal value parameters)来描述它操作的参数的种类一样,一个泛型声明也有形式类型参数(formal type parameters)。当一个方法被调用,实参(actual arguments)替换形参,方法体被执行。当一个泛型声明被调用,实际类型参数(actual type arguments)取代形式类型参数。

泛型方法

在使用前,我们先明确一下泛型的各种通配符:

  1. T:type 数据类型
  2. E:element 元素
  3. K:key 键
  4. V:value 值
  5. ?:未知类型

示例:

class ArrayAlg {

    public static <T> T getMiddle(T... a) {
        return a[a.length / 2];
    }
}

测试

同一个类的方法,使用不同类型的数组,都可以正常得到数据

String[] array = {"123","345","456" , "567"};
String string = ArrayAlg.getMiddle(array);
System.out.println( string );

Integer[] array2 = {33,44,55,66,77,88};
System.out.println( ArrayAlg.getMiddle(array2) );

有限制的通配符

考虑一个简单的画图程序,它可以用来画各种形状,比如矩形和圆形。 为了在程序中表示这些形状,你可以定义下面的类继承结构:

// 抽象类
public abstract class Shape {
    public abstract void draw();
}

// 画布
public class Canvas {
    public void draw(Shape s) {
        s.draw();
    }
}

/// ---- 抽象类的子类-----------------------

public class Circle extends Shape {
    
    private int x, y, radius;

    public void draw() { 
        // ...
    }
}

public class Rectangle extends Shape {
    private int x, y, width, height;

    public void draw() {
        // ... 
    }
}

所有的图形通常都有很多个形状。假定它们用一个 list 来表示,Canvas 里有一个方法来画出所有的形状会比较方便

import java.util.List;

public class Canvas {
    public void draw(Shape s) {
        s.draw();
    }

    public void drawAll(List<Shape> shapes) {
        for (Shape s : shapes) {
            s.draw();
        }
    }
}

现在,类型规则导致 drawAll()只能使用 Shape的list 来调用。它不能,比如说对 List<Circle>来调用。 这很不幸, 因为这个方法所作的只是从这个 list 读取 shape,因此它应该也能对 List<Circle>调用。我们真正要的是这个方法能够接受一个任意种类的 shape

import java.util.List;

public class Canvas {
    public void draw(Shape s) {
        s.draw();
    }

    // 注意方法参数的变化
    public void drawAll(List<? extends Shape> shapes) { 
        for (Shape s : shapes) {
            s.draw();
        }
    }
}

我们把类型 List<Shape> 替换成了 List<? extends Shape>。现在drawAll()可以接受任何 Shape 的子类的 List,所以我们可以对 List<Circle>进行调用

List<? extends Shape>是有限制通配符的一个例子。这里?代表一个未知的类型,就像我们前面看到的通配符一样。但是,在这里,我们知道这个未知的类型实际上是Shape 的一个子类(它可以是 Shape本身或者 Shape 的子类而不必是 extends 自 Shape)。我们说 Shape是这个通配符的上限(upper bound)。
像平常一样,要得到使用通配符的灵活性有些代价。这个代价是,现在向 shapes 中写入是非法的。比如下面的代码是不允许的

public void addRectangle(List<? extends Shape> shapes) { 
   //   编译时会报错 
   shapes.add( new Rectangle()); 
}

shapes.add 的第二个参数类型是? extends Shape ——一个 Shape 未知的子类。因此我们不知道这个类型是什么,我们不知道它是不是 Rectangle 的父类;它可能是也可能不是一个父类,所以这里传递一个 Rectangle 不安全

擦除和翻译

先看下面的代码

public static String loophole(Integer x) {
    List<String> ys = new LinkedList<String>();
    List xs = ys;
    xs.add(x); 
    return ys.iterator().next();
}

public static void main(String[] args) {
    loophole(123);
}

可以看到,程序类型的转换异常,但是编译器却没有报错

这样的原因是,泛型是通过 java 编译器的称为擦除(erasure)的前端处理来实现的。你可以(基本上就是)把它认为是一个从源码到源码的转换,它把泛型版本的 loophole()转换成非泛型版本。 结果是,java 虚拟机的类型安全和稳定性决不能冒险,即使在又unchecked warning 的情况下。

擦除去掉了所有的泛型类型信息。所有在尖括号之间的类型信息都被扔掉了,因此,比如说一个 List<String>类型被转换为 List。所有对类型变量的引用被替换成类型变量的上限(通常是 Object)

Java 的泛型支持仅在语法级别

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

推荐阅读更多精彩内容

  • 集合框架使用集合做什么集合的分类具体的实现类ListArrayListLinkedListSetTreeSetMa...
    han741阅读 247评论 0 1
  • springMvc/springBoot/springCloud接受json对象作为传入参数注意点 首先先看一下p...
    stayFAndH阅读 356评论 0 0
  • Ajax介绍 Ajax是什么? 1、Ajax :Asynchronous JavaScript and XML (...
    拾壹_pro阅读 98评论 0 0
  • bug修复后可正常运行 include<stdio.h> include<graphics.h> include<...
    不努力的小企鹅阅读 183评论 0 1
  • 俄罗斯方块(bug未修复前) include<stdio.h> include<graphics.h> inclu...
    不努力的小企鹅阅读 163评论 0 1