杰瑞科技汇

Java核心技术卷1基础知识9讲什么?

第9章:接口、lambda表达式与内部类

本章主要探讨Java中用于实现抽象、函数式编程和代码组织的三个关键特性。

Java核心技术卷1基础知识9讲什么?-图1
(图片来源网络,侵删)

第一部分:接口

接口是Java中一种引用数据类型,它是一系列方法的声明,但没有方法体,它规定了类必须做什么,但规定不了如何去做,这完美地体现了“面向接口编程”的思想。

接口的概念与作用

  • 抽象行为的契约:接口定义了一组方法,任何实现该接口的类都必须提供这些方法的具体实现,它像一份“契约”,承诺了类具备某种能力。
  • 实现多继承:Java不支持类的多继承(一个类不能直接继承多个类),但一个类可以实现多个接口,这弥补了Java在多继承上的不足,是一种“多实现”。
  • 解耦:当你的代码依赖于一个接口而不是具体的实现类时,代码的耦合度会降低,你可以轻松地更换接口的实现,而无需修改调用方的代码。
  • 多态:接口是实现多态的重要方式,可以将接口类型的变量引用到任何实现了该接口的对象上。

接口的定义与实现

定义一个接口: 使用 interface 关键字。

public interface Comparable {
    // 抽象方法 (在Java 7及之前,接口中所有方法都是抽象的)
    int compareTo(Object other);
}

实现一个接口: 使用 implements 关键字,一个类可以实现多个接口,用逗号隔开。

public class Employee implements Comparable, Cloneable { // 实现了两个接口
    private String name;
    private double salary;
    // 必须实现接口中的所有抽象方法
    @Override
    public int compareTo(Object otherObject) {
        Employee other = (Employee) otherObject;
        return Double.compare(this.salary, other.salary); // 按工资比较
    }
    // ... 其他方法和构造器 ...
}

接口的演化 (Java 8+ 的重要增强)

Java 8为接口带来了巨大的变化,使其功能更加强大。

Java核心技术卷1基础知识9讲什么?-图2
(图片来源网络,侵删)
特性 Java 7及之前 Java 8+
方法类型 只有抽象方法 抽象方法 + 默认方法 + 静态方法
字段 只能是 public static final 的常量 仍然是 public static final 的常量
方法体 抽象方法没有方法体 默认方法和静态方法有方法体

a) 默认方法

  • 目的:在不破坏现有实现类代码的前提下,为接口添加新的方法,这是为了解决Java API(如Collection接口)向后兼容的问题。
  • 语法:使用 default 关键字修饰。
  • 调用规则:当一个类实现多个接口,且这些接口中存在同名的默认方法时,编译器会报错,要求程序员在类中显式地覆盖这个冲突的默认方法。
public interface Resizable {
    void setWidth(int width);
    void setHeight(int height);
    // Java 8+ 默认方法
    default void setSize(int width, int height) {
        setWidth(width);
        setHeight(height);
    }
}
public class Rectangle implements Resizable {
    @Override
    public void setWidth(int width) { /* ... */ }
    @Override
    public void setHeight(int height) { /* ... */ }
    // 不需要实现 setSize(),因为它有默认实现
}

b) 静态方法

  • 目的:提供与接口相关的工具性方法,无需创建接口的实例即可调用。
  • 语法:使用 static 关键字修饰。
  • 调用:通过接口名直接调用,如 Collections.sort(...)
public interface MathUtils {
    static int add(int a, int b) {
        return a + b;
    }
}
// 调用方式
int sum = MathUtils.add(10, 20); // 直接通过接口调用

接口与抽象类的区别

这是一个非常经典和重要的面试题。

特性 接口 抽象类
继承 implements (实现) extends (继承)
构造器 没有 有,用于子类初始化
字段 只能是 public static final 任意类型 (private, protected, static, final等)
方法 Java 8+: 抽象方法、默认方法、静态方法 抽象方法、具体方法、静态方法
多继承 一个类可以实现多个接口 一个类只能继承一个抽象类
设计目的 定义能力行为 (Can do) 定义是什么 (Is a)

简单总结

Java核心技术卷1基础知识9讲什么?-图3
(图片来源网络,侵删)
  • 用接口:当你想定义一种通用的行为能力,希望任何不相关的类都能具备这种能力时。“可比较”、“可克隆”、“可序列化”。
  • 用抽象类:当你想定义一个“模板”,让多个相关的类共享代码和状态,并且它们之间有明确的“是一个”的关系时。“图形” -> “圆形”、“矩形”。

第二部分:Lambda表达式

Lambda表达式是Java 8引入的最重要的新特性之一,它允许我们将函数作为方法参数传递,极大地简化了编写匿名内部类(特别是只有一个方法的接口)的代码。

函数式接口

Lambda表达式的基础是函数式接口,一个函数式接口是指只包含一个抽象方法的接口。

  • @FunctionalInterface 注解:这是一个标记注解,用于告诉编译器该接口是一个函数式接口,如果接口中包含多于一个抽象方法,编译器会报错,虽然可以不写这个注解,但强烈建议加上,以增强代码的可读性和安全性。

经典的函数式接口示例:

接口名 抽象方法 描述
java.lang.Runnable run() 无参数,无返回值
java.util.Comparator<T> compare(T, T) 两个参数,返回int
java.util.function.Supplier<T> get() 无参数,返回T
java.util.function.Consumer<T> accept(T) 一个参数,无返回值
java.util.function.Function<T, R> apply(T) 一个参数,返回R

Lambda表达式的语法

Lambda表达式本质上是一个匿名函数,其基本语法是:

(参数列表) -> { 方法体 }

  • 参数列表:如果方法没有参数,则使用空括号 ,如果只有一个参数,可以省略括号,如 x
  • ->:Lambda操作符,读作“goes to”。
  • 方法体:如果方法体只有一行代码,可以省略大括号 和分号 ,如果这一行代码是 return 语句,则 return 关键字也要省略。

代码简化示例:

假设我们有一个 Arrays.sort() 方法,需要一个 Comparator

传统方式 (使用匿名内部类):

String[] names = {"Peter", "Paul", "Mary"};
Arrays.sort(names, new Comparator<String>() {
    @Override
    public int compare(String s1, String s2) {
        return s1.compareTo(s2);
    }
});

使用Lambda表达式:

String[] names = {"Peter", "Paul", "Mary"};
Arrays.sort(names, (s1, s2) -> s1.compareTo(s2));
  • (s1, s2):对应 compare 方法的参数列表。
  • ->:Lambda操作符。
  • s1.compareTo(s2):对应方法体,因为只有一行,所以省略了 和 return

方法引用

当Lambda表达式只是调用一个已有的方法时,可以使用方法引用,使代码更加简洁。

语法对象::实例方法类::静态方法

示例:

上面的Lambda表达式 s1.compareTo(s2) 可以用方法引用替换:

Arrays.sort(names, String::compareTo);

这行代码的含义是:“使用 String 类的 compareTo 方法来进行比较”,编译器会自动推断出参数 s1s2


第三部分:内部类

内部类是指在一个类的内部定义的类,内部类是一个编译器现象,JVM并不知道内部类的存在,编译器会将内部类编译成独立的 .class 文件(如 OuterClass$InnerClass.class)。

使用内部类的目的

  • 封装性:内部类可以访问外部类的私有成员,这使得它们可以紧密地绑定在一起,形成一个功能单元。
  • 实现多继承:通过内部类,一个类可以“继承”多个具体类或实现多个接口。
  • 编写事件处理代码:GUI编程中,事件监听器通常使用内部类来实现,代码结构清晰。

内部类的四种类型

a) 成员内部类

  • 定义在外部类的内部,与外部类的成员(字段、方法)处于同一级别。
  • 可以访问外部类的所有成员(包括私有的)。
  • 创建对象时,必须先有外部类的对象。
public class Outer {
    private int x = 10;
    class Inner {
        public void print() {
            System.out.println("x = " + x); // 访问外部类的私有成员
        }
    }
}
// 创建方式
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner(); // 或 Outer.Inner inner = new Outer().new Inner();
inner.print();

b) 静态内部类

  • 使用 static 修饰。
  • 它只属于外部类本身,而不属于外部类的某个实例。
  • 不能访问外部类的非静态成员(实例成员),因为静态内部类不持有外部类的引用。
  • 创建时不需要外部类的对象。
public class Outer {
    private static int x = 10;
    private int y = 20;
    static class Inner {
        public void print() {
            System.out.println("x = " + x); // 可以访问静态成员
            // System.out.println("y = " + y); // 编译错误!不能访问非静态成员
        }
    }
}
// 创建方式
Outer.Inner inner = new Outer.Inner();
inner.print();

c) 局部内部类

  • 定义在方法或作用域块(如 for 循环、if 语句)内部。
  • 作用域仅限于定义它的方法或块内。
  • 可以访问外部类的成员,以及方法中的 final 或 effectively final 的局部变量。
public class Outer {
    private int x = 10;
    public void method() {
        int y = 20; // effectively final
        class LocalInner {
            public void print() {
                System.out.println("x = " + x);
                System.out.println("y = " + y);
            }
        }
        LocalInner inner = new LocalInner();
        inner.print();
    }
}

d) 匿名内部类

  • 这是最常见的内部类形式,它没有名字。
  • 它是一种特殊的局部内部类,在声明和实例化同时进行。
  • 通常用于只需要创建一个类的实例,并且这个类只被使用一次的场景(如事件监听器)。
  • 它可以继承一个类,也可以实现一个接口。

语法: new SuperType() { ... }

示例 (实现接口):

// 传统方式
btn.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("Button clicked!");
    }
});
// 使用Lambda表达式 (更简洁)
btn.addActionListener(e -> System.out.println("Button clicked!"));

这里的 new ActionListener() { ... } 就是一个匿名内部类,Lambda表达式其实就是匿名内部类的一种语法糖。


特性 核心思想 关键点
接口 定义行为契约,实现多继承 Java 8引入默认方法和静态方法,增强了功能。
Lambda表达式 将函数作为参数传递,简化代码 依赖于函数式接口,是匿名内部类的语法糖。
内部类 增强封装性,实现多继承,组织代码 分为成员、静态、局部和匿名四种,匿名内部类常用于事件处理。

理解并掌握这三大特性,是迈向Java高级开发者的关键一步,它们让Java代码变得更加灵活、简洁和富有表现力。

分享:
扫描分享到社交APP
上一篇
下一篇