一、简单的泛型类和接口

当指定一个泛型类时,类的声明则包括一个或多个类型参数,这些参数被放入在类名后面的一对尖括号内。

示例一:

package cn.generic.example;

public class GenericMemoryCell <AnyType>{

    public AnyType read() {

        return storedValue;
} public void write(AnyType x) { storedValue=x;
} private AnyType storedValue; }

GenericMemoryCell有一个类型参数。在这个例子中对类型参数没有明显的限制,所以用户可以创建像GenericMemoryCell<String>和GenericMemoryCell<Integer>类声明内部,我们可以声明泛型类型的域和使用泛型类型作为参数或返回类型的方法。比如,类GenericMemoryCell<String>的write方法需要一个String类型的参数。如果传递其它参数那将产生一个编译错误。

同时也可以声明接口是泛型的。

示例二:

package cn.generic.example;

public interface Comparable <AnyType>{

    public int compareTo(AnyType other);

}

在Java5以前,Comparable接口不是泛型,而它的comparaTo()方法需要一个Object作为参数。于是,传递到compareTo方法的任何引用变量即使不是一个合理的类型也都会编译,而只是在运行时报告ClassCastException错误。在Java5中Comparable接口是泛型的。

再比如以我目前用到的ORM框架MyBatis-Plus,其中的BaseMapper也是泛型接口,如下图所示:

二、自动装箱和拆箱

什么是装箱和拆箱?

一句话概括:装箱就是自动将基本数据类型转换为包装器类型;拆箱就是  自动将包装器类型转换为基本数据类型。

示例三(可与<数据结构与算法分析>读书笔记--实现泛型构件pre-Java5 中的示例三代码进行比较):

package cn.generic.example;

public class BoxingDemo {

    public static void main(String[] args) {

        GenericMemoryCell<Integer> m = new GenericMemoryCell<Integer>();

        m.write(37);

        int val = m.read();

        System.out.println("Contents are:"+val);
}
}

三、菱形运算符

以上面的示例三代码中的GenericMemoryCell<Integer> m = new GenericMemoryCell<Integer>()来说,有些烦人,因为既然m是GenericMemoryCell<Integer>类型的,显然创建的对象也必须是GenericMemoryCell<Integer>类型的,任何其他类型的参数都会产生编译错误。Java7增加了一种新的语言特征,称为菱形运算符。

可以将GenericMemoryCell<Integer> m = new GenericMemoryCell<Integer>()改写为GenericMemoryCell<Integer> m = new GenericMemoryCell<>()

示例四:

package cn.generic.example;

public class BoxingDemo {

    public static void main(String[] args) {

        GenericMemoryCell<Integer> m = new GenericMemoryCell<>();
m.write(5);
int val = m.read();
System.out.println("Contents are:"+val);
}
}

四、带有限制的通配符

带限制的通配符,通常有两种表现形式:

  • ? extends E

  • ? super E

使用原则可遵循PECS原则,其实就是四个单词的组合。

PECS — producer-extends, consumer-super

翻译过来就是生产者继承,消费者使用。

五、泛型static方法

有时候特定类型很重要,或许是因为下面几个原因:

(1)特定类型用作返回类型;

(2)该类型用在多于一个的参数类型中;

(3)该类型用于声明一个局部变量。

如果是这样,那么,必须要声明一种带很多类型参数的显式泛型方法。

示例五:

package cn.generic.example;

public class GenericStaticExample {

  public static <AnyType> boolean contains(AnyType[]arr, AnyType x) {

     for(AnyType val:arr)

             if(x.equals(val)) 

                 return true;

         return false;

  }

}

上面显示是一种泛型static方法,该方法对值x在数组arr中进行一系列查找。通过使用一种泛型方法,代替使用Object作为参数的非泛型方法,当在Shape对象的数组中查找Apple对象时我们能够得到编译时错误。

泛型方法特别像是泛型类,因为类型参数表使用相同的语法。在泛型方法中的类型参数位于返回类型之前。

六、类型限界

示例六(在一个数组中找出最大元的泛型static方法,以例说明类型参数的限界)

package cn.generic.example;

public class TypeLimitExample {

      public static <AnyType extends Comparable<? super AnyType>> AnyType findMax(AnyType[] arr) {

          int maxIndex = 0;

          for (int i = 0; i < arr.length; i++) 

              if(arr[i].compareTo(arr[maxIndex])>0)
maxIndex = i; return arr[maxIndex]; } }

七、类型擦除

泛型在很大程度上是Java语言中的成分而不是虚拟机中的结构。泛型类可以由编译器通过所谓的类型擦除过程而转为非泛型类。这样,编译器就生成一种与泛型类同名的原始类,但是类型参数都被删去了。类型变量由它们的类型限界来代替,当一个具有擦除返回类型的泛型方法被调用时,一些特性被自动插入。如果使用一个泛型类而不带泛型参数,那么使用的是原始类。

类型擦除的一个重要推论是,所生成的代码与程序员在泛型之前所写的代码并没有太多的差异,而且事实上运行的也并不快。其显著优点在于,程序员不必把一些类型转换放到代码中,编译器将进行重要的类型检验。

八、对于泛型的限制

对于泛型类型有许多限制。由于类型擦除的原因,这里列出的每一个限制都是必须要遵守的。

1.基本类型

基本类型不能用做类型参数。因此,GenericMemoryCell<int>是非法的。我们必须要使用包装类。

2.instanceof检测

instanceof检测和类型转换工作只对原始类型进行。

示例七:

package cn.generic.example;

public class InstanceOfCheckExample {

    public static void main(String[] args) {
GenericMemoryCell<Integer> celll = new GenericMemoryCell<>();
celll.write(4); Object cell = celll; GenericMemoryCell<String> cell2 = (GenericMemoryCell<String>) cell;
String s = cell2.read(); } }

这里的类型转换在运行时是成功的,因为所有的类型都是GenericMemoryCell。但在最后一行,由于对read的调用企图返回一个String对象从而产生一个运行时错误。

结果,类型转换将产生一个警告,而对应的instanceof检测是非法的。

3.static的语境

在一个泛型类中,static方法和static域均不可引用类的类型变量,因为在类型擦除后类型变量就不存在了。另外,由于实际上只存在一个原始类,因此static域在该类的诸泛型实例之间是共享的。

4.泛型类型实例化

不能创建一个泛型类型的实例。如果T是一个类型变量

    T obj = new T();

则语句是非法的。T由它的限界代替,这可能是Object或抽象类,因此对new的调用没有意义。

5.泛型数组对象

也不能创建一个泛型数组。如果T是一个类型变量

    T[] arr = new T[10];

则语句是非法的。T将由它的限界代替,这可能是Object T,于是(由类型擦除产生的)对T[]的类型转换将无法进行,因为Object[] IS-NOT-A T[]。由于我们不能创建泛型对象的数组,因此一般说来我们必须创建一个擦除类型的数组,然后使用类型转换。这种类型转换将产生一个关于未检验的类型转换的编译警告。

6.参数化类型的数组

参数化类型的数组的实例化是非法的。

        GenericMemoryCell<String> [] arr1 = new GenericMemoryCell<>[10];
GenericMemoryCell<Double> cell = new GenericMemoryCell<>();
cell.write(4.5);
Object[] arr2 = arr1;
arr2[0] = cell;
String s = arr1[0].read();

正常情况下,我们认为第四行的赋值会生成一个ArrayStoreException,因为赋值的类型有错误。可是,在类型擦除之后,数组的类型为GenericMemoryCell[],而加到数组中的对象也是GenericMemoryCell,因此不存在ArrayStoreException异常。于是,该段代码没有类型转换,它最终将在第五行产生一个ClassCastException异常,这正是泛型应该避免的情况。

示例代码已经上传到我的Github:https://github.com/youcong1996/The-Data-structures-and-algorithms/tree/master/Introduction

<数据结构与算法分析>读书笔记--利用Java5泛型实现泛型构件的更多相关文章

  1. <数据结构与算法分析>读书笔记--函数对象

    关于函数对象,百度百科对它是这样定义的: 重载函数调用操作符的类,其对象常称为函数对象(function object),即它们是行为类似函数的对象.又称仿函数. 听起来确实很难懂,通过搜索我找到一篇 ...

  2. <数据结构与算法分析>读书笔记--最大子序列和问题的求解

    现在我们将要叙述四个算法来求解早先提出的最大子序列和问题. 第一个算法,它只是穷举式地尝试所有的可能.for循环中的循环变量反映了Java中数组从0开始而不是从1开始这样一个事实.还有,本算法并不计算 ...

  3. <数据结构与算法分析>读书笔记--运行时间计算

    有几种方法估计一个程序的运行时间.前面的表是凭经验得到的(可以参考:<数据结构与算法分析>读书笔记--要分析的问题) 如果认为两个程序花费大致相同的时间,要确定哪个程序更快的最好方法很可能 ...

  4. <数据结构与算法分析>读书笔记--数学知识复习

    数学知识复习是<数据结构与算法分析>的第一章引论的第二小节,之所以放在后面,是因为我对数学确实有些恐惧感.不过再怎么恐惧也是要面对的. 一.指数 基本公式: 二.对数 在计算机科学中除非有 ...

  5. <数据结构与算法分析>读书笔记--实现泛型构件pre-Java5

    面向对象的一个重要目标是对代码重用的支持.支持这个目标的一个重要的机制就是泛型机制:如果除去对象的基本类型外,实现的方法是相同的,那么我们就可以用泛型实现来描述这种基本的功能. 1.使用Object表 ...

  6. <数据结构与算法分析>读书笔记--运行时间中的对数及其分析结果的准确性

    分析算法最混乱的方面大概集中在对数上面.我们已经看到,某些分治算法将以O(N log N)时间运行.此外,对数最常出现的规律可概括为下列一般法则: 如果一个算法用常数时间(O(1))将问题的大小削减为 ...

  7. <数据结构与算法分析>读书笔记--要分析的问题

    通常,要分析的最重要的资源就是运行时间.有几个因素影响着程序的运行时间.有些因素(如使用编译器和计算机)显然超出了任何理论模型的范畴,因此,虽然它们是重要的,但是我们在这里还是不能考虑它们.剩下的主要 ...

  8. <数据结构与算法分析>读书笔记--模型

    为了在正式的构架中分析算法,我们需要一个计算模型.我们的模型基本上是一台标准的计算机,在机器中指令被顺序地执行.该模型有一个标准的简单指令系统,如加法.乘法.比较和赋值等.但不同于实际计算机情况的是, ...

  9. <数据结构与算法分析>读书笔记--递归

    一.什么是递归 程序调用自身的编程技巧称为递归( recursion).递归做为一种算法在程序设计语言中广泛应用. 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的 ...

随机推荐

  1. Spring IOC 容器源码分析

    声明!非原创,本文出处 Spring 最重要的概念是 IOC 和 AOP,本篇文章其实就是要带领大家来分析下 Spring 的 IOC 容器.既然大家平时都要用到 Spring,怎么可以不好好了解 S ...

  2. ScheduledExecutorService的两种方法

    开发中,往往遇到另起线程执行其他代码的情况,用java定时任务接口ScheduledExecutorService来实现. ScheduledExecutorService是基于线程池设计的定时任务类 ...

  3. drop,truncate,delete 区别

    一.SQL中的语法 1.drop table 表名称                         eg: drop table  dbo.Sys_Test   2.truncate table 表 ...

  4. linux下lamp环境修改网站根目录

    Apache默认的网站目录是在/var/www/html,我们现在要把网站目录更改成 /var/www 目录下,操作如下: 1.修改httpd,conf文件 vi /etc/httpd/conf/ht ...

  5. CSS 水平居中和垂直居中

    1.水平居中——行内元素 text-align: center; 2.水平居中——定宽块状元素 margin: auto,满足定宽和块状两个条件的元素是可以通过设置“左右margin”值为“auto” ...

  6. html基础标签下

    1.1 单标签 ◆注释标签   ctrl+/ ◆水平线标签   <hr> ◆换行标签   <br> 1.2 双标签 ◆段落标签    <p></p> ◆ ...

  7. h5向上翻页图标晃动动画,css固定h5向上翻页图标在页面上

    //html结构<div class='upImg'><div> //css .upImg { background-image: url(../images/01.png); ...

  8. Nodejs编译Native Code:使用C++构建工具npm

    Nodejs的很多NPM包需要本地编译,通常是C++写的代码,例如图像处理模块等. 这是如果生产环境没有安装Visual Studio 2015等开发工具,通常会编译失败,发现了一个npm专门干这事儿 ...

  9. android控件跟随手势滑动改变位置

    要求:1.通过手指移动来拖动图片   2.控制图片不能超出屏幕显示区域 技术点:1.MotionEvent处理2.对View进行动态定位(layout) activity_main.xml: < ...

  10. [iOS] UICollectionView实现图片水平滚动

    最新更新: 简单封装了一下代码,参考新文章:UICollectionView实现图片水平滚动 先简单看一下效果: 新博客:http://wossoneri.github.io 准备数据 首先先加入一些 ...