Java基础︱泛型

本文从泛型的基本概念,使用编写泛型及泛型的局限性,再到泛型实现的擦拭法分析,还从extends与super通配符对泛型的应用做了系统性介绍。

泛型概念

  1. ArrayList类内部方法
public class ArrayList {
    private Object[] array;
    private int size;
    public void add(Object e) {...}
    public void remove(int index) {...}
    public Object get(int index) {...}
}

如果将上述ArrayList存储为String,Integer等类型,需要强制转换,容易出错。

为了解决这个问题,我们必须把ArrayList变成一种模板:ArrayList,代码如下:

public class ArrayList<T> {
    private T[] array;
    private int size;
    public void add(T e) {...}
    public void remove(int index) {...}
    public T get(int index) {...}
}
  1. 泛型就是定义一种模板,编写模板代码来适应任意类型。例如ArrayList,然后在代码中为用到的类创建对应的ArrayList<类型>
ArrayList<String> strList = new ArrayList<String>();
  1. 在Java标准库中的ArrayList实现了List接口,它可以向上转型为List 。但不能把ArrayList<Integer>向上转型为ArrayList<Number>List<Number>

使用泛型

  1. 使用ArrayList时,如果不定义泛型类型时,泛型类型实际上就是Object,没有发挥泛型的优势。

    List list = new ArrayList();
    list.add("Hello");
    list.add("World");
    String first = (String) list.get(0);
    String second = (String) list.get(1);
    

    当我们定义泛型类型<String>后,List<T>的泛型接口变为强类型List<String>

    // 无编译器警告:
    List<String> list = new ArrayList<String>();
    list.add("Hello");
    list.add("World");
    // 无强制转型:
    String first = list.get(0);
    String second = list.get(1);
    
  2. 编译器如果能自动推断出泛型类型,就可以省略后面的泛型类型。 如:

    List<Number> list = new ArrayList<Number>();
    
  3. 泛型接口

    除了ArrayList使用了泛型,还可以在接口中使用泛型。例如,Arrays.sort(Object[])可以对任意数组进行排序,但待排序的元素必须实现Comparable这个泛型接口:

    public interface Comparable<T> {
        /**
         * 返回-1: 当前实例比参数o小
         * 返回0: 当前实例与参数o相等
         * 返回1: 当前实例比参数o大
         */
        int compareTo(T o);
    }
    
    public class Main {
        public static void main(String[] args) {
            Person[] ps = new Person[] {
                new Person("Bob", 61),
                new Person("Alice", 88),
                new Person("Lily", 75),
            };
            Arrays.sort(ps);
            System.out.println(Arrays.toString(ps));
        }
    }
    
    class Person implements Comparable<Person> {
        String name;
        int score;
        Person(String name, int score) {
            this.name = name;
            this.score = score;
        }
        public int compareTo(Person other) {
            return this.name.compareTo(other.name);
        }
        public String toString() {
            return this.name + "," + this.score;
        }
    }
    
    输出:
    [Alice,88, Bob,61, Lily,75]
    

    编写泛型

    1. 编写泛型步骤

      (1)按照某种类型,如String,来编写类

      public class Pair {
          private String first;
          private String last;
          public Pair(String first, String last) {
              this.first = first;
              this.last = last;
          }
          public String getFirst() {
              return first;
          }
          public String getLast() {
              return last;
          }
      }
      

      (2)标记所有的特征类型

      (3)把特定类型String替换成T,并申明

      public class Pair<T> {
          private T first;
          private T last;
          public Pair(T first, T last) {
              this.first = first;
              this.last = last;
          }
          public T getFirst() {
              return first;
          }
          public T getLast() {
              return last;
          }
      }
      
  4. 静态方法

    编写泛型类时,要特别注意,泛型类型<T>不能用于静态方法。

    public class Pair<T> {
        private T first;
        private T last;
        public Pair(T first, T last) {
            this.first = first;
            this.last = last;
        }
        public T getFirst() { ... }
        public T getLast() { ... }
    
        // 静态泛型方法应该使用其他类型区分:
        public static <K> Pair<K> create(K first, K last) {
            return new Pair<K>(first, last);
        }
    }
    
  5. 多个泛型类型

    public class Pair<T, K>
    
    Pair<String, Integer> p = new Pair<>("test", 123);
    

擦拭法

1.Java语言的泛型实现方式是擦拭法(Type Erasure)。所谓擦拭法是指,虚拟机对泛型其实一无所知,所有的工作都是编译器做的。

例如,我们编写了一个泛型类Pair,这是编译器看到的代码:

public class Pair<T> {
    private T first;
    private T last;
    public Pair(T first, T last) {
        this.first = first;
        this.last = last;
    }
    public T getFirst() {
        return first;
    }
    public T getLast() {
        return last;
    }
}

而虚拟机根本不知道泛型。这是虚拟机执行的代码:

public class Pair {
    private Object first;
    private Object last;
    public Pair(Object first, Object last) {
        this.first = first;
        this.last = last;
    }
    public Object getFirst() {
        return first;
    }
    public Object getLast() {
        return last;
    }
}

因此,Java使用擦拭法实现泛型,导致了:

  • 编译器把类型<T>视为Object
  • 编译器根据<T>实现安全的强制转型。

使用泛型的时候,我们编写的代码也是编译器看到的代码, 而虚拟机执行的代码并没有泛型。

2.Java泛型的局限性

  • 不能是基本类型,如int
  • 无法取得带泛型的class
Pair<String> p1 = new Pair<>("Hello", "world");
Pair<Integer> p2 = new Pair<>(123, 456);
Class c1 = p1.getClass();
Class c2 = p2.getClass();
System.out.println(c1==c2); // true
System.out.println(c1==Pair.class); // true
  • 无法判断带泛型的class
Pair<Integer> p = new Pair<>(123, 456);
// Compile error:
if (p instanceof Pair<String>.class) {
}

原因和前面一样,并不存在Pair.class,而是只有唯一的Pair.class

  • 不能实例化类型

    要实例化T类型,我们必须借助额外的Class参数:

    public class Pair<T> {
        private T first;
        private T last;
        public Pair(Class<T> clazz) {
            first = clazz.newInstance();
            last = clazz.newInstance();
        }
    }
    

    上述代码借助Class参数并通过反射来实例化T类型,使用的时候,也必须传入Class。例如:

    Pair<String> pair = new Pair<>(String.class);
    

    因为传入了Class的实例,所以我们借助String.class就可以实例化String类型。

extends通配符

1.泛型的继承关系:Pair不是Pair的子类, 使用Pair<? extends Number>使得方法接收所有泛型类型为NumberNumber子类的Pair类型。

 public class Main { 

 public static void main(String[] args) {
        Pair<Integer> p = new Pair<>(123, 456);
        int n = add(p);
        System.out.println(n);
    }

     //Pair<? extends Number> p
    static int add(Pair<? extends Number> p) {
        Number first = p.getFirst();
        Number last = p.getLast();
        return first.intValue() + last.intValue();
    }

}

class Pair<T> {
    private T first;
    private T last;
    public Pair(T first, T last) {
        this.first = first;
        this.last = last;
    }
    public T getFirst() {
        return first;
    }
    public T getLast() {
        return last;
    }
}

2.使用<? extends Number> 的泛型定义称之为上界通配符 ,即把泛型类型T的上界限定在Number了。

3.extends通配符作用:只可读,不可写

  • 允许调用get()方法获取Integer的引用;
  • 不允许调用set(? extends Integer)方法并传入任何Integer的引用(null除外)。

4.也可使用extends限定T类型,比如public class Pair<T extends Number> { ... },泛型类型就限定为Number以及Number的子类。

super通配符

  1. 注意到Pair<? super Integer>表示,方法参数接受所有泛型类型为Integer或Integer父类的Pair类型。

    public static void main(String[] args) {
            Pair<Number> p1 = new Pair<>(12.3, 4.56);
            Pair<Integer> p2 = new Pair<>(123, 456);
            setSame(p1, 100);
            setSame(p2, 200);
            System.out.println(p1.getFirst() + ", " + p1.getLast());
            System.out.println(p2.getFirst() + ", " + p2.getLast());
        }
        
        static void setSame(Pair<? super Integer> p, Integer n) {
        p.setFirst(n);
        p.setLast(n);
    }
    

2.<? super Integer> 不允许调用get()方法获得Integer的引用。 唯一例外是可以获取Object的引用:Object o = p.getFirst()

3.对比extends跟super通配符

作为方法参数,<? extends T>类型和<? super Integer>类型的区别在于:

  • <? extends T>允许调用读方法T get()获取T的引用,但不允许调用写方法set(T)传入T的引用(传入null除外);
  • <? super Integer>允许调用写方法set(T)传入T的引用,但不允许调用读方法T get()获取T的引用(获取Object除外)。

一个是允许读不允许写,另一个是允许写不允许读。

4.PECS原则

我们可以用PECS原则:Producer Extends Consumer Super。

即:如果需要返回T,它是生产者(Producer),要使用extends通配符;如果需要写入T,它是消费者(Consumer),要使用super通配符。

Collectionscopy()方法为例:

public class Collections {
    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        for (int i=0; i<src.size(); i++) {
            T t = src.get(i); // src是producer
            dest.add(t); // dest是consumer
        }
    }
}

需要返回Tsrc是生产者,因此声明为List,需要写入Tdest是消费者,因此声明为List

5.无限定通配符

因为<?>通配符既没有extends,也没有super,因此:

  • 不允许调用set(T)方法并传入引用(null除外);
  • 不允许调用T get()方法并获取T引用(只能获取Object引用)。

换句话说,既不能读,也不能写,那只能做一些null判断:

static boolean isNull(Pair<?> p) {
    return p.getFirst() == null || p.getLast() == null;
}

<?>通配符很少使用,有一个独特的特点,就是:Pair<?>是所有Pair<T>的超类。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦晨涌京

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值