Java基础Review(一):泛型

一、背景

最近在封装一些通用组件的时候,发现自己对于泛型的理解并不深刻,因此开始网上冲浪查找资料。很多文章讲的一知半解,因此特地记录一下,也供后面自己翻阅。

二、泛型的由来

以这个类为例,如果使用object作为参数类型, 确实可以编译通过。但是在取值的时候一不小心就会报错,如果非要不报错就得每个item判断一下类型再强转,可读性非常差。

有没有什么办法在保持object所具有的对象重用功能,同时保证可读性,在编辑阶段就能报错提示?泛型就由此诞生了。

因此,我理解上泛型实际上是在一些使用场景上弥补了object的不足,但是不表示泛型可以替代object,因为泛型实际上也有自己的一些使用规则束缚,没有object简单粗暴。

public class Orange {

    private List list;

    public List get() {
        return list;
    }

    public void set(Object object) {
        list.add(object);
    }

    public static void main(String[] args) {
        Orange orange = new Orange();
        orange.set("1");
        orange.set(Boolean.FALSE);
        List resultList = orange.get();
        resultList.forEach(item->{
            //运行报错
            String result = (String)item;
        });
    }

}

三、泛型的使用标准

类型参数(又称类型变量)用作占位符,指示在运行时为类分配类型。根据需要,可能有一个或多个类型参数,并且可以用于整个类。根据惯例,类型参数是单个大写字母,该字母用于指示所定义的参数类型。下面列出每个用例的标准类型参数:

E:元素

K:键

V:值

N:数字

T:类型

?:表示不确定的类型(无限制通配符)

可以看得出来,命名规则是取的英文首字母,这是一个规范,就好比方法的命名需要见名知意一样。

四、泛型的分类及使用

4.1、 泛型类

泛型类就是在类的后面添加<T>标识,来指定当前类是一个泛型类。

在类new的时候需要指定T的类型,比如String。那么类型就会关联到使用的方法上,setType的时候入参就只能是String类型,如果换成其他类型就会编译报错。

public class Orange<T> {

    // type的类型由T指定,即:由外部指定
    private T type;

    // 返回值的类型由外部决定
    public T getType() {
        return type;
    }

    // 设置的类型也由外部决定
    public void setType(T type) {
        this.type = type;
    }

    public static void main(String[] args) {
        Orange<String> stringOrange = new Orange<>();
        stringOrange.setType("or");
        stringOrange.setType(1); //会编译报错
        System.out.println(stringOrange.getType());
    }
}

new的时候也可以不指定类型,那么默认就是object类型,这样使用泛型的意义就不大了。

 public static void main(String[] args) {
        Orange objectOrange = new Orange<>();
        objectOrange.setType(1);
        //换成其他类型会报错,需要强转
        Object result = objectOrange.getType();
    }

同时也支持多种类型的泛型,实际上使用场景并不多

public class Orange<T,K> {

    // type的类型由T指定,即:由外部指定
    private T type;

    private K key;

    // 返回值的类型由外部决定
    public T getType() {
        return type;
    }

    // 设置的类型也由外部决定
    public void setType(T type) {
        this.type = type;
    }

    public K getKey() {
        return key;
    }


    public void setKey(K key){
        this.key = key;
    }

    public static void main(String[] args) {
        //定义两种不同类型的泛型
        Orange<Integer, Boolean> integerBooleanOrange = new Orange<>();
        //设置第一个具体值
        integerBooleanOrange.setType(100);
        //设置第二个具体值
        integerBooleanOrange.setKey(Boolean.FALSE);
        System.out.println(integerBooleanOrange.getKey());

    }
}

可以发现,泛型类有以下特征(以下用T举例):

  • 类后面都伴随着<T> ,作用是申明这个类是一个泛型类
  • 泛型可以用在入参、返回值以及成员变量上
  • 只要使用到泛型,就必需在类后面申明这是一个泛型类,否则编译失败
  • 泛型在使用上都会受到类后<T>的制约,比如申明的是<Integer>,那么类中使用T的地方默认都是Integer类型
  • 如果要传基本数据类型的泛型,必须是包装类
4.3、泛型接口

泛型接口和泛型类实际上类型,接口上申明了是什么泛型,实现类也要使用相同的泛型,两者几乎就是套娃一样的。

public interface AppleInterface<T> {
    public T getAppleKind();
}


class GreenApple implements AppleInterface<String>{

    @Override
    public String getAppleKind() {
        return "Green colour";
    }
}

class ToyApple implements AppleInterface<Boolean>{
    @Override
    public Boolean getAppleKind() {
        return false;
    }
}
4.4、泛型方法

泛型类实际上有个缺点:每次针对不同类型的参数,都必须创建不同类型的对象。这使得方法依赖于创建对象时候申明的类型,它们之间的耦合度太高了,而泛型方法的出现,就是为了解决这个问题。

从以下代码就可以看出,每次水果店要卖一种水果,都需要定义具体的类型,耦合度非常高。

public class FruitShop<T> {

    public T sale(T param){
        return param;
    }


    public static void main(String[] args) {
        FruitShop<Orange> orangeFruitShop = new FruitShop<>();
        Orange orange = orangeFruitShop.sale(new Orange());

        FruitShop<Apple> appleFruitShop = new FruitShop<>();
        Apple sale = appleFruitShop.sale(new Apple());
    }
}

基于上面这个例子改造,感受一下泛型方法的魅力

public class FruitShop {

    //泛型方法
    public <T> T sale(T param){
        return param;
    }


    public static void main(String[] args) {
        FruitShop FruitShop = new FruitShop();
        Orange orange = FruitShop.sale(new Orange());
        Apple apple = FruitShop.sale(new Apple());
    }
}

可以看到使用泛型方法后,完全不需要多次定义FruitShop,使得方法和类解耦。方法具体的泛型只在方法本身上申明,调用的时候确定具体参数类型。

泛型类的四大特征:

  • 必须在方法后面加上<T>,作用是申明这是一个泛型方法
  • 泛型可以使用在方法的入参、返回值
  • 泛型方法支持多个泛型<T,V>,可以用一个表示入参,一个表示返回值
  • 严格的调用方法 对象.<T>method(),如FruitShop.<Orange>sale(new Orange()),也可省略成对象.method(),如例子
4.5、泛型类和泛型方法(泛型接口)的混用

最近在写封装项目的支付方式的时候发现,A、B两种支付方式的入参一样,但是返回值是不一样的。于是就针对入参定义了泛型接口,出参在具体的pay()方法上使用了泛型方法。当然这是一个例子,实际上并不严谨,完全可以把返回值用一个大的对象包裹起来,在类上申明两个泛型<T,R>

public interface TradeInterface<T>  {
    /**
     * 支付
     * @param requestParam
     * @return
     * @param <R>
     */
    <R> R pay(T requestParam);

    /**
     * 退款
     * @param requestParam
     * @return
     * @param <R>
     */
    <R> R refund(T requestParam);

}
4.6、泛型的边界

有些情况下,需要我们对泛型进行限制,比如<T extends Orange>就限制了这个具体的类型,Orange就是T的上限,T可可以是Orange的任意子类,换做其他类型就会编译报错

public class FruitShop{

    public <T extends Orange> T sale(T param){
        return param;
    }


    public static void main(String[] args) {
        FruitShop fruitShop = new FruitShop();
        fruitShop.sale(new Orange());
        // 编译报错
        fruitShop.sale(new Apple<>());

    }
}
多重限定

如果有多个限定,则可以使用&符号,但是如果其中限定包含类需要写在最前面,且只能存在一个类

public <T extends Orange & AppleInterface> T sale(T param){
        return param;
    }
4.7、通配符
1. 无边界通配符:?

很多时候会搞不清T、?的关系,为什么有了T还需要?

当代码不依赖具体的参数类型时,那就可以使用无边界通配符。如下,如果使用Apple类里面的setParam方法,因为通配符无法表示具体含义,所以即使调用了setParam方法也会报错。但是不涉及指定通配符相关的方法依旧可以调用。

public class Apple<T>{
    private T param;

    public void setParam(T param){
        this.param = param;
    }

    public static void main(String[] args) {
        Apple<?> apple = new Apple<>();
        apple.hashCode();
    }
}

看起来通配符和object有点类似,实际上还是有一些区别。

1.一个方法的入参是List<?> ,调用这个方法的时候可以是任意List。而如果是List<Object>,则限制了指定类型。

2.可以向 List<Object> 中插入 Object 对象,或者任何其子类对象,但是你只能向 List<?>中插入 null 值。

2. 上界通配符: ? extends 上界类型

和4.6类型,只不过具体的泛型T替换成了?

3. 下界通配符:? super 子类

使用的时候通配符必须是子类或者其父类

public class FruitShop {

    public AppleInterface sale(AppleInterface<? super ReadApple> param) {
        return param;
    }


    public static void main(String[] args) {
        FruitShop fruitShop = new FruitShop();
        fruitShop.sale(()->  new ReadApple());
        //GreenApple是AppleInterface的另一个子类,编译报错
        fruitShop.sale(()->  new GreenApple());
    }
}
4.8、泛型擦除

关于泛型擦除,实际上赘述起来可以单开一篇,后续会专门更新一篇来讲

  • 24
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值