和我一起学Effective Java之泛型

泛型

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

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

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

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

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

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

优点:

  • 在编译时发现插入类型的错误(越早发现错误越好,编译时发现好于运行时发现)
  • 不再需要手工转换类型
   //JDK5之前的写法,使用的是原始类型
    private static final List stringList = new ArrayList();

    //有了泛型之后的写法,使用泛型
    private static final List<String> stringList2 = new ArrayList<String>();

    //JDK7 能将后面<>里的类型省略,被称为Diamond
    private static final List<String> 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<?>

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

学习链接:

1.https://docs.oracle.com/javase/tutorial/java/generics/erasure.html

2.http://stackoverflow.com/questions/313584/what-is-the-concept-of-erasure-in-generics-in-java

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

      //类型擦除
        List<String> stringList = new ArrayList<>();
        List<Integer> 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);

运行结果:

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

  • 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)
    }

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

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

消除非受检警告

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

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

 @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;
    }

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

 @SuppressWarnings("unchecked")
    public <T> 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<Object> objectList = new ArrayList<String>();//无法通过编译 imcompatible types
   // String类是Object类的子类
   //String[]是Object[]的子类
   //而List<String>并不是List<String>的子类型  
  • 数组是具体化的(reified),在运行时才知道并检查它们的元素类型约束。而泛型通过擦除来实现的。泛型只在编译时强化类型信息,并在运行时擦除它们的元素类型信息。擦除就是使泛型可以与没有使用泛型的代码可以互用。


          Object[] objectArray = new String[1];
          List<String> objectList = new ArrayList<String>();
          objectArray[0] = 3;//可通过编译,运行时报错
//        objectList.add(1);//编译时报错

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

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

优先考虑泛型

泛型相比于Object的优点:

  • 不需要强制类型转换
  • 编译时类型安全

public class SomeClazz<T> {
    public Object dosthWithObj(Object obj){
        return obj;
    }

    public T dosthWithT(T t){
        return t;
    }

    public static void main(String[] args) {
        SomeClazz<Foo> someClazz = new SomeClazz<Foo>();
        Foo foo = new Foo();
        Foo foo1 = (Foo) someClazz.dosthWithObj(foo);
        Foo foo2 = someClazz.dosthWithT(foo);
    }
}
public class Stack<E> {
    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<Integer> 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<E extends Number>

    SomeClazz<Number>是合法的

优先考虑泛型方法

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

泛型方法语法:

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

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

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

public static <K,V> Map<K,V> newHashMap(){
        return new HashMap<K,V>();
    }

泛型单例工厂:

 public interface UnaryFunction<T>{
        T apply(T arg);
    }
    private static UnaryFunction<Object> IDENTITY_FUNCTION =
            new UnaryFunction<Object>() {
                @Override
                public Object apply(Object arg) {
                    return arg;
                }
            };

    @SuppressWarnings("unchecked")
    public static <T> UnaryFunction<T> identityFunction(){
        return (UnaryFunction<T>) IDENTITY_FUNCTION;
    }

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

递归类型限制:

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

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

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

参数化类型是不可变的。

虽然String类是Object类的子类,但是List<String>和List<Object>无关
/**
 * 栈的实现
 * @param <E>
 * API:
 *   public Stack();
 *   public void push(E e);
 *   public E pop();
 *   public boolean isEmpty();
 *
 * 新增API:
 *   before:
 *     public void pushAll(Iterable<E> i);
 *     public void popAll(Collection<E> c);
 *   after:
 *     使用有限制的通配符类型(bounded wildcard type)
 *    public void pushAll(Iterable<? extends E> i);
 *    public void popAll(Collection<? super E> c);
 *
 */

class Stack<E>{
    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<size;i++){
            String s = " ["+elements[i]+"]";
            sb.append(s);
        }
        return sb.toString();
    }

    //before
//    public void pushAll(Iterable<E> i){
//        for(E e:i){
//            push(e);
//        }
//    }
//    public void popAll(Collection<E> c){
//        while (!isEmpty()){
//            c.add(pop());
//        }
//    }
    //after
    public void pushAll(Iterable<? extends E> i){
        for(E e:i){
            push(e);
        }
    }
    public void popAll(Collection<? super E> c){
        while(!isEmpty()){
            c.add(pop());
        }
    }

        Stack<Number> stack= new Stack<>();
        Iterable<Integer> integers = Arrays.asList(1,2,3,4,5);
        Collection<Object> 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

时间: 2024-11-17 19:11:25

和我一起学Effective Java之泛型的相关文章

和我一起学Effective Java之创建和销毁对象

前言 主要学习创建和销毁对象: 1.何时以及如何创建对象 2.何时以及如何避免创建对象 3.如何确保它们能够适时地销毁 4.如何管理对象销毁之前必须进行的清理动作 正文 一.用静态工厂方法代替构造器 获取类的实例的常用方法有: 1.公有的构造器 2.公有的静态工厂方法 下面通过Boolean类(基本类型boolean的包装类)的简单示例来学习: //公有的构造器 public Boolean(String s) { this(parseBoolean(s)); } //公有的静态工厂方法 pub

和我一起学Effective Java之类和接口

类和接口 使类和成员的可访问性最小 信息隐藏(information hiding)/封装(encapsulation):隐藏模块内部数据和其他实现细节,通过API和其他模块通信,不知道其他模块的内部工作情况. 原因:有效地解除各模块之间的耦合关系 访问控制机制(access control):决定类,接口和成员的可访问性.由声明的位置和访问修饰符共同决定. 对于顶层的类和接口,两种访问级别: 包级私有的(package-private) 公有的(public) 对于成员(域,方法,嵌套类,嵌套

Android 中的 Effective Java(速查表)

本文讲的是Android 中的 Effective Java(速查表), Effective Java 是一本被广泛认可的著作,它指明了在写 Java 代码时兼顾可维护性与效率的方式.Android 也是使用 Java 来开发的,这意味着前书中的所有建议仍旧可用,真的是这样吗?并不尽然.某些同学 认为书中的"大部分"建议都不适用于 Android 开发,但我认为并不是这样.我承认书中的部分建议确实不适用,因为并非所有 Java 特性都有针对 Android 优化(比如说枚举,序列化等等

Java 中泛型的全面解析(转)

Java泛型(generics) 是JDK 5中引入的一个新特性,允许在定义类和接口的时候使用类型参数(type parameter).声明的类型参数在使用时用具体的类型来替换.泛型最主要的应用是在JDK 5中的新集合类框架中.对于泛型概念的引入,开发社区的观点是褒贬不一.从好的方面来说,泛型的引入可以解决之前的集合类框架在使用过程中通常会出现的运行时刻类型错误,因为编译器可以在编译时刻就发现很多明显的错误.而从不好的地方来说,为了保证与旧有版本的兼容性,Java泛型的实现上存在着一些不够优雅的

java的泛型集合求大神指教

问题描述 java的泛型集合求大神指教 animal ani=new animal("欧欧", "欧瑞娜"); animal ani2=new animal("丫丫", "拉布拉多"); animal ani3=new animal("菲菲", "拉布拉多"); animal ani4=new animal("美美", "欧瑞娜"); List l

Effective Java Second Edition中文版已出版

http://yulimin.javaeye.com/blog/340464 我自己今天才刚见到书:) 译者序 Java从诞生到日趋完善,经过了不断的发展壮大,目前全世界拥有了成千上万的Java开发人员.如何编写出更清晰.更正确.更健壮且更易于重用的代码,是大家所追求的目标之一.作为经典Jolt获奖作品的新版书,它已经进行了彻底的更新,涵盖了自第1版之后所引入的Java SE 5和Java SE 6的新特性.作者探索了新的设计模式和语言习惯用法,介绍了如何充分利用从泛型到枚举.从注解到自动装箱的

适用于Android开发的Effective Java

"Effective Java" 被许多人看做是编写高效且可维护的 Java 代码的重要指导书之一.Android 使用 Java 开发是否意味着里面的建议都要用上?不完全是. 有些人认为这本书给出的大多数建议不适用于 Android 开发.在我看来,情况并非如此. 我认为这本书的一些部分是不适用,不管是因为不是所有 Java 功能都已优化到能与 Android 一起使用(例如枚举.序列化等),还是因为是移动设备的限制例如 Dalvik/ART 表现不同于桌面版的 JVM). 但不管怎

Effective Java --&amp;amp;gt;(一)创建和销毁对象

创建|对象 Effective Java学习笔记JAVA语言支持四种基本类型:接口(Interface).类(Class).数组(Array).和原语类型(Primitive).前三种类型通常被称为引用类型(reference type),类的实例和数组是对象(object),而原语类型的值不是对象.一个类的成员(member)包括它的域(field),方法(method),成员类(member class)和成员接口(member interface).一个方法的原型(signature)包括

Effective Java (2) 遇到多个构造器参数时要考虑用构建器

一.背景 对于有多个可选参数的类,我们一般通过什么办法传递参数呢?这里提供了三种办法: ①. 重叠构造器模式 ②. JavaBeans模式 ③. Builder构建器模式 下面我们来分析一下以上三种方法的优势及弊端. 二.重叠构造器模式 重叠构造器模式中第一个构造器中只有必要参数,第二个构造器有一个可选参数,第三个构造器中有两个可选参数,依次类推,最后一个构造器中包含所有可选参数.这种方案可行,但是有较大缺陷. 缺点:当有很多可选参数的时候,客户端代码很难编写,并难以阅读,如果客户端不小心颠倒了