完整的Java泛型教程

Java的泛型作为JDK 5的功能之一引入。就个人而言,我发现泛型中使用的尖括号“ <>”非常引人入胜,它总是使我不得不重新考虑在哪里使用它,或者看到它是用其他人的代码编写的。坦率地说,很长一段时间以来我一直在使用泛型,但我仍然不完全有信心盲目使用它。在本教程中,我将介绍对Java泛型有用的所有内容以及与它们有关的内容。如果您认为我可以在教程的任何部分使用更精确的词,或者可以添加示例,或者您不同意我的观点;给我留言。我很高兴知道您的观点。

表中的内容

1)为什么要泛型?
2)泛型在Java中的工作方式
3)泛型的类型?
   i)泛型类型类或接口
   ii)泛型类型方法或构造函数
4)泛型类型数组
5)具有通配符的泛型
    i)无界通配符
    ii)有界通配符
        a)上界通配符
        b)下界通配符
6)不允许做什么与泛型?

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

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

通过提供替代正式类型参数的实际类型参数,实例化泛型类型以形成参数化类型。类似的类LinkedList<E>是泛型类型,具有类型参数E。实例化(例如LinkedList<Integer>或)LinkedList<String>称为参数化类型,而String和Integer是各自的实际类型参数。

1)为什么要泛型?

如果仔细研究java收集框架类,则会发现大多数类都采用类型的参数/参数,Object并从中返回方法的值Object。现在,以这种形式,他们可以将任何Java类型用作参数并返回相同的值。它们本质上是异构的,即不是特定的相似类型。

像我们这样的程序员经常想指定一个集合只包含某种类型的元素,例如Integeror String或or Employee。在原始的收集框架中,如果在代码中添加一些检查之前没有添加额外的检查,就不可能拥有同类收集。引入泛型来消除此限制是非常具体的。他们会在编译时自动在代码中添加这种类型的参数检查。这可以节省我们编写大量不必要的代码的时间,如果编写正确的话,这些代码实际上不会在运行时添加任何值。

“用通俗易懂的术语来说,泛型使用Java语言强制类型安全。”

没有这种类型的安全性,您的代码可能会感染各种错误,而这些错误只会在运行时才被发现。使用泛型,使它们在编译时本身突出显示,甚至在获得Java源代码文件的字节码之前,也使代码健壮。

“泛型通过在编译时检测到更多错误来增加代码的稳定性。”

因此,现在我们有了一个合理的想法,为什么泛型首先出现在java中。下一步是了解有关它们如何在Java中工作的知识。在源代码中使用泛型时实际发生的情况。

2)泛型在Java中的工作方式

泛型的核心是“ 类型安全 ”。类型安全到底是什么?编译器只是保证,如果在正确的位置使用了正确的类型,则ClassCastException在运行时不应有任何类型。用例可以是Integerie的列表List<Integer>。如果您在java中声明一个列表,例如List<Integer>,则java保证它将检测并报告您将任何非整数类型插入上述列表的任何尝试。

Java泛型中的另一个重要术语是“ 类型擦除 ”。从本质上讲,这意味着使用泛型添加到源代码中的所有额外信息将从其生成的字节码中删除。在字节码内部,它将是旧的Java语法,如果您根本不使用泛型,则会得到该语法。当未在语言中添加泛型时,这必然有助于生成和执行在Java 5之前编写的代码。

让我们看一个例子。

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);     

“准确地说,Java中的泛型不过是类型安全代码的语法糖,所有此类类型信息都将由编译器的类型擦除功能擦除。”

3)泛型的类型?

现在我们对泛型的意义有了一些了解。现在开始探索围绕泛型的其他重要概念。我将从确定各种方法开始,泛型可以应用于源代码。

通用类型类或接口

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

DemoClass是简单的Java类,具有一个属性t(也可以大于一个);属性的类型是Object。

class DemoClass {
   private Object t;

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

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

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

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类的构造函数也具有类型信息。因此,您可以仅具有单一类型的所有属性的维度实例。

4)通用类型数组

任何语言中的数组都具有相同的含义,即数组是元素类型相似的集合。在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[]并且可以通过type的引用变量访问字符串数组Object[]

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

5)带有通配符的泛型

在通用代码中,称为通配符的问号(?)表示未知类型。通配符参数化类型是通用类型的实例,其中至少一个类型参数是通配符。通配符参数化类型的例子有Collection<?<List<? extends Number<Comparator<? super String>Pair<String,?>。通配符可以在多种情况下使用:作为参数,字段或局部变量的类型;有时作为返回类型(尽管更具体的做法是更好的编程习惯)。通配符从不用作泛型方法调用,泛型类实例创建或超类型的类型参数。

在不同的地方使用通配符也具有不同的含义。例如

  • Collection表示Collection接口的所有实例化,而与type参数无关。
  • 列表表示所有列表类型,其中元素类型是Number的子类型。
  • Comparator<? super String< 表示Comparator接口的所有实例化,这些实例化类型为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;
   }
}

下界通配符

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

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

这里的问题是和的GrandChildClass子类型。因此,任何SuperClasses和ChildClasses的通用列表都可以容纳GrandChildClasses。在这里,我们必须使用’ super ‘关键字来使用下限通配符。ChildClassSuperClass

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{
   
}

6)不允许使用泛型?

到目前为止,我们已经了解了许多可以使用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可能不是subclass java.lang.Throwable

现在,这一切就此结束了这次有关Java泛型的讨论。在以后的文章中,我将提出与泛型相关的更多有趣的事实和功能。

如果有不清楚的地方,请给我评论/或者您还有其他问题。

saigon has written 1440 articles

Leave a Reply