DAY08--泛型


关键词:泛型类(参数化类型),泛型方法(即参数化方法),类型参数(T),

泛型

为什么需要泛型?

  • 一般的类和方法,只能使用具体的类型,要么使用基本类型,要么使用自定义类型,如果我们需要编写适用于多种类型的代码,通过继承和接口的方式,局限性都太大,(JAVA单继承特性,实现接口则是需要实现方法),故要通过泛型,
  • 泛型的意义在于它有很强的通用性
泛型类

生成器与工厂方法的区别:生成器不需要参数(只需要定义一个next方法),而工厂方法一般需要参数

interface Generator<T> {
    T next() throws Exception;
}

class Coffee {
    private static int count=0;
    private final int id=count++;
    @Override
    public String toString() {
        return this.getClass().getSimpleName() +
                "id=" + id;
    }
}
//拿铁
class Latte extends Coffee {

}

//摩卡
class Mocha extends Coffee {

}

//热奶
class Cuppuccino extends Coffee {

}

public class CoffeeGenerator implements Generator<Coffee>,Iterable<Coffee>{
    Class[] classes = {Coffee.class, Latte.class, Mocha.class, Cuppuccino.class};
    Random random=new Random(47);
    public int size;// for iteratioin

    public CoffeeGenerator(int size) {
        this.size = size;
    }

    @Override
    public Coffee next() {
        try {
            return (Coffee) (classes[random.nextInt(classes.length)].newInstance());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Iterator iterator() {
        return new CoffeeIterator();
    }

    public class CoffeeIterator implements Iterator<Coffee>{
        int count=size;
        @Override
        public boolean hasNext() {
            return count>0;
        }

        @Override
        public Coffee next() {
            count--;
            //其实这个迭代器也能生成元素,虽然迭代器的本意不是这个,哈哈
            return CoffeeGenerator.this.next();
        }
    }
    
    public static void main(String[] args) {
        CoffeeGenerator coffees = new CoffeeGenerator(5);
        for (int i = 0; i <5 ; i++) {
            System.out.println(coffees.next());
        }
        for (Coffee coffee : new CoffeeGenerator(5)) {
            System.out.println(coffee);
        }
    }
}
//output
Mochaid=0
Latteid=1
Mochaid=2
Coffeeid=3
Coffeeid=4
Mochaid=5
Latteid=6
Mochaid=7
Coffeeid=8
Coffeeid=9
泛型方法

定义:泛型参数放置在返回值之前的方法,(其他的只能算普通方法)

规则

  • 尽量使用泛型方法,而不是泛型类
  • 是否拥有泛型方法,与这个类是不是泛型类没有关系
  • 静态方法无法访问泛型类的类型参数

创建一个通用的Generator,

class CountObject {
    private static int count=0;
    private int id=count++;

    public int getId() {
        return id;
    }

    @Override
    public String toString() {
        return "CountObject{" +
                "id=" + id +
                '}';
    }
}

class BasicGenerator<T> implements Generator{
    public Class<T> type;

    public BasicGenerator(Class<T> type) {
        this.type = type;
    }
//返回一个生成器对象
    public static <T> Generator<T> create(Class<T> c) {
        return new BasicGenerator<T>(c);
    }

    @Override
    public T next() throws Exception {
        return type.newInstance();
    }
}
public class BasicGeneratorTest {
    public static void main(String[] args) throws Exception {
        Generator generator = BasicGenerator.create(CountObject.class);
        for (int i = 0; i <5 ; i++) {
            System.out.println(generator.next());
        }
    }
}
擦除

为什么要有擦除?

  • 擦除是实现泛型的重要部分,不然将不兼容之前的非泛型代码

谁来执行擦除?

  • 编译器会擦除类型参数,故ArrayList与ArrayList的Class对象是一样的,且在泛型代码内部无法获取类型参数的具体类型,得到的只是类型参数的占位符,如E,K,V
class A {

}

class B {

}

class Q<Q> {

}

//坐标
class Coordinates<X, Y> {

}

public class LostInformation {
    public static void main(String[] args) {
        LinkedList<A> linkedList = new LinkedList<>();
        HashMap<A, B> hashMap = new HashMap<>();
        Q<A> q = new Q<>();
        Coordinates<A, B> coordinates = new Coordinates<>();
        System.out.println(Arrays.toString(linkedList.getClass().getTypeParameters()));
        System.out.println(Arrays.toString(hashMap.getClass().getTypeParameters()));
        System.out.println(Arrays.toString(q.getClass().getTypeParameters()));
        System.out.println(Arrays.toString(coordinates.getClass().getTypeParameters()));
    }
}
//output
[E]
[K, V]
[Q]
[X, Y]

擦除的后果

  • 因为擦除让我们失去了在泛型代码中进行某些操作的能力,任何运行时需要知道确切类型的操作将报错
public class Erased<T> {
    private final int SIZE=10;
    public void f(Object o) {
        if (o instanceof T){}// error
        T t=new T();// error
        T[] array=new T[SIZE];// error
        T[] array1 = (T[]) new Object[SIZE];        
    }
}

边界

因为擦除在方法体(以方法为单位)中移除了类型信息,所以在运行时的问题就是边界,即对象进入和离开方法的地点,这些也是编译器进行检查和插入转型代码的地点,

擦除的补偿

为什么需要补偿?

  • 因为擦除让我们失去了在泛型代码中进行某些操作的能力,任何在运行时需要知道明确类型(而你给的是Object)的操作都将被禁止
    • 如创建对象 new T(),instanceof的使用,创建数组 new T[]

类型参数保存在哪里?那个类的Class对象中,

如何补偿?

  • 通过传入具体类型的类型标签:如具体类的Class对象
class Building {

}

class House extends Building {

}

public class ClassTypeCapture<T> {
    Class<T> type;

    public ClassTypeCapture(Class<T> type) {
        this.type = type;
    }

    public Boolean capture(Object arg) {
        return this.type.isInstance(arg);
    }

    public static void main(String[] args) {
        ClassTypeCapture<Building> ctp = new ClassTypeCapture<Building>(Building.class);
        System.out.println(ctp.capture(new Building()));
        System.out.println(ctp.capture(new House()));
        ClassTypeCapture<House> ctp1 = new ClassTypeCapture<House>(House.class);
        System.out.println(ctp1.capture(new Building()));
        System.out.println(ctp1.capture(new House()));
    }
}
//output
true
true
false
true
通配符

通配符?需要引用某一确切的类型,如

LinkedList<? extends Fruit> fruits = new LinkedList<? extends Fruit>();//error
LinkedList<? extends Fruit> fruits = new LinkedList<Fruit>();//right

数组编译期检查缺陷,有些异常在运行时才抛出,泛型可以在编译期进行错误检测

JAVA泛型不是协变的,数组才是,举个例子

  • Number[]是Integer[]基类
  • LinkedList类型则不是LinkedList的基类
边界

上边界

T与Myclass(指定确切类型)可以互换,通配符?需要引用某一确切的类型

? extend T:指明了上界T

LinkedList<? extends Fruit> fruits

表明这个fruits将引用持有Fruit类型或Fruit某一派生类类型的LinkedList对象

  • ?引用的是T或者是T的某一派生类类型,添加T类类型的元素是不安全的
  • get方法返回的用T来接收

下边界

? super T:指明了下界T,

LinkedList<? super Fruit> fruits

表明这个fruits将引用持有Fruit类型或Fruit某一基类类型的LinkedList对象

  • 此时?引用的是T或者是T的某一基类类型,故添加T类型的元素是安全的
  • get方法返回的是Object来接收,因为Object是所有类的基类
class Food {

}

class Fruit extends Food{

}

class Banana extends Fruit{

}

class WildCards {
    public static void main(String[] args) {
        LinkedList<Fruit> container = new LinkedList<>();
        container.add(new Food());
        container.add(new Fruit());
        container.add(new Banana());

        LinkedList<? extends Fruit> fruits = new LinkedList<Banana>(Arrays.asList(new Banana(),new Banana()));
        fruits.add(new Food());// compile error
        fruits.add(new Fruit());// compile error
        fruits.add(new Banana());// compile error
        fruits = new LinkedList<Food>();// compile error
        fruits = new LinkedList<Fruit>();
        fruits = new LinkedList<Banana>();
        Fruit fruit = fruits.get(0);

        LinkedList<? super Fruit> anotherFruits = new LinkedList<Food>(Arrays.asList(new Fruit(),new Banana()));
        anotherFruits.add(new Food());// compile error
        anotherFruits.add(new Fruit());
        anotherFruits.add(new Banana());
        anotherFruits = new LinkedList<Food>();
        anotherFruits = new LinkedList<Fruit>();
        anotherFruits = new LinkedList<Banana>();// compile error
        Object object = anotherFruits.get(0);
    }
}

? extend T,?引用的是T或者是T某一的派生类类型,添加T类类型的元素是不安全的

在这里插入图片描述

? super T,此时?引用的是T或者是T的某一基类类型,故添加T类型的元素是安全的
在这里插入图片描述

PECS原则
  • 如果需要从一个集合中读取T类型的元素,且不能写入,用 ? extends,即Producer extends
  • 如果需要从一个集合中写入T类型的元素,且不需要读,则用 ? super,即Consumer super
  • 如果需要一个集合又读又写,则是不要用通配符

无界通配符 ?

无界通配符类型与原生类型不同,

为什么需要无界通配符?

  • 处理多个泛型参数是,有时其中一个参数是任意类型,其他参数是确切类型时候

捕获转换

  • 未指定确切类型的通配符类型被捕获,并转换成确切的类型
public class CaptureCoversion {
    static <T> void f1(Holder<T> holder) {
        T t = holder.get();
        System.out.println(t.getClass().getSimpleName());
    }

    static void f2(Holder<?> holder) {
        f1(holder);
    }

    public static void main(String[] args) {
        Holder<Double> holder = new Holder<Double>(1.0);
        f2(holder);
    }
}

问题

基本类型不能作为类型参数

  • 如不能创建ArrayList之类的东西

  • 解决的方法是:使用包装器类,自动包装机制帮你双向转换

public class ListOfInt {
    public static void main(String[] args) {
        List<Integer> list = new LinkedList<>();
        for (int i = 0; i <5 ; i++) {
            list.add(i);
        }
        for (int i = 0; i <5 ; i++) {
            System.out.print(list.get(i)+"\t");
        }
    }
}

当然,自动包装机制不能解决所有问题,它不能应用到数组(单个会自动转换,数组则不会自动转换)

int arr[] = new Integer[]{1, 2, 3};//error

实现参数化接口

  • 一个类不能实现同一泛型接口的两种变体,如Hourly
interface Payable<T> {
    
}

class Employee implements Payable<Employee> {
    
}
//钟点工
public class Hourly extends Employee implements Payable<Hourly>{ //error
    
}
自限定类型(待续)
动态类型安全
异常
混型
潜在类型机制
对潜在类型机制的补偿

注意

  • 泛型涉及到编译器行为,日后要补充一下
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值