学习笔记之《Java核心技术卷I》---- 第八章 泛型程序设计

  • 泛型类的定义格式:class Pair<T>{ }
  • 普通类中泛型方法的定义:public static <T> T getMiddle(T... a){ return a[a.length / 2]; }
  • 调用方法时,可以使用:ClassName.getMideele("John","Q"); //编译器有足够的信息可以从传进来的参数("John","Q")推断出泛型类型T应该为String
  • 对类型变量T设置限定:public static <T extends Comparable> T min(T... a){ ... } 
  • 类型变量设置限定使用关键字 extends 而不是implements 解释:<T extends BoundingType> 表示T应该是绑定类型的子类型。T和绑定类型可以是类,也可以是接口。选择extends的原因是更接近子类的概念
  • 一个类型变量或通配符可以有多个限定,例如:T extends Comparable & Serializable 限定类型用“&”分隔,而逗号用来分隔类型变量
  • 在Java的继承中,可以根据需要拥有多个接口超类型(即一个接口可以extends多个接口,一个类也可以implements多个接口),但在泛型中,限定类(即BoundingType)至多有一个类。如果用一个类作为限定,它必须是限定列表中的第一个(相当于某个类既继承一个超类,又实现多个接口,必须是先写extends ClassName,再写implements InterfaceName)。并且为了提高效率,应该将标签接口(即没有方法的接口)放在边界列表的末尾
  • Java泛型类型擦除机制:擦除类型变量,并替换为限定类型(无限定的变量用Object)
class pair<T>{
	private T first;
	private T second;
	public pair(T first,T second) {
		// TODO Auto-generated constructor stub
		this.first = first;
		this.second = second;
	}
}

//经泛型擦除后变为:

class pair{
	private Object first;
	private Object second;
	public pair(Object first,Object second) {
		// TODO Auto-generated constructor stub
		this.first = first;
		this.second = second;
	}
}
  •  当程序调用泛型方法时,如果擦除返回类型,编译器自动插入强制类型转换。在虚拟机中,用参数类型和返回类型确定一个方法
  • 在代码进入jvm之前,与泛型相关的信息会被擦除,但在编译阶段,泛型信息仍然存在。代码:
//a1,a2的原始类型相同,但它们并不是同一种类
public class Test1 {
	public static void main(String[] args) throws FileNotFoundException{
		A<String> a1 = new A<>();
		A<Integer> a2 = new A<>();
//使用getClass返回原始类型
	System.out.println(a1.getClass().getSimpleName().equals(a2.getClass().getSimpleName()));//True
		System.out.println(a2.getClass().getSimpleName());//A
	}
}
class A<T>{
}
  • 虚拟机中没有泛型,只有普通的类和方法
  • 所有的类型参数都用它们的限定类型转换 
  • 不能用基本类型实例化类型参数
  • 不能创建参数化类型的数组,但可以声明参数化类型数组的变量。如果非要创建带类型变量的对象数组,可以使用ArrayList创建
A<String>[] table1 = new A<String>[10];//错误
A<String>[] table2;//正确
ArrayList<A<String>> list = new ArrayList<>();//正确
  • 不能实例化类型变量,即 new T(...),new T[...]或T.class都是非法的
  • 不能在静态域或静态方法中引用类型变量
  • 不能抛出或捕获泛型类的实例
  • 关于带有超类型限定的通配符可以向泛型对象写入,带有子类型限定的通配符可以从泛型对象读取的理解:
public class Test1 {
	public static void main(String[] args) {
		ArrayList<? extends Employee> list1 = new ArrayList<>();
		list1.add(new Executive());//报错
		list1.add(null);//将报错行注释后正确
		System.out.println(list1.get(0));//将报错行注释后输出null
	}
}
class Employee{
	
}
class Manager extends Employee{
	
}
class Executive extends Manager{
	
}

当前讨论为extends关键字对于get和set的情况。建了一个list1,指定其内存放的元素为Employee类及其子类,编译器只知道是Employee类及其子类,并不知道是Employee类、Manager类或者是Executive类,也就是说编译器此时并不知道Employee类的子类有哪些!!!。所以当你试图往里面加入元素的时候,编译器会表示 “我不知道具体该存什么值,所以我不能把你放进去”。可能你会觉得,可以添加一个Executive类的对象,因为list1存放的是上面三种类的哪个,Executive都可以自动转换为它们(子类向父类可以自动向上转换);但是,假如我再有一个类是 Executive1 继承了 Manager,那么此时你存放Executive显然就是不适当的(因为此时list1存放的可能是Executive1,而它们之间是不能相互自动转换的),存放Executive1同理。最终要得,我还是觉得应该是编译器不知道Employee的子类有哪些!!!而list1可以添加null,那是因为null可以表示任何对象,因此无论你存放的是什么类型,null都可以往list1中添加。最后get方法,返回的是一个对象,这个对象所属的类是Employee类的子类,因此用一个Employee变量接收即可,所以get方法可以正确表示。

public class Test1 {
	public static void main(String[] args) {
		ArrayList<? super Executive> list1 = new ArrayList<>();
		list1.add(new Executive());//正确编译
		System.out.println(list1.get(0));//将报错行注释后输出learningNotes.Executive@7852e922
	}
}
class Employee{
	
}
class Manager extends Employee{
	
}
class Executive extends Manager{
	
}

现在讨论super关键字对于get和set的情况。list1存放的是Executive的所有父类及其本身类,因此,无论你存放的是Executive还是它的父类,都是可以添加Executive对象(子类向父类自动转换), 对于get方法,也只需要把结果用一个Object类型的变量接收即可(因为Object类是Executive类的最高子类

综上,我认为:无论是super还是extends,get()方法都是可以使用的;但是set()方法只对super使用(不考虑set(null)的话)

  • 生产者使用extends,消费者使用super。首先,生产者和消费者都是队列,消费者队列从生产者队列中取值并放到消费者队列中,因此生产者队列必需得提供get方法,生产者队列必需得提供add方法或者set方法。详细等看了后面的线程章节再细说
  • 通配符 “?”不是类型变量,不能在编写代码中用“?”作为一种类型
  • 泛型和反射,以后再看

猜你喜欢

转载自blog.csdn.net/smart_ferry/article/details/84926411