Java基础教程:泛型基础

引入泛型

传统编写的限制:

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

解决这种限制的三种方法:

1.多态:将方法的参数类型设为基类,那么该方法就可以接收从这个基类导出的任何类作为参数。

class Primary{} //定义基类

class Test()
{
public void f(Primary p)
{...}
}

2.方法的参数使用接口:任何实现了该接口的类都可以满足该方法。

interface Primary{} //定义接口

class Test()
{
public void f(Primary p) //实现了该接口的所有类都可以作为参数
{...}
}

3.使用泛型。

泛型的说明

  泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

  泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

  泛型实现了参数化类型的概念,使代码可以应用于某种不具体的类,而不是具体的一个类或者接口。简单说就是使代码可以适用于广泛的类型

简单泛型

使用泛型预定义参数类型

  

说明:基本数据类型无法作为类型参数

在使用时具体化类型参数

  也就是在声明的时候先不用去管具体的类型,用一个参数代替,在使用的时候,这个参数需要具体化。

  

小结

  泛型类最主要的使用是应用在集合中,代码封装了一个ArrayList,这样我们在编写类的时候,就不会受限于具体的类,因为具体使用的类型是在初始化对象的时候才指定的。
我们为什么要这样呢?如果这个实现类不用泛型,如果处理多种类型数据的时候,就要编写多个实现类,来针对处理。

元组

简单元组实例

  

通过继承机制来实现长度更长的元组

  

泛型接口

说明:

  泛型也可以应用于接口,类和接口的类型参数应该保持一致,都是T或者其他。

  泛型接口最常用的一个用法是实现Iterable接口,实现迭代方法!

实例:

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.Random; public class RandomList<T> implements Iterable<T> {
private ArrayList<T> storage = new ArrayList<T>();
private Random rand = new Random(new Date().getTime()); public void add(T item) {
storage.add(item);
} public T select() {
return storage.get(rand.nextInt(storage.size()));
} /* 实现Iterable接口 */
public Iterator<T> iterator() {
return storage.iterator();
}
}

泛型方法

说明:

  是否拥有泛型方法和其是否是泛型类并无直接关系,也就是普通类也可以有泛型方法。其次,在使用泛型方法的时候,不需要明确指定参数的值。

实例:

  比如,实例中,不需要明确知道你个参数的类型。

  

  当然,也可以显示的指明类型参数。

  

  泛型方法和可变参数列表可以很好的共存
  

类型变量的限定

说明

  1.添加限定来让类型变量具有特定功能。

  

  2.可以有多个限定(接口和类都是可以的),但要注意顺序。

  

为什么关键字不用implements呢?毕竟Comparable是一个接口:

  <T extends BoundingType>

  这句代码表示T应该是绑定类型(BoundingType)的子类型。T和绑定类型可以是类,也可以是接口。
  若T是接口,那么方法调用时传进来的是T的实现类也是可以的。extends更接近于子类的概念,所以选用extends。

神秘的擦除

问题:

  
  我们很可能会认为c1!=c2,结果为false,因为c1不能装入Integer,c2也不能装入String。

说明:

  在泛型代码内部,无法获取任何有关泛型参数类型的信息,也就是你无法获得那个具体的类型是什么,你仅仅可以获知的诸如类型参数标识符和泛型边界这类的信息

  这意味者当你在使用泛型的时候,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。因此问题中c1==c2,为true。这两种形式都被擦除成为它们的“原生”类型,即List.

原始类型与擦除:

  虚拟机没有泛型类型对象,所有对象都属于普通类。无论何时定义了一个泛型类型,都自动提供了一个相应的原始类型,将泛型类型还原成原始类型的过程,称为擦除
  原始类型的名字就是删去类型参数后的泛型类型名。擦除类型变量,并替换为限定类型(无限定类型的变量默认用Object)。

  

  左图的原始类型变为:public class Test<Object>,所有T都变成Object 右图变为:public class Test<Comparable>,所有T都变成Object。   

说明:

  这样就不难理解,为什么,加上限定边界后,我们就可以调用obj.compareTo()了,因为擦除类型变量后,T都被还原成Comparable,效果就是任何实现了Comparable的子类都可以调用Test对象的compare()方法。如果泛型是在Java 1.0就出现的,那么这个特性将不会使用擦除来实现——它将使用具体化,使类型参数保持为第一类的实体,因此你就能够在类型参数上执行基于类型的语言操作和反射操作。

再一次强调:

Test<Cat> test = new Test<Cat>(); 

  看起来class Test应该知道现在工作于Cat之上,而泛型语法也在强烈暗示,在整个类中的各个地方,类型T都在被替换。但是事实并非如此,无论何时,当你在编写这个类的代码时,必须时刻提醒自己:它仅仅只是一个Object
擦除的补偿。

再谈边界

  1.边界使得你可以在用于泛型的类型参数上设置限制条件。尽管这使得你可以强制规定泛型可以应用的类型,但是其潜在的一个更重要的效果是你可以按照自己的边界类型来调用方法。
  2.因为擦除移除了类型信息,所以,可以用无界泛型参数调用的方法只能是那些可以用Object调用的方法,那么你就可以用这些类型字节来调用方法。

通配符

说明:

    1.Java泛型是强制类型检测的,泛型类型的子类型互不相关。

    publicclass Test {
public static void main(String[] args) throws Exception{
List<Integer> listInteger =new ArrayList<Integer>();
List<String> listString =new ArrayList<String>();
printCollection(listInteger);
printCollection(listString);
}
public static void printCollection(Collection<Object> collection){
for(Object obj:collection){
System.out.println(obj);
}
}
} //Integer String都是Object的子类型,但是结果会报错,这就说明了泛型不考虑继承关系
The method printCollection(Collection<Object>) in the type GernericTest is not applicable for the arguments (List<Integer>)

  2.我们希望泛型能向普通类那样具有面向对象的一些特征:

    • 向上转型为一个泛型对象。
    • 向下转型为一个泛型对象。

  3.为了使泛型能具有面向对象的一些继承关系,Java引入了通配符的一些概念:无界通配符 ?

为了使泛型的子类型仍然具有相关性,可以直接使用无界通配符

  

使用通配符上界“? extends T”,来指定继承关系

  

  

说明:

  ? extends T 的本质上的实现是泛型的自动向上转型。

  使用通配符下界“?super T”

  使用方法和解释同上。

泛型表达式的翻译

  1.当程序调用泛型方法时,如果擦除了泛型返回类型,编译器插入类型转换。

 Pair<Employee> buddies = ...
Employee buddy = buddies.getFirst();
    • 擦除getFirst的返回类型后将返回Object类型,编译器自动插入Employee的强制类型转换。

    • 也就是说,编译其把这个方法调用翻译为两条虚拟指令:
      ◇ 对原始方法Pair.getFirest的调用。
      ◇ 将返回的Object类型,强制转换为Employee类型。
  2.当存取一个泛型域时也要插入强制类型转换。
    假设 Pair 类的first 域 和 second 域都是 公有的(这不是种好的编程风格, 但在java语法中,这是合法的)。 
    表达式: Employee buddy = buddies.first; 也会在结果字节码中插入强制类型转换; 

Java基础教程:泛型基础的更多相关文章

  1. Java基础教程——泛型

    泛型 Generics:泛型,愿意指"无商标的". 泛型,可以理解为"宽泛的数据类型",就是将类型由原来的具体的类型泛化. 泛型在建立对象时不指定类中属性的具体 ...

  2. Java基础教程——网络基础知识

    参考阅读[中国互联网发展史]:https://zhuanlan.zhihu.com/p/61602252 协议 计算机网络中的通信必须有一些约定,这些约定称为"通信协议". 通信协 ...

  3. php基础教程-必备基础知识

    PHP 脚本在服务器上执行. 您应当具备的基础知识 在继续学习之前,您需要对下面的知识有基本的了解: HTML CSS JavaScript 如果您希望首先学习这些项目,请在我们的 首页 访问这些教程 ...

  4. Python笔记_第五篇_Python数据分析基础教程_NumPy基础

    1. NumPy的基础使用涵盖如下内容: 数据类型 数组类型 类型转换 创建数组 数组索引 数组切片 改变维度 2. NumPy数组对象: NumPy中的ndarray是一个多维数组对象,该兑现共有两 ...

  5. cocos基础教程(4)基础概念介绍

    在Cocos2d-x-3.x引擎中,采用节点树形结构来管理游戏对象,一个游戏可以划分为不同的场景,一个场景又可以分为不同的层,一个层又可以拥有任意个可见的游戏节点(即对象,游戏中基本上所有的类都派生于 ...

  6. .Net程序员之Python基础教程学习----列表和元组 [First Day]

    一. 通用序列操作: 其实对于列表,元组 都属于序列化数据,可以通过下表来访问的.下面就来看看序列的基本操作吧. 1.1 索引: 序列中的所有元素的下标是从0开始递增的. 如果索引的长度的是N,那么所 ...

  7. React-Native基础教程

    React-Native牛刀小试仿京东砍啊砍砍到你手软 React-Native基础教程 *React-Native基础篇作者git *React-Native官方文档 *Demo 几个月前faceb ...

  8. 学习参考《Python基础教程(第3版)》中文PDF+英文PDF+源代码

    python基础教程ed3: 基础知识 列表和元组 字符串 字典 流程控制 抽象(参数 作用域 递归) 异常 魔术方法/特性/迭代器 模块/标准库 文件 GUI DB 网络编程 测试 扩展python ...

  9. Java基础教程(21)--泛型

    一.为什么使用泛型   泛型意味着编写的代码可以被很多不同类型的对象所重用.例如,我们不希望为存放String和Integer对象的集合设计不同的类.现在的ArrayList类可以存放任何类型的对象, ...

随机推荐

  1. 2. Trailing Zeros【easy】

    2. Trailing Zeros[easy] Write an algorithm which computes the number of trailing zeros in n factoria ...

  2. hud项目lcd调试过程的一些见解

    1.帧缓冲(FrameBuffer)设备驱动帧缓冲设备为标准的字符型设备,在Linux中主设备号29,定义在/include/uapi/linux/major.h中的FB_MAJOR,次设备号定义帧缓 ...

  3. RTT驱动实现步骤

    设备驱动实现步骤: 1. 按照RT-Thread对象模型,扩展一对象有两种方式: 1)定义自己的私有数据结构,然后赋值到RT-Thread设备控制空的user_data指针上: 2)从struct r ...

  4. Linux gdb调试器

    gdb的启动 --gdb 程序名 [corefile] --corefile是可选的,但能增强gdb的调试能力 --强调:启动gdb必须在编译命里加上"-g"参数,"-g ...

  5. Android 缓存详解目录

    1.http://www.cnblogs.com/lzrabbit/p/3734850.html 2.

  6. 第一百八十八节,jQuery,选项卡 UI

    jQueryUI,选项卡 UI 学习要点: 1.使用 tabs 2.修改 tabs 样式 3.tabs()方法的属性 4.tabs()方法的事件 5.tabs 中使用 on 选项卡(tab),是一种能 ...

  7. Servlet 处理日期

    使用 Servlet 的最重要的优势之一是,可以使用核心 Java 中的大多数可用的方法.本章将讲解 Java 提供的 java.util 包中的 Date 类,这个类封装了当前的日期和时间. Dat ...

  8. Hibernate一对多映射列表实例(使用xml文件)

    如果持久化类具有包含实体引用的列表(List)对象,则需要使用一对多关联来映射列表元素. 在这里,我们使用论坛应用场景,在论坛中一个问题有多个答案. 在这种情况下,一个问题可以有多个答案,每个答案可能 ...

  9. RAC中数据文件创建到了本地路径(非系统表空间) 使用rman转移

    环境: 11.2.0.1 + RHEL5.8 參考文档ID:1678747.1 1.模拟创建 一节点: SQL> create tablespace tdb datafile '/u02/app ...

  10. HDU2602Bone Collector 简单0-1背包

    Bone Collector Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) T ...