Java进阶之深入理解泛型

1.泛型概念的提出(为什么需要泛型)?

(1)代码复用
(2)类型安全
(3)编程的便利,不需要强转

// 不使用泛型的例子
List list = new ArrayList();
list.add(1);
list.add(2);
list.add("五号");//一不小心插入了String
for (Object object : list){
  //人为的强制类型转化到具体的目标类型,取出“五号”时出现ClassCastException
  Integert = (Integer)object;
}
/**
 * 当我们使用泛型时,该错误就会避免上面集合没有使用泛型时出现的错误
 */
Listlist = newArrayList();
list.add(1);
list.add(2);
list.add("五号");//插入String,提示:编译错误

(5)学习链接
java基础入门-泛型(1)-为什么需要使用泛型?

2 认识泛型

(1)泛型就是类型化参数化,处理的数据类型不是固定的,而是可以作为参数传入。
(2)泛型允许在定义类、接口、方法时使用类型形参,这个类型形参将在声明变量、创建对象、调用方法时动态地指定。例如:

// 定义一个接口
interface Money{
   Eget(intindex);
   boolean add(E e);
}
// 定义一个类
public class Apple{
   private T info;
   public Apple(T info) {
     this.info = info;
   }
   public T getInfo(){
     return this.info;
   }
   public void setInfo(T info){
     this.info = info;
   }
   public static void main(String[] args) {
     Appleap1 = new Apple("小苹果");
     System.out.println(ap1.getInfo());
     Appleap2 = new Apple(1.23);
     System.out.println(ap2.getInfo());
   }
}

(3)注意:在静态方法、静态初始化块或者静态变量的声明和初始化中不允许使用类型形参。因为不管为泛型的类型形参传入哪一种类型实参,对于Java来说,它们依然被当成同一个类处理,在内存中也只占用一块内存空间。不管泛型的实际类型参数是什么,它们在运行时总有同样的类(class),例如下面的程序将输出true。

static List l1 = new ArrayList();
static List l2 = new ArrayList();
System.out.println(l1.getClass() == l2.getClass());// true

3 上界下界

3.1 上界

上界用extends关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。

List numbers = new ArrayList(); 
List ints = new ArrayList(); 
ints.add(100);
ints.add(34);
numbers.addAll(ints);//提示编译错误
List ints = new ArrayList(); 
List numbers = ints;//假设这是合法的 
numbers.add(new Double(12.34));//出现了Double的值

addAll需要的参数类型是List-Number,而传递过来的参数类型是List-Integer,不适用。因为Integer是Number的子类,而List不是List的子类。**通俗理解是,苹果是水果的子类;而装苹果的篮子不是装水果的篮子。**如果使用泛型设定上界解决,编译就没有任何问题了:

public  void addAll(List list)  {  
    for(int i=0; i
numbers.addAll(ints);

3.2 下界

java泛型中的上界下界(上限下限)

4 类型通配符-PECS原则

4.1 ? extends — 生产者

(1)不能写入数据–add()(确切地说不能add出除null之外的对象,包括Object)

List ints = new ArrayList(); 
List numbers = ints;  //works, 
  • numbers是一个Number子类的List,由于Integer是Number的子类,因此将ints赋给numbers是合法的。
  • 编译器会阻止将a类加入numbers。在向numbers中添加元素时,编译器会检查类型是否符合要求。因为编译器只知道ints是Number某个子类的List,但并不知道这个子类具体是什么类,为了类型安全,只好阻止向其中加入任何子类。
  • 那么可不可以加入Number呢?也不可以,不能往一个使用了? extends的数据结构里写入任何的值。

(2)只能读取数据–get():由于编译器知道它总是Number的子类型,因此我们总可以从中读取出Number对象。

public void addAll(List list)  {  
    for(int i=0; i

4.2 ? super — 消费者

(1)只能写入数据–add()

List ints = new ArrayList(); 
ints.add(100); 
ints.add(34); 
List numbers = new ArrayList();  
ints.copyTo(numbers);                
public void copyTo(List list)  {  
    for(int i=0; i

numbers是一个Integer超类(父类,superclass)的List。我们可以加入Integer对象或者其任何子类对象(因为编译器会自动向上转型),但由于编译器并不知道List的内容究竟是Integer的哪个超类,因此不允许加入特定的任何超类型。所以使用? super解决问题,如下:

public void copyTo(List list)  {  
    for(int i=0; i

(2)不能读取数据–get():编译器在不知道是什么类型的情况下只能返回Object对象,因为Object是任何Java类的最终祖先类,Object fruit = apples.get(0);。

4.3 PECS原则小结

(1)? 和 ? extends T 通配符用于读取,使得方法可以读取T或T的任意子类型的容器对象。
(2) ? super T 通配符用于读取和比较,使得对象可以写入父类型的容器,使得父类型的比较方法可以应用于子类对象。

4.4 泛型方法和类型通配符的区别

(1)大多数时候都可以使用泛型方法来代替类型通配符。例如对于Java的Collection接口中两个方法的定义:

public interface Collection{
   boolean containsAll(Collectionc);
   booleanaddAll(Collection c);
   ...
}

(2)上面集合中的两个方法的形参都采用了类型通配符的形式,也可以采用泛型方法的形式,如下所示:

public interface Collection{
    boolean containsAll(Collection c);
    boolean addAll(Collection c);
   ...
}

(3)上面两个方法中类型形参T只使用了一次,类型形参T产生的唯一效果是在不同的调用点传入不同的实际类型。对于这种情况,应该使用通配符,因为通配符就是被设计用来支持灵活的子类化的。
(4)泛型方法允许类型形参被用来表示方法的一个或多个参数之间的类型依赖关系,或者方法返回值与参数之间的类型依赖关系。如果没有这样的类型依赖关系,就不应该使用泛型方法。

4.5 "通配符"与“类型参数”关系

(1)“有限定通配符”与“类型参数”关系

    // 设置有限定通配符(等价)
    public void addAllWildcards(DynamicArray c) {
        for(int i=0; i,T的上界为E(等价)
    public  void addAll(DynamicArray c) {
        for (int i = 0; i < c.size; i++) {
            add(c.get(i));
        }
    }

(2)“无限定通配符形式-”与“类型参数”关系

    // 设置无限定通配符形式-(等价)
    public static int indexOf1(DynamicArray arr, Object elm) {
        for (int i = 0; i < arr.size(); i++) {
            if (arr.get(i).equals(elm)) {
                return i;
            }
        }
        return -1;
    }
    // 设置类型参数-泛型方法(等价)
    public static  int indexO1(DynamicArray arr, T elm) {
        for (int i = 0; i < arr.size(); i++) {
            if (arr.get(i).equals(elm)) {
                return i;
            }
        }
        return -1;
    }

(3)超类型通配符(无法用类型参数替代)

    //设置超类型通配符(无法用类型参数替代)
    public void copyTo(DynamicArray dest){
        for(int i=0; i

4.6 学习链接

Java泛型中的PECS原则
《Java编程的逻辑-第八章》

5 泛型类和泛型方法(静态方法泛型)

(1)泛型类

// 泛型类定义的泛型,在整个类中有效。泛型类的对象明确要操作的具体类型后,所有方法要操作的类型就已经固定了。
public class Demo {
    public void show(T t)  {
        System.out.println("show: "+t);
    }
}

(2)泛型方法

public class Demo {
    public  void show(T t) {
        System.out.println("show: "+t);
    }
    public  void print(Q q) {
        System.out.println("print:"+q);
    }
}

(3)同时定义泛型类和泛型方法

public class Demo {
    public void show(T t) {
        System.out.println("show: "+t);
    }
    public  void print(Q q) {
        System.out.println("print:"+q);
    }

(4)静态方法不可以访问类上定义的泛型,可以将泛型定义在方法上

public class Demo {
    public void show(T t) {
        System.out.println("show: "+t);
    }
    public static void method(W t) {
        System.out.println("method: "+t);
    }
}

(5)学习链接
AVA——泛型类和泛型方法(静态方法泛型)

6 如何继承泛型

6.1 泛型

public class ServiceImpl {
    public void test(M, T) {
        // do something
    }
}

子类继承父类分两种情况:

// 1、保持子类的泛型化
public TestServiceImpl extends ServiceImpl {
    @Override
    public void test(M, T) {
    }
}
// 2、子类不再泛型化
public TestServiceImpl extends ServiceImpl {
    @Override
    public void test(String, Integer) {
    }
}

6.2 泛型

public abstract class AbstractMvpPresenter {
}

子类继承父类分两种情况:

// 1、保持子类的泛型化
public class BaseLoadMorePresenter extends AbstractMvpPresenter { 
}

public class RequestPresenter extends BaseLoadMorePresenter {
}
// 2、子类不再泛型化
public class BaseLoadMorePresenter extends AbstractMvpPresenter { 	
}

6.3 学习链接

java 如何继承泛型
java中继承+接口+泛型的无解组合

你可能感兴趣的:(Java进阶)