1、概述java
泛型在Java中有很重要的地位,在面相对象编程及各类设计模式中有很是普遍的应用。编程
什么是泛型?为何要使用泛型?设计模式
泛型,即“参数化类型”。一提到参数,定义方法时有形参,调用方法时传递实参。数组
泛型的本质是为了参数化类型(在不建立新的类型的状况下,经过泛型指定的不一样类型来控制形参具体限制的类型)。app
2、具体实例dom
package OSChina.Genericity; import java.util.Random; public class FruitGenerator<T> implements Generator<T>{ String[] fruits = new String[]{"apple","banana","Pear"}; @Override public String next(T t) { Random random = new Random(); System.out.println(fruits[random.nextInt((int)t)]); return fruits[random.nextInt((int)t)]; } public static void main(String[] args) { FruitGenerator ff = new FruitGenerator(); ff.next(3); } }
崩溃了。ide
然而为何呢?测试
ArrayList能够存听任意类型,例子中添加了一个String类型,添加了一个Integer类型,再使用时都以String的方式使用,所以程序崩溃了。为了解决相似这样的问题(在编译阶段就能够解决),泛型应运而生。ui
咱们将第一行声明初始化list的代码更改一下,编译器会在编译阶段就可以帮咱们发现相似这样的问题。this
定义泛型以后,编译都通不过了,要的就是这个效果!
3、特性
泛型只在编译阶段有效。看下面的代码:
package OSChina.Genericity; import java.util.ArrayList; import java.util.List; public class Test2 { public static void main(String[] args) { List<String> list = new ArrayList<String>(); List<Integer> list2 = new ArrayList<Integer>(); System.out.println(list.getClass()); System.out.println(list.getClass()==list2.getClass()); } }
4、泛型的使用
泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法
(一)泛型类
package OSChina.Genericity; //此处T能够随便写为任意标识,常见的如T、E、K、V等形式的参数经常使用于表示泛型 //在实例化泛型类时,必须指定T的具体类型 public class Generic<T> { //key这个成员变量的类型为T,T的类型由外部指定 private T key; //泛型构造方法形参key的类型也为T,T的类型由外部指定 public Generic(T key){ this.key = key; } //泛型方法getKey的返回值类型为T,T的类型由外部指定 public T getKey(){ return key; } public static void main(String[] args) { //泛型的类型参数只能是类类型(包括自定义类),不能是简单类型 //传入的实参类型需与泛型的类型参数类型相同,即为Integer. Generic<Integer> genericInteger = new Generic<Integer>(123456); //传入的实参类型需与泛型的类型参数类型相同,即为String. Generic<String> genericString = new Generic<String>("江疏影"); System.out.println("泛型测试,key is "+genericInteger.getKey()); System.out.println("泛型测试,key is "+genericString.getKey()); } }
泛型参数就是随便传的意思!
Generic generic = new Generic("111111"); Generic generic1 = new Generic(4444); Generic generic2 = new Generic(55.55); Generic generic3 = new Generic(false);
instanceof不容许存在泛型参数
如下代码不能经过编译,缘由同样,泛型类型被擦除了
(二)泛型接口
泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各类类的生产器中,能够看一个例子:
//定义一个泛型接口 public interface Generator<T> { public T next(); }
当实现泛型接口的类,未传入泛型实参时:
/** * 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一块儿加到类中 * 即:class FruitGenerator<T> implements Generator<T>{ * 若是不声明泛型,如:class FruitGenerator implements Generator<T>,编译器会报错:"Unknown class" */ class FruitGenerator<T> implements Generator<T>{ @Override public T next() { return null; } }
当实现泛型接口的类,传入泛型实参时:
package OSChina.Genericity; import java.util.Random; public class FruitGenerator implements Generator<String>{ String[] fruits = new String[]{"apple","banana","Pear"}; @Override public String next() { Random random = new Random(); System.out.println(fruits[random.nextInt(3)]); return fruits[random.nextInt(3)]; } public static void main(String[] args) { FruitGenerator ff = new FruitGenerator(); ff.next(); } }
(三)泛型通配符
咱们知道integer是number的一个子类,同时Generic<Integer>和Generic<Number>其实是相同的一种基本类型。那么问题来了,在使用Generic<Number>做为形参的方法中,可否使用Generic<Integer>的实例传入呢?在逻辑上相似于Generic<Number>和Generic<Integer>是否能够当作具备父子关系的泛型类型呢?
为了弄清楚这个问题,咱们使用Generator<T>这个泛型类继续看下面的例子:
public void showKeyValue(Generic<Number> obj){ Log.d("泛型测试","key value is " + obj.getKey()); }
Generic<Integer> gInteger = new Generic<Integer>(123); Generic<Number> gNumber = new Generic<Number>(456); showKeyValue(gNumber); // showKeyValue这个方法编译器会为咱们报错:Generic<java.lang.Integer> // cannot be applied to Generic<java.lang.Number> // showKeyValue(gInteger);
经过提示信息咱们能够看到Generic<Integer>不能被看做为Generic<Number>的子类。由此能够看出:同一种泛型能够对应多个版本(由于参数类型是不肯定的),不一样版本的泛型类实例是不兼容的。
回到上面的例子,如何解决上面的问题?总不能为了定义一个新的方法来处理Generic<Integer>类型的类,这显然与java中的多台理念相违背。所以咱们须要一个在逻辑上能够表示同时是Generic<Integer>和Generic<Number>父类的引用类型。由此类型通配符应运而生。
咱们能够将上面的方法改一下:
public void showKeyValue(Generic<?> obj){ Log.d("泛型测试","key value is " + obj.getKey()); }
类型通配符通常是使用?代替具体的类型参数,注意了,此处?是类型实参,而不是类型形参。此处的?和Number、String、Integer同样都是一种实际的类型,能够把?当作全部类型的父类。是一种真实的类型。
能够解决当具体类型不肯定的时候,这个通配符就是 ? ;当操做类型时,不须要使用类型的具体功能时,只使用Object类中的功能。那么能够用 ? 通配符来表未知类型。
(四)泛型方法
泛型类,是在实例化类的时候指明泛型的具体类型;
泛型方法,是在调用方法的时候指明泛型的具体类型。
/** * 泛型方法的基本介绍 * @param tClass 传入的泛型实参 * @return T 返回值为T类型 * 说明: * 1)public 与 返回值中间<T>很是重要,能够理解为声明此方法为泛型方法。 * 2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并非泛型方法。 * 3)<T>代表该方法将使用泛型类型T,此时才能够在方法中使用泛型类型T。 * 4)与泛型类的定义同样,此处T能够随便写为任意标识,常见的如T、E、K、V等形式的参数经常使用于表示泛型。 */ public <T> T genericMethod(Class<T> tClass)throws InstantiationException , IllegalAccessException{ T instance = tClass.newInstance(); return instance; }
Object obj = genericMethod(Class.forName("com.test.test"));
一、泛型方法的基本用法
public class GenericTest { //这个类是个泛型类,在上面已经介绍过 public class Generic<T>{ private T key; public Generic(T key) { this.key = key; } //我想说的实际上是这个,虽然在方法中使用了泛型,可是这并非一个泛型方法。 //这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。 //因此在这个方法中才能够继续使用 T 这个泛型。 public T getKey(){ return key; } /** * 这个方法显然是有问题的,在编译器会给咱们提示这样的错误信息"cannot reslove symbol E" * 由于在类的声明中并未声明泛型E,因此在使用E作形参和返回值类型时,编译器会没法识别。 public E setKey(E key){ this.key = keu } */ } /** * 这才是一个真正的泛型方法。 * 首先在public与返回值之间的<T>必不可少,这代表这是一个泛型方法,而且声明了一个泛型T * 这个T能够出如今这个泛型方法的任意位置. * 泛型的数量也能够为任意多个 * 如:public <T,K> K showKeyName(Generic<T> container){ * ... * } */ public <T> T showKeyName(Generic<T> container){ System.out.println("container key :" + container.getKey()); //固然这个例子举的不太合适,只是为了说明泛型方法的特性。 T test = container.getKey(); return test; } //这也不是一个泛型方法,这就是一个普通的方法,只是使用了Generic<Number>这个泛型类作形参而已。 public void showKeyValue1(Generic<Number> obj){ Log.d("泛型测试","key value is " + obj.getKey()); } //这也不是一个泛型方法,这也是一个普通的方法,只不过使用了泛型通配符? //同时这也印证了泛型通配符章节所描述的,?是一种类型实参,能够看作为Number等全部类的父类 public void showKeyValue2(Generic<?> obj){ Log.d("泛型测试","key value is " + obj.getKey()); } /** * 这个方法是有问题的,编译器会为咱们提示错误信息:"UnKnown class 'E' " * 虽然咱们声明了<T>,也代表了这是一个能够处理泛型的类型的泛型方法。 * 可是只声明了泛型类型T,并未声明泛型类型E,所以编译器并不知道该如何处理E这个类型。 public <T> T showKeyName(Generic<E> container){ ... } */ /** * 这个方法也是有问题的,编译器会为咱们提示错误信息:"UnKnown class 'T' " * 对于编译器来讲T这个类型并未项目中声明过,所以编译也不知道该如何编译这个类。 * 因此这也不是一个正确的泛型方法声明。 public void showkey(T genericObj){ } */ public static void main(String[] args) { } }
二、类中的泛型方法
固然这并非泛型方法的所有,泛型方法能够出现杂任何地方和任何场景中使用。可是有一种状况是很是特殊的,当泛型方法出如今泛型类中时,咱们再经过一个例子看一下
public class GenericFruit { class Fruit{ @Override public String toString() { return "fruit"; } } class Apple extends Fruit{ @Override public String toString() { return "apple"; } } class Person{ @Override public String toString() { return "Person"; } } class GenerateTest<T>{ public void show_1(T t){ System.out.println(t.toString()); } //在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E能够为任意类型。能够类型与T相同,也能够不一样。 //因为泛型方法在声明的时候会声明泛型<E>,所以即便在泛型类中并未声明泛型,编译器也可以正确识别泛型方法中识别的泛型。 public <E> void show_3(E t){ System.out.println(t.toString()); } //在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,能够与泛型类中声明的T不是同一种类型。 public <T> void show_2(T t){ System.out.println(t.toString()); } } public static void main(String[] args) { Apple apple = new Apple(); Person person = new Person(); GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>(); //apple是Fruit的子类,因此这里能够 generateTest.show_1(apple); //编译器会报错,由于泛型类型实参指定的是Fruit,而传入的实参类是Person //generateTest.show_1(person); //使用这两个方法均可以成功 generateTest.show_2(apple); generateTest.show_2(person); //使用这两个方法也均可以成功 generateTest.show_3(apple); generateTest.show_3(person); } }
三、泛型方法与可变参数
若是静态方法要使用泛型的话,必须将静态方法也定义成泛型方法 。
package OSChina.Genericity; public class GenericFruit { //静态方法中使用泛型,必需要将泛型定义在方法上。 public static <T> void printMsg(T...args){ for(T t:args){ System.out.println("泛型测试,it is "+t); } } public static void main(String[] args) { printMsg("1111",2222,"江疏影","0.00",55.55); } }
五、泛型方法总结
尽可能使用泛型方法!
(五)泛型上下边界
为泛型添加上边界,即传入的类型实参必须是指定类型的子类型。
static?
报错啦!String类型不是Number类型的子类
泛型的上下边界添加,必须与泛型的声明在一块儿 。
(六)关于泛型数组要提一下
看到了不少文章中都会提起泛型数组,通过查看sun的说明文档,在java中是”不能建立一个确切的泛型类型的数组”的。
也就是说下面的这个例子是不能够的:
使用通配符建立泛型数组是能够的
List<?>[] ls = new ArrayList<?>[10];
下面采用通配符的方式是被容许的:数组的类型不能够是类型变量,除非是采用通配符的方式,由于对于通配符的方式,最后取出数据是要作显式的类型转换的。