泛型个人见解

泛型

1.泛型介绍

1.1泛型类与泛型方法

泛型类是在类名+”<T>“, 泛型方法是在返回类型前面+”<T>“。这里的T是指一种不确定参数,随便起什么名字都可以。

下面的代码演示了泛型类与泛型方法与其调用:

public class GenericsClass<T> {

    public <K> boolean genericsMethod(T obj1, K obj2){
        return obj1==obj2;
    }
    
    public static void main(String[] args){
        String testStr = "obj";
        GenericsClass<String> genericsClass = new GenericsClass<String>();
        genericsClass.genericsMethod(testStr, testStr);
    }
    
}

 

1.2为什么使用泛型

在java1.5后开始引入泛型,其目的有两个:一是提高代码的复用性, 二是让开发者在代码编译期间更早的发现代码可能出现的异常.

关于第一点: 提高代码的复用性。假设现在有一个Box类:

package joseph.learning.generics;

public class Box {
    
    public String obj;public String getObj() { return obj; }
    public void setObj(String obj) { this.obj = obj; }

}

里面有一个String类型的field,这样做的话假如我想往Box里面加入Integer类型的field就做不到了,现在只能加入String类型的Field,代码得不到复用,如果改成下面的这样子,就可以加入任何类型的field了:

public class Box<T> {
    
    public T obj;
public T getObj() { return obj; } public void setObj(T obj) { this.obj = obj; } }

调用方式:

        Box<String> box1 =new Box<String>();
        Box<Integer> box2 =new Box<Integer>();
        Box<Boolean> box3 =new Box<Boolean>();

但其实不加入泛型也能做到代码复用,将field改为Object类型就好了,其实泛型类型的field在编译出来的class文件中就是经过处理转为成object类型的。 

2.边界符

 现在我们要实现这样一个功能,查找一个泛型数组中大于某个特定元素的个数:

public static <T> int countGreaterThan(T[] anArray, T elem) {
    int count = 0;
    for (T e : anArray)
        if (e > elem)  // compiler error
            ++count;
    return count;
}

但是这样是错误的, 因为除了int, dobule, float, char, byte这些基本数据类型,其他类不一定能使用操作符>,所以编译错报错,那么使用边界符可以解决这一问题。

    public static <T extends Comparable<T>> void compare(T [] arry, T element){
        int count = 0;
        for(T ele : arry){
            if(ele.compareTo(element)>0){
                count++;
            }
        }
    }

这样就相对与告诉编译器,我传进来的参数T都是实现了Comparable接口,都能调用compareTo这个方法。

 

3.通配符

 借用一下上面的Box类,抛出一个问题,Box<Number>与Box<Integer>, Box<Double>有什么联系吧,答案是没有。

 我们先定义几个简单类:

class Fruit {}
class Apple extends Fruit {}
class Orange extends Fruit {}

然后定义一个类GenericsReading,有个read方法在内部类在Reader里面去读取list:

public class GenericsReading {
    List<Fruit> fruitList =Arrays.asList(new Fruit());
    List<Apple> AppleList =Arrays.asList(new Apple());
    
    static class Reader<T>{
         T read(List<T> list){
            return list.get(0);
        }
    }
    
    void reading(){
        Reader<Fruit> fruitReader =new Reader<Fruit>();
        fruitReader.read(fruitList);
        //fruitReader.read(AppleList); Errors: List<Fruit> cannot be applied to List<Apple>
    }
}

我们发现往其中添加AppleList会报错,因为List<Fruit>与List<Apple>没有联系。

如果使用通配符就可以解决这一个问题:

    static class WildCardReader<T>{
         T wildCardRead(List<? extends T> list){
            return list.get(0);
        }
    }
    
    void wildCardReading(){
        WildCardReader<Fruit> fruitReader =new WildCardReader<Fruit>();
        fruitReader.wildCardRead(fruitList);
        fruitReader.wildCardRead(AppleList); 
    }

‘? extends T’ 相当于告诉编译器我传入的参数都是T的子类(包括自己本身)。

 

PESC原则

 1. List<? extends Fruit> fruitList = new ArrayList<Fruit>(); 只能get不能Add。

原因:

  • 当我们尝试add一个Apple的时候,fruitList可能指向new ArrayList<Orange>();
  • 当我们尝试add一个Orange的时候,fruitList可能指向new ArrayList<Apple>();
  • 当我们尝试add一个Fruit的时候,这个Fruit可以是任何类型的Fruit,而flist可能只想某种特定类型的Fruit,编译器无法识别所以会报错。

所以对于实现了<? extends T>的集合类只能将它视为Producer向外提供(get)元素,而不能作为Consumer来对外获取(add)元素。

2.  List<? super Apple> AppleList = new ArrayList<Fruit>(); 只能Add不能get。

原因: 当我们尝试通过list来get一个Apple的时候,可能会get得到一个Fruit,这个Fruit可以是Orange等其他类型的Fruit。

3. 总结:

  • “Producer Extends” – 如果你需要一个只读List,用它来produce T,那么使用? extends T。
  • “Consumer Super” – 如果你需要一个只写List,用它来consume T,那么使用? super T。
  • 如果需要同时读取以及写入,那么我们就不能使用通配符了。

类型擦除

类型擦除就是说Java泛型只能用于在编译期间的静态类型检查,然后编译器生成的代码会擦除相应的类型信息,这样到了运行期间实际上JVM根本就知道泛型所代表的具体类型。

1. 在jdk1.5之后泛型才引用进来,为了兼容之前的版本,事实上编译出的class文件是没有泛型的,泛型参数 ‘T’都会转为object处理。

避免方法: 使用边界符即‘T extends interface’, 这样‘T’都会转为为相应的接口类型。

2. 

Java泛型很大程度上只能提供静态类型检查,然后类型的信息就会被擦除,所以像下面这样利用类型参数创建实例的做法编译器不会通过:

    public void add(List<T> list){
        
       T t =new T();
       list.add(t);
}

因为编译器根本不知道T到底是什么类型。这时我们可以使用反射:

public static <E> void append(List<E> list, Class<E> cls) throws Exception {
    E elem = cls.newInstance();   // OK
    list.add(elem);
}

3. 我们无法对泛型代码使用instance of关键字:

public static <E> void rtti(List<E> list) {
    if (list instanceof ArrayList<Integer>) {  // compile-time error
        // ...
    }
}

这时我们设置边界,使用通配符?

public static void rtti(List<?> list) {
    if (list instanceof ArrayList<?>) {  // OK; instanceof requires a reifiable type
        // ...
    }
}

 

原文引用:http://www.importnew.com/26387.html

 

 

转载于:https://www.cnblogs.com/jospe/p/7496811.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值