目录
1. 接口
1.1. 接口概念
1.2. Java 8 接口:default、static
1.3. 接口 vs 抽象类
1.4. 常见接口:Comparable
1.5. 常见接口:Cloneable
1.6. 引用赋值与克隆
1.7. 接口多态与冲突
2. 包与模块
2.1. 包的作用
2.2. 全限定名
2.3. 类名歧义性处理
2.4. package 语句
2.5. import 语句
2.6. 编译和运行包中的类
2.7. CLASSPATH 环境变量
2.8. 模块
3. 访问控制修饰符
3.1. 四种访问权限
3.2. 成员访问控制
3.3. 类的访问控制
3.4. 字段封装
4. 非访问控制符:final、static、abstract
4.1. final
4.2. static
实例成员 vs 静态成员
4.3. 抽象类和抽象方法
5. 枚举
5.1. 枚举定义
5.2. 核心特性
5.3. 常用内置方法
1. 接口
1.1. 接口概念
接口是行为契约,定义能力规范,不关心实现,可以理解为完全抽象类。特点:
- 字段默认是静态常量(public static final),方法默认是抽象方法(public abstract)
- 每个接口都会被编译为单独的 .class 文件
- 不能实例化,没有构造方法,类使用 implements 实现接口
- 可以作为引用类型:接口名 变量名 = new 实现类名()
它可以指向任意一个实现了这个接口的具体类对象 - 一个类可以实现多个接口,接口可以使用 extends 多继承接口
interface Flyable { void fly(); } class Bird implements Flyable { public void fly() { System.out.println("Bird flies"); } }d e f 正确
interface 关键字,字段是常量,方法类型正确,不能写构造方法
检查关键字位置、字段修饰符和方法体,接口中的抽象方法不需要书写方法体
1.2. Java 8 接口:default、static
default 默认方法
接口可以提供默认实现,实现类可以直接使用,也可以重写,替换默认行为,必须有方法体
interface Greeting { default void sayHello() { System.out.println("Hello"); } }static 静态方法
接口可以提供 public static 方法,为接口相关逻辑提供统一入口,必须有方法体,通过接口名调用
interface Utils { static void print() { System.out.println("Utils"); } }新增方法时可以保持向后兼容,公共实现可复用,减少重复代码,接口仍可作为稳定的类型契约。Java 9 还新增了 private 私有方法,用于接口内部复用
1.3. 接口 vs 抽象类
| 维度 | 抽象类 | 接口 |
|---|---|---|
| 变量 | 无限制 | 必须是public static final |
| 构造方法 | 有,供子类构造方法链式调用 | 没有构造方法 |
| 实例化 | 不能用new实例化 | 不能用new实例化 |
| 方法 | 可包含抽象方法和具体方法 | 可包含abstract、default、static方法 |
| 继承 | Java 类只能单继承 | 一个类可以实现多个接口 |
- 接口:行为规范,多实现,只能常量,Java 8+ 才有方法体
- 抽象类:类模板,单继承,可有实例变量、构造方法
1.4. 常见接口:Comparable
Comparable 定义了用于比较对象的 compareTo 方法
int compareTo(T o);返回值含义:负数:当前对象小于比较对象;0:两个对象相等;正数:当前对象大于比较对象
Integer.valueOf(3).compareTo(Integer.valueOf(5)); // -1 "ABC".compareTo("ABC"); // 0 Date date1 = new Date(2013, 1, 1); Date date2 = new Date(2012, 1, 1); date1.compareTo(date2); // 正数- 日期较晚的对象返回正数
- 字符串、数字类、日期类都实现了 Comparable 接口
- 返回值只表达顺序,不直接表示差值
1.5. 常见接口:Cloneable
- Cloneable 是标记接口:不包含常量和方法实现,表示这个类的对象可以被克隆。实现后,对象可以使用 Object 类中定义的 clone() 方法进行克隆,克隆结果是内容相同、引用不同的对象
- 理解“标记”:没有需要实现的方法,只表示类拥有某种能力,常用于框架和类库识别
- Cloneable 不是 clone() 方法本身,它只是允许该对象被克隆的标记
class Student implements Cloneable { int id; } // 在别的类里(比如 main 方法里): Student s1 = new Student(); s1.clone(); // 编译报错!因为 clone() 是 protected 的,外部不能直接调用。 class Student implements Cloneable { // 1. 戴手环 int id; @Override public Student clone() throws CloneNotSupportedException { // 2. 开门,把protected改成public return (Student) super.clone(); // 3. 真正干活的是这句,调父类Object的克隆能力 } } // 测试: public class Main { public static void main(String[] args) throws CloneNotSupportedException { Student s1 = new Student(); s1.id = 100; Student s2 = s1.clone(); // 现在才能成功调用! System.out.println(s2.id); // 100 } }1.6. 引用赋值与克隆
Calendar calendar1 = calendar; Calendar calendar2 = (Calendar) calendar.clone(); 结果: calendar == calendar1; // true calendar == calendar2; // false引用赋值:同一个对象;clone:新对象,内容相同
Java 类库中许多类都实现了 Cloneable,如 Calendar、ArrayList
ArrayList<Double> list1 = new ArrayList<>(); list1.add(1.5); list1.add(2.5); list1.add(3.5); ArrayList<Double> list2 = (ArrayList<Double>) list1.clone(); ArrayList<Double> list3 = list1; list2.add(4.5); list3.remove(1.5);- list3 = list1 是引用赋值,操作 list3 会影响 list1
- list2 是克隆对象,修改 list2 不会影响 list1 的列表结构
1.7. 接口多态与冲突
- 多态:接口引用可以指向实现类对象,编译看左边,运行看右边
- 冲突:多个接口有同名 default 方法时,实现类必须重写
可用接口名.super.方法()调用指定接口默认方法interface A { default void print() { System.out.println("A"); } } interface B { default void print() { System.out.println("B"); } } // class C implements A, B { } // 编译报错:Duplicate default methods named print class C implements A, B { @Override public void print() { System.out.println("我自己重写的方法"); // 必须重写 } } class C implements A, B { @Override public void print() { A.super.print(); // 调用接口 A 的默认实现,输出 "A" B.super.print(); // 调用接口 B 的默认实现,输出 "B" System.out.println("再输出自己的逻辑"); } } - 访问权限:接口方法默认 public,重写时访问权限不能缩小
- 标记接口:无成员,仅做类型标记,如 Serializable
2. 包与模块
2.1. 包的作用
包用于管理类和接口,功能如下:
- 将功能相近的类和接口放在同一个包中,方便查找和使用
- 创建类名空间,可以有效的区分名称相同的类。当不同Java源文件中两个类名字相同时,它们可以通过隶属不同的包来相互区分
- 结合权限修饰符,可以实现对包中类和接口的访问控制
2.2. 全限定名
在 Java 程序中可以直接使用类的全限定名:
java.util.Date d = new java.util.Date();使用 import 后更方便(二者等价):
import java.util.Date; Date d = new Date();2.3. 类名歧义性处理
如果源文件中引入了两个包中的同名类,使用时不能省略包名。例如同时引入:
import java.util.Date; import java.sql.Date; // 此时应使用全限定名: java.util.Date date1; java.sql.Date date2;2.4. package 语句
通常把工作在一起的类放在一个包中。使用package语句创建包,规则:
- 必须写在源文件最顶部,前面只能有注释或空行
- 一个源文件最多只能有一条 package 语句,包名对应磁盘目录结构。没有就放在无名包中
- 包名是一个合法的标识符,或由若干合法标识符加 “.” 构成
- 包名一般全部小写,用反向Internet域名作为包名的起始部分
例如:com.example.mypackage - 包名和文件的路径名相对应。例如:若 Circle.java 源文件中使用了
package com.example.mypackage
则需要把源文件Circlejava放在\com\example\mypackage的目录中。
2.5. import 语句
需要使用其他包中的类或接口时,可以使用 import 语句导入(package 之后、类定义之前)
- 特定导入:import java.util.Scanner
- 通配符(*)导入:import java.util.* (不会递归导入子包)
- 使用同一个包下的类,无需 import
- java.lang 包中的类自动导入,无需 import,java.lang 的子包不会自动导入
2.6. 编译和运行包中的类
编译时,可以使用 javac -d 指定包的根目录
javac -d d:\JavaD\ch04 d:\JavaD\ch04\pk\TestPkg.java如果当前目录在 d:\JavaD\ch04:
javac -d . pk\*.java其中 . 表示当前目录。运行时,需要指明主类的全限定名:
java pk.TestPkg无名包直接写类名运行
2.7. CLASSPATH 环境变量
可以设置 CLASSPATH,让 Java VM 确定包的位置
Windows 用分号 ; 分隔路径 Linux 用冒号 : 分隔路径
Windows 示例: set CLASSPATH=.;C:\Program Files\Java\jdk1.8.0\lib 也可以在命令行中指定: java -classpath d:\JavaD\ch04;c:\java\classes;. pk.TestPkg 或使用 -cp: java -cp d:\JavaD\ch04;c:\java\classes;. pk.TestPkg2.8. 模块
Java 9 以上版本引入模块概念,一个模块可以包含多个包和一个模块描述文件(module-info.java):
- 普通 Jar 包:只包含 class,包全部对外可见,没有封装控制
- 模块 Jar:包含 module-info.class,可以精确控制哪些包对外暴露、依赖哪些其他模块
module-info.java 必须放在模块源码根目录,编译后生成 module-info.class。java.base 是核心基础模块,包含 java.lang、java.util 等基础包。所有模块默认隐式 requires java.base,不用手写
3. 访问控制修饰符
3.1. 四种访问权限
Java 提供 3 个访问控制修饰符、4 种访问权限级别:
| 修饰符 | 访问权限级别 |
|---|---|
public | 公有 |
protected | 受保护 |
| 无修饰符 | 包访问 |
private | 私有 |
3.2. 成员访问控制
类中的成员(字段和方法)受以下 4 种访问权限控制:
| 修饰符 | 可见范围 | 关键边界 |
|---|---|---|
public | 任何类都可以访问 | 完全开放 |
protected | 自身所在类、子类、同一个包中的其他类 | 延续到子孙类 |
| 无修饰符(默认) | 自身所在类,以及同一个包中的其他类 | 只以包为边界 |
private | 只有自身所在类中可以访问 | 隐藏实现细节 |
- 判断顺序:先判断是否在同一个包内,再判断是否存在继承关系。
- private 成员仅在本类内部可见,同类代码中可以访问同类其他对象的私有成员,跨类不可访问
public class Person { private String name; public Person(String name) { this.name = name; } public boolean hasSameName(Person other) { return this.name.equals(other.name); // 合法 } } public class Test { public void check(Person p) { // System.out.println(p.name); // 编译错误 } }| 修饰符 | 本类 | 同包 | 子类 | 任意类 |
|---|---|---|---|---|
private | 是 | 否 | 否 | 否 |
| 默认 | 是 | 是 | 否 | 否 |
protected | 是 | 是 | 是 | 否 |
public | 是 | 是 | 是 | 是 |
3.3. 类的访问控制
类本身也有访问级别,相对于包而言:
- public:其他任何类都可以声明该类的引用变量
- 无修饰符:只有在同一个包中的其他类可以声明该类的变量
3.4. 字段封装
封装的核心:隐藏字段本身,公开字段的读写方法
做法:
- 私有字段 private,避免外部直接修改数据,保护数据完整性,降低耦合
- getter 方法:public,返回字段当前值;boolean 类型常用 isPropertyName()
- setter 方法:public,验证或转换新值,在受控位置更新字段
public class Circle { private double radius; private static int numberOfObjects = 0; public Circle() { this(1.0); } public Circle(double radius) { this.radius = radius; numberOfObjects++; } public double getRadius() { return radius; } public void setRadius(double radius) { this.radius = radius; } public static int getNumberOfObjects() { return numberOfObjects; } public double getArea() { return Math.PI * radius * radius; } }注意:
- 封装不是“都定义为 private”,关键是为外部调用设计清晰、稳定、可验证的公开接口
- 要访问某个成员,首先该类本身必须可见,再要求成员可见,两层控制叠加,才形成完整的封装边界
4. 非访问控制符:final、static、abstract
4.1. final
final 表示最终的、不可改变。final 只锁死地址,不锁死对象内容
- final 类禁止被继承,类内方法隐式 final
- final 方法,不能被子类覆盖,可以重载,可以继承调用。
- 字段与局部变量都可以使用 final 修饰,但值能且只能被赋值一次,一旦给定就不能更改
赋值差异:
- static final 字段:在定义时或者某个 static 初始化器中赋值
- 实例 final 字段:在定义时或者构造方法中赋值
- final 局部变量:在定义时赋值
public static final double PI = 3.14159265358979323846;4.2. static
static 表示静态的,属于类,不属于对象实例,default 与 static 方法必须有方法体
- 静态变量 / 类变量使用 static 修饰,存储在公共内存地址中,被这个类的所有对象共享,建议通过类名访问:ClassName.value,类加载时初始化,一个对象修改后,其他对象也能看到
- 静态常量使用 static final 声明:
public static final double PI = 3.14159265358979323846;静态方法 / 类方法属于类,不属于某个实例,使用 static 修饰,可以无需创建实例,直接通过类名调用:ClassName.methodName(arguments)。不能直接访问实例变量、实例方法,不能使用 this 或 super,实例方法可以直接访问静态成员,静态方法不能反向依赖实例状态,静态方法只有隐藏,不存在重写
public class Counter { private static int count = 0; public static void increase() { count++; } public static int getCount() { return count; } }实例成员 vs 静态成员
| 对比项 | 实例成员 | 静态成员 |
|---|---|---|
| 归属 | 对象 | 类 |
| 内存 | 每个对象独立 | 所有对象共享 |
| 访问 | 对象名.成员 | 类名.成员 |
| 方法中限制 | 可访问静态和实例成员 | 不能直接访问实例成员,不能用this/super |
| 重写 | 可重写 | 只有隐藏,没有重写 |
4.3. 抽象类和抽象方法
abstract 关键字只能修饰类和方法
- 抽象类用关键字 abstract 修饰,不能使用 new 创建实例,可以没有抽象方法,可以有构造方法,供子类链式调用,可以包含普通属性、普通方法
- 抽象方法用关键字 abstract 修饰,只有方法签名,没有方法体,只能在抽象类或接口中定义,强制子类重写
含抽象方法的类必须是抽象类,子类未实现全部抽象方法时,子类仍须声明为 abstract
public abstract class GeometricObject { public abstract double getArea(); public abstract double getPerimeter(); }只有 e f 合法
先看abstract是否放在class之前,再判断抽象方法是否合法
5. 枚举
5.1. 枚举定义
JDK 1.5 新增 enum 关键字定义枚举类型,枚举本质是一种特殊的类,编译后生成 .class 文件。定义时列出全部可选常量,实例固定,不能随意创建新对象
enum Season { spring, summer, fall, winter } Season time = Season.spring;枚举是实例数量固定的特殊类,用来定义一组有限常量,类型安全
5.2. 核心特性
枚举常量默认:public static final,是枚举类的实例。枚举类不能手动 new,构造方法默认私有 private。枚举可以写成员变量、构造方法、普通方法、抽象方法,枚举隐式继承 java.lang.Enum,不能再继承其他类,可以实现接口
5.3. 常用内置方法
| 方法 | 作用 |
|---|---|
values() | 返回枚举常量数组,便于遍历 |
ordinal() | 返回常量序号,从 0 开始 |
name() | 返回常量字符串名称 |
valueOf() | 根据字符串获取枚举对象 |
for (Season s : Season.values()) { System.out.println(s.name() + " - " + s.ordinal()); } Season s = Season.valueOf("spring");