泛型

一、基本概念和原理

泛型将接口的概念进一步延申,“泛型”的字面意思是广泛的类型。

类、接口和方法都可以应用于非常广泛的类型,代码与它们能够操作

的数据类型不再绑定到一起,同一套代码可以应用到多种数据类型。

这样,不仅可以复用代码降低耦合,而且可以提高代码的可读性和安全性。

1.泛型类

public class Pair <T>{
private T first;
private T second; public Pair(T first, T second) {
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public void setFirst(T first) {
this.first = first;
}

T表示类型参数,泛型就是类型参数化,处理的数据类型不是固定的,而是可以作为参数传入。

//类型参数可以有多个,用逗号分隔
public class Pair2<T, U> {
private T first;
private U second;
public Pair2(T first, U second) {
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public U getSecond() {
return second;
}
}
        Pair<Integer> pair = new Pair<>(99, 88); //Integer就是传递的实际类型参数
System.out.println(pair.getFirst() + " " + pair.getSecond()); //99 88
Pair2<String, Integer> tang = new Pair2<>("Tang", 22);
System.out.println(tang.getFirst() + " " + tang.getSecond());//Tang 22

2.泛型方法

除了泛型类,方法也可以是泛型的,而且,一个方法是不是泛型与它所在的类是不是泛型无关。

public class Alex {
//类型参数位E,放在返回值前面,与泛型类一样类型参数可以有多个
public static <E> int indexOf(E[] arr, E elm) {
for (int i = 0; i < arr.length; i++) {
if (arr[i].equals(elm)) {
return i;
}
}
return -1;
}
public static void main(String[] args) {
int index = indexOf(new Integer[]{1, 2, 3}, 2);
System.out.println(index);//
int tom = indexOf(new String[]{"Tom", "Jimmy", "Cat"}, "Tom");
System.out.println(tom);//
}
}

3.泛型接口

public interface Comparable<T> {
public int compareTo(T o);
}

4.基本原理

Java泛型的内部原理:

public class Pair {
Object first;
Object second;
public Pair(Object first, Object second){
this.first = first;
this.second = second;
}
public Object getFirst() {
return first;
}
public Object getSecond() {
return second;
}
}

对于泛型类,Java编译器会将泛型代码转换为非泛型代码(Object或者其上边界类型),

就如上面的Pair类代码,将类型参数T擦除,替换为Object,插入必要的强制类型转换。Java

虚拟机执行的时候不知道泛型这回事,只知道普通的类和代码。

Java是从Java5才引入泛型的,所以用类型擦除来实现泛型是一种不得已的选择。

泛型的好处:

编译器帮我们发现代码问题,因此更安全,还提升了代码的可读性。

5.类型参数的限定

Java支持给类型参数设定上界,类型参数必须为给定的上界类型或者其子类型。

//限定类型后如果类型使用错误编译器就会提醒
//并且擦除类型时就会转换为相应的边界类型
public class SonPair<U extends String, V extends Integer> extends Pair<U, V>{
public SonPair(U first, V second) {
super(first, second);
}
}

其中上界可以是某个具体类,也可以是接口或者其他类型参数。

总之,泛型是计算机程序中的一种重要思维方式,它将数据结构和算法与数据类型相分离,

使得同一套数据结构和算法能够应用于各种数据类型,而且可以保证类型安全,提高可读性。

二、通配符

1.更简洁的参数类型限定

1)有限定统配符

形如ClassName<? extends E>这种统配符,叫做有限定统配符匹配E或者E的某个子类,具体的类型未知。

public void addAll(DynamicArray<? extends E> c)
public <T extends E> void addAll(DynamicArray<T> c)

虽然与使用<T extends E>的效果一样,该方法与使用<T extends E>相比不用定义新的类型参数T,

因此也不必把add方法定义为泛型方法,也不用改动相关的源代码。

2)无限定通配符

形如ClassName<?>的统配符为无限定统配符。

比较:

public static int indexOf(DynamicArray<?> arr, Object elm)
public static <T> int indexOf(DynamicArray<T> arr, Object elm)

使用无限定统配符比添加一个泛型参数T更加简洁。

但是,不管是有限定统配符还是无限定统配符都有一个缺陷:只能读,不能写。

例如:

DynamicArray<Integer> ints = new DynamicArray<>();
DynamicArray<? extends Number> numbers = ints;
Integer a = 200;
numbers.add(a); //报错
numbers.add((Number)a)Ҕ //报错
numbers.add((Object)a); //报错

为什么会报错?

其次,如果方法的返回值依赖于类型参数,也不能用通配符:

public static <T extends Comparable<T>> T max(DynamicArray<T> arr){
T max = arr.get(0);
for(int i=1; i<arr.size(); i++){if(arr.get(i).compareTo(max)>0){
max = arr.get(i);
}
}
return max;
}
//很难用统配符代替

总之:

1)统配符能做的类型参数都能做

2)因为更简洁,能用通配符就用通配符

3)如果参数类型之间有依赖关系,或者返回值依赖类型参数,或者需要写操作,只能由类型参数。

4)两者往往配合使用

2.超类型通配符

形如<? super E>的通配符被称为超类型通配符,表示E的某个父类型。

使用场景:

1)可以更灵活地写入:

    public void copyTo(DynamicArray<E> dest){
for(int i=0; i<size; i++){
dest.add(get(i));
}
}
DynamicArray<Integer> ints = new DynamicArray<Integer>();
ints.add(100);
ints.add(34);
DynamicArray<Number> numbers = new DynamicArray<Number>();
ints.copyTo(numbers);//报错,因为期望的类型是DynamicArray<Integer>

解决办法:

    public void copyTo(DynamicArray<? super E> dest){
for(int i=0; i<size; i++){
dest.add(get(i));
}
}

2)解决子类没有实现泛型接口的问题:

public static <T extends Comparable<T>> T max(DynamicArray<T> arr)
    //注意Base实现了Comparable<Base>
class Base implements Comparable<Base>{
//code
}
    //Child并没有实现Comparable<Child>它实现的是Comparable<Base>
class Child extends Base {
//code
}
    DynamicArray<Child> childs = new DynamicArray<Child>();
childs.add(new Child(20));
childs.add(new Child(80));
Child maxChild = max(childs);//编译器报错,提示类型不匹配,

报错原因:在<T extends Comparable<T>> 中对T类型的要求是extends Comparable<T>

而Child并没有实现Comparable<Child>,其实Child也没有必要去重复实现Comparable接口。

解决办法:

public static <T extends Comparable<? super T>> T max(DynamicArray<T> arr)

<? super T>可以配匹Base所以整体是匹配的。

另外,请注意:参数类型限定只有extends形式没有super形式,比如:

<E super T>是有问题的,Java不支持这种语法。

总结:

1)<? super E>用于灵活写入与比较,使得使得父可以使用于子。

2)<?> <? extends E>用于灵活读取。

三、细节和局限性

我们将通过以下几方面介绍这些细节和局限性:

1.使用泛型类、方法和接口

1)因为类型参数会被替换为object或者一个类的父类,所以基本类型不能用来实例化类型参数,解决办法,使用包装类型

2)运行时类型信息不适用于泛型:

在内存中每个类都有一份类型信息,而每个对象也都保存着对应的类型信息的引用,

这个类型信息也是一个对象,它的类型为Class,Class本身也是一个泛型类。

每个类的类型对象可以通过<类名>.class的方式引用,可以通过getClass()方法获得。

这个类型只有一份与泛型无关,所以Java不支持如下写法:

Pair<Integer>.class

一个泛型对象的getClass()方法的返回值与其原始类型对象也是相同的。

同理也不支持:

if (p1 instanceof Pair<Integer>)

不过支持:

if (p2 instanceof Pair<?>)

3)由于类型擦除可能会引发一些冲突:

比如上一个Base与Child的例子,Child没有专门实现Comparable接口

可是如果Child想自己实现接口,重写compareTo()方法呢比如:

class Child extends Base implements Comparable<Child>{
//编译器报错
}

遗憾的是编译器会报错,Comparable接口不能如此被实现两次(一次是Comparable<Base>, 一次是Comparable<Child>)

因为类型擦除后,实际上只能有一个。因此只能通过重写compareTo()方法修改比较方法:

 class Child extends Base {
@Override
public int compareTo(Base o) {
if(!(o instanceof Child)){
throw new IllegalArgumentException();
}
Child c = (Child)o;
return 0;
}
}

4)对重载方法的影响:

public static void test(DynamicArray<Integer> intArr)
public static void test(DynamicArray<String> strArr)//报错

2.定义泛型类、方法和接口

1)不能通过类型参数创建对象,解决方法:使用反射等。

2)泛型类型参数不能用于静态变量和方法,为什么????

3.泛型与数组

1)不能创建泛型数组,例如:

Pair<Object,Integer>[] options = new Pair<Object,Integer>[]{
new Pair("1

Java笔记(五)泛型的更多相关文章

  1. 疯狂java笔记(五) - 系统交互、System、Runtime、Date类

    一.程序与用户交互(Java的入口方法-main方法): 运行Java程序时,都必须提供一个main方法入口:public static void main(String[] args){} publ ...

  2. Java笔记(五)……运算符

    算术运算符 算术运算符的注意问题: 如果对负数取模,可以把模数负号忽略不记,如:5%-2=1.但被模数是负数就另当别论. 对于除号"/",它的整数除和小数除是有区别的:整数之间做除 ...

  3. java之jvm学习笔记五(实践写自己的类装载器)

    java之jvm学习笔记五(实践写自己的类装载器) 课程源码:http://download.csdn.net/detail/yfqnihao/4866501 前面第三和第四节我们一直在强调一句话,类 ...

  4. Linux系统运维笔记(五),CentOS 6.4安装java程序

    Linux系统运维笔记(五),CentOS 6.4安装java程序 用eclipse编译通的java程序,现需要实施到服务器.实施步骤: 一,导出程序成jar包. 1,在主类编辑界面点右健,选  ru ...

  5. Java基础之十五 泛型

    第十五章 泛型 一般的类和方法,只能使用具体的类型:要么是基本类型,要么是自定义类型.如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大. 在面对对象编程语言中,多态算是一种泛化机 ...

  6. Java IO学习笔记五:BIO到NIO

    作者:Grey 原文地址: Java IO学习笔记五:BIO到NIO 准备环境 准备一个CentOS7的Linux实例: 实例的IP: 192.168.205.138 我们这次实验的目的就是直观感受一 ...

  7. Java笔记--泛型总结与详解

    泛型简介: 在泛型没有出来之前,编写存储对象的数据结构是很不方便的.如果要针对每类型的对象写一个数据结构,     则当需要将其应用到其他对象上时,还需要重写这个数据结构.如果使用了Object类型, ...

  8. Java笔记2 : 泛型的体现,及其上限、下限、通配符

    Java中的泛型是在jdk5.0引入的,语法不难,但是需要注意的细节有很多,这里写一下备忘. 首先是最简单的泛型类,泛型方法,泛型接口: //泛型接口的定义 interface MyInter< ...

  9. Java笔记14:泛型初探

    一.泛型简介 泛型是从Java SE 1.5开始出现的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数.这种参数类型可以用在类.接口和方法的创建中,分别称为泛型类.泛型接口.泛 ...

  10. java笔记整理

    Java 笔记整理 包含内容     Unix Java 基础, 数据库(Oracle jdbc Hibernate pl/sql), web, JSP, Struts, Ajax Spring, E ...

随机推荐

  1. 使用git克隆项目、从dev分支上更新代码以及将代码提交到Coding(或GitHub)上面

    本教程的目的: 这是个crm项目中,有两个分支一个是master 和 dev ,master主分支,不允许提交代码,我要拉去dev分支上最新的代码,并将修改后的项目,在推送到dev分支上. 一. 1. ...

  2. appium+python测试app使用相对坐标定位元素

    我们获取到的是绝对坐标,如果换一个屏幕分辨率不同的手机那这个坐标自然会发生变化,要实现不同手机均能实现点击同一控件自然要用到相对坐标了,具体方法如下: 1.获取当前空间的绝对坐标(x1,y1),开启指 ...

  3. C++ Primer 笔记——lambda表达式

    1.一个lambda表达式表示一个可调用的代码单元,可以理解为一个未命名的内联函数,但是与函数不同,lambda表达式可能定义在函数内部.其形式如下: [capture list] (paramete ...

  4. 论文阅读笔记三十七:Grid R-CNN(CVPR2018)

    论文源址:https://arxiv.org/abs/1811.12030 开源代码:未公开 摘要 本文提出了目标检测网络Grid R-CNN,其基于网格定位机制实现准确的目标检测.传统方法主要基于回 ...

  5. 步步为营103-ZTree 二级联动

    1:添加引用 <%--流程类别多选--引用js和css文件--开始--%> <link rel="stylesheet" href="../css/zT ...

  6. WCF三种通信方式

    一.概述 WCF在通信过程中有三种模式:请求与答复.单向.双工通信.以下我们一一介绍. 二.请求与答复模式 描述: 客户端发送请求,然后一直等待服务端的响应(异步调用除外),期间处于假死状态,直到服务 ...

  7. CSS常见Bugs及解决方案列表

    以下实例默认运行环境都为Standard mode 如何在IE6及更早浏览器中定义小高度的容器? 方法: #test{overflow:hidden;height:1px;font-size:0;li ...

  8. 计算1至n中数字X出现的次数【math】

    转自: nailperry 一.1的数目 编程之美上给出的规律: 1. 如果第i位(自右至左,从1开始标号)上的数字为0,则第i位可能出现1的次数由更高位决定(若没有高位,视高位为0),等于更高位数字 ...

  9. nginx 域名泛解析

    部分应用场景下要求服务器根据客户输入的二级域名地址自动访问不同的页面,比如一个服务器放置了不同的业务,商城.官网等多个业务,又不想一个个配置server, 网站目录结构入戏: html 网站根目录 m ...

  10. Zabbix微信报警触发

    (1)         企业应用-创建应用 1.除了对个人添加微信报警之外,还可以添加不同管理组,接受同一个应用推送的消息, 成员账号,组织部门ID,应用Agent ID,CorpID和Secret, ...