一、泛型概念
一般的类和方法,只能使用具体的类型,要么是基础类型,要么是自定义的类,接口等。泛型,按字面意思来理解就是泛化的类型。什么是泛化的类型呢,在面向对象里,继承是一种泛化机制,方法可以接受一个基类的参数,那么该基类延伸出来的所有子类都可以传递进来,这可以说是一种泛化,广泛化,通用化。由于Java的单继承和final类的不可继承,这种泛化是有很大限制的。接口呢,进一步扩大化了代码的表达能力,可以多继承。如果方法的参数是一个接口,这种限制就放松了很多,任何实现了该接口的类都可满足该方法。但有时候接口仍然不满足我们对于通用性代码的要求,因为一旦指明了接口,就必须使用这种特定的接口,有时候我们需要的是“某种不具体的类型”,而不是某个类或接口。 你知道它是一种类型,但具体是哪种还不确定,要到实际调用的时候才会确定使用哪种类型。由此,泛型实现了参数化类型的概念,使得代码可以应用于多种类型,具有更广泛的表达能力。
那么到底什么是泛型,给一个比较正式的定义。泛型:即参数化类型。将我们平时用的具体的类型参数化,类似于方法调用的形参,实参。所需要的类型定义的时候是不确定的,参数化的,就像形参一样。实际使用的时候再确定具体的类型,类比实参。
二、为什么使用泛型
举一个被举了无数遍的例子
List list = new ArrayList(); list.add("abc"); list.add("123"); list.add(100); for( int i = 0; i < list.size(); i++ ){ String val = (String) list.get(i); System.out.println("泛型测试"+ val) }
在编译期,这完全没有问题,编译器不会报错。list默认可以存入任何Object的子类,所以可以放入两个String类型和一个Integer类型。但运行时程序就会崩溃
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
泛型能够很好的解决这个问题,在编译期就能够发现这样的类型不匹配的问题。
List<String> list = new ArrayList<String>(); list.add("abc"); list.add("123"); list.add(100); ## compile error for( int i = 0; i < list.size(); i++ ){ String val = (String) list.get(i); System.out.println("泛型测试"+ val) }
编译器立刻就能够发现这样的错误。
三、泛型基本用法:泛型类、 泛型接口、 泛型方法
- 泛型类
- 泛型类基本语法
1 class 类名<泛型标识,可以使用任意的泛型标识,例如常用的T, K, V。 标识泛型所指定的类型>{ 2 private 泛型标识 val; 3 }
最简单的一个泛型类
public class Generic<T> { private T val; Generic(){ } Generic(T val){ this.val = val; } public T getVal(){ return val; } }
- 泛型类基本语法
- 泛型接口
- 泛型也可以用于接口上,常用用法为类的生成器,可以生成各种不同类型的对象。语法跟泛型类相同
public interface Generator<T> { public T generate(); }
// 泛型实现类这里又分两种:
// 1、实现类未传入泛型实参,那么实现类也必须将接口的泛型声明一起加入到类中来。
// class CustomGenerator implements Generator<T> 编译器会报错 public class CustomGenerator<T> implements Generator<T>{ private T val; CustomGenerator(T val){ this.val = val; } public T generate(){ return val; } public static void main(String[] args) { CustomGenerator<String> custGeneraotr = new CustomGenerator<String>("213"); System.out.println(custGeneraotr.generate()); } }// 2、传入泛型实参时,虽然我们只定义了一个泛型接口,但可以为 T 传入不同的实参形成许多不同的具体类生成器,
// 例如传入 String 得到 String 的生成器,传入 Intege r得到 Integer 的生成器。
// 传入泛型实参时,原接口中所有泛型标识都要替换为具体的泛型实参,例如原接口中的 public T generator()
// 需变为 public String generator();
public class StringGenerator implements Generator<String>{
@Override
public String generate() {
return "";
}
}
class IntegerGenerator implements Generator<Integer>{
@Override
public Integer generator(){
return 0;
}
}
- 泛型也可以用于接口上,常用用法为类的生成器,可以生成各种不同类型的对象。语法跟泛型类相同
- 泛型方法
泛型也可以用于单独的方法上,并不要求所在的类上泛型类。泛型类和泛型方法之间并没有必然的联系。但是有一个原则: 无论何时,只要你能做到,你就应该尽量使用泛型方法。
也就是说,如果使用泛型方法能够替代使用泛型类,你就应该只使用泛型方法,这样事情更简单明白。
另外,static 静态方法无法访问泛型类的类型参数,如果静态方法要使用泛型参数,那么就必须声明为泛型方法。
基本语法 public <泛型标识> 返回值 方法名(。。。){ ... 方法体 }
具体示例:
import java.util.HashMap; import java.util.Map; public class GenericTest{ // 这是一个泛型类 class Generics<T> { private T key; Generics(T val) { this.key = key; } /** * 首先这不是一个泛型方法,这只是泛型类里的一个普通成员方法, * 只不过它的返回值上泛型类声明过的泛型参数 * * @return */ public T getKey() { return key; } } /** * 这也不是一个泛型方法,它只不过接受一个泛型类做形参而已 * @param generics */ public void showKey(Generics<Integer> generics){ System.out.println(generics.getKey()); } /** * 这才是一个泛型方法 public 与返回值之间都 <T> 必不可少,这表明这是一个泛型方法。 * 当然方法的泛型参数可以有多个,也可以跟类泛型参数不同,方法里我可以用E,也可以用T * 例如 public <k,V> showKeyValue(){ ... } * @param <E> * @return */ public <E> E showKeyValue(Generics<E> container){ return container.getKey(); } //这也不是一个泛型方法,这也是一个普通的方法,只不过使用了泛型通配符? public void showKeyValue2(Generics<?> obj){ System.out.println("泛型测试 key value is " + obj.getKey().toString()); } }