Java学习笔记——新特性-泛型
泛型,JDK1.5新加入的,解决数据类型的安全性问题,其主要原理是在类声明时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这样在类声明或实例化时只要指定好需要的具体的类型即可。
Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。同时,代码更加简洁、健壮。
1 泛型声明及定义
-
类
尖括号里的标识符只要符合一般命名规则即可,一般使用大写字母,常用 “T” 表示(Type的缩写)。
class ClassName<T>{ //使用T类型定义变量 private T t; //使用T类型定义构造器 public ClassName(T t){ this.t = t; } //使用T类型定义一般方法 public getT(){ return T; } public setT(T t){ this.t = t; } //static的方法中不能使用类的泛型 //public static void show(T t){ //} //不能在catch的参数使用泛型 //try{} //catch(T t){} }
public class ArrayList<E> extends AbstractList<E> implements List<E>, ... {}
-
接口
与类基本相同。
public interface Map<K, V> {} public interface List<E> extends Collection<E> {}
-
方法
public <E> List<E> fromArrayToList(E[] e, List<E> list){ list.clear(); for(E e1 : e) { list.add(e1); } return list; }
第一个表示了这是一个泛型方法(与其所在类是否为泛型类无关),之后的所有E都表示同一类型,调用时根据实参的类型确定E(当有多个形参与E有关时,要求传入的相应的几个实参类型之间应有一定的关系(继承等)否则会出错),所以至少得有一个与E类型有关的形参,该泛型方法才有意义。即使以下代码可以通过,但明显失去了泛型的意义:
public <ta1> ta1 method(int t) { // 同时可看出泛型的命名不一定得是一个字母 ta1 t1 = null; return t1; }
2 泛型类实例化和泛型方法调用
-
泛型类的实例化
List<String> strList = new ArrayList<String>(); Iterator<String> iterator = strList.iterator(); Map<String, Integer> map = new HashMap<>(); // new 后面的尖括号内的类型可省略 List strList1 = new ArrayList(); // 没有指定类型,则默认为Object,但还是会稍有差别,见后文 List list = new ArrayList<Integer>(); // 前面没有指定类型,即使后面的有指定也无效,同上 // List<int> list = new ArrayList<int>(); // 编译错误,T只能是类,不能用基本数据类型填充
-
方法调用
Integer[] integers = new Integer[] {1, 2, 3}; List<Integer> list = new ArrayList<Integer>(); System.out.println(fromArrayToList(integers, list)); // 调用上面定义的泛型方法
3 泛型和继承的关系
如果B是A的一个子类型(子类或者子接口),而G是具有泛型声明的类或接口,G<B>并不是G<A>的子类型!
比如:String是Object的子类,但是List<String>并不是List<Object>的子类。
List<Integer> list = new ArrayList<Integer>();
// List<Object> list1 = list; // 编译报错,泛型不同的引用不能相互赋值
List list2 = list; // 没有指明泛型时可以这样赋值,并且可以往其中加入其它类型的值,会改变原来的list
list2.add("AAA");
注意以下几种写法的差别:
public void printColl1(Collection coll) { // 可以接收所有Collection及其子类的参数
for(Object object : coll) {
System.out.println(object);
}
}
public <T> void printColl2(Collection<T> coll) { // 同上
for(T t : coll) {
System.out.println(t);
}
}
public void printColl3(Collection<Object> coll) { // 不能接收除Object的其他指定类型的Collection
for(Object object : coll) {
System.out.println(object);
}
}
总之如果要使用泛型,那就全部指定类型,这样就不会乱了。
4 通配符
通配符 ‘?’ 用在泛型中,表示各种泛型的父类。
比如List<?>是List<String>、List<Object>等各种泛型List的父类。
List<String> list = new ArrayList<String>();
List<?> list1 = list;
//可以读取声明为通配符的集合类的对象,读取永远是安全的,因为不管list的真实类型是什么,它包含的都是Object。
Iterator<?> iterator = list1.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
//不允许向声明为通配符的集合类中写入对象。唯一例外的是null
//list1.add("CC");
//list1.add(123);
list1.add(null);
3中的遍历还可以写成:
public void printColl4(Collection<?> coll) {
for(Object object : coll) {
System.out.println(object);
}
}
有限制的通配符:
<? extends A> 可以存放A及其子类
<? super A> 可以存放A及其父类
如:
<? extends Number>
(无穷小 , Number]
只允许泛型为Number及Number子类的引用调用
<? super Number>
[Number , 无穷大)
只允许泛型为Number及Number父类的引用调用
<? extends Comparable>
只允许泛型为实现Comparable接口的实现类的引用调用
5 总结
当没有泛型时,使用集合时没有指定元素的类型,所有类型的对象都可以往里边加入,当开发需要限定某个集合的类型时,强制类型转换不够方便,并且类型不安全(什么类型的数据都可以往里边加,强转时可能出处)。加入泛型之后,可以指定集合元素的类型,可以在编译阶段就进行类型的判断,保证了类型的安全。为了充分发挥类型安全这个作用,不同泛型之间的引用自然不能相互赋值,即使其指定类型有继承关系也不行,因为这样的话又会再次导致类型不安全。有泛型的类最好指定一个类型,否则无法充分发挥其优势,若不指定类型,则使用通配符 “?”,这样可以接收所有其他类型,并且只能读取其数据,不能往里面加入数据(这样保证了类型安全)。
此外,泛型也使得代码可复用性提高,开发时可考虑使用泛型使得代码更加简洁容易维护。
以上笔记参考自尚硅谷