博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
和我一起学Effective Java之泛型
阅读量:6338 次
发布时间:2019-06-22

本文共 9002 字,大约阅读时间需要 30 分钟。

泛型

不要在新代码中使用原始类型

泛型(generic):声明中具有一个或多个类型参数

原始类型(raw type):不带任何实际类型参数的泛型名称

格式: 类或接口的名称 < 对应于泛型形式类型参数的实际参数 >

如 List<String> 就是对应于List<E>的实际参数为String的参数化类型

如与List<E>对应的原始类型是List

优点:

  • 在编译时发现插入类型的错误(越早发现错误越好,编译时发现好于运行时发现)
  • 不再需要手工转换类型
//JDK5之前的写法,使用的是原始类型    private static final List stringList = new ArrayList();    //有了泛型之后的写法,使用泛型    private static final List
stringList2 = new ArrayList
(); //JDK7 能将后面<>里的类型省略,被称为Diamond private static final List
stringList3 = new ArrayList<>(); public static void main(String[] args) { String str = "test"; Integer integer = 1; stringList.add(str); stringList.add(integer);//可通过编译,但之后报ClassCastException错误 stringList2.add(str);// stringList2.add(integer);//无法通过编译 for(Iterator iterator = stringList.iterator();iterator.hasNext();){ String string = (String) iterator.next(); System.out.println(string); } for(Iterator iterator = stringList2.iterator();iterator.hasNext();){ String string = iterator.next(); System.out.println(string); }

ListList<Object>之间的区别?

List逃避了泛型检查,List<Object>则是告知编译器,它能够持有任意类型的对象

无限制的通配符类型:

使用泛型,但不确定或者不关心实际的类型参数,可以用一个问号代替。如List<?>

泛型信息在运行时会被擦除

学习链接:

下面通过一个小demo说明类型擦除

//类型擦除        List
stringList = new ArrayList<>(); List
integerList = new ArrayList<>(); System.out.println(stringList.getClass().toString()); System.out.println(integerList.getClass().toString()); System.out.println(stringList.getClass()==integerList.getClass()); integerList.add(100); Method method = integerList.getClass().getMethod("add",Object.class); method.invoke(integerList,"abc"); System.out.println(integerList);

运行结果:

QQ20160401-0.png

一般不在代码中使用原始类型,除了两种例外情况(都是因为泛型信息在运行时会被擦除):

  • 1.在类文字(class literals)中

如: List.class,String[].class,int.class都合法 List<String>.class,List<String>.class都不合法

  • 2.instanceof
  • if(o instanceof Set){   //原始类型(Raw Type)  Set
    set = (Set
    )o;//通配符类型(WildCard Type)}

下面的表格是泛型相关的术语:

泛型相关的术语

下面这张图很好的介绍了无限制通配符和其他泛型符号之间的关系:

Generics%20Inheritance%20Java.png

消除非受检警告

始终在尽可能小的范围内使用SuppressWarnings注解

Java源码中的ArrayList类中有个toArray方法,其中就有强转的警告:

@SuppressWarnings("unchecked")    public 
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; }

最好是将范围进一步缩小。将注解由整个方法到局部的变量声明上去。

@SuppressWarnings("unchecked")    public 
T[] toArray(T[] a) { if (a.length < size){ @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; }

列表优于数组

  • 数组是协变的(covariant),泛型则是不可变的
Object[] objectArray = new String[1];   List objectList = new ArrayList
();//无法通过编译 imcompatible types // String类是Object类的子类 //String[]是Object[]的子类 //而List
并不是List
的子类型
  • 数组是具体化的(reified),在运行时才知道并检查它们的元素类型约束。而泛型通过擦除来实现的。泛型只在编译时强化类型信息,并在运行时擦除它们的元素类型信息。擦除就是使泛型可以与没有使用泛型的代码可以互用。
Object[] objectArray = new String[1];          List
objectList = new ArrayList
(); objectArray[0] = 3;//可通过编译,运行时报错// objectList.add(1);//编译时报错

QQ20160328-2.png

数组和泛型不能很好地混合使用。可用列表代替数组。

总结:数组是协变且可具体化的,泛型是不可变的且可被擦除的。-->数组提供了运行时类型安全而编译时类型不安全。而泛型反之。

优先考虑泛型

泛型相比于Object的优点:

  • 不需要强制类型转换
  • 编译时类型安全
public class SomeClazz
{ public Object dosthWithObj(Object obj){ return obj; } public T dosthWithT(T t){ return t; } public static void main(String[] args) { SomeClazz
someClazz = new SomeClazz
(); Foo foo = new Foo(); Foo foo1 = (Foo) someClazz.dosthWithObj(foo); Foo foo2 = someClazz.dosthWithT(foo); }}
public class Stack
{ private E [] elements; private static final int MAX_SIZE = 16; private int size = 0; @SuppressWarnings("unchecked") public Stack(){ elements = (E[]) new Object[MAX_SIZE]; } public void push(E e){ ensureSize(); elements[size++]=e; } public E pop(){ if(size==0) throw new EmptyStackException(); E e = elements[--size]; elements[size]=null; return e; } private void ensureSize() { if(size==elements.length){ elements= Arrays.copyOf(elements,2*size+1); } } public static void main(String[] args) { Stack
stack = new Stack<>(); for(int i =0;i<50;i++){ stack.push(i); } for(int i = 0;i<10;i++){ System.out.println(i+": "+stack.pop()); } }}class EmptyStackException extends RuntimeException{}

前面曾鼓励优先使用列表而不是数组。并不意味着所有的泛型中都要使用列表。况且Java并不是生来就支持列表的。

每个类型都是它自身的子类型。

如有 SomeClazz
SomeClazz
是合法的

优先考虑泛型方法

方法可以考虑泛型化,特别是静态工具方法。

泛型方法语法:

方法修饰语 泛型 返回值 方法名()

public static <T> T foo(T args);

/**     * 使用泛型方法     * 返回两个集合的联合     * @param s1     * @param s2     * @param 
* @return * * 局限:两个参数和返回的结果的类型必须全部相同 * 解决方法:使用有限制的通配符 */ public static
Set
unionGeneric(Set
s1,Set
s2){ Set
result = new HashSet<>(s1); result.addAll(s2); return result; }public static
Map
newHashMap(){ return new HashMap
(); }

泛型单例工厂:

public interface UnaryFunction
{ T apply(T arg); } private static UnaryFunction
IDENTITY_FUNCTION = new UnaryFunction() { @Override public Object apply(Object arg) { return arg; } }; @SuppressWarnings("unchecked") public static
UnaryFunction
identityFunction(){ return (UnaryFunction
) IDENTITY_FUNCTION; } /** * 每次都要创建一个,很浪费,而且它是无状态的. * 泛型被具体化了,每个类型都需要一个恒等函数,但是它们被擦除后,就只需要一个泛型单例了. * @param
* @return */ public static
UnaryFunction
identityFunction2(){ return new UnaryFunction
() { @Override public T apply(T arg) { return arg; } }; }

递归类型限制:

通过某个包含该类型参数本身的表达式来限制类型参数

>//针对可以与自身进行比较的每个类型T

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

参数化类型是不可变的。

虽然String类是Object类的子类,但是List
和List
无关
/** * 栈的实现 * @param 
* API: * public Stack(); * public void push(E e); * public E pop(); * public boolean isEmpty(); * * 新增API: * before: * public void pushAll(Iterable
i); * public void popAll(Collection
c); * after: * 使用有限制的通配符类型(bounded wildcard type) * public void pushAll(Iterable
i); * public void popAll(Collection
c); * */class Stack
{ private E [] elements; private static final int INIT_CAPABILITY = 16; private int size = 0; @SuppressWarnings("unchecked") public Stack(){ elements = (E[]) new Object [INIT_CAPABILITY]; } public void push(E e){ checkCapability(); elements[size++]=e; } public E pop(){ if(size==0) throw new RuntimeException("Empty Stack"); E e = elements[--size]; elements[size]=null; return e; } private void checkCapability() { if(size==elements.length) elements = Arrays.copyOf(elements,2*elements.length-1); } public boolean isEmpty(){ return size==0; } @Override public String toString() { StringBuilder sb = new StringBuilder(); String start = super.toString(); sb.append(start); for(int i = 0 ;i
i){
// for(E e:i){
// push(e);// }// }// public void popAll(Collection
c){ // while (!isEmpty()){ // c.add(pop());// }// } //after public void pushAll(Iterable
i){ for(E e:i){ push(e); } } public void popAll(Collection
c){ while(!isEmpty()){ c.add(pop()); } } Stack
stack= new Stack<>(); Iterable
integers = Arrays.asList(1,2,3,4,5); Collection
objectCollection = new LinkedList<>(); //before// stack.pushAll(integers);//参数类型不对// stack.popAll(objectCollection);//参数类型不对 //after stack.pushAll(integers); System.out.println(stack); stack.popAll(objectCollection); System.out.println(stack);

从上面的Demo中我们知道,Java中提供了有限制的通配符类型来提高API的灵活性。

Collection<? extends E>

Collection<? super E>

一般在表示生产者消费者的输入参数上使用通配符类型。

PECS:Producer-extends Consumer-super

------------------     * 参数化类型  通配符类型     *  T生产者   extends     *  T消费者   super     * ------------------
原文地址:http://www.cnblogs.com/JohnTsai/p/5344733.html
你可能感兴趣的文章
巧用AJAX技术,通过updatePanel控件实现局部刷新
查看>>
20140420技术交流活动总结
查看>>
SaltStack配置salt-api
查看>>
各种情况下block的类型
查看>>
ThinkPHP 3.2.x 集成极光推送指北
查看>>
js作用域链
查看>>
java中如何选择Collection Class--java线程(第3版)
查看>>
为运维人员插上腾飞更远的翅膀!
查看>>
Word 2003中编辑标记与格式标记大讨论
查看>>
从国内向海外转移域名经验谈
查看>>
浅谈apache与tomact的整合
查看>>
SQL Server vNext CTP1 on Linux
查看>>
1-为 Lync Server 2010 准备 Active Directory 域服务
查看>>
NetBackup下ORACLE恢复测试方案实例解析
查看>>
【有奖征文】“失业”程序员的苦辣酸甜
查看>>
IE9是如何被FireFox4超越全球市场份额的?
查看>>
linux bunzip2命令
查看>>
敏捷个人:通过实践TOGAF来思考如何学习并应用新的方法?
查看>>
Android系统的开机画面显示过程分析(6)
查看>>
vivo Hi-Fi+QQ音乐 数字音乐市场的一剂良方
查看>>