//例如List接口声明时,定义了一个表示类型的参数E,这个接口就是一个泛型接口
//这个接口全称是List,读作E的List,但人们通常简称其为List
public interface List<E> extends Collection<E> {
...
}
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Stamp {
private static final Collection stamps = new ArrayList();
public static void main(String[] args) {
stamps.add(new Coin());
for (Iterator i = stamps.iterator(); i.hasNext();) {
//1. 取出时,会报错,Coin cannot be cast to Stamp,但放入时只有提醒
//2. 出错之后应该尽快发现,最好是编译时就发现
//3. 本例中直到运行时才发现,那么如果取出的地方,与放入不在一个类中,且离放入的地方非常远,那么你就很难定位放入错误元素的位置
Stamp stamp = (Stamp) i.next();
}
}
}
class Coin {
}
//利用参数化的类型,声明变量,错误的插入,编译器会报错
private static final Collection<Stamp> stamps = new ArrayList();
...
//编译报错,无法通过编译,因为编译器发现stamps使用泛型声明的,因此会插入隐式的转换,当想把Coin强转为Stamp,发现他们没父子关系,因此报错了
stamps.add(new Coin());
import java.util.ArrayList;
import java.util.List;
public class TestHan {
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
unsafeAdd(strings, Integer.valueOf(42));
//由于unsafeAdd方法中,第一个参数,形参使用了原生类型定义变量,导致泛型信息被擦除,因此Integer可以插入元素
//而strings.get时,系统发现strings上有泛型信息,自动想为其转换为String类型,Integer转String,报错
String s = strings.get(0);
}
private static void unsafeAdd(List list, Object o) {
list.add(o);
}
}
//调整方法签名
private static void unsafeAdd(List<Object> list, Object o) {
list.add(o);
}
//方法调用时,编译器不再提示warning,而是直接报错,无法通过
unsafeAdd(strings, Integer.valueOf(42));
//合法
List.class;
String[].class;
int.class;
//不合法
List<String>.class;
List<?>.class;
//不允许使用o instanceof Set
//但可以使用 o instanceof Set>
//一旦想使用o,为了安全,就应该将其转换为参数化的类型,下面写法为instanceof后跟泛型类的一个最佳实践
if (o instanceof Set) { // Raw type
Set<?> s = (Set<?>) o; // Wildcard type
...
}
//没有<>会有警告
Set<Lark> exaltation = new HashSet<>();
//ArrayList中的toArray方法,它在长度不止一行的方法/构造器中会用了SuppressWarnings注释,实际上可以把它移到一个局部变量的声明中
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
//修改后
public <T> T[] toArray(T[] a) {
if (a.length < size) {
//这里新建了一个变量,并将注释SuppressWarnings放到了局部变量的声明中,最后加上了注释,写明为何这样是安全的
// This cast is correct because the array we're creating
// is of the same type as the one passed in, which is T[].
@SuppressWarnings("unchecked") T[] result =
(T[]) Arrays.copyOf(elements, size, a.getClass());
return result;
}
System.arraycopy(elements, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
//由于泛型只在编译时知道存储的元素类型,一旦被擦除,
List<Long> o2 = new ArrayList<Long>();
//原来理解是将参数化类型的变量传给原生类型会发生擦除,实际上擦除是泛型本身的一个特性,即编译时知道元素信息,运行时候去掉,只不过我们是利用了这个特性,可以让检查实效
List ol = o2;
ol.add("I don't fit in");
System.out.println(ol.get(0));
//编译不报错,但运行时抛 java.lang.ArrayStoreException
Object[] objectArray = new Long[1];
objectArray[0] = "I don't fit in";
//编译时就报错,cannot convert from ArrayList to List
List<Object> ol = new ArrayList<Long>();
ol.add("I don't fit in");
//1. 假设有数组泛型,下面写法表示一个数组,这个数组中的元素都是集合,而这个集合中只允许放入String类型的元素
//List[] stringLists = new List[1];
//2. 由于数组是协变的,因此Object[]是List[]的父类型,而一旦转为Object[],我们就可以通过objects向 stringLists中添加非List类型的数据,这是因为泛型通过擦除实现,参数化类型可以转为原生类型,原生类型可以转为Object
//Object[] objects = stringLists;
//List intList = List.of(42);
//objects[0] = intList;
//3. 现在出现问题,系统认为stringLists[0].get(0)是String型,无需强制转换,但实际上这个类型Integer,因此会抛ClassCastException,而Java认为,编译没有警告的地方,就不应该发生转换异常,上面没警告,下面又抛异常,因此Java不允许这种情况发生,但如果第一行为List[] stringLists = new List[1]; 就可以编译通过,因为这种写法,不符合泛型的语法规则,会有警告,带有警告的内容,抛异常,是被允许的
//这种问题出现是由于混用可具体化的数组,与不可具体化的泛型而导致的,如果都是具体化的,或都是不可具体化的,就不会有这种问题
//4. 为防止这种情况发生,就必须在第一行就报错
//String s = stringLists[0].get(0);
//实际上定义一个泛型数组的变量是允许的,只不过不能通过new创建其实例,因为一旦有实例,就可以通过其引用stringLists,修改实例的值
List<String>[] stringLists = null;
//现在如下代码也是可以通过编译的,但实际上下方代码使用c中元素时一定会造成报错,那实际上和上面一样,可能报错可能不报错
//也就是说其实泛型数组应该可以使用,是不一定报错的,只要使用得当
List<String> a = new ArrayList<String>();
List b = a;
List<Integer> c = b;
//下面代码可以通过编译
List<?>[] a = new List<?>[1];
@SafeVarargs
public static void test(List<String> ...a) {
}
//如果上面方法没有@SafeVarargs注释,下方代码会有warning
//这是由于当调用可变参数的方法时,会创建一个数组来存放方法声明中的那个可变参数(varargs),如果这个数组的元素类型是不可具体化的,就会得到警告
test(new ArrayList<String>(),new ArrayList<String>());
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
public class Chooser {
private final Object[] choiceArray;
public Chooser(Collection choices) {
choiceArray = choices.toArray();
}
public Object choose() {
Random rnd = ThreadLocalRandom.current();
return choiceArray[rnd.nextInt(choiceArray.length)];
}
public static void main(String[] args) {
List bbb = new ArrayList();
bbb.add(5);
bbb.add("1234");
Chooser a = new Chooser(bbb);
//1. 如果想使用choices中存放的元素,必须对choose的结果进行强制转换,如果转错了,运行时会报错
//2. 下方代码有时报错,有时不报错
//3. 为了避免自己转换,想引用泛型解决
String b = (String)a.choose();
}
}
import java.util.Collection;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
public class Chooser<T> {
private final T[] choiceArray;
public Chooser(Collection<T> choices) {
//此处编译器会报warning,因为数组是具体化的,编译时啥也检查不出来,因此它也无法保证运行时,这个强制转换能正常完成,因为它压根不知道运行时T到底是什么,Object的对象如果不是T的子类的实例,转T时是可能失败的
//toArray方法,返回的是一个Object[],由于数组是协变的,所以实际上Object[]是存在转为T[]的可能的,但如果toArray这个方法中,返回的并不是一个T类型的数组,那么这个方法就会报错,可以想象自己乱写一个toArray,返回一个数组,里面包含各种不同类型元素
//如果我们改为使用List,由于List不具体的,也不协变,即,当传入泛型时,如果想通过编译,toArray方法根本没法返回一个包含非T子类元素的集合,因此就不会出现这个问题
choiceArray = (T[])choices.toArray();
}
public T choose() {
Random rnd = ThreadLocalRandom.current();
return choiceArray[rnd.nextInt(choiceArray.length)];
}
}
//选择用List代替数组,消除warning,运行时不会得到ClassCastException
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
public class Chooser<T> {
private final List<T> choiceArray;
public Chooser(Collection<T> choices) {
//List并不协变,因此不会出现转换有问题,即将new ArrayList(choices)赋值给choiceArray,只要编译成功,运行时一定成功,因此编译器不再报错
choiceArray = new ArrayList<T>(choices);
}
public T choose() {
Random rnd = ThreadLocalRandom.current();
return choiceArray.get(rnd.nextInt(choiceArray.size()));
}
}
import java.util.Arrays;
import java.util.EmptyStackException;
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}
public boolean isEmpty() {
return size == 0;
}
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
import java.util.Arrays;
import java.util.EmptyStackException;
public class Stack<E> {
private E[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
//e. 一旦你认定类型安全,就可以使用@SuppressWarnings,来禁止警告
@SuppressWarnings("unchecked")
public Stack() {
//1. elements = (E[]) new E[DEFAULT_INITIAL_CAPACITY];无法使用new创建泛型数组的对象
//1. 解决方案一
//a. 此处会有警告,因为编译器不能保证运行时,这个Object数组,一定可以转换成E[]
//b. 这个地方的处理比较有趣,我们知道Integer[] a = (Integer[])new Object[2],这种代码,运行时是不可能通过的,因为Object[]是父类,如果不是指向子类的实例,运行时发生转换是会报错的。但此处为何不会报错呢,这是因为由于擦除的解决方案,编译器,只有在使用参数化类型的引用为真实类型的变量赋值时,才加上转换,也就是说elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY]在编译器中由于elements为参数化类型的变量,而Object数组为真实的类型引用,所以编译后的字节码,并不将Object[]转为String[],但一旦满足条件,例如Stack a = new Stack<>();String[] b(真实类型)= a.elements;(参数化类型)那么编译器会将字节码变为String[] b= (String[])a.elements,运行时,由于Object[]转String[]报错。
//c. 实际上在后面pop方法E result = elements[--size];中才会发生转换
//d. 当你确定数组中元素不能被外界访问,也无法被push外方法访问,即无法被外界修改为非E类型,那么这样数组元素就一定是E这个类型,我们认为就是安全的
//f. 至此,你可以确保使用Stack中pop方法时,无需显式转换,也不用担心ClassCastException
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(E e) {
ensureCapacity();
elements[size++] = e;
}
public E pop() {
if (size == 0)
throw new EmptyStackException();
E result = elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}
public boolean isEmpty() {
return size == 0;
}
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
public static void main(String[] args) {
Stack<String> a = new Stack<>();
}
}
//方案二
private Object[] elements;
...
public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
...
public E pop() {
if (size == 0)
throw new EmptyStackException();
@SuppressWarnings("unchecked")E result = (E) elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}
//Object[]中很可能存放的并不是E类型的元素,如果使用elements数组元素时,系统自动将其元素转为E类型,可能会出问题。但此例中,Object[]中不可能放非E的元素,因此不会有危害
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
//实际的类型参数必须是Delayed的一个子类型
//允许DelayQueue内部,或客户端中,在DelayQueue的元素上,直接使用Delayed的方法(因为是他的子类型),而无需显式的转换,也没有出现ClassCastException的风险
//类型参数E称为有限制的类型参数
class DelayQueue<E extends Delayed> implements BlockingQueue<E>
//未使用泛型时,该方法有很多警告
public static Set union(Set s1, Set s2) {
Set result = new HashSet(s1);
result.addAll(s2);
return result;
}
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
//没添加泛型,会有警告
Set<E> result = new HashSet<>(s1);
result.addAll(s2);
return result;
}
//1. 恒等函数:即f(x)=x这种函数,java类库中的Function.identity这个静态方法,会创建一个Function对象,这个Function对象的apply方法会原封不同返回该方法的参数。那么这个Function对象就是一个恒等函数,而identity方法,能够产生一个恒等函数,书中叫它恒等函数分发器
//2. 我们自己模拟实现一个恒等函数的分发器
//首先我们考虑直接创建一个恒等函数(UnaryOperator继承了Function,也有apply方法)
UnaryOperator<String> IDENTITY_FN = t->t;
String a = "123";
String b = IDENTITY_FN.apply(a);
UnaryOperator<BigDecimal> IDENTITY_Big = t->t;
BigDecimal a1 = new BigDecimal("123");
BigDecimal b1 = IDENTITY_Big.apply(a1);
//但这产生了一个问题,想要对不同类型的对象,产生恒等函数,需要建立多个UnaryOperator对象,如果建立一个也可以,但需要强制转换
UnaryOperator IDENTITY_FN_OBJ = t->t;
BigDecimal a2 = new BigDecimal("123");
BigDecimal b2 = (BigDecimal) IDENTITY_FN_OBJ.apply(a2);
//因此我们考虑使用泛型单例工厂
private static UnaryOperator<Object> IDENTITY_FN = (t) -> t;
@SuppressWarnings("unchecked")
public static <T> UnaryOperator<T> identityFunction() {
//此处和之前例子中,E[] a = (E[])new Object[5],原理大致相同,都不会真正转换
//会产生warning,因为这样写转换关系,实际上是编译后的内容,在这不会有转换,只会将调用IDENTITY_FN.apply()的返回值转为T类型,而编译器并不确定,在apply的返回结果,可以转为T类型,但其实由于这个函数的特殊性,总返回参数中传入的对象,而我们限定的参数中传入的是T,那么实际上就一定可以转为一个T类型的变量,因此不会产生转换问题,所以可以用@SuppressWarnings("unchecked")注释,屏蔽他的warning
return (UnaryOperator<T>) IDENTITY_FN;
}
//此时客户端代码如下
public static void main(String[] args) {
String[] strings = { "jute", "hemp", "nylon" };
UnaryOperator<String> sameString = identityFunction();
for (String s : strings)
System.out.println(sameString.apply(s));
Number[] numbers = { 1, 2.0, 3L };
UnaryOperator<Number> sameNumber = identityFunction();
for (Number n : numbers)
System.out.println(sameNumber.apply(n));
}
//TestNew具有了public int compareTo(T o)方法,T可以是任何类型
//下面写法表示:TestNew对象可以与任何对象进行比较
TestNew implements Comparable
//下面写法表示:TestNew对象可以与任何String对象进行比较
TestNew implements Comparable<String>
//下面写法表示:TestNew对象可以与任何TestNew对象进行比较
TestNew implements Comparable<TestNew>
//想定义一个方法,可以根据元素的自然顺序,找到集合中最大的那个元素。这意味着,要求集合中的每个元素都能与列表中的其他元素进行比较
//下面写法表示,该泛型方法中的这个E,必须实现Comparable接口,且只能与自身对应的这种类型的对象进行比较,实际上一个不只能与自身比较的E也能被传入,只不过max这个方法中根本不允许它去和别人比
public static <E extends Comparable<E>> E max(Collection<E> c) {
if (c.isEmpty())
throw new IllegalArgumentException("Empty collection");
E result = null;
for (E e : c)
if (result == null || e.compareTo(result) > 0)
result = Objects.requireNonNull(e);
return result;
}
//可以看做一个自类型接口
@FunctionalInterface
public interface Builder<T extends Builder<T>> {
T self();
}
//实现该接口的类,拥有一个可以返回自身类型的self方法
class Wusihan implements Builder<Wusihan> {
@Override
public Wusihan self() {
return null;
}
}
与泛型类的优点,以及最佳实践相同
public interface Iterator<E> {
boolean hasNext();
E next();
}
//Iterable接口实际上只是给与实现他的类、接口,一个iterator方法,能够返回Iterator
public interface Iterable<T> {
Iterator<T> iterator();
}
//List实现了Iterable,所以可以调用list.iterator获得迭代器
Iterator it = list.iterator();
while (it.hasNext()) {
System.out.print(it.next() + ",");
}
for (Integer i : list) {
System.out.println(i);
}
//编译后内容
//java的for循环只是一个语法糖(我理解语法糖就是把一些东西简写了,很好用),内部其实是通过iterator迭代器方式实现的
Integer i;
for(Iterator iterator = list.iterator(); iterator.hasNext(); System.out.println(i)){
i = (Integer)iterator.next();
}
//假如在JDK中的Stack类中增加如下代码
public void pushAll(Iterable<E> src) {
for (E e : src)
push(e);
}
//客户端代码
Stack<Number> a = new Stack<>();
Iterable<Integer> integers = List.of(123,456,789);
//无法编译通过,因为Iterable不是Iterable的子类型
a.pushAll(integers);
//改进后的pushAll,表示Iterable中的泛型应该是E的子类型
public void pushAll(Iterable<? extends E> src) {
for (E e : src)
push(e);
}
//该方法将Stack中所有元素弹出,并添加到指定集合中
public void popAll(Collection<E> dst) {
while (!isEmpty())
dst.add(pop());
}
//客户端代码
Stack<Number> a = new Stack<>();
//正常逻辑,Number属于Object,那么存放Number的栈,其内元素应该可以放入存放Object的集合中,但此处编译报错,因为Collection
a.popAll(new ArrayList<Object>());
//修改后popAll方法,表示集合中元素应该是E的超类型
public void popAll(Collection<? super E> dst) {
while (!isEmpty())
dst.add(pop());
}
//1. 修改之前的Chooser类中的构造器中的泛型,从而允许List类
//3. choices传给choiceArray,而choiceArray最后用于创造T,因此此处应该使用extends
public Chooser(Collection<? extends T> choices) {
choiceArray = new ArrayList<T>(choices);
}
public static void main(String[] args) {
List<Integer> l = new ArrayList<Integer>();
//2. 未修改Chooser构造器前,该方法无法通过编译
Chooser<Number> a = new Chooser<>(l);
System.out.println(a.choose());
}
//改造之前的union方法,使其可以将Set的s1和Set的s2合并到一起
//注意s1和s2都生产E的对象,传给新的result,因此应该使用extends
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
Set<E> result = new HashSet<>(s1);
result.addAll(s2);
return result;
}
//1. 注意不要使用通配符类型作为返回类型,即返回值Set不要改造成Set extends E>,因为如果这样写,虽然提供了灵活性,但也导致客户端代码定义其返回值类型时,必须使用通配符类型
public static <E> Set<E> union(Set<? extends E> s1, Set<? extends E> s2) {
//没添加泛型,会有警告
Set<E> result = new HashSet<>(s1);
result.addAll(s2);
return result;
}
//客户端代码
Set<Integer> integers = Set.of(1, 3, 5);
Set<Double> doubles = Set.of(2.0, 4.0, 6.0);
//2. 如果1中返回值使用Set extends E>,那么客户端代码需要如下
//Set extends Number> numbers = union(integers, doubles);
//java8之前,类型推导不够智能,下面代码会报错,可以做如下修改,提供一个显式的类型参数
//Set numbers = Chooser.union(integers, doubles);
Set<Number> numbers = union(integers, doubles);
//c可以产生E的对象,并返回,因此对于Collection应该使用extends,而对于参数类型>中,可以想象Comparable的对象,拥有一个compare方法,它用来消费E(compare方法传入参数E),因此应该使用super
//注意所有的Comparable和Comparator都是消费者
public static <E extends Comparable<E>> E max(Collection<E> c) {
if (c.isEmpty())
throw new IllegalArgumentException("Empty collection");
E result = null;
for (E e : c)
if (result == null || e.compareTo(result) > 0)
result = Objects.requireNonNull(e);
return result;
}
//改造后方法
public static <E extends Comparable<? super E>> E max(Collection<? extends E> c)
//对于原方法,下面方法报错,新方法不会
//因为ScheduledFuture继承了Delayed,Delayed继承了Comparable,即ScheduledFuture实际上是继承了Comparable,系统无法推导E的类型,因此编译报错,但修改后就不会有问题
//即需要用通配符支持那些不直接实现Comparable或Comparator,而是实现了Comparable<自身类型的父类型>的这种类
List<ScheduledFuture<?>> scheduledFutures = null ;
max(scheduledFutures);
//无限制类型参数用无限制通配符取代,有限制类型参数用有限制的通配符取代
//类型参数E只使用了一次,因此用类型通配符代替比较方便
public static <E> void swap (List<E> list,int i,int j);
public static void swap (List<?> list,int i,int j);
public static void swap(List<?> list, int i, int j) {
//set时会有问题,因为List>的对象list,由于不知道它的泛型类型,因此只能向里面放入null,因此下方代码无法编译通过
list.set(i, list.set(j, list.get(i)));
}
//改造,编写一个可以捕捉通配符类型的方法
public static void swap(List<?> list, int i, int j) {
swapHelper(list, i, j);
}
//这个辅助方法,可以允许我们导出简单的这种基于通配符的方法声明,这样swap的客户端不必面对复杂的swapHelper声明
private static <E> void swapHelper(List<E> list, int i, int j) {
list.set(i, list.set(j, list.get(i)));
}
// 1. 如果方法声明中,其可变参数的类型为非具体化的(例:List),会提示警告:Type safety: Potential heap pollution via varargs parameter a,意思是可能会造成堆污染
// 这是因为当一个参数化的类型的变量,a,如果指向一个不是这个参数化的类型的实例时,会产生堆污染。堆污染导致编译器自动生成的转换失败,破坏了泛型系统的基本保证
// 正常情况下,编译器是不允许参数化的类型的变量指向一个不是这个参数化的类型的实例的。而可变参数类型为参数化的类型这种情况,为堆污染提供了便利的条件,因此会提示这个warning
public static void test(List<String>... a) {
}
//可变参数的类型为参数化的类型,而引发的问题
static void dangerous(List<String>... stringLists) {
List<Integer> intList = List.of(42);
Object[] objects = stringLists;
// 会造成堆污染,相当于修改了参数化类型List的变量stringLists的指向,指到了一个List实例上
objects[0] = intList;
// 堆污染导致了ClassCastException,因为实际上编译器会产生一个不可见的转换,Integer转String
// 由此证明,可变的参数中,使用了参数化的类型是不安全的
String s = stringLists[0].get(0);
}
//这些方法都是很小心编写的,都是类型安全的
Arrays.asList(T a)
Collections.addAll(Collection<? super T> C,T...elements)
Enum.of(E first ,E...rest)
public static void main(String[] args) {
// 1. 调用上面那种方法的地方也会有一个warning:Type safety: A generic array of List is
//如果在上面定义方法上加上注释@SuppressWarnings("unchecked"),只是方法定义位置不再警告,但调用处还是会警告
//必须在定义方法处@SafeVarargs注释,表示方法设计者承诺,该方法时类型安全的,才能使所有客户端警告取消
//必须确定上面那个方法确实类型安全,才可以使用@SafeVarargs注释
test(new ArrayList<String>(), new ArrayList<String>());
}
public static void test(List<String>... a) {
//违反第3条,将数组传给了不被信任的代码
Object[] b = a;
//不被信任的代码
b[0] = List.of(42);
}
//B方法
//1. 直接使用该方法toArray("123","456")时,会返回一个String[]的实例
static <T> T[] toArray(T... args) {
return args;
}
static <T> T[] pickTwo(T a, T b, T c) {
switch (ThreadLocalRandom.current().nextInt(3)) {
case 0:
//2. pickTwo方法中,相当于使用该方法toArray(a,b),由于编译时,还不知道a,b具体类型,因此会返回一个Object[]的实例
//3. 客户端相当于不被信任的代码,pickTwo将数组泄露给客户端,违背了第3条,因此pickTwo也不安全
return toArray(a,b);
case 1:
return toArray(a,c);
case 2:
return toArray(b,c);
}
throw new AssertionError();
}
//客户端
public static void main(String[] args) {
//java.lang.ClassCastException
String[] a = pickTwo("1234", "12345","6666");
}
//正常可变参数类型为泛型的方法写法,需要确定类型安全并加上@SafeVarargs注释,保证客户端不会warning
@SafeVarargs
static <T> List<T> flatten(List<? extends T>... lists) {
List<T> result = new ArrayList<>();
for (List<? extends T> list : lists)
result.addAll(list);
return result;
}
//优点:使用List替换,编译器可以证明该方法类型安全,因此不再提示warning,可以省略@SafeVarargs注释
//缺点:客户端代码繁琐(客户端得先组一个list出来),运行速度慢一点
static <T> List<T> flatten(List<List<? extends T>> lists) {
List<T> result = new ArrayList<>();
for (List<? extends T> list : lists)
result.addAll(list);
return result;
}
//之前的pickTwo方法,使用List替换泛型数组
static <T> List<T> pickTwo(T a, T b, T c) {
switch (rnd.nextInt(3)) {
case 0:
return List.of(a, b);
case 1:
return List.of(a, c);
case 2:
return List.of(b, c);
}
throw new AssertionError();
}
//如果将一行中每个列的元素,都使用Object存放,也可以,但会出现代码过于复杂
//key表示列明,String类型,Object表示列的值,由于不确定是哪种类型,只能用Object类型接收
Map<String,Object> a = new HashMap<>();
//省略将数据每列数据放入a的代码
....
//取出数据时,我们不知道acct_no是String类型,因此必须转换,非常不方便,也不确定是否可以转换成功
String acct_no = (String)a.get("acct_no");
int acct_seqn = (Integer)a.get("acct_seqn");
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
//1. 假设现在有一个表Favorites,它的每一行,会保存不同的人所喜爱的不同类型的内容,例如其第一列保存最喜爱的String,第二列保存最喜爱的Integer,第三列保存最喜爱的Class,等等
public class Favorites {
//2. 将键参数化,而不是将容器参数化,即通配符代表的是键Class的类型,而不是容器Map中的K的类型
//3. 对于String.class,属于Class类型,Integer.class属于Class类型
//4. Favorites的值的类型,只能是Object,因为Java目前无法保证Map中的键和值的类型之间的关系(比如这种定义:Map, T>),即键是Class,但值是Object,不能保证值是String
private Map<Class<?>, Object> favorites = new HashMap<>();
public <T> void putFavorite(Class<T> type, T instance) {
favorites.put(Objects.requireNonNull(type), instance);
}
//9. 由于Java中无法做到在Map中定义键和值的关系,那么只能人为去进行转换,将取到的Object类型,转成键的参数的类型T
public <T> T getFavorite(Class<T> type) {
//10. type的cast方法,是对Java转换符的动态模拟,当一个实例,是type的类型参数T 的实例,就返回该实例,使用如下方法替代程序也能运行,但会有异常
//(T)favorites.get(type);
return type.cast(favorites.get(type));
}
public static void main(String[] args) {
Favorites f = new Favorites();
//5. String.class为类型令牌:当一个类的字面被用在方法中,来传达编译时和运行时类型信息时,就叫类型令牌
f.putFavorite(String.class, "1234");
f.putFavorite(Integer.class, 0xcafebabe);
f.putFavorite(Class.class, Favorites.class);
//6. Favorites的实例是类型安全的:当你向他请求String,它一定不会反回一个Integer给你
//7. 同时它也是异构的:每个键都有一个不同的参数化类型
//8. 因此称Favorites为类型安全的异构容器
String favoriteString = f.getFavorite(String.class);
int favoriteInteger = f.getFavorite(Integer.class);
Class<?> favoriteClass = f.getFavorite(Class.class);
System.out.printf("%s %x %s%n", favoriteString, favoriteInteger, favoriteClass.getName());
}
}
//此时编译不报错
f.putFavorite((Class)String.class, 1234);
//直到下面客户端代码才报错
String favoriteString = f.getFavorite(String.class);
//可以通过修改putFavorite代码,在放入时,就确认要放入的instance是否为type所表示的类型的实例,这样就可以重新获得类型安全,编译还是能通过,但在放入元素时就会报错,而不是等到取出元素时
public <T> void putFavorite(Class<T> type, T instance) {
favorites.put(type, type.cast(instance));
}
//Jdk为防止恶意传入原生类型对象,破坏类型安全的做法
//java.util.Collections类中提供了checkedSet、CheckedList、CheckedMap等方法,根据一个集合,返回另一个集合,这个集合可以确保放入这个集合中的元素,与其参数化的类型相符
//不受检查的集合
List<String> list = Arrays.asList("12","23");
List obj = list;
//此时不会抛异常
obj.add(112);
//受检查的集合
List<String> list = Arrays.asList("12", "23");
List<String> safeList = Collections.checkedList(list, String.class);
List obj = safeList;
//在运行时,添加错误的元素就会报异常,不会等到取出元素时
obj.add(new Date());
//如下代码,编译就会报错,因为List.class语法是错误的,List.class和List.class共用一个Class对象,即List.class
f.putFavorite(List<String>.class, new ArrayList<String>());
//例如AnnotatedElement接口中的该方法,T只能是Annotation的子类型,因此也限制了类型令牌只能是Annotation子类型.class,即该方法中只能传入注解类型的类型令牌
//被注解的元素本质上是一个类型安全的异构容器,容器的键就是注解的类型,值是注解对象本身
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
//该方法会有警告,因为转换是非受检的,可以使用asSubclass方法去除这种警告,和之前的type.cast(favorites.get(type));方法很相似
Class<? extends Annotation> b = (Class<? extends Annotation>) a;
static Annotation getAnnotation(AnnotatedElement element, String annotationTypeName) {
Class<?> annotationType = null;
try {
annotationType = Class.forName(annotationTypeName);
} catch (Exception ex) {
throw new IllegalArgumentException(ex);
}
//将调用它的class对象转换成其参数(Annotation.class)表示的类的对象,转换成功返回调用它的class对象,否则抛出ClassCastException
//和下面方法一样,只不过下面方法时未受检的,会有warnning
//return element.getAnnotation((Class) annotationType);
return element.getAnnotation(annotationType.asSubclass(Annotation.class));
}