Java泛型-泛型类,接口和方法

时间:2020-01-09 10:35:18  来源:igfitidea点击:

Java 5中引入了Java泛型,以在编译时提供严格的类型检查。

Java泛型中的类型参数

Java中的泛型使我们能够编写可与不同数据类型一起使用的泛型类,接口和方法。可能是因为在定义类,接口和方法时指定了类型参数。类型参数可以是任何类或者接口,例如Integer,String,自定义类或者接口。

例如,在Collection API中,ArrayList类写为

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{

..
..
}

这里E是一个类型参数,包含在尖括号(<>)中。由于指定了类型参数ArrayList <E>,这意味着ArrayList类是泛型类,其中E是此List中元素的类型。
使用此通用类定义,ArrayList可以使用不同的数据类型。初始化ArrayList时必须指定实际类型。

//List that stores Integers
List<Integer> nList = new ArrayList<Integer>();
nList.add(1);
nList.add(2);
nList.add(3);

// List that stores Strings
List<String> sList = new ArrayList<String>();
sList.add("A");
sList.add("B");
sList.add("C");

// List that stores objects of type Employee
List<Employee> eList = new ArrayList<Employee>();
Employee emp1 = new Employee("Jean", "HR", 6000);
eList.add(emp1);

为什么需要泛型

我们可能会争辩说,Java中的Object类已经可以用来引用任何类对象,这使其成为用作通用参数的良好候选对象。例如,在下面的类中,有一个方法将Object类型作为参数,因此我们可以将任何类型传递给此方法。

public class Test {
  public static void main(String[] args) throws IOException {
    Test t = new Test();
    t.display(1);
    t.display("Hello");
    t.display(5.67);
  }

  public void display(Object o) {
    System.out.println("passed argument is- " + o);
    System.out.println("passed argument's type is- " + o.getClass().getTypeName());
  }
}

输出:

passed argument is- 1
passed argument's type is- java.lang.Integer
passed argument is- Hello
passed argument's type is- java.lang.String
passed argument is- 5.67
passed argument's type is- java.lang.Double

如我们所见,通过使用Object作为参数,我可以拥有一个通用方法,该方法可以与任何类型一起使用,因此为什么需要泛型。答案是Java中的泛型为代码带来类型安全性。我们将在下一部分中讨论该功能。

Java泛型的好处

1.编译时进行严格的类型检查

泛型在编译时提供严格的类型检查,因此任何类型冲突都将在编译时本身而不是在运行时引发java.lang.ClassCastException时产生错误。

例如,我们已将List初始化为非通用List,而意图是在其中存储字符串。由于不是通用的,这意味着其所有元素都将作为对象类的对象存储。如果我们错误地将Integer添加到此列表,则不会有任何编译时错误,因为Integer也是Object类型。

从List检索元素时,必须将其显式转换为该类型,并且当遇到Integer时,它将引发ClassCastException。

public class Test {
  public static void main(String[] args) throws IOException {
    // Not generic
    List sList = new ArrayList();
    sList.add("A");
    sList.add("B");
    // Adding Integer
    sList.add(1);
    sList.add("C");
    
    Iterator itr = sList.iterator();
    while(itr.hasNext()){
      // Casting to string when retrieving
      String str = (String)itr.next();
      System.out.println("" + str);
    }
  }
}

输出:

A
B
Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')
	at com.theitroad.programs.Test.main(Test.java:27)

使用泛型,我们可以指定可以存储在列表中的元素类型,从而提供类型安全性。如果尝试将任何其他类型的元素添加到此List,则在编译时本身会出错。

public class Test {
  public static void main(String[] args) throws IOException {
    // Generic List
    List<String> sList = new ArrayList<String>();
    sList.add("A");
    sList.add("B");
    // Not allowed, Error at compile time if 
    // Integer is added 
    //sList.add(1);
    sList.add("C");
    
    Iterator<String> itr = sList.iterator();
    while(itr.hasNext()){
      String str = itr.next();
      System.out.println("" + str);
    }
  }
}

2.不需要显式强制转换

由于类型是通过泛型指定的,因此可以确保我们只能存储指定类型的元素,因此在检索元素时不需要显式转换。

在上面的代码中,当使用非通用列表时,需要强制类型转换。

Iterator itr = sList.iterator();
while(itr.hasNext()){
  // Casting to string when retrieving
  String str = (String)itr.next();
  System.out.println("" + str);
}

使用通用列表类型时,不需要强制转换。

Iterator<String> itr = sList.iterator();
while(itr.hasNext()){
  String str = itr.next();
  System.out.println("" + str);
}

3.实现通用算法

通过使用泛型,程序员可以实现可在不同类型上工作,易于阅读且类型安全的泛型算法。这是一个简单的泛型类示例,可以设置和获取任何类型的值。

public class Test<T> {
  T obj;
  public T getObj() {
    return obj;
  }
  public void setObj(T obj) {
    this.obj = obj;
  } 

  public static void main(String[] args) throws IOException {
    // With Integer type
    Test<Integer> intParam = new Test<Integer>();
    intParam.setObj(7);
    int value = intParam.getObj();
    System.out.println("Integer value- " + value);
    
    // With String type
    Test<String> strParam = new Test<String>();
    strParam.setObj("Test Value");
    String str = strParam.getObj();
    System.out.println("String value- " + str);
    
    // With Double type
    Test<Double> doubleParam = new Test<Double>();
    doubleParam.setObj(23.45);
    double dblValue = doubleParam.getObj();
    System.out.println("Double value- " + dblValue);
  }
}
Integer value- 7
String value- Test Value
Double value- 23.45

参见本文中的如何在Java中编写通用的冒泡排序。

Java泛型中的类型参数命名约定

按照约定,类型参数名称是单个大写字母。最常用的类型参数名称为:

  • T –类型

  • V –价值

  • E –元素

  • K –键

  • N –数字

  • S,U,V等–第二,第三,第四类型

通用类

完成泛型的介绍之后,让我们看看如何在Java中创建泛型类。

通用类以以下格式定义:

类名称<T1,T2,…,Tn> {
/ /
}

类名后面有一个类型参数部分,由尖括号(<>)分隔。它指定类型参数(也称为类型变量)T1,T2,…和Tn。

通用类创建Java示例
在此示例中,我们将创建一个具有两个类型参数的通用类,并将其用于不同的数据类型。

class GenericClass<K, V> {
  private K key;
  private V value;
  public GenericClass(K key, V value) {
    this.key = key;
    this.value = value;
  }
  public K getKey(){
    return key;
  }
  public V getValue(){
    return value;
  }
}

public class GenericDemo{
  public static void main(String[] args) {
    GenericClass<String, String> g1 = new GenericClass<>("Test", "Value");
    System.out.println("Key- " + g1.getKey());
    System.out.println("Value- " + g1.getValue());

    GenericClass<Integer, Integer> g2 = new GenericClass<>(1, 2);
    System.out.println("Key- " + g2.getKey());
    System.out.println("Value- " + g2.getValue());
    
    GenericClass<Integer, String> g3 = new GenericClass<>(1, "One");
    System.out.println("Key- " + g3.getKey());
    System.out.println("Value- " + g3.getValue());
  }    
}

输出:

Key- Test
Value- Value
Key- 1
Value- 2
Key- 1
Value- One

通用接口

通用接口的创建与通用类一样。

接口名称<T1,T2,…,Tn> {
/ /
}

实施通用接口时应遵循的一些规则如下

  • 如果接口使用通用类型参数,则实现通用接口的类必须是具有相同类型参数的通用类。
public class GenericClass<E> implements GenericInterface<E>
  • 如果为接口提供数据类型,则可以使用普通类。
public class NormalClass implements GenericInterface<Integer>
  • 泛型类除了必须使用的类型参数以外,还可以具有其他参数,因为如果实现了泛型接口。
public class GenericClass<K, V, E> implements GenericInterface<E>

通用方法

泛型类中的任何方法都可以指定该类的类型参数,也可以自由添加自己的类型参数。我们也可以在非泛型类中使用泛型方法。

通用方法Java示例

在这里,我们将在非泛型类中使用泛型方法。

class TestClass {
  // Generic method
  public <T> void displayArrayElements(T[] arr){
    System.out.println("Elements in Array- " + Arrays.toString(arr));
  }
}

public class GenericDemo{
  public static void main(String[] args) {
    TestClass obj = new TestClass();
    Integer[] intArray = {1, 2, 3, 4, 5, 6, 7};
    Double[] doubleArray = {1.2, 2.3, 3.4, 4.5, 5.6};
    String[] strArray = {"A", "B", "C", "D"};
    obj.displayArrayElements(intArray);
    obj.displayArrayElements(doubleArray);
    obj.displayArrayElements(strArray);
  }    
}

输出:

Elements in Array- [1, 2, 3, 4, 5, 6, 7]
Elements in Array- [1.2, 2.3, 3.4, 4.5, 5.6]
Elements in Array- [A, B, C, D]

如我们所见,如果我们正在编写具有自己的类型参数的泛型方法,则需要在access修饰符之后声明类型参数。

公共<T> void displayArrayElements(T [] arr)

我们也可以在调用通用方法时在尖括号中指定实际数据类型。尽管Java可以根据方法参数的类型自动推断类型,但这不是强制性的

obj.displayArrayElements(intArray);
或者这个
obj。<Integer> displayArrayElements(intArray);

钻石操作员

从Java 7开始,并不是必须指定调用泛型类的构造函数所需的类型实参,我们可以传递空的类型实参集(<>),只要编译器可以确定或者推断出以下类型的实参即可:上下文。这对尖括号<>被非正式地称为菱形。

例如,如果我们具有如下定义的通用类

public class Test<T> {
    ..
    ..
}

然后,我们可以从Java 7开始创建其实例。

Test<Integer> obj = new Test<>();

无需在右侧指定Integer,只需传递空尖括号<>,类型将自动推断。