Java泛型理解

一、Java泛型引入
    java泛型的应用可以提高的代码的复用性,同时泛型提供了类型检查,减少了数据的类型转换,同时保证了类型安全。
    泛型如何保证类型安全:
    
 List list = new ArrayList();
      list.add("abc");
      list.add(new Integer(1));    //可以通过编译
      for (Object object : list) {
          System.out.println((String)object);//抛出ClassCastException异常
      }

    上面的代码会在运行时抛出ClassCastException,因为它尝试将一个Integer转换为String。
    而从java5开始,Collection的用法:
     
List<String> list = new ArrayList<>();
      list.add("abc");
      //list.add(new Integer(1));    //编译错误
      for (String string : list) {
          System.out.println(string);//无需任何强制类型转换
      }

    List的创建增加了类型参数String,因此只能向list添加String类型对象,添加其他对象会抛出编译异常;
    foreach循环不需要再添加任何强制类型转换,也就移除了运行时的ClassCastException异常。
    
二、泛型的类与接口
    使用泛型定义自己的类与接口。
    
 public class Gen {
          private Object obj;
          
          public Object getObj() {
              return obj;
          }
      
          public void setObj(Object obj) {
              this.obj = obj;
          }
          
          public static void main(String[] args) {
              Gen gen = new Gen();
              gen.setObj("abc");
              String str = (String) gen.getObj();//类型转换,可能会引起运行时ClassCastException
          }
      }

      使用泛型来重新定义Gen————使用<>指定泛型参数。
    
 public class Gen<T> {
            T obj;
        
            public T getObj() {
                return obj;
            }
        
            public void setObj(T obj) {
                this.obj = obj;
            }
            public static void main(String[] args) {
                Gen<String> gen = new Gen<>();
                gen.setObj("abc");
        //        gen.setObj(10);        //无法通过编译
                String str = gen.getObj();    //无需类型转换
                //-----------------------------
                Gen gen2 = new Gen();//raw type原始类型
                gen2.setObj("abc");
                gen2.setObj(10);    //可以通过编译,自动装箱将10转化为Integer对象
                Integer num = (Integer) gen2.getObj();//使用了强制类型转换
            }
        }

        main()方法中使用的是泛型类型Gen<String>,不再需要强制类型转换,也就移除了运行时的ClassCastException。
        也定义了一个没有使用泛型类型的gen2,编译器会警告“Gen is a raw type,References to generic type Gen<T> should be parameterized”。
        当不提供泛型类型时,会默认使用Object代替,所以,gen2可以设置String和Integer类型,不过,应该尽量避免这种情况出现,如此,又需要用到强制类型转换,也伴随着运行时的ClassCastException异常。
        
        可以使用@SuppresWarings("rawtypes")来抑制编译器弹出警告。
        
        接口的泛型应用和类的泛型应用很类似,如下:
       
public interface List <E> {
             void add(E x);
             Iterator<E> iterator();
        }
        
        public interface Iterator<E> {
             E next();
             boolean hasNext();
        }

        也可以将此应用到自定义的接口与类当中。还可以使用多个泛型参数来定义接口与类,比如Map<K,V>.
        泛型类型也可以作为一个参数来用,如:new HashMap<String, List<String>>()。
        
三、泛型的命名规范
        为了与Java关键字区别开,Java泛型参数只是使用一个大写字母来定义。各种常用泛型参数的意义如下:
        E——Element,常用在Java Collection里,如:List<E>,Iterator<E>,Set<E>
        K,V——Key,value,代表Map的键值对
        N——Number,数字
        T——Type,类型,如String,Integer等
        S,U,V——2nd,3rd,4th类型,和T的用法一样
        
四、泛型的方法与构造函数
    如果不希望整个类都被泛型化,可以只在某个方法上应用泛型。还可以在构造函数上应用泛型。
       
public class GenMethod {
                public static <T> void fromArrayToCollection(T[] a,Collection<T> c){
                    for (T t : a) {
                      c.add(t);
                    }
                }
                
                public static void main(String[] args) {
                    Object[] oa = new Object[100];
                    Collection<Object> co = new ArrayList<>();
                    
                    GenMethod.<Object>fromArrayToCollection(oa, co);
                }        
            }

    定义方法所用的泛型参数需要在修饰符之后添加,如public static <T>,如果有多个泛型参数,可如此定义<K,V>或者<T1,T2>
    不建议在泛型变量里添加其他类型,如下会引起编译错误(或隐含错误):
      
 public static <T> void fromArrayToCollection(T[] a,Collection<T> c){
            for (T t : a) {
                c.add(t);
                c.add(new Object());    
            }
        }

    泛型方法的调用GenMethod.<Object>fromArrayToCollection(oa, co); 在方法前声明了泛型类型Object。
    因为编译器可以推断这个泛型类型,因此也可以这样写: GenMethod.fromArrayToCollection(oa, co)。
    也可以如下:
      
 String[] sa = new String[100];
        Collection<String> cs = new ArrayList<String>();
        
        // T 推断为String
        fromArrayToCollection(sa, cs);
        
        // T 推断为Object
        fromArrayToCollection(sa, co);
        
        Integer[] ia = new Integer[100];
        Float[] fa = new Float[100];
        Number[] na = new Number[100];
        Collection<Number> cn = new ArrayList<Number>();
        
        //T 推断为Number
        fromArrayToCollection(ia, cn);
        
        //T 推断为Number
        fromArrayToCollection(fa, cn);
        
        //T 推断为Number
        fromArrayToCollection(na, cn);
        
        //T 推断为Object
        fromArrayToCollection(na, co);
        
        //编译错误,Number与String不能兼容
        fromArrayToCollection(na, cs);

        
五、泛型参数的界限
        如果希望泛型类型只能是一部分类型,比如操作数据的时候,会希望是Number或其子类类型,
            这其实就是给泛型参数添加一个界限。其定义形式为:
            <T extends BoundingType>
        此定义表示T应该是BoundingType的子类型(subtype)。T和BoundingType可以是类,也可以是接口。
        而且,此处的“extends”表示的是子类型,不等同于继承。如:
          
 public class Box<T> {
                private T t;
            
                public void set(T t) {
                    this.t = t;
                }
            
                public T get() {
                    return t;
                }
            
                public <U extends Number> void inspect(U u) {
                    System.out.println("T: " + t.getClass().getName());
                    System.out.println("U: " + u.getClass().getName());
                }
            
                public static void main(String[] args) {
                    Box<String> integerBox = new Box<>();
                    integerBox.set("abc");    //能通过编译,因为T指定为String类型
            //        integerBox.inspect("abc");//不能通过编译,因为U必须是Number类型或其子类
                    integerBox.inspect(new Integer(10));
                }
            }

            限定了泛型参数的界限,也可以调用BoundingType(如Number)的相对应的方法,如下:
               
public class NumberTest<T extends Integer> {
                    private T num;
                    
                    public NumberTest(T num) { this.num = num;}
                    
                    public boolean isOdd(){
                        return num.intValue()%2 == 1;
                    }
                    
                    //....
                }
              
            如何为泛型参数添加多个限制范围,多重限制范围格式如下:
                <T extends A & B & C>
            一个泛型参数可以有多重限制范围,使用“&”分隔。且限制范围中至多有一个类。
            如果用一个类作为限定,它必须是限定列表中的第一个。如:
               
Class A { /* ... */ }
                interface B { /* ... */ }
                interface C { /* ... */ }
                class D <T extends A & B & C> { /* ... */ }

            如果BoundingType不是放在第一位,会产生编译异常:
            
   class D <T extends B & A & C> { /* ... */ }  // 无法通过编译

              
    六、泛型方法与泛参界限的综合
            泛型方法是一个有用的工具,那泛参的界限就是这个工具的灵魂,为这个工具添加了一些“行为准则”。
            如下:设计一个方法,统计在一个数组里比指定元素大的个数,
              
 public static <T> int countGreater(T[] array,T elem) {
                    int count = 0;
                    for (T t : array) {
                        if (t > elem) {//编译错误
                            ++count;
                        }
                    }
                    return  count;
                }

            上面这个方法无法通过编译,因为操作符“>”只可以用在基本数据类型(byte,char,short,int,float,long,double,boolean),
            不可以用来比较对象之间的大小(除非实现了Comparable接口)。
            所以要为<T>添加一个界限Comparable<T>:
              
 public interface Comparable<T> {
                    public int compareTo(T o);
                }

            更改后的代码如下:
              
 public static <T extends Comparable<T>> int countGreater(T[] array,T elem) {
                    int count = 0;
                    for (T t : array) {
                        if (t.compareTo(elem) > 0) {//无编译错误
                            ++count;
                        }
                    }
                    return  count;
                }

            也可以选择添加界限Comparator<T,T>,只不过此界限需要两个参数。
            
七、泛型、继承与子类型
        如果两个类之间相互兼容(继承与被继承),那么便可以将一个类对象赋值给另一个对象,
        比如:可以将一个String对象赋值给Object,String是Object的子类,
           
String someString = new String();
Object someObject = new Object();
someObject = someString;

        在面向对象中,这是一种“is-a”关系。String是Object的一种对象,所以上面的赋值是可以的。
        这种“is-a”关系,同样也适用泛型。如果将泛参设置Number,那么在随后的调用里,只需要传入一个数据对象就行了,如下:
           
Box<Number> box = new Box<>();
box.add(new Integer(1));   
box.add(new Double(1.0));

        但是对于这种方法:public void someMethod(Box<Number> n) { /*.....*/}
        可以接受Box<Number>类型的参数,但是不能接受Box<Integer>或者Box<Double>类型的参数。
        因为Box<Integer>和Box<Double>都不是Box<Number>的子类。
        Integer是Number的子类,Box<Integer>和Box<Double>的共同父类是Object,但是它俩之间不是子类关系。
        即,无论类A与类B是否存在关联,myClass<A>和myClass<B>都没有任何关联,其共同的父类是Object。
        
八、泛型类与子类型
         泛型的extends与继承的extends不同,泛型的extends其后可以是一个类,也可以是一个接口。
         泛型的extends代表子类型,而不是子类,或许可以把其等同于extends(继承)和implement的并集。
         
         泛型里,也存在子类型,前提是其泛型参数的限制没有改变,可以认为泛参没有改变,其实就是从原来的类或接口来判断泛型的子类型。
         例如:ArrayList<E> implement List<E>,而List<E> extends  Collection<E>
         那么,ArrayList<String>就是List<String>的子类型,而List<String>则是Collection<String>的子类型。
         
         假设需要定义自己的List接口——PayLoadList,其定义如下:
            interface PayloadList<E,P> extends List<E> {
              void setPayload(int index, P val);
              //...
            }

        则这几种都是List<String>的子类型:
            PayloadList<String,String>
            PayloadList<String,Integer>
            PayloadList<String,Exception>



















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值