Java泛型示例教程——泛型方法、类、接口
Java 泛型中引入的最重要的特性之一。
如果您一直在使用Java 集合并使用版本 5 或更高版本,我相信您已经使用过它。
Java 中带有集合类的泛型非常简单,但它提供了比创建集合类型更多的特性。
我们将在本文中尝试学习泛型的特性。如果我们使用行话,理解泛型有时会变得混乱,所以我会尽量让它简单易懂。
Java 中的泛型
Java 5 中添加了泛型以提供编译时类型检查并消除ClassCastException
在使用集合类时常见的风险。整个集合框架被重新编写以使用泛型来实现类型安全。让我们看看泛型如何帮助我们安全地使用集合类。
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 之后,我们使用如下的集合类。
List<String> list1 = new ArrayList<String>(); // java 7 ? List<String> list1 = new ArrayList<>();
list1.add("abc");
//list1.add(new Integer(5)); //compiler error
for(String str : list1){
//no type casting needed, avoids ClassCastException
}
请注意,在创建列表时,我们已指定列表中元素的类型为 String。因此,如果我们尝试在列表中添加任何其他类型的对象,程序将抛出编译时错误。还要注意,在 for 循环中,我们不需要对列表中的元素进行类型转换,因此在运行时删除 ClassCastException。
Java泛型类
我们可以使用泛型类型定义我们自己的类。泛型类型是通过类型参数化的类或接口。我们使用尖括号 (<>) 来指定类型参数。
为了理解,假设我们有一个简单的类:
package com.journaldev.generics;
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 泛型类来重写相同的类,如下所示。
package com.journaldev.generics;
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
}
}
注意在 main 方法中使用 GenericsType 类。我们不需要进行类型转换,我们可以在运行时移除 ClassCastException。如果我们在创建时不提供类型,编译器会产生一个警告“GenericsType 是原始类型。
对泛型类型 GenericsTypeObject
,因此它允许 String 和 Integer 对象。但是,我们应该始终尽量避免这种情况,因为在处理可能产生运行时错误的原始类型时,我们将不得不使用类型转换。
提示:我们可以使用
@SuppressWarnings("rawtypes")
注解来抑制编译器警告,
Java 通用接口
Comparable interface 是接口中泛型的一个很好的例子,它被写成:
package java.lang;
import java.util.*;
public interface Comparable<T> {
public int compareTo(T o);
}
类似的,我们可以在java中创建泛型接口。我们也可以像 Map 接口那样有多个类型参数。同样,我们也可以为参数化类型提供参数化值,例如new HashMap<String, List<String>>();
有效。
Java 泛型类型
Java 通用类型命名约定有助于我们轻松理解代码,命名约定是 Java 编程语言的最佳实践之一。所以泛型也有自己的命名约定。通常,类型参数名称是单个大写字母,以使其易于与 java 变量区分开来。最常用的类型参数名称是:
- E – 元素(被 Java 集合框架广泛使用,例如 ArrayList、Set 等)
- K – 键(在Map中使用)
- N – 数字
- T – 类型
- V – 值(用于地图)
- S、U、V 等 – 第 2、第 3、第 4 种类型
Java泛型方法
有时我们不希望整个类都被参数化,在这种情况下,我们可以创建 java 泛型方法。由于构造函数是一种特殊的方法,我们也可以在构造函数中使用泛型类型。
这是一个显示 java 泛型方法示例的类。
package com.journaldev.generics;
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 编译器足够聪明,可以确定要使用的变量的类型,这种工具称为类型推断。
Java 泛型有界类型参数
假设我们想要限制可以在参数化类型中使用的对象类型,例如在比较两个对象的方法中,我们想要确保接受的对象是 Comparables。要声明有界类型参数,请列出类型参数的名称,然后是 extends 关键字,然后是其上限,类似于下面的方法。
public static <T extends Comparable<T>> int compare(T t1, T t2){
return t1.compareTo(t2);
}
这些方法的调用类似于无界方法,只是如果我们尝试使用任何不是 Comparable 的类,它将抛出编译时错误。
有界类型参数可以与方法以及类和接口一起使用。
Java泛型也支持多个边界,即<T extends A & B & C>。在这种情况下,A 可以是接口或类。如果 A 是类,那么 B 和 C 应该是一个接口。我们不能在多个边界中拥有多个类。
Java 泛型和继承
我们知道如果 A 是 B 的子类,Java 继承允许我们将变量 A 分配给另一个变量 B。所以我们可能认为 A 的任何泛型类型都可以分配给 B 的泛型类型,但事实并非如此。让我们用一个简单的程序来看看这个。
package com.journaldev.generics;
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