四、泛型相关

第23条 java1.5及以后请使用泛型,不要再使用原生态类型

List<E> E的列表 E是元素类型的形式参数
List<String> String是对应于E的实际类型,是一个参数值
几个名词:

  • 泛型 generic type 具体指泛型类和泛型接口 如:List<String>
  • 类型参数 type parameter,也可以说是,参数化类型 parameterized type 如:E形参,String实际值。
  • 原生态类型 raw type 如:List

List和List<Object>之前有什么区别?前者逃避了泛型检查,而后者明确地告诉编译器,它能够持有的对象类型是什么。

可以将List<String>传给类型为List的参数 ,但是不能将它传给类型为List<Object>的参数。
泛型有子类型化的规则(subtyping),List<String>是原生态类型List的一个子类型,而不是List<Object>的子类型。
所以使用List这样的原生态类型,就是失去类型安全性,用List<Object>就不会。

Set<?>
?称为无限制的通配符类型 unbound wildcard type

泛型信息可以在运行时被擦除。

只有两种情况下,可以使用原生态类型:

  1. 在类文字(class literal)中必须用原生态类型。如:List.class、String[].class、int.class都合法,但是List<String>.class和List<?>则不合法
  2. 判断一个实例是否是泛型的一个实例时用到:
    由于泛型信息可以在运行时被擦除,所以在参数化类型上使用instanceof操作符是非法的。
    if (o instanceof List<String>) { } 在运行时,<String>这个信息被擦除了,不知道这个List中的元素是什么类型,所以无法判断o是否是List<String>的一个实例。
    只能这样写 if (o instaceof List<?>) ,这样对instanceof操作符不会有任何影响,此时<?>有点多余了,去掉后变成if (o instaceof List),此为可以使用原生态类型的第二种情况。
 if (o instaceof List) {
  List<?> m = (List<?>)o
 }
一旦确定o是一个List,就应该转成List<?>,使m成为一个受编译器检查的类型,这样更安全
附一个名词表,以便于理解此部分的概念:

达达达

第24条 消除非受检警告

用泛型编程时,会遇到很多编译器警告:非受检强制转化警告、非受检方法调用警告等。
未经检查的警告是重要的,不要忽略它们。 每个未经检查的警告都表示可能在运行时抛出ClassCastException 异常。 要尽最大努力消除这些警告。
如果无法消除警告,同时明确知道代码是安全的,可以用@Suppress注解来禁止这条警告。并在注释中 记录你禁止此警告的理由。
但是如果忽略明知道是安全的警告,而没有禁止。那么以后出现一条真正的警告时,就无法分清哪些是安全的警告、哪些是需要处理的。
@Suppress注解可以用在任何粒度的级别中,但应在最小的范围上使用。不要在类的级别上使用,可能会掩盖掉重要的警告。

第25条 列表优先于数组

数组与泛型之间的区别:

  1. 数组是协变的(convariant),泛型是不可变的(invariant)
  2. 数组是具体化的(reified),泛型是通过擦除来实现的(erasure)
      数组在编译期,由于支持协变,类型信息较弱。而在运行时才知道元素的类型约束,并检查元素类型是否合法。
      泛型只在编译期强化类型信息,而在运行时擦除元素类型信息,这样可以兼容早期没有泛型时,已经大量使用的代码。
    参考以下代码进行理解:
Object[] objArr = new Integer[1]; // 编译通过
objArr[0] = "I don't fit in"; // 运行失败 java.lang.ArrayStoreException

// 编译失败
List<Object> objList = new ArrayList<String>();

由于以上区别,导致数组 与 泛型不能一起很好的协同工作。
禁止创建泛型数组:List<String>[] listArr = new ArrayList<String>[2];
当遇到创建泛型数组错误时,最好的解决方法是使用List<E>,而不是数组类型E[]。这样可能会损失一些性能和简洁性,但换回的却是更好的类型安全和互用性。

第26条 优先考虑泛型

自定义泛型时可能遇到 泛型和数组协作的情况:

// Object-based collection - a prime candidate for generics
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);
	}
}
// Initial attempt to generify Stack - won't compile!
public class Stack<E> {
	private E[] elements;
	private int size = 0;
	private static final int DEFAULT_INITIAL_CAPACITY = 16;
	public Stack() {
?0 	elements = new E[DEFAULT_INITIAL_CAPACITY]; // 会报错generic array creation,违反了禁止创建泛型数组,怎么解决呢?
	} 
	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;
	} .
	.. // no changes in isEmpty or ensureCapacity
}

?0解决方法:
一、

@SuppressWarnings("unchecked")
public Stack() {
elements = (E[])new Object[DEFAULT_INITIAL_CAPACITY];
}

二、

private Object[] elements;
@SuppressWarnings("unchecked")
E result = (E)elements[--size];

第二种方法,需要在每一个elements取出来后做类型强转,比较麻烦,所以第一种方法更常用。

总之,泛型类型比需要在客户端代码中强制转换的类型更安全,更易于使用。 当你设计新的类型
时,确保它们可以在没有这种强制转换的情况下使用。 这通常意味着使类型泛型化。 如果你有任何现
有的类型,应该是泛型的但实际上却不是,那么把它们泛型化。 使这些类型的新用户的使用更容易,
而不会破坏现有的客户端。

第27条 优先考虑泛型方法

Set<Apple> set1 = new HashSet<Apple>();
Set<Apple> set1 = new HashSet<>(); //java7及以后:泛型对象的创建 实现了类型推导(type inference)
java8以后又添加了 泛型方法的类型推导

泛型单例工厂:
举例:泛型 函数

	public interface SelfFunction<T> {
        T apply(T arg);
    }

    private static final SelfFunction<Object> SELF_FUNCTION =
            new SelfFunction<Object>() {
                public Object apply(Object arg) { return arg; }
            };

    @SuppressWarnings("unchecked")
    public static <T> SelfFunction<T> identifyFunction() {
        return (SelfFunction<T>)SELF_FUNCTION;
    }
	
	public static void main(String[] args) {
		SelfFunction<String> ss = identifyFunction();
        System.out.println(ss.apply("aa"));

        SelfFunction<Integer> sL = identifyFunction();
        System.out.println(sL.apply(1));
	}

在(SelfFunction<T>)SELF_FUNCTION强制转换时,对于T来说,SELF_FUNCTION是SelfFunction<Object>类型,而不是SelfFunction<T>,所以会有一个警告Unchecked cast:SelfFunction<Object> to SelfFunction<T> ,使用@SuppressWarnings(“unchecked”) 来禁止警告。

// Using a recursive type bound to express mutual comparability
public static <E extends Comparable<E>> E max(Collection<E> c);

第28条 利用有限制的通配符<? extends T><? super T>来提升API灵活性

有限制的通配符 bounded wildcard type,例 Iterable<? Extends E>

参数化类型是不可变的 invariant,如List<String>。对于List<Type1> List<Type2>它们之间既不是父类型关系,也不是子类型关系,单纯是两个独立的类型。

对于泛型方法,为了获得最大限度的灵活性,要在表示生产者或消费者的输入参数上使用通配符类型。PECS助记符 producer-extends consumer-super
如果参数化类型表示T的一个生产者,就使用<? extends T> 如果表示一个消费者就用<? super T>。PECS助记符突出了使用通配符的基本原则。

如果编译器不能推断出你希望他拥有的类型时,可使用一个显式的类型参数 explicit type parameter来告诉他使用哪种类型。
Set<Number> numbers = Union.<Number>union(integers, doubles);

如果类型参数在方法中只出现一次,就可以用通配符代替它。无限制的类型参数,用无限制通配符代替。有限制的类型参数,用有限制的通配符代替。

public static <E> void swap(List<E> list, int i, int j){}
public static void swap1(List<?> list, int i, int j){} //√ 对外提供公共方法,此种更简洁

第29条 优先考虑类型安全的异构容器

泛型最常用于集合,如Set、Map,以及单元素的容器,如ThreadLocal、AtomicReference。这些用法中它充当被参数化了的容器。
Class 在Java1.5中被泛型化了,类的类型从字面上看是Class<T>,如String.class属于Class<String>类型。
当一个类的字面文字被用在方法中,来传达编译时和运行时的类型信息时,就被称作Type Token。String.class就是一个Type Token。

public <T> void putFavorite(Class<T> type, T instance){
	favorites.put(type, type.cast(instance));  // Class.cast动态转换类型
}

无法为List<String>获得一个Class对象,List<String>.class是个语法错误,List<String>和List<Integer>是同一个类型List.class,同时又验证了泛型参数运行时被擦除的行为。

// Use of asSubclass to safely cast to a bounded type token
static Annotation getAnnotation(AnnotatedElement element,
String annotationTypeName) {
Class<?> annotationType = null; // Unbounded type token
try {
annotationType = Class.forName(annotationTypeName);
} catch (Exception ex) {
throw new IllegalArgumentException(ex);
} r
eturn element.getAnnotation(
annotationType.asSubclass(Annotation.class));  // Class.asSubclass
}

被注解的元素本质上是个类型安全的异构容器,容器的键属于注解类型。

总之,泛型 API 的通常用法(以集合 API 为例)限制了每个容器的固定数量的类型参数。 你可以
通过将类型参数放在键上而不是容器上来解决此限制。 可以使用 Class 对象作为此类型安全异构容器
的键。 以这种方式使用的 Class 对象称为类型令牌。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

洛克Lee

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值