当前位置 : 主页 > 编程语言 > java >

带你入门Java的泛型

来源:互联网 收集:自由互联 发布时间:2021-08-21
目录 泛型 1、简单泛型 (1)元组 (2)堆栈 2、泛型接口 3、泛型方法 (1)类型推断 (2)通用的Generator (3)Set实用工具实现数学方法 4、擦除 (1)迁移兼容性 (2)擦除的问题 5、擦
目录
  • 泛型
    • 1、简单泛型
      • (1)元组
      • (2)堆栈
    • 2、泛型接口
      • 3、泛型方法
        • (1)类型推断
        • (2)通用的Generator
        • (3)Set实用工具实现数学方法
      • 4、擦除
        • (1)迁移兼容性
        • (2)擦除的问题
      • 5、擦除的补偿
        • (1)由于擦除原因,无法通过instanceof比较类型。如果引入类型标签,就可以转而使用动态的isInstance()。
        • (2)创建类型实例
        • (3)泛型数组
      • 6、边界
        • 7、通配符
          • (1)List<? extends Fruit>协变
          • (2)List<? super Fruit>逆变
          • (3)无界通配符List<?>
          • (4)捕获转换
        • 8、问题
          • (1)任何基本类型都不能作为类型参数
          • (2)实现参数化接口
          • (3)转型和警告
          • (4)重载
          • (5)基类劫持了接口
        • 9、自限定
          • 10、异常
          • 总结

            泛型

            1、简单泛型

            泛型的主要目的之一就是用来指定容器要持有什么类型的对象,而且由编译器来保证类型的正确性。

            泛型暂时不指定类型,在使用时决定具体使用什么类型。通过<T>来实现,T就是类型参数。

            (1)元组

            class TwoTuple<A,B>{
                public final A first;
                public final B second;
                public TwoTuple(A a,B b){
                    first = a;
                    second = b;
                }
            ​
                @Override
                public String toString() {
                    return "{ " + first +
                            ", " + second +
                            '}';
                }
            }

            (2)堆栈

            class LinkedStack<T>{
                private class Node {
                    T item;
                    Node next;
                    Node() { item = null; next = null; }
                    Node(T item, Node next) {
                        this.item = item;
                        this.next = next;
                    }
                    boolean end() { return item == null && next == null; }
                }
            ​
                private Node top = new Node();
                public void push(T item) { top = new Node(item, top); }
                public T pop() {
                    T result = top.item;
                    if(!top.end())
                        top = top.next;
                    return result;
                }
            }
            (3)RandomList
            class RandomList<T>{
                private ArrayList<T> storage = new ArrayList<>();
                private Random rand = new Random(47);
                public void add(T item){
                    storage.add(item);
                }
                public T select(){
                    return storage.get(rand.nextInt(storage.size()));
                }
            }

            2、泛型接口

            泛型也可以应用于接口,例如生成器,这是一种专门负责创建对象的类。

            import net.mindview.util.Generator;
            import java.util.Iterator;
            ​
            class Fibonacci implements Generator<Integer> {
                private int count = 0;
                public Integer next(){
                    return fib(count++);
                }
                private int fib(int n){
                    if(n<2) return 1;
                    return fib(n-2) + fib(n-1);
                }
            }
            ​
            class IterableFibonacci implements Iterable<Integer> {
                private Fibonacci fib = new Fibonacci();
                private int n;
                public IterableFibonacci(int count){
                    n = count;
                }
            ​
                @Override
                public Iterator<Integer> iterator() {
                    return new Iterator<Integer>() {
                        @Override
                        public boolean hasNext() {
                            return n>0;
                        }
            ​
                        @Override
                        public Integer next() {
                            n--;
                            return fib.next();
                        }
                        public void remove() { // Not implemented
                            throw new UnsupportedOperationException();
                        }
                    };
                }
            }

            3、泛型方法

              泛型方法使得该方法能够独立于类而产生变化。使用泛型方法的时候,通常不必指明参数类型,因为编译器会为我们找出具体的类型,这称为类型参数推断。

             class GenericMethods{
                 public <T> void f(T x){
                  System.out.println(x.getClass().getSimpleName());
                 }
            }

            (1)类型推断

              使用泛型有时候需要向程序中加入更多的代码。如下所示:

             Map<Person,List<? extends Pet>> petPerson = 
                 new HashMap<Person,List<? extends Pet>>();

            在泛型方法中可以通过类型推断来简化一部分工作。如下所示:

            class New{
                public static <K,V> Map<K,V> map(){
                    return new HashMap<K,V>();
                }
            ​
                public static void main(String[] args) {
                    Map<Person,List<? extends Pet>> petPerson = New.map();
                }
            }

            类型推断只对赋值操作有效,其他时候并不起作用。如果将一个泛型方法的结果作为参数,传递给另一个方法时,另一个方法需要显式的类型说明。如下所示:

            public class ExplicitTypeSpecification{
                static void f(Map<Person,List<? extends Pet>> petPerson){}
                public static void main(String[] args) {
                    f(New.<Person,List<? extends Pet>>map());
                }
            }

            (2)通用的Generator

            import net.mindview.util.Generator;
            ​
            public class BasicGenerator<T> implements Generator<T>{
                private Class<T> type;
                public BasicGenerator(Class<T> type){
                    this.type = type;
                }
                public T next(){
                    try {
                        return type.newInstance();
                    }catch (Exception e){
                        throw new RuntimeException(e);
                    }
                }
                public static <T> Generator<T> create(Class<T> type){
                    return new BasicGenerator<T>(type);
                }
            }

            (3)Set实用工具实现数学方法

            public class Sets{
                @SuppressWarnings("unchecked")
                protected static <T> Set<T> copy(Set<T> s) {
                    if(s instanceof EnumSet)
                        return ((EnumSet)s).clone();
                    return new HashSet<T>(s);
                }
            ​
                //并集
                public static <T> Set<T> union(Set<T> a, Set<T> b) {
                    Set<T> result = copy(a);
                    result.addAll(b);
                    return result;
                }
                //交集
                public static <T> Set<T> intersection(Set<T> a, Set<T> b) {
                    Set<T> result = copy(a);
                    result.retainAll(b);
                    return result;
                }
                //差集
                public static <T> Set<T> difference(Set<T> superset, Set<T> subset) {
                    Set<T> result = copy(superset);
                    result.removeAll(subset);
                    return result;
                }
                //包含除了交集以外的所有元素
                public static <T> Set<T> complement(Set<T> a, Set<T> b) {
                    return difference(union(a, b), intersection(a, b));
                }
            }

            4、擦除

            Java泛型是使用擦除来实现的,这意味着当你在使用泛型时,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。因此List<String>和List<Integer>在运行时事实上是相同的类型,都被擦除成它们的“原生”类型List。

            (1)迁移兼容性

            泛型类型只有在静态类型检查期间才出现,在此之后,程序中的所有泛型类型都将被擦除,替换为他们的非泛型上界。擦除的核心动机是它使得泛化的客户端可以用非泛化的类库来使用,反之亦然,这经常被称为“迁移兼容性”。

            (2)擦除的问题

            泛型的所有关于参数的类型信息都丢失了,所以不能用于显式地引用运行时类型的操作之中,例如转型、instanceof操作和new表达式。

            5、擦除的补偿

            (1)由于擦除原因,无法通过instanceof比较类型。如果引入类型标签,就可以转而使用动态的isInstance()。

            public class ClassTypeCapture<T>{
                Class<T> kind;
                public ClassTypeCapture(Class<T> kind){
                    this.kind = kind;
                }
                public boolean f(Object arg){
                    return kind.isInstance(arg);
                }
            }

            (2)创建类型实例

            通过工厂对象来创建实例。如果使用类型标签,就可以使用newInstance()来创建这个类型的新对象。

            class ClassAsFactory<T>{
                T x;
                public ClassAsFactory(Class<T> kind){
                    try{
                        x = kind.newInstance();
                    }catch(Exception e){
                        throw new RuntimeException(e);
                    }
                }
            }

            如果类没有默认的构造器,上面的案例会创建失败。为了解决这个问题,可以通过显示的工厂来实现。

            interface FactoryI<T>{
                T create();
            }
            class Foo2<T>{
                private T x;
                public <F extends FactoryI<T>> Foo2(F factory){
                    x = factory.create();
                }
            }
            class IntegerFactory implements FactoryI<Integer>{
                public Integer create(){
                    return new Integer(6);
                }
            }

            另一种方式是模板方法设计模式。

            abstract class GenericWithCreate<T>{
                final T element;
                GenericWithCreate(){ element = create(); }
                abstract T create();
            }
            ​
            class X{}
            ​
            class Creator extends GenericWithCreate<X>{
                X create(){ return new X(); }
            }

            (3)泛型数组

            无法通过 T[] array = new T[sz] 来创建泛型数组,一般的解决方法是在需要泛型数组的地方都使用ArrayList。

            在创建泛型数组时,有以下三种情况:

            ①创建时强制转型

            public class GenericArray<T>{
                private T[] array;
                @SuppressWarnings("unchecked")
                public GenericArray(int sz){
                    array = (T[])new Object[sz];
                }
                public T[] rep(){ return array; }
            ​
                public static void main(String[] args) {
                    GenericArray<Integer> gai = new GenericArray<Integer>(10);
                    Integer[] ia = gai.rep();//引起ClassCastException
                    Object[] oa = gai.rep();
                }
            }

            ②方法返回时强制转型

            class GenericArray2<T>{
                private Object[] array;
                @SuppressWarnings("unchecked")
                public GenericArray(int sz){
                    array = new Object[sz];
                }
                public T[] rep(){ return (T[])array; }
                public static void main(String[] args) {
                    GenericArray<Integer> gai = new GenericArray<Integer>(10);
                    Integer[] ia = gai.rep();//引起ClassCastException
                    Object[] oa = gai.rep();
                }
            }

            ③使用Array.newInstance()

            以上两种方法都无法创建具体类型的数组,无法推翻底层的数组类型,只能是Object[]。通过传入类型标记Class<T>,可以从擦除中恢复。

            class GenericArray3<T>{
                private T[] array;
                @SuppressWarnings("unchecked")
                public GenericArray(Class<T> type,int sz){
                    array = (T[]) Array.newInstance(type,sz);
                }
                public T[] rep(){ return array; }
                public static void main(String[] args) {
                    GenericArray<Integer> gai = new GenericArray<Integer>(Integer.class,10);
                    Integer[] ia = gai.rep();//可以正常运行
                    Object[] oa = gai.rep();
                }
            }

            6、边界

            边界使得你可以在用于泛型的参数类型上设置限制条件,可以按照自己的边界类型来调用方法。

            public class Test {
                public static void main(String[] args) {
                    Man m = new Man();
                    m.hear();
                    m.smell();
                }
            }
            ​
            interface SuperPower{}
            interface SuperHearing extends SuperPower{
                void hearSubtleNoises();
            }
            interface SuperSmell extends SuperPower{
                void trackBySmell();
            }
            ​
            class SuperHero<POWER extends SuperPower>{
                POWER power;
                SuperHero(POWER power){ this.power = power; }
                POWER getPower(){ return power; }
            }
            ​
            class CaineHero<POWER extends SuperHearing & SuperSmell> extends SuperHero<POWER>{
                CaineHero(POWER power){ super(power); }
                void hear(){ power.hearSubtleNoises(); }
                void smell(){ power.trackBySmell(); }
            }
            ​
            class SuperHearSmell implements SuperHearing,SuperSmell{
            ​
                @Override
                public void hearSubtleNoises() {
                    System.out.println("hearSubtleNoises");
                }
            ​
                @Override
                public void trackBySmell() {
                    System.out.println("trackBySmell");
                }
            }
            ​
            class Man extends CaineHero<SuperHearSmell>{
                Man(){ super(new SuperHearSmell()); }
            }

            7、通配符

            (1)List<? extends Fruit>协变

            表示具有任何从Fruit继承的类型的列表。List<? extends Fruit>可以合法地指向一个List<Apple>。一旦执行这种类型的向上转型,就将丢失掉向其中传递任何对象的能力,甚至是传递Object也不行。

            List<? extends Fruit> flist =
                Arrays.asList(new Apple());
            //Compile Error:can't add any type of object
            //add()的参数是<? extends Fruit>,编译器不知道需要Fruit的哪个
            //具体的子类型,因此不接受任何类型的Fruit
            //flist.add(new Apple());
            //flist.add(new Fruit());
            //flist.add(new Object());
            flist.add(null);//Legal but uninteresting
            Apple a = (Apple)flist.get(0);//No warning
            Fruit f = flist.get(0);//No warning
            flist.contains(new Apple());//参数是Object
            flist.indexOf(new Apple());//参数是Object

            (2)List<? super Fruit>逆变

            超类型通配符可以安全地传递一个类型对象到泛型类型中。List<? super Fruit>意味着向其中添加Fruit或Fruit的子类型是安全的。

            List<? super Fruit> flist = new ArrayList<Fruit>();
                    flist.add(new Apple());
                    flist.add(new Fruit());
            //Error:Incompatible Type
            //Fruit f = flist.get(0);
            Object f = flist.get(0);//OK,but type information has been lost

            (3)无界通配符List<?>

            List实际上表示“持有任何Object类型的原生List”,List<?>表示“具有某种特定类型的非原生List,只是我们不知道那种类型是什么”,List<? extends Object>表示“类型是Object的导出类”。

            无界通配符的一个重要应用:处理多个泛型参数时,允许一个参数可以是任何类型,同时为其他参数确定某种特定类型。

            Map<String,?> map = new HashMap<String,Integer>;
            map = new HashMap<String,String>;

            原生Holder与Holder<?>是大致相同的事物,但存在不同。它们会揭示相同的问题,但是后者将这些问题作为错误而不是警告报告。

            static void rawArgs(Holder holder,Object arg){
                //holder.set(arg);
                //Warning:Unchecked call to set(T) as member
                //of the raw type Holder
                //holder.set(new Wildcards());//Same Warning
                //Can't do this:don't have any 'T'
                //T t = holder.get();
                //OK,but type infomation has been lost
                Object obj = holder.get();
            }
            //Similar to rawArgs(),but errors instead of warnings
            static void unboundedArg(Holder<?> holder,Object arg){
                //holder.set(arg);
                //Error:set(capture of ?) in Holder<capture of ?>
                //cannot be applied to (Object)
                //holder.set(new Wildcards());//Same Error
                //Can't do this:don't have any 'T'
                //T t = holder.get();
                //OK,but type infomation has been lost
                Object obj = holder.get();
            }

            (4)捕获转换

            未指定的通配符类型被捕获,并被转换为确切类型。在f2()中调用f1(),参数类型在调用f2()的过程中被捕获,因此它可以在对f1()的调用中被使用。不能从f2()中返回T,因为T对于f2()来说是未知的。

            static <T> void f1(Holder<T> holder){
                T t = holder.get();
                 System.out.println(t.getClass().getSimpleName());
            }
            ​
            static <T> void f2(Holder<T> holder){
                f1(holder);
            }

            8、问题

            (1)任何基本类型都不能作为类型参数

            (2)实现参数化接口

            一个类不能实现同一个泛型接口的两种变体。将泛型参数移除掉后,这段代码就可以正常编译了。

            interface Payable<T>{}
            ​
            class Employee implements Payable<Employee>{}
            ​
            //Compile Error:cannot be inherited with different type arguments
            class Hourly extends Employee implements Payable<Hourly>{}

            (3)转型和警告

            使用带有泛型类型参数的转型或instanceof不会有任何效果。

            由于擦除原因,编译器无法知道这个转型是否安全,并且pop()方法实际上并没有执行任何转型。如果没有@SuppressWarnings注解,编译器将对pop()产生“Unchecked cast”警告。

            private int index = 0;
            private Object[] storage;
            @SuppressWarnings("unchecked")
            public T pop(){ return (T)storage[--index]; }

            (4)重载

            由于擦除的原因,重载方法将产生相同的类型签名,导致程序不能编译。

            public class UseList<W,T>{
                 void f(List<T> v){}
                 void f(List<W> v){}
             }

            (5)基类劫持了接口

            一旦为Comparable确定了ComparablePet参数,那么其他任何实现类都不能与ComparablePet之外的任何对象比较。在前面的“实现参数化接口”章节里面的第一个例子,就体现了基类劫持接口。

            public class ComparablePet
            implements Comparable<ComparablePet> {
              public int compareTo(ComparablePet arg) {
                  return 0;
              }
            }
            ​
            class Cat extends ComparablePet implements Comparable<Cat>{
              // Error: Comparable cannot be inherited with
              // different arguments: <Cat> and <Pet>
              public int compareTo(Cat arg) { return 0; }
            } ///:~
            ​
            class Hamster extends ComparablePet
                implements Comparable<ComparablePet>{
                public int compareTo(ComparablePet arg) {
                    return 0;
                }
            }

            9、自限定

            class Subtype extends BasicHolder<Subtype> {}这样用,就构成自限定了。从定义上来说,它继承的父类的类型参数是它自己。

            从使用上来说,Subtype对象本身的类型是Subtype,且Subtype对象继承而来的成员(element)、方法的形参(set方法)、方法的返回值(get方法)也是Subtype了(这就是自限定的重要作用)。这样Subtype对象就只允许和Subtype对象(而不是别的类型的对象)交互了。

            class BasicHolder<T> {
                T element;
                void set(T arg) { element = arg; }
                T get() { return element; }
                void f() {
                    System.out.println(element.getClass().getSimpleName());
                }
            }
            ​
            class Subtype extends BasicHolder<Subtype> {}
            ​
            public class CRGWithBasicHolder {
                public static void main(String[] args) {
                    Subtype st1 = new Subtype(), st2 = new Subtype(), st3 = new Subtype();
                    st1.set(st2);
                    st2.set(st3);
                    Subtype st4 = st1.get().get();
                    st1.f();
                }
            } /* Output:
            Subtype
            */

            10、异常

            由于擦除原因,将泛型应用于异常是非常受限的。但是,类型参数可能会在一个方法的throws子句中用到,这使得你可以编写随检查型异常的类型而发生变化的泛型代码。

            interface
            Processor<T,E extends Exception> {
                void process(List<T> resultCollector) throws E;
            }
            ​
            class
            ProcessRunner<T,E extends Exception>
                    extends ArrayList<Processor<T,E>> {
                List<T> processAll() throws E {
                    List<T> resultCollector = new ArrayList<T>();
                    for(Processor<T,E> processor : this)
                        processor.process(resultCollector);
                    return resultCollector;
                }
            }
            ​
            class Failure extends Exception {}
            ​
            class Processor1 implements
                    Processor<String,Failure> {
                static int count = 3;
                public void process(List<String> resultCollector)
                        throws Failure1_1, Failure1_2 {
                    if(count-- > 1)
                        resultCollector.add("Hep!");
                    else
                        resultCollector.add("Ho!");
                    if(count < 0)
                            throw new Failure1();
                }
            }
            ​
            public class Test {
                public static void main(String[] args) {
                    ProcessRunner<String,Failure> runner =
                            new ProcessRunner<String,Failure>();
                    for(int i = 0; i < 3; i++)
                        runner.add(new Processor1());
                    try {
                        System.out.println(runner.processAll());
                    } catch(Failure e) {
                        System.out.println(e);
                    }
                }
            }

            总结

            本篇文章就到这里了,希望能给您带来帮助,也希望您能够多多关注自由互联的更多内容!

            网友评论