科普|泛型(出现历史、定义、用法、原理)

2024-07-01  本文已影响0人  蚍蜉一生

一、泛型是什么 ,为啥会有泛型

    在泛型出现前,Java等强类型语言中方法的参数、返回值、类的成员变量、局部变量都必须是一个特定数据类型。比如:要找出Integer、Double 数组和 Float 数组中的最大值,如果没有泛型,就需要为每种类型编写单独的方法:

// 找出 Integer 数组中的最大值
    public static Integer findMax(Integer[] array) {
        Integer max = array[0];
        for (Float element : array) {
            if (element > max) {
                max = element;
            }
        }
        return max;
    }


 // 找出 Float 数组中的最大值
    public static Float findMax(Float[] array) {
        Float max = array[0];
        for (Float element : array) {
            if (element > max) {
                max = element;
            }
        }
        return max;
    }

  // 找出 Double 数组中的最大值
    public static Double findMax(Double[] array) {
        Double max = array[0];
        for (Double element : array) {
            if (element > max) {
                max = element;
            }
        }
        return max;
    }

.......

观察这些方法发现,除了方法的入参、返回值、还有关联的局部变量不一样,每个方法的运算步骤可以说是一毛一样。那如何在数据类型不一样的情况下实现思想和步骤复用呢?我们很容易想到向上转型,但向上转型会丢失类型限制,出现诸如返回值不满足类型要求,类型编译时检查失效从而导致的运行时类型转换错误等问题。
    这种情况下,泛型出现了,它既实现了方法的复用,又能对数据类型做一致性限制,如下所示:

   // 定义一个泛型方法来找出数组中的最大值
   //  <T extends Comparable<T>> 是泛型声明,声明该泛型的类型必须是Comparable<T>的子类 泛型类型为T
   //  泛型类型声明为T之后,后面使用的入参类型T[],局部变量类型、返回值类型都使用了T类型,就是说编译时候会检查这类型是否一致
    public static <T extends Comparable<T>> T findMax(T[] array) {
        T max = array[0];
        for (T element : array) {
            if (element.compareTo(max) > 0) {
                max = element;
            }
        }
        return max;
    }
类型安全限制

如上图,case1 findMax入参是Integer[],即T = Integer,它的返回值是Integer,所以赋值给i没有错;case2 中findMax入参是Float[] 即T = Float,它的返回值就是Float,那么直接赋值给Integer,就会报错,就是通过泛型这种机制实现了数据类型一致性的限制,避免了运行时类型转换出错。
    在面向对象语言中,所有方法都在类中,所以泛型不仅能够解决方法的复用,也能够解决类的成员变量、局部变量等的复用(如何用下一个章节介绍),总结下泛型的作用有两点:

1. 类和方法复用:可以使用调用者传入类型;

2. 类型限制: 一致性、类型上下界限制(子类、父类限制);

二、泛型如何使用

泛型使用遵守先声明后使用的步骤,常见的有以下几种:

2.1 泛型类

public class Box<T> {  // <T>是泛型声明 跟在类名后面
    private T content;     // 泛型使用,这里说名了 content是T类型的

    public void setContent(T content) {
        this.content = content;
    }

    public T getContent() {     // 泛型使用 这里说明 getContent 返回类型是T
        return content;
    }

    public static void main(String[] args) {
        Box<String> stringBox = new Box<>();   //  这里Box<> = Box<String> jdk7之后可以省略,编译器根据前面自动推导,通过构造函数将T类型赋值为String类型
        stringBox.setContent("Hello");         
        System.out.println("String content: " + stringBox.getContent()); // 输出:Hello

        Box<Integer> integerBox = new Box<>();
        integerBox.setContent(123);
        System.out.println("Integer content: " + integerBox.getContent()); // 输出:123
    }
}

常见的泛型类有Map、Set、List以及他们的子类。

2.2 泛型方法

public class GenericMethodExample {

    // 泛型方法,<T> 是泛型声明,需要放在返回类型之前声明 
    // T[] 是泛型使用,表明入参是T类型的数组
    public static <T> T getFirst(T[] array) {
        if (array == null || array.length == 0) {
            return null;
        }
        return array[0];
    }

    public static void main(String[] args) {
        String[] stringArray = {"Hello", "World"};
        Integer[] intArray = {1, 2, 3};

       
        // <String> 在方法前面显式指出了返回类型
        String firstString = GenericMethodExample.<String>getFirst(stringArray); 
        // intArray 和firstInt类型 隐式推断类型参数
        Integer firstInt = getFirst(intArray); 

        System.out.println("First String: " + firstString); // 输出:First String: Hello
        System.out.println("First Integer: " + firstInt); // 输出:First Integer: 1
    }
}

接下来是两个参数的泛型方法

public class DualGenericExample {


    @RequiresApi(api = Build.VERSION_CODES.N)
    // <T, U> 是泛型声明 U是返回值类型  T是一个输入参数类型
    // Function<T, U> converter 然后 T U 又传递到一个泛型接口中 当作它的两个参数的类型
    public static <T, U> U convertAndPrint(T input, Function<T, U> converter) {
        U result = converter.apply(input);
        System.out.println("Converted Result: " + result);
        return result;
    }

    @RequiresApi(api = Build.VERSION_CODES.N)
    public static void main(String[] args) {
        // 示例1:将整数转换为字符串并打印
        Integer num = 123;
        String numStr = convertAndPrint(num, new Function<Integer, String>() {
            @Override
            public String apply(Integer integer) {
                return String.valueOf(integer);
            }
        });
        // 输出:Converted Result: 123

        // 示例2:将字符串转换为其长度并打印
        String str = "Hello";
        Integer length = convertAndPrint(str, new Function<String, Integer>() {
            @Override
            public Integer apply(String s) {
                return s.length();
            }
        });
        // 输出:Converted Result: 5
    }
}

2.3 泛型接口

泛型接口允许我们在接口定义中使用类型参数,从而使接口能够处理不同类型的数据。

// 类似于泛型类
public interface Comparable<T> {
    int compareTo(T o);
}

public class GenericInterfaceExample implements Comparable<GenericInterfaceExample> {
    private int value;

    public GenericInterfaceExample(int value) {
        this.value = value;
    }

    @Override
    public int compareTo(GenericInterfaceExample other) {
        return Integer.compare(this.value, other.value);
    }

    public static void main(String[] args) {
        GenericInterfaceExample obj1 = new GenericInterfaceExample(10);
        GenericInterfaceExample obj2 = new GenericInterfaceExample(20);
        System.out.println(obj1.compareTo(obj2)); // 输出:-1(因为 10 < 20)
    }
}

2.4 泛型通配符

    在未引入泛型通配符时,泛型的限制是类型一致性限制(必须都是声明的类型T);引入通配符之后限制变松了,有三种:无界通配符<?> (无限制)上界通配符<? extends T >(必须是T类型及其子类型)和下界通配符<? super T>(必须是T类型及父类型),分别举例如下:

import java.util.List;
// 无限制的通配符,不管什么类型都可以打印,一般用于只需要读取数据,而不需要写入数据
public class UnboundedWildcardExample {
    public static void printList(List<?> list) {
        for (Object obj : list) {
            System.out.println(obj);
        }
    }

    public static void main(String[] args) {
        List<String> stringList = List.of("Hello", "World");
        List<Integer> intList = List.of(1, 2, 3);

        printList(stringList); // 可以打印 List<String>
        printList(intList);    // 可以打印 List<Integer>
    }
}
import java.util.List;
//上界通配符 限制父类,比如是Number子类的 才能使用,适用于需要读取某种类型及其子类型的数据的情况。
public class UpperBoundedWildcardExample {
    public static void printNumbers(List<? extends Number> list) {
        for (Number num : list) {
            System.out.println(num);
        }
    }

    public static void main(String[] args) {
        List<Integer> intList = List.of(1, 2, 3);
        List<Double> doubleList = List.of(1.1, 2.2, 3.3);

        printNumbers(intList);   // 可以打印 List<Integer>
        printNumbers(doubleList); // 可以打印 List<Double>
    }
}

import java.util.List;
import java.util.ArrayList;
// 下界通配符 限制子类,必须是Integer的父类才能使用该方法
// 适合写入某种类型及子类型的数据的情况,注意理解下,因为方法是限制的子类型
// 下届通配符 是限制下界,比如本例子中,是限制是必须是Integer父类才能使用该方法,这个父类是
// 使用者给的,比如Number,所以上面说 适合是写入某种类型 Number以及子类Integer写入使用。
public class LowerBoundedWildcardExample {
    public static void addNumbers(List<? super Integer> list) {
        list.add(1);
        list.add(2);
        list.add(3);
    }

    public static void main(String[] args) {
        List<Number> numberList = new ArrayList<>();
        addNumbers(numberList); // 可以向 List<Number> 添加 Integer

        List<Object> objectList = new ArrayList<>();
        addNumbers(objectList); // 可以向 List<Object> 添加 Integer
    }
}

// 类型要求一致性的场景,
public static <T> void addElement(List<T> list, T element) {
    list.add(element);
}

public static void main(String[] args) {
    List<String> stringList = new ArrayList<>();
    addElement(stringList, "Hello"); // 可以添加元素

    List<Integer> intList = new ArrayList<>();
    addElement(intList, 1); // 可以添加元素
}
// 类型要求一致的场景,通配符? 不能使用
public static void addElement(List<?> list, Object element) {
     list.add(element); // 编译错误,无法添加元素
}

总结下:泛型通配符就是从原来必须等于T 或者其他类型的限制,放松为无限制或者限制参数的父类和子类,使用更加灵活,类型安全降低。

三、泛型实现原理简介

回顾前文,泛型有两个作用:

    类型擦除(泛型擦除)+强制类型转换 是泛型实现的主要原理,具体来说就是在编译时****,Java 编译器****会****将****泛型类型参数替换为它们的非泛型上界,如果没有指定上界,则替换为 Object,并且使用强制类型转换保证类型一致,这样就实现了在方法和类的复用(可以使用各种类型)和运行时候类型一致性。`
    除此之外编译器会使用泛型信息(定义、入参、返回值等)进行类型检查类型推断,以确保代码在编译时候是类型安全的。
    所以我们可以说泛型是编译时特性,虚拟机对泛型一无所知。

四、什么时候使用泛型?

    当构建通用工具、通用算法、通用数据结构,就是需要复用的时候请考虑泛型,什么时候需要复用,就是当考虑通过规模化降低成本的时候。

上一篇下一篇

猜你喜欢

热点阅读