一文看懂JAVA泛型

[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,?>

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

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

  • Collection表示Collection接口的所有实例化,无论类型参数如何。
  • List表示所有列表类型,其中元素类型是Number的子类型。
  • Comparator<? super String< 表示作为String超类型的类型参数类型的Comparator接口的所有实例化。

通配符参数化类型不是可以出现在新表达式中的具体类型。它只是暗示了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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容

  • “泛型”这个术语的意思是:"适用于许多许多的类型”。如何做到这一点呢,正是通过解耦类或方法与所使用的类型之间的约束...
    王侦阅读 1,131评论 0 0
  • Java 泛型是 Java 5 引入的一个重要特性,相信大多数 Java 开发者都对此不陌生,但是泛型背后的实现原...
    JohnnyShieh阅读 2,062评论 6 37
  • 一. 为什么需要泛型 先来说说一个简单的实例。 这个例子很简单,有一个仓库Store,里面储存了一个数据data,...
    wo883721阅读 773评论 0 3
  • 参考地址:《Java 泛型,你了解类型擦除吗?》 《Java中的逆变与协变》 《java 泛型中 T、E .....
    琦小虾阅读 3,006评论 0 11
  • 允许混沌,就是允许年轻 我很喜欢那些讲道理讲的很清楚的人,我觉得能把别人说明白的人,至少自己的人生是清晰的,当下既...
    Raintown阅读 326评论 4 4