Java 泛型
通用的意思是不特定或不固定的东西。在 Java 中,泛型类和方法被创建来处理不同的数据类型和对象。
我们只是为类或方法可以接受的类型创建一个参数。泛型提供了一个额外的抽象层,帮助我们避免运行时错误。他们还提供型安全。让我们进一步了解 Java 中的泛型。
Java 泛型类
假设,我们需要一个可以处理任何对象类型的类。我们可以使用菱形运算符(< > ) 在 Java 中创建一个泛型类。
考虑下面显示的例子。菱形运算符之间的字母 T 称为类型参数。可以使用String
类型、Integer
类型甚至双精度来创建类对象。
//Generic class with parameter type T
class GenericClass<T>
{
T field1;//Object of type T
GenericClass(T f1)
{
this.field1 = f1;
}
}
public class GenericsDemo
{
public static void main(String[] args)
{
GenericClass<String> g1 = new GenericClass("100");
GenericClass<Integer> g2 = new GenericClass(100);
GenericClass<Double> g3 = new GenericClass(100.0);
}
}
在上面的例子中,我们只有一个类型参数。我们还可以使用多个类型参数来创建泛型类。下面是一个例子。
//Generic Class with multiple type parameters
class GenericClass<E, T>
{
E field1;//Object of type E
T field2;//Object of type T
GenericClass(E f1, T f2)
{
this.field1 = f1;
this.field2 = f2;
}
}
public class GenericsDemo
{
public static void main(String[] args)
{
GenericClass<String, Integer> g1 = new GenericClass("100", 100);
GenericClass<Integer, String> g2 = new GenericClass(100, "100");
GenericClass<Double, String> g3 = new GenericClass(100.0, "100");
}
}
Java 泛型方法
就像类一样,Java 中的方法也可以采用类型参数。这将使我们能够对不同的对象类型使用相同的方法。
考虑下面的代码,它使用 Generic print()方法来打印对象。Parameter
类型为 T 的 Generic 方法,我们可以在方法实现的任何地方使用这个类型 T。即使方法返回 void,也必须使用 diamond 运算符。
public class GenericsDemo
{
public static <T> void print(T object)
{
System.out.println(object);
}
public static void main(String[] args)
{
String s = "100";
Integer i = 100;
Double d = 100.0;
print(s);
print(i);
print(d);
}
}
100 100 100.0
就像泛型类一样,g 泛型方法也可以取多个类型参数。例如,让我们创建一个方法,将任意类型的两个列表作为参数,它应该打印这两个列表。我们可以在菱形运算符中定义两种Parameter
类型(T 和 E)。
import java.util.ArrayList;
import java.util.LinkedList;
public class GenericsDemo
{
public static <T, E> void printLists(ArrayList<T> l1, LinkedList<E> l2)
{
System.out.println(l1);
System.out.println(l2);
}
public static void main(String[] args)
{
ArrayList<Integer> al = new ArrayList<>();
al.add(5);
al.add(10);
al.add(15);
LinkedList<String> ll = new LinkedList<>();
ll.add("five");
ll.add("ten");
ll.add("fifteen");
printLists(al, ll);
}
}
【5,10,15】 【五,十,十五】
有界泛型方法
泛型类和方法为用户提供了很大的自由,但这有时会对我们不利。我们可以使用扩展关键字来限制泛型接受的类型范围。
<T extends ClassName>
这将只允许泛型类或方法中的父类及其所有子类。
例如,让我们创建三个类(ClassA、ClassB 和 ClassC),并从 ClassA 扩展 ClassB。然后,下面显示的 boundedPrint()方法将对类 a 和类 b 的对象起作用。
class ClassA
{
public void printClassA()
{
System.out.println("Class A");
}
}
class ClassB extends ClassA
{
public void printClassB()
{
System.out.println("Class B");
}
}
class ClassC
{
public void printClassC()
{
System.out.println("Class C");
}
}
public class GenericsDemo
{
//Bounded Generic Method
public static <T extends ClassA> void boundedPrint(T object)
{
System.out.println("Object of ClassA or a subclass of ClassA");
}
public static void main(String[] args)
{
ClassA a = new ClassA();
ClassB b = new ClassB();
boundedPrint(a);
boundedPrint(b);
}
}
类甲的对象或类甲的子类 类甲的对象或类甲的子类
然而,如果我们对 ClassC 的对象使用这个方法,我们会得到一个编译错误。
public static void main(String[] args)
{
ClassC c = new ClassC();
boundedPrint(c);//Compilation error
}
线程“main”Java . lang . error:未解决的编译问题: genericsdode 类型中的方法 boundprint(T)不适用于 genericsdode . main(genericsdode . Java:35)处的参数(ClassC)
我们也可以将有界泛型用于实现接口的类。我们将使用带有 extends 关键字的接口名称来代替类名。
<T extends Interface>
我们还可以一起使用类和接口来施加更严格的限制。
<T extends Class & Interface>
泛型中的类型安全
使用泛型的一个主要优势是它提供了类型安全。如果我们已经为泛型定义了一个类型,那么编译器知道期望什么对象,并在编译时返回一个错误,而不是给出一个运行时错误。
例如,如果我们创建一个ArrayList
来存储整数,并且没有为其元素指定类型,那么下面的代码将成功编译。但是我们会在运行时得到一个错误。
import java.util.ArrayList;
public class GenericsDemo
{
public static void main(String[] args)
{
ArrayList l = new ArrayList();
l.add(100);//Integer
l.add(101);//Integer
l.add("102");//String
Integer i1 = (Integer)l.get(0);
Integer i2 = (Integer)l.get(1);
Integer i3 = (Integer)l.get(2);
}
}
线程“main”Java . lang . class castexception 中的异常:类 java.lang.String 无法强制转换为类 java.lang.Integer(Java . lang . string 和 Java . lang . integer 位于 generic demo . main(generic demo . Java:48)处加载程序“bootstrap”的模块 java.base 中)
相反,如果我们使用菱形运算符并将类型定义为整数,我们将在编译时得到一个错误。这减少了运行时的开销。此外,我们不需要显式转换,因为编译器知道将返回哪种类型,并应用隐式转换。
import java.util.ArrayList;
public class GenericsDemo
{
public static void main(String[] args)
{
ArrayList<Integer> l = new ArrayList<Integer>();
l.add(100);//Integer
l.add(101);//Integer
l.add("102");//String
Integer i1 = l.get(0);
Integer i2 = l.get(1);
Integer i3 = l.get(2);
}
}
线程“main”Java . lang . error:未解决的编译问题:
类型 ArrayList 中的 add(Integer)
泛型中的类型擦除
泛型在编译时使用一种称为类型擦除的技术。类型擦除将泛型的Parameter
类型 T 替换为对象类(或者在泛型有界的情况下为父类),并在编译时强制类型约束。这确保了在运行时不需要做额外的工作,并减少了运行时开销。它确保不会为参数化类型创建新类。
例如,考虑以下通用方法。
public static <T> void print(T obj)
{
//Implementation
}
编译时,类型参数 T 将被对象类型替换,代码如下所示。
public static void print(Object obj)
{
//Implementation
}
如果类型参数是有界的,则在编译时用父类替换它。
public static <T extends ClassA> void boundedPrint(T obj)
{
//Implementation
}
public static void boundedPrint(ClassA obj)
{
//Implementation
}
由于类型擦除,泛型不能与基元类型一起工作。这是因为原始类型没有扩展对象类,或者任何其他类,因此在类型擦除过程中,编译器不能替换它们。但是我们总是可以使用包装类(比如整型和双精度型)来代替原始类型。
摘要
在本教程中,我们学习了 Java 中泛型的基础知识。它为程序员解决了很多问题,并使我们的代码可重用。一个通用方法可以用于多种类型的对象。泛型还提供类型安全,不需要显式转换。泛型帮助我们为不同类型的对象编写通用算法。