一、引言

在学习集合的时候我们会发现一个问题,将一个对象丢到集合中后,集合并不记住对象的类型,统统都当做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));

《Java核心技术卷一》之 泛型的更多相关文章

  1. java核心技术卷一

    java核心技术卷一 java基础类型 整型 数据类型 字节数 取值范围 int 4 +_2^4*8-1 short 2 +_2^2*8-1 long 8 +_2^8*8-1 byte 1 -128- ...

  2. Java核心技术第八章-泛型

    摘要 本文根据<Java核心技术 卷一>一书的第八章总结而成,部分文章摘抄书内,作为个人笔记. 文章不会过于深入,望读者参考便好. 为什么要使用泛型程序设计 泛型程序设计(Generic ...

  3. 对《Java核心技术卷一》读者的一些建议

    <Java核心技术卷一>是唯一可以和<Java编程思想>媲美的一本 Java 入门书.单从技术的角度来看,前者更好一些.但上升到思想层面嘛,自然后者更好,两者的偏重点不同. 思 ...

  4. 【阅读笔记】Java核心技术卷一 #0

    这是一篇备忘性质的读书笔记,仅记录个人觉得有用的知识点 本文作为一个目录索引,部分章节跳过 吐槽:此书中文翻译有不少地方不太通顺,这种情况我要把英文版对应的部分也读一遍才能明白(说实话,英文里的从句表 ...

  5. 读《java核心技术卷一》有感

    过去一个多月了吧.才囫囵吞枣地把这书过了一遍.话说这书也够长的,一共706页.我从来不是个喜欢记录的人,一直以来看什么书都是看完了就扔一边去,可能有时候有那么一点想记录下来的冲动,但算算时间太紧,很多 ...

  6. Java核心技术点之泛型

    1. Why ——引入泛型机制的原因 假如我们想要实现一个String数组,并且要求它可以动态改变大小,这时我们都会想到用ArrayList来聚合String对象.然而,过了一阵,我们想要实现一个大小 ...

  7. Java核心技术卷一基础知识-第12章-泛型程序设计-读书笔记

    第12章 泛型程序设计 本章内容: * 为什么要使用泛型程序设计 * 定义简单泛型类 * 泛型方法 * 类型变量的限定 * 泛型代码和虚拟机 * 约束与局限性 * 泛型类型的继承规则 * 通配符类型 ...

  8. Java核心技术第八章——泛型程序设计(1)

    1.泛型程序设计 泛型程序设计意味着编写的代码可以被很多不同类型的对象所重用.例如:不希望为了聚集String和Integer对象分别设计不同的类.(个人觉得此处说的聚集译为:创建一个对象,属性可以为 ...

  9. Java核心技术卷一基础技术-第13章-集合-读书笔记

    第13章 集合 本章内容: * 集合接口 * 具体的集合 * 集合框架 * 算法 * 遗留的集合 13.1 集合接口 Enumeration接口提供了一种用于访问任意容器中各个元素的抽象机制. 13. ...

随机推荐

  1. Docker入门教程-Linux环境安装Nginx及入门使用

    介绍 Nginx("engine x")是一款是由俄罗斯的程序设计师Igor Sysoev所开发高性能的 Web和 反向代理 服务器,也是一个 IMAP/POP3/SMTP 代理服 ...

  2. springMVC入门(一)------springMVC基本概念与安装

    springMVC简介 springMVC是一个基于MVC的web框架,属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面. springMVC安装 本例中使用 ...

  3. 面试28k职位,老乡面试官从HashCode到HashMap给我讲了一下午!「回家赶忙整理出1.6万字的面试材料」

    作者:小傅哥 博客:https://bugstack.cn 目录 一.前言 二.HashCode为什么使用31作为乘数 1. 固定乘积31在这用到了 2. 来自stackoverflow的回答 3. ...

  4. Typora--我用过的最好用的markdown编辑器

    Typora Markdown编辑器,让人专注于书写的编辑器,书写博客和笔记的不二之选! 之前使用过程中只是使用了默认的功能,没有进行任何的第三发查件使用,各种方面出现了很多的局限性,比如插入了图片之 ...

  5. 曹工改bug:cpu狂飙,old gc频繁,线程神秘死亡连环案件调查报告

    曹工改bug:cpu狂飙,old gc频繁,线程神秘死亡连环案件调查报告 前言 前两天,访问开发环境上一个java服务,发现一直转圈圈,因为我开着fiddler,可以看到的现象是,接口一直没返回:本来 ...

  6. Bellman-Ford算法 例题:P3371 单源最短路径

    看到还没人用Bellman-Ford过,赶紧水一发 lz非常弱,求各位大佬轻喷qwq 洛谷题目传送门:P3371 0."松弛"操作 如果存在一条边\((u,v)\)通过中继的方式可 ...

  7. 夜息seo培训内部教程

    http://www.wocaoseo.com/thread-268-1-1.html 随着SEO日益正规化,在企业中推行SEO变得越来越重要,在上一文<将SEO整合入整个网站项目>中也有 ...

  8. WebApi之DOM的基本介绍

    1.1.1 什么是DOM ​ 文档对象模型(Document Object Model,简称DOM),是 W3C 组织推荐的处理可扩展标记语言(html或者xhtml)的标准编程接口. ​ W3C 已 ...

  9. url_for函数——快速寻找url

    我们已经知道,知道了url就可以找到对应的视图函数,那么现在问题来了,如果我们知道了视图函数,要怎么找到url呢?这时候我们就需要url_for函数了. # coding: utf-8from fla ...

  10. 23种设计模式 - 数据结构(Composite - iterator - Chain of Responsibility)

    其他设计模式 23种设计模式(C++) 每一种都有对应理解的相关代码示例 → Git原码 ⌨ 数据结构 Composite 动机(Motivation) 软件在某些情况下,客户代码过多依赖于对象容器复 ...