泛型让代码安全简单

一、泛型的优点

泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中。泛型是对Java的类型的一种扩展,以支持创建可以按类型进行参数化的类。

看个例子,如果没有泛型

// 这是可取的,因为我们的参数类型真的是String类型
List list = new ArrayList();
m.add("123");
String s = (String) m.get(0);

//但是有时候,我们并不知道里面是什么类型的数据,比如放了一个Integer
m.add(new Integer(1));
String s = (String) m.get(1);
// 这时候就会报错

通过引入泛型,我们就可以避免这种不必要的尴尬

List<Integer> list = new ArrayList<Integer>();
m.add("123");           // 编译通过
m.add(new Integer(1));  // 编译不通过

可见,这在我们的代码编译中增加了一层类型检查,这样可以防止将错误类型保存在集合中。

而且Java源码中的集合类,都已经使用了泛型。

二、定义泛型

我们可以在方法、接口、或者类上定义一个泛型,不过它们的范围是有区别的。如我们可以通过来定义一个泛型K。

类或接口上定义的泛型,影响范围是整个类或接口,而方法泛型只能影响这个方法,但是方法泛型优先级高于类泛型。下面代码会详细列出各种情况:

// 这里我们在类上定义两个泛型变量
public class GenericsClass<K, V> {

    // 方法泛型与类泛型冲突,不加注解会有警告
    // 这边的K是以传入的list的类型,而不是类中的K类型
    @SuppressWarnings("hiding")
    public <K> K testK(List<K> list) {
        return list.get(0);
    }
    // 测试上面的问题,可以得出结论方法中的泛型优先级高于类,会将其覆盖
    public static void main(String[] args) {
        GenericsClass<String, Object> aClass = new GenericsClass<>();
        List<Integer> list = new ArrayList<Integer>();
        Integer aInteger =aClass.testK(list);
    }

    public <T> void testT(List<T> list) {

    }

    public void testV(List<V> list){

    }

    // T cannot be resolved to a type
    // 编译不通过,方法泛型不能在其他方法上使用
    public void testTT(List<T> list){

    }

    class InClass{
        // 编译通过,内部类可以使用类上的泛型
        public void testK(List<K> list){

        }
    }

}
class OutClass{
    // K cannot be resolved to a type
    // 编译不通过,类泛型只能在本类中使用
    public void testK(List<K> list){

    }
}

三、具体实例

1.ArrayList实例

如果我们想将ArrayList转换成一个数组,可能很多人会直接使用toArray();方法,但是这里会有一个问题,

    List<String> list = new ArrayList<String>();

    String[] stus = (String[]) list.toArray();// 运行时报错

    // public Object[] toArray()
    // 从源码可以发现,返回类型为Object[],这样会有类型转换问题

如果对结果又疑问,可以参考之前的博客:http://blog.csdn.net/odeviloo/article/details/52320030

这里可以用for循环来对Object[]数组中的对象逐个强制转型再放入stus中,代码如下

Object[] objs = list.toArray();
for(int i=0;i<objs.length;i++){
    stus[i] = (Stirng) objs[i];
}

但是这很麻烦,好在我们有了泛型。可以看到ArrayList又提供了一个返回泛型的方法

// public <T> T[] toArray(T[] a)

String[] objsa = list.toArray(new String[6]);

一句话搞定,轻松方便

2.泛型静态工厂

老办法创建map,这样每次new的时候都需要制定类型

HashMap<String, Stu> oldmap = new HashMap<String, Stu>();

使用泛型静态工厂模式,会让代码看起来十分简单

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

    HashMap<String, Stu> map = FunctionI.newHashMap();

3.泛型中的继承

我们先定义类Stu继承于Stuc,并且有如下代码

public class FunctionI<E> {
    public void onlyE(List<E> e) {
    }
}

那么如果代码如下会有编译报错

FunctionI<Stu> fun = new FunctionI<Stu>();
List<Stuc> list = new ArrayList<Stuc>();
list.add(new Stuc());
fun.onlyE(list);

这是由于fun对象指定泛型E为Stu类型,而传入的list中的类型为其子类。

使用extends

我们增加如下方法

    public void extendsE(List<? extends E> e) {
        for (E obj : e) {
            posh(obj);
        }
    }

那么再调用此方法,则可以编译通过。此方法可以传入任何继承于E类型的(这里也就是Stu)对象。如果我们还有Stub继承Stu,一样没有问题。

fun.extendsE(list);

注:?是一种实例化,是不确定类型的实例化,直接写?,等同于? extends Object。

使用super

同样,我们也可以使用super来放入任何是E父类的对象类型。

    public void superE(List<? super E> e) {
        while (!e.isEmpty())
            e.remove(pop());
    }

继承原则

这边有个原则PECS,表示producer-extends,consumer-super,如果没有遵守,我们可能还会碰到运行时的类型转换问题。

extendsE这里是e提供obj给FuncitonI对象的push(E e)使用,所以为生产者,传入push的必须为E的子类

superE是e通过FuncitonI使用其pop方法,也就是说传入的参数是消费者,pop返回的是FuncitonI的泛型E,那么List中的类型就要为E的父类,这样才能转换正确

4.多类型参数的泛型

    public static <E> List<E> unionUndo(List<E> e1, List<E> e2) {
        return new ArrayList<E>();
    }

    public static <E> List<E> unionExt(List<? extends E> e1, List<? extends E> e2) {
        return new ArrayList<E>();
    }
    List<Integer> list1 = new ArrayList<Integer>();
    List<Double> list2 = new ArrayList<Double>();
    List<Number> list3 = FunctionI.unionUndo(list1, list2); 

    List<Number> list4 = FunctionI.<Number> unionExt(list1, list2);

unionUndo无法编译通过,因为泛型E无法确定,但是unionExt可以编译通过,因为我们在方法前指定了E的类型为Nubmer,而且入参都要为其子类,由于list1和2中存放的类型都是Number的子类所以可以编译通过。

5.解决针对自身的检查

查看Collections的源码可以看到一个极其负责的方法。

    public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) {
        Iterator<? extends T> i = coll.iterator();
        T candidate = i.next();

        while (i.hasNext()) {
            T next = i.next();
            if (next.compareTo(candidate) > 0)
                candidate = next;
        }
        return candidate;
    }

我们就来看下这个方法的入参规则,其实,这里复杂的也就是前面部分

// GregorianCalendar extends Calendar
// Calendar implements Comparable<Calendar>
class Demo1<T extends Comparable<T>> {
}

Demo1<GregorianCalendar> p = null;

可见,Demo1编译不通过,这里相当于

class Demo2<T extends Comparable<? super T>> {
}

Demo2<GregorianCalendar> p2 = null;

Demo2则可以正常编译,这样就解决了Demo1中不满足的问题

更多源码:https://github.com/oDevilo/Java-Base

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值