一文看懂JAVA泛型

2019-08-13  本文已影响0人  程序员will

[TOC]

Java泛型

Java Generics ”是一个技术术语,表示与泛型类型和方法的定义和使用相关的一组语言特性。在java中,泛型类型或方法与常规类型和方法的不同之处在于它们具有类型参数。

“Java Generics是一种语言功能,允许定义和使用泛型类型和方法。”

通过提供替换形式类型参数的实际类型参数,实例化通用类型以形成参数化类型。类似的类LinkedList<E>是泛型类型,具有类型参数E. 实例化(例如LinkedList<Integer>a或a LinkedList<String>)称为参数化类型,String和Integer是相应的实际类型参数。

泛型在JAVA中

让我们通过一个例子来理解。

List<Integer> list = new ArrayList<Integer>();
 
list.add(1000);     //works fine
 
list.add("lokesh"); //compile time error;

当您编写上面的代码并编译它时,您将得到以下错误:“类型中的方法add(Integer)List<Integer>不适用于参数(String) ”。编译器警告你。这完全是泛型的唯一目的,即类型安全。

第二部分是从上面的例子中删除第二行后获取字节码。如果你比较上面例子的字节码和/或没有泛型,那么就没有任何区别。显然,编译器删除了所有泛型信息。所以,上面的代码与下面的代码非常类似,没有泛型。

List list = new ArrayList();
 
list.add(1000);

泛型的类型

通用类型类或接口

如果一个类声明了一个或多个类型变量,则它是通用的。这些类型变量称为类的类型参数。让我们通过一个例子来理解。

DemoClass是简单的java类,它有一个属性t

class DemoClass {
   private Object t;
 
   public void set(Object t) { this.t = t; }
    
   public Object get() { return t; }
}

这里我们希望一旦用特定类型初始化类,类应仅与该特定类型一起使用。例如,如果我们想要一个类的实例保存类型' String'的值t ,那么程序员应该设置并获得唯一的String类型。由于我们已将属性类型声明为Object,因此无法强制执行此限制。程序员可以设置任何对象; 并且可以期望get方法中的任何返回值类型,因为所有java类型都是Object类的子类型。

要强制执行此类型限制,我们可以使用以下泛型:

class DemoClass<T> {
   //T stands for "Type"
   private T t;
 
   public void set(T t) { this.t = t; }
    
   public T get() { return t; }
}

示例用法DemoClass将如下所示:

DemoClass<String> instance = new DemoClass<String>();
instance.set("lokesh");   //Correct usage
instance.set(1);        //This will raise compile time error

以上类比也适用于界面。让我们快速查看一个示例,了解如何在java中的接口中使用泛型类型信息。

//Generic interface definition
interface DemoInterface<T1, T2>
{
   T2 doSomeOperation(T1 t);
   T1 doReverseOperation(T2 t);
}
 
//A class implementing generic interface
class DemoClass implements DemoInterface<String, Integer>
{
   public Integer doSomeOperation(String t)
   {
      //some code
   }
   public String doReverseOperation(Integer t)
   {
      //some code
   }
}

通用类型方法或构造函数

通用方法与泛型类非常相似。它们仅在一个方面是不同的,类型信息的范围仅在方法(或构造函数)内。通用方法是引入其自己的类型参数的方法。

让我们通过一个例子来理解这一点。下面是泛型方法的代码示例,该方法可用于仅在该类型的变量列表中查找所有类型参数的出现。

public static <T> int countAllOccurrences(T[] list, T item) {
   int count = 0;
   if (item == null) {
      for ( T listItem : list )
         if (listItem == null)
            count++;
   }
   else {
      for ( T listItem : list )
         if (item.equals(listItem))
            count++;
   }
   return count;
}  

如果您传递一个列表String和另一个字符串来搜索此方法,它将正常工作。但是如果你试图找到一个Number列表String,它将给出编译时错误。

与上面相同可以是通用构造函数的示例。让我们来看一下通用构造函数的单独示例。

class Dimension<T>
{
   private T length;
   private T width;
   private T height;
 
   //Generic constructor
   public Dimension(T length, T width, T height)
   {
      super();
      this.length = length;
      this.width = width;
      this.height = height;
   }
}

在此示例中,Dimension类的构造函数也具有类型信息。因此,您可以拥有仅具有单个类型的所有属性的维度实例。

通用类型数组

任何语言的数组都具有相同的含义,即数组是相似类型元素的集合。在java中,在运行时推送数组中任何不兼容的类型将抛出ArrayStoreException。这意味着数组在运行时保留其类型信息,泛型使用类型擦除或在运行时删除任何类型信息。由于上述冲突,不允许在java中实例化通用数组。

public class GenericArray<T> {
    // this one is fine
    public T[] notYetInstantiatedArray;
  
    // causes compiler error; Cannot create a generic array of T
    public T[] array = new T[5];
}

在与上面的泛型类和类相同的行中,我们可以在java中使用泛型数组。我们知道数组是类似类型元素的集合,并且推送任何不兼容的类型将ArrayStoreException在运行时抛出; 这不是Collection类的情况。

Object[] array = new String[10];
array[0] = "lokesh";
array[1] = 10;      //This will throw ArrayStoreException

上面的错误并不是很难。它可以随时发生。因此最好将类型信息提供给数组,以便在编译时自己捕获错误。

数组不支持泛型的另一个原因是数组是共变量的,这意味着超类型引用数组是子类型引用数组的超类型。也就是说,Object[]是一个超类型,String[]并且可以通过类型的引用变量访问字符串数组Object[]

Object[] objArr = new String[10];  // fine
objArr[0] = new String();

带有通配符的泛型

在通用代码中,称为通配符的问号(?)表示未知类型。通配符参数化类型是泛型类型的实例,其中至少一个类型参数是通配符。通配符参数化类型的例子有Collection<?<List<? extends Number<Comparator<? super String>Pair<String,?>

通配符可用于各种情况:作为参数,字段或局部变量的类型; 有时作为返回类型(虽然更好的编程实践更具体)。通配符从不用作泛型方法调用,泛型类实例创建或超类型的类型参数。

在不同的地方拥有外卡也有不同的含义。例如

通配符参数化类型不是可以出现在新表达式中的具体类型。它只是暗示了java泛型强制执行的规则,即在使用了外卡的任何特定场景中哪些类型都有效。

例如,下面是涉及通配符的有效声明:

Collection<?> coll = new ArrayList<String>();
//OR
List<? extends Number> list = new ArrayList<Long>();
//OR
Pair<String,?> pair = new Pair<String,Integer>();

以下不是通配符的有效用法,它们会给出编译时错误。

List<? extends Number> list = new ArrayList<String>();  //String is not subclass of Number; so error
//OR
Comparator<? super String> cmp = new RuleBasedCollator(new Integer(100)); //Integer is not superclass of String

泛型中的通配符可以是无界的,也可以是有界的。让我们用各种术语来识别它们。

无界通配符参数化类型

一种泛型类型,其中所有类型参数都是无界通配符"?“对类型变量没有任何限制。例如

ArrayList<?>  list = new ArrayList<Long>(); 
//or
ArrayList<?>  list = new ArrayList<String>(); 
//or
ArrayList<?>  list = new ArrayList<Employee>(); 

有界通配符参数化类型

有界通配符对可能的类型进行了一些限制,您可以使用它来实例化参数化类型。使用关键字“super”和“extends”强制执行此限制。为了更清楚地区分,让我们将它们划分为上限有界通配符和下限有界通配符。

上边界的通配符

例如,假设您要编写一个适用于List <String>,List <Integer>和List <double>的方法,您可以通过使用上限有通配符来实现此目的,例如,您将指定List <?扩展Number>。这里Integer,Double是Number类的子类型。通俗地说,如果希望泛型表达式接受特定类型的所有子类,则将使用“ extends ”关键字使用上限通配符。

public class GenericsExample<T>
{
   public static void main(String[] args)
   {
      //List of Integers
      List<Integer> ints = Arrays.asList(1,2,3,4,5);
      System.out.println(sum(ints));
       
      //List of Doubles
      List<Double> doubles = Arrays.asList(1.5d,2d,3d);
      System.out.println(sum(doubles));
       
      List<String> strings = Arrays.asList("1","2");
      //This will give compilation error as :: The method sum(List<? extends Number>) in the
      //type GenericsExample<T> is not applicable for the arguments (List<String>)
      System.out.println(sum(strings));
       
   }
    
   //Method will accept
   private static Number sum (List<? extends Number> numbers){
      double s = 0.0;
      for (Number n : numbers)
         s += n.doubleValue();
      return s;
   }
}
较低的有界通配符

如果希望泛型表达式接受特定类型的“超级”类型或特定类的父类的所有类型,则使用“超级”关键字为此目的使用下限通配符。

在下面给出的例子中,我创建了三个班,即SuperClassChildClassGrandChildClass。关系如下面的代码所示。现在,我们必须创建一个方法,以某种方式获取GrandChildClass信息(例如从DB)并创建它的实例。我们希望将这个新内容存储GrandChildClass在已有的列表中GrandChildClasses

这里的问题是,GrandChildClass的父类是ChildClass,他的父类又是SuperClass。因此,SuperClasses和ChildClasses的任何通用列表都能够保存GrandChildClasses。在这里,我们必须使用' super '关键字来帮助下限通配符。

package test.core;
 
import java.util.ArrayList;
import java.util.List;
 
public class GenericsExample<T>
{
   public static void main(String[] args)
   {
      //List of grand children
      List<GrandChildClass> grandChildren = new ArrayList<GrandChildClass>();
      grandChildren.add(new GrandChildClass());
      addGrandChildren(grandChildren);
       
      //List of grand childs
      List<ChildClass> childs = new ArrayList<ChildClass>();
      childs.add(new GrandChildClass());
      addGrandChildren(childs);
       
      //List of grand supers
      List<SuperClass> supers = new ArrayList<SuperClass>();
      supers.add(new GrandChildClass());
      addGrandChildren(supers);
   }
    
   public static void addGrandChildren(List<? super GrandChildClass> grandChildren)
   {
      grandChildren.add(new GrandChildClass());
      System.out.println(grandChildren);
   }
}
 
class SuperClass{
    
}
class ChildClass extends SuperClass{
    
}
class GrandChildClass extends ChildClass{
    
}

Generics不允许做什么

到目前为止,我们已经了解了许多可以用java中的泛型来做的事情,以避免ClassCastException应用程序中的许多实例。我们也看到了通配符的用法。现在是时候确定一些在java泛型中不允许做的任务了。

a)不能拥有类型的静态字段

无法在类中定义静态通用参数化成员。任何这样做的尝试都会产生编译时错误:无法对非静态类型T进行静态引用。

public class GenericsExample<T>
{
   private static T member; //This is not allowed
}

b)无法创建T的实例

任何创建T实例的尝试都将失败并显示错误:无法实例化类型T.

public class GenericsExample<T>
{
   public GenericsExample(){
      new T();
   }
}

c)泛型与声明中的原语不兼容

对,是真的。您不能声明像List或Map <String,double>这样的通用表达式。绝对可以使用包装类代替基元,然后在传递实际值时使用基元。通过使用自动装箱将原语转换为相应的包装类来接受这些值原语。

final List<int> ids = new ArrayList<>();    //Not allowed
 
final List<Integer> ids = new ArrayList<>(); //Allowed

d)无法创建通用异常类

有时,程序员可能需要传递泛型类型的实例以及抛出异常。这在Java中是不可能的。

// causes compiler error
public class GenericException<T> extends Exception {}

当您尝试创建此类异常时,最终会得到如下消息:泛型类GenericException可能不是子类java.lang.Throwable

上一篇下一篇

猜你喜欢

热点阅读