泛型说明
泛型(generics)是JDK5 中引入的一个新特性,泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
泛型的本质是参数化类型,即给类型指定一个参数,然后在使用时再指定此参数具体的值,那样这个类型就可以在使用时决定了。这种参数类型可以用在类,接口和方法中,分别被称为泛型类,泛型接口,泛型方法。
为什么要使用泛型
泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率
安全性
//集合未使用泛型
List list = new ArrayList();
list.add("smile");
list.add(30);//编译正常
//集合使用泛型
List<String> newList = new ArrayList();
newList.add("smile");
newList.add(30);//编译不正常
使用泛型后,定义好的集合newList
在编译的时候,add(30)就会编译失败。
相当于告诉编译器每个集合接收的对象类型是什么,编译器在编译期就会做类型检查,告知是否插入了错误类型的对象,使程序更加安全,增强了程序的健壮性。
消除转换
//未使用泛型
List list = new ArrayList();
list.add("smile");
String name = (String) list.get(0);
//使用泛型
List<String> newList = new ArrayList();
newList.add("smile");
String newName = newList.get(0);
使用泛型的一个附带好处是,消除代码中许多强制类型转换,这使得代码更加可读,并减少出错机会
提升性能
在非泛型编程中,将简单的类型作为Object传递的时看会引起Boxing(装箱)和Unboxing(拆箱)操作,这两个过程都是具有很大开销的。引入泛型后,就不必进行Boxing和Unboxing操作了,所以运行效率相对较高,特别是在对集合操作非常频繁的系统中,这个特点带来的性能提升更加明显。
泛型变量固定了类型,使用的时候就已经知道是值类型还是引用类型,避免了不必要的装箱,拆箱操作。
重用性
提升了代码的重用性
使用泛型
泛型有三种使用方法
-
泛型类
将泛型定义在类上
public class className <T> {}
泛型类型必须是引用类型(非基本数据类型)
List<int> list = new ArrayList<>();//编译异常
使用泛型的数组,不能初始化
class GenericTest<T> { private T[] values; public void setValue(T value) { this.values = new T[3];//编译异常 } } //由于初始化的数组,需要开辟内存空间,在不清楚具体类型的时候,编译器无法开辟驻准确的空间大小
静态方法中不能使用类的泛型
class GenericTest<T> { private T value; public static void test(T value) {//编译异常 System.out.println(value); } } //由于静态方法独属于类,而类的泛型属于声明后的对象
静态属性中不能使用类的泛型
class GenericTest<T> { private static T value;//编译异常 public GenericTest(T value) { System.out.println(value); } } //由于静态属性独属于类,而类的泛型属于声明后的对象
定义泛型类,在类名后添加一对尖括号,并在尖括号中填写类型参数,参数可以有多个,多个参数使用
,
进行分隔public class className<T,E,V> {}
通常类型参数我们都使用大写的单个字母表示:
T: 任意类型type
E:集合中元素类型 element
K:key-value形式 key
V:key-value形式 value
泛型使用
public class Generic{ public static void main(String[] args) { GenericTest<String> generic = new GenericTest<>("smile"); System.out.println(generic.getValue()); } } //定义泛型 class GenericTest<T> { private T value; public GenericTest(T value) { this.value = value; } public T getValue() { return value; } public void setValue(T value) { this.value = value; } }
-
泛型接口
将泛型定义在接口上
public interface interfaceName <T> {}
泛型接口使用
-
继承使用
interface GenericInterfaceA <T> { T run(T value); } //不指定泛型类,默认使用Object interface GenericInterfaceD extends GenericInterfaceA { } //继续使用父接口的泛型,不指定具体的泛型 interface GenericInterfaceB<T> extends GenericInterfaceA<T> { } //指定具体的泛型类型 interface GenericInterfaceC extends GenericInterfaceA<String> { }
-
实现使用
//接口实现过程中,不指定具体的泛型类,默认使用Object class GenericClassA implements GenericInterfaceA{ @Override public Object run(Object value) { return null; } } //接口实现过程中,指定具体的泛型类 class GenericClassC implements GenericInterfaceA<String>{ @Override public String run(String value) { return null; } } //接口继承的时候,已经指定接口继承泛型,不需要额外指定 class GenericClassD implements GenericInterfaceC{ @Override public String run(String value) { return null; } }
-
-
泛型方法
将泛型定义在方法上
public <T> returnType functionName(T variableName) {}
泛型方法可以是普通方法,静态方法,或构造方法
class GenericClassA implements GenericInterfaceA{ @Override public Object run(Object value) { return null; } //构造方法 public <T> GenericClassA(T name){ System.out.println(name); } //普通方法 public <T> void testA(T name) { System.out.println(name); } //静态方法 public static <T> void testB(T name) { System.out.println(name); } }
是否拥有泛型方法,与其所在的类是否是泛型没有关系
使用泛型方法的时候,通常不必指明类型参数,因为编译器会为我们找出具体的类型。这称为类型参数推断(type argument inference)。类型推断只对赋值操作有效,其他时候并不起作用
public class Generic{ public static void main(String[] args) { GenericFunction genericFunction = new GenericFunction(); //会自动进行类型推断 genericFunction.run("smile"); } } class GenericFunction { public <T> void run(T name) { System.out.println(name); } }