黑马程序员---泛型总结

来源:互联网 发布:原子能实验室玩具知乎 编辑:程序博客网 时间:2024/04/28 17:47

---------------------- Java开发、Android培训、期待与您交流! ----------------------


1、泛型总结

1.1 泛型

所谓泛型:就是允许在定义类、接口时指定类型形参,这个类型形参将在声明变量、创建对象时确定。JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。

(1) 定义泛型接口、类

public class Apple<T> {

    private T info;

    public Apple() {}

    public Apple(T info) {

        this.info = info;

    }

 

    public T getInfo() {

        return info;

    }

 

    public void setInfo(T info) {

        this.info = info;

    }

    public static void main(String[] args) {

        Apple<String> a1 = new Apple<String>("苹果");

        print(a1.getInfo());

 

        Apple<Double> a2 = new Apple<Double>(3.14);

        print(a2.getInfo());

    }

}

上面程序定义了一个带泛型声明的Apple<T>类,实际使用Apple<T>类时会为T形参传入实际类型,这样就可以生成Apple<String>、Apple<Double>…形式的多个逻辑子类(物理上并不存在)。

 

1.2 类型通配符

当使用一个泛型类时,应该为这个泛型类传入一个类型实参,如果没有传入类型实际参数,就会引起泛型警告。假如现在需要定义一个方法,该方法里有一个集合形象,集合形参的元素类型时不确定的,应该如何定义?

public void test(List c) {

for (int i=0; i<c.size(); i++) {

    System.out.println(c.get(i));

}

}

此处使用List没有传入实际类型参数,将引起泛型警告。考虑为List接口传入实际的类型参数,因为List集合里元素类型是不确定的,将上面的方法改为如下形式:

public void test(List<Object> c) {

for (int i=0; i<c.size(); i++) {

    System.out.println(c.get(i));

}

}

表面上看起来,上面的方法声明没有问题,这个方法声明却是没有任何问题。问题是调用该方法时出入实际参数值可能不如我们所期望的,以下代码试图调用该方法:

List<String> strList = new ArrayList<String>();

Test(strList);

The method test(List<Object>) in the type GenericTest is not applicable for the arguments (List<String>),编译时错误。

这意味着:List<String>对象不能被当成List<Object>对象使用,也就是说List<String>类不是List<Object>类的子类。

(1) 使用类型通配符

为了表示各种泛型List的父类,需要使用类型通配符,类型通配符是一个问号,将一个问号作为类型实参传给List集合,写成:List<?>。这个问号被称为通配符,他的元素类型可以匹配任何类型

public void test(List<?> c) {

for (int i=0; i<c.size(); i++) {

    System.out.println(c.get(i));

}

}

(2)设定类型通配符的上限

当直接使用List<?>这种形式时,即表明这个List集合可以是任何泛型类List的父类。但还有特殊情形:只想表示某一类泛型List的父类。



import java.util.ArrayList;

import java.util.List;

public class Canvas {

public static void main(String[] args) {

Canvas canvas = new Canvas();

List<Circle> circleList = new ArrayList<Circle>();

circleList.add(new Circle());

canvas.drawAll(circleList);

List<Rectangle> rectangleList = new ArrayList<Rectangle>();

rectangleList.add(new Rectangle());

canvas.drawAll(rectangleList);

}

/*

 * 使用设定通配符的上限

 * <? extends Shape>:Shape及其子类。

 */

public void drawAll(List<? extends Shape> shapes) {

for (Shape s : shapes) {

s.draw(this);

}

}

}

abstract class Shape {

public abstract void draw(Canvas c);

}

class Circle extends Shape {

@Override

public void draw(Canvas c) {

print("在画布上画一个圆。");

}

}

 

class Rectangle extends Shape {

@Override

public void draw(Canvas c) {

print("在画布上画一个矩形。");

}

}

其中List<? extends Shape>表示所有Shape泛型List的父类,Shape及其子类。此处?代表一个未知的类型,就像前面看到的通配符一样。只是此处我们知道这个未知类型总是Shape的一个子类(也可以是Shape本身)。也可以把Shape看成这个通配符的上限

(3)设定类型形参的上限

Java泛型不仅允许在使用通配符形参时设定类型上限,也可以在定义类型形参设定上限,用于表示传给该类型形参的实际类型必须是该上限类型,或是该上限类型的子类。


public class AppleTwo<T extends Number> {

col;

public static void main(String[] args) {

AppleTwo<Integer> a1 = new AppleTwo<Integer>();

AppleTwo<Double> a2 = new AppleTwo<Double>();

//引起编译异常

//因为String类型传给T形参,但String不是Number的子类型。

AppleTwo<String> a3 = new AppleTwo<String>();

}

}

 

Bound mismatch: The type String is not a valid substitute for the bounded parameter <T extends Number> of the type AppleTwo<T>

<T extends Number>,该Apple类的类型形参的上限是Number类,这表明使用Apple类是为T形参传入的实际类型参数只能是Number,或者Number的子类。

2、 泛型方法

前面介绍了在定义类、接口时可以使用类型形参,在该类的方法定义和属性定义、接口的方法定义中,这些类型参数可以被当成普通类型来用。在另外一些情况下,定义类、接口时没有使用类型形参,但定义方法时想自己定义类型形参,这是可以的。

2.1 定义泛型方法

加入要实现这样一个方法,负责将一个Object数组的所有元素添加到一个Collection集合中,用以下代码实现。

static void fromArrayToCollection(Object[] a, Collection<Object> c) {

for (Object o : a) {

c.add(o);

}

}

由于Collection<Object>不是Collection<…>类的父类,所以这个方法的功能非常有限,它只能将Object数组的元素复制到Object的Collection中,下面代码将引起编译错误:

String[] str = {"a", "b"};

List<String> strList = new ArrayList<String>();

fromArrayToCollection(str, strList);

错误提示如下:

The method fromArrayToCollection(Object[], Collection<Object>) in the type AppleTwo<T> is not applicable for the arguments (String[], List<String>)

 

JDK1.5提供了泛型方法。所谓泛型方法,就是在声明方法时定义一个或多个类型形参。泛型方法的用法格式是:

修饰符 <T, S> 返回值类型 方法名(形参列表) {

    //方法体…

}

将上面的方法改写为:

static <T> void fromArrayToCollection(T[] a, Collection<T> c) {

for (T o : a) {

c.add(o);

}

}

以下是完整的程序部分:


import java.util.ArrayList;

import java.util.Collection;

public class GenericMethodTest {

static <T> void fromArrayToCollection(T[] a, Collection<T> c) {

for (T t : a) {

c.add(t);

}

}

public static void main(String[] args) {

Object[] oa = new Object[100];

Collection<Object> co = new ArrayList<Object>();

//下面代码中T代表Object类型

fromArrayToCollection(oa, co);

String[] sa = new String[100];

Collection<String> cs = new ArrayList<String>();

//下面代码中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<Number>(); 

//下面代码中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);

}

}

 


import java.util.ArrayList;

import java.util.Collection;

import java.util.List;

 

public class Test {

 

static <T> void test(Collection<T> a, Collection<T> c) {

for (T o : a){

c.add(o);

}

}

public static void main(String[] args) {

List<Object> ao = new ArrayList<Object>();

List<String> as = new ArrayList<String>();

//下面代码将产生编译错误

test(as , ao);

}

}

以上代码将产生编译错误:

The method test(Collection<T>, Collection<T>) in the type Test is not applicable for the arguments (List<String>, List<Object>)

可以修改为以下代码:


 import java.util.ArrayList;

import java.util.Collection;

import java.util.List;

 

public class Test {

 

static <T> void test(Collection<? extends T> a, Collection<T> c){

for (T o : a){

c.add(o);

}

}

public static void main(String[] args) {

List<Object> ao = new ArrayList<Object>();

List<String> as = new ArrayList<String>();

test(as , ao);

}

}

2.2 泛型方法和类型通配符的区别

大多数时候可以使用泛型方法来代替类型通配符,例如对于JDK的Collection接口中两个方法定义:

public interface Collection<E> {

boolean containsAll(Collection<?> c);

boolean addAll(Collection<? extends E> c);

}

上面两个方法的形参都采用了类型通配符的形式,也可以采用泛型方法的形式来代替它,如下:

public interface Collection<E> {

<T> boolean  containsAll(Collection<T> c);

<T extends E> boolean addAll(Collection<T> c);

}

上面使用了<T extends E>泛型形式,这是定义类型形参时设定上限(其中E是Collection接口定义的类型形参,在该接口中E可以当成普通类型使用)。上面两个方法中类型形参T只使用了一次,类型形参T的唯一作用就是可以在不同的调用点传入不同的实际类型。对于这种情况,应该使用通配符:通配符就是被设计用来支持灵活的子类化的

泛型方法允许类型形参被用来表示方法的一个或多个参数之间的类型依赖关系,或者方法返回值与参数之间的类型依赖关系。如果没有这样的类型依赖关系,不应该使用泛型方法。

2.3设定通配符下限

假设自己实现一个工具方法:实现将src集合里的元素复制到dest集合里的功能,因为dest集合可以保存所有src集合里所有元素,所以dest集合元素的类型应该是src集合元素类型的父类。为了实现两个参数之间的类型依赖,考虑同时使用通配符、泛型参数来实现该方法:

public static <T> void copy(Collection<T> dest, Collection<? extends T> src) {

for (T ele : src) {

dest.add(ele);

}

}

上面的方法实现了前面的功能,现在假设该方法需要一个返回值,返回最后一个被复制的元素,代码如下:

public static <T> T copy(Collection<T> dest, Collection<? extends T> src) {

T last = null;

for (T ele : src) {

last = ele;

dest.add(ele);

}

return last;

}

问题:当遍历src集合的元素时,src元素的类型是不确定的(可以肯定时T的子类),程序只能用T来笼统地表示各种src集合的元素类型。

List<Number> ln = new ArrayList<Number>();

List<Integer> li = new ArrayList<Integer>();

Integer last = copy(ln, li);

 

编译错误:Type mismatch: cannot convert from Number to Integer

import java.util.ArrayList;

import java.util.List;

import java.util.Collection;

public class MyUtils {

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<Number>();

List<Integer> li = new ArrayList<Integer>();

li.add(5);

Integer last = copy(ln , li);

System.out.println(ln);

}

}

2.4 泛型方法与方法重载

如果一个类中包含以下两个方法:

public static <T> void copy(Collection<T> dest, Collection<? extends T> src) {

}  //(1)

public static <T> void copy( Collection<? extends T> dest, Collection<T> src) {

} //(2)

 

public static void main(String[] args) {

List<Number> ln = new ArrayList<Number>();

List<Integer> li = new ArrayList<Integer>();

copy(ln, li); //(3)

}

这两个方法的参数列表区分不很明确: (3)既可以匹配(1)的copy方法,此时T类型参数的类型是Number;也可以匹配(2)的copy方法,此时T的类型是Integer。编译器无法确定这行代码。

---------------------- Java开发Android培训、期待与您交流! ----------------------详细请查看:www.itheima.com


0 0