十二、面向对象-高级
1. 关键字 static
1.1 static 概念
将类的成员(属性、方法、代码块、内部类)归属于类本身所有,所有实例对象共享同一个 static 成员
也就是说,无论创建多少个对象,共享同一份 static 修饰的类成员(属性、方法、代码块、内部类)
- static 的含义是 “
静态的” - 被 static 修饰的属性,称之为静态属性
- 被 static 修饰的方法,称之为静态方法
- 被 static 修饰的代码块,称之为静态代码块
- 被 static 修饰的内部类,称之为静态内部类
1.2 static 的特点
- 随着类的加载而加载(不需要依托于创建对象时才加载)
- 被 static 修饰的成员,被该类的
所有对象共享 - 在访问权限允许时,可以
直接通过类调用
1.3 静态变量
被 static 修饰的属性(变量),称之为静态属性(变量),常用于作为常量等
语法格式:
[权限修饰符] static 数据类型 变量名;
- 静态变量的默认值规则和实例变量一样
- 静态变量所有对象共享
- 静态变量的 get/set 方法也静态的,当局部变量与静态变量
重名时,可使用类名.静态变量进行区分
内存解析:
1.4 静态方法
被 static 修饰的方法,称之为静态方法,常用于工具类中的方法等
语法格式:
[权限修饰符] static 返回值类型 方法名(形参列表) {
// 方法体
}
- 在 static 方法内部
只能访问类的static修饰的属性或方法,不能访问类的非static的结构 - 静态方法可以被子类继承,但不能被子类重写
- 静态方法的调用都只看编译时类型
内存解析: 暂无
1.5 静态代码块
类加载时自动执行一次,常用于初始化静态资源、加载配置、连接数据库等
语法格式:
public class Test {
// 静态代码块
static {
System.out.println("类加载时执行,只执行一次");
}
}
"tips: 类加载顺序中,静态代码块 > 构造代码块 > 构造方法"
1.6 静态内部类
静态内部类 不需要创建外部类对象 就能使用,但只能访问外部类的 静态成员
语法格式:
public class Outer {
static int num = 100;
// 静态内部类
static class Inner {
public void show() {
System.out.println(num);
}
}
}
1.7 拓展知识点
- 静态方法能不能重写? 不能,静态方法是 属于类 的,重写是针对对象的多态
- 静态变量存在哪里? JDK8 之前在方法区,JDK8+ 在 堆内存 的类元数据中
- main 方法为什么是 static? 程序启动时还没有对象,必须用 static 让 JVM 直接调用
2. 关键字 final
含义为:最终的,不可修改的
- 修饰类时,表示这个类不能被继承,没有子类。提高安全性,提高程序的可读性
- 修饰变量时,一旦赋值,它的值就不能被修改
- 修饰方法时,表示这个方法不能被子类重写
3. 关键字 abstract
含义为:抽象的
- 使用
abstract修饰的类称之为 抽象类,表明该类不需要被实例化 - 使用
abstract修饰的方法称之为 抽象方法,表明该方法不需要具体的方法体,必须子类实现 - 抽象类中可以有抽象方法和普通方法,但抽象方法只能声明在抽象类中
简单来说,抽象类通常是一个 不需要被实例化的父类,抽象方法通常是一个不需要具体方法体的父类方法
语法格式:
// 抽象类
[权限修饰符] abstract class 类名{
// 抽象方法
public abstract void test();
}
特点:
- 抽象类 不能创建对象,只能创建其 非抽象子类 的对象
- 抽象类中,也 有构造方法,是供子类创建对象时,初始化父类成员变量使用的
- 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类
- 抽象类的子类,必须重写抽象父类中 所有的 抽象方法(抽象类继承抽象类时,无须遵循此条)
- 不能用abstract修饰 变量、代码块、构造器
- 不能用abstract修饰 私有方法、静态方法、final的方法、final的类
4. 关键字 interface
4.1 概念
接口(interface)是一种 完全抽象 的引用类型,是方法的集合(Java 8+ 支持默认方法、静态方法),核心作用是 定义规范
可以把接口理解为 规定 :规定接口(interface)的实现类必须做什么,具体做什么由实现类自己决定
- 接口是完全抽象的规范,核心是用
implement实现 - 普通类实现接口时,必须
实现所有抽象方法 - 接口与实现类支持多实现,接口与接口支持多继承
- Java8 以后允许接口存在默认方法,可以直接继承或重写;允许存在静态方法,可通过接口直接调用
4.2 特点
-
无法实例化:接口不能直接
new对象 -
多继承(多实现):一个类可以实现多个接口(可弥补 Java 单继承的缺陷)
-
访问修饰符:接口中的方法默认是
public abstract、变量默认是public static final,可省略不写 -
实现规则:普通类实现接口,必须重写所有抽象方法
4.3 语法格式
// 接口定义关键字:interface
public interface Animal {
// 常量:默认 public static final,必须赋值
String TYPE = "动物";
// 抽象方法:默认 public abstract,没有方法体
void eat();
void sleep();
// Java 8+ 默认方法:有方法体,实现类可直接使用/重写
default void breathe() {
System.out.println("用氧气呼吸");
}
// Java 8+ 静态方法:接口直接调用,实现类不能继承
static void info() {
System.out.println("这是动物接口");
}
}
4.4 接口的实现 implements
关键字:implements
一个类可以 同时继承一个父类 + 实现多个接口(用逗号分隔)
// 实现接口:必须重写所有抽象方法
public class Dog implements Animal {
// 重写抽象方法(必须加 public)
@Override
public void eat() {
System.out.println("狗吃骨头");
}
@Override
public void sleep() {
System.out.println("狗趴着睡");
}
}
// 第二个接口
interface Run {
void run();
}
// 一个类实现多个接口
public class Cat implements Animal, Run {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
@Override
public void sleep() {
System.out.println("猫躺着睡");
}
@Override
public void run() {
System.out.println("猫快速跑");
}
}
4.5 接口的多继承
接口(interface)直接是允许多继承的
interface Bird extends Animal,Fly {
void sing();
}
4.6 抽象类和接口的区别
| 特性 | 接口(Interface) | 抽象类(Abstract Class) |
|---|---|---|
| 继承方式 | implements,可多实现 | extends,单继承 |
| 成员变量 | 只能是 public static final 常量 | 支持任意类型变量 |
| 构造方法 | 无构造方法 | 有构造方法 |
| 方法 | Java8+:抽象、默认、静态、私有方法 | 抽象方法、普通方法 |
| 核心用途 | 定义规范 / 行为 | 定义模板 / 共性 |
4.7 接口的常用场景
-
定义统一规范:比如所有动物必须实现
eat()、sleep() -
实现多继承:Java 类只能继承一个父类,用
接口弥补缺陷 -
解耦代码:面向接口编程,降低模块间的依赖
-
框架设计:Spring、MyBatis 等大量使用接口做扩展
5. 内部类
5.1 概念
定义在一个类内部的类,称之为内部类,其所在的类就成为外部类
- 成员内部类(普通)
- 静态内部类(嵌套)
- 局部内部类(方法中)
- 匿名内部类(最常用,Lambda的前身)
5.2 成员内部类
定义在类中、方法外,依赖外部类对象
class Outer {
private int a = 10;
// 成员内部类
class Inner {
void test() {
// 可以直接访问外部类私有成员
System.out.println(a);
}
}
}
// 创建方式
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
- 内部类持有 外部类.this 引用
- 不能定义 static 成员(除 static final 常量)
- 可访问外部所有成员(包括 private)
5.3 静态内部类
用 static 修饰,不依赖外部对象
class Outer {
private static int a = 10;
static class Inner {
void test() {
System.out.println(a);
// 只能访问外部静态成员
}
}
}
// 创建方式
Outer.Inner inner = new Outer.Inner();
- 最常用、最推荐(无内存泄漏风险)
- 不持有外部类引用
- 可包含任意 static / 非 static 成员
5.4 局部内部类
定义在 方法 / 代码块 内部
class Outer {
void test() {
int num = 20;
// 局部内部类
class Inner {
void f() {
System.out.println(num);
}
}
}
}
- 作用域仅限当前方法
- 只能访问方法中 final / 有效 final 变量
- 极少用
5.5 匿名内部类(重)
没有类名,一次性使用,常用于创建接口 / 抽象类对象
// 接口
interface Runnable {
void run();
}
// 匿名内部类实现接口
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("run");
}
};
// jdk8+ j简化Lambda表达式
Runnable r = () -> System.out.println("run");
场景:
- 点击事件
- 线程创建
- 集合排序
- 接口回调
5.6 各内部类的核心区别(以及 effectively final 解释)
| 类型 | 依赖外部对象? | 能否 static 成员 | 访问外部 |
|---|---|---|---|
| 成员内部类 | 是 | 不能 | 所有成员 |
| 静态内部类 | 否 | 能 | 仅静态成员 |
| 局部内部类 | 是 | 不能 | 方法内 final 变量 |
| 匿名内部类 | 是 | 不能 | 同局部 |
-
成员内部类会持有外部类引用 → 容易内存泄漏
-
静态内部类 不会持有外部引用,最安全
-
匿名内部类访问局部变量必须是 effectively final(Effectively final 指的是:虽然没有显式使用
final关键字修饰,但在初始化后从未被重新赋值的局部变量,编译器会将其视为final变量处理,允许在 Lambda 或匿名内部类中安全引用)
| 特性 | final 变量 | effectively final 变量 |
|---|---|---|
| 声明方式 | 必须显式加 final 关键字 | 不加 final,但实际未被修改 |
| 适用范围 | 实例变量、静态变量、局部变量 | 仅限局部变量(含方法参数) |
| 是否可修改 | 一旦赋值,不可更改 | 初始化后不能重新赋值,否则不再是 effectively final |
| 编译器处理 | 明确禁止修改 | 编译器自动判断并隐式视为 final |
6. 枚举 Enum(重)
enum 是一种特殊的类,用来表示固定、有限、确定的一组值
比如:季节、性别、状态、订单状态、颜色、方向等
优点:
- 代替大量常量(
public static final) - 类型安全,不会传错值
- 自带方法,功能强大
- 可加字段、方法、构造器
特点:
- 枚举类默认继承
java.lang.Enum - 不能继承其他类,但可以实现接口
- 构造器默认 private,自己写也必须 private(不能外部 new)
语法格式(简单版):
public enum Season {
SPRING, SUMMER, AUTUMN, WINTER
}
Season s = Season.SPRING;
- 枚举常量默认:
public static final - 常量名大写,逗号分隔
- 最后一个分号可省略(有其他内容必须加)
语法格式:(高级版)
public enum OrderStatus {
// 枚举实例(必须放在第一行)
CREATED(1, "已创建"),
PAID(2, "已支付"),
SHIPPED(3, "已发货"),
FINISHED(4, "已完成");
// 成员变量
private final int code;
private final String desc;
// 构造器(默认 private,可不写)
OrderStatus(int code, String desc) {
this.code = code;
this.desc = desc;
}
// getter
public int getCode() {
return code;
}
public String getDesc() {
return desc;
}
}
OrderStatus status = OrderStatus.PAID;
System.out.println(status.getCode()); // 2
System.out.println(status.getDesc()); // 已支付
枚举类的常用方法:(自带)
OrderStatus status = OrderStatus.CREATED;
status.name(); // 返回字符串 "CREATED"
status.ordinal(); // 返回序号 0(从0开始)
OrderStatus.values();// 返回所有枚举数组 OrderStatus[]
OrderStatus.valueOf("PAID"); // 字符串转枚举
枚举类不能继承其他类(默认继承Enum类),但是支持实现接口
interface CodeDesc {
int getCode();
String getDesc();
}
enum Status implements CodeDesc {
YES(1, "是"),
NO(0, "否");
private final int code;
private final String desc;
Status(int code, String desc) {
this.code = code;
this.desc = desc;
}
@Override
public int getCode() {
return code;
}
@Override
public String getDesc() {
return desc;
}
}
枚举单例(最推荐的单例模式)
使用枚举编写单例模式的功能代码,是 最简单、最安全的,无线程安全问题
public enum Singleton {
INSTANCE;
public void doSomething() {
// 业务方法
}
}
Singleton.INSTANCE.doSomething();
总结:
- enum 是类,继承 Enum,不能再继承其他类
- 枚举实例是 public static final
- 构造器 只能 private
- 可包含:成员变量、构造器、普通方法、静态方法
- 可实现接口,不能继承类
- values()、valueOf()、name()、ordinal() 常用
- 枚举单例是最佳单例
- 适合:固有限值(状态、类型、性别等)
7. 注解 Annotation
7.1. 概念
Java 注解(Annotation)是 JDK 5 引入的元数据机制,用于为类、方法、字段等程序元素附加额外信息,不直接参与业务逻辑,但可被编译器、工具或运行时框架读取并处理,核心价值是简化配置、编译期校验、运行时动态逻辑
在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等。
未来的开发模式都是基于注解的,JPA是基于注解的,Spring2.5以上都是基于注解的,Hibernate3.x以后也是基于注解的,Struts2有一部分也是基于注解的了。注解是一种趋势,一定程度上可以说:框架 = 注解 + 反射 + 设计模式
注解是 Java 简化开发、提升效率的核心机制,从 JDK 内置注解到框架自定义注解,形成了完整的元数据体系。掌握注解的核心是理解生命周期和反射解析,这是开发框架、实现自定义逻辑的基础
注解分类:
| 生命周期 | 保留策略 | 作用场景 | 典型示例 |
|---|---|---|---|
| 源码注解 | SOURCE | 仅源码有效,编译后丢弃,用于编译检查 | @Override、@SuppressWarnings |
| 编译注解 | CLASS | 保留至字节码,运行时不加载,用于字节码处理 | 编译时代码生成工具 |
| 运行时注解 | RUNTIME | 保留至运行时,通过反射解析,框架核心依赖 | @Autowired、@RequestMapping |
7.2 JDK中的三个基本注解
1. @Override
- 用于检测被标记的方法为有效的重写方法,如果不是,则报编译错误!
- 只能标记在方法上
- 它会被编译器程序读取
2. @Deprecated
- 用于表示被标记的数据已经过时,不推荐使用
- 可以用于修饰 属性、方法、构造、类、包、局部变量、参数
- 它会被编译器程序读取
3. @SuppressWarnings
-
抑制编译警告。当我们不希望看到警告信息的时候,可以使用 SuppressWarnings 注解来抑制警告信息
-
可以用于修饰类、属性、方法、构造、局部变量、参数
-
它会被编译器程序读取
-
可以指定的警告类型有(了解)
- all,抑制所有警告
- unchecked,抑制与未检查的作业相关的警告
- unused,抑制与未用的程式码及停用的程式码相关的警告
- deprecation,抑制与淘汰的相关警告
- nls,抑制与非 nls 字串文字相关的警告
- null,抑制与空值分析相关的警告
- rawtypes,抑制与使用 raw 类型相关的警告
- static-access,抑制与静态存取不正确相关的警告
- static-method,抑制与可能宣告为 static 的方法相关的警告
- super,抑制与置换方法相关但不含 super 呼叫的警告
- …
7.3 元注解
JDK1.5在java.lang.annotation包定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。简单来说就是,对某个注解进行注释说明的注解
(1)@Target: 用于描述注解的使用范围
- 可以通过枚举类型ElementType的10个常量对象来指定
- TYPE,METHOD,CONSTRUCTOR,PACKAGE…..
(2)@Retention: 用于描述注解的生命周期
- 可以通过枚举类型RetentionPolicy的3个常量对象来指定
- SOURCE(源代码)、CLASS(字节码)、RUNTIME(运行时)
唯有RUNTIME阶段才能被反射读取到
(3)@Documented:表明这个注解应该被 javadoc工具记录
(4)@Inherited: 允许子类继承父类中的注解
7.4 自定义注解
通过 @interface 关键字定义,结合元注解控制特性,配合反射实现逻辑
// 元注解:指定作用范围和生命周期
@Target({ElementType.METHOD, ElementType.TYPE}) // 可用于类/方法
@Retention(RetentionPolicy.RUNTIME) // 运行时保留,可反射解析
@Documented
public @interface MyAnnotation {
// 注解属性:支持基本类型、String、Class、枚举、数组
String value() default "";
// 单值属性,默认值可选
int age() default 18;
String[] tags() default {};
}
@MyAnnotation(value = "测试类", age = 25, tags = {"开发", "注解"})
public class AnnotationDemo {
@MyAnnotation(value = "测试方法", tags = "实战")
public void testMethod() {
System.out.println("执行测试方法");
}
}
7.5 框架常用注解
- JDK:基础 + 元注解(@Override、@Target、@Retention)
- Spring:Bean、AOP、配置(@Service、@Autowired)
- SpringMVC:Web 接口(@GetMapping、@RequestBody)
- SpringBoot:自动配置(@SpringBootApplication)
8. 包装类
1. 概念
Java提供了两个类型系统,基本数据类型与引用数据类型。使用基本数据类型在于效率,然而当要使用只针对对象设计的API或新特性(例如泛型),怎么办呢?
包装类就是为 每一种基本数据类型 提供的 对应引用类型,把基本类型 “包装” 成对象,让它拥有对象的特性
| 基本数据类型 | 包装类(java.lang) |
|---|---|
| byte | Byte |
| short | Short |
| int | Integer |
| long | Long |
| float | Float |
| double | Double |
| char | Character |
| boolean | Boolean |
2. 核心作用
- Java 集合(List/Map/Set)只能存对象,不能存基本类型
List<int> list = new ArrayList<>(); // 报错! List<Integer> list = new ArrayList<>(); // 正确 - 包装类提供了大量实用方法(字符串转数字、最大值、最小值等)
- 支持泛型(泛型必须用包装类)
- 可以赋值为 null(基本类型不能为 null,包装类可以)
3. 自动装箱 & 自动拆箱(JDK 5+ 特性)
- 自动装箱:基本类型 → 包装类(编译器自动完成)
- 自动拆箱:包装类 → 基本类型(编译器自动完成)
代码示例:
// 自动装箱:int → Integer
Integer a = 10;
// 自动拆箱:Integer → int
int b = a;
// 混合运算也会自动拆箱
Integer c = 20;
int sum = a + c; // 30
4. 包装类常用方法
- 字符串 → 基本类型
String str = "123";
int num = Integer.parseInt(str); // 123
double d = Double.parseDouble("3.14");
2. 基本类型 → 字符串
String s1 = Integer.toString(123);
String s2 = String.valueOf(456);
3. 获取常量(最大值 / 最小值)
System.out.println(Integer.MAX_VALUE); // 2147483647
System.out.println(Integer.MIN_VALUE); // -2147483648
5. 重点坑:== 比较 Integer 缓存机制
Integer 默认缓存 -128 ~ 127 之间的数字,这个范围的数字用 == 比较是相等的;超出范围会创建新对象,== 比较地址就不相等
代码示例:
Integer a = 100;
Integer b = 100;
System.out.println(a == b); // true(在缓存里)
Integer x = 200;
Integer y = 200;
System.out.println(x == y); // false(超出缓存,新对象)
✅ 正确比较方式:所有包装类都用 equals()
System.out.println(x.equals(y)); // true
6. 基本类型 vs 包装类
| 对比项 | 基本类型 | 包装类 |
|---|---|---|
| 类型 | 基本数据类型 | 引用类型 |
| 存储位置 | 栈(高效) | 堆(对象) |
| 默认值 | 0 /false 等 | null |
| 能否存 null | 不能 | 能 |
| 集合能否使用 | 不能 | 能 |
| 比较 | == 比较值 | == 比较地址,equals 比较值 |
7. 总结
- 包装类是 基本类型的对象版,解决
基本数据类型不是对象的问题 - 8 种基本类型对应 8 个包装类,重点记
Integer和Character - 自动装箱 / 拆箱 让基本类型和包装类可以直接互相赋值
- 比较包装类 必须用 equals () ,不要用 ==
- 集合、泛型只能用 包装类