Java SE day3
00 分钟
2024-9-13
2024-9-18
tags
date
type
status
slug
summary
category
password
icon

泛型程序设计

初识泛型

泛型类

  • 在定义一个变量不明确用什么类型时会使用泛型类型,它会在程序编译的时候才使用
  • 不能用在静态类中,同时不能直接创建对象和数组,不支持基本类型,只支持引用类型
  • 要存放基本数据类型的值,得使用对应的包装类
  • 使用泛型类时,实例化时需要传递具体的类型参数。
  • 类型参数在编译时会被替换为具体的类型。
  • 权限修饰符 类名<T>{ },对于要使用到T类型的变量都在前面加个T
  • public class Main { public static void main(String[] args) { System.out.println("Hello world!"); Person<String,Integer,String,Integer> person=new Person<>(); person.setAge(20); person.setName("John"); person.setGender("Male"); person.setHeight(180); System.out.println(person.getAge()+" "+person.getName()+" "+person.getGender()+" "+person.getHeight()); }​ public static class Person<T,A,B,C>{ private T name; private A age; private B gender; private C height;​ public A getAge() { return age; }​ public void setAge(A age) { this.age = age; }​ public C getHeight() { return height; }​ public void setHeight(C height) { this.height = height; }​ public B getGender() { return gender; }​ public void setGender(B gender) { this.gender = gender; }​ public T getName() { return name; }​ public void setName(T name) { this.name = name; }​ }​}

泛型方法

  • 权限修饰符 <T> 返回值类型 方法名(T 参数名){ 方法体}
  • public class GenericMethodExample { // 泛型方法 public static <T> void printArray(T[] array) { for (T element : array) { System.out.println(element); } }​ public static void main(String[] args) { Integer[] intArray = {1, 2, 3}; String[] strArray = {"Hello", "World"};​ // 调用泛型方法 printArray(intArray); printArray(strArray); }}​
  • s

泛型与多态

  • 泛型接口
    • interface InterfaceName<T> { void method(T parameter);}​
    • interface Storage<T> { void add(T item); T get(int index);}​class StringStorage implements Storage<String> { private String[] items = new String[10]; private int count = 0;​ @Override public void add(String item) { items[count++] = item; }​ @Override public String get(int index) { return items[index]; }}​

泛型通配符

  • 无界通配符? —— 表示任何类型,适用于方法参数中使用。
    • public void method(List<?> list) { // 可以接受任何类型的 List}
  • 上界通配符 ? extends T —— 表示泛型类型必须是 TT 的子类。
    • public void method(List<? extends Number> list) { // 只允许 Number 及其子类的列表}
    • 读取为主:可以安全地读取列表中的元素(例如 Number 类型的元素),但你不能向列表中添加元素,因为编译器无法确定具体类型。
  • 下界通配符 ? super T —— 表示泛型类型必须是 TT 的父类。
    • public void method(List<? super Integer> list) { // 可以处理 Integer 及其父类(如 Number、Object)}
    • 写入为主:你可以向列表中添加 Integer 或其子类的值,但读取时,编译器只能保证返回的是 Object 类型,因为列表可能包含比 Integer 更高层次的父类。

泛型类型边界

  • class ClassName<T extends ClassOrInterface> { // T 必须是 ClassOrInterface 或其子类}
  • class NumberBox<T extends Number> { private T number; public NumberBox(T number) { this.number = number; } public T getNumber() { return number; } public static void main(String[] args) { NumberBox<Integer> intBox = new NumberBox<>(123); NumberBox<Double> doubleBox = new NumberBox<>(45.67); System.out.println("Integer: " + intBox.getNumber()); System.out.println("Double: " + doubleBox.getNumber()); }}
  • 这里的 T extends Number 表示泛型类型 T 必须是 Number 或其子类(如 IntegerDouble

泛型类与继承

  • 泛型类可以继承其他泛型类或普通类,并且子类可以指定自己的泛型参数,也可以直接使用父类的泛型参数。
  • // 定义一个泛型类 Parent,具有泛型参数 Tclass Parent<T> { private T data; public Parent(T data) { this.data = data; } public T getData() { return data; }}// 子类 Child 继承父类 Parent,继续使用泛型 Tclass Child<T> extends Parent<T> { public Child(T data) { super(data); }}public class Main { public static void main(String[] args) { Child<String> child = new Child<>("Hello"); System.out.println(child.getData()); // 输出: Hello }}// 子类 Child 指定 Parent 泛型类型为 Integerclass Child extends Parent<Integer> { public Child(Integer data) { super(data); }}public class Main { public static void main(String[] args) { Child child = new Child(42); // 子类的泛型类型已固定为 Integer System.out.println(child.getData()); // 输出: 42 }}

    类型擦除

    • 在 Java 泛型中,类型擦除是一个重要的概念。泛型类型信息只在编译时存在,运行时所有的泛型类型信息都会被擦除,替换为原始类型(如 Object
    • 实际上泛型知识在编译的阶段进行类型检查,程序运行的时候是不会保留泛型的具体类型信息
    • 在某一些情况下需要通过强制类型转换或生成桥接方法来保证类型的正确性(所以实际上编译器是帮我们写了个桥接方法支持重写的)
    • 局限性是,不能运行时判断类型或创建泛型数组

    函数式接口

    1. Supplier<T> 供给型函数式接口
        • Supplier 是供给型接口,表示该接口的实现是一个生产者,它会返回一个结果,而不会接受任何参数。Supplier<T> 接口的抽象方法是 get(),它返回一个 T 类型的对象
        • public class Student { public void hello() { System.out.println("Hello!"); }}// 使用 Supplier 生成 Student 对象private static final Supplier<Student> STUDENT_SUPPLIER = Student::new;public static void main(String[] args) { Student student = STUDENT_SUPPLIER.get(); // 通过 Supplier 获取 Student 对象 student.hello(); // 输出: 我是学生!}
        • Supplier<Student> 是一个函数式接口,其 get() 方法会返回一个 Student 对象。
        • Student::new 是方法引用,表示通过 Student 类的构造方法生成对象。
    1. Consumer<T> 消费型函数式接口
        • 它接受一个参数,但没有返回值。通常用于执行某个操作而不需要返回结果。Consumer<T> 接口的抽象方法是 accept(T t)
        • private static final Consumer<Student> STUDENT_CONSUMER = student -> System.out.println(student + " 被消费了!");public static void main(String[] args) { Student student = new Student(); STUDENT_CONSUMER.accept(student); // 输出: Student@hashcode 被消费了!}
        • Consumer<Student> 表示接受一个 Student 对象并对其进行消费(处理),但没有返回值。
        • Lambda 表达式 student -> System.out.println(...) 表示对 Student 对象进行输出操作。

        andThen 方法:

        andThen 方法允许将多个 Consumer 串联在一起连续执行。
    1. Function<T, R> 函数型函数式接口
        • Function<T, R> 是一个通用的转换接口,它接受一个参数并返回另一个类型的结果。通常用于数据的转换、处理等操作。Function<T, R> 接口的抽象方法是 apply(T t),它接受类型 T 的参数并返回类型 R 的结果。
        • @FunctionalInterfacepublic interface Function<T, R> { R apply(T t); // 接收一个参数并返回结果}private static final Function<Integer, String> INTEGER_TO_STRING = Object::toString;public static void main(String[] args) { String result = INTEGER_TO_STRING.apply(10); // 将 Integer 转为 String System.out.println(result); // 输出: 10}
        • 解释:
          • Function<Integer, String> 表示该函数接受一个 Integer 类型的参数,并返回一个 String 类型的结果。
          • Object::toString 是方法引用,用于将整数转换为字符串。
        • composeandThen 方法:
          • compose(Function before):在当前函数之前执行一个函数。
          • andThen(Function after):在当前函数之后执行另一个函数。
          • // compose: 将字符串的长度作为 Integer 传入下一个函数String result = INTEGER_TO_STRING.compose(String::length).apply("Hello"); // 输出: "5"// andThen: 判断转换后的字符串是否为空Boolean isEmpty = INTEGER_TO_STRING.andThen(String::isEmpty).apply(10); // 输出: false
    1. Predicate<T> 断言型函数式接口
        • Predicate<T> 是一个用于进行条件判断的函数式接口,它接受一个参数并返回一个 boolean 值。通常用于过滤和判断操作。Predicate<T> 接口的抽象方法是 test(T t),它根据传入的参数返回 truefalse
        • Function<T, R> 接口定义: @FunctionalInterfacepublic interface Function<T, R> { R apply(T t); // 接收一个参数并返回结果}示例:使用 Function<T, R> 进行类型转换 private static final Function<Integer, String> INTEGER_TO_STRING = Object::toString;public static void main(String[] args) { String result = INTEGER_TO_STRING.apply(10); // 将 Integer 转为 String System.out.println(result); // 输出: 10}
        • 解释:
          • Function<Integer, String> 表示该函数接受一个 Integer 类型的参数,并返回一个 String 类型的结果。
          • Object::toString 是方法引用,用于将整数转换为字符串。
          • composeandThen 方法:

          • compose(Function before):在当前函数之前执行一个函数。
          • andThen(Function after):在当前函数之后执行另一个函数。
          • // compose: 将字符串的长度作为 Integer 传入下一个函数String result = INTEGER_TO_STRING.compose(String::length).apply("Hello"); // 输出: "5"// andThen: 判断转换后的字符串是否为空Boolean isEmpty = INTEGER_TO_STRING.andThen(String::isEmpty).apply(10); // 输出: false
    1. 总结
      1. Java 8 提供了这四个核心的函数式接口,极大简化了基于匿名类的编程,使得代码更加简洁、易读,并且与 Lambda 表达式 结合紧密:
      2. Supplier<T>:供给型接口,用于提供数据。
      3. Consumer<T>:消费型接口,用于处理和消费数据。
      4. Function<T, R>:函数型接口,用于数据转换。
      5. Predicate<T>:断言型接口,用于条件判断。
      6. 这些接口通过 Lambda 提供了高度简洁的语法,并且通过 andThencomposeor 等方法,使得函数式编程风格在 Java 中更加自然和强大。
    上一篇
    Java 数据结构第0天
    下一篇
    常用语法知识模糊点

    评论
    Loading...