[笔记] 疯狂JAVA讲义(第3版)第9章 泛型

第9章 泛型

泛型很大程度上是为了让集合能记住其元素的数据类型。

9.1 泛型入门

9.1.1 编译时不检查类型的异常

//ListErr.java
package ch9;

import java.util.ArrayList;
import java.util.List;

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对象丢进List集合,导致强制类型转换成String时引发ClassCastException异常。

9.1.2 使用泛型

java5 后引入了“参数化类型”(parameterized type)的概念,允许程序在创建集合时指定集合元素的类型。如List<String>,表明该List只能存放String类型对象。
Java的参数化类型被称为泛型(Generic)。

对于前面的ListErr程序,可以使用泛型改进。

//创建集合,只能保存字符串。放入其他类型时会引起编译错误
List<String> strList = new ArrayList<String>();

9.1.3 Java7泛型的“菱形”语法

java7 之前使用泛型的接口、变量时,构造器也必须带泛型:

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

Java7之后,允许省略构造器后面的泛型信息,只给出<>即可:

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

9.2 深入泛型

所谓泛型,就是允许在定义类、接口、方法时使用类型形参,这个类型形参将在声明变量、创建对象、调用方法时动态地指定。Java5改写了集合中的全部类和接口,提供了泛型支持。

9.2.1 定义泛型接口、类

可以为任何类、接口增加泛型声明。下面自定义一个Apple类:

//Apple.java
package ch9;
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<>("苹果");
		System.out.println(a1.getInfo());
		Apple<Double>a2 = new Apple<>(5.67);
		System.out.println(a2.getInfo());
	}
	
}

9.2.2 从泛型类派生子类

从泛型类、接口派生时,父类不能再包含类型形参:

//错误,父类不能带类型形参
public class A extends Apple<T>{}

如果想从Apple派生出一个子类,可以使用如方式:

public class A extends Apple<String>

也可以是:

public class A extends Apple

定义类、接口、方法时可以声明类型形参,使用时应该向类型形参传入实际类型。

如果从Apple<String>派生子类,Apple类中的T都会被替换成String。

如果使用Apple类时没有传入实际类型参数,java编译器可能会发出警告:使用未经检查或不安全的操作。此时,系统会把Apple<T>的T当成Object类型处理。

9.2.3 并不存在泛型类

不管为泛型的类型形参传入哪一种形参,对于Java来说,它们仍然被当成同一个类处理,在内存中也只占用一块内存空间。 因此,静态方法、静态变量、静态初始化块中不允许使用类型形参。

由于系统并没有真正生成泛型类,所以instanceof也不能用于泛型类。

9.3 类型通配符

9.3.1 使用类型通配符

为了表示各种泛型List的父类,可以使用类型通配符,类型通配符是一个问号?,这个问号表示可以匹配任何类型:

public void test(List<?> c){
    for(int i = 0; i < c.size(); i++){
        System.out.println(c.get(i));
    }
}

可以使用任何类型的List来调用它。

但这种List<?>仅表示它是各种List的父类,并不能把元素加入其中,

List<?> c = new ArrayList<String>();
//下面语句引起编译错误
c.add(new Object());

因为无法确定c的类型,所以不能向其中添加对象。

9.3.2 设定类型通配符的上限

如果程序希望List<?> 只是某一类泛型List的父类:

//它表示所有Shape泛型List的父类
List<? extends Shape>


类似地,由于无法确定这个受限制通配符的具体类型,所以不能添加对象进这个集合。

9.3.3 设定类型形参的上限

表示传入的类型形参是上限类型或是上限类型的子类:

//传入的类型形参是Number类或是Number的子类
public class Apple<T extends Number>
{
T col;
...
}

另一个更严格地情况是,类型形参有多个上限:(使用&符号连接多个上限)

T必须是Number或Number子类,并且必须实现ava.io.Serializable接口
public class Apple<T extends Number & java.io.Serializable>

9.4 泛型方法

9.4.1 定义泛型方法

所谓泛型方法,就是声明方法时定义一个或多个类型形参:

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

//GenericMethodTest.java
package ch9;
import java.util.ArrayList;
import java.util.Collection;
public class GenericMethodTest {
	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[10];
		Collection<Object> co = new ArrayList<>();
		fromArrayToCollection(oa, co);
		String[] sa = new String[10];
		Collection<String> cs = new ArrayList<>();
		fromArrayToCollection(sa, cs);	
        //T是Object
		fromArrayToCollection(sa, co);
	}
}


方法中的泛型形参无需显式传入实际类型参数,调用时根据实参推断类型形参的值。

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

大多数时候都可以使用泛型方法来代替类型通配符

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的唯一效果是可以在不同调用点传入不同的实际类型。

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

如果需要,也可以同时使用泛型方法和通配符:

public static<T>void copy(List<T>dest,
                          List<? exteds T>src)

9.4.3 Java 7 的菱形语法与泛型构造器

构造器签名中也可以声明类型形参。

如果构指定了造器中声明的类型形参的实际类型,则不可以使用“菱形”语法。

9.4.4 设定通配符下限

<? super Type>

表示是Type或Type父类

9.4.5 泛型方法与方法重载

public static<T>void copy(Collection<T>dest,
                          Collection<? exteds T>src)
 public static<T> T copy(Collection<? superT>dest,
                         Collection T>src)  

调用copy时会引起编译错误。

9.4.6 Java 8 改进的类型推断

1、根据调用方法的上下文推断参数的目标类型

2、在方法调用链中,将推断得到的类型参数传递到最后一个方法。

需要注意的是,这种推断并不万能,有些地方要手动指定类型参数。

9.5 擦除和转换

在严格地泛型代码中,带泛型声明的类总应该带着类型参数。但为了与老版本兼容,也允许不指定类型参数。如果没指定类型参数,则该类型参数为raw type(原始类型),默认为声明该参数指定的第一个上限类型。

9.6 泛型和数组

数组元素的类型不能包含类型变量或类型形参,除非是无上限的类型通配符。

简单来说,不要创建泛型数组。

9.7 小结

奇怪的知识增加了。

很多奇怪的用法,奇怪的错误,比如9.5和9.6节。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只大鸽子

如有帮助,欢迎关注同名公众号

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值