java泛型使用建议

1、建议不要使用原生态类型,很容易出现类型转换异常
有些人不能理解原生态的List 和 List(Object)有什么区别?虽然原生态list和list(Object)都可以放置任何类型的元素,但是原生态可以传递任何类型泛型的list,比如 list a = new ArrayList(); list(String) b = new ArrayList<>();
a = b; 但是不同泛型类型之间不能传递 如果你把上面的 a换成list(Object)就会编译不通过。其实这也很好理解,原生态是最初状态的list,所以大家都可以传递,但是list(Object)是泛型list,只是这种泛型允许任意类型的元素,但是不支持其他类型传递。如果支持传递了,就很奇怪,比如List(Object) a = List(String) b 把一个泛型安全的为String的b集合传递给a,但是a又是允许任意类型的泛型集合,所以a.add(2),这时候b中也会出现2这个元素,这就很奇怪了,因为b是String类型的呀!
看上去有点绕口,举个例子你就知道了!!
下面展示一些 内联代码片

// A code block
// An highlighted block
public static void main(String[] args) {
        List<String> strings = new ArrayList<>();
        unsafeAdd(strings, 4);
        String s = strings.get(0);
    }

    /**
     * 如果你的参数使用的是原生态类型,这很危险,因为会容易产生类型转换异常
     * 倘若你将参数换成List<Object>编译器就会不让通过 继而达到安全
     * @param strings
     * @param o
     */
    private static void unsafeAdd(List strings, Object o) {
        strings.add(o);
    }

其实java使用原生态类型,完全是为了兼容早期泛型未出现时的版本代码,泛型是编译时类型加强,运行时类型擦除成原生态类型,所以原生态类型理论上不该出现在你的代码中。
2、列表优于数组安全
数组是协变的,比如Object[] a = new Long[1];父类型指向子类型,并且数组是具体化的,上面的数组a中的元素的具体类型是在运行时强化的,泛型则是编译时强化的,如果你在a中放置一个String类型的元素,只会在运行时抛出异常,而泛型则可以在编译时就具有类型安全!因此不好混用数组和泛型,因为一个是运行时确定类型,一个要求编译时确定类型,如new E[]、new List<泛型>[]都是错误的!所以好的方式就是用List<泛型>代替数组E[]
看一段代码吧:
下面展示一些 内联代码片

// An highlighted block
public class Chooser<T> {
    private final T[] choiceArray;

    //通过列表的方式 安全和互用性
    private List<T> tList;

    /**
     * 编译器告诉我们他无法在运行的时候知道类型
     * 因为擦除 但是实际上是可以运行的
     *
     * @param choices
     */
    public Chooser(Collection<T> choices) {
        tList = new ArrayList<>(choices);
        //可以加注解 或者采取上面的方式
        choiceArray = (T[]) choices.toArray();
    }
}

列表的方式可能运行速度慢点,但是绝对更安全,如果采用数组则要消除编译器的警告,并注释保证不会出现未受检异常。而且该数组千万不能返回给客户端,或者让其访问,同时也不能传递给其他不安全的方法,因为他实际上是一个数组,虽然你在Chooser构造器中强转的时候是可以保证安全的,但是并不能代表别人使用的时候安全,在其他方法中使用的时候安全。比如
客户端用一个Objec【】数组接受的时候,因为数组是协变的,而且是运行时类型加强,所以Object[0]=1;Object[1]=“a”;都是合法的,但是很明显T[]数组中的元素就不是类型安全的了。
3、优先考虑泛型、泛型方法
4、利用有限制通配符提高api灵活性
有一种方法effective java中称之为pecs,producer-extends,consumer-super,现在有一个类型安全的栈,它遵循第三条建议,现在有一个pushAll方法,将某个集合中的所有元素放入栈中,还有一个popAll方法,将栈中的所有元素取出来,放入一个集合中,刚开始你可能是这样设计的:
下面展示一些 内联代码片

// An highlighted block
public class Stack<E> {
    private E[] elements;
    private int size = 0;
    private static final int DEFAULT_INITCAPICITY = 16;


    /**
     * 这种用法是合法的但不是类型安全的,必须自己保证安全,私有化数组,push时只能存放对应类型元素才安全
     * 当然还有一种方法就是Object数组 保证push和pop时类型一致即可
     * 第一种方式优先,因为第二种方式每次pop都要转换一下  但是第一种方式会导致堆污染:数组的运行时类型与它的
     * 编译时类型不匹配(除非E正好是Object)
     */
    @SuppressWarnings("unchecked")
    public Stack() {
        //禁止泛型数组有些讨厌 这表明泛型一般不能返回它的元素类型数组
        this.elements = (E[]) new Object[DEFAULT_INITCAPICITY];
    }

    public void push(E e) {
        ensureCapicity();
        elements[size++] = e;
    }

    public void pushAll(Iterable<E> iterable) {
        for (E e : iterable) {
            push(e);
        }
    }

    /**
     * PECS 灵活 producer extends consumer super
     *
     * @param collection
     */
    public void popAll(Collection<E> collection) {
        while (!isEmpty()) {
            collection.add(pop());
        }
    }

    public E pop() {
        if (size == 0) {
            throw new EmptyStackException();
        }
        E result = elements[--size];
        /**
         * 这里为什么要置为null呢,就是消除过期的引用防止内存泄漏
         * 对于stack来说只要size范围内的引用
         */
        elements[size] = null;
        return result;
    }

    /**
     * 扩容
     */
    private void ensureCapicity() {
        if (elements.length == size) {
            elements = Arrays.copyOf(elements, 2 * size + 1);
        }
    }
}

上面代码看上去很完美了,因为他已经做到类型安全了,但是他不够灵活,什么意思?比如我有这样一个代码Stack(Number) s =new Stack();我想往里面放入Interger元素怎么办?这就做不到了,还好我们有通配符:改写上面pushAll方法:
下面展示一些 内联代码片

// An highlighted block
public void pushAll(Iterable<? extends E> iterable) {
        for (E e : iterable) {
            push(e);
        }
    }

但是对于popAll方法,我想要把他取回到某一个集合中,比如Collection< Object >中,最开始的设计也不能满足要求,还好有通配符:
下面展示一些 内联代码片

// An highlighted block
public void popAll(Collection<? super E> collection) {
        while (!isEmpty()) {
            collection.add(pop());
        }
    }

这就是pecs,很灵活,popAll返回客户端消费者用super,pushAll生产元素用extend!
5、小心泛型和可变参数
先介绍个概念叫做堆污染,当一个参数化类型的变量指向一个不是该类型的变量时就会产生堆污染,很多情况下都是泛型指向数组。然后解释一个问题,我们回过头看第二条的时候说过泛型和数组不好混用,因为一个是编译时类型加强,一个是运行时类型加强,所以你不能使用如E[] e = new E[];但是你却可以声明一个泛型数组变量E[] e;只要你不显示创建,这很矛盾,但是java允许,就是因为很多情况下使用它会很便利,而且可读性也好。Java类库有好多这样的方法如Arrays.asList(T…a);这里说明下可变参数实际上就是一个数组。不过,实际上将泛型指向数组是很危险的,那Java还存在泛型指向数组只是因为实际用途多。那么如何保证这种堆污染是安全的呢?你可能使用了@SafeVarages注解,但是他并不能保证安全。经过前面的总结,你可能会想,如果我在使用泛型数组的方法里,不增加元素,并且保护好数组,不就能保证安全了吗?大多数情况下,这样确实安全了,但是还是有隐患,我们看一个例子:
下面展示一些 内联代码片

// An highlighted block
    static <T> T[] toArray(T... args) {
        return args;
    }

    /**
     * 编译器会根据类型推导出如返回String[]  但是运行时擦除将只会返回Object[]
     * 这时就会报错
     * @return
     */
    static <T> T[] pickTwo(T a, T b, T c) {
        switch (ThreadLocalRandom.current().nextInt(3)) {
            case 0:
                return toArray(a, b);
            case 1:
                return toArray(a, c);
            case 2:
                return toArray(b, c);
            default:
                return toArray(a, b, c);
        }
    }
    public static void main(String[] args) {
        //为何下面代码会报错 Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
        String[] strings = pickTwo("Good", "Fast", "Cheap");
    }

这段程序看似是安全的,但实际上运行后你会发现ClassCastException,为什么呢?因为对于泛型jvm会擦除成原生态的Object,所以实际上在运行时toArray方法会产生一个Object[]数组,而在调用peckTwo方法返回的是类型推导的String[],就会导致类型转换异常。但是你单独调用toArray是不会产生异常的,这是因为单独调用toArray会生成String[],但是多次调用jvm只能返回给你一个Object[]。这里的话,只能解释到这,具体我也存在疑惑。 不过你记住不要让其他方法访问泛型可变参数方法即可。那该如何解决上述问题呢?如果我想要该方法被别人访问,就可以采用第二条建议,将参数变为list即可,或者在代码里创建一个list返回也行,只要取代了数组就可以了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值