Java基础——泛型

  • 为什么使用泛型?
        List list1 = new ArrayList();
        list1.add("Droy");
        list1.add(36);

        for(int i = 0; i < list1.size();i++){
            //抛出异常:
            //java.lang.ClassCastException: 
            //java.lang.Integer cannot be cast to java.lang.String
            String data1 = (String)list1.get(i);
            //由于数据放入时没有明确类型,而取出时不清楚数据类型导致的。
        }

        //a.2.使用泛型示例
        List<String> list2 = new ArrayList<String>();
        list2.add("Droy");
        //list2.add(36);//直接报错,因为List<String>限定了类型只能是String。

在上述示例中,我们可以直观感受下泛型最普遍的用法,通过定义List接口的类型实参为String,限制了放入的数据类型,就可以避免取出数据时的异常。泛型可以简化我们的代码开发,保证代码质量。更多的用法需要理解泛型后灵活运用。

  • 泛型定义

泛型,就是参数化类型,常见到泛型接口/类、泛型方法,常用T、K、E、V定义类型形参。
例如,List接口源代码:

public interface List<E> extends Collection<E> {
    int size();

    boolean isEmpty();

    boolean contains(Object var1);

    Iterator<E> iterator();

    Object[] toArray();

    <T> T[] toArray(T[] var1);

    boolean add(E var1);

    boolean remove(Object var1);

    boolean containsAll(Collection<?> var1);

    boolean addAll(Collection<? extends E> var1);

    boolean addAll(int var1, Collection<? extends E> var2);

    boolean removeAll(Collection<?> var1);

    boolean retainAll(Collection<?> var1);

    default void replaceAll(UnaryOperator<E> var1) {
        Objects.requireNonNull(var1);
        ListIterator var2 = this.listIterator();

        while(var2.hasNext()) {
            var2.set(var1.apply(var2.next()));
        }

    }

    default void sort(Comparator<? super E> var1) {
        Object[] var2 = this.toArray();
        Arrays.sort(var2, var1);
        ListIterator var3 = this.listIterator();
        Object[] var4 = var2;
        int var5 = var2.length;

        for(int var6 = 0; var6 < var5; ++var6) {
            Object var7 = var4[var6];
            var3.next();
            var3.set(var7);
        }

    }

    void clear();

    boolean equals(Object var1);

    int hashCode();

    E get(int var1);

    E set(int var1, E var2);

    void add(int var1, E var2);

    E remove(int var1);

    int indexOf(Object var1);

    int lastIndexOf(Object var1);

    ListIterator<E> listIterator();

    ListIterator<E> listIterator(int var1);

    List<E> subList(int var1, int var2);

    default Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, 16);
    }
}

List<E>中E是类型形参,而接口实现中List<String>中String就是类型实参。同时,我们可以注意到List接口中有很多方法也定义了类型形参E,它们将与接口实现传入的类型实参保持一致。

  • 泛型定义的类的类型比较

我们先来思考一个问题,如果我们定义了一个泛型类Person,此时我们实例化两个不同类型实参的对象,他们是否属于同一类型,具体代码如下:

public class GenericsDemo {

    static class Person<T> {

        private T attribute;

        public Person(T attribute){
            setAttribute(attribute);
        }

        public T getAttribute() {
            return attribute;
        }

        public void setAttribute(T attribute) {
            this.attribute = attribute;
        }
    }

    public static void main(String[] args) {                
        //B.泛型定义的类的类型比较
        Person<String> name = new Person<String>("Droy");
        Person<Integer> age = new Person<Integer>(36);

        System.out.println("name值:"+name.getAttribute());
        System.out.println("age值:"+age.getAttribute());
        System.out.println("类的比较:"+(name.getClass()==age.getClass()));
    }

}
输出结果:
name值:Droy
age值:36
类的比较:true

通过上述试验,我们发现虽然泛型传递的类型实参不同,但是基本类型是相同的。为什么?因为java中泛型只是在编译过程中使用,编译通过后泛型信息就会被去除,运行时只有原始类型。
因此,只要能绕过编译过程,直接通过反射方式就可以向泛型集合中加入其他类型的数据。

        Person<String> name = new Person<String>("Droy");
        Person<Integer> age = new Person<Integer>(36);

        System.out.println("name值:"+name.getAttribute());
        System.out.println("age值:"+age.getAttribute());
        System.out.println("类的比较:"+(name.getClass()==age.getClass()));
        
        try{
            age.getClass().getMethod("setAttribute",Object.class).invoke(age,"Zhou");
        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println("age.invoke值:"+age.getAttribute());
输出结果:
name值:Droy
age值:36
类的比较:true
age.invoke值:Zhou

age对象本来定义的类型实参是Integer类型,可通过反射绕开编译过程后,我们使它的值变成了String类型的。

  • 泛型类型通配符?

我们在形式上用?代替具体的类型实参,逻辑上可以认识为是所有类型的父类。

public class GenericsDemo {

    static class Person<T> {

        private T attribute;

        public Person(){

        }

        public Person(T attribute){
            setAttribute(attribute);
        }

        public T getAttribute() {
            return attribute;
        }

        public void setAttribute(T attribute) {
            this.attribute = attribute;
        }
    }


    //定义获取数据方法
    public static void getData1(Person person){
        System.out.println("人的属性: "+person.getAttribute());
    }

    public static void getData2(Person<?> person){
        System.out.println("人的属性: "+person.getAttribute());
    }

    public static void getData3(Person<Integer> person){
        System.out.println("人的属性: "+person.getAttribute());
    }

    public static void main(String[] args) {

        Person<String> name = new Person<String>("Droy");
        Person<Integer> age = new Person<Integer>(36);

        //C.泛型类型通配符?
        getData1(name);
        getData1(age);

        getData2(name);
        getData2(age);

//        getData3(name);//直接报错
        getData3(age);
    }

}

如上述代码中所见,方法getData2(Person<?> person)使用通配符来代替具体类型实参,这样我们调用时传递的就不在受到实参的局限性,这也满足了我们对代码多态性的需求,不必为每一个实参定义一个新的函数。

  • 泛型通配符上下限

这是泛型通配符的扩展,主要用于将实参范围固定在某个特定区间。

泛型通配符上限,形式< ? extends 父类>。

比如,Person<? extends Number>,我们要将类型实参的上限定义在Number下,即通配符?代表的类型实参必须是继承Number父类的。


public class GenericsDemo {

    static class Person<T> {

        private T attribute;

        public Person(){

        }

        public Person(T attribute){
            setAttribute(attribute);
        }

        public T getAttribute() {
            return attribute;
        }

        public void setAttribute(T attribute) {
            this.attribute = attribute;
        }
    }

    //类型通配符上限
    public static void getData4(Person<? extends Number> person){
        System.out.println("人的属性: "+person.getAttribute());
    }

    public static void main(String[] args) {

        Person<String> name = new Person<String>("Droy");
        Person<Integer> age = new Person<Integer>(36);

        //D.类型通配符上限
        Person<Number> statrue = new Person<Number>(176);

//        getData4(name);//直接报错
        getData4(age);
        getData4(statrue);
    }

}

上述示例中如果类型实参不是继承Number的则会直接报错,如getData4(name)。

泛型通配符下限,< ? super 子类>。

比如,Person<? super Byte>,我们要将类型实参的下限定义在byte上,即通配符?代表的类型实参必须是Byte及其父类。

public class GenericsDemo {

    static class Person<T> {

        private T attribute;

        public Person(){

        }

        public Person(T attribute){
            setAttribute(attribute);
        }

        public T getAttribute() {
            return attribute;
        }

        public void setAttribute(T attribute) {
            this.attribute = attribute;
        }
    }

    //类型通配符下限
    public static void getData5(Person<? super Byte> person){
        System.out.println("人的属性: "+person.getAttribute());
    }

    public static void main(String[] args) {
        Person<String> name = new Person<String>("Droy");
        Person<Integer> age = new Person<Integer>(36);
        Person<Number> statrue = new Person<Number>(176);

        //E.类型通配符下限
//        getData5(name);//直接报错
//        getData5(age);//直接报错
        getData5(statrue);

    }
}

上述示例中如果不是Byte的父类的类型实参就会报错。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值