《疯狂java讲义》第9章 泛型

第9章 泛型

9.1 泛型入门

java集合有一个缺点:把一个对象“丢进”集合里,集合就会“忘记”这个对象的数据类型,变成了Object类型。(及核对元素类型没有任何限制,取出时通常要强制类型转换)

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

9.1.2 使用泛型

  • java的参数化类型被称为泛型(Generic)。
//创建一个只想保存字符串的List集合
List<String> strList = new ArrayList<String>();
strList.add("java");
//下面代码加入数字将引起编译错误
strList.add(5);

9.1.3 java 9 增强的“菱形”语法

  • 可以在构造器后省略完整的泛型信息,只要给出一对尖括号(<>)即可,java可以推断尖括号里的泛型信息。
List<String> strList = new ArrayList<>();
Map<String, Integer> scores = new HashMap<>();
  • 两个尖括号放一块像一个菱形,被称为“菱形”语法。只是更好的简化了泛型编程。
  • 甚至允许在创建匿名内部类时使用菱形语法:
interface Foo<T>{
	void test(T t);
}
public class AnnoymousTest{
	public static void main(String[] args){
		//指定Foo类中的泛型为String
		Foo<String> f = new Foo<>(){...};
		//使用泛型通配符,此时上限为Object
		Foo<?> f = new Foo<>(){...};
		//使用泛型通配符,此时上限为Number
		Foo<? extends Number> f = new Foo<>(){...};
	}
}

9.2 深入泛型

所谓泛型,就是允许在定义类、接口、方法时使用类型形参,这个类型形参(或叫泛型)将在声明变量、创建对象、调用方法时动态地指定(即传入实际的类型参数,也可称为类型实参)。

9.2.1 定义泛型接口、类

  • 泛型的实质:允许在定义接口、类时声明泛型形参,泛型形参在整个接口、类体内可当成类型使用,几乎所有可使用普通类型的地方都可以使用这种泛型形参。
  • 例如使用List类型时,如果为E形参传入String类型实参,则产生了一个新的类型:List< String>类型,可以把List< String>想象成E被全部替换成String的特殊List子接口
  • 包含泛型声明的类型可以在定义变量、创建对象时传入一个类型实参,从而可以动态地生成无数多个逻辑上的子类,但这种子类在物理上并不存在
  • 可以为任何类、接口增加泛型声明(并不是只有集合类才可以使用泛型声明)
//定义Apple类时使用了泛型声明
public class Apple<T>{
	//使用T类型定义实例变量
	private T info;
	//下面方法中使用T类型来定义构造器
	public Apple(T info){
		this.info = info;
	}
	public static void main(String[] args){
		//传给T形参的是String,所以构造器参数只能是String
		Apple<String> a1 = new Apple<>("苹果");
		//传给T形参的是Double,所以构造器参数只能是Double
		Apple<Double> a2 = new Apple<>(5.67);
	}
}
  • 当创建带泛型声明的自定义类,为该类定义构造器时,构造器名还是原来的类名,不要增加泛型声明。(例如,为Apple<T> 类定义构造器,其构造器名依然是Apple,而不是Apple!调用该构造器时却可以使用Apple的形式,当然应该为T形参传入实际的类型参数。)

9.2.2 从泛型类派生子类

  • 创建了带泛型声明的接口、父类之后,可以为给接口创建实现类,或从该父类派生子类。(当时用这些接口、父类时不能再包含泛型形参)
//使用Apple类时为T形参传入String类型( 派生子类 )
public class A extends Apple<String>
//使用Apple类时没有为T形参传入实际的类型参数( 使用类、接口 )
public class A extends Apple	//编译器可能发出警告,泛型检查的警告
  • 使用Apple类时省略泛型的形式被称为原始类型(raw type)。
  • 如果从Apple<String>类派生子类,则在Apple类中所有使用T类型的地方都将被替换String类型。

9.2.3 并不存在泛型类

  • 前面提到可以把ArrayList<String>类当成ArrayList的子类,事实上,ArrayList类也确实像一种特殊的ArrayList类:该ArrayList对象只能添加String对象作为集合元素。但实际上,系统并没有为ArrayList<String>生成新的class文件,而且也不会把ArrayList<String>当成新类来处理。
  • 不管为泛型形参传入哪一种类型实参,对于Java来说,它们依然被当成同一个类处理,在内存中也只占用一块内存空间,因此在静态方法静态初始化块或者静态变量声明初始化不允许使用泛型形参
  • 由于系统中并不会真正生成泛型类,所以instanceof运算符后不能使用泛型类

9.3 类型通配符

数组和泛型有所不同,假设Foo是Bar的一个子类型(子类或者子接口),那么Foo]
依然是Bar]的子类型;但G<Foo>不是G<Bar>的子类型。Foo]自动向上转型为Bar]的方式被称为型变。也就是说,Java的数组支持型变,但Java集合并不支持型变。

9.3.1 使用类型通配符

  • 为了表示各种泛型List的父类,可以使用类型通配符,类型通配符是一个问号(),将一个问号作为类型实参传给List集合,写作:List<?>(意思是元素类型未知的List)。它的元素类型可以匹配任何类型。
  • List<?>、Set<?>、Collection<?>、Map<?,?>等。

9.3.2 设定类型通配符的上限

  • 被限制的泛型通配符表示如下:
//它表示泛型形参必须是Shape子类的List
List<? extends Shape>
  • 只要List后尖括号里的类型是Shape的子类型即可。
  • 同时,程序也无法确定这个类型是什么,所以无法将任何对象添加到这种集合中。
  • 对于更厂泛的泛型类来说,指定遇配符上限就是为了支持类型型变。比如Foo是Bar的子类,这样A<Bar>就相当于A<?extends Foo>的子类,可以将A<Bar>赋值给A<?extends Foo>类型的变量,这种型变方式被称为协变
  • 对于协变的泛型类来说,它只调用泛型类型作为返回值类型的方法(编译器会将该方法返回值当成通配符上限的类型);而不能调用泛型类型作为参数的方法。口诀是:协变只出不进!

9.3.3 设定类型通配符的下限

  • 通配符的下限<? super 类型>的方式来指定。
  • 指定通配符的下限就是为了支持类型型变。比如Foo是Bar的子类,当程序需要一个A<?super Bar>变量时,程序可以将A、A赋值给A<?super Bar>类型的变量,这种型变方式被称为逆变。
  • 对于逆变的泛型集合来说,编译器只知道集合元素是下限的父类型。因此,这种逆变的泛型集合能向其中添加元素,从集合中取元素时只能被当成Object类型处理。
  • Java集合框架中的TreeSet<E>有一个构造器也用到了这种设定通配符下限的语法(TreeMPA也有类似的用法)。

9.3.4 设定泛型形参的上限

  • Java泛型不仅允许在使用通配符形参时设定上限,而且可以在定义泛型形参时设定上限,用于表示传给该泛型形参的实际类型要么是该上限类型,要么是该上限类型的子类。
public class Apple<T extends Number>
  • 程序为泛型形参设定多个上限(至多有一个父类上限,可以有多个接口上限),表明该泛型形参必须是其父类的子类(是父类本身也行),并且实现多个上限接口。
//表明T类型必须是Number类或其子类,并必须实现java.io.Serializable接口
public class Apple<T extends Number & java.io.Serializable>{
	...
}

9.4 泛型方法

9.4.1 定义泛型方法

  • 泛型方法:在声明方法时定义一个或多个泛型形参。
static <T> void test(T[] a, Collection<T> c){...}
  • 方法声明中定义的泛型只能在该方法里使用。
  • 方法的泛型参数无需显示传入实际类型的参数。
  • 有时,传入的参数不一样,编译器无法正确识别T所代表的实际类型。(将该方法的前一个形参类型改为Collection<? extendsT>,方法的前一个Collection集合里的元素类型是后一个Collection集合里元素类型的子类即可。)

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); 
}
  • 同时使用泛型方法和通配符
public class Collections{
	public static <T> void copy(List<T> dest, List<? extends T> src){...}
}
  • 类型通配符与泛型方法(在方法签名中显式声明泛型形参)还有一个显著的区别类型通配符既可以在方法签名中定义形参的类型,也可以用于定义变量的类型;但泛型方法中的泛型形参必须在对应方法中显式声明。

9.4.3 java 7 的“菱形”语法与泛型构造器

  • java允许在构造器签名中声明泛型形参,产生了泛型构造器。让java根据参数的类型来“推断”泛型形参的类型,也可以显示的为构造器中的泛型形参指定实际的类型。
class Foo{
	public <T> Foo(T t){..}
}
//泛型构造器中的T类型为
String new Foo("疯狂Java讲义");
//泛型构造器中的T类型为
Integer new Foo(200);
//显式指定泛型构造器中的T类型为String
//传给Foo构造器的实参也是String对象,完全正确
new <String>Foo("疯狂Android讲义");
//显式指定泛型构造器中的T类型为String,
//但传给Foo构造器的实参是Doub1e对象,下面代码出错
new <String>Foo(12.3);
  • 如果显示指定了泛型构造器中声明的泛型形参的实际类型,则不可以使用“菱形”语法
//MyClass 类声明中的E形参是String类型
//泛型构造器中声明的T形参是Integer类型
MyClass<String> mcl = 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);

9.4.4 泛型方法与方法重载

  • 因为泛型既允许设定通配符的上限,也允许设定通配符的下限,从而允许在一个类里包含如下两个方法定义。
public class MyUtils public static <T>void copy(Collection<T> dest,Collection<?extends T>src)
{...}
public static <T> T copy(Collection<?super T> dest,Collection<T> src)
{...}
  • 如果只是在该类中定义这两个方法不会有任何错误,但只要调用这个方法就会引起编译错误。

9.4.5 java 8 改进的类型推断

  • Java8改进了泛型方法的类型推断能力:
    (1)可通过调用方法的上下文来推断泛型的目标类型。
    (2)可在方法调用链中,将推断得到的泛型传递到最后一个方法。

9.5 擦除和转换

  • 擦除:当把一个具有泛型信息的对象赋给另一个没有泛型信息的变量时,所有在尖括号之间的类型信息都将被扔掉
  • 比如一个List<String>类型被转换为List,则该List对集合元素的类型检查变成了泛型参数的上限Object。
  • 对泛型而言,可以直接把一个List对象赋给一个List<String>对象,编译器仅仅提示“未经检查的转换”。(List特殊)

9.6 泛型与数组

  • 数组元素的类型不能包含泛型变量或泛型形参,除非是无上限的类型通配符。但可以声明元素类型包含泛型变量或泛型形参的数组。
  • 也就是说,只能声明List<String>[]形式的数组,但不能创建ArrayList<String>[10]这样的数组对象
  • java 允许创建无上限的通配符泛型数组,如:new ArrayList<?>[10]
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值