当前位置: 首页 > news >正文

《Java核心技术卷一》之 泛型

在学习集合的时候我们会发现一个问题,将一个对象丢到集合中后,集合并不记住对象的类型,统统都当做Object处理,这样我们取出来再使用时就得强制转换类型,导致代码臃肿,而且加入集合时都是以Object,没做类型检查,那么强制转换就容易出错,泛型的诞生就是为解决这些问题。

二、使用泛型

泛型是如何解决这个问题呢?按照上面的问题,我们只需要在创建集合时指定集合元素的类型,那么集合就能记住对象的类型,那当我们加入是就只能按照指定的类型进行加入,而取出使用时也不需要强制转换:

ArrayList<Integer> list = new ArrayList<Integer>();

这是Java 7之前的写法,很明显构造器上面的泛型没有必要,现在推荐以下写法:

ArrayList<Integer> list = new ArrayList<>();

既然我已经指定了类型,那么添加时只能添加Integer,并且使用时可以直接当做Integer使用

System.out.println(list.get(2)+3);

这种参数化类型就是泛型,泛型就是允许在定义类、接口、方法时使用类型形参,这个参数形参将在申明变量、创建对象、调用方法时动态指定。

三、 定义泛型接口、类

来看看List接口和Map接口的定义:

public interface List<E> extends Collection<E>
public interface Map<K,V>

List接口定义时指定了一个类型形参,Map接口定义了两个类型形参。接口定义了形参之后,在接口中形参就可以当做一种类型来使用,那么其中的方法就可以使用类型形参

boolean add(E e);

这种方式其实也是一种代码复用,我们通过类型形参,高度的将参数抽象,而不需要每种类型都去重新定义类,只要在使用时确定类型即可。我们也可以自定义泛型类

public class WebResult<T> {
    //使用T类型形参定义实例变量
    private T data;

    public WebResult() {
    }
    //使用T类型形参构造对象
    public WebResult(T data) {
        this.data = data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public T getData() {
        return this.data;
    }

    public static void main(String[] args) {
        WebResult<String> webResult = new WebResult<>("返回一个String对象");
        System.out.println(webResult.getData());
        WebResult<Integer> webResult1 = new WebResult<>(10);
        System.out.println(webResult1.getData());
    }

}

四、通配符

先看下面这段代码:

public static void main(String[] str){
    ArrayList<String> arrayList=new ArrayList();
    test(arrayList);
}

public static void test(List<Object> test){
    for (int i = 0; i <test.size() ; i++) {
        System.out.println(test.get(i));
    }
}

这段代码会出现编译错误,因为List<String>对象不能作为List<Object>使用,这说明泛型不是协变的,因为之前数组的设计是协变的,导致存在安全性问题,而泛型的设计原则是编译时不出现警告就不会出现类型转换错误,那为了表示各种泛型的父类,就引入了通配符:?这个问号代表可以是匹配任何类型。

将方法修改:

public static void test(List<?> test){
    for (int i = 0; i <test.size() ; i++) {
        System.out.println(test.get(i));
    }
}

这样便可以顺利编译,我们再加上这段代码:

public static void main(String[] str){
    ArrayList<String> arrayList=new ArrayList();
    test(arrayList);
    List<?> strings = arrayList;
    strings.add("abc");
}

这里我们可以将arrayList给strings,按说这个时候是不能赋值的,因为List不知道类型参数的值,这是编译器作用,可以进行类型推理,但是后面的strings.add("abc")是不能通过编译的,编译器不能对 List 的类型参数作出足够严密的推理,以确定将 String 传递给 List.add() 是类型安全的。所以编译器将不允许这么做。

4.1 设置通配符上限

List<?>这种方式,通配的是所有的类型,很多时候我们可以确定是哪一类对象可以添加进去,我们只希望它代表某一类泛型的父类,这个时候我们可以设置通配符的上限。

//动物类
public abstract class Animal {
    public abstract void say();
}

public class Cat extends Animal {
    @Override
    public void say() {
        System.out.println("喵喵");
    }
}

public class Dog extends Animal {

    @Override
    public void say() {
        System.out.println("旺旺");
    }
}

这个时候我们就限定了上限

public static void test1(List<? extends Animal> animals) {
    for (int i = 0; i < animals.size(); i++) {
        animals.get(i).say();
    }
}

我们也可以直接在定义类型形参的时候设置上限

public class WebResult<T extends Animal> {

4.2 通配符下限

既然有设置上限,那也有设置下限,那在什么情况下会使用下限呢?看个例子

//将src中的集合复制到dest,并返回最后一个值
public static <T> T copy(Collection<T> dest, Collection<? extends T> src) {
    T last = null;
    for (T ele : src) {
        last = ele;
        dest.add(ele);
    }
    return last;
}

功能比较很简单,将src中的集合复制到dest,并返回src最后一个值

List<Number> ln = new ArrayList<>();
List<Integer> li = new ArrayList<>();
//编译出错,类型不确定
Integer last = copy(ln, li);

这个时候出错,因为虽然我们知道返回的值一定是Integer,但是由于copy方法的返回值并不是,所有相当于我们在复制的过程中丢失了src的类型,如果我们想定义约束关系使得返回值明确即:dest集合元素类型与src的关系要么相同要么是其父类,为了表示这种约束关系,引入了<? super T> 这个通配符表示它必须是T本身或者T的父类。复制代码

//将src中的集合复制到dest,并返回最后一个值
public static <T> T copy(Collection<? super T> dest, Collection<? extends T> src) {
    T last = null;
    for (T ele : src) {
        last = ele;
        dest.add(ele);
    }
    return last;
}

五、泛型方法

除了泛型接口和泛型类,我们还可以定义泛型方法,写法如下:

static <T> void arrayToList(T[] a, List<T> list) {
    for (T o : a) {
        list.add(o);
    }
}

调用如下:

Object[] objects = new Object[10];
List<Object> list = new ArrayList<>();
arrayToList(objects, list);

Integer[] integers = new Integer[10];
List<Integer> integerList = new ArrayList();
arrayToList(integers, integerList);

String[] strings = new String[10];
List<String> stringList = new ArrayList<>();
arrayToList(strings, stringList);
//编译错误,类型不正确
arrayToList(strings, integerList);

这里可以看出泛型方法跟类型通配符的功能有点类似,其实在大部分情况下我们可以用泛型方法代替类型通配符。

泛型方法允许类型形参被用来表示方法的一个或者多个参数之间的依赖关系,或者说与返回值之间的关系,如果没有这种关系,我们就不使用泛型方法。

六、擦除与转换

当把一个具有泛型信息的对象赋给一个没有泛型信息的变量时,所有的类型信息就都丢掉了,比如List<String>类型被转换成List,则对该List的类型检查也变成了Object。

public class WebResult<T extends Number> {
    //使用T类型形参定义实例变量
    private T data;

    public WebResult() {
    }

    //使用T类型形参构造对象
    public WebResult(T data) {
        this.data = data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public T getData() {
        return this.data;
    }

    public static void main(String[] args) {
        WebResult<Integer> webResult1 = new WebResult<>(10);
        System.out.println(webResult1.getData());
        WebResult<Integer> a = new WebResult<>(20);
        WebResult b = a;
        //已经擦除了泛型,只能按最高类型Object
        //Integer bData = b.getData();
        Object object=b.getData();
    }

}

原本的泛型类上限是Number,而当把a赋给擦除泛型的b对象时,编译器失去了推断能力,只能把其当做Objec来处理。

而当一个List转成泛型对象是java是允许的

List<Integer> integerList = new ArrayList<>();
List stringList = integerList;
//允许直接将list对象转换给
List<String> strings = stringList;
//直接获取数据会出现错误,因为转换不成功
System.out.println(stringList.get(0));

转载于:https://www.cnblogs.com/-wanglei/p/11232610.html

相关文章:

  • emacs 窗口控制
  • 如何在同一任务列表中显示我的任务及我所属组的任务
  • POJ2017-Speed Limit
  • springboot不占用端口启动
  • C语言标准中的逻辑位移和算术位移
  • 本体感受和演讲能力
  • 设计模式:动态代理
  • eDocEngine_3.0.4.273的手动安装
  • TensorFlow(2)- 建立一个简单的神经网络
  • 智能营销笔记本有用吗?
  • 什么是算法
  • Research Articles For Language Learning
  • 聪明的程序员用Delphi,真正的程序员用C++,偷懒的程序员用PowerShell
  • 第七章 管理类型(In .net4.5) 之 使用类型
  • 插入数据进入已排好序的数组
  • 07.Android之多媒体问题
  • Akka系列(七):Actor持久化之Akka persistence
  • CSS实用技巧干货
  • HTTP--网络协议分层,http历史(二)
  • IIS 10 PHP CGI 设置 PHP_INI_SCAN_DIR
  • Java 实战开发之spring、logback配置及chrome开发神器(六)
  • Java比较器对数组,集合排序
  • Mac转Windows的拯救指南
  • Odoo domain写法及运用
  • passportjs 源码分析
  • storm drpc实例
  • 高度不固定时垂直居中
  • 源码之下无秘密 ── 做最好的 Netty 源码分析教程
  • 职业生涯 一个六年开发经验的女程序员的心声。
  • 【干货分享】dos命令大全
  • 宾利慕尚创始人典藏版国内首秀,2025年前实现全系车型电动化 | 2019上海车展 ...
  • 进程与线程(三)——进程/线程间通信
  • 移动端高清、多屏适配方案
  • ​flutter 代码混淆
  • (3)选择元素——(17)练习(Exercises)
  • (k8s中)docker netty OOM问题记录
  • (附源码)springboot宠物管理系统 毕业设计 121654
  • (附源码)基于ssm的模具配件账单管理系统 毕业设计 081848
  • (附源码)计算机毕业设计ssm基于B_S的汽车售后服务管理系统
  • (续)使用Django搭建一个完整的项目(Centos7+Nginx)
  • (一)C语言之入门:使用Visual Studio Community 2022运行hello world
  • (转载)Linux 多线程条件变量同步
  • .class文件转换.java_从一个class文件深入理解Java字节码结构
  • .NET 8 中引入新的 IHostedLifecycleService 接口 实现定时任务
  • .NET Micro Framework 4.2 beta 源码探析
  • .NET 除了用 Task 之外,如何自己写一个可以 await 的对象?
  • .NET/C# 检测电脑上安装的 .NET Framework 的版本
  • .net知识和学习方法系列(二十一)CLR-枚举
  • .NET中的Event与Delegates,从Publisher到Subscriber的衔接!
  • /boot 内存空间不够
  • @Builder用法
  • [Android Pro] android 混淆文件project.properties和proguard-project.txt
  • [APIO2015]巴厘岛的雕塑
  • [BZOJ 1040] 骑士
  • [C#]winform使用引导APSF和梯度自适应卷积增强夜间雾图像的可见性算法实现夜间雾霾图像的可见度增强