Day16_集合与泛型(泛型类与泛型接口,泛型方法,类型变量的上限与泛型的擦除,类型通配符)

news2024/9/21 22:13:03

文章目录

  • Day16 泛型
    • 学习目标
    • 1 泛型的概念
      • 1.1 没有泛型的问题
      • 1.2 泛型的引入
      • 1.2 泛型的好处
      • 1.3 泛型的定义
    • 2 泛型类与泛型接口
      • 2.1 使用核心类库中的泛型类/接口
        • 案例一:Collection集合相关类型
        • 案例二:Comparable接口
      • 2.2 自定义泛型类与泛型接口
        • 语法格式
        • 案例一:自定义泛型类
        • 案例二:自定义泛型接口
      • 2.3 小结
    • 3 泛型方法
      • 3.1 泛型方法的调用
      • 3.2 自定义泛型方法
      • 3.3 泛型类与泛型方法的区别
        • 1、<泛型变量>声明位置不同
        • 2、<泛型变量>使用的范围不同
    • 4 类型变量的上限与泛型的擦除
      • 4.1 <类型变量>的上限
        • 案例1:定义泛型类的<类型变量>时指定上限
        • 案例2:定义泛型方法的<类型变量>时指定上限
      • 4.2 泛型擦除与泛型上限
    • 5 类型通配符
      • 5.1 类型通配符
      • 5.2 类型通配符的三种使用形式
      • 5.3 泛型变量T与通配符?区别
        • 1、T可以单独使用,而?必须依赖于泛型类或泛型接口使用
        • 2、T只能在声明时指定上限,?可以指定上限和下限
        • 3、同一个方法中多个T代表相同的类型,多个?没有关联性
        • 4、T是可以确定的类型,?是不能确定的类型

Day16 泛型

学习目标

  • 能够理解泛型的好处和意义
  • 能够在使用集合相关API时正确指定泛型
  • 能够使用其他泛型类、泛型接口
  • 能够认识泛型方法
  • 能够使用泛型定义类、接口、方法
  • 能够理解泛型上限作用
  • 能够阐述泛型通配符的作用
  • 能够识别通配符的上下限

1 泛型的概念

1.1 没有泛型的问题

例如

(1)在设计集合类型时,只能确定集合用来装对象,但是无法确定装什么类型的对象,即集合的元素类型未知,

(2)在设计比较器接口时,只能确定两个对象比较大小的结果是正整数、父整数、零,但是无法确定是两个什么类型的对象比较大小。

这些都必须要在集合被创建时或比较器接口被实现时才能确定。这就造成了两个问题:

(1)为了让集合和比较器类型得以顺利实现,就把元素类型设计为Object,那么在使用时,编译器就无法进行更加具体的类型检查====> 类型安全问题==

(2)为了调用元素对象非Object类的方法,不得不向下转型====> 代码繁琐的问题==

package com.atguigu.nogeneric;

import java.util.ArrayList;

public class TestNoGeneric {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add("hello");
        list.add("java");
        list.add(1);//编译器不进行类型检查
        for (Object o : list) {
            String s = (String) o;//需要向下转型,繁琐
            System.out.println(s +"的长度:" + s.length());
        }
    }
}
package com.atguigu.nogeneric;

public class Circle implements Comparable{
    private double radius;

    public Circle(double radius) {
        super();
        this.radius = radius;
    }

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }

    @Override
    public String toString() {
        return "Circle [radius=" + radius + "]";
    }

    @Override
    public int compareTo(Object o) {
        Circle c = (Circle) o;//向下转型,繁琐
        return Double.compare(this.radius, c.radius);
    }
}

1.2 泛型的引入

类比1:生活中的启发:

例如:生产瓶子的厂家,一开始并不知道我们将来会用瓶子装什么,我们什么都可以装,但是有的时候,我们拿到一些瓶子准备装东西时,想要限定这些瓶子分别只能用来装什么,这样我们不会装错,而取东西的时候也可以放心的取,无需再三思量。我们生活中是在瓶子上“贴标签”,这样就轻松解决了问题,在Java中是否也可以在使用集合或比较器等类型时,再给它们“贴标签”呢?

在这里插入图片描述

类比2:参数的启发

在Java中我们在声明方法时,当在完成方法功能时如果有未知的数据需要参与,这些未知的数据需要在调用方法时才能确定,我们把这样的数据通过形参表示,在方法体中用这个形参名来代表那个未知的数据,而调用者在调用时,通过实参对应的传入值就可以了。

在这里插入图片描述

类似于上面的类比,JDK1.5设计了泛型的概念。泛型即为“把类型当成参数传递的一种机制”。例如:java.lang.Comparable接口和java.util.Comparator接口,其中就是类型变量,在使用时再给类型变量T传递一个具体的类型。

public interface Comparable<T>{
    int compareTo(T o) ;
}
public interface Comparator<T>{
     int compare(T o1, T o2) ;
}

1.2 泛型的好处

如果有了泛型并使用泛型,那么既能保证安全,又能简化代码。

因为把不安全的因素在编译期间就排除了;既然通过了编译,那么类型一定是符合要求的,就避免了类型转换。

package com.atguigu.usegeneric;

public class Circle implements Comparable<Circle>{
    private double radius;

    public Circle(double radius) {
        super();
        this.radius = radius;
    }

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }

    @Override
    public String toString() {
        return "Circle [radius=" + radius + "]";
    }

    @Override
    public int compareTo(Circle o) {
        return Double.compare(this.radius, o.radius);
    }
}
package com.atguigu.usegeneric;

import java.util.Comparator;

public class CircleComparator implements Comparator<Circle> {

    @Override
    public int compare(Circle o1, Circle o2) {
        //不再需要强制类型转换,代码更简洁
        return Double.compare(o1.getRadius(), o2.getRadius());
    }

}
package com.atguigu.usegeneric;

import java.util.ArrayList;

public class TestUseGeneric {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
//        list.add(1);//编译器进行类型检查
        for (String s : list) {
            System.out.println(s +"的长度:" + s.length());
        }
    }
}

1.3 泛型的定义

<类型>这种语法形式就叫泛型。

  • 是类型变量(Type Variables),而是代表未知的数据类型,我们可以指定为,, 等。相当于把某个具体的类型泛化为一般的类型,所以称为泛型。
  • 类比方法的参数的概念,我们可以把,称为类型形参,将 称为类型实参,有助于我们理解泛型;

2 泛型类与泛型接口

我们把类名或接口名后面带、、<K,V>等的类或接口称为泛型类或泛型接口。

2.1 使用核心类库中的泛型类/接口

自从JDK1.5引入泛型的概念之后,对之前核心类库中的API做了很大的修改,例如:集合框架集中的相关接口和类、java.lang.Comparable接口、java.util.Comparator接口、Class类等等。

下面以Collection、ArrayList集合以及Iterator迭代器为例演示,泛型类与泛型接口的使用。

案例一:Collection集合相关类型

(1)创建一个Collection集合(暂时创建ArrayList集合对象),并指定泛型为

(2)添加5个[0,100)以内的整数到集合中,

(3)使用foreach遍历输出5个整数,

(4)使用集合的removeIf方法删除偶数,为Predicate接口指定泛型

(5)再使用Iterator迭代器输出剩下的元素,为Iterator接口指定泛型。

package com.atguigu.genericclass.use;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Random;
import java.util.function.Predicate;

public class TestNumber {
    public static void main(String[] args) {
        Collection<Integer> coll = new ArrayList<Integer>();
        Random random = new Random();
        for (int i = 1; i <= 5 ; i++) {
            coll.add(random.nextInt(100));
        }

        System.out.println("coll中5个随机数是:");
        for (Integer integer : coll) {
            System.out.println(integer);
        }

        coll.removeIf(new Predicate<Integer>() {
            @Override
            public boolean test(Integer integer) {
                return integer % 2 == 0;
            }
        });

        System.out.println("coll中删除偶数后:");
        Iterator<Integer> iterator = coll.iterator();
        while(iterator.hasNext()){
            Integer number = iterator.next();
            System.out.println(number);
        }

    }
}
案例二:Comparable接口

(1)声明矩形类Rectangle,包含属性长和宽,属性私有化,提供有参构造、get/set方法、重写toString方法,提供求面积和周长的方法。

(2)矩形类Rectangle实现java.lang.Comparable接口,并指定泛型为,重写int compareTo(T t)方法,按照矩形面积比较大小,面积相等的,按照周长比较大小。

(3)在测试类中,创建Rectangle数组,并创建5个矩形对象

(4)调用Arrays的sort方法,给矩形数组排序,并显示排序前后的结果。

package com.atguigu.genericclass.use;

public class Rectangle implements Comparable<Rectangle>{
    private double length;
    private double width;

    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    public double getLength() {
        return length;
    }

    public void setLength(double length) {
        this.length = length;
    }

    public double getWidth() {
        return width;
    }

    public void setWidth(double width) {
        this.width = width;
    }

    public double area(){
        return length * width;
    }

    public double perimeter(){
        return 2 * (length + width);
    }

    @Override
    public String toString() {
        return "Rectangle{" +
                "length=" + length +
                ", width=" + width +
                ",area =" + area() +
                ",perimeter = " + perimeter() +
                '}';
    }

    @Override
    public int compareTo(Rectangle o) {
        int compare = Double.compare(area(), o.area());
        return compare != 0 ? compare : Double.compare(perimeter(),o.perimeter());
    }
}

package com.atguigu.genericclass.use;

import java.util.Arrays;

public class TestRectangle {
    public static void main(String[] args) {
        Rectangle[] arr = new Rectangle[4];
        arr[0] = new Rectangle(6,2);
        arr[1] = new Rectangle(4,3);
        arr[2] = new Rectangle(12,1);
        arr[3] = new Rectangle(5,4);

        System.out.println("排序之前:");
        for (Rectangle rectangle : arr) {
            System.out.println(rectangle);
        }

        Arrays.sort(arr);

        System.out.println("排序之后:");
        for (Rectangle rectangle : arr) {
            System.out.println(rectangle);
        }
    }
}

2.2 自定义泛型类与泛型接口

当我们在类或接口中定义某个成员时,该成员的相关类型是不确定的,而这个类型需要在使用这个类或接口时才可以确定,那么我们可以使用泛型。

语法格式
【修饰符】 class 类名<类型变量列表>extends 父类】 【implements 父接口们】{
    
}
【修饰符】 interface 接口名<类型变量列表>extends 父接口们】{
    
}
案例一:自定义泛型类

例如:我们要声明一个学生类,该学生包含姓名、成绩,而此时学生的成绩类型不确定,为什么呢,因为,语文老师希望成绩是“优秀”、“良好”、“及格”、“不及格”,数学老师希望成绩是89.5, 65.0,英语老师希望成绩是’A’,‘B’,‘C’,‘D’,‘E’。那么我们在设计这个学生类时,就可以使用泛型。

package com.atguigu.genericclass.define;

public class Student<T>{
    private String name;
    private T score;

    public Student() {
        super();
    }
    public Student(String name, T score) {
        super();
        this.name = name;
        this.score = score;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public T getScore() {
        return score;
    }
    public void setScore(T score) {
        this.score = score;
    }
    @Override
    public String toString() {
        return "姓名:" + name + ", 成绩:" + score;
    }
}
    @Test
    public void test01(){
        //语文老师使用时:
        Student<String> stu1 = new Student<String>("张三", "良好");

        //数学老师使用时:
        //Student<double> stu2 = new Student<double>("张三", 90.5);//错误,必须是引用数据类型
        //Student<Double> stu2 = new Student<Double>("张三", 90);//错误,90是int,不能自动装箱为Double
        Student<Double> stu2 = new Student<Double>("张三", 90.0);//可以
        Student<Double> stu3 = new Student<Double>("张三", 90D);

        //英语老师使用时:
        Student<Character> stu4 = new Student<Character>("张三", 'C');

        //错误的指定
        //Student<Object> stu5 = new Student<String>();//错误的
    }
案例二:自定义泛型接口

案例需求:

1、定义一个计算器接口Calculator<T,R>,T代表操作数的类型,R代表计算结果的类型

  • 包含两个数计算的方法caculate,要求操作数的类型相同,但具体类型不确定,计算结果可能与操作数的类型不同。
  • 包含求两个数最大值的方法max,要求操作数的类型相同,结果与操作数的类型也相同

2、编写实现类实现计算器接口

  • 两个Integer整数相加及最大值,
    • 相加结果用Long表示
    • 返回两个整数中更大的那个,如果一样大,就返回第1个
  • 两个String相加及最大值,
    • 相加结果仍然是String,
    • 返回两个字符串中更长的字符串,如果一样长,就返回第1个
public interface Calculator<T, R> {
    R calculate(T t1, T t2);
    T max(T t1, T t2);
}
package com.atguigu.generic.classinterface;

import org.junit.Test;

public class TestGenericInterface {

    @Test
    public void test1() {
        System.out.println(Integer.MAX_VALUE);
        Calculator<Integer, Long> c = new Calculator<Integer, Long>() {
            @Override
            public Long calculate(Integer t1, Integer t2) {
                return (long) t1 + t2;
            }

            @Override
            public Integer max(Integer t1, Integer t2) {
                return t1 >= t2 ? t1 : t2;
            }
        };
        Long sum1 = c.calculate(65536, 65536);
        Long sum2 = c.calculate(Integer.MAX_VALUE, Integer.MAX_VALUE);
        Integer max = c.max(1, 2);
        System.out.println("sum1 = " + sum1);
        System.out.println("sum2 = " + sum2);
        System.out.println("max = " + max);
    }

    @Test
    public void test2() {
        Calculator<String, String> c = new Calculator<String, String>() {
            @Override
            public String calculate(String t1, String t2) {
                return t1 + t2;
            }

            @Override
            public String max(String t1, String t2) {
                return t1.length() >= t2.length() ? t1 : t2;
            }
        };

        String sum = c.calculate("hello", "world");
        String max = c.max("hello", "java");
        System.out.println("sum = " + sum);
        System.out.println("max = " + max);
    }
}

2.3 小结

1、<类型变量列表>:可以是一个或多个类型变量,一般都是使用单个的大写字母表示。例如:、<K,V>等。

2、<类型变量列表>中的类型变量不能用于静态成员上。

3、在同一个类或接口中同一个类型变量代表同一种数据类型

4、<实际类型参数>必须是引用数据类型,不能是基本数据类型

5、可以在创建泛型类的对象时指定<类型变量>对应的<实际类型>

(1)指定泛型实参时左右两边必须一致

在这里插入图片描述

(2)JDK1.7支持自动类型推断的简写形式:ArrayList list= new ArrayList<>();

6、子类继承泛型父类时,子接口继承泛型父接口、或实现类实现泛型父接口时,

(1)可以指定<类型变量>对应的<实际类型>,此时子类或实现类不再是泛型类

package com.atguigu.genericclass.define;

//ChineseStudent不再是泛型类
public class ChineseStudent extends Student<String>{

    public ChineseStudent() {
        super();
    }

    public ChineseStudent(String name, String score) {
        super(name, score);
    }

}
public class Rectangle implements Comparable<Rectangle>

(2)用子类/子接口的类型变量指定父类或父接口的类型变量,子类/子接口的类型变量可以和原来字母一样,也可以换一个字母,此时子类、子接口、实现类仍然是泛型类或泛型接口

public interface Iterable<T>
public interface Collection<E> extends Iterable<E>  //E:Element元素
public interface List<E> extends Collection<E>
public class ArrayList<E>extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, Serializable

3 泛型方法

在方法的返回值类型前面声明了等,该方法就是泛型方法。

泛型方法在调用时,由实参的类型确定泛型方法类型变量的具体类型。

3.1 泛型方法的调用

在java.util.Arrays数组工具类中,有很多泛型方法,例如:

  • public static List asList(T… a):添加任意个任意类型的对象到List集合中

  • public static T[] copyOf(T[] original, int newLength):复制任意对象数组,新数组长度为newLength。

    如果没有泛型,只能用Object[]数组,那么对象数组复制后只能返回Object[]数组,就太麻烦了。

package com.atguigu.method;

import java.util.Arrays;
import java.util.List;

public class TestArrays {
    public static void main(String[] args) {
        String[] arr = {"java", "world", "hello"};
        String[] strings = Arrays.copyOf(arr, arr.length * 2);
        System.out.println(Arrays.toString(strings));

        List<String> list = Arrays.asList("java", "world", "hello");
        System.out.println(list);
    }
}

3.2 自定义泛型方法

我们除了在类名或接口名后面声明泛型的<类型变量>之外, 还可以在方法的返回值类型前面为这个方法单独声明泛型的<类型变量>,这个方法可以是静态方法,也可以是非静态方法。

语法格式:

【修饰符】 <类型变量列表> 返回值类型 方法名(【形参列表】)throws 异常列表】{
    //...
}

示例代码:

我们编写一个集合工具类,实现将多个元素都添加到一个Collection集合中。

package com.atguigu.method;

import java.util.Collection;

public class MyCollections {
    public static <T> void addAll(Collection<T> coll, T... args){
        for (T t : args) {
            coll.add(t);
        }
    }
}

package com.atguigu.method;

import java.util.ArrayList;

public class MyCollectionsTest {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        MyCollections.addAll(list, "hello","world","java");
        System.out.println(list);
    }
}

3.3 泛型类与泛型方法的区别

1、<泛型变量>声明位置不同
  • 声明方法时,在【修饰符】与返回值类型之间声明类型变量,我们把声明(是声明不是单纯的使用)了类型变量的方法称为泛型方法
【修饰符】 <类型变量列表> 返回值类型 方法名(【形参列表】)throws 异常列表】{
    //...
}

例如:java.util.Arrays类中的
public static <T> List<T> asList(T... a){
    ....
}
  • 声明类或接口时,在类名或接口名后面声明类型变量,我们把这样的类或接口称为泛型类或泛型接口
【修饰符】 class 类名<类型变量列表>extends 父类】 【implements 父接口们】{
    
}
【修饰符】 interface 接口名<类型变量列表>implements 父接口们】{
    
}

例如:
public class ArrayList<E>    
public interface Map<K,V>{
    ....
}    
2、<泛型变量>使用的范围不同
  • 在类或接口名后面声明的<泛型变量>在整个类中都可以使用,而且同名的<泛型变量>代表的类型是相同。
  • 在方法返回值类型前面声明的<泛型变量>仅限于当前方法使用,和其他方法同名的<泛型变量>代表的类型是无关的。
package com.atguigu.different;

public class TestDifferent1{
    public static void main(String[] args) {
        Demo<String> demo = new Demo<>();
        demo.m1("hello");
        demo.m2("world");

        Example example = new Example();
        example.m1("hello");
        example.m2(666);
    }
}
/*
同一个Demo对象的m1和m2的T类型是有关联的,是同一种类型
 */
class Demo<T> {
    void m1(T t1){
        System.out.println("t1 = " + t1);
    }
    void m2(T t2){
        System.out.println("t2 = " + t2);
    }
}

/*
同一个Example对象的m1和m2的T类型是无关的,独立的
 */
class Example{
    <T> void m1(T t1){
        System.out.println("t1 = " + t1);
    }
    <T> void m2(T t2){
        System.out.println("t2 = " + t2);
    }
}

4 类型变量的上限与泛型的擦除

4.1 <类型变量>的上限

当在声明类型变量时,如果不希望这个类型变量代表任意引用数据类型,而是某个系列的引用数据类型,那么可以设定类型变量的上限。

语法格式:

<类型变量  extends 上限>

如果有多个上限

<类型变量  extends 上限1 & 上限2>

如果多个上限中有类有接口,那么只能有一个类,而且必须写在最左边。接口的话,可以多个。

如果在声明<类型变量>时没有指定任何上限,默认上限是java.lang.Object。

案例1:定义泛型类的<类型变量>时指定上限

例如:我们要声明一个两个数算术运算的工具类,要求两个数必须是Number数字类型,并且实现Comparable接口。

package com.atguigu.limmit;

import java.math.BigDecimal;
import java.math.BigInteger;

public class NumberTools<T extends Number & Comparable<T>>{
    private T a;
    private T b;

    public NumberTools(T a, T b) {
        super();
        this.a = a;
        this.b = b;
    }

    public T getSum(){
        if(a instanceof BigInteger){
            return (T) ((BigInteger) a).add((BigInteger)b);
        }else if(a instanceof BigDecimal){
            return (T) ((BigDecimal) a).add((BigDecimal)b);
        }else if(a instanceof Byte){
            return (T)(Byte.valueOf((byte)((Byte)a+(Byte)b)));
        }else if(a instanceof Short){
            return (T)(Short.valueOf((short)((Short)a+(Short)b)));
        }else if(a instanceof Integer){
            return (T)(Integer.valueOf((Integer)a+(Integer)b));
        }else if(a instanceof Long){
            return (T)(Long.valueOf((Long)a+(Long)b));
        }else if(a instanceof Float){
            return (T)(Float.valueOf((Float)a+(Float)b));
        }else if(a instanceof Double){
            return (T)(Double.valueOf((Double)a+(Double)b));
        }
        throw new UnsupportedOperationException("不支持该操作");
    }

    public T getSubtract(){
        if(a instanceof BigInteger){
            return (T) ((BigInteger) a).subtract((BigInteger)b);
        }else if(a instanceof BigDecimal){
            return (T) ((BigDecimal) a).subtract((BigDecimal)b);
        }else if(a instanceof Byte){
            return (T)(Byte.valueOf((byte)((Byte)a-(Byte)b)));
        }else if(a instanceof Short){
            return (T)(Short.valueOf((short)((Short)a-(Short)b)));
        }else if(a instanceof Integer){
            return (T)(Integer.valueOf((Integer)a-(Integer)b));
        }else if(a instanceof Long){
            return (T)(Long.valueOf((Long)a-(Long)b));
        }else if(a instanceof Float){
            return (T)(Float.valueOf((Float)a-(Float)b));
        }else if(a instanceof Double){
            return (T)(Double.valueOf((Double)a-(Double)b));
        }
        throw new UnsupportedOperationException("不支持该操作");
    }
}

测试类

package com.atguigu.limmit;

public class NumberToolsTest {
    public static void main(String[] args) {
        NumberTools<Integer> tools = new NumberTools<Integer>(8,5);
        Integer sum = tools.getSum();
        System.out.println("sum = " + sum);
        Integer subtract = tools.getSubtract();
        System.out.println("subtract = " + subtract);
    }
}
案例2:定义泛型方法的<类型变量>时指定上限

我们编写一个数组工具类,包含可以给任意对象数组进行从小到大排序,调用元素对象的compareTo方法比较元素的大小关系。要求数组的元素类型必须是java.lang.Comparable接口类型。

package com.atguigu.limmit;

public class MyArrays {
    public static <T extends Comparable<T>> void sort(T[] arr){
        for (int i = 1; i < arr.length; i++) {
            for (int j = 0; j < arr.length-i; j++) {
                if(arr[j].compareTo(arr[j+1])>0){
                    T temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
    }
}

测试类

package com.atguigu.limmit;

import com.atguigu.generic.Circle;

import java.util.Arrays;

public class MyArraysTest {
    public static void main(String[] args) {
        int[] arr = {3,2,5,1,4};
//		MyArrays.sort(arr);//错误的,因为int[]不是对象数组

        String[] strings = {"hello","java","chai"};
        MyArrays.sort(strings);
        System.out.println(Arrays.toString(strings));

        Circle[] circles = {new Circle(2.0),new Circle(1.2),new Circle(3.0)};
//        MyArrays.sort(circles); //编译报错
    }
}

4.2 泛型擦除与泛型上限

当使用参数化类型的类或接口时,如果没有指定泛型,那么会怎么样呢?

会发生泛型擦除,自动按照最左边的第一个上限处理。如果没有指定上限,上限即为Object。

package com.atguigu.limmit;

import java.util.ArrayList;
import java.util.Collection;

public class TestErase {
    public static void main(String[] args) {
        NumberTools tools = new NumberTools(8,5);
        Number sum = tools.getSum();//自动按照Number处理
        System.out.println("sum = " + sum);
        Number subtract = tools.getSubtract();
        System.out.println("subtract = " + subtract);

        Collection coll = new ArrayList();
        coll.add("hello");
        coll.add(1);
        for (Object o : coll) {//自动按照Object处理
            System.out.println(o);
        }
    }
}

5 类型通配符

5.1 类型通配符

当我们声明一个变量/形参时,这个变量/形参的类型是一个泛型类或泛型接口,例如:Collection类型,但是我们仍然无法确定这个泛型类或泛型接口的类型变量的具体类型,此时我们考虑使用类型通配符 ? 。

package com.atguigu.wild;

import java.util.Collection;

public class MyCollections {
    public static void print(Collection<?> coll){
        for (Object o : coll) {
            System.out.println(o);
        }
    }
}

package com.atguigu.wild;

import org.junit.Test;

import java.util.Arrays;

public class TestWild {
    @Test
    public void test01(){
        MyCollections.print(Arrays.asList(1,2,3));
        MyCollections.print(Arrays.asList("hello","java","world"));
        MyCollections.print(Arrays.asList(1.5,2.6,6.3));
    }
}

5.2 类型通配符的三种使用形式

类型通配符 ? 有三种使用形式:

  • <?>:完整形式为:类名<?> 或接口名<?>,此时?代表任意类型。
  • <? extends 上限>:完整形式为:类名<? extends 上限类型> 或接口名<? extends 上限类型>,此时?代表上限类型本身或者上限的子类,即?代表 <= 上限的类型。
  • <? super 下限>:完整形式为:类名\<? super 下限类型> 或接口名\<? super 下限类型>,此时?代表下限类型本身或者下限的父类,即?代表>= 下限的类型。

案例:

声明一个集合工具类MyCollections,要求包含:

  • public static boolean different(Collection<?> c1, Collection<?> c2):比较两个Collection集合,此时两个Collection集合的泛型可以是任意类型,如果两个集合中没有相同的元素,则返回true,否则返回false。
  • public static void addAll(Collection<? super T> c1, T… args):可以将任意类型的多个对象添加到一个Collection集合中,此时要求Collection集合的泛型指定必须>=元素类型。
  • public static void copy(Collection<? super T> dest,Collection<? extends T> src):可以将一个Collection集合的元素复制到另一个Collection集合中,此时要求原Collection泛型的类型<=目标Collection的泛型类型。
package com.atguigu.wildcard;

import java.util.Collection;

public class MyCollections {
    public static boolean different(Collection<?> c1, Collection<?> c2){
        return c1.containsAll(c2) && c2.containsAll(c1);
    }

    public static <T> void addAll(Collection<? super T> c1, T... args){
        for (int i = 0; i < args.length; i++) {
            c1.add(args[i]);
        }
    }

    public static <T> void copy(Collection<? super T> dest,Collection<? extends T> src){
        for (T t : src) {
            dest.add(t);
        }
    }

}

测试类

package com.atguigu.wildcard;

import java.util.ArrayList;
import java.util.Collection;

public class MyCollectionsTest {
    public static void main(String[] args) {
        Collection<Integer> c1 = new ArrayList<Integer>();
        MyCollections.addAll(c1,1,2,3,4,5);
        System.out.println("c1 = " + c1);

        Collection<String> c2 = new ArrayList<String>();
        MyCollections.addAll(c2,"hello","java","world");
        System.out.println("c2 = " + c2);

        System.out.println("c1 != c2 " + MyCollections.different(c1, c2));

        Collection<Object> c3 = new ArrayList<>();
        MyCollections.copy(c3,c1);
        MyCollections.copy(c3,c2);
        System.out.println("c3 = " + c3);
    }
}

5.3 泛型变量T与通配符?区别

1、T可以单独使用,而?必须依赖于泛型类或泛型接口使用
package com.atguigu.generic.wild;

import java.util.ArrayList;

public class TestDifferent1 {
    public static <T> void test1(T t){
        System.out.println(t);
    }

//    public static void test2(? t){//错误
    public static void test2(ArrayList<?> list){
        System.out.println(list);
    }
}

2、T只能在声明时指定上限,?可以指定上限和下限
package com.atguigu.generic.wild;

import java.util.ArrayList;

public class TestDifferent2 {
    public static <T extends Number> void test1(T t){
        System.out.println(t);
    }
    public static void test2(ArrayList<? extends Number> list){
        System.out.println(list);
    }
    public static void test3(ArrayList<? super Number> list){
        System.out.println(list);
    }
}

3、同一个方法中多个T代表相同的类型,多个?没有关联性
package com.atguigu.generic.wild;

import java.util.Collection;

public class TestDifferent3 {
    public static <T> void test1(Collection<T> c1, Collection<T> c2){
        c1.addAll(c2);
        //c1和c2的<T>是同一个类型
    }
    public static <T> void test2(Collection<? super T> c1, Collection<? extends T> c2){
        c1.addAll(c2);
        //c1和c2的<T>是同一个类型
    }
    public static void test3(Collection<?> c1, Collection<?> c2) {
//        c1.addAll(c2);//报错
        //c1和c2的<?>没有关联
    }
}

4、T是可以确定的类型,?是不能确定的类型
package com.atguigu.generic.wild;

import java.util.Arrays;
import java.util.Collection;

public class TestDifferent4 {
    public static <T> void test1(Collection<T> coll,T t){
        coll.add(t);//coll可以添加T或T的子类对象
        for (T element : coll) {
            System.out.println(element);
        }
    }

    public static void test2(Collection<?> coll){
//        coll.add("hello");
//        coll.add(1);
//        coll.add(1.0);
        /*
        上面所有添加操作都报错。
        为什么?
        因为<?>表示未知的类型,集合的元素是不确定的,那么添加任意类型对象都有风险。

        void add(E t)方法无法正常使用
        因为此时E由?表示,即表示直到add方法被调用时,E的类型仍然不确定,所以该方法无法正常使用
         */

        coll = Arrays.asList("hello","java","world");
        for (Object o : coll) {
            System.out.println(o);
        }
    }

    public static void test3(Collection<? extends Number> coll){
//        coll.add(1);
//        coll.add(1.0);
//        coll.add("hello");
        /*
        上面所有添加操作都报错。
        为什么?
        因为<?>表示未知的类型,代表<=Number的任意一种

        void add(E t)方法无法正常使用
        因为此时<E>由<? extends Number>表示,即表示直到add方法被调用时,E的类型仍然不确定,所以该方法无法正常使用。它可以是<=Number的任意一种类型。
         */

        coll = Arrays.asList(1,2,3.0);
        for (Object o : coll) {
            System.out.println(o);
        }
    }

    public static void test4(Collection<? super Number> coll){
        coll.add(1);
        coll.add(1.0);
//        coll.add("hello");
        /*
        前两个可以,最后一个不行
        <? super Number>代表>=Number类型。最小可能是Number。
        //可以添加Number对象或Number子类对象
         */
    }

    public static void test5(Collection coll){
        //coll添加任意类型的对象都可以
        coll.add(1);
        coll.add(1.0);
        coll.add("hello");
        for (Object o : coll) {
            System.out.println(o);
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1468526.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言中的字体背景颜色汇总

客官请看效果 客官请看代码 #include <stdio.h> #include <stdlib.h> #include <windows.h>int main() {int i;for (i 0; i < 254; i) {SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), i); // 设置当前文本颜色为循环变量对应的颜色printf(…

Maven - 代码混淆proguard-maven-plugin vs 代码加密classfinal

文章目录 proguard-maven-plugin 代码混淆官网地址入门小结 ClassFinal 代码加密介绍Gitee项目模块说明功能特性环境依赖使用说明下载加密maven插件方式无密码模式机器绑定启动加密后的jartomcat下运行加密后的war 版本说明协议声明 classfinal实战工程pom编译打包配置文件运行…

Dear ImGui的UE5.3集成实践

Dear ImGui一直较为火热&#xff0c;这是一个调试使用并且可以响应快速迭代的Gui库&#xff0c;甚至可以做到在任何代码块中调用API即显示。如果你想更多的了解一下可访问其官方网站&#xff1a;https://www.dearimgui.org/ 那么本文就来在UE5中尝试踩坑使用它。 UE4.26版本 …

LangChain Agent v0.2.0简明教程 (上)

快速入门指南 – LangChain中文网 langchain源码剖析系列课程 九天玩转Langchain! 1. LangChain是什么2. LangChain Expression Language (LCEL)Runnable 接口3. Model I/O3.1 Prompt Templates3.2 Language Model3.3 Output ParsersUse case(Q&A with RAG)1. LangChain…

手把手写深度学习(22):视频数据集清洗之过滤静态/运动程度低的数据

手把手写深度学习(0)&#xff1a;专栏文章导航 前言&#xff1a;当我们训练自己的视频生成模型时&#xff0c;现在大部分基于扩散模型架构都差不多&#xff0c;关键点在数据上&#xff01;视频数据的预处理远远比图像数据复杂&#xff0c;其中有一点是如果静态数据、运动程度低…

Python奇幻之旅(从入门到入狱高级篇)——面向对象进阶篇(下)

目录 引言 3. 面向对象高级和应用 3.1. 继承【补充】 3.1.1. mro和c3算法 c3算法 一句话搞定继承关系 3.1.2. py2和py3区别 3.3. 异常处理 3.3.1. 异常细分 3.3.2. 自定义异常&抛出异常 3.3.3. 特殊的finally 3.4. 反射 3.4.1. 一些皆对象 3.4.2. import_modu…

一元函数微分学——刷题(18

目录 1.题目&#xff1a;2.解题思路和步骤&#xff1a;3.总结&#xff1a;小结&#xff1a; 1.题目&#xff1a; 2.解题思路和步骤&#xff1a; 遇到绝对值函数&#xff0c;需要把它转化为分段函数&#xff0c;从而更加方便求导数&#xff1a; 3.总结&#xff1a; 遇到绝对…

算法沉淀——动态规划之斐波那契数列模型(leetcode真题剖析)

算法沉淀——动态规划之斐波那契数列模型 01.第 N 个泰波那契数02.三步问题03.使用最小花费爬楼梯04.解码方法 动态规划&#xff08;Dynamic Programming&#xff0c;简称DP&#xff09;是一种通过将原问题分解为相互重叠的子问题并仅仅解决每个子问题一次&#xff0c;将其解存…

Linux日志轮替

文章目录 1. 基本介绍2. 日志轮替文件命名3. logrotate 配置文件4. 把自己的日志加入日志轮替5. 日志轮替机制原理6. 查看内存日志 1. 基本介绍 日志轮替就是把旧的日志文件移动并改名&#xff0c;同时建立新的空日志文件&#xff0c;当旧日志文件超出保存的范围之后&#xff…

深度学习基础(四)医疗影像分析实战

之前的章节我们初步介绍了卷积神经网络&#xff08;CNN&#xff09;和循环神经网络&#xff08;RNN&#xff09;&#xff1a; 深度学习基础&#xff08;三&#xff09;循环神经网络&#xff08;RNN&#xff09;-CSDN博客文章浏览阅读1.2k次&#xff0c;点赞17次&#xff0c;收…

window: C++ 获取自己写的dll的地址

我自己用C写了一个插件,插件是dll形式的,我的插件式在dll的目录下有个config文件夹,里面是我用json写的插件配置文件,当插件运行的时候我需要读取到json配置文件,所有最重要的就是如何获取dll的路径. 大概就是这么个结构, 我自己封装了一个函数.只适用于window编程,因为里面用…

个人博客系列-前端部署-创建框架(4)

项目环境介绍 Vue3 Vite TypeScript 服务器&#xff1a;阿里云contos node版本&#xff1a;v18.18.2 npm版本&#xff1a;v10.2.4 执行下面一行命令&#xff0c;创建vue3框架 npm create vuelatest修改端口&#xff1a;9528&#xff0c; 此步骤可以忽略&#xff08;使用默…

十三、集合进阶——双列集合

集合进阶——双列集合 13.1 双列集合的特点13.2 Map集合13.2.1 Map集合常用的API13.2.2 Map的遍历方式 13.3 HashMap13.4 LinkedHashMap13.5 TreeMap13.6 源码解析HashMap源码解读TreeMap源码解读 13.7 可变参数13.8 Collections13.9综合练习 13.1 双列集合的特点 双列集合一次…

【动态规划专栏】动态规划:似包非包---不同的二叉树

本专栏内容为&#xff1a;算法学习专栏&#xff0c;分为优选算法专栏&#xff0c;贪心算法专栏&#xff0c;动态规划专栏以及递归&#xff0c;搜索与回溯算法专栏四部分。 通过本专栏的深入学习&#xff0c;你可以了解并掌握算法。 &#x1f493;博主csdn个人主页&#xff1a;小…

SmartX 携手 openGauss 社区发布联合方案评测与性能最佳实践

近日&#xff0c;北京志凌海纳科技有限公司&#xff08;以下简称 “SmartX”&#xff09;携手 openGauss 社区完成了 openGauss 数据库基于 SmartX 超融合平台&#xff08;SMTX OS&#xff09;和 SmartX 分布式存储平台&#xff08;SMTX ZBS&#xff09;的性能测试和调优。 结果…

【C++】模板初阶 | 泛型编程 | 函数模板 | 类模板

目录 1. 泛型编程 2. 函数模板 2.1 函数模板概念 2.2 函数模板格式 2.3 函数模板的原理 2.4 函数模板的实例化 2.5 模板参数的匹配原则 3. 类模板 3.1 类模板的定义格式 3.2 类模板的实例化 【本节目标】 1. 泛型编程 2. 函数模板 3. 类模板 1. 泛型编程 如何实现一…

C语言调试

目录 一.Debug和Release介绍 二.Windows环境调试介绍 三.窗口介绍 &#xff08;1&#xff09;自动窗口和局部变量窗口 &#xff08;2&#xff09;监视窗口 &#xff08;3&#xff09;调用堆栈 &#xff08;4&#xff09;查看汇编信息 &#xff08;5&#xff09;查看寄存…

Java零基础 - 算术运算符

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一个人虽可以走的更快&#xff0c;但一群人可以走的更远。 我是一名后…

Win11网络连接选项和蓝牙选项突然消失的解决办法

在设置或者开始栏里搜索“网络重置” 打开网络重置&#xff1a; 然后点击立即重置&#xff0c;之后按照系统提示操作即可

Mybatis总结--传参二

#叫做占位符 Mybatis是封装的JDBC 增强版 内部还是用的jdbc 每遇到一个#号 这里就会变为&#xff1f;占位符 一个#{}就是对应一个问号 一个占位符 用这个对象执行sql语句没有sql注入的风险 八、多个参数-使用Param 当 Dao 接口方法有多个参数&#xff0c;需要通过名称使…