Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

如何在Java程序中使用泛型

如何在Java程序中使用泛型

泛型可以使你的代码更灵活、更易读,并能帮助你在运行时避免ClassCastExceptions。让我们通过这篇结合Java集合框架的泛型入门指南,开启你的泛型之旅。

Java 5引入的泛型增强了代码的类型安全性并提升了可读性。它能帮助你避免诸如ClassCastException(当尝试将对象强制转换为不兼容类型时引发的异常)这类运行时错误。

本教程将解析泛型概念,通过三个结合Java集合框架的实例演示其应用。同时我们将介绍原始类型(raw types),探讨选择使用原始类型而非泛型的场景及其潜在风险。

Java编程中的泛型

为何使用泛型?

如何利用泛型保障类型安全

Java集合框架中的泛型应用

Java泛型类型示例

原始类型与泛型对比

为何使用泛型?

泛型在Java集合框架中被广泛用于java.util.List、java.util.Set和java.util.Map等接口。它们也存在于Java其他领域,如java.lang.Class、java.lang.Comparable 和java.lang.ThreadLocal。

在泛型出现前,Java代码常缺乏类型安全保障。以下是非泛型时代Java代码的典型示例:

List integerList = new ArrayList();integerList.add(1);integerList.add(2);integerList.add(3);for (Object element : integerList) {   Integer num = (Integer) element; // 必须显式类型转换   System.out.println(num);}

这段代码意图存储Integer对象,但没有任何机制阻止你添加其他类型(如字符串):

integerList.add("Hello");

当尝试将String强制转换为Integer时,这段代码会在运行时抛出ClassCastException。

利用泛型保障类型安全

为解决上述问题并避免ClassCastExceptions,我们可以使用泛型指定列表允许存储的对象类型。此时无需手动类型转换,代码更安全且更易理解:

List<Integer> integerList = new ArrayList<>();integerList.add(1);integerList.add(2);integerList.add(3);for (Integer num : integerList) {   System.out.println(num);}

List<Integer>表示"存储Integer对象的列表"。基于此声明,编译器确保只有Integer对象能被添加至列表,既消除了类型转换需求,也预防了类型错误。

Java集合框架中的泛型

泛型深度集成于Java集合框架,提供编译时类型检查并消除显式类型转换需求。当使用带泛型的集合时,你需指定集合可容纳的元素类型。Java编译器基于此规范确保你不会意外插入不兼容对象,从而减少错误并提升代码可读性。

为演示泛型在Java集合框架中的使用,让我们观察几个实例。

List和ArrayList的泛型应用

前例已简要展示ArrayList的基本用法。现在让我们通过List接口的声明深入理解这一概念:

public interface List<E> extends SequencedCollection<E> { … }

此处声明泛型变量为"E",该变量可被任何对象类型替代。注意变量E代表元素(Element)。

接下来演示如何用具体类型替换<E>变量。下例中将<E>替换为<String>:

List<String> list = new ArrayList<>();list.add("Java");list.add("Challengers");// list.add(1); // 此行会导致编译时错误

List<String>声明该列表仅能存储String对象。如代码最后一行所示,尝试添加Integer将引发编译错误。

Set和HashSet的泛型应用

Set接口与List类似:

public interface Set<E> extends Collection<E> { … }

我们将用<Double>替换<E>,使集合只能存储Double值:

Set<Double> doubles = new HashSet<>();doubles.add(1.5);doubles.add(2.5);// doubles.add("three"); // 编译时错误double sum = 0.0;for (double d : doubles) {   sum += d;}

Set<Double>确保只有Double值能被添加至集合,防止因错误类型转换引发的运行时错误。

Map和HashMap的泛型应用

我们可以声明任意数量的泛型类型。以键值数据结构Map为例,K代表键(Key),V代表值(Value):

public interface Map<K, V> { … }

现在用String替换K作为键类型,用Integer替换V作为值类型:

Map<String, Integer> map = new HashMap<>();map.put("Duke", 30);map.put("Juggy", 25);// map.put(1, 100); // 此行会导致编译时错误

此例展示将String键映射到Integer值的HashMap。添加Integer类型的键将不被允许并导致编译错误。

泛型命名规范

我们可以在任何类中声明泛型类型。虽然可以使用任意名称,但建议遵循命名规范:

E 代表元素(Element)

K 代表键(Key)

V 代表值(Value)

T 代表类型(Type)

应避免使用无意义的"X"、"Y"或"Z"等名称。

Java泛型类型                                                                          使用示例

现在通过更多示例深入演示Java中泛型类型的声明与使用。

创建通用对象容器

我们可以在自定义类中声明泛型类型,不必局限于集合类型。下例中,Box类通过声明泛型类型E来操作任意元素类型。注意泛型类型E声明于类名之后,随后即可作为属性、构造器、方法参数和返回类型使用:

// 定义带泛型参数E的Box类public class Box<E> {   private E content; // 存储E类型对象   public Box(E content) { this.content = content; }   public E getContent() { return content; }   public void setContent(E content) {       this.content = content;   }   public static void main(String[] args) {       // 创建存储Integer的Box       Box<Integer> integerBox = new Box<>(123);       System.out.println("整数盒内容:" + integerBox.getContent());       // 创建存储String的Box       Box<String> stringBox = new Box<>("Hello World");       stringBox.setContent("Java Challengers");       System.out.println("字符串盒内容:" + stringBox.getContent());   }}

输出结果:

整数盒内容:123字符串盒内容:Java Challengers

代码要点:

Box类使用类型参数E作为容器存储对象的占位符,允许Box处理任意对象类型

构造器初始化Box实例时接受指定类型对象,确保类型安全

getContent返回与实例创建时指定的泛型类型匹配的对象,无需类型转换

setContent通过类型参数E确保只能设置正确类型的对象

main方法创建了存储Integer和String的Box实例

每个Box实例操作特定数据类型,展现泛型在类型安全方面的优势

此例展示了Java泛型的基础实现,演示了如何以类型安全方式创建和操作任意类型对象。

处理多数据类型

我们可以声明多个泛型类型。以下Pair类包含<K, V>泛型值。如需更多泛型参数,可扩展为<K, V, V1, V2, V3>等,代码仍可正常编译。

Pair类示例:

class Pair<K, V> {   private K key;   private V value;   public Pair(K key, V value) {       this.key = key;       this.value = value;   }   public K getKey() { return key; }   public V getValue() { return value; }   public void setKey(K key) { this.key = key; }   public void setValue(V value) { this.value = value; }}public class GenericsDemo {   public static void main(String[] args) {       Pair<String, Integer> person = new Pair<>("Duke", 30);       System.out.println("姓名:" + person.getKey());       System.out.println("年龄:" + person.getValue());       person.setValue(31);       System.out.println("更新后年龄:" + person.getValue());   }}

输出结果:

姓名:Duke年龄:30更新后年龄:31

代码要点:

Pair<K, V>类包含两个类型参数,适用于任意数据类型组合

构造器与方法使用类型参数实现严格类型检查

创建存储String(姓名)和Integer(年龄)的Pair对象

访问器和修改器方法操作Pair数据

Pair类可存储管理关联信息而不受特定类型限制,展现泛型的灵活性与强大功能

此例展示泛型如何创建支持多数据类型的可复用类型安全组件,提升代码复用性和可维护性。

让我们再看一个示例。

方法级泛型声明

泛型类型可直接在方法中声明,无需在类级别定义。若某个泛型类型仅用于特定方法,可在方法签名返回类型前声明:

public class GenericMethodDemo {   // 声明泛型类型<T>并打印指定类型数组   public static <T> void printArray(T[] array) {       for (T element : array) {           System.out.print(element + " ");       }       System.out.println();   }   public static void main(String[] args) {       Integer[] intArray = {1, 2, 3, 4};       printArray(intArray);       String[] stringArray = {"Java", "Challengers"};       printArray(stringArray);   }}

输出结果:

1 2 3 4Java Challengers

原始类型与泛型对比

原始类型指未指定类型参数的泛型类或接口名称。在Java 5引入泛型前,原始类型被广泛使用。现今开发者通常仅在与遗留代码兼容或与非泛型API交互时使用原始类型。即使使用泛型,仍需了解如何识别和处理原始类型。

典型原始类型示例——未指定类型参数的List声明:

List rawList = new ArrayList();

此处List rawList声明了一个未指定泛型参数的列表。rawList可存储任意类型对象(Integer、String、Double等)。由于未指定类型,编译器不会对添加至列表的对象类型进行检查。

使用原始类型的编译警告

Java编译器会对原始类型使用发出警告,提醒开发者可能存在的类型安全隐患。当使用泛型时,编译器会检查集合(如List、Set)中存储的对象类型、方法返回类型和参数是否匹配声明类型,从而预防如ClassCastException的常见错误。

使用原始类型时,由于未指定存储对象类型,编译器无法进行类型检查,因此会发出警告提示你绕过了泛型提供的类型安全机制。

编译警告示例

以下代码演示编译器如何对原始类型发出警告:

List list = new ArrayList(); // 警告:原始使用参数化类'List'list.add("hello");list.add(1);

编译时通常会显示:

注意:SomeFile.java使用了未经检查或不安全的操作。注意:使用-Xlint:unchecked重新编译以获取详细信息。

使用-Xlint:unchecked参数编译将显示更详细警告:

warning: [unchecked] unchecked call to add(E) as a member of the raw type List   list.add("hello");           ^where E is a type-variable:   E extends Object declared in interface List

若确信使用原始类型不会引入风险,或处理无法重构的遗留代码,可使用@SuppressWarnings("unchecked")注解抑制警告。但需谨慎使用,避免掩盖真实问题。

使用原始类型的后果

尽管原始类型有助于向后兼容,但存在两大缺陷:类型安全性缺失和维护成本增加。

类型安全性缺失:泛型的核心优势是类型安全,使用原始类型将丧失这一优势。编译器不进行类型正确性检查,可能导致运行时ClassCastException。

维护成本增加:使用原始类型的代码缺乏泛型提供的明确类型信息,维护难度加大,易产生仅在运行时暴露的错误。

类型安全问题示例:使用原始类型List而非泛型List<String>时,编译器允许添加任意类型对象。当从列表检索元素并尝试强制转换为String时,若实际为其他类型将导致运行时错误。

泛型知识要点回顾

泛型以高度灵活性提供类型安全保障。以下回顾关键要点:

泛型是什么?为何使用?

code.Java 5引入泛型以提升代码类型安全性和灵活性

主要优势在于帮助避免ClassCastException等运行时错误

泛型广泛应用于Java集合框架,也见于Class、Comparable、ThreadLocal等组件

通过阻止不兼容类型插入实现类型安全

Java集合中的泛型

List和ArrayList:List<E>允许指定元素类型E,确保列表类型专一

Set和HashSet:Set<E>限定元素为类型E,保持一致性

Map和HashMap:Map<K,V>定义键值类型,提升类型安全性和代码清晰度

泛型使用优势

通过阻止不兼容类型插入减少错误

明确类型关联提升代码可读性和可维护性

便于以类型安全方式创建和管理集合等数据结构

【注】本文译自: How to use generics in your Java programs | InfoWorld

  • 发表于:
  • 原文链接https://page.om.qq.com/page/OSTlMJ1idvmM1Jvx86jKnFsQ0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券
首页
学习
活动
专区
圈层
工具