Java数据结构基础——泛型、通配符

ced485cbb11e458d81a746890b32cf3f.gif

 作者:敲代码の流川枫

博客主页:流川枫的博客

专栏:和我一起学java

语录:Stay hungry stay foolish

工欲善其事必先利其器,给大家介绍一款超牛的斩获大厂offer利器——牛客网

点击免费注册和我一起刷题吧

文章目录

1. 泛型概念

泛型的引出

2. 泛型语法

3. 泛型的使用

类型推导(Type Inference)

裸类型(Raw Type)

4. 泛型的擦除机制

5. 泛型的上界

6. 泛型方法

7. 通配符

通配符上界

通配符下界


1. 泛型概念

泛型:就是适用于许多许多类型。从代码上讲,就是对类型实现了参数化

一般的类和方法,只能使用具体的类型: 要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。  ——《Java编程思想》

泛型的引出

实现一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值

可以将数组定义为Object类,因为所有类默认继承于这个类

public class Test {
    public static void main(String[] args) {
        MyArray myArray = new MyArray();
        myArray.setVal(0,10);
        myArray.setVal(1, "s");//字符串也可以存放
        myArray.setVal(2,10.3);
        //String ret = myArray.getPos(1);//编译报错
        String ret = (String) myArray.getPos(1);
        System.out.println(ret);
    }
}
class MyArray {
    public Object[] array = new Object[10];
    public Object getPos(int pos) {
        return this.array[pos];
    }
    public void setVal(int pos,Object val) {
        this.array[pos] = val;
    }
}

        //String ret = myArray.getPos(1);//编译报错
        String ret = (String) myArray.getPos(1);
        System.out.println(ret);

这里我们看到发生了向下转型,需要手动强制类型转换才可以,这样数据很多的时候就会很麻烦

使用Object数组的缺点有二,存放元素时可以存放任何类型的元素,再者,取出元素的时候需要手动强转

此时泛型就解决了这些问题,它将类型参数化了

2. 泛型语法

class 泛型类名称<类型形参列表> {
// 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> {
}
class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数 */ {
// 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> extends ParentClass<T1> {
// 可以只使用部分类型参数
}

注意:

1. 类名后的 <T> 代表占位符,表示当前类是一个泛型类

类型形参一般使用一个大写字母表示,常用的名称有:
E 表示 Element
K 表示 Key
V 表示 Value
N 表示 Number
T 表示 Type
S, U, V 等等 - 第二、第三、第四个类型

2. 不能new 泛型类型的数组

T[] ts = new T[5];//是不对的

3.<>里必须是类类型,不能是简单类型

我们将上述代码改写: 

class MyArray<T> {
    public T[] array = (T[]) new Object[10];
    public T getPos(int pos) {
        return this.array[pos];
    }
    public void setVal(int pos,T val) {
        this.array[pos] = val;
    }
}
public class Test {
    public static void main(String[] args) {
        MyArray<Integer> myArray = new MyArray<Integer>();
        myArray.setVal(0,10);
        myArray.setVal(1, "s");//报错
        myArray.setVal(2,10.3);//报错
        int a = myArray.getPos(0);//不用强转
        System.out.println(a);
        MyArray<String> myArray1 = new MyArray<String>();
        myArray1.setVal(0,"hello");
        myArray1.setVal(1,"world");
        String b = myArray1.getPos(0);//不用强转
        System.out.println(b);
    }
}

泛型的主要目的:就是指定当前的容器,要持有什么类型的对象。让编译器去做检查。此时,就需要把类型作为参数传递。需要什么类型,就传入什么类型 

泛型存在的意义:

1.在存放类型的时候会进行类型的检查

2.取出元素的时候会自动强制类型转换

注意:泛型时在编译的时候的一种机制,在运行时是没有泛型的概念的

3. 泛型的使用

泛型类<类型实参> 变量名; // 定义一个泛型类引用
new 泛型类<类型实参>(构造方法实参); // 实例化一个泛型类对象

MyArray<Integer> list = new MyArray<Integer>();

类型推导(Type Inference)

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

        MyArray<Integer> myArray = new MyArray<>();

        myArray.setVal(0,10);
        int a = myArray.getPos(0);//不用强转
        System.out.println(a);

        MyArray<String> myArray1 = new MyArray<>();

        myArray1.setVal(0,"hello");
        myArray1.setVal(1,"world");
        String b = myArray1.getPos(0);//不用强转
        System.out.println(b);
    }
}

编译器可以根据上下文推导出类型实参时,可以省略类型实参的填写

    MyArray<Integer> myArray = new MyArray<>();

    MyArray<String> myArray1 = new MyArray<>();

裸类型(Raw Type)

裸类型是一个泛型类型,但是没有实参,取值是还是要强转

注意: 我们不要自己去使用裸类型,裸类型是为了兼容老版本的 API 保留的机制

4. 泛型的擦除机制

编译的过程当中,将所有的T替换为Object这种机制,我们称为:擦除机制

getPos中返回值为T,被替换为Object,setVal中返回值为空,即V,val参数类型被替换为Object

先根据你制定了类型进行检查和转换,在编译的时候把T都擦除成了Object

因此,Java的泛型机制是在编译级别实现的。编译器生成的字节码在运行期间并不包含泛型的类型信息

5. 泛型的上界

在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束

语法

class 泛型类名称<类型形参 extends 类型边界> {
        //...
}

示例

public class MyArray<E extends Number> {
        //...
}

只接受 Number 的子类型作为 E 的类型,实参没有指定类型边界 E,可以视为 E extends Object

public class Test {
    public static void main(String[] args) {
        MyArray<Integer> myArray = new MyArray<>();
        MyArray<Float> myArray1 = new MyArray<>();
        MyArray<Double> myArray2 = new MyArray<>();
    }
}
class MyArray<E extends Number> {
}

6. 泛型方法

静态的泛型方法 需要在static后用<>声明泛型类型参数

class Util {
    public static <E> void swap(E[] array, int i, int j) {
        E t = array[i];
        array[i] = array[j];
        array[j] = t;
    }
}

<E>是泛型的形参,void是返回值,可以直接通过类名.来调用,不用new 

 非静态

7. 通配符

看一个例子:

class Message<T> {
    private T message ;
    public T getMessage() {
return message;
}
    public void setMessage(T message) {
this.message = message;
}
}
public class TestDemo {
    public static void main(String[] args) {
    Message<String> message = new Message() ;
    message.setMessage("hello");
    fun(message);
    Message<Integer> message1 = new Message() ;
    message1.setMessage(10);
    fun(message1);//报错
    }
    public static void fun(Message<String> temp){
        System.out.println(temp.getMessage());
    }
}

报错原因是fun方法只能接收Message<String>类型的参数,而传入的是Message<Integer>类型的参数

接下来我们体会通配符的作用

 public static void fun(Message<?> temp){
        System.out.println(temp.getMessage());
 }

此时不再报错

通配符可以接收所有的泛型类型,但是又不能够让用户随意修改

public class TestDemo {
    public static void main(String[] args) {
        Message<Integer> message = new Message() ;
        message.setMessage(55);
        fun(message);
    }
    // 此时使用通配符"?"描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改
    public static void fun(Message<?> temp){
        temp.setMessage(100); //无法修改!
        System.out.println(temp.getMessage());
    }
}

通配符上界

<? extends 上界>
<? extends Number>//可以传入的实参类型是Number或者Number的子类
class Food {

}
class Fruit extends Food {

}
class Apple extends Fruit {

}
class Banana extends Fruit {

}
class Message<T> { // 设置泛型上限
    private T message ;
    public T getMessage() {
        return message;
    }
    public void setMessage(T message) {
        this.message = message;
    }
}
public class TestDemo {
    public static void main(String[] args) {
        Message<Apple> message = new Message<>() ;
        message.setMessage(new Apple());
        fun(message);
        Message<Banana> message2 = new Message<>() ;
        message2.setMessage(new Banana());
        fun(message2);
    }
    // 此时使用通配符"?"描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改
    public static void fun(Message<? extends Fruit> temp){
    //temp.setMessage(new Banana()); //无法修改!
    //temp.setMessage(new Apple()); //无法修改!
        System.out.println(temp.getMessage());
    }
}

此时无法在fun函数中对temp进行添加元素,因为temp接收的是Fruit和他的子类,此时存储的元素应该是哪个子类无法确定。所以添加会报错,但是可以获取元素

public static void fun(Message<? extends Fruit> temp){
    //temp.setMessage(new Banana()); //无法修改!
    //temp.setMessage(new Apple()); //无法修改!
    Fruit b = temp.getMessage();
    System.out.println(b);
}

通配符的上界,不能进行写入数据,只能进行读取数据 

通配符下界

<? super 下界>
<? super Integer>//代表 可以传入的实参的类型是Integer或者Integer的父类类型

看一个例子

class Food {
}
class Fruit extends Food {
}
class Apple extends Fruit {
}
class Message<T> {
    private T message ;
    public T getMessage() {
        return message;
    }
    public void setMessage(T message) {
        this.message = message;
    }
}
public class TestDemo {
    public static void main(String[] args) {

        Message<Fruit> message = new Message<>() ;
        message.setMessage(new Fruit());
        fun(message);

        Message<Food> message2 = new Message<>() ;
        message2.setMessage(new Food());
        fun(message2);

        Message<Apple> message1 = new Message<>();
        fun(message1);//报错
    }

    //temp 接收Fruit及其子类的一个Message
    public static void fun(Message<? super Fruit> temp){

        // 此时可以修改 添加的是Fruit 或者Fruit的子类
        temp.setMessage(new Apple());//这个是Fruit的子类
        temp.setMessage(new Fruit());//这个是Fruit的本身

        //Fruit fruit = temp.getMessage(); 不能接收,这里无法确定是哪个父类

        System.out.println(temp.getMessage());//只能直接输出
    }
}

通配符的下界,不能进行读取数据,只能写入数据

因为无法确定是哪个父类

说明可以传入的实参的类型必须是Fruit或者Fruit的父类类型

“ 本期的分享就到这里了, 记得给博主一个三连哈,你的支持是我创作的最大动力!

ced485cbb11e458d81a746890b32cf3f.gif

  • 91
    点赞
  • 72
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 68
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YoLo♪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值