java泛型通配符

发布时间 2023-03-29 18:16:15作者: dogtwohaha

java泛型/通配符

泛型

  • E - Element (在集合中使用,因为集合中存放的是元素)
  • T - Type(表示Java 类,包括基本的类和我们自定义的类)
  • K - Key(表示键,比如Map中的key)
  • V - Value(表示值)
  • N - Number(表示数值类型)
  • ? - (表示不确定的java类型)

为什么会有泛型?

1.需要解决代码冗余,提高复用性

2.需要编译期的检查

编译期检查

        ArrayList<Animal> arrayList = new ArrayList<>();
        arrayList.add(dog);
        arrayList.add(new Object());//编译器这里会报错

运行结果

java: 对于add(java.lang.Object), 找不到合适的方法

提高代码复用性

DogCat 分别继承 Animal


/*   void run(Dog t) {
        System.out.println(t);
    }
    void run(Cat t) {
        System.out.println(t);
    }

    void run(Animal t) {
        System.out.println(t);
    }*/
void run(T t) {
    System.out.println(t);
}

T可以传入任意类型,和Object一样,但一般都会给泛型一个界限,有助于编译期发现问题

//如果T 指定为Animal类型 则
think.run(dog);
think.run(cat);
think.run(new Car());//编译期会报错
think.run(new ArrayList<>());//编译期会报错

泛型使用

示例

测试类

package com.huke;

import java.util.ArrayList;

/**
 * Author:huke
 * DATE 2023/3/29 10:00
 * 泛型
 */
public class GenericsTest<T extends Animal,V extends  Car> {
    public static void main(String[] args) {
        GenericsTest<Animal> genericsTest = new GenericsTest<>();
        Dog dog = new Dog();
        Cat cat = new Cat();
        
        ArrayList<Animal> arrayList = new ArrayList<>();
        arrayList.add(dog);
        arrayList.add(cat);
        
        Think<Animal, Car> think = new Think<>(arrayList);//指定泛型T,V的界限
        think.run(dog);
        think.run(cat);
        think.run(new Car());//编译期会报错
        think.run(new ArrayList<>());//编译期会报错

    }
}

Think

package com.huke;

import java.util.ArrayList;

/**
 * Author:huke
 * DATE 2023/3/29 10:23
 */
public class Think<T, V> {
    T t;
    V v;
    ArrayList<T> arrayList;

    Think(ArrayList<T> arrayList) {
        this.arrayList = arrayList;
    }


    void run() {
        this.arrayList.forEach(a -> System.out.println(a.toString()));
    }

    /*   void run(Dog t) {
        System.out.println(t);
    }
    void run(Cat t) {
        System.out.println(t);
    }

    void run(Animal t) {
        System.out.println(t);
    }*/

    void run(T t) {
        System.out.println(t);
    }

   
}

泛型在创建对象时就必须确定,如果没有确定则会使用Object

public class WailCardTest<T> {
    public static void main(String[] args) {
        WailCardTest<Object> wailCardTest = new WailCardTest<>();
    }
}

静态方法的泛型

    //静态方法需要声明泛型 <T, V>
    public static <T, V> T playBall(T t, V v) {
        System.out.println("动物们玩球:t:" + t + ",v:" + v);
        return t;
    }

如果不指定无法通过编译期

// 报错:java: 无法从静态上下文中引用非静态 类型变量 T
public static  T playBall(T t, V v) {
    System.out.println("动物们玩球:t:" + t + ",v:" + v);
    return t;
}

静态方法 泛型使用需要方法泛型定义

主要原因

1.Java中的静态方法属于类级别,在类加载时加载进内存,普通方法是在类实例化时才被加载进内存,因此类级别无法访问任何实例变量或方法。对于泛型而言对象不创建泛型无法确认.而静态方法不需要实例化既可以访问

2.当静态方法使用泛型时,Java编译器无法推断出泛型类型,因为在编译时它不知道类被实例化时会传入哪种类型。因此,需要在方法上定义泛型,以便告诉编译器需要使用哪种类型,以便将泛型类型替换为实际类型

通配符

解决了什么问题?

1.使用泛型时难以选择具体类型

2.不希望使用Object类型

3.希望进行编译期检查

通配符使用

通配符不能作为参数入参只能作为引用参数

package com.huke;

import java.util.ArrayList;

/**
 * Author:huke
 * DATE 2023/3/29 15:04
 * 通配符
 */
public class WailCardTest<T> {
    public static void main(String[] args) {
        WailCardTest<Object> wailCardTest = new WailCardTest<>();

        ArrayList<Cat> cats = new ArrayList<>();
        cats.add(new Cat());
        wailCardTest.play(cats);

        ArrayList<Car> cars = new ArrayList<>();
        cars.add(new Car());
        wailCardTest.play(cars);
        System.out.println("play方法执行完毕!");



        ArrayList<Animal> animals = new ArrayList<>();
        animals.add(new Cat());
        wailCardTest.showBySuper(animals);

    }

    private void play(ArrayList<?> arrayList) {
        arrayList.forEach(System.out::println);
    }

    private void showByExtends(ArrayList<? extends Animal> arrayList) {
        arrayList.forEach(System.out::println);
        //arrayList.add(new Cat()); //报错
    }

    private void showBySuper(ArrayList<? super Animal> arrayList) {
        arrayList.forEach(System.out::println);
        System.out.println("---------");
        arrayList.add(new Cat()); //不报错
        Object object = arrayList.get(0);
        System.out.println("arrayList[0]:"+object);//但是取出来直接成为Object
        arrayList.forEach(System.out::println);
    }
}

输出

Cat
Car
play方法执行完毕!
Cat
---------
arrayList[0]:Cat
Cat
Cat

错误写法

void test(? t){//编译不通过

}
//可以改为
void test(ArrayList<?> arrayList) {
    arrayList.forEach(System.out::println);
}

ArrayList<?> arrayList = new ArrayList<>();//这种写法
arrayList.add(new Cat());//编译不通过

//可以改为
ArrayList<? super  Animal> arrayList = new ArrayList<>();
arrayList.add(new Cat());

pecs概念

pecs全称是Producer Extends Consumer Super

使用extends确定上界的只能是生产者,只能往外生产东西,取出的就是上界类型。不能往里塞东西。

使用Super确定下界的只能做消费者,只能往里塞东西。取出的因为无法确定类型只能转成Object类型

  • 用于灵活写入,主要目的是统一使用父类的容器,使得对象可以写入父类型的容器。或者用于比较,使得父类型的比较方法可以应用于子类对象。
  • 用于灵活读取,使得方法可以读取 E 或 E 的任意子类型的容器对象。

示例


//上限
private void showByExtends(ArrayList<? extends Animal> arrayList) {
    arrayList.forEach(System.out::println);
    //arrayList.add(new Cat()); //报错
}
//下限
private void showBySuper(ArrayList<? super Animal> arrayList) {
    arrayList.forEach(System.out::println);
    System.out.println("---------");
    arrayList.add(new Cat()); //不报错
    Object object = arrayList.get(0);
    System.out.println("arrayList[0]:"+object);//但是取出来直接成为Object
    arrayList.forEach(System.out::println);
}

泛型擦除

什么是泛型擦除?

泛型是个语法糖,只存在于编译器中。而不存在于虚拟机(JVM)中

编译阶段:编译器对带有泛型的java代码进行编译时,会去执行类型检查和类型推断,然后生成普通的不带泛型的字节码,供JVM接收并执行,此时泛型信息被擦除

示例

测试类

package com.huke;

/**
 * Author:huke
 * DATE 2023/3/29 17:34
 */
public class GenericsDeleteTest {
    public static void main(String[] args) {
        Phone<String > phone = new Phone<>();
        phone.setT("绿色");
        String color = phone.getT();
        System.out.println(color);

    }
}

Phone

package com.huke;

/**
 * Author:huke
 * DATE 2023/3/29 17:35
 */
public class Phone <T> {
    private  T t;

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }
}

反编译字节码

public class com.huke.GenericsDeleteTest {
  public com.huke.GenericsDeleteTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class com/huke/Phone
       3: dup
       4: invokespecial #3                  // Method com/huke/Phone."<init>":()V
       7: astore_1
       8: aload_1
       9: ldc           #4                  // String 绿色
      11: invokevirtual #5                  // Method com/huke/Phone.setT:(Ljava/lang/Object;)V
      14: aload_1
      15: invokevirtual #6                  // Method com/huke/Phone.getT:()Ljava/lang/Object;
      18: checkcast     #7                  // class java/lang/String
      21: astore_2
      22: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
      25: aload_2
      26: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      29: return
}

分析

11:开始将String类型转为Object

15:get后得到Object类型

18:检查类型时候可以转换,转为String

泛型和通配符的区别?

相同点

都可以接收未知参数,都可以指定类型界限

不同点

通配符

当设置泛型通配符上限的时候,只能读取不能写入

当设置泛型通配符下限的时候,可以写入,读取出来就是Object类型

泛型

可以对集合进行添加操作,因为调用泛型方法的时候,类型就已经确定了