泛型-IMPORTANT

一、基本

  • 多态中的将基类作为方法参数类型,会带来一定的性能损耗
  • 可是有的时候,即使使用的是接口,也依然对程序的约束太强了
  • 泛型:参数化类型
  • 希望类或方法,能够具备最广泛的表达能力,解耦类或方法与之所用的类型之间的约束
  • 主要是为JDK 的容器服务

1. 自定义容器

1. 指定容器要持有的具体的类型,而且由编译器来保证类型的正确性
2. 与其使用Object, 更喜欢暂时不指定类型,而是稍后再决定具体使用什么类型
3. 泛型和多态不冲突,可以放入子类
4. 告诉编译器想使用什么类型,然后编译器帮你处理一切细节
package com.erick.day02;

public class Demo01 {
    public static void main(String[] args) {
        /*使用的时候,再去定义具体的类型*/
        ThirdHolder<Animal> holder = new ThirdHolder<>(new Dog());
        Animal animal = holder.get();
        System.out.println(animal.getClass().getTypeName());
    }
}

class Animal {
}

class Dog extends Animal {
}

/*使用的时候,再去定义具体的类型*/
class ThirdHolder<T> {
    private T t;

    public ThirdHolder(T t) {
        this.t = t;
    }

    public T get() {
        return t;
    }
}

/*可以持有一个Object类型*/
class SecondHolder {
    private Object obj;

    public SecondHolder(Object obj) {
        this.obj = obj;
    }

    public Object get() {
        return obj;
    }
}

/*只能持有一个具体的类型*/
class FirstHolder {
    private Integer obj;

    public FirstHolder(Integer obj) {
        this.obj = obj;
    }

    public Integer get() {
        return obj;
    }
}

1. 自定义元组

  • 仅一次方法调用,就能返回多个对象,但是return语句只允许返回单个对象
  • 创建一个对象,用它来持有想要返回的多个对象,但不想这个类包含太多的指定的类
  • 用一组元组可以解决这个问题
1. 该容器对象允许读取其中元素,但是不允许向其中存放新的对象(数据传送对象/信使)
2. 元组可以具有任意长度,元组中的对象可以是任意类型
package com.erick.day02;

public class Demo02 {
    public static void main(String[] args) {
        TwoTuple<Integer, Double> result = new TwoTuple<>(1, 2.3);
        System.out.println(result);
    }
}

/*利用元组的继承,实现定义多个数据类型*/
class FiveTuple<A, B, C, D, E> extends TwoTuple<A, B> {
    public final C c;
    public final D d;
    public final E e;

    public FiveTuple(A a, B b, C c, D d, E e) {
        super(a, b);
        this.c = c;
        this.d = d;
        this.e = e;
    }
}

class TwoTuple<A, B> {
    /*设置为public,虽然对外可见,但是类在初始化的时候,就已经
     * 给了值,并且不能修改
     * @@@ 只能读不能写  */
    public final A a;
    public final B b;

    /*类在初始化后,就是final类型的*/
    public TwoTuple(A first, B b) {
        this.a = first;
        this.b = b;
    }

    @Override
    public String toString() {
        return "TwoTuple{" +
            "first=" + a +
            ", second=" + b +
            '}';
    }
}

二、泛型接口

  • 接口可以生成需要的类型的对象
  • 匿名内部类
package com.erick.day02;

public class Demo03 {
    public static void main(String[] args) {

        Generator<Integer> generator = new Generator<Integer>() {
            @Override
            public Integer next() {
                return 1;
            }
        };

        System.out.println(generator.next());
    }
}

/*泛型接口*/
interface Generator<T> {
    T next();
}

三、泛型方法

1. 基本使用

  • 是否拥有泛型方法,与其所在的类是否是泛型没有关系
  • 尽量使用泛型方法,而不是泛型类
  • 泛型方法使用时,不需要指定传入的参数的类型,编译器会进行类型推断
package com.erick.day02;

public class Demo04 {
    public static void main(String[] args) {
        /*一般*/
        Fruit.deleteOneFruit(Double.valueOf(2.3));
        Fruit.deleteOneFruit(2.3);
    }
}

class Fruit {
    /*泛型普通方法
    * 1. 在调用的时候不需要指定参数类型,编译器会自动进行类型推断
    * 2. 这样就看起来像是一个方法被无限重载过一样
    * 3. 如果传入的基本类型,自动打包机制就会介入
    * 4. 泛型方法和自动打包避免了许多以前我们必须自己写的代码*/
    public <T> void createOneFruit(T t) {
        System.out.println("creating one fruit: " + t.getClass().getSimpleName());
    }

    /*static方法如果要访问泛型,必须使其成为泛型方法*/
    public static <T> void deleteOneFruit(T t) {
        System.out.println("delete one fruit: " + t.getClass().getSimpleName());
    }
}

2. 可变参数

  • 可变参数和泛型方法能够很好的共存
package com.erick.day02;

import java.util.ArrayList;

public class Demo05 {
    public static void main(String[] args) {
        get(1, 2.3, new ArrayList<String>());
    }

    /*可以为方法提供若干个不同的参数类型,结合良好*/
    private static <T> void get(T... args) {
        System.out.println(args.length);
    }
}

四、泛型擦除

1. 泛型擦除

  • 在使用泛型时,任何具体类型信息都被擦除,所以不同类型参数的泛型,实际上是同一种类型
  • 比如List<String> 和 List<Integer>实际上都会被擦除为List
  • 同一个泛型类,但是其中的具体的泛型类型不同,但是字节码class对象是相同的
  • 在泛型代码内部,无法获得任何有关泛型参数类型的信息
package com.erick.day02;

import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.List;

public class Demo06 {
    public static void main(String[] args) {
        method02();
    }

    private static void method01() {
        /*分别获取到两个泛型类型的不同的字节码对象
         * 1. first : class java.util.ArrayList
         * 2. second: class java.util.ArrayList
         * 3. 同一个泛型类,但是其中的具体的类不一样,但是对应的字节对象是一样的*/
        Class<? extends ArrayList> first = new ArrayList<String>().getClass();
        Class<? extends ArrayList> second = new ArrayList<Integer>().getClass();
        System.out.println(first);
        System.out.println(second);
    }

    /*1. 在泛型代码内部,无法获得任何有关泛型参数类型的信息
    * 2. 获取到的结果只是类似 E, V, A, B的一个占位符而已*/
    private static void method02() {
        List<String> list = new ArrayList<>();
        /*可以获取到泛型的内部的类型信息*/
        TypeVariable<? extends Class<? extends List>>[] typeParameters = list.getClass().getTypeParameters();
        for (TypeVariable type: typeParameters){
            /*获取到的结果是 E */
            System.out.println(type.getName());
        }
    }
}

2. 边界问题-擦除到父类

2.1 无边界
  • 类型擦除带来的问题, 无法通过 T t, t.method()的方法来进行代码编写
class Erick<T> {
    private T t;

    public Erick(T t) {
        this.t = t;
    }
    
    public void method(){
        /*无法在泛型中使用这种代码*/
        t.wrok();
    }
}
2.2 extends边界
  • 通过extends来告知编译器,只能接受遵循这个边界的类型
  • <T extends Animal>: T只能是 Animal的子类
  • 类型擦除到了Animal, 就好像是用Animal代替了 T
package com.erick.day03;

public class Demo01 {
    public static void main(String[] args) {
        Zoo<Dog> dogZoo = new Zoo<>(new Dog());
        dogZoo.dailyLife();

        Zoo<Cat> catZoo = new Zoo<>(new Cat());
        catZoo.dailyLife();
    }
}

/*用 extends: 表示T 的类型必须是继承*/
class Zoo<T extends Animal> {
    private T t;

    public Zoo(T t) {
        this.t = t;
    }

    /*这样编译就不再会出错*/
    public void dailyLife() {
        t.eat();
        t.sleep();
    }
}


class Cat implements Animal {

    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }

    @Override
    public void sleep() {
        System.out.println("猫打盹");
    }
}

class Dog implements Animal {

    @Override
    public void eat() {
        System.out.println("狗吃肉");
    }

    @Override
    public void sleep() {
        System.out.println("狗睡觉");
    }
}


/*遵循的边界*/
interface Animal {
    void eat();

    void sleep();
}
  • 程序中的所有泛型类型,都将被擦除,替换为它们的非泛型上界
  • 比如 List<T> 就会被擦除为 List
  • 主要是为了迁移兼容性

五、多继承

  • 边界的潜在的效果: 你可以按照自己的边界类型来调用方法
  • 边界条件设置: 先是class,然后是interface
  • 类只能继承一个,接口可以实现多继承
package com.erick.day04;

public class Demo01 {
    public static void main(String[] args) {
        BigChina<People> bigChina = new BigChina<>(new People());
        bigChina.allTheWay();
    }
}

class BigChina<T extends Animal & LivingThing & Earth> {
    private T t;

    public BigChina(T t) {
        this.t = t;
    }

    public void allTheWay() {
        t.eat();
        t.getAddress();
        t.life();
    }
}

/*具体的实现类,可以看到和上面边界限制的出现了一定的冗余*/
class People extends Animal implements LivingThing, Earth {

    @Override
    public void life() {
        System.out.println("人类的生活");
    }

    @Override
    public void getAddress() {
        System.out.println("人类住在房子里面");
    }

    @Override
    public void eat() {
        System.out.println("人类要吃熟食");
    }
}

class Animal {
    public void eat() {
        System.out.println("动物吃东西");
    }
}

interface LivingThing {
    void life();
}

interface Earth {
    void getAddress();
}

六、通配符

  • 可以向导出类型的数组,赋予基类类型的数组引用

1. 数组的向上转型

  • 错误的用了向上转型,因此导致在运行期会出现一定的问题
package com.erick.day04;

public class Demo02 {
    public static void main(String[] args) {
        method();
    }

    /*test method*/
    private static void method() {

        Fruit[] fruits = new Apple[5];
        fruits[0] = new Apple();

        /*1. 运行起来后,这是一个Apple的数组,因此只能存储Apple 以及它的子类
        * 2. 但从代码上来看,它的确是一个 Fruit的数组啊,所以编译器也允许在其中放入 Fruit, 但是实际运行的时候,就会报错
        * */
        
        /*实际上,向上转型不适合用在这里*/
        fruits[1] = new Fruit(); // ArrayStoreException
    }
}

class Fruit {

}

class Apple extends Fruit {

}

class ShanxiApple extends Apple {

}
  • 泛型的出现,很重要的一个原因就是要把运行时候的问题,提升到编译时期
  • 我们是在讨论容器的类型,而不是容器持有的类型
    private static void method2() {
        /*就会出现编译错误*/
        List<Fruit> list = new ArrayList<Apple>();
    }

2. 通配符的出现

  • 如果向在两个类型之间建立某种类型的向上转型关系,可以引入通配符
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值