一、泛型初衷

Java集合不会知道我们需要用它来保存什么类型的对象,所以他们把集合设计成能保存任何类型的对象,只要就具有很好的通用性。但这样做也带来两个问题:

  –集合对元素类型没有任何限制,这样可能引发一些问题:例如想创建一个只能保存Sting对象的集合,但程序也可以轻易地将int对象“丢”进去,所以可能引发异常。
  –由于把对象“丢进”集合时,集合丢失了对象的状态信息,集合只知道它盛装的是Object,因此取出集合元素后通常还需要进行强制类型转换。这种强制类型转换既会增加编程的复杂度、也可能引发ClassCastException。

二、在集合中使用泛型

在集合中使用泛型后带来如下优势

  –程序再也不能“不小心”把其他对象“丢进”strList集合中;

  –程序更加简洁,集合自动记住所有集合元素的数据类型,从而无需对集合元素进行强制类型转换。
下面的代码中,"不小心"把一个Integer对象"丢进"了集合。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.*;
 
public class ListErr
{
    public static void main(String[] args)
    {
        // 创建一个只想保存字符串的List集合
        List strList = new ArrayList();
        strList.add("疯狂Java讲义");
        strList.add("疯狂Android讲义");
        // "不小心"把一个Integer对象"丢进"了集合
        strList.add(5);     // ①
        strList.forEach(str -> System.out.println(((String)str).length())); // ②
    }
}
1
"不小心"把一个Integer对象"丢进"了集合
1
②引发ClassCastException异常。

三、什么是泛型

  所谓泛型:就是允许在定义类、接口指定类型形参,这个类型形参在将在声明变量、创建对象时确定(即传入实际的类型参数,也可称为类型实参)。

  JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。

四、泛型的“菱形”语法 <>

  如下代码:

1
2
List<String> books = new ArrayList<String>();
Map<String,Integer> books = new ArrayList<String,Integer>();

  在java 7 以前,<>中的粗体字代码都是必须的,但是现在可以不带粗体字代码。Java自动推断出ArrayList的<>里应该是String还是String,Integer。

  现在改为:

1
2
List<String> books = new ArrayList<>();
Map<String,Integer> books = new ArrayList<>();

第一段和第二段代码是完全等价的。

泛型的简单应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.*;
 
public class DiamondTest
{
    public static void main(String[] args)
    {
        // Java自动推断出ArrayList的<>里应该是String
        List<String> books = new ArrayList<>();
        books.add("疯狂Java讲义");
        books.add("疯狂Android讲义");
        // 遍历books集合,集合元素就是String类型
        books.forEach(ele -> System.out.println(ele.length()));
        // Java自动推断出HashMap的<>里应该是String , List<String>
        Map<String , List<String>> schoolsInfo = new HashMap<>();
        // Java自动推断出ArrayList的<>里应该是String
        List<String> schools = new ArrayList<>();
        schools.add("斜月三星洞");
        schools.add("西天取经路");
        schoolsInfo.put("孙悟空" , schools);
        // 遍历Map时,Map的key是String类型,value是List<String>类型
        schoolsInfo.forEach((key , value) -> System.out.println(key + "-->" + value));
    }
}

  

五、深入泛型

1.定义泛型接口、类

  定义Apple类时使用了泛型声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 定义Apple类时使用了泛型声明
public class Apple<T>
{
    // 使用T类型形参定义实例变量
    private T info;
    public Apple(){}
    // 下面方法中使用T类型形参来定义构造器
    public Apple(T info)
    {
        this.info = info;
    }
    public void setInfo(T info)
    {
        this.info = info;
    }
    public T getInfo()
    {
        return this.info;
    }
    public static void main(String[] args)
    {
        // 由于传给T形参的是String,所以构造器参数只能是String
        Apple<String> a1 = new Apple<>("苹果");
        System.out.println(a1.getInfo());
        // 由于传给T形参的是Double,所以构造器参数只能是Double或double
        Apple<Double> a2 = new Apple<>(5.67);
        System.out.println(a2.getInfo());
    }
}

2.从泛型派生子类

A1继承泛型类:

1
2
3
4
5
6
7
8
//使用泛型类时,为T形参传入String类类型
public class A1 extends Apple<String>{}//正确
 
//使用泛型类时,没有为T形参传入实际类型参数,这会产生警告:泛型检查警告,使用了未经检查或不安全的操作
public class A1 extends Apple{}//正确
 
//apple类不能跟类型形参
public class A1 extends Apple<T>{}//错误

继承Apple类,T被String代替。子类会继承到String getInfo()和void setInfo()两个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class A1 extends Apple<String>
{
    // 正确重写了父类的方法,返回值
    // 与父类Apple<String>的返回值完全相同
    public String getInfo()
    {
        return "子类" super.getInfo();
    }
    /*
    // 下面方法是错误的,重写父类方法时返回值类型不一致。从父类继承的应该是public String getinfo()
    public Object getInfo()
    {
        return "子类";
    }
    */
}

正确写法如下:

1
2
3
4
5
6
7
8
9
10
public class A2 extends Apple
{
    // 重写父类的方法
    public String getInfo()
    {
        // super.getInfo()方法返回值是Object类型,
        // 所以加toString()才返回String类型
        return super.getInfo().toString();
    }
}

3.并不存在泛型类

  虽然可以把ArrayList<String>类当成ArrayList的子类,事实上ArrayList<String>类也确实是一种特殊的ArrayList类,这个ArrayList<String>对象只能添加String对象作为集合元素。但实际上,系统并没有为ArrayList<String>生成新的class文件,而且也不会把ArrayList<String>当成新类来处理。
  实际上,泛型对其所有可能的类型参数,都具有同样的行为,从而可以把相同的类被当成许多不同的类来处理。与此完全一致的是,类的静态变量和方法也在所有的实例间共享,所以在静态方法、静态初始化、或者静态变量的声明和初始化中不允许使用类型形参。
  系统中并不会真正生成泛型类,所以instanceof运算符后不能使用泛型类。

 六、类型通配符

1
2
3
4
public void test(List<Object> c)
{
  for(int i=0;i<c.size();i++)<br data-filtered="filtered">  {<br data-filtered="filtered">    Syso(c.get(i));<br data-filtered="filtered">  }<br data-filtered="filtered">
}

这段代码看上去没有任何问题,方法的声明也没有任何问题。但是问题在于:调用该方法传入的实际参数的值。例如:

1
2
//创建一个List<String>对象
List<String> strList = new ArrayList<>();<br data-filtered="filtered">//将strList作为参数调用test<br data-filtered="filtered">test(strList);

编译上面的程序,发生错误。

1
无法将Test中的test(java.util.list<java.lang.Object>)应用于java.util.list<java.lang.String>

这说明List<String>对象不能被当成List<Object>对象使用,也就是说:List<String>类并不是List<Object>类的子类。

此外,数组和泛型有所不同:假设Foo是Bar的一个子类型(子类或者子接口),那么Foo[]依然是Bar[]的自类型;但G<Foo>不是G<Bar>的子类型。

七、?的用法

  为了表示各种泛型List的父类,我们需要使用类型通配符,类型通配符是一个问号(?),将一个问号作为类型实参传给List集合,写作:List<?>(意思是未知类型元素的List)。这个问号(?)被称为通配符,它的元素类型可以匹配任何类型。

  在“六”中的程序,将

1
2
3
4
5
6
7
8
public void test(List<Object> c)
{
  for(int i=0;i<c.size();i++)
  {
    Syso(c.get(i));
  }
 
}

改为:

1
2
3
4
5
6
7
8
public void test(List<?> c)
{
  for(int i=0;i<c.size();i++)
  {
    Syso(c.get(i));
  }
 
}

再次编译就没有了错误。

这里的?可谓什么都可以表示,是不是给它的权力太大了!!   当然我们有自己的解决办法:设定类型通配符的上限

  使用List<?>这种形式是,即表明这个List集合可以是任何泛型List的父类。但还有一种特殊的情形,我们不想这个List<?>是任何泛型List的父类,只想表示它是某一类泛型List的父类。 
  我们需要一种泛型表示方法,它可以表示所有Shape泛型List的父类,为了满足这种需求,Java泛型提供了被限制的泛型通配符。被限制的泛型通配符的如下表示:List<? extends Shape> 
1
2
3
4
5
// 定义一个抽象类Shape
public abstract class Shape
{
    public abstract void draw(Canvas c);
}

  

1
2
3
4
5
6
7
8
9
/ 定义Shape的子类Circle
public class Circle extends Shape
{
    // 实现画图方法,以打印字符串来模拟画图方法实现
    public void draw(Canvas c)
    {
        System.out.println("在画布" + c + "上画一个圆");
    }
}

  

1
2
3
4
5
6
7
8
9
// 定义Shape的子类Rectangle
public class Rectangle extends Shape
{
    // 实现画图方法,以打印字符串来模拟画图方法实现
    public void draw(Canvas c)
    {
        System.out.println("把一个矩形画在画布" + c + "上");
    }
}

上面定义了三个形状类,Sharp抽象父类,Circle类和Rectangle类继承了抽象类Sharp。

下面定义一个Canvas类,该画布类不同的形状。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.*;
 
public class Canvas
{
    // 同时在画布上绘制多个形状
    public void drawAll(List< Shape> shapes)
    {
        for (Shape s : shapes)
        {
            s.draw(this);
        }
    }
 
    public static void main(String[] args)
    {
        List<Circle> circleList = new ArrayList<Circle>();
        Canvas c = new Canvas();
        // 由于List<Circle>并不是List<Shape>的子类型,
        // 所以下面代码引发编译错误
        c.drawAll(circleList);
    }
}

修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.*;
 
public class Canvas
{
    // 同时在画布上绘制多个形状,使用被限制的泛型通配符
    public void drawAll(List<? extends Shape> shapes)
    {
        for (Shape s : shapes)
        {
            s.draw(this);
        }
    }
 
    public static void main(String[] args)
    {
        List<Circle> circleList = new ArrayList<Circle>();
        Canvas c = new Canvas();
        // 由于List<Circle>并不是List<Shape>的子类型,但是使用了通配符
        // 所以下面代码正确
        c.drawAll(circleList);
    }
}

这段代码就没有了错误。

  Java泛型不仅允许在使用通配符形参时设定类型上限,也可以在定义类型形参时设定上限,用于表示创给该类型形参的实际类型必须是该上限类型,或是该上限类型的子类。 例如:
1
2
3
4
5
6
7
8
9
10
11
12
public class Apple<T extends Number>
{
    T col;
    public static void main(String[] args)
    {
        Apple<Integer> ai = new Apple<>();
        Apple<Double> ad = new Apple<>();
        // 下面代码将引起编译异常,下面代码试图把String类型传给T形参
        // 但String不是Number的子类型,所以引发编译错误
        Apple<String> as = new Apple<>();       // ①
    }
}

八、泛型方法

  如果定义类、接口是没有使用类型形参,但定义方法时想自己定义类型形参,这也是可以的,JDK1.5还提供了泛型方法的支持。

  泛型方法的语法格式为:
    修饰符 <T , S> 返回值类型 方法名(形参列表)
    {
      //方法体...
    }
  泛型方法的方法签名比普通方法的方法签名多了类型形参声明,类型形参声明以尖括号括起来,多个类型形参之间以逗号(,)隔开,所有类型形参声明放在方法修饰符和方法返回值类型之间。 
  

  与类、接口中使用泛型参数不同的是,方法中的泛型参数无需显式传入实际类型参数,因为编译器根据实参推断类型形参的值。它通常推断出最直接的类型参数。 

九、泛型方法与类型通配符的区别

  大时候都可以使用泛型方法来代替类型通配符。
  泛型方法允许类型形参被用来表示方法的一个或多个参数之间的类型依赖关系,或者方法返回值与参数之间的类型依赖关系。如果没有这样的类型依赖关系,不应该使用泛型方法。
十、设定通配符的下限
  Java集合框架中的TreeSet<E>有一个构造器也用到了这种设定通配符下限的语法,如下所示:

    TreeSet(Comparator<? super E> c)

十一、擦除与转换
  在严格的泛型代码里,带泛型声明的类总应该带着类型参数。但为了与老的Java代码保持一致,也允许在使用带泛型声明的类时不指定类型参数。如果没有为这个泛型类指定类型参数,则该类型参数被称作一个raw type(原始类型),默认是该声明该参数时指定的第一个上限类型。
  当把一个具有泛型信息的对象赋给另一个没有泛型信息的变量时,则所有在尖括号之间的类型信息都被扔掉了。比如说一个List<String>类型被转换为List,则该List对集合元素的类型检查变成了成类型变量的上限(即Object),这种情况被为擦除。

……待续

Java 泛型 三的更多相关文章

  1. Java泛型三:Java泛型详解

    原文地址https://www.cnblogs.com/lzq198754/p/5780426.html 1.为什么需要泛型 泛型在Java中有很重要的地位,网上很多文章罗列各种理论,不便于理解,本篇 ...

  2. 一起学 Java(三) 集合框架、数据结构、泛型

    一.Java 集合框架 集合框架是一个用来代表和操纵集合的统一架构.所有的集合框架都包含如下内容: 接口:是代表集合的抽象数据类型.接口允许集合独立操纵其代表的细节.在面向对象的语言,接口通常形成一个 ...

  3. 【Java心得总结三】Java泛型上——初识泛型

    一.函数参数与泛型比较 泛型(generics),从字面的意思理解就是泛化的类型,即参数化类型.泛型的作用是什么,这里与函数参数做一个比较: 无参数的函数: public int[] newIntAr ...

  4. Effective Java 第三版——29. 优先考虑泛型

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  5. 深入分析Java反射(三)-泛型

    前提 Java反射的API在JavaSE1.7的时候已经基本完善,但是本文编写的时候使用的是Oracle JDK11,因为JDK11对于sun包下的源码也上传了,可以直接通过IDE查看对应的源码和进行 ...

  6. Java:泛型基础

    泛型 引入泛型 传统编写的限制: 在Java中一般的类和方法,只能使用具体的类型,要么是基本数据类型,要么是自定义类型.如果要编写可以应用于多种类型的代码,这种刻板的限制就会束缚很多! 解决这种限制的 ...

  7. 【Java心得总结四】Java泛型下——万恶的擦除

    一.万恶的擦除 我在自己总结的[Java心得总结三]Java泛型上——初识泛型这篇博文中提到了Java中对泛型擦除的问题,考虑下面代码: import java.util.*; public clas ...

  8. Java—泛型

    泛型是JDK 5 中引入的一个新特性,泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型.泛型本质是参数化类型,也就是所操作的数据类型指定为一个参数. 假定我们有这样一个需求: ...

  9. Java泛型学习笔记 - (七)浅析泛型中通配符的使用

    一.基本概念:在学习Java泛型的过程中, 通配符是较难理解的一部分. 主要有以下三类:1. 无边界的通配符(Unbounded Wildcards), 就是<?>, 比如List< ...

随机推荐

  1. Spider-Python爬虫之聚焦爬虫与通用爬虫的区别

    为什么要学习爬虫? 学习爬虫,可以私人订制一个搜索引擎. 大数据时代,要进行数据分析,首先要有数据源. 对于很多SEO从业者来说,从而可以更好地进行搜索引擎优化. 什么是网络爬虫? 模拟客户端发送网络 ...

  2. Python关于函数作为返回值的理解(3分钟就看完了)

    话不多说,直接看例子,上代码: def line_conf(): def line(x): return 2 * x + 1 return line #return a function object ...

  3. loaction.reload(false)和location.reload(true) js发起请求

    loaction.reload(false)和location.reload(true)差别: loaction.reload(false) 先判断页面有没修改,有的话就从服务器下载页面,没有就直接从 ...

  4. Thawte SSL Web Server

      Thawte SSL Web Server ,需要验证域名所有权和申请单位信息,属于企业验证(OV)型SSL证书,提供40位/56位/128位,最高支持256位的自适应加密.被2048位的根证书签 ...

  5. [luoguP1040] 加分二叉树(DP)

    传送门 区间DP水题 代码 #include <cstdio> #include <iostream> #define N 41 #define max(x, y) ((x) ...

  6. poj3440--Coin Toss(几何上的概率)

    Coin Toss Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 3946   Accepted: 1076 Descrip ...

  7. Xterm256终端颜色的名称

    hi x016_Grey0 ctermfg=16 guifg=#000000 "rgb=0,0,0 hi x017_NavyBlue ctermfg=17 guifg=#00005f &qu ...

  8. Ubuntu12.04之SSH

    Ubuntu 12.04 关于SSH的知识 (1)安装完ubuntu系统12.04. (2)查看网络配置,输入命令ip addr后,显示有IP地址. (3)使用SSH终端工具Xshell连接系统,发现 ...

  9. DELPHI IDFTP

    FTP是一个标准协议,它是在计算机和网络之间交换文件的最简单的方法. FTP也是应用TCP/IP协议的应用协议标准.FTP通常于将作者的文件上传至服务器,或从服务器上下传文件的一种普遍的使用方式作为用 ...

  10. [bzoj3238][Ahoi2013]差异_后缀数组_单调栈

    差异 bzoj-3238 Ahoi-2013 题目大意:求任意两个后缀之间的$LCP$的和. 注释:$1\le length \le 5\cdot 10^5$. 想法: 两个后缀之间的$LCP$和显然 ...