Java Genrics 是 Java 5 中引入的最重要的功能之一。javascript
若是您一直在使用Java Collections并使用版本 5 或更高版本,那么我肯定您已经使用过它。java
Java 中具备集合类的泛型很是容易,可是它提供了比仅建立集合类型更多的功能。程序员
咱们将在本文中尝试学习泛型的功能。若是咱们使用专业术语,对泛型的理解有时会变得混乱,所以,我将尽可能保持其简单易懂。面试
Java 5 中添加了泛型,以提供编译时类型检查,并消除了ClassCastException
使用集合类时常见的风险。整个收集框架都进行了重写,以使用泛型进行类型安全。让咱们看看泛型如何帮助咱们安全地使用集合类。算法
List list = new ArrayList(); list.add("abc"); list.add(new Integer(5)); for(Object obj : list){ String str=(String) obj; }
上面的代码能够很好地编译,可是在运行时会引起ClassCastException,由于咱们试图将列表中的对象强制转换为String,而其中一个元素是Integer类型。在Java 5以后,咱们使用以下收集类。编程
List<String> list1 = new ArrayList<String>(); // java 7 ? List<String> list1 = new ArrayList<>(); list1.add("abc"); //list1.add(new Integer(5)); //编译错误 for(String str : list1){ //no type casting needed, avoids ClassCastException }
请注意,在建立列表时,咱们已指定列表中元素的类型为String。所以,若是咱们尝试在列表中添加任何其余类型的对象,则该程序将引起编译时错误。还要注意,在循环中中,咱们不须要列表中元素的类型转换,所以在运行时删除了ClassCastException。安全
咱们可使用泛型类型定义本身的类。泛型类型是经过类型进行参数化的类或接口。咱们使用尖括号(<>)来指定类型参数。markdown
为了了解其好处,咱们假设有一个简单的类:框架
package com.journaldev.generics; public class GenericsTypeOld { private Object t; public Object get() { return t; } public void set(Object t) { this.t = t; } public static void main(String args[]){ GenericsTypeOld type = new GenericsTypeOld(); type.set("Pankaj"); String str = (String) type.get(); //type casting, error prone and can cause ClassCastException } }
请注意,在使用此类时,咱们必须使用类型转换,而且它能够在运行时产生ClassCastException。如今,咱们将使用Java通用类替换以下所示的相同类。async
package com.journaldev.generics; public class GenericsType<T> { private T t; public T get(){ return this.t; } public void set(T t1){ this.t=t1; } public static void main(String args[]){ GenericsType<String> type = new GenericsType<>(); type.set("Pankaj"); //valid GenericsType type1 = new GenericsType(); //raw type type1.set("Pankaj"); //valid type1.set(10); //valid and autoboxing support } }
注意main方法中GenericsType类的使用。咱们不须要进行类型转换,而且能够在运行时删除ClassCastException。若是咱们在建立时未提供类型,则编译器将发出警告,“ GenericsType是原始类型。
泛型类型GenericsType
Object
,所以它容许String和Integer对象。可是,咱们应始终尝试避免这种状况,由于在处理可能产生运行时错误的原始类型时,咱们必须使用类型转换。
还要注意,它支持Java自动装箱。
Comparable接口是接口中泛型的一个很好的例子,它写为:
package java.lang; import java.util.*; public interface Comparable<T> { public int compareTo(T o); }
以相似的方式,咱们能够在Java中建立通用接口。咱们也能够像Map界面具备多个类型参数。一样,咱们也能够为参数化类型提供参数化值,例如new HashMap<String, List<String>>();
有效。
Java通用类型命名约定能够帮助咱们轻松理解代码,而且具备命名约定是Java编程语言的最佳实践之一。所以,泛型也带有本身的命名约定。一般,类型参数名称是单个大写字母,以能够实现与Java变量区分开。最经常使用的类型参数名称为:
有时咱们不但愿整个类都被参数化,在这种状况下,咱们能够建立java泛型方法。因为构造函数是一种特殊的方法,所以咱们也能够在构造函数中使用泛型类型。
这是一个显示Java泛型方法示例的类。
package com.journaldev.generics; public class GenericsMethods { //Java Generic Method public static <T> boolean isEqual(GenericsType<T> g1, GenericsType<T> g2){ return g1.get().equals(g2.get()); } public static void main(String args[]){ GenericsType<String> g1 = new GenericsType<>(); g1.set("Pankaj"); GenericsType<String> g2 = new GenericsType<>(); g2.set("Pankaj"); boolean isEqual = GenericsMethods.<String>isEqual(g1, g2); //above statement can be written simply as isEqual = GenericsMethods.isEqual(g1, g2); //This feature, known as type inference, allows you to invoke a generic method as an ordinary method, without specifying a type between angle brackets. //Compiler will infer the type that is needed } }
注意_的isEqual_方法签名显示了在方法中使用泛型类型的语法。另外,请注意如何在咱们的Java的程序中使用这些方法。咱们能够在调用这些方法时指定类型,也能够像普通方法同样调用它们。Java编译器足够聪明,能够肯定要使用的变量的类型,这种功能称为类型变量。
假设咱们要限制能够在参数化类型中使用的对象的类型,例如在比较两个对象的方法中,而且咱们要确保接受的对象是可比较的。要声明一个有界的类型参数,请列出类型参数的名称,而后列出扩展关键字,再加上其上限,如下下面的方法。
public static <T extends Comparable<T>> int compare(T t1, T t2){ return t1.compareTo(t2); }
这些方法的调用与无界方法相似,不一样之处在于,若是咱们尝试使用任何非Comparable的类,则引起编译时错误。
绑定类型参数能够与方法以及类和接口一块儿使用。
Java泛型也支持多个范围,即<T扩展A&B&C>。在这种状况下,A能够是接口或类。若是A是类,则B和C应该是接口。在多个范围内,咱们不能有多个类。
咱们知道,若是A是B的子类,则Java继承容许咱们将变量A分配给另外一个变量B。所以,咱们可能认为能够将A的任何泛型类型分配给B的泛型类型,但事实并不是如此。让咱们用一个简单的程序看看。
package com.journaldev.generics; public class GenericsInheritance { public static void main(String[] args) { String str = "abc"; Object obj = new Object(); obj=str; // works because String is-a Object, inheritance in java MyClass<String> myClass1 = new MyClass<String>(); MyClass<Object> myClass2 = new MyClass<Object>(); //myClass2=myClass1; // compilation error since MyClass<String> is not a MyClass<Object> obj = myClass1; // MyClass<T> parent is Object } public static class MyClass<T>{} }
咱们能够经过扩展或实现来泛型一个通用类或接口。一个类或接口的类型参数与另外一类或接口的类型参数之间的关系由extend和实现子句肯定。
例如,ArrayList
只要不更改type参数,子类型关系就会保留,下面显示了多个type参数的示例。
interface MyList<E,T> extends List<E>{ }
List
问号(?)是泛型中的通配符,表示未知类型。通配符能够用做参数,字段或局部变量的类型,有时还能够用做返回类型。在调用通用方法或实例化通用类时,不能使用通配符。在如下各节中,咱们将学习上界通配符,下界通配符和通配符捕获。
9.1)Java泛型上界通配符
上限通配符用于在方法中放宽对变量类型的限制。假设咱们要编写一个将返回列表中数字总和的方法,那么咱们的实现将是这样的。
public static double sum(List<Number> list){ double sum = 0; for(Number n : list){ sum += n.doubleValue(); } return sum; }
如今,上述实现的问题在于它不适用于Integers或Doubles,由于咱们知道List
能够像下面的程序同样修改上面的实现。
package com.journaldev.generics; import java.util.ArrayList; import java.util.List; public class GenericsWildcards { public static void main(String[] args) { List<Integer> ints = new ArrayList<>(); ints.add(3); ints.add(5); ints.add(10); double sum = sum(ints); System.out.println("Sum of ints="+sum); } public static double sum(List<? extends Number> list){ double sum = 0; for(Number n : list){ sum += n.doubleValue(); } return sum; } }
就像按照接口编写代码同样,在上述方法中,咱们可使用上限类号码的全部方法。请注意,对于上界列表,除空以外,咱们不容许将任何对象添加到列表中。若是咱们尝试在sum方法内将元素添加到列表中,则该程序将没法编译。
9.2)Java泛型无限制通配符
有时,咱们但愿通用方法适用于全部类型,在这种状况下,可使用无界通配符。与使用<?extends Object>。
public static void printData(List<?> list){ for(Object obj : list){ System.out.print(obj + "::"); } }
咱们能够为_PrintData_方法提供List
9.3)Java泛型下界通配符
假设咱们要在方法中将总体添加到整数列表中,咱们能够将参数类型保持为List