泛型相关

发布 | 2024-08-09 | JAVA

泛型的基本操作

泛型定义

泛型即“参数化类型”,就是将具体的类型变成参数化类型。在声明一个泛型时,传递的是一个类型形参(Type Parameter),在调用时传递的是一个类型实参(Type Argument)。
参考形式参数

泛型的好处

示例代码:

JavaBean:圆类型

class 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 + "]";
    }
    
}

比较器

import java.util.Comparator;

public class CircleComparator implements Comparator{

    @Override
    public int compare(Object o1, Object o2) {
        //强制类型转换
        Circle c1 = (Circle) o1;
        Circle c2 = (Circle) o2;
        return Double.compare(c1.getRadius(), c2.getRadius());
    }
    
}

测试类

public class TestGeneric {
    public static void main(String[] args) {
        CircleComparator com = new CircleComparator();
        System.out.println(com.compare(new Circle(1), new Circle(2)));
        
        System.out.println(com.compare("圆1", "圆2"));//运行时异常:ClassCastException
    }
}

那么我们在使用如上面这样的接口时,如果没有泛型或不指定泛型,很麻烦,而且有安全隐患。

因为在设计(编译)Comparator接口时,不知道它会用于哪种类型的对象比较,因此只能将compare方法的形参设计为Object类型,而实际在compare方法中需要向下转型为Circle,才能调用Circle类的getRadius()获取半径值进行比较。

使用泛型:

比较器:

class CircleComparator implements Comparator<Circle>{

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

测试类

import java.util.Comparator;

public class TestGeneric {
    public static void main(String[] args) {
        CircleComparator com = new CircleComparator();
        System.out.println(com.compare(new Circle(1), new Circle(2)));
        
//        System.out.println(com.compare("圆1", "圆2"));//编译错误,因为"圆1", "圆2"不是Circle类型,是String类型,编译器提前报错,而不是冒着风险在运行时再报错
    }
}

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

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

声明类型变量\<T>

  • 声明类或接口时,在类名或接口名后面声明类型变量,这样的类或接口称为泛型类或泛型接口
【修饰符】 class 类名<类型变量列表> 【extends 父类】 【implements 父接口们】{
    
}
【修饰符】 interface 接口名<类型变量列表> 【implements 父接口们】{
    
}

例如:
public class ArrayList<E>    
public interface Map<K,V>{
    ....
}    
  • 声明方法时,在【修饰符】与返回值类型之间声明类型变量,我们把声明(是声明不是单纯的使用)了类型变量的方法称为泛型方法
【修饰符】 <类型变量列表> 返回值类型 方法名(【形参列表】)【throws 异常列表】{
    //...
}

例如:java.util.Arrays类中的
public static <T> List<T> asList(T... a){
    ....
}

自定义泛型结构

自定义泛型类和泛型接口

在声明类或接口时,类或接口中定义某个成员时,该成员有些类型是不确定的,而这个类型需要在使用这个类或接口时才确定,可以使用泛型。

声明泛型类

语法格式:

【修饰符】 class 类名<类型变量列表> {
    
}

注意:

  • <类型变量列表>:可以是一个或多个类型变量,一般都是使用单个的大写字母表示。例如:<T><K,V> 等。
  • 当类或接口上声明了<类型变量列表>时,其中的类型变量不能用于静态成员上。

示例:

   public class GerClass<T> {
    private T obj;//成员变量使用类上定义的类型变量T
   
       public T getObj() {//实例方法使用类上定义的类型变量T
           return obj;
       }
   
       public void setObj(T obj) {
        this.obj = obj;
       }
       
       //public static void test(T t){ }       //此时类型变量T不能用在静态成员上    
   }

声明泛型接口

语法格式:

【修饰符】 interface 接口名<类型变量列表> 【implements 父接口们】{
    
}

示例:

//泛型接口
public interface GerInterface<T> {
    void show(T t);
}
  1. 泛型类和接口的子类或实现类

    泛型类和接口一样可以被继承或实现,一个类在继承父类或实现接口时分两种情况:

    • 子类或实现类明确泛型类的类型参数变量

      //定义实现类时,明确接口中声明的类型参数,此时实现类不再是泛型类
      public class GerInterfaceImpl implements GerInterface<String> {
      
          @Override
          public void show(String t) {
              System.out.println(t);
          }
      
      }
      public class User implements Comparable<User>{
          @Override
          public int compareTo(User u){
              
              return 0;
          }
      }
  • 子类不明确泛型类的类型参数变量

    //定义实现类时,实现类不明确接口中声明的类型参数,实现类仍然是泛型类
    public class GerInterfaceImpl<T> implements GerInterface<T> {
    
        @Override
        public void show(T t) {
            System.out.println(t);
        }
    
    }
    //ArrayList类实现了泛型接口,未明确泛型类型参数,依然是泛型类
    public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    {
    }

使用泛型类和接口

  • 在使用这种参数化的类与接口创建对象时,我们需要指定泛型变量的实际类型参数(必须是引用数据类型)

       public static void main(String[] args) {
           //使用泛型类或接口时,明确泛型参数类型为String
           GerInterface<String> gi = new GerInterfaceImpl<String>();
           //gi.show(123);//泛型确定了String,这里编译失败
           gi.show("hello");
       }
  • 指定泛型实参时,必须左右两边类型参数一致。JDK1.7后支持简写形式,右边类型参数可以省略:

    GerInterface<String> gi = new GerInterfaceImpl<>();//省略右边泛型类型
  • 当使用参数化类型的类或接口时,如果没有指定泛型,相当于Object类型。

    //实现类确定了泛型类型
    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 c){//参数类型确定
            return Double.compare(radius,c.radius);
        }
    }
       //类型擦除:
       public class CircleComparator implements Comparator{
           @Override
           public int compare(Object o1, Object o2) {
               //未指定泛型类型,默认为Object,使用时还要强制类型转换
               Circle c1 = (Circle) o1;
               Circle c2 = (Circle) o2;
               return Double.compare(c1.getRadius(), c2.getRadius());
           }
       }

使用泛型类:

public class TestGeneric{
 public static void main(String[] args) {
     //语文老师使用时:
     Student<String> stu1 = new Student<String>("张三", "良好");

     //数学老师使用时:
     //Student<double> stu2 = new Student<double>("张三", 90.5);//错误,必须是引用数据类型
     Student<Double> stu2 = new Student<Double>("张三", 90.5);

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

     //错误的指定
     //Student<Object> stu = new Student<String>();//错误的
 }
}

继承泛型类并指定类型变量:

class ChineseStudent extends Student<String>{//继承时确定了泛型类型

 public ChineseStudent() {
     super();
 }

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

}

使用泛型类的子类:

public class TestGeneric{
 public static void main(String[] args) {
     //语文老师使用时:
     ChineseStudent stu = new ChineseStudent("张三", "良好");
 }
}

自定义泛型方法

前面介绍了在定义类、接口时可以声明<类型变量>,在该类的方法和属性定义、接口的方法定义中,这些<类型变量>可被当成普通类型来用。使用泛型时,如果外界只关心某个方法,而不关心类其他的成员,那么可以只在该方法上声明泛型,方法泛型化,称为泛型方法。

语法格式:

【修饰符】 <类型变量列表> 返回值类型 方法名(【形参列表】)【throws 异常列表】{
    //...
}
  • <类型变量列表>:可以是一个或多个类型变量,一般都是使用单个的大写字母表示。例如:<K,V>等。
  • 静态方法也可以单独泛型化。区别泛型类或接口中的静态方法(不能使用泛型类或接口定义的泛型变量)。

示例:

public class GernericMethod {
    //泛型方法
    public  static <T>  T getMsg(T t){
        return t;
    }
}
public static void main(String[] args) {
    GernericMethod.getMsg("hello");
}

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

类型变量的上限

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

语法格式:

<类型变量  extends 上限>

如果有多个上限

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

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

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

class A implements Comparable {
    @Override
    public int compareTo(Object o) {
        return 0;
    }
}

public class MethodTest2 {
    //要求传入的值是Number和Comparable的孩子
    static <T extends Number & Comparable> void cc(T t) {

    }
    //要求传入的值是A的孩子
    static <T extends A> void mm(T t){
    }
    //要求传入的值是可以比较的
    public static <T extends Comparable> void show(T t) {

    }
    @Test
    public void test02() {
        Double d;
        cc(3.14);
        cc(100);
       // cc("张三");
        A a = new A();
        mm(a);
    }
    @Test
    public void test01() {
        A a = new A();
        show(a);
        Integer i;
        show(6666);
        show(3.14);
        show("张三");
    }
    
}

泛型擦除

1没有指定泛型

当使用参数化类型的类或接口时,会发生泛型擦除,自动按照最左边的第一个上限处理。如果没有指定上限,上限即为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);
        }
    }
}
泛型存在于编译时

类型通配符

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

例如:

这个学生类是一个参数化的泛型类,代码如下

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;
    }
}

<?>任意类型

例如:我们要声明一个学生数组,可以存贮任意泛型的学生。

测试类

public class TestGeneric {
    public static void main(String[] args) {
        // 语文老师使用时:
        Student<String> stu1 = new Student<String>("张三", "良好");

        // 数学老师使用时:
        // Student<double> stu2 = new Student<double>("张三", 90.5);//错误,必须是引用数据类型
        Student<Double> stu2 = new Student<Double>("张三", 90.5);

        // 英语老师使用时:
        Student<Character> stu3 = new Student<Character>("张三", 'C');
        
        Student<Object> stu4 = new Student<>("王五", new Object());
        
        Student<Object>[] arr0 = new Student[4];//只能添加stu4
        Student<?>[] arr = new Student[4];
        arr[0] = stu1;
        arr[1] = stu2;
        arr[2] = stu3;
        arr[3] = stu4;

        
    }
}

<? extends 上限>

用于设定通配符上限

例如:我们要声明一个学生管理类,这个管理类要包含一个方法,找出学生数组中成绩最高的学生对象。

要求学生的成绩的类型必须可比较大小,实现Comparable接口。

学生管理类:

class StudentService {
    //分数score的类型必须是实现了Comparable接口的
    public static Student max(Student<? extends Comparable>[] arr){
        Student<? extends Comparable> max = arr[0];
        for (int i = 0; i < arr.length; i++) {
            if(arr[i].getScore().compareTo(max.getScore())>0){
                max = arr[i];
            }
        }
        return max;
    }
}

测试类

public class TestGeneric {
    public static void main(String[] args) {
        Student<? extends Double>[] arr = new Student[3];
        arr[0] = new Student<Double>("张三", 90.5);
        arr[1] = new Student<Double>("李四", 80.5);
        arr[2] = new Student<Double>("王五", 94.5);
        
        Student<? extends Comparable> max = StudentService.max(arr);
        System.out.println(max);
    }
}

<? super 下限>

用于设定通配符下限

现在要声明一个数组工具类,包含可以给任意对象数组进行从小到大排序,只要你指定定制比较器对象,而且这个定制比较器对象可以是当前数组元素类型自己或其父类的定制比较器对象

数组工具类:

class MyArrays{
    public static <T> void sort(T[] arr, Comparator<? super T> c){
        for (int i = 1; i < arr.length; i++) {
            for (int j = 0; j < arr.length-i; j++) {
                if(c.compare(arr[j], arr[j+1])>0){
                    T temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
    }
}

例如:有如下JavaBean

class Person{
    private String name;
    private int age;
    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
    public Person() {
        super();
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "name=" + name + ", age=" + age;
    }
}
class Student extends Person{
    private int score;

    public Student(String name, int age, int score) {
        super(name, age);
        this.score = score;
    }

    public Student() {
        super();
    }

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }

    @Override
    public String toString() {
        return super.toString() + ",score=" + score;
    }
    
}

测试类

public class TestGeneric {
    public static void main(String[] args) {
        Student[] all = new Student[3];
        all[0] = new Student("张三", 23, 89);
        all[1] = new Student("李四", 22, 99);
        all[2] = new Student("王五", 25, 67);
        
        MyArrays.sort(all, new Comparator<Person>() {

            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge() - o2.getAge();
            }
        });
        
        System.out.println(Arrays.toString(all));
        
        MyArrays.sort(all, new Comparator<Student>() {

            @Override
            public int compare(Student o1, Student o2) {
                return o1.getScore() - o2.getScore();
            }
        });
        System.out.println(Arrays.toString(all));
    }
}

© 著作权归作者所有

本文由 趣代码Blog 创作,采用 知识共享署名4.0 国际许可协议进行许可,本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。

评论关闭