一、泛型初衷
Java集合不会知道我们需要用它来保存什么类型的对象,所以他们把集合设计成能保存任何类型的对象,只要就具有很好的通用性。但这样做也带来两个问题:
二、在集合中使用泛型
在集合中使用泛型后带来如下优势
–程序再也不能“不小心”把其他对象“丢进”strList集合中;
import java.util.*;public class ListErr{ public static void main(String[] args) { // 创建一个只想保存字符串的List集合 List strList = new ArrayList(); strList.add("疯狂Java讲义"); strList.add("疯狂Android讲义"); // "不小心"把一个Integer对象"丢进"了集合 strList.add(5); // ① strList.forEach(str -> System.out.println(((String)str).length())); // ② }}
①"不小心"把一个Integer对象"丢进"了集合
②引发ClassCastException异常。
三、什么是泛型
所谓泛型:就是允许在定义类、接口指定类型形参,这个类型形参在将在声明变量、创建对象时确定(即传入实际的类型参数,也可称为类型实参)。
四、泛型的“菱形”语法 <>
如下代码:
Listbooks = new ArrayList ();Map books = new ArrayList ();
在java 7 以前,<>中的粗体字代码都是必须的,但是现在可以不带粗体字代码。Java自动推断出ArrayList的<>里应该是String还是String,Integer。
现在改为:
Listbooks = new ArrayList<>();Map books = new ArrayList<>();
第一段和第二段代码是完全等价的。
泛型的简单应用:
import java.util.*;public class DiamondTest{ public static void main(String[] args) { // Java自动推断出ArrayList的<>里应该是String Listbooks = new ArrayList<>(); books.add("疯狂Java讲义"); books.add("疯狂Android讲义"); // 遍历books集合,集合元素就是String类型 books.forEach(ele -> System.out.println(ele.length())); // Java自动推断出HashMap的<>里应该是String , List Map > schoolsInfo = new HashMap<>(); // Java自动推断出ArrayList的<>里应该是String List schools = new ArrayList<>(); schools.add("斜月三星洞"); schools.add("西天取经路"); schoolsInfo.put("孙悟空" , schools); // 遍历Map时,Map的key是String类型,value是List 类型 schoolsInfo.forEach((key , value) -> System.out.println(key + "-->" + value)); }}
五、深入泛型
1.定义泛型接口、类
定义Apple类时使用了泛型声明
// 定义Apple类时使用了泛型声明public class Apple{ // 使用T类型形参定义实例变量 private T info; public Apple(){} // 下面方法中使用T类型形参来定义构造器 public Apple(T info) { this.info = info; } public void setInfo(T info) { this.info = info; } public T getInfo() { return this.info; } public static void main(String[] args) { // 由于传给T形参的是String,所以构造器参数只能是String Apple a1 = new Apple<>("苹果"); System.out.println(a1.getInfo()); // 由于传给T形参的是Double,所以构造器参数只能是Double或double Apple a2 = new Apple<>(5.67); System.out.println(a2.getInfo()); }}
2.从泛型派生子类
A1继承泛型类:
//使用泛型类时,为T形参传入String类类型public class A1 extends Apple{}//正确//使用泛型类时,没有为T形参传入实际类型参数,这会产生警告:泛型检查警告,使用了未经检查或不安全的操作public class A1 extends Apple{}//正确//apple类不能跟类型形参public class A1 extends Apple {}//错误
继承Apple类,T被String代替。子类会继承到String getInfo()和void setInfo()两个方法。
public class A1 extends Apple{ // 正确重写了父类的方法,返回值 // 与父类Apple 的返回值完全相同 public String getInfo() { return "子类" + super.getInfo(); } /* // 下面方法是错误的,重写父类方法时返回值类型不一致。从父类继承的应该是public String getinfo() public Object getInfo() { return "子类"; } */}
正确写法如下:
public class A2 extends Apple{ // 重写父类的方法 public String getInfo() { // super.getInfo()方法返回值是Object类型, // 所以加toString()才返回String类型 return super.getInfo().toString(); }}
3.并不存在泛型类
六、类型通配符
public void test(List
这段代码看上去没有任何问题,方法的声明也没有任何问题。但是问题在于:调用该方法传入的实际参数的值。例如:
//创建一个List对象List strList = new ArrayList<>(); //将strList作为参数调用test test(strList);
编译上面的程序,发生错误。
无法将Test中的test(java.util.list)应用于java.util.list
这说明List<String>对象不能被当成List<Object>对象使用,也就是说:List<String>类并不是List<Object>类的子类。
此外,数组和泛型有所不同:假设Foo是Bar的一个子类型(子类或者子接口),那么Foo[]依然是Bar[]的自类型;但G<Foo>不是G<Bar>的子类型。
七、?的用法
为了表示各种泛型List的父类,我们需要使用类型通配符,类型通配符是一个问号(?),将一个问号作为类型实参传给List集合,写作:List<?>(意思是未知类型元素的List)。这个问号(?)被称为通配符,它的元素类型可以匹配任何类型。
在“六”中的程序,将
public void test(List
改为:
public void test(List c){ for(int i=0;i
再次编译就没有了错误。
这里的?可谓什么都可以表示,是不是给它的权力太大了!! 当然我们有自己的解决办法:设定类型通配符的上限
// 定义一个抽象类Shapepublic abstract class Shape{ public abstract void draw(Canvas c);}
/ 定义Shape的子类Circlepublic class Circle extends Shape{ // 实现画图方法,以打印字符串来模拟画图方法实现 public void draw(Canvas c) { System.out.println("在画布" + c + "上画一个圆"); }}
// 定义Shape的子类Rectanglepublic class Rectangle extends Shape{ // 实现画图方法,以打印字符串来模拟画图方法实现 public void draw(Canvas c) { System.out.println("把一个矩形画在画布" + c + "上"); }}
上面定义了三个形状类,Sharp抽象父类,Circle类和Rectangle类继承了抽象类Sharp。
下面定义一个Canvas类,该画布类不同的形状。
import java.util.*;public class Canvas{ // 同时在画布上绘制多个形状 public void drawAll(List< Shape> shapes) { for (Shape s : shapes) { s.draw(this); } } public static void main(String[] args) { ListcircleList = new ArrayList (); Canvas c = new Canvas(); // 由于List 并不是List 的子类型, // 所以下面代码引发编译错误 c.drawAll(circleList); }}
修改如下:
import java.util.*;public class Canvas{ // 同时在画布上绘制多个形状,使用被限制的泛型通配符 public void drawAll(List shapes) { for (Shape s : shapes) { s.draw(this); } } public static void main(String[] args) { ListcircleList = new ArrayList (); Canvas c = new Canvas(); // 由于List 并不是List 的子类型,但是使用了通配符 // 所以下面代码正确 c.drawAll(circleList); }}
这段代码就没有了错误。
public class Apple{ T col; public static void main(String[] args) { Apple ai = new Apple<>(); Apple ad = new Apple<>(); // 下面代码将引起编译异常,下面代码试图把String类型传给T形参 // 但String不是Number的子类型,所以引发编译错误 Apple as = new Apple<>(); // ① }}
八、泛型方法
如果定义类、接口是没有使用类型形参,但定义方法时想自己定义类型形参,这也是可以的,JDK1.5还提供了泛型方法的支持。
九、泛型方法与类型通配符的区别
……待续