第23条: 不要在新代码中使用原生态类型

声明中具有一个或多个类型参数的类或接口,就是泛型类或接口。每种泛型都定义一组参数化的类型,每个泛型都定义一个原生态类型。例如List<E>相对应的原生态类型是List。

public class RawTypeTest {
public static void main(String[] args) {
Collection stamps = new ArrayList(); //raw type
// Collection<Stamp> stamps = new ArrayList();
stamps.add(new Stamp());
stamps.add(new Stamp());
stamps.add(new Coin());
for(Iterator i = stamps.iterator(); i.hasNext(); ){
Stamp s = (Stamp)i.next();
System.out.println("xxx");
}
}
}
class Stamp{}
class Coin{}

该程序会出现ClassCastException。但如果使用泛型,则会在编译期就能准确告诉我们哪里出错了。而且也不用对元素进行手工转换,编译器会插入隐式转换。可以看出,如果不提供类型参数,使用集合类型和其他泛型仍然是合法的,但如果使用原生态类型,就会失掉类型安全性。看下面的例子:

public class RawTypeTest2 {

    public static void main(String[] args) {
// TODO Auto-generated method stub
List<String> strings = new ArrayList<String>();
unsafeAdd(strings, new Integer(42));
String s = strings.get(0);
} private static void unsafeAdd(List list, Object o){
list.add(o);
}
}

该程序也会出现ClassCastException,但在编译期没有任何错误。泛型有子类化规则,List<String>是原生态类型List的一个子类型,但不是List<Object>的子类型。所以List<String>

引用可以传递给类型List的参数,但不能传递给类型list<Object>的参数。原生态类型和参数化的类型List<Object>之间的区别是,前者逃避了泛型检查,后者则明确告诉编译期,它能够持有任意类型的对象。如果将unsafeAdd(List list, Object o)改为unsafeAdd(List<Object> list, Object o),则在编译期就会报错。所以使用原生态类型很危险,如果要使用泛型,但不确定或不关心实际的类型参数,就可以使用一个问号代替。

private static int numElementsInCommon(Set<?> s1, Set<?> s2){
int result = 0;
for(Object o1 : s1){
if(s2.contains(o1)){
result++;
}
}
return result;
}

无限制通配类型Set<?>和原生态类型Set的区别是前者是类型安全的,原生态类型可以将任何元素放进使用原生态类型的集合中,很容易破坏该集合的类型约束条件。但我们不能把任何元素(除null之外)放到Collection<?>中,也就是说只能往Collection<?>放null,这样就不会破坏类型约束条件。

不要在新代码中使用原生态类型这个规则有两个小小的例外:

1.在类文字中必须使用原生态类型。如List.class, int.class合法,而List<String>.class, List<?>.class则不合法。

2.instanceof操作符中使用原生态类型。

if(o instanceof Set){   //raw type
Set<?> m = (Set<?>)o; //wildcard type
//remainder omitted
}

这是泛型使用instanceof操作符的首选方法。一旦确定o是个set,就必须把它转换成通配符类型。

第24条: 消除非受检警告

用泛型编程时,会遇到很多编译期警告。有些非受检警告难以消除,但也要尽可能消除每一个非受检警告。如果消除了所有警告,就可以确保代码是类型安全的。如果无法消除警告,同时可以证明引起警告的代码是类型安全的,就可以用一个@SuppressWarnings("unchecked")注解来禁止这条警告。但也应注意要在尽可能小的范围内使用SuppressWarning注解。

    public <T> T[] toArray(T[] a) {
if (a.length < size){
// Make a new array of a's runtime type, but my contents:
@SuppressWarnings("unchecked") T[] result= (T[]) Arrays.copyOf(elementData, size, a.getClass());
return result;
}
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}

每当使用SuppressWarning注解时,都要添加一条注解,说明为什么这么做是安全的。总之,非受检警告很重要,不要忽略他们。

第25条: 列表优先于数组

数组与泛型相比,有两个重要的不同点。首先,数组是协变的。表示如果sub为super的子类型,那么数组类型sub[]就是 super[]的子类型。泛型则是不可变的,对于任意两个不同的type1和type2,List<Type1>既不是 List<Type2>的子类型,也不是 List<Type2>的超类型。

public static void main(String[] args) {
// fails at runtime
Object[] objectArray = new Long[1];
objectArray[0] = "I don't fit in";
System.out.println(objectArray[0]); //won't compile!
// List<Object> ol = new ArrayList<Long>();
// ol.add("I don't fit in");
}

第二大区别在于,数组是具体化的,因此数组在运行时才知道并检查他们元素的约束类型。相比之下,泛型则是通过擦除来实现的。泛型只在编译期强化它们的类型信息,并在运行时丢弃它们的元素类型信息。擦除就是使泛型可以与没有使用泛型的代码随意进行互用。由于这些区别,因此数组和泛型不能很好地混用,new List<E>[], new List<String>[], new E[] 这些都会导致一个“泛型数组创建”的错误。所以发现自己将它们混合起来使用,第一反应就应该是用列表代替数组。

第26条: 优先考虑泛型

使用泛型和泛型方法比较容易,但自己编写泛型类则比较困难。

我们将前面写过的stack类改为泛型类,在这过程中至少会碰到一个问题:

public Stack(){
elements = new E[DEFAULT_INITIAL_CAPACITY]; //generic array creation exception
}

在构造器中这样写,会出现generic array creation 异常。我们不能创建不可具体化的类型数组。有两种方法可以选:

public class Stack<E> {
// private E[] elements;
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
// @SuppressWarnings("unchecked")
public Stack(){
elements = new Object[DEFAULT_INITIAL_CAPACITY];
// 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();
}
@SuppressWarnings("unchecked")
E result = (E) elements[--size];
elements[size] = null;
return result;
}
private void ensureCapacity(){
if(elements.length == size){
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}

一种方法是把私有域E[]改为Object[],并修改相应的pop方法。第二种是在构造器中创建Object数组,并把它转为E数组。但要证实未受检的转换是安全的,因此可以禁止该警告。具体选哪种方法看个人偏好。如在代码中有许多地方需要从数组中读取元素,因此采用第一种方法需要多次转换成E,所以第二种方法可能更常用些。

总而言之,使用泛型比使用需要在客户端代码中进行转换的类型来的更加安全,也更加容易。所以只要时间允许,就把现有的类型都泛型化。这对于这些类的新用户来说会变得更加轻松,又不会破坏现有的客户端。

第27条: 优先考虑泛型方法

就如类可以从泛型中受益一样,方法也一样。静态工具方法尤其适合泛型化。

考虑两个集合联合的方法:

public static Set union(Set s1, Set s2){
Set result = new HashSet(s1);
result.addAll(s2);
return result;
}

上述代码会有两条警告,指出不应使用原生态类。为了修正这些警告,使方法成为类型安全的,需要将方法声明修改为一个类型参数。

public class GenericMethods {
public static <E> Set<E> union(Set<E> s1, Set<E> s2){
Set<E> result = new HashSet<E>(s1);
result.addAll(s2);
return result;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Set<String> guys = new HashSet<String>(Arrays.asList("Tom", "Dick", "Harry"));
Set<String> stooges = new HashSet<String>(Arrays.asList("Larry", "Moe", "Curly"));
Set<String > xxx = union(guys, stooges);
System.out.println(xxx);
} }

对于简单的泛型方法而言,就是这么回事。union方法的局限在于,三个集合类型必须全部相同。利用有限制的通配符类型,可以使这个方法变得更加灵活。泛型方法的一个显著特点是,无需明确指出类型参数的值,编译器通过检查方法参数的类型来计算类型参数的值。对于上述程序而言,union两个参数都是Set<String>,从而知道E必须是String,这个过程称作为类型推导。总之,应确保新方法不用转换就能使用,这意味着要将它们泛型化。

第28条: 利用有限制通配符来提升API的灵活性

之前我们提到了<?>形式的无限制通配符,这里则是有限制通配符。上一条目中已经出现了有限制通配符,它一共有这么2种:

<? extends E>:表示可接受E类型的子类型;

<? super E>:表示可接受E类型的父类型。

增加pushAll和popAll后的stack程序:

public class Stack<E> {
// private E[] elements;
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
// @SuppressWarnings("unchecked")
public Stack(){
// elements = new E[DEFAULT_INITIAL_CAPACITY]; //generic array creation exception
elements = new Object[DEFAULT_INITIAL_CAPACITY];
// 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();
}
@SuppressWarnings("unchecked")
E result = (E) elements[--size];
elements[size] = null;
return result;
}
private void ensureCapacity(){
if(elements.length == size){
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
public void pushAll(Iterable<? extends E> src){
for(E e : src){
push(e);
}
}
private boolean isEmpty(){
return size == 0;
}
public void popAll(Collection<? super E> dst){
while(!isEmpty()){
dst.add(pop());
}
}
public static void main(String[] args){
Stack<Number> numberStack = new Stack<>();
final Collection<Integer> intList = new ArrayList<>();
intList.add(1);
intList.add(2);
intList.add(3);
Iterable<Integer> integers = new Iterable<Integer>() {
public Iterator<Integer> iterator() {
// TODO Auto-generated method stub
return intList.iterator();
}
};
numberStack.pushAll(integers); Collection<Object> objects = new ArrayList<>();
numberStack.popAll(objects);
System.out.println(objects.toString());
}
}

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

一个Favorites类:

public class Favorites {
private Map<Class<?>, Object> favorites = new HashMap<>();
public <T> void putFavorite(Class<T> type, T instance){
if(type == null){
throw new NullPointerException("type is null");
}
favorites.put(type, instance);
}
public <T> T getFavorite(Class<T> type){
return type.cast(favorites.get(type));
}
public static void main(String[] args){
Favorites f = new Favorites();
f.putFavorite(String.class, "Java");
f.putFavorite(Integer.class, 0xcafebabe);
f.putFavorite(Class.class, Favorites.class);
String favoritesString = f.getFavorite(String.class);
Integer favoritesInteger = f.getFavorite(Integer.class);
Class<?> favoritesClass = f.getFavorite(Class.class);
System.out.println(favoritesString + " | " + favoritesInteger + " | " + favoritesClass.getName());
}
}

Favorites实例是类型安全的,当向它请求String的时候,它从来不会返回一个Integer给你。同时也是异构的,不像普通的map,它的所有键都是不同类型的,因此我们将Favorites称为类型安全的异构容器。从代码我们可以看出该类跟据键取出来的值是Object,这是很危险的一件事情。我们写的代码能在编译时检查就在编译时检查而不要等到真正运行起来才做检查,这也就是上面Favorites所带来的好处,它是类型安全的,同时它也是异构的,这个例子值得细细品味。

4. 泛型_EJ的更多相关文章

  1. 一起学 Java(三) 集合框架、数据结构、泛型

    一.Java 集合框架 集合框架是一个用来代表和操纵集合的统一架构.所有的集合框架都包含如下内容: 接口:是代表集合的抽象数据类型.接口允许集合独立操纵其代表的细节.在面向对象的语言,接口通常形成一个 ...

  2. .NET面试题系列[8] - 泛型

    “可变性是以一种类型安全的方式,将一个对象作为另一个对象来使用.“ - Jon Skeet .NET面试题系列目录 .NET面试题系列[1] - .NET框架基础知识(1) .NET面试题系列[2] ...

  3. C#4.0泛型的协变,逆变深入剖析

    C#4.0中有一个新特性:协变与逆变.可能很多人在开发过程中不常用到,但是深入的了解他们,肯定是有好处的. 协变和逆变体现在泛型的接口和委托上面,也就是对泛型参数的声明,可以声明为协变,或者逆变.什么 ...

  4. 编写高质量代码:改善Java程序的151个建议(第7章:泛型和反射___建议106~109)

    建议106:动态代理可以使代理模式更加灵活 Java的反射框架提供了动态代理(Dynamic Proxy)机制,允许在运行期对目标类生成代理,避免重复开发.我们知道一个静态代理是通过主题角色(Prox ...

  5. 6.在MVC中使用泛型仓储模式和依赖注入实现增删查改

    原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/crud-operations-using-the-generic-repository-pat ...

  6. C#泛型详解(转)

    初步理解泛型: http://www.cnblogs.com/wilber2013/p/4291435.html 泛型中的类型约束和类型推断 http://www.cnblogs.com/wilber ...

  7. C# 泛型

    C# 泛型 1.定义泛型类 在类定义中包含尖括号语法,即可创建泛型类: class MyGenericClass<T> { //Add code } 其中T可以遵循C#命名规则的任意字符. ...

  8. java8中lambda表达式的应用,以及一些泛型相关

    语法部分就不写了,我们直接抛出一个实际问题,看看java8的这些新特性究竟能给我们带来哪些便利 顺带用到一些泛型编程,一切都是为了简化代码 场景: 一个数据类,用于记录职工信息 public clas ...

  9. java 泛型

    1.Student stu =tool.getObj();右边得到的是Object类型,需要向下转型,强转换. 2. 3. 4.泛型方法不能被静态修饰这样写 5.如果想定义定义静态泛型方法,只能这样写 ...

随机推荐

  1. 升讯威微信营销系统开发实践:所见即所得的微官网( 完整开源于 Github)

    GitHub:https://github.com/iccb1013/Sheng.WeixinConstruction因为个人精力时间有限,不会再对现有代码进行更新维护,不过微信接口比较稳定,经测试至 ...

  2. 微服务中Feign快速搭建

    在微服务架构搭建声明性REST客户端[feign].Feign是一个声明式的Web服务客户端.这使得Web服务客户端的写入更加方便 要使用Feign创建一个界面并对其进行注释.它具有可插入注释支持,包 ...

  3. 来啊踩fastjson打印入参导致业务跑偏的坑

    线上代码对日志的记录,重要性自不必说.但是怎样记录日志也是有讲究的! 日志可以直接在每个方法中进行日志记录,优点是想怎么记就怎么记,缺点是记日志的代码可能会超过你的业务代码,可读性急剧下降,这也是日志 ...

  4. 原生JS-旋转木马

    原生JS-旋转木马 今天写一个原生JS写的旋转木马JS效果. 实现原理: 1.建立一个数组给每一张图片写对应的z-index,opacity,top,width: 2.实现旋转的操作是把建造的数组里面 ...

  5. jQuery之animate()用法

    最近在学习jQuery,看到一个很有意思的函数animate(),但是在网上却没有查到相关的详细资料,于是打算参考jQuery API,自己总结一下. 概述 animate() 方法执行 CSS 属性 ...

  6. ubuntu 16.04下安装ADB

    1. 安装adb工具. 从谷歌的网站下载LINUX adb调试工具(FQ),当然可以随便百度一个一大堆. http://developer.android.com/tools/device.html ...

  7. 【PHP篇】输出方法

    php开始处加:error_reporting(E_ALL & ~E_NOTICE);  //不打印注意 echo: echo “字符串”;   //也可为单引号 echo $变量名; ech ...

  8. 冒泡 MS Azure 不便宜。。。

    一直在等 MS Azure 中国开卖, 最近有消息说正式商用了... 看看去,ok 发现官方网站 很奇葩.没有购买的地方 说毛线 啊 卧槽 欺骗感情还是吊人胃口? 好看了一下VM的价格,卧槽真不便宜. ...

  9. Abp + MongoDb 改造默认的审计日志存储位置

    一.背景 在实际项目的开发当中,使用 Abp Zero 自带的审计日志功能写入效率比较低.其次审计日志数据量中后期十分庞大,不适合与业务数据存放在一起.所以我们可以重新实现 Abp 的 IAuditi ...

  10. Swing——布局管理器

    前言 在编写图形界面时,总是需要考虑的就是组件放在哪里,组件怎么大才合适.在Swing中就有现成的布局管理器帮我们做这些事情,我们不必写代码去一一安排.下面将介绍什么是布局管理器.Swing中常用布局 ...