【08-泛型】
|
泛型
泛型初衷
•Java集合不会知道我们需要用它来保存什么类型的对象,所以他们把集合设计成能保存任何类型的对 象,这样就具有很好的通用性。但这样做也带来两个问题:
–集合对元素类型没有任何限制,这样可能引发一些问题:例如想创建一个只能保存Dog对象的集 合,但程序也可以轻易地将Cat对象“丢”进去,所以可能引发异常。
–由于把对象“丢进”集合时,集合丢失了对象的状态信息,集合只知道它盛装的是Object,因此取 出集合元素后通常还需要进行强制类型转换。这种强制类型转换既会增加编程的复杂度、也可能引发 ClassCastException。
在集合中使用泛型
•在集合中使用泛型后带来如下优势: –程序再也不能“不小心”把其他对象“丢进”strList集合中; –程序更加简洁,集合自动记住所有集合元素的数据类型,从而无需对集合元素进行强制类型转换。 泛型
•所谓泛型:就是允许在定义类、接口指定类型形参,这个类型形参在将在声明变量、创建对象时确定 (即传入实际的类型参数,也可称为类型实参)。
•JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变 量、创建集合对象时传入类型实参,这就是前面程序看到List<String>和ArrayList<String>两种类 型。
从泛型类派生子类
•当创建了带泛型声明的接口、父类之后,可以为该接口创建实现类,或从该父类来派生子类,但值得 指出的是,当使用这些接口、父类时不能再包含类型形参。
•如果使用泛型类时没有传入实际的类型参数,Java编译器可能发出警告:使用了未经检查或不安全的 操作——这就是泛型检查的警告,
并不存在泛型类
•虽然可以把ArrayList<String>类当成ArrayList的子类,事实上ArrayList<String>类也确实是一 种特殊的ArrayList类,这个ArrayList<String>对象只能添加String对象作为集合元素。但实际上, 系统并没有为ArrayList<String>生成新的class文件,而且也不会把ArrayList<String>当成新类来 处理。
•实际上,泛型对其所有可能的类型参数,都具有同样的行为,从而可以把相同的类被当成许多不同的 类来处理。与此完全一致的是,类的静态变量和方法也在所有的实例间共享,所以在静态方法、静态初 始化、或者静态变量的声明和初始化中不允许使用类型形参。
•系统中并不会真正生成泛型类,所以instanceof运算符后不能使用泛型类。
类型通配符
•List<String>对象不能被当成List<Object>对象使用,也就是说:List<String>类并不是 List<Object>类的子类。
•数组和泛型有所不同:假设Foo是Bar的一个子类型(子类或者子接口),那么Foo[]依然是Bar[]的 子类型;但G<Foo>不是G<Bar>的子类型。
•为了表示各种泛型List的父类,我们需要使用类型通配符,类型通配符是一个问号(?),将一个问号 作为类型实参传给List集合,写作:List<?>(意思是未知类型元素的List)。这个问号(?)被称为通 配符,它的元素类型可以匹配任何类型。
设定类型通配符的上限
•使用List<?>这种形式是,即表明这个List集合可以是任何泛型List的父类。但还有一种特殊的情形, 我们不想这个List<?>是任何泛型List的父类,只想表示它是某一类泛型List的父类。
供了被限制的泛型通配符。被限制的泛型通配符的如下表示:
•List<? extends Shape>
设定类型形参的上限
•Java泛型不仅允许在使用通配符形参时设定类型上限,也可以在定义类型形参时设定上限,用于表示 创给该类型形参的实际类型必须是该上限类型,或是该上限类型的子类。 例如
• Apple<T extends Number>
泛型方法
•如果定义类、接口是没有使用类型形参,但定义方法时想自己定义类型形参,这也是可以的,JDK1.5 还提供了泛型方法的支持。
•泛型方法的语法格式为: –修饰符 <T , S> 返回值类型 方法名(形参列表) –{ – //方法体... –}
•泛型方法的方法签名比普通方法的方法签名多了类型形参声明,类型形参声明以尖括号括起来,多个 类型形参之间以逗号(,)隔开,所有类型形参声明放在方法修饰符和方法返回值类型之间。
使用泛型方法
•与类、接口中使用泛型参数不同的是,方法中的泛型参数无需显式传入实际类型参数,因为编译器根 据实参推断类型形参的值。它通常推断出最直接的类型参数。 泛型方法和类型通配符
•大多数时候都可以使用泛型方法来代替类型通配符。
•泛型方法允许类型形参被用来表示方法的一个或多个参数之间的类型依赖关系,或者方法返回值与参 数之间的类型依赖关系。如果没有这样的类型依赖关系,不应该使用泛型方法。
设定通配符的下限
•Java集合框架中的TreeSet<E>有一个构造器也用到了这种设定通配符下限的语法,如下所示: –TreeSet(Comparator<? super E> c)
擦除和转换
•在严格的泛型代码里,带泛型声明的类总应该带着类型参数。但为了与老的Java代码保持一致,也允 许在使用带泛型声明的类时不指定类型参数。如果没有为这个泛型类指定类型参数,则该类型参数被称 作一个raw type(原始类型),默认是该声明该参数时指定的第一个上限类型。
•当把一个具有泛型信息的对象赋给另一个没有泛型信息的变量时,则所有在尖括号之间的类型信息都 被扔掉了。比如说一个List<String>类型被转换为List,则该List对集合元素的类型检查变成了类型 变量的上限(即Object),这种情况被为擦除。
现在贴出相关代码:
public class DiamondTest {
public static void main(String[] args)
{
// Java自动推断出ArrayList的<>里应该是String
List<String> books = new ArrayList<>();
books.add("疯狂Java讲义");
books.add("疯狂Android讲义");
// 遍历books集合,集合元素就是String类型
books.forEach(ele -> System.out.println(ele.length()));
// Java自动推断出HashMap的<>里应该是String , List<String>
Map<String , List<String>> schoolsInfo = new HashMap<>();
// Java自动推断出ArrayList的<>里应该是String
List<String> schools = new ArrayList<>();
schools.add("斜月三星洞");
schools.add("西天取经路");
schoolsInfo.put("孙悟空" , schools);
// 遍历Map时,Map的key是String类型,value是List<String>类型
schoolsInfo.forEach((key , value) -> System.out.println(key + "-->" + value));
}
}
public class GenericList {
public static void main(String[] args)
{
// 创建一个只想保存字符串的List集合
List<String> strList = new ArrayList<String>(); // ①
strList.add("疯狂Java讲义");
strList.add("疯狂Android讲义");
// 下面代码将引起编译错误
strList.add(5); // ②
strList.forEach(str -> System.out.println(str.length())); // ③
}
}
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())); // ②
}
}
public class A1 extends Apple<String> {
// 正确重写了父类的方法,返回值
// 与父类Apple<String>的返回值完全相同
public String getInfo() {
return "子类" + super.getInfo();
}
/*
* // 下面方法是错误的,重写父类方法时返回值类型不一致 public Object getInfo() { return "子类"; }
*/
}
public class A2 extends Apple {
// 重写父类的方法
public String getInfo() {
// super.getInfo()方法返回值是Object类型,
// 所以加toString()才返回String类型
return super.getInfo().toString();
}
}
// 定义Apple类时使用了泛型声明
public class Apple<T> {
// 使用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<String> a1 = new Apple<>("苹果");
System.out.println(a1.getInfo());
// 由于传给T形参的是Double,所以构造器参数只能是Double或double
Apple<Double> a2 = new Apple<>(5.67);
System.out.println(a2.getInfo());
}
}
public class R<T> {
// 下面代码错误,不能在静态变量声明中使用类型形参
// static T info;
T age;
public void foo(T msg) {
}
// 下面代码错误,不能在静态方法声明中使用类型形参
// public static void bar(T msg){}
}
public class Apple<T extends Number> {
T col;
public static void main(String[] args) {
Apple<Integer> ai = new Apple<>();
Apple<Double> ad = new Apple<>();
// 下面代码将引起编译异常,下面代码试图把String类型传给T形参
// 但String不是Number的子类型,所以引发编译错误
Apple<String> as = new Apple<>(); // ①
}
}
public class ArrayErr {
public static void main(String[] args) {
// 定义一个Integer数组
Integer[] ia = new Integer[5];
// 可以把一个Integer[]数组赋给Number[]变量
Number[] na = ia;
// 下面代码编译正常,但运行时会引发ArrayStoreException异常
// 因为0.5并不是Integer
na[0] = 0.5; // ①
List<Integer> iList = new ArrayList<>();
// 下面代码导致编译错误
List<Number> nList = iList;
}
}
public class Canvas {
// // 同时在画布上绘制多个形状
// public void drawAll(List<Shape> shapes)
// {
// for (Shape s : shapes)
// {
// s.draw(this);
// }
// }
// 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<Circle> circleList = new ArrayList<Circle>();
Canvas c = new Canvas();
// 由于List<Circle>并不是List<Shape>的子类型,
// 所以下面代码引发编译错误
c.drawAll(circleList);
}
}
// 定义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 + "上");
}
}
// 定义一个抽象类Shape
public abstract class Shape {
public abstract void draw(Canvas c);
}
public class ErrorTest {
// 声明一个泛型方法,该泛型方法中带一个T类型形参
static <T> void test(Collection<T> from, Collection<T> to) {
for (T ele : from) {
to.add(ele);
}
}
public static void main(String[] args) {
List<Object> as = new ArrayList<>();
List<String> ao = new ArrayList<>();
// 下面代码将产生编译错误
test(as, ao);
}
}
class Foo {
public <T> 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<String> Foo("疯狂Android讲义");
// 显式指定泛型构造器中的T参数为String,
// 但传给Foo构造器的实参是Double对象,下面代码出错
new<String> Foo(12.3);
}
}
class MyClass<E> {
public <T> MyClass(T t) {
System.out.println("t参数的值为:" + t);
}
}
public class GenericDiamondTest {
public static void main(String[] args) {
// MyClass类声明中的E形参是String类型。
// 泛型构造器中声明的T形参是Integer类型
MyClass<String> mc1 = new MyClass<>(5);
// 显式指定泛型构造器中声明的T形参是Integer类型,
MyClass<String> mc2 = new<Integer> MyClass<String>(5);
// MyClass类声明中的E形参是String类型。
// 如果显式指定泛型构造器中声明的T形参是Integer类型
// 此时就不能使用"菱形"语法,下面代码是错的。
// MyClass<String> mc3 = new <Integer> MyClass<>(5);
}
}
public class GenericMethodTest {
// 声明一个泛型方法,该泛型方法中带一个T类型形参,
static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
for (T o : a) {
c.add(o);
}
}
public static void main(String[] args) {
Object[] oa = new Object[100];
Collection<Object> co = new ArrayList<>();
// 下面代码中T代表Object类型
fromArrayToCollection(oa, co);
String[] sa = new String[100];
Collection<String> cs = new ArrayList<>();
// 下面代码中T代表String类型
fromArrayToCollection(sa, cs);
// 下面代码中T代表Object类型
fromArrayToCollection(sa, co);
Integer[] ia = new Integer[100];
Float[] fa = new Float[100];
Number[] na = new Number[100];
Collection<Number> cn = new ArrayList<>();
// 下面代码中T代表Number类型
fromArrayToCollection(ia, cn);
// 下面代码中T代表Number类型
fromArrayToCollection(fa, cn);
// 下面代码中T代表Number类型
fromArrayToCollection(na, cn);
// 下面代码中T代表Object类型
fromArrayToCollection(na, co);
// 下面代码中T代表String类型,但na是一个Number数组,
// 因为Number既不是String类型,
// 也不是它的子类,所以出现编译错误
// fromArrayToCollection(na, cs);
}
}
class MyUtil<E> {
public static <Z> MyUtil<Z> nil() {
return null;
}
public static <Z> MyUtil<Z> cons(Z head, MyUtil<Z> tail) {
return null;
}
E head() {
return null;
}
}
public class InferenceTest {
public static void main(String[] args) {
// 可以通过方法赋值的目标参数来推断类型参数为String
MyUtil<String> ls = MyUtil.nil();
// 无需使用下面语句在调用nil()方法时指定类型参数的类型
MyUtil<String> mu = MyUtil.<String> nil();
// 可调用cons方法所需的参数类型来推断类型参数为Integer
MyUtil.cons(42, MyUtil.nil());
// 无需使用下面语句在调用nil()方法时指定类型参数的类型
MyUtil.cons(42, MyUtil.<Integer> nil());
// 希望系统能推断出调用nil()方法类型参数为String类型,
// 但实际上Java 8依然推断不出来,所以下面代码报错
// String s = MyUtil.nil().head();
String s = MyUtil.<String> nil().head();
}
}
public class MyUtils {
// 下面dest集合元素类型必须与src集合元素类型相同,或是其父类
public static <T> T copy(Collection<? super T> dest, Collection<T> src) {
T last = null;
for (T ele : src) {
last = ele;
dest.add(ele);
}
return last;
}
public static void main(String[] args) {
List<Number> ln = new ArrayList<>();
List<Integer> li = new ArrayList<>();
li.add(5);
// 此处可准确的知道最后一个被复制的元素是Integer类型
// 与src集合元素的类型相同
Integer last = copy(ln, li); // ①
System.out.println(ln);
}
}
public class RightTest {
// 声明一个泛型方法,该泛型方法中带一个T形参
static <T> void test(Collection<? extends T> from, Collection<T> to) {
for (T ele : from) {
to.add(ele);
}
}
public static void main(String[] args) {
List<Object> ao = new ArrayList<>();
List<String> as = new ArrayList<>();
// 下面代码完全正常
test(as, ao);
}
}
public class TreeSetTest {
public static void main(String[] args) {
// Comparator的实际类型是TreeSet的元素类型的父类,满足要求
TreeSet<String> ts1 = new TreeSet<>(new Comparator<Object>() {
public int compare(Object fst, Object snd) {
return hashCode() > snd.hashCode() ? 1
: hashCode() < snd.hashCode() ? -1 : 0;
}
});
ts1.add("hello");
ts1.add("wa");
// Comparator的实际类型是TreeSet元素的类型,满足要求
TreeSet<String> ts2 = new TreeSet<>(new Comparator<String>() {
public int compare(String first, String second) {
return first.length() > second.length() ? -1
: first.length() < second.length() ? 1 : 0;
}
});
ts2.add("hello");
ts2.add("wa");
System.out.println(ts1);
System.out.println(ts2);
}
}
class Apple<T extends Number> {
T size;
public Apple() {
}
public Apple(T size) {
this.size = size;
}
public void setSize(T size) {
this.size = size;
}
public T getSize() {
return this.size;
}
}
public class ErasureTest {
public static void main(String[] args) {
Apple<Integer> a = new Apple<>(6); // ①
// a的getSize方法返回Integer对象
Integer as = a.getSize();
// 把a对象赋给Apple变量,丢失尖括号里的类型信息
Apple b = a; // ②
// b只知道size的类型是Number
Number size1 = b.getSize();
// 下面代码引起编译错误
Integer size2 = b.getSize(); // ③
}
}
public class ErasureTest2 {
public static void main(String[] args) {
List<Integer> li = new ArrayList<>();
li.add(6);
li.add(9);
List list = li;
// 下面代码引起“未经检查的转换”的警告,编译、运行时完全正常
List<String> ls = list; // ①
// 但只要访问ls里的元素,如下面代码将引起运行时异常。
System.out.println(ls.get(0));
}
}
|


浙公网安备 33010602011771号