如何在Java中使用泛型 -Manusha

20-05-23 banq

泛型是Java中的关键概念。大多数Java代码库都将使用它们。因此,不可避免的是某个时候开发人员会遇到它们。这就是为什么正确理解它们至关重要。正确理解泛型也将帮助您获得Java面试机会。

在本文中,我将讨论泛型是什么,如何在Java中使用它们以及它们的优点。

Java 5中添加了泛型,以提供编译时类型检查,并消除了使用集合类时常见的ClassCastException风险。

Java中的集合类用于存储和操作对象组。例如,ArrayList集合类可以存储任何类型的对象。因为它被设计为Java基类类型Object的容器。因此,ArrayList对象可以容纳String或Integer或任何Java类型。使用泛型,我们可以定义ArrayList可以容纳的对象类型。因此允许创建单个对象类型ArrayLists。

public class GenEx1{
     public static void main(String []args){
         ArrayList<String> al = new ArrayList<String>();
         al.add("Name");
         al.add("Age");
         al.add(22); // Compile Error!
     }
}

如您在上面的示例中看到的那样,泛型类型是通过使用尖括号来定义的。在此示例中,只能将String对象存储在ArrayList中。Java中的集合类现在具有通用类型。现在让我们看看如何编写我们自己的通用类,接口和方法。

泛型类

在泛型类声明中,类的名称后跟类型参数部分。我们可以遵循相同的语法来创建泛型接口。类型参数也被称为类型变量,是用于指定一个泛型的类型名称的标识符。泛型类的类型参数部分可以包含一个或多个用逗号分隔的类型参数。这些类也称为参数化类。

class Test<K, V>{
    private K key;
    private V value;
    
    Test(K key, V value){
        this.key = key;
        this.value = value;
    }
    
    public K getKey(){
        return key;
    }
    public V getValue(){
        return value;
    }
    
}
public class GenEx2{
     public static void main(String []args){
         Test<String,Integer> pair1 = new Test<String,Integer>("ID",223);
         Test<String,Integer> pair2 = new Test<String,Integer>("Age",22);
         System.out.println(pair1.getKey() + "=" + pair1.getValue() );
         System.out.println(pair2.getKey() + "=" + pair2.getValue() );
     }
}

在上面的示例中,Test类具有两个名为K和V的类型参数。因此,Test类的对象可以存储两种不同类型的值。

泛型方法

如果我们可以编写一个单一的排序方法来对Integer数组,String数组或任何支持排序的类型的数组中的元素进行排序,那会很好,对吗?Java泛型方法允许我们使用单个方法声明来指定一组相关类型。您将能够编写一个可以用不同类型的参数调用的通用方法声明。必须在方法返回类型之前指定类型参数部分。类型参数也可以用作方法的返回类型。

public class GenEx3{
    
     public static < E > void printArray( E[] inputArray ) {
        for(E element : inputArray) {
            System.out.print(element +" ");
        }
        System.out.println();
   }

     public static void main(String args[]){
        Integer[] intArray = { 1, 2, 3, 4, 5 };
        Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
        Character[] charArray = { 'P', 'I', 'Z', 'Z', 'A' };

        System.out.print("integerArray contains: ");
        printArray(intArray);
        System.out.print("doubleArray contains: ");
        printArray(doubleArray);
        System.out.print("characterArray contains: ");
        printArray(charArray);
     }
}

在上面的示例中,printArray方法可用于打印任何类型的数组的元素。

泛型中的有界类型参数

到目前为止,我们只看到了无界限的泛型类型参数。无界意味着我们的泛型类型参数可以是我们想要的任何类型。有时您可能希望限制允许传递给参数的类型。例如,对数字进行操作的方法可能只希望接受Number类或其子类的实例。有界类型参数用于此目的。要声明一个有界的类型参数,请列出该类型参数的名称,然后是extends关键字,然后是其上限。

public abstract class Cage<T extends Animal> {
    abstract void addAnimal(T animal)
}

class Animal{}
class Dog extends Animal{}
class Cat extends Animal{}

在示例中,笼型的通用类型必须始终是Animal或Animal类的子类。因此,我们可以将Cat,Dog或Animal类作为通用类型参数传递。

如果需要,我们还可以为泛型类型声明多个范围。因此,可以修改以下示例中的抽象类,如下所示。

public abstract class Cage<T extends Animal & Comparable<T>>

在这里,类型参数现在必须同时考虑Animal类和Comparable接口。

泛型中的通配符和子类型

问号(?)是泛型中的通配符,表示未知类型。如果我们希望我们的通用方法适用于所有类型,在这种情况下,可以使用无界通配符。无界通配符由<?>表示。我们还可以使用有界通配符,有界通配符有两种类型:上界通配符和下界通配符。

上界通配符用于在方法中放宽对变量类型的限制。例如,假设我们不知道列表将是数字,整数还是双精度类型。那么我们如何获得该列表中元素的总和?我们可以使用上限通配符来解决此问题。下面的示例显示了如何实现它。

public void method( List<? extends Number> list){
  double sum = 0;
  for(Number i : list){
   sum += i.doubleValue();
  }
  System.out.println(sum);
}

下界通配符用于增加方法中对变量类型的限制。假设我们只想将Integers添加到列表中,而我们也想接受Integer的超类型列表。我们可以使用下界通配符来实现此目的。从下面的示例中,我们可以看到如何使用它。

public void addIntegers(List<? super Integer> list){
 list.add(new Integer(10));
 list.add(new Integer(20));
}

虽然Integer在Java中是一个亚型Number,List<Integer>是不是一个亚型List<Number>?他们的共同父母是List<?>。因此,泛型类中的子类型使用通配符完成。下面的示例将帮助您理解这一点。

ArrayList<? extends Integer> intList = new ArrayList<>();
ArrayList<? extends Number>  numList = intList; // OK

ArrayList<Integer> intList2 = new ArrayList<>();
ArrayList<Number>  numList2 = intList2; // Compile Error!

泛型的优势

既然您知道如何使用泛型,那么您必须考虑为什么我们需要使用它们。嗯,我们使用它们的三个主要原因是:

  1. 类型安全
  2. 不需要类型转换
  3. 可重用代码

泛型确保了编译时的安全性,这使您可以在编译代码时捕获无效类型。因此,您无需担心运行时异常。不需要泛型转换是使用泛型的另一个优点。您定义初始类型,然后让代码为您进行转换。我们还可以避免代码重复。没有泛型,我们必须复制并粘贴相同的代码,但要使用不同的类型。使用泛型,我们不必这样做。