Java 泛型

原文:https://www.studytonight.com/java-examples/java-generics

通用的意思是不特定或不固定的东西。在 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()方法来打印对象。在方法定义中,告诉 Java 这是一个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)方法不适用于 genericsdode . main(genericsdode . Java:44)中的参数(String) 【T3)

泛型中的类型擦除

泛型在编译时使用一种称为类型擦除的技术。类型擦除将泛型的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 中泛型的基础知识。它为程序员解决了很多问题,并使我们的代码可重用。一个通用方法可以用于多种类型的对象。泛型还提供类型安全,不需要显式转换。泛型帮助我们为不同类型的对象编写通用算法。