Java 泛型示例入门教程 - 泛型方法、类、接口

4decfbbf0d17407db1decc2221575fae.png

Java 中的泛型是什么?它们的用途是什么?您是否也在考虑这个问题?不用再多说了,我们将尝试通过示例解释 Java 中的泛型。

1. 泛型介绍

泛型一词的意思是参数化类型。参数化类型至关重要,因为它们使我们能够创建数据库、接口和方法,通过这些数据库、接口和方法,它们操作的数据类型作为参数给出。在泛型中,可以创建一个类。对参数化类型进行操作的类接口或方法称为泛型,如泛型类或泛型方法,泛型仅适用于对象。并且它们的类型根据其类型参数而不同。

Java 泛型是 Java 5 中引入的最重要的特性之一,用于处理类型安全的对象,以提供编译时类型检查并消除ClassCastException使用集合类时常见的风险。它在编译时检测错误并使代码稳定。Java 集合框架始终支持泛型来指定要存储的对象类型。始终有必要了解,Java 可以创建使用对象类型引用进行操作的通用接口、类和方法。该对象将是所有其他类的超类;此对象引用可以引用任何对象。

Java 中的泛型增加了所缺乏的安全性类型并简化了流程,因为不再需要明确使用强制类型转换在对象和所操作的数据之间进行转换。

6ae7ebfeacea4cceabfa33d8d800ba02.png

Java整个集合框架被重写为使用泛型来确保类型安全。让我们看看泛型如何帮助我们安全地使用集合类。

List list = new ArrayList();
list.add("abc");
list.add(new Integer(5)); //OK

for(Object obj : list){
	//type casting leading to ClassCastException at runtime
    String str=(String) obj; 
}

上述代码编译正常,但在运行时会抛出 ClassCastException,因为我们试图将列表中的 Object 转换为 String,而其中一个元素是 Integer 类型。在 Java 5 之后,我们使用如下所示的集合类。

// java 7 ? List<String> list1 = new ArrayList<>(); 
List<String> list1 = new ArrayList<String>(); 
list1.add("abc");
//list1.add(new Integer(5)); //compiler error

for(String str : list1){
     //no type casting needed, avoids ClassCastException
}

请注意,在创建列表时,我们已指定列表中元素的类型为 String。因此,如果我们尝试在列表中添加任何其他类型的对象,程序将抛出编译时错误。还请注意,在 for 循环中,我们不需要对列表中的元素进行类型转换,因此在运行时删除了 ClassCastException。

c15f6048d7a1440d8887f859fb1544c5.png

2. Java泛型类

我们可以用泛型类型定义自己的类。泛型类型是通过类型参数化的类或接口。我们使用尖括号 (<>) 来指定类型参数。为了理解其好处,假设我们有一个简单的类:

public class GenericsTypeOld {

	private Object t;

	public Object get() {
		return t;
	}

	public void set(Object t) {
		this.t = t;
	}

        public static void main(String args[]){
		GenericsTypeOld type = new GenericsTypeOld();
		type.set("Pankaj"); 
		String str = (String) type.get(); //type casting, error prone and can cause ClassCastException
	}
}

请注意,在使用此类时,我们必须使用类型转换,并且它可能在运行时产生 ClassCastException。现在我们将使用 java 泛型类重写相同的类,如下所示。

public class GenericsType<T> {

	private T t;
	
	public T get(){
		return this.t;
	}
	
	public void set(T t1){
		this.t=t1;
	}
	
	public static void main(String args[]){
		GenericsType<String> type = new GenericsType<>();
		type.set("Pankaj"); //valid
		
		GenericsType type1 = new GenericsType(); //raw type
		type1.set("Pankaj"); //valid
		type1.set(10); //valid and autoboxing support
	}
}

注意在主方法中使用了 GenericsType 类。我们不需要进行类型转换,并且可以在运行时删除 ClassCastException。如果我们在创建时不提供类型,编译器将发出警告“GenericsType 是原始类型。对泛型类型 GenericsType<T> 的引用应该参数化”。当我们不提供类型时,类型将变为,Object因此它允许 String 和 Integer 对象。但是,我们应该始终尽量避免这种情况,因为在处理原始类型时我们必须使用类型转换,这可能会产生运行时错误。

提示:我们可以使用@SuppressWarnings("rawtypes")注释来抑制编译器警告

3. Java 通用接口

Comparable 接口是接口中泛型的一个很好的例子,它的写法如下:

package java.lang;
import java.util.*;

public interface Comparable<T> {
    public int compareTo(T o);
}

类似地,我们可以在 Java 中创建通用接口。我们也可以像 Map 接口一样拥有多个类型参数。同样,我们也可以向参数化类型提供参数化值,例如new HashMap<String, List<String>>();有效。

4. Java 泛型类型

Java 泛型命名约定有助于我们轻松理解代码,命名约定是 Java 编程语言的最佳实践之一。因此,泛型也有自己的命名约定。通常,类型参数名称是单个大写字母,以便于与 Java 变量区分开来。最常用的类型参数名称是:

  • E - 元素(Java 集合框架广泛使用,例如 ArrayList、Set 等)
  • K - 键(用于地图)
  • N - 数字
  • T 型
  • V - 值(用于地图)
  • S、U、V 等 - 第二、第三、第四种类型

5. Java 泛型方法

有时我们不希望整个类都被参数化,在这种情况下,我们可以创建 java 泛型方法。由于构造函数构造函数构造函数是一种特殊的方法,我们也可以在构造函数中使用泛型类型。下面是一个类,展示了 java 泛型方法的示例。

public class GenericsMethods {

	//Java Generic Method
	public static <T> boolean isEqual(GenericsType<T> g1, GenericsType<T> g2){
		return g1.get().equals(g2.get());
	}
	
	public static void main(String args[]){
		GenericsType<String> g1 = new GenericsType<>();
		g1.set("Pankaj");
		
		GenericsType<String> g2 = new GenericsType<>();
		g2.set("Pankaj");
		
		boolean isEqual = GenericsMethods.<String>isEqual(g1, g2);
		//above statement can be written simply as
		isEqual = GenericsMethods.isEqual(g1, g2);
		//This feature, known as type inference, allows you to invoke a generic method as an ordinary method, without specifying a type between angle brackets.
		//Compiler will infer the type that is needed
	}
}

请注意isEqual方法签名显示了在方法中使用泛型类型的语法。另外,请注意如何在我们的 Java 程序中使用这些方法。我们可以在调用这些方法时指定类型,也可以像调用普通方法一样调用它们。Java 编译器足够智能,可以确定要使用的变量的类型,此功能称为类型推断

6. Java 泛型有界类型参数

假设我们想要限制参数化类型中可以使用的对象类型,例如在比较两个对象的方法中,我们想要确保接受的对象是可比较的。要声明有界类型参数,请列出类型参数的名称,后跟 extends 关键字,后跟其上限,类似于下面的方法。

public static <T extends Comparable<T>> int compare(T t1, T t2){
	return t1.compareTo(t2);
}

7. Java 泛型和继承

我们知道,如果 A 是 B 的子类, Java 继承允许我们将变量 A 分配给另一个变量 B。因此,我们可能认为 A 的任何泛型类型都可以分配给 B 的泛型类型,但事实并非如此。让我们通过一个简单的程序来看一下。

public class GenericsInheritance {

	public static void main(String[] args) {
		String str = "abc";
		Object obj = new Object();
		obj=str; // works because String is-a Object, inheritance in java
		
		MyClass<String> myClass1 = new MyClass<String>();
		MyClass<Object> myClass2 = new MyClass<Object>();
		//myClass2=myClass1; // compilation error since MyClass<String> is not a MyClass<Object>
		obj = myClass1; // MyClass<T> parent is Object
	}
	
	public static class MyClass<T>{}

}

我们不允许将 MyClass<String> 变量分配给 MyClass<Object> 变量,因为它们没有关系,事实上 MyClass<T> 父级是 Object。

8. Java 泛型类和子类型

我们可以通过扩展或实现泛型类或接口来将其子类型化。一个类或接口的类型参数与另一个类或接口的类型参数之间的关系由 extends 和 implements 子句决定。例如,ArrayList<E> 实现了扩展 Collection<E> 的 List<E>,因此 ArrayList<String> 是 List<String> 的子类型,而 List<String> 是 Collection<String> 的子类型。只要我们不更改类型参数,子类型关系就会保留,下面显示了多个类型参数的示例。

interface MyList<E,T> extends List<E>{
}

注意:

  1. 泛型不支持子类型,类似List<Number> numbers = new ArrayList<Integer>();的写法无法编译。
  2. 无法创建泛型数组,因此List<Integer>[] array = new ArrayList<Integer>[10]也无法编译。
  •  

9. Java 泛型通配符

问号(?)是泛型中的通配符,表示未知类型。通配符可以用作参数、字段或局部变量的类型,有时也可以用作返回类型。在调用泛型方法或实例化泛型类时,我们不能使用通配符。在以下部分中,我们将学习上限通配符、下限通配符和通配符捕获。

9.1 Java 泛型上界通配符

上限通配符用于放宽方法中变量类型的限制。假设我们想编写一个返回列表中数字总和的方法,那么我们的实现将是这样的。

public static double sum(List<Number> list){
	double sum = 0;
	for(Number n : list){
		sum += n.doubleValue();
	}
	return sum;
}

现在,上述实现的问题在于它不适用于整数或双精度列表,因为我们知道 List<Integer> 和 List<Double> 不相关,这时上限通配符就很有用了。我们使用带有extends关键字的泛型通配符和上限类或接口,这将允许我们传递上限或其子类类型的参数。上述实现可以像下面的程序一样进行修改。

import java.util.ArrayList;
import java.util.List;

public class GenericsWildcards {

	public static void main(String[] args) {
		List<Integer> ints = new ArrayList<>();
		ints.add(3); ints.add(5); ints.add(10);
		double sum = sum(ints);
		System.out.println("Sum of ints="+sum);
	}

	public static double sum(List<? extends Number> list){
		double sum = 0;
		for(Number n : list){
			sum += n.doubleValue();
		}
		return sum;
	}
}

这类似于以接口的形式编写代码,在上面的方法中,我们可以使用上限类 Number 的所有方法。请注意,对于上限列表,我们不允许向列表中添加除 null 之外的任何对象。如果我们尝试在 sum 方法内向列表添加元素,则程序将无法编译。

9.2 Java 泛型无界通配符

有时我们会遇到这样的情况:我们希望泛型方法适用于所有类型,在这种情况下,可以使用无限制通配符。它与使用 <? extends Object> 相同。

public static void printData(List<?> list){
	for(Object obj : list){
		System.out.print(obj + "::");
	}
}

我们可以向printData方法提供 List<String> 或 List<Integer> 或任何其他类型的 Object 列表参数。与上限列表类似,我们不允许向列表中添加任何内容。

9.3 Java 泛型下界通配符

假设我们想在方法中将整数添加到整数列表中,我们可以将参数类型保留为 List<Integer>,但它将与整数绑定,而 List<Number> 和 List<Object> 也可以保存整数,因此我们可以使用下限通配符来实现这一点。我们使用泛型通配符 (?) 和super关键字以及下限类来实现这一点。我们可以将下限或下限的任何超类型作为参数传递,在这种情况下,java 编译器允许将下限对象类型添加到列表中。

10. 总结

以上就是关于Java 中的泛型的全部内容,Java 泛型是一个非常广泛的主题,需要花费大量时间才能理解并有效使用它。本文旨在提供泛型的基本细节,以及我们如何使用它来扩展具有类型安全性的程序。

 

 

 

 

 

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

伏羲栈

你的打赏是我精心创作的动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值