泛型概述

泛型概述

Author: Msuenb

Date: 2023-02-20


所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类 型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如, 继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。

Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生 ClassCastException异常。同时,代码更加简洁、健壮。

泛型类与泛型接口

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

  • Collection 集合相关类型

    ArrayList<Integer> list = new ArrayList<>();	
    // 指定泛型为 Integer
    for (int i = 0; i < 5; i++) {
        list.add(i);
    }
    
    Iterator<Integer> iterator = list.iterator();
    while (iterator.hasNext()) {
        System.out.println(iterator.next());
    }
    
  • Comparable 接口

    class Person implements Comparable<Person>{	
        // 指定泛型为 Person
        String name;
        int age;
    
        @Override
        public int compareTo(Person o) {
            return this.age - o.age;
        }
    }
    

自定义泛型类与泛型接口

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

语法格式:

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

【修饰符】 interface 接口名<类型变量列表> 【extends 父接口】 {}
  • 自定义泛型类:

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

    class Student<T> {
        private String name;
        private T score;
    
        public Student() {
        }
    
        public Student(String name, T score) {
            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='" + name + '\'' + ", score=" + score ;
        }
    }
    
    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);//错误,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>();//错误的
    }
    
  • 自定义泛型接口

    案例需求:

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

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

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

    • 两个Integer整数相加及最大值,
      • 相加结果用Long表示
      • 返回两个整数中更大的那个,如果一样大,就返回第1个
    • 两个String相加及最大值,
      • 相加结果仍然是String,
      • 返回两个字符串中更长的字符串,如果一样长,就返回第1个
    interface Calculator<T, R> {
        R calculator(T t1, T t2);
        T max(T t1, T t2);
    }
    
    @Test
    public void test01() {
        Calculator<Integer, Long> calculator = new Calculator<Integer, Long>() {
            @Override
            public Long calculator(Integer t1, Integer t2) {
                return (long) t1 + t2;
            }
    
            @Override
            public Integer max(Integer t1, Integer t2) {
                return t1 > t2 ? t1 : t2;
            }
        };
    
        Long sum = calculator.calculator(Integer.MAX_VALUE, Integer.MAX_VALUE);
        System.out.println("sum = " + sum);
        Integer max = calculator.max(2, 9);
        System.out.println("max = " + max);
    }
    
    @Test
    public void test02() {
        Calculator<String, String> calculator = new Calculator<String, String>() {
            @Override
            public String calculator(String t1, String t2) {
                return t1 + t2;
            }
    
            @Override
            public String max(String t1, String t2) {
                return t1.length() >= t2.length() ? t1 : t2;
            }
        };
    
        String sum = calculator.calculator("hello", "world");
        System.out.println("sum = " + sum);
        String max = calculator.max("hello", "world");
        System.out.println("max = " + max);
    }
    

泛型注意事项

  1. <类型变量列表>:可以是一个或多个类型变量,<E, T, R>

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

    class AAA<T> {
        static T field;	// 不能用于静态属性类型
    	static T method(T t) {}	// 不能用于静态方法 返回值/参数类型
    }
    
  3. 在同一个类或接口中同一个类型变量代表同一种数据类型

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

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

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

      ArrayList<Object> list = new ArrayList<Sting>();	// 错误 这并不是多态
      
    2. JDK1.7支持自动类型推断的简写形式

      ArrayList<String> list = new ArrayList<>();
      

    泛型如果不指定,将被擦除,泛型对应的类型均按Object处理,但不等价于指定为Object。

    ArrayList list = new ArrayList(); // 泛型擦除 编译时不会类型检查

  6. 不能使用new E[]。但是可以:E[] elements = (E[])new Object[capacity];

  7. 子类继承父类时,子接口继承泛型父接口、实现类实现泛型父类接口时

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

      // ChineseStudent不再是泛型类
      public class ChineseStudent extends Student<String>{}	// 继承时指定实际类型
      
      public class Rectangle implements Comparable<Rectangle>
      
    2. 用子类/子接口的类型变量指定父类或父接口的类型变量、此时子类、子接口、实现类仍然是泛型类或泛型接口

      public interface Iterable<T>
      public interface Collection<E> extends Iterable<E>	// 字母可以不一样
      public class ArrayList<E> extends AbstractList<E> implements List<E>
      

泛型方法

方法也可以被泛型化,不管它所在的类是不是泛型类。在泛型方法中可以定义泛型参数,参数的类型就是传入数据的类型。

泛型方法格式:

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

自定义泛型方法

在方法的返回值类型前面为这个方法单独声明泛型的<类型变量>,这个方法可以是静态方法,也可以是非静态方法。

import java.util.Collection;

public class MyCollections {
    public static <T> void addAll(Collection<T> coll, T... args){
        for (T t : args) {
            coll.add(t);
        }
    }
}
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);
    }
}

泛型类与泛型方法区别

  1. <泛型变量>生命的位置不同

    • 声明方法时,在修饰符与返回值类型之间声明类型变量,把声明(而不是使用)了类型变量的方法称为泛型方法

      class AAA<T> {
      	static T method(T t) {}	// 不是泛型方法
      }
      
    • 声明类或接口时,在类名或接口名后面声明类型变量,把这样的类或接口称为泛型类或泛型接口

  2. <泛型变量>使用范围不同

    • 在类或接口名后面声明的<范型变量>在整个类中都可以使用,而且同名的范型变量代表的类型是相同的
    • 在方法返回值类型前声明的<泛型变量>仅限于当前方法使用,和其他方法同名的<泛型变量>代表的类型是无关的
    // 同一个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);
        }
    }
    

泛型上限与泛型擦除

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

语法格式:<类型变量 extends 上限>,如果有多个上限:<类型变量 extends 上限1 & 上限2>

  • 定义泛型类的<泛型变量>时指定上限

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

    class NumberTools<T extends Number & Comparable<T>> {
        private T a;
        private T b;
    
        public NumberTools(T a, T b) {
            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 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);
        }
    }
    
  • 定义泛型方法的<类型变量>时指定上限

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

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

当使用参数化类型的类或接口时,如果没有指定泛型,会发生泛型擦除,自动按照最左边的第一个上限处理。如果没有指定上限,上限即为Object。

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

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

类型通配符

当声明一个变量/形参时,这个变量/形参的类型是一个泛型类或泛型接口,但无法确定这个泛型类或泛型接口的类型变量的具体类型,此时可以使用类型通配符 ?

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

  1. <?>:此时 ? 表示任意类型
  2. <? extends Comparable>:只允许泛型为实现Comparable接口的实现类
  3. <? super Number>:只允许泛型为Number及Number父类
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);
    }
}

泛型 T 与通配符 ? 的区别

  1. T 可以单独使用,而 ? 必须依赖于泛型类或泛型接口使用

    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 只能在声明时指定上限,? 可以指定上限和下限

    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 代表相同的类型,多个 ? 没有关联性

    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 是可以确定的类型,? 是不能确定的类型

posted @ 2023-02-20 13:29  msuenb  阅读(14)  评论(0编辑  收藏  举报