泛型 (泛型定义、泛型语法、泛型类的使用、擦除机制、桥接方法、泛型方法、擦除机制带来的影响、泛型的边界、通配符)--- Java内功心法

目录

1.泛型定义:

2.泛型语法:

3.泛型类的使用:

4.擦除机制:

桥接方法:

擦除机制带来的影响:

4.泛型的边界:

4.1泛型的上界:

4.2泛型方法:

4.3通配符:

4.3.1通配符语法:

4.3.2通配符上界:

4.3.3通配符下界:


1.泛型定义:

        泛型简单来说就是适用于许多许多类型,它就像是C++中的模板一样,可以接收许多许多类型来使用。

        泛型的主要目的:就是指定当前的容器,要持有什么类型的对象,然后让编译器去做检查。

2.泛型语法:

//class 泛型类名称<类型形参列表> {
//  在类中可以使用类型参数
//}

class Demo1<T> {

}

class Demo2<T1, T2, T3 ... Tn> {

}

class Demo3<T>extends 继承类 {

}

class Demo2<T1, T2, T3 ... Tn> extends 继承类<T1, T2>{
    //只有相同的类型形参才能使用(T1, T2)
}

我们来模拟实现一个可以存放任意类型的数组吧:


class MyArrays<T> {
    private T[] arr = (T[])new Object[10];
                      //这里需要强转!!
    //      T[] arr = new T[10];这样写是不对的,会报错!!

    public void setArr(int pos, T value) {
        arr[pos] = value;
    }

    public T getArr(int pos) {
        return arr[pos];
    }
}

public class Demo5 {
    public static void main(String[] args) {
              //这里的类型一定要是引用类型,不能是基本类型!!
        MyArrays<Integer> arr = new MyArrays<>();

        //在数组中存放元素
        arr.setArr(0, 1);
        arr.setArr(1, 2);
        arr.setArr(2, 3);
        arr.setArr(3, 4);

        //打印下标为2的元素
        System.out.println(arr.getArr(2));
    }
}

3.泛型类的使用:

语法:

泛型类名称<类型参数> 变量名;
或者
泛型类名称<类型参数> 变量名 = new 泛型类名称<类型参数>();
或者
泛型类名称<类型参数> 变量名 = new 泛型类名称<>();

使用案例:

ArrayList<Integer> arrayList = new ArrayList<Integer>();

 

ArrayList<Integer> arrayList = new ArrayList<>();
//原因是编译器进行了类型推导,推导出了实例化的形参是Integer类

4.擦除机制:

        在前文中,我们模拟写了一个泛型数组:

        那么在运行的时候,编译器就会自动将代码里的“T”替换为Object类(确切来说是泛型参数用其最左边界(最顶级的父类型)类型替换,默认则是Object),这种机制我们称为擦除机制。

         那这里提出一个问题:为什么不能直接初始化一个泛型数组呢(T[] arr = new T[10];)?

         在编译器实行擦除机制后,会将(T[] arr = new T[10];)变为(Object[] arr = new Object[10];),因为返回的Object数组里存放的可以是如何类型的变量,比如String类的、Character类的等等,那么如果将这些类型强转为Integer类,编译器会认为是不安全的。

        那么正确的写法应该是(了解即可):

class MyArrays<T> {
    private T[] arr;

    public MyArrays(Class<T> c, int capacity) {
        arr = (T[]) Array.newInstance(c,capacity);
    }
}

桥接方法:

public class Parent<T> {
    public Number get(T key){//number几乎包含了所有数据类型
        return 0;
    } 
}

class child1 extends Parent<String>{
    public Number get(String key){
        return 1;
    } 
}

class child2 extends Parent<String>{
    public Integer get(String key){
        return 2;
    }
}

我们知道有擦除机制在,上述代码的泛型都会被擦除,并且类型参数会被擦成Object,那么子类的重写方法的参数应该是Object的,但我们写成上述也并没有报错,这是怎么回事呢?

原因就是父类的T被擦成了object,所以编译器为我们先重写了父类的参数类型为object的方法,再通过该桥接方法区调用自己的重写的方法。

擦除机制带来的影响:

1.不能用同一个泛型类的实例区分方法签名

示例:

public class Test {
    public void test(List<String> a){
        System.out.println("String");
    }
    public void test(List<Integer> b){
        System.out.println("Integer");
    }
}

        这样是不行的,因为List< String >和List< Integeer >在类型擦除后都变成了List,那么两个方法的签名就一模一样了,编译器就会直接报错。

2.不能同时catch同一个泛型异常类的多个实例,原理同上一条相似。

4.泛型的边界:

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

4.1泛型的上界:

语法:

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

示例:

public class MyArray <T extends Number> {
    ...
}

在创建对象的时候:

MyArray<Integer> arr1;//正确,因为Integer是Number的子类
MyArray<String> arr1;//编译错误,因为String不是Number的子类

4.2泛型方法:

语法:

方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { ... }

示例:

class Test<E> {
    public <E>void method1(E value) {
        
    }
    
    public static <E>void method2(E value) {
        //如果是static修饰的方法,就在static后加<参数类型>
    }
}

使用示例:

        Test test = new Test();
        test.<Integer>method1(1);//使用类型推导
        test.method2("String");//不使用类型推导

4.3通配符:

4.3.1通配符语法:

? 用于在泛型的使用,即为通配符

通配符的使用场景:

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("abc");
        fun(message);
    }
    public static void fun(Message<String> temp){
        System.out.println(temp.getMessage());
    }
}

        以上代码如果使用这个fun方法那么就只能传入Message<String>,而不能传入Message<Integer>这种类型参数,那为例实现代码的复用,我们可将这个Message<String>参数类型使用通配符改成Message<?>,那么这个方法就能传入其他参数类型的变量啦~~

    public static void fun(Message<?> temp){
        //把String改成通配符'?'

        //改成通配符后不能修改Message里的变量,原因是它传入的类型不确定
        //temp.setMessage(100); 无法修改!
        System.out.println(temp.getMessage());
    }

注意:改成通配符后不能修改Message里的变量,因为它传入的类型不确定。

4.3.2通配符上界:

语法:<? 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());
    }
}

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

4.3.3通配符下界:

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

示例:

class Food {
}
class Fruit extends Food {
}
class Apple extends Fruit {
}
class Plate<T> {
    private T plate ;
    public T getPlate() {
        return plate;
    }
    public void setPlate(T plate) {
        this.plate = plate;
    }
}
public class TestDemo {
    public static void main(String[] args) {
        Plate<Fruit> plate1 = new Plate<>();
        plate1.setPlate(new Fruit());
        fun(plate1);
        Plate<Food> plate2 = new Plate<>();
        plate2.setPlate(new Food());
        fun(plate2);
    }
    public static void fun(Plate<? super Fruit> temp){
// 此时可以修改!!添加的是Fruit 或者Fruit的子类
        temp.setPlate(new Apple());//这个是Fruit的子类
        temp.setPlate(new Fruit());//这个是Fruit的本身
//Fruit fruit = temp.getPlate(); 不能接收,这里无法确定是哪个父类
        System.out.println(temp.getPlate());//只能直接输出
    }
}

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值