IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> [ XJTUSE ]JAVA语言进阶知识——9.3 泛型 -> 正文阅读

[Java知识库][ XJTUSE ]JAVA语言进阶知识——9.3 泛型

9.3 泛型

9.3.1 泛型的定义以及实例化

Java泛型( generic)等同于模板或参数多态,很多语言都支持泛型,如C++、Microsoft的C#,泛型是JDK 1.5引人的影响最大的特性。泛型本质上就是数据类型参数化,允许将任意数据类型指定为一个参数。泛型的目的是通过为类或者方法声明一种通用模式,使类中的某些数据成员或者成员方法的参数、返回值可以取任意类型,从而采用统一的方法或者类即可处理不同的数据类型。泛型把数据类型作为参数传递,就像把数值作为函数的参数传递一样。通俗地说,泛型就如同一种形参,只不过它表示的不是数值,而是某种数据类型。需要注意的是,泛型的数据类型必须是引用类型,如果是基本数据类型,就需要转化为对应的封装类类型。

泛型是以什么样的方式实现的?在程序中可以定义泛型类、泛型方法,甚至泛型接口。首先,在定义类、接口、方法时用一个通用名称(如type, E)代替操作的具体数据类型,即把具体数据类型指定为一一个参数,在实例化时再把这个参数用实际的类型名称替换。这就是所谓的参数化类型,这和C++的Template class (类模板)具有异曲同工之功效。这样做的结果是简化代码并且增强安全性

定义泛型通常有以下两种格式:;

●泛型类,定义在类名后面:

public class TestClassName <T,S extends T>{}

●泛型方法,定义在方法修饰符后面:

public <T,S extends T> T testMethod(T t, S s){}

T和S为通配符,以上表示定义泛型T、S,并且S继承于T。实例化泛型类时,以实际类名替代通配符,如TestClassName<String> list;实例化泛型方法时,编译器会自动对类型参数进行赋值,当不能成功赋值时,报编译错误。以下示例为泛型类和泛型方法的定义及实例化。

示例:在Product类中用T和V代替具体数据类型String和Integer,直到声明对象时,才赋予实际的类型名称。

代码如下:

class Product<T,V>{//声明一个类型参数通配符的类,这里的<T,V>是必需的
    private T type;//在本例中,T和V最后呗String和Integer分别替换
    private V id;
    Product(T type, V id){
        this.type=type;
        this.id = id;
    }

    public T getType() {
        return type;
    }

    public V getId() {
        return id;
    }
}
public class GenericTest {
    public static void main(String[] args) {
        //创建对象时指定Product具体类型
        Product<String,Integer> apple = new Product<>("ipad",12);
        System.out.println("Product type: "+apple.getType());
        System.out.println("Product id: "+apple.getId());
    }
}

运行结果如下:

Product type: ipad
Product id: 12

此例中定义泛型类Product时,类名后面必须紧跟着通配符<T,V>,以代替实际的数据类型。Product 类的数据成员形参类型用T和V表示,用于接收外部传人的类型实参。构造方法是一个形参为T和V的泛型方法,泛型方法getType()和getID返回值分别以T和V表示。在定义数据成员和成员方法时,并不知道T、V表示的数据类型是什么,直到实例化一个Product对象apple时,才指明T真正替代的是String类型,V表示Integer类型集合,通过apple对象调用方法时,方法返回值会自动被具体化,被赋予实际的数据类型。

接下来解释泛型类定义格式public class TestClassName<T, S extends T>{}, 其中extends T表示对泛型类范围加以限制,限定范围通过extends关键字实现,这种方式称为限定泛型,比如< S extends Collection>表示S限定范围为Collection接口的具体类,实例化时,如果传人非Collection接口的具体类,则编译会报错。关键字extends其后可以是类或者接口,因此extends已不仅仅是继承的含义了,对上例而言应该理解为S类型是实现Collection接口的任意一个具体类,如果S后面是一个类名,那么S类型就是该类的子类。

另外,实例化泛型类或方法时,赋值的都是指定的具体类型,当赋值的类型不确定时,用通配符(?) 代替。例如:

List<?> aList;
List<? extends Number> aNumberList;

若没有extends,只指定了<?>,即为默认情况,意味着凡是Object及其子类,都可以,用来实例化。通配符既可以向下限制(通配符下限),例如,<? extends Collection>表示接受Collection及其实现的具体类;通配符也可向上限制(通配符上限),例如,<? super Double>表示类型只能接受Double及其上层父类类型如Number、Object的实例。

泛型编程通常遵循以下3个步骤:

1)定义一个用通配符作为类型参数的泛型类或泛型方法。

2)实例化泛型类的对象,把实际数据类型赋予对象。

3)实例化泛型方法,编译器自动对类型参数赋值,赋值不成功,则编译报错。

按照惯例,类型通配符命名为一个大写字母,Java API中常用的通配符名称如下:

●E: Element, 表示类型形参,可以接受具体的类型实参。
●K: Key,键。
●N: Number, 数。
●T: Type,类型。
●V: Value,值。
●S、U、V等:第二、第三、第四个类型等。

9.3.2 泛型在集合中的应用

在实际开发中,集合中大量用到泛型。JDK 1.4.2 和更早版本的集合都有一个共同的安全隐患: 一旦将某个对象添加到一个集合中,该对象便失去了其原有的类型信息,成为Object对象,也就是集合只保存没有任何特定类型特征的对象。这就意味着集合可以容纳任意数据类型的对象,可以将任意类型的对象添加到同一集合中。从集合中取出对象时,不清楚它到底是什么类型,必须显式或强制类型转换,转换要求开发者预先知道实际的数据类型,一旦没有强制类型转换或者转换出错,编译器也不会报错,直到运行时出现异常,这种方式存在潜在的安全隐患。下例是一个需要强制类型转换的例子,此例中包含了ArrayList的使用方法。

示例:将Chicken类对象放入一个ArrayList集合中,再从中取出来

class Chicken {
    int chickenNum;

    public Chicken(int i) {
        chickenNum = i;
    }

    public void show() {
        System.out.println("Chicken id: " + chickenNum);
    }
}

public class GenericTest2 {
    public static void main(String[] args) {
        ArrayList animals = new ArrayList();//定义一个ArrayList animals
        for (int i = 0; i < 5; i++) {
            animals.add(new Chicken(i));
        }
        for (int j = 0; j < 5; j++) {
            ((Chicken) animals.get(j)).show();
        }
    }
}

此例创建了一个名为animals的ArrayList 集合来存放对象,接着添加5个Chicken对象到集合animals里,这些对象在集合中都转换成了Object类型。当把对象从animals中取出来时,需要强制类型转换恢复成原有类型。若在JDK 1.5 及以上版本运行此程序,如不进.行强制类型转换,则编译无法通过。

针对这种隐患,JDK1.5引入了泛型,其目的在于增强集合使用的灵活性,把隐患控制在编译阶段,编译时自动检测类型安全,若有类型不匹配,则及时给出编译警告,并且自动进行隐式强制类型转换。泛型要求创建一个集合时,给出所能放置对象类型的通用名称,以代替象具体的数据类型,或者直接指明对象的具体类型名称,泛型使程序明确告知编译器,每个集合能够放置的特定类型是什么。下面用泛型修改上例,在创建ArrayList集合时,设定其放置Chicken类型对象。
代码如下:

class Chicken {
    int chickenNum;
    public Chicken(int i) {
        chickenNum = i;
    }

    public void show() {
        System.out.println("Chicken id: " + chickenNum);
    }
}

public class GenericTest2 {
    public static void main(String[] args) {
        //泛型
        ArrayList<Chicken>animals = new ArrayList<Chicken>();
        for (int i = 0; i < 5; i++) {
            animals.add(new Chicken(i));
        }
        for (int j = 0; j < 5; j++) {
           animals.get(j).show();
        }
    }
}

此例中在创建ArrayList时,即指定所放置对象的类型为Chicken,若添加对象类型不匹配,编译时会及时报错,把潜在的错误限制在了编译阶段。程序中凡是定义集合的语句,如果没有配合使用泛型,如此例中去掉,编译器将给出警告。

下例是一个使用泛型实现的LinkedList集合示例,此例中创建集合时以通配符作为对象的类型形参,直到向集合里添加对象时,再以实参形式指定其真正类型。

示例:用泛型实现-一个LinkedList集合。

代码如下:

public class GenericTest3<E> {
    //创建一个LinkedList,具体放置元素用E代替
    private LinkedList<E> list = new LinkedList<E>();

    public void push(E o) {
        list.addFirst(o);//压栈
    }

    public E top() {
        return list.getFirst();//查询栈顶元素
    }

    public E pop() {
        return list.removeFirst();//出栈
    }

    @Override
    public String toString() {
        return list.toString();
    }

    public static void main(String[] args) {
        GenericTest3<String> sl = new GenericTest3<>();
        for (int i = 0; i < 5; i++) {
            sl.push(String.valueOf(i));
        }
        System.out.println("s1 = " + sl);
    }
}

运行结果如下:

s1 = [4, 3, 2, 1, 0]

总体而言,泛型在集合中使用时,无论是在创建一个集合时给出对象类型通配符,还是直接指明对象真正的类型名称,从集合中取出对象时不再需要强制类型转换,杜绝了运行时可能产生的潜在问题,保证程序运行的稳定性。

从前面讲述的通配符下限可推断出,在继承关系下,如果要把子类的对象添加到一个集合里,只需指定集合存放的对象类型为其父类类型,即只要是父子类关系的类型都允许放入集合中,取出对象时无须再进行强制类型转换。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-09-03 11:46:23  更:2021-09-03 11:47:34 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 13:17:33-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码