深入理解 Java 泛型编程

文章目录

  • 1、避免在代码中对类型使用强制转换
  • 2、String 是 Object 的子类,但是 A 并不是 A 的子类
  • 3、通配符 ?
  • 4、
  • 5、泛型方法

JDK 5.0 引入了泛型,为什么要引入泛型,因为使用泛型可以编写泛型方法,可以让减少重复代码,让代码更简洁,更安全。

泛型是对代码进行抽象封装的一种手段,例如著名的 C++ 模板就是泛型机制。

1、避免在代码中对类型使用强制转换

不使用泛型

List myIntList = new LinkedList(); // 1
myIntList.add(new Integer(0)); // 2
Integer x = (Integer) myIntList.iterator().next(); // 3    

在代码中进行强制类型转换,既不优雅,又容易出错(runtime error)。

使用泛型

List<Integer> 
    myIntList = new LinkedList<Integer>(); // 1'
myIntList.add(new Integer(0)); // 2'
Integer x = myIntList.iterator().next(); // 3'

通过泛型的方式,可以使代码准确的表达编程者的意图,避免强制类型转换潜在的运行风险。例如上例中,通过泛型将集合中元素的类型限定为 Integer,不能往集合中添加任何非 Integer 类型的元素,也不允许在集合元素上调用任何非 Integer 类型的方法,否则会出现编码错误。

Java 是一种强类型、静态类型的编程语言,泛型语法的本质是提供一种机制,让编译器在编译代码时获取更多的类型元信息,避免代码在运行态出错。

另一方面,泛型语法也增强了代码的可读性,代码维护人员能更直观的获取对象的类型信息。

2、String 是 Object 的子类,但是 A 并不是 A 的子类

想一下,下面的代码合法吗?

List<String> ls = new ArrayList<String>(); // 1
ls.add(new Object()); // 2 

第一行,没问题!
第二行,会产生编译错误!

先思考一个问题,List 与 List 是什么关系?直觉上会认为,List 也是 List, 如同 String 对象也是 Object 对象一样。

当然,答案是否定的,为什么呢?因为:

String.class.isAssignableFrom(Object.class) == false;

在面向对象语言中子类对象可以赋值给父类引用,但是父类对象不允许赋值给子类的引用。我们可以将 String 对象添加到 List 链表中,但是不可以将 Object 对象添加到 List 链表中。

在学习泛型时,一定要理解这一点,因为它跟直觉相悖,容易犯错。

3、通配符 ?

所谓的通配符 ?,可以简单的理解为 type of unknown,也就是未知类型。可以用它来指代任意类型。

public void showError(Collection<Object> collection){
	System.out.println(collection.size());
}

public void showOk(Collection<?> collection){
	System.out.println(collection.size());
}

public void test(){
	showError(new ArrayList<String>()); // Compile error
	showOk(new ArrayList<String>());
}

另外,使用了通配符的集合,只能读,不能改, ? 表示元素类型为 unknown,无法判断赋值语句是否合法,因为在面向对象语言中子类对象可以赋值给父类引用,但是父类对象不允许赋值给子类的引用。

Collection<?> c = new LinkedList<String>();
c.add(new Object()); // Compile time error
((LinkedList<String>)c).add(0, "hello world"); // success 

上例中的集合 c,尽管使用了泛型的方式来声明和定义,但其本质仍然是 ArrayList 没有改变。如果想让 c 中添加元素,必须进行强制类型转换。

public void addRectangle(List<? extends Shape> shapes) {
       shapes.add(0, new Rectangle()); // Compile-time error!
}

上例中,为什么会产生编译错误?因为这里既然使用了通配符,就说明元素的真正类型是未知的,是 unknown,如果传入的实参类型为 LinkedList,那么调用 shapes.add() 时,必然会发生运行时错误,而通过泛型的方式,可以在编译期检测,来规避这种错误。

4、

使用了通配符的集合,是不能编辑的,因为通配符没有提供关于类型的准确信息,如果既要使用泛型,又要编辑对象,怎么办呢?

static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
    for (T o : a) {
        c.add(o); // Correct
    }
}

表示一切继承了 T 的 unknown 类型。

例如:

public void test(List<? extends Number> list){
	...
}

这里的 ? 可以是 Integer, Long, Float .., 但肯定不能为 String

5、泛型方法

当方法中需要改变泛型对象的属性,或者返回特定的类型时,需要定义泛型方法,而不是使用通配符。

static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
    for (T o : a) {
        c.add(o); // Correct
    }
}

好好理解下面例子中的各种情况:

Object[] oa = new Object[100];
Collection<Object> co = new ArrayList<Object>();

// T inferred to be Object
fromArrayToCollection(oa, co); 

String[] sa = new String[100];
Collection<String> cs = new ArrayList<String>();

// T inferred to be String
fromArrayToCollection(sa, cs);

// T inferred to be 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 inferred to be Number
fromArrayToCollection(ia, cn);

// T inferred to be Number
fromArrayToCollection(fa, cn);

// T inferred to be Number
fromArrayToCollection(na, cn);

// T inferred to be Object
fromArrayToCollection(na, co);

// compile-time error
fromArrayToCollection(na, cs);

泛型方法会做一定的类型推断。

你可能感兴趣的:(Java,Java,泛型)