跳转至

Inheritance and Polymorphism⚓︎

2544 个字 192 行代码 预计阅读时间 15 分钟

Inheritance⚓︎

继承(inheritance) 是面向对象设计方法论的重要组成部分,是一种将一个类的行为或实现定义为另一个类的超集的能力。通过继承,可以在类之间分享字段和方法。而在 Java 中,继承自然是一个关键的技术。

继承的语法如下:

class ThisClass extends SuperClass {
    // class body
}
  • 继承与构造函数 (ctor)

    • 将继承特性视为嵌入的对象
    • 子类对象的一部分就是超类对象
    • 因此超类的那部分必须在子类初始化之前进行初始化,也就是说要先构造好基类
    • 如果没有向基类显式传递参数,那么就会调用默认构造函数
    • 对于带参的超类构造函数,使用 super 关键字来调用这个构造函数并传参
  • 没有名称隐藏(name hiding):Java 允许子类定义和超类名称相同但参数列表不同的方法,此时这个方法不会覆盖超类的同名方法

  • 不会被继承的东西:

    • 构造函数
    • 私有数据被隐藏,但仍然存在
  • 初始化和类加载

    • TBD
  • 向上转型(upcasting):

    • 将对象句柄当作其基类型的句柄来处理
    • 子类和超类的关系为:新类是现有类的一种类型
  • 方法调用绑定(method call binding):连接方法调用与方法体,应调用哪个函数?

    • 静态绑定(static binding):按代码调用函数
    • 动态绑定(dynamic binding):调用对象的函数
  • 覆盖(override)

    • 当超类和子类的方法名和函数均相同时,子类方法会覆盖超类方法
    • 对字段、构造函数和私有方法不适用

Abstract Functions and Abstract Classes⚓︎

  • 抽象类(abstract class) 是为了创建一个对所有从它派生的类的公共接口
  • 抽象方法(abstract method) 是不完整的,只有声明,没有方法体;包含抽象方法的类就是抽象类
  • 接口(interface):完全的抽象类

    • 接口中的所有方法都是 public
    • 接口中所有的数据成员都是 public static final
    • 定义接口

      public interface InterfaceName extends BaseInterfaces
      
    • 从接口继承

      class ClassName implements interfaces
      
    • 接口可作为类使用

    • 一个接口可以继承自多个接口,但不能继承自类
    • 一个类可实现多个接口
  • default:接口可以拥有包含函数体的“默认”函数

    public interface Greeting {
        // 普通抽象方法
        void sayHello(String name);
    
        // default 方法:带默认实现,实现类可复用也可覆盖
        default void sayHi(String name) {
            System.out.println("Hi, " + name + " (from default method)");
        }
    }
    
    class EnglishGreeting implements Greeting {
        @Override
        public void sayHello(String name) {
            System.out.println("Hello, " + name);
        }
    }
    
  • 默认方法和子类化

    public interface Parent {
        public void message(String body);
        public default void welcome() { message("Parent: Hi!"); }
        public String getLastMessage();
    }
    
    public interface Child extends Parent {
        public default void welcome() { message("Child: Hi!"); }
    }
    
    • 更深层的情况:

  • 多继承(multiple inheritance):接口支持多重继承,可能会遇到两个接口都提供了具有相同签名的默认方法的情况

    public interface Jukebox {
        public default String rock() { return "... all over the world!"; }
    }
    public interface Carriage {
        public default String rock() { return "... from side to side"; }
    }
    public class MusicalCarriage implements Carriage, Jukebox {
        @Override
        public String rock() { return Carriage.super.rock(); }
    }
    
  • 三条法则:

    1. 任何类都优于任何接口。所以如果超类链中有一个带有方法体或抽象声明的函数,我们可以完全忽略接口。
    2. 子类型优于超类型。如果我们有两个接口抢着提供默认方法,并且其中一个接口扩展另一个接口,此时子类获胜。
    3. 没有规则 3。如果前两条规则不能给我们答案,子类必须实现该方法或声明它为抽象。
  • 接口中的静态方法

    • 属于接口本身,不被实现类继承,只能通过接口名调用,必须带方法体
    • 默认 public,不允许写 abstractdefaultsynchronized
    • 不能被实现类覆盖(因为压根不属于实现类)
    例子
    public interface MathUtils {
        int add(int a, int b); // 普通抽象方法
        static int max(int a, int b) { // 静态方法:工具方法,直接挂在接口上
            return a >= b ? a : b;
        }
    }
    class Demo implements MathUtils {
        @Override
        public int add(int a, int b) {
            return a + b;
        }
        public static void main(String[] args) {
            // 1. 抽象方法必须通过实例调用
            Demo d = new Demo();
            System.out.println(d.add(3, 4));          // 7
            // 2. 静态方法只能通过接口名调用
            System.out.println(MathUtils.max(5, 2));  // 5
            // 3. 以下写法均编译错误
            // d.max(5, 2);      // 错误:静态方法不属于实现类
            // Demo.max(5, 2);   // 错误
        }
    }
    

POJO and Records⚓︎

  • POJO(简单老式 Java 对象 (plain old Java object))是一种设计约定 / 编程风格“除了业务字段和普通的 getter/setter,不依赖任何特定框架的接口、注解或继承”
  • POJO = 只有私有字段 + 无参构造 + getter/setter + 不继承 / 实现框架类的纯净对象
  • 2005 Gavin King 提出 POJO 概念,倡导“业务对象应该干净,框架功能通过配置或 AOP 织入”,随后 HibernateSpring 崛起,POJO 成为主流
例子
public final class Point {
    private final int x;
    private final int y;
    public Point(int x, int y) { this.x = x; this.y = y; }
    public int x() { return x; }
    public int y() { return y; }
    @Override public boolean equals(Object o) { ... }
    @Override public int hashCode() { ... }
    @Override public String toString() { ... }
}

字段都是 final,类也是 final


记录(record):

record Point(int x, int y) {}
  • 编译器自动帮你生成:
    • 两个 private final 字段x , y
    • 全参构造器 Point(int x, int y)
    • 只读访问器(不叫 getX(),就叫 x()
    • equalshashCodetoString(按字段顺序实现)
    • 类本身被 final 修饰,不能再继承
  • 记录是一种“数据载体”类的语法糖(从 Java 14 开始引入)
  • 帮你少写样板代码,专门用来干净地、不可变地保存一组值
  • 追加自定义:

    record Point(int x, int y) {
        // 1. 自定义构造器(必须最终调用自动生成的全参构造)
        public Point {
            if (x < 0 || y < 0) throw new IllegalArgumentException();
        }
        // 2. 额外方法
        public double distance(Point p) {
            return Math.sqrt(Math.pow(this.x - p.x, 2) + Math.pow(this.y - p.y, 2));
        }
        // 3. 实现接口
        public static final Comparator<Point> BY_X = Comparator.comparingInt(Point::x);
    }
    
    • 不能显式再声明字段(只能追加 static 字段)
    • 不能继承别的类(且已经隐式 final
  • 适用场景:

    • DTO / VO(接口返回体、MapStruct 映射)
    • 复合主键、坐标、经纬度、RGB 值等“小对象”
    • 函数式代码里需要快速组合 / 解构的数据块
    • 语义清晰,线程安全,自动equals /hashCode / toString,是 Java 走向现代简洁语法的重要一步

Enums⚓︎

语法:

enum Season { WINTER, SPRING, SUMMER, FALL }
  • 运算
  • 继承
  • Java enum 是一种类
  • enum 中的常量项是这种类的固定对象
    • enum 不能 new 对象
  • enum 可以有成员变量和成员函数
  • enum 可以有构造函数,对象创建时可以指定构造函数参数
  • 对象创建时可以声明匿名子类
  • 用途:

    • 单件模式:enum 的对象在类装载时创建,保证其唯一性
    • 命令驱动模式:enum 可以方便地从字符串转换成枚举量,从而展开进一步的运算
  • valueOf()

    • Java 为每个枚举自动生成的静态工具方法,用来把字符串转成对应的枚举常量
    enum Color { RED, GREEN, BLUE }
    Color c = Color.valueOf("RED");  // 返回 Color.RED
    System.out.println(c);           // RED
    
    enum Op {
        foward() {
            void run() {System.out.println("foward");}
        }
        backward() {
            void run() {System.out.println("backward");}
        }
        void run() {}
    }
    Op.valueOf(in.read()).run();
    

Inner Classes⚓︎

  • 可以将一个类的定义放在另一个类的定义里面
  • 方法内定义的类
  • 方法内部作用域中定义的类
  • 匿名内部类

    • 实现接口
    • 扩展带有非默认构造函数的类
    • 执行字段初始化
    • 通过实例初始化执行构造
  • 作为成员,内部类可访问外部类的任何东西

  • 覆盖内部类

Lambda Expression⚓︎

  • lambda 表达式是一段可以传递的代码块,以便稍后单次或多次执行
  • lambda 表达式是一种值,能够存储在变量中并传递给函数
例子

利用比较器 (comparator) 排序:

import java.util.*;

public class LambdaExample1 {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Charlie", "Alice", "Bob");
        // Before Java 8
        Collections.sort(names, new Comparator<String>() {
            @Override
            public int compare(String a, String b) {
                return a.compareTo(b);
            }
        });
        System.out.println("Sorted (old way): " + names);
        // With Lambda
        Collections.sort(names, (a, b) -> a.compareTo(b));
        System.out.println("Sorted (lambda): " + names);
    }
}

使用 Predicate 过滤:

import java.util.*;
import java.util.function.Predicate;

public class LambdaExample2 {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);

        // Define a predicate using a lambda
        Predicate<Integer> isEven = n -> n % 2 == 0;

        // Use the predicate to filter numbers
        for (Integer n : numbers) {
            if (isEven.test(n)) {
                System.out.println(n + " is even");
            }
        }
    }
}
BinaryOperator<Long> add = (x,y) -> x+y;
System.out.println(add.apply(100L, 200L));

BinaryOperator<Long> addExp = (Long x,Long y) -> x+y;
System.out.println(addExp.apply(100L, 200L));
  • 左边的三种参数形态可以和右边的三种代码形态交叉组合成一个 lambda 表达式

    ->
    () x + y
    x {;}
    (x, y) {; return x}

Closure⚓︎

  • 闭包(closure) 是由一个函数及其从周围作用域捕获的自由变量组成的组合

    • 函数:一段可执行的代码块
    • 自由变量:在函数内部使用但定义在函数外部的变量
    • 捕获:函数“记住”变量的值(或引用,即使在原始作用域消失后也是如此
  • 简而言之:闭包是一个与其环境捆绑在一起的函数

  • 闭包作为匿名类

    public class ClosureExample {
        public static void main(String[] args) {
            String greeting = "Hello"; // free variable
    
            Runnable r = new Runnable() {
                @Override
                public void run() {
                    System.out.println(greeting + " World");
                }
            };
            r.run();
        }
    }
    
  • 闭包作为 lambda 表达式

    public class ClosureExample {
        public static void main(String[] args) {
            String greeting = "Hello"; // free variable
            Runnable r = () -> System.out.println(greeting + " World");
            r.run();
        }
    }
    
  • Java 对闭包的限制

    • Java 只捕获 final 或实际上是 final 的变量(意味着变量在被捕获后不能重新赋值)

      int x = 10;
      Runnable r = () -> System.out.println(x);
      // x++; compile error, because `x` must be effectively final
      
    • Java 捕获变量的值,而不是变量引用本身

      • JavaScript 的闭包可以直接修改外部变量

Functional Interface⚓︎

  • 函数式接口(functional interface):有一个函数的接口

    interface Func {
        void ff();
    }
    
    public class FuncInterface {
        public void app(Func f) {
            f.ff();
        }
        public static void main(String[] args) {
            FuncInterface fi = new FuncInterface();
            fi.app(()->System.out.println("Hello"));
        }
    }
    
  • 任意 lambda 表达式的类型是一个函数式接口

  • 系统库中的函数式接口

    ├── 0 参数
    │   └── Supplier<T> (返回值:T)
    ├── 1 参数
    │   ├── Predicate<T> (返回值:boolean)
    │   ├── Function<T,R> (返回值:R)
    │   ├── Consumer<T> (无返回值)
    │   ├── UnaryOperator<T> (T -> T, Function 特例)
    │   └── 基本类型专用
    │       ├── IntPredicate / LongPredicate / DoublePredicate
    │       ├── IntFunction<R> / LongFunction<R> / DoubleFunction<R>
    │       ├── IntConsumer / LongConsumer / DoubleConsumer
    │       ├── IntSupplier / LongSupplier / DoubleSupplier
    │       └── ToIntFunction<T> / ToLongFunction<T> / ToDoubleFunction<T>
    ├── 2 参数
    │   ├── BiPredicate<T,U> (返回值:boolean)
    │   ├── BiFunction<T,U,R> (返回值:R)
    │   ├── BiConsumer<T,U> (无返回值)
    │   ├── BinaryOperator<T> (T × T -> T, BiFunction 特例)
    │   └── 基本类型专用
    │       ├── ObjIntConsumer<T>
    │       ├── ObjLongConsumer<T>
    │       └── ObjDoubleConsumer<T>
    └── 应用场景...
    
    • 基础型

      • Predicate<T>:接收一个参数,返回 boolean,常用来做条件判断

        Predicate<String> isEmpty = s -> s.isEmpty();
        Predicate<Integer> atLeast5 = x-> x>5;
        public inteface Predicate<T> {
            boolean test(T t);
        }
        
      • Function<T, R>:接收一个参数,返回一个结果,常用于数据转换

        Function<String, Integer> length = s -> s.length();
        
      • Consumer<T>:接收一个参数,没有返回值,常用于执行某些操作(打印、存储等)

        Consumer<String> print = s -> System.out.println(s);
        
      • Supplier<T>:不接收参数,返回一个结果,常用于提供数据

        Supplier<Double> random = () -> Math.random();
        
    • 运算型

      • UnaryOperator<T>:继承自 Function<T, T>,接收一个参数,返回相同类型的结果,常用于自我变换

        UnaryOperator<Integer> square = x -> x * x;
        
      • BinaryOperator<T>:继承自 BiFunction<T, T, T>,接收两个类型相同的参数,返回一个相同类型的结果,常用于聚合运算(比如 max/min

        BinaryOperator<Integer> add = (a, b) -> a + b;
        BinaryOperator<Integer> addInt = (x,y) -> x+y;
        
    • 带两个参数的

      • BiPredicate<T, U>:接收两个参数,返回 boolean

        BiPredicate<String, Integer> longerThan = (s, len) -> s.length() > len;
        
      • BiFunction<T,U,R>:接收两个参数,返回一个结果

        BiFunction<Integer, Integer, String> sumToString = (a, b) -> "Sum=" + (a+b);
        
      • BiConsumer<T,U>:接收两个参数,没有返回值

        BiConsumer<String, Integer> printPair = (s, i) -> System.out.println(s + ":" + i);
        
    • 基本类型专用:Java 为了性能,提供了对 intlongdouble 的专门版本,比如:

      • IntPredicateIntFunction<R>IntConsumerIntSupplier
      • LongPredicateLongFunction<R>LongConsumerLongSupplier
      • DoublePredicateDoubleFunction<R>DoubleConsumerDoubleSupplier
      • ToIntFunction<T>ToDoubleFunction<T>ToLongFunction<T>
      • ObjIntConsumer<T>(接收一个对象和一个 int

  • 急切调用(eager call):在 Java 中,当调用形如 \(y = f(x)\) 的函数时,\(x\) 的值会在调用 \(f()\) 之前就会被求解出来。特别地,当 \(x\) 是一个调用另一个函数 \(g()\) 的表达式时,即 \(y = f(g(x))\) 时,\(g(x)\) 应该要在调用 \(f()\) 之前被执行和调用。这就是急切调用。
  • 懒惰调用(lazy call):另一种方式是将整个函数调用 \(g(x)\) 传递给 \(f()\),然后让它在 \(f()\) 的内部求解,因此叫做懒惰调用。此时 \(f()\) 接收的是函数接口而不是求解好的参数值
例子:日志系统

TBD

关键点

  • lambda 表达式是一种没有名称的方法,用于传递行为,就像它是数据一样
  • lambda 表达式看起来像这样:

    BinaryOperator<Integer> add = (x, y) -> x + y;
    
  • 函数式接口是一个具有单个抽象方法的接口,用作 lambda 表达式的类型

  • lambda 表达式使得懒惰求值成为可能
Python 的比较
  • Python lambda 表达式的结果是函数,而 Java 的是(实现了函数式接口的匿名子类的)对象
  • Python lambda 表达式的结果可以存入任何变量,而 Java 的只能存入所实现的函数式接口的变量
    • 所以 Java lambda 表达式的参数表必须符合其所实现的函数式接口中的函数

评论区

如果大家有什么问题或想法,欢迎在下方留言~