在没有泛型之前,一旦把一个对象丢进java集合中,集合就会忘记对象的类型,把所有的对象当成Object类型处理,当程序从集合中取出对象后,就需要进行强制类型转换,这种强制类型转换不仅使代码臃肿,而且容易引起ClassCastExeception异常。 Java泛型还增强了枚举类,反射等方面的功能.
一.泛型入门
1.编译时不检查类型的异常
public class ListErr
{
public static void main(String[] args)
{
// 创建一个只想保存字符串的List集合
List strList = new ArrayList();
strList.add("疯狂Java讲义");
strList.add("疯狂Android讲义");
// "不小心"把一个Integer对象"丢进"了集合
strList.add(5); // ①
//将会导致程序在此处引发ClassCastException异常,
//因为程序试图把一个Interger对象转换为String类型
strList.forEach(str -> System.out.println(((String)str).length())); // ②
}
}
2.使用泛型
Java的参数化类型被称为泛型;
public class GenericList
{
public static void main(String[] args)
{
// 创建一个只想保存字符串的List集合
List strList = new ArrayList(); // ①
strList.add("疯狂Java讲义");
strList.add("疯狂Android讲义");
// 下面代码将引起编译错误
strList.add(5); // ②
strList.forEach(str -> System.out.println(str.length())); // ③
}
}
创建这种特殊集合的方法是:在集合接口,类后增加尖括号,尖括号里放一个数据类型,即表明这个集合接口,集合类只能保存特定类型的对象。
3.Java7泛型的菱形语法
以前的定义如下:
ListstrList = new ArrayList (); Map scores = new HashMap ();
Java7之后允许构造器后不需要带完整的泛型信息,只要给出一对尖括号即可,系统自动推断出具体类型。
ListstrList = new ArrayList<>(); Map scores = new HashMap<>();
public class DiamondTest
{
public static void main(String[] args)
{
// Java自动推断出ArrayList的<>里应该是String
List books = 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类就可以包含一个泛型声明。
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.从泛型类派生子类
当创建了带泛型声明的接口,父类之后,可以为该接口创建实现类,或从该父类派生子类,需要指出的是,但使用这些接口,父类是不能在包含类型形参。如下代码就是错误:
//定义类A继承Apple类,Apple类不能跟类型形参 public class A extends Apple{}
定义方法时可以声明数据形参,调用方法时必须为这些数据形参传入实际的数据,与此类似的是,定义类,接口,方法时可以声明类型形参,使用类,接口,方法时应该为类型形参传入实际的类型。
可以改为如下:
//使用Apple类时为T形参传入String类型 public class A extends Apple
调用方法时必须为所有的数据形参传入参数值,与调用方法不同的是,使用类,接口时也可以不为类型形参传入实际的类型参数,即下面代码也是正确的:
//使用Apple类时,没有为T形参传入实际的类型参数 public class A extends Apple
public class A1 extends Apple{ // 正确重写了父类的方法,返回值 // 与父类Apple 的返回值完全相同 public String getInfo() { return "子类" + super.getInfo(); } }
3.并不存在泛型类
不管为泛型的类型形参传入哪一种类型实参,对于Java来说,他们依然被当成同一个类处理,在内存中也只占用一块内存空间,因此在静态方法,静态初始化块或者静态变量的声明和初始化中不允许使用类型形参,下面演示这种错误:
public class R{ // 下面代码错误,不能在静态变量声明中使用类型形参 // static T info; T age; public void foo(T msg){} // 下面代码错误,不能在静态方法声明中使用类型形参 // public static void bar(T msg){} }
由于系统中并不会真正生成泛型类,所以instanceof运算符后不能使用泛型类。如下
java.util.Collectioncs = new java.util.ArrayList<>(); // 下面代码编译时引起错误:instanceof运算符后不能使用泛型 if (cs instanceof java.util.ArrayList ) {...}
三.类型通配符
1.使用类型通配符
为了表示各种泛型List的父类,可以使用类型通配符,类型通配符是一个问号?,将一个问号作为类型实参传给List集合,它的元素类型可以匹配任何类型。
public void test(List> c) {
for (int i = 0; i < c.size(); i++) {
System.out.println(c.get(i));
}
}
但这种带通配符的List仅表示它是各种泛型List的父类,并不能把元素加入到其中,如下代码会引起编译错误
List> c = new ArrayList(); // 下面程序引起编译错误 c.add(new Object());
另一方面,程序可以调用get方法来返回List>集合指定索引处的元素,其返回值是一个未知类型,但可以肯定的是,它总是一个Object。
2.设定类型通配符的上限
程序不希望这个List>是任何泛型List的父类,只希望它代表某一类泛型List的父类。
// 定义一个抽象类Shape
public abstract class Shape
{
public abstract void draw(Canvas c);
}
// 定义Shape的子类Circle
public class Circle extends Shape
{
// 实现画图方法,以打印字符串来模拟画图方法实现
public void draw(Canvas c)
{
System.out.println("在画布" + c + "上画一个圆");
}
}
// 定义Shape的子类Rectangle
public class Rectangle extends Shape
{
// 实现画图方法,以打印字符串来模拟画图方法实现
public void draw(Canvas c)
{
System.out.println("把一个矩形画在画布" + c + "上");
}
}
接下来就是如何定义一个Canvas类
public class Canvas
{
// 方法一:由于List并不是List的子类型
//因此在c.drawAll(circleList)会引起编译错误
// // 同时在画布上绘制多个形状
// public void drawAll(List shapes)
// {
// for (Shape s : shapes)
// {
// s.draw(this);
// }
// }
// 方法二: 这个方法的drawAll可以接受List对象作为参数
// 问题是上面的方法实现体显得极为臃肿:使用泛型还需要进行强制类型转换
// public void drawAll(List> shapes)
// {
// for (Object obj : shapes)
// {
// Shape s = (Shape)obj;
// s.draw(this);
// }
// }
// 同时在画布上绘制多个形状,使用被限制的泛型通配符
public void drawAll(List extends Shape> shapes)
{
for (Shape s : shapes)
{
s.draw(this);
}
}
public static void main(String[] args)
{
List circleList = new ArrayList();
Canvas c = new Canvas();
// 由于List并不是List的子类型,
// 所以下面代码引发编译错误
c.drawAll(circleList);
}
}
类似地,由于程序无法确定这个受限制的通配符的具体类型,所以不能把Shape对象或子类的对象加入这个泛型集合中,如下代码就是错误的
public void addRectangle(List extends Shape> shapes) {
//下面代码引起编译错误
shapes.add(0, new Rectangle());
}
3.设定类型形参的上限
Java泛型不仅允许在使用通配符形参时设定上限,而且可以在定义类型形参时设定上限,用于表示传给该类型形参的实际类型要么是该上限类型,要么是该上限类型的子类。
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<>(); // ① } }
四.泛型方法
1.定义泛型方法
比如将一个Object数组的所有元素添加到一个Collection集合中,可以考虑如下代码来实现:
static void fromArrayToCollection(Object[] a, Collection
这个方法功能非常有限,它只能将Object[]数组的元素复制到元素为Object(Object的子类不行)的Collection集合中,即一下代码将引起编译错误:
String[] strArr = {"a", "b"};
List strList = new ArrayList<>();
//Collection对象不能当成Collection
为此java5提供的泛型方法,就是在声明方法时定义一个或多个类型形参。
格式如下:
修饰符返回值类型 方法名(形参列表) { // 方法体.... }
采用支持泛型的方法,就可以将上面的fromArrayToCollection方法改为如下形式:
staticvoid fromArrayToCollection(T[] a, Collection c) { for (T o:a) { c.add(o); } }
public class GenericMethodTest
{
// 声明一个泛型方法,该泛型方法中带一个T类型形参,
static void fromArrayToCollection(T[] a, Collection c)
{
for (T o : a)
{
c.add(o);
}
}
public static void main(String[] args)
{
Object[] oa = new Object[100];
Collection
为了让编译器能准确推断出泛型方法中类型形参的类型,
public class ErrorTest
{
// 声明一个泛型方法,该泛型方法中带一个T类型形参
static void test(Collection from, Collection to)
{
for (T ele : from)
{
to.add(ele);
}
}
public static void main(String[] args)
{
List
2.泛型方法和类型通配符的区别
大多数时候都可以使用泛型方法来代替类型通配符:
public interface Collection{ boolean containsAll(Collection> c); boolean addAll(Collection extends E> c); ... }
上面集合中两个方法的形参都采用了类型通配符的形式,也可以采用泛型方法的形式,如下所示:
public interface Collection{ boolean containsAll(Collection c); boolean addAll(Collection c); ... }
3.Java7的菱形语法与泛型构造器
正如泛型方法允许在方法签名中声明类型形参一样,Java也允许在构造器签名中声明类型形参,这样就产生了所谓的泛型构造器
一旦定义了泛型构造器,接下来在调用构造器时,不仅可以让Java根据数据参数的类型来推断类型形参的类型,也可以显示地为构造器中的类型形参指定实际的类型。如下所示
class Foo
{
public Foo(T t)
{
System.out.println(t);
}
}
public class GenericConstructor
{
public static void main(String[] args)
{
// 泛型构造器中的T参数为String。
new Foo("疯狂Java讲义");
// 泛型构造器中的T参数为Integer。
new Foo(200);
// 显式指定泛型构造器中的T参数为String,
// 传给Foo构造器的实参也是String对象,完全正确。
new Foo("疯狂Android讲义");
// 显式指定泛型构造器中的T参数为String,
// 但传给Foo构造器的实参是Double对象,下面代码出错
new Foo(12.3);
}
}



