JavaSE-02-面向对象(详细版)

Java是面向对象语言,本文将结合丰富的案例讲解面向对象的语法。熟练运用面向对象思维,助力写出高内聚低耦合的漂亮代码,同时很好地实现业务需求,项目代码具有扩展性,同时也能锤炼看问题的角度。

一、面向过程与面向对象

说到面向对象,就不得不说面向过程,这是两种不同的编程方式,但是这两者并不割裂。早期的高级编程语言,如:C语言,便是面向过程的代表,代码讲究如何一步步实现需求,实现的方案有优有劣,这便是[算法],所以早期提出编程=算法+数据结构这样的高度总结。优秀的算法,对于同样的机器,能够高效地计算出结果,重点落在算法上,而数据只是凑合辅助,更有价值的是算法。

而如今的面向过程语言,如:Java,C++等,更多是先聚焦在如何高效组织数据上,而将视角拉高一些,俯瞰这些数据,将其归类封装,变成了一个个有边界的数据集合,同时加上操作这些数据的行为,便可以打包为一个个的对象。当一个个具体的对象,有相似之处,便可以再次抽象一层,称之为对象模版,类。面向对象的视角,更有益于实现大型项目,互相协作,高效并行。

上文所说,面向过程与面向对象虽说大体是两个不同的编程方式,编程语言的特点。但是因为面向对象是后来者,所以其身上或多或少会有面向过程的影子。这也很好理解,因为使用面向对象进行高纬度封装后,聚焦到一个个方法函数,其实现也是需要一步步去做的,才能实现,这不就是面向过程吗?

总结来说,先面向对象从高层面划分类对象,这些对象封装着属性以及改变属性状态的行为;然后具体到行为如何实现时,使用面向过程的思维,思考如何使用高效的算法实现该行为,从广州到北京的路线千千万万,哪条才是最优的呢[面向过程]?而决定从广州去北京,而不是从海南去北京,那便是面向对象整体的规划了[面向对象]。

二、类与对象

什么是类与对象?

现实生活中:对象,就是一个个具体的客观存在的物理实体,有形有状,摸得着看得见,如:一辆辆具体的车;类,则是制造具体车的模具,工人们根据模具,便可以造出一辆辆的车。

从总结归纳的角度看:对象是普遍的现象,类是对具体现象的抽象,将其特性行为抽取出来,形成共性。可以根据所得规律(类)判断新的现象(对象)是否符合要求。

Java世界有个高度的总结:万物皆对象。没有对象,那就new一个出来。对象是具体的,类是抽象的,模板的。

类的组成成分

那么类的组成成分有哪些呢?

  • 成员变量(属性)
  • 成员方法(行为)
  • 构造方法(new实例化+属性初始化)
  • 代码块(补充构造方法)
  • 内部类(类中类)
public class Person { private String name; // 姓名 private int age; // 年龄 private char sex; // 性别 public Person () {} // 无参构造 public Person (String name, int age, char sex) { // 有参构造 System.out.println( "构造方法执行" ); this .name = name; // 初始化属性…… this .age = age; this .sex = sex; } { System.out.println( "代码块执行" ); // 每次new对象都执行 } static { System.out.println( "静态代码块执行" ); // 只执行一次 } public String getName () { // 行为:获取姓名 return name; } public void setName (String name) { // 行为:设置姓名 this .name = name; } public int getAge () { // 行为:获取年龄 return age; } public static class Heart { .... // 一样的类成分:属性、行为、构造方法、代码块、内部类 } }

上述的类成分,可以随意搭配,不是一定都全部齐全才叫类,一般的类,就只有:属性、行为、构造方法这三种成分,也是最常见的实体类。类有了,下一步便是new一个对象出来见见世面。

public class Main { public static void main (String[] args) { Person water = new Person ( "water" , 18 , '男' ); String name = water.getName(); System.out.println(name); System.out.println( "===============" ); Person mary = new Person ( "Mary" , 18 , '女' ); System.out.println(mary.getAge()); } } // 输出 静态代码块执行 代码块执行 构造方法执行 water =============== 代码块执行 构造方法执行 18

new实例化对象时,执行顺序:静态代码块 > 代码块 > 构造方法

构造方法

  • 每个类都有一个默认的无参构造方法,可以不显式
  • 如果提供了有参构造,而又需要无参构造,则需要显式
  • 作用:创建对象,初始化成员属性
  • 一般建议提供两个构造方法:无参构造,全参构造

new对象过程分析

Person p = new Person("water",18,'男')创建对象的细节:代码执行到这一行时,先在堆内存区域new出一块空间(划分,有地址,如:0x001),接着进行初始化,如果有入参,则会给成员属性赋值,若没有,则使用默认值,如:引用类型默认null,基本整型为0,布尔类型为false等;第三步,完成实例化和初始化后,将这片空间的地址0x001指向一个变量存储,这里是p,那么此刻p存储着对象的地址值,根据地址可以找到对象,这就是对象引用,通过p引用对象的内容,包括调用对象的行为方法,改变其属性状态等等。这一刻,p就代表着water这个人。

构建标准类

  • 成员属性私有化,private修饰
  • 若需要设置和访问,则提供public的set/get方法
  • 提供无参构造,全参构造
  • 提供其他必须的成员方法

小结

  • 类或对象的成分:属性、行为、构造方法,内部类,代码块
  • 产生对象,new 构造方法
  • 有参构造方法,可以对属性进行初始化赋值
  • 实例化对象时,静态代码块最先执行并且只执行一次
  • 代码块每次都先于构造方法执行,像是构造方法的补充

三、权限修饰符

顾名思义,这是Java用于控制调用者对资源访问的权限控制,符合日常认知,保证数据安全,操作规范。

四种权限符

权限修饰符,指的是:

  • private,私有
  • default,默认、缺省
  • protected,受保护
  • public,公开

这四种权限,保护属性、行为、类不被滥用,能更好地保证数据安全,调用安全。其权限范围如下:

访问范围 本类 同包 子类 不同包
private × × ×
default × ×
protected ×
public

认识包

包相当于电脑中的文件夹,便于管理代码文件;而包的命名,根据公司的域名倒写,如:www.water.com则包名com.water.project.module.funtiction这里包名使用.隔开。域名+项目名+模块名等。

测试验证

设计两个包,和三个类,测试权限修饰符。结果如下:
可以看到Main类中的main方法是我们的执行入口,分别实例化Cat、Dog、DogSon三个对象,调用不同方法,发现有三处地方报错,这对象各自属性也引用了其他包的对象或同包对象,有些方法调用是报错的!印证了上述的权限修饰符作用范围。

使用建议

  • private用于类内大方法的封装复用
  • default接口JDK8后新增默认方法使用,定义接口实现类公共行为时使用
  • protected父类允许子类使用的方法
  • public任何类都能调用的公开方法,对外提供能力

四、封装性

什么是封装?

面向对象三大特性之一:封装性。使用private来修饰属性、方法、类,意味着将其封装起来,避免访问,就像黑屋子,外面的人,看不到里面的构造,具有安全性。同时降低了调用者的使用难度,因为private外面压根调用不了;而只有在本类可以使用,那么证明这个是安全的,开发者也可以大胆改造。

该露的露,该隐藏的隐藏。

使用方式

  • private修饰属性,提供公开的getter/setter方法进行访问和赋值
  • private修饰方法,表明方法实现细节不想暴露出去,仅供本类使用
  • private修饰构造方法,表明不想被外面new出对象,一般用于单例
  • private修饰类,表明仅能其外部类能访问

错误案例:

public class Person { String name; int age; } public static void main (String[] args) { Person p = new Person (); p.name = "water" ; p.age = 15 ; // 随意继续赋值 p.name = "Bob" ; // 就像自家后花园 }

正确做法:

public class Person { private String name; private ing age; public String getName () { return name; } public void setName () { this .name = name; } .... } public static void main (String args[]) { Person p = new Person (); p.setName( "water" ); String name = p.getName(); System.out.println(name); // 输出water }

this关键字

this表示正在调用该方法的对象引用,哪个对象调用则代表哪个对象。成员方法中隐去了这个对象入参。就像下面那样。

public void setName (Person this ) { // 隐去了该具体对象引用 this .name = name; }

作用

  • 方法入参变量(局部变量)和成员变量同名时,用于区分
  • this,指代当前对象
  • this(),表示当前对象的无参构造方法
  • this(xx),表示当前对象的有参构造方法

五、继承性

什么是继承?

面向对象三大特性之一:继承性。Java是单继承,只能继承一个父类,但可以间接继承(父类的父类),而这个的最顶级老祖宗,则是Object类,所以Java是一个联系非常紧密的类语言,没有一个类是孤立的,即使没有明显地extends某个父类,也会默认继承Object类,同时继承是可以传递的,子类继承父类的一切,包括属性和行为,但不代表都能访问,需要遵循权限修饰符的限制;所以子类真的集万千宠爱于一身,并且还能发展自己的事业(扩展自己的属性、行为)张扬自己的才华横溢。

基本使用

public class Father { private String name; public Father () { System.out.println( "父类无参构造方法" ); } public Father (String name) { System.out.println( "父类有参构造方法" ); this .name = name; } public void dance () { System.out.println( "跟着张学友学跳舞" ); } private void cook () { // 子类拥有,但访问不了,private修饰了 System.out.println( "爸爸烧得一手好菜" ); } public String getName () { return name; } public void setName (String name) { this .name = name; } }

继承格式:修饰符 子类 extends父类

// 使用extends关键字表示继承 public class Son extends Father { private String hobby; // 子类扩展属性 public Son () { super (); // 可以忽略,默认第一行调用父类构造方法 } public Son (String name, String hobby) { super (name); // 调用父类的构造方法,有父亲才有儿子 this .hobby = hobby; System.out.println( "子类有参构造方法" ); } @Override public void dance () { // 子类重写父类的方法 System.out.println( "跟着坤哥学跳舞" ); } public void study () { // 子类新增方法 System.out.println( "学习" ); } public String study (String bookName) { // 方法重载:方法名相同,参数不同,返回值不同 return "正在学习" + bookName; } public String getHobby () { return hobby; } public void setHobby (String hobby) { this .hobby = hobby; } @Override public String toString () { // 覆盖重写祖宗Object类的toString方法 return "Son{" + "hobby='" + hobby + '\'' + '}' ; }

父子类执行顺序

new子类,看看调用顺序是怎样的?答案是:先有父亲,再有儿子。代码层面,也是在构造方法中或隐藏或显式地调用super父类的构造方法,优先对其父类进行初始化。

public static void main (String[] args) { Son son = new Son ( "Father Mike" , "Basketball" ); String programming = son.study( "Programming" ); System.out.println(programming); } // 输出 父类有参构造方法 子类有参构造方法 正在学习Programming // 结论: 1 、先执行父类构造 2 、再执行子类构造(子类构造引用了 super (xx)或 super ())

内存分析

Java数据内存分析,基本数据类型存储在栈,引用类型new出来的对象和数组存储在堆中。一个个方法执行的过程,是进栈压栈的过程,每个方法调用都会生成一个独有的stack frame,在这片空间进行逻辑运算,而基本类型的局部变量就存储在这,生命周期随方法调用结束而结束;若涉及的引用类型变量,如果是局部变量,那么也就成为了无引用对象,会被后台的GC线程回收;而从入参而来的引用类型变量,则还保留在调用者的空间上,有引用,改变的仅仅是对象属性的值,而引用(对象地址值)则没变化。

一个类new出多个对象,每个对象都有其自己的成员属性;而成员方法的调用则是公用的,毕竟脱胎于同一个类。

Person p1 = new Person ( "water" ); // p1 指向已经初始化name=water的对象空间,0x001 Person p2 = null ; // p2 指向null,表示空,不代表任何具体对象引用 p2 = p1; // p2 是变量,可以随意变换存储的值,指向了p1所指向的对象,也是 0x001 p2.setName( "Bob" ); // 所以p2也就可以操控0x001对象的行为,改变属性name值 String name = p2.getName(); // 此时name已经被改变为Bob System.out.println(name); // 输出 Bob System.out.println(p == p2); // true

super关键字

super表示超类,父类,用于区分子类和父类;super()表示父类的无参构造方法,super(xx)表示父类的有参构造方法,同时super.name父类属性 与name入参属性的区分。

小结

  • extends继承关键字
  • 子类可以继承父类的一切,但并不一定能访问所有资源,遵循权限规则
  • 继承的属性、行为,可以传递,意味着子类可以使用祖宗类的能力
  • Object类为默认祖宗类
  • 所有类都有父类
  • 按类向上寻找,最终都能找到Object
  • new 子类时,先调用父类的构造方法,初始化父类属性,才到子类
  • 本质是子类拥有父类的空间的引用(不是一个显式对象),复用父类代码
  • 子类可以覆盖重写父类的方法,并且修饰权限不能小于父类的
  • Java是值传递,对象有一个十六进制的地址值,代表整个对象,指向具体的对象

六、抽象类

什么是抽象类?

在 Java 中,使用 abstract 关键字修饰的类称为抽象类。抽象类不能被实例化(即不能通过 new 创建对象),它的主要作用是作为其他类的基类,提供共性逻辑和接口定义。

public abstract class Animal { // 可以有构造函数、成员变量、具体方法等 }

抽象类的核心特点

特点 说明
不能实例化 new AbstractClass() 是非法的
可以包含抽象方法 如: public abstract void run();
可以包含具体方法 支持实现业务逻辑
子类必须实现抽象方法 否则子类也必须声明为抽象类
构造函数可用 抽象类可以有构造函数,用于初始化成员
继承关系单一 Java 是单继承语言,只能继承一个抽象类

抽象类 vs 普通类

对比项 抽象类 普通类
是否能被实例化 ❌ 否 ✅ 是
是否可含抽象方法 ✅ 是 ❌ 否
是否必须被继承 ❌ 否 ❌ 否
是否有构造函数 ✅ 是 ✅ 是
是否支持多继承 ❌ 否 ❌ 否

抽象类的常见用途

模板方法设计模式(Template Method Pattern)

这是抽象类最经典的使用方式。父类中定义算法骨架,将不确定步骤延迟到子类实现。

  • 确定步骤:由抽象类统一实现。
  • 不确定步骤:声明为抽象方法,由子类实现。
  • 钩子方法:提供默认实现,子类可选择性重写。
  • 强制实现方法:不提供实现,调用时抛异常,子类若需使用必须重写。
工具类封装(防误用)

有时候我们希望某个类只提供静态方法,不允许创建实例。此时可以用抽象类来“封印”构造器:

public abstract class CollectionUtil { public static <T> boolean isEmpty (Collection<T> collection) { return collection == null || collection.isEmpty(); } }

⚠️ 小贴士:也可以用私有构造器 + final 类实现类似效果,但抽象类更直观。

模板方法实战案例

定义抽象类 AbstractInvoke
import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 抽象模板类,封装通用调用流程: * 验证请求 -> 转换请求 -> 执行调用 -> 校验结果 -> 记录日志 */ public abstract class AbstractInvoke { private static final Logger log = LoggerFactory.getLogger(AbstractInvoke.class); // 模板方法,定义了调用的标准流程 public Object invoke (Object request) { verify(request); Object convert = convertRequest(request); log.info( "请求参数{}" , convert); Object response = doInvoke(convert); Object result = verifyResult(response); log.info( "响应结果{}" , result); return result; } // 钩子方法:验证请求,子类可选择性重写 protected void verify (Object request) { // 默认空实现 } // 请求转换方法,由子类实现 protected abstract Object convertRequest (Object request) ; // 抽象方法:执行实际调用,子类必须实现 protected abstract Object doInvoke (Object convertedRequest) ; // 响应校验方法,子类可覆盖扩展 protected Object verifyResult (Object response) { if (response == null ) { throw new RuntimeException ( "响应为空" ); } return response; } // 获取重试次数,默认3次,子类可重写 protected int getRetry () { return 3 ; } // 描述方法,若子类未重写则抛异常,表示“必须重写才能使用” public String desc () { throw new RuntimeException ( "desc 方法必须被重写" ); } }
子类实现示例

a. 一般调用场景

class NormalInvoke extends AbstractInvoke { @Override public Object doInvoke (Object convertedRequest) { return "Normal Response" ; } @Override public Object convertRequest (Object request) { return "Normal Converted Request" ; } @Override public String desc () { return "normal method invoke" ; } }

b. 重要调用场景

class ImportantInvoke extends AbstractInvoke { @Override public Object doInvoke (Object convertedRequest) { return "Important Response" ; } @Override public Object convertRequest (Object request) { return "Important Converted Request" ; } @Override public void verify (Object request) { // 增加额外校验逻辑 System.out.println( "ImportantInvoke: performing enhanced validation..." ); } @Override public int getRetry () { return 5 ; // 更高重试次数 } @Override public String desc () { return "important method invoke" ; } }

最佳实践

实践 说明
定义算法骨架 使用模板方法固定流程,提升可维护性
抽象方法明确职责 强制子类完成核心逻辑
钩子方法灵活扩展 提供默认行为,子类可选覆写
异常抛出式方法 父类不实现,子类如需使用必须重写
工具类防误用 利用抽象类无法实例化的特性,防止滥用
命名规范清晰 抽象类命名建议以 Abstract 开头,如 AbstractService , AbstractHandler

小结

  • 抽象类是面向对象编程的重要组成部分,适用于提取公共逻辑、定义接口契约。
  • 模板方法模式是其最经典的应用,通过定义算法骨架提高代码复用性和扩展性。
  • 抽象类不一定含有抽象方法,但含有抽象方法的类必须是抽象类。
  • 抽象类除了抽象方法,还可以包含默认实现、钩子方法、异常抛出方法等多种形式。
  • 抽象类不能被实例化,但其组成结构与普通类一致(属性、方法、构造函数等)。

七、接口

什么是接口?

在 Java 中,接口(Interface) 是一种更高层次的抽象机制。它定义了一组行为规范,不关心这些行为如何实现,只关注方法签名和返回值。类通过 implements 实现接口,从而具备某种能力。

public interface Person { String getName () ; void setName (String name) ; int getAge () ; void setAge ( int age) ; void run () ; }

JDK8 及以后版本中,接口支持默认方法、静态方法和私有方法,使其更加灵活。

接口的核心特点

特性 描述
所有方法默认是 public abstract 即使省略修饰符
支持常量定义 默认 public static final
不可实例化 不能使用 new Interface()
多实现 一个类可以实现多个接口
方法无实现(JDK8前) JDK8+ 支持默认方法和静态方法
从 JDK9 起支持私有方法 用于辅助默认方法实现

接口 vs 抽象类

对比项 接口 抽象类
方法实现 JDK8+ 前只有抽象方法 可以有具体方法
成员变量 必须是 public static final 常量 可以是任意类型
构造函数 没有构造函数 有构造函数
继承关系 支持多实现 单继承
访问权限 所有方法默认 public 支持 protected , private 等
设计目的 表示“能做什么”(can-do) 表示“是什么”(is-a)

接口的组成演变(JDK 版本演进)

JDK 版本 新增特性 说明
JDK1.0 - JDK7 只有抽象方法和常量 最纯粹的抽象
JDK8 默认方法(default)、静态方法 提供默认实现,增强扩展性
JDK9 私有方法 辅助默认方法内部逻辑复用

示例:JDK8+ 接口新特性

public interface Logger { // 抽象方法 void log (String message) ; // 默认方法 default void info (String message) { log( "[INFO] " + message); } // 静态方法 static void warn (String message) { System.out.println( "[WARN] " + message); } // 私有方法(JDK9+) private void formatLog (String msg) { System.out.println( "Formatted: " + msg); } }

接口的使用方式

定义行为规范
interface Person { String getName () ; void setName (String name) ; int getAge () ; void setAge ( int age) ; void run () ; }

类必须实现所有抽象方法,否则该类也必须声明为抽象类。

使用接口作为常量类(推荐替代方案)
interface MyNumber { int ONE = 1 ; int TWO = 2 ; }

更推荐使用枚举或普通类封装常量,避免接口被滥用。

接口的实现类

a. 抽象类部分实现接口

public abstract class AbstractPerson implements Person { @Override public void run () { System.out.println( "Running..." ); } }

b. 具体实现类继承抽象类并实现接口

public class RichPerson extends AbstractPerson implements Person { @Override public String getName () { return "rich" ; } @Override public void setName (String name) { // ... } @Override public int getAge () { return 30 ; } @Override public void setAge ( int age) { // ... } }

注:implements Person 可省略,但保留有助于明确表达类的行为契约。

解决接口冲突问题

当多个接口存在同名方法时:

  • 实现类只需重写一次即可。
  • 如果想只实现其中某些方法,可以通过中间类做空实现来规避。

示例:中间类处理接口方法

interface A { void methodA () ; void commonMethod () ; } interface B { void methodB () ; void commonMethod () ; } abstract class Middle implements A , B { @Override public void commonMethod () {} } class MyClass extends Middle { @Override public void methodA () { System.out.println( "Only implement A" ); } }

接口的设计哲学

“我只给你们这些资源,你们给我打造一个赚钱的产品,不管你们怎么实现。”

这句话形象地描述了接口的本质:上层设计只提供方法签名,下层实现自由发挥。接口是系统间解耦的关键手段。

接口的最佳实践

实践 说明
明确职责 接口应代表一类行为,如 Runnable , Serializable
小而专 接口方法不宜过多,建议单一职责原则
合理使用默认方法 避免破坏已有实现的前提下扩展功能
不要滥用作常量类 推荐使用 enum 或 final class 替代
避免多重实现冲突 若接口方法重复,需谨慎设计
命名规范 接口命名建议以形容词或动词开头,如 Runnable , Comparable

三大实战场景

解耦设计(Decoupling)

场景描述:通过接口定义行为规范,实现类可自由替换,调用方无需关心具体实现,降低模块之间的依赖关系。

示例:支付系统解耦设计

// 定义支付接口 public interface Payment { void pay ( double amount) ; } // 支付宝支付实现 public class Alipay implements Payment { @Override public void pay ( double amount) { System.out.println( "使用支付宝支付:" + amount + " 元" ); } } // 微信支付实现 public class WechatPay implements Payment { @Override public void pay ( double amount) { System.out.println( "使用微信支付:" + amount + " 元" ); } } // 上层业务类 public class OrderService { private Payment payment; public OrderService (Payment payment) { this .payment = payment; } public void checkout ( double amount) { payment.pay(amount); } }

使用方式:

OrderService orderService = new OrderService ( new Alipay ()); orderService.checkout( 100 ); // 输出:使用支付宝支付... orderService = new OrderService ( new WechatPay ()); orderService.checkout( 200 ); // 输出:使用微信支付...

这种设计使得 OrderService 与具体支付方式完全解耦,只需更换实现即可扩展功能。

插件化架构(Plugin Architecture)

场景描述:通过接口定义统一的插件规范,外部开发者可以按照接口实现自己的插件逻辑,主程序动态加载插件。

示例:日志插件系统

// 插件接口 public interface LogPlugin { void log (String message) ; } // 控制台日志插件 public class ConsoleLogPlugin implements LogPlugin { @Override public void log (String message) { System.out.println( "[CONSOLE] " + message); } } // 文件日志插件 public class FileLogPlugin implements LogPlugin { @Override public void log (String message) { // 实际写入文件操作略 System.out.println( "[FILE] " + message); } } // 插件管理器 public class PluginManager { private List<LogPlugin> plugins = new ArrayList <>(); public void addPlugin (LogPlugin plugin) { plugins.add(plugin); } public void executeLog (String message) { for (LogPlugin plugin : plugins) { plugin.log(message); } } }

使用方式:

PluginManager manager = new PluginManager (); manager.addPlugin( new ConsoleLogPlugin ()); manager.addPlugin( new FileLogPlugin ()); manager.executeLog( "系统启动成功" ); // 输出: // [CONSOLE] 系统启动成功 // [FILE] 系统启动成功

主程序无需知道插件如何实现,只需按接口调用即可,便于构建灵活、可扩展的插件系统。

回调机制(Callback)

场景描述:通过接口传递一个“任务完成后的处理方法”,常用于异步操作、事件监听等场景。

示例:网络请求回调

// 回调接口 public interface HttpCallback { void onSuccess (String response) ; void onFailure (String error) ; } // 模拟网络请求工具 public class HttpClient { public void get (String url, HttpCallback callback) { new Thread (() -> { try { Thread.sleep( 1000 ); // 模拟网络延迟 if ( "http://success" .equals(url)) { callback.onSuccess( "响应数据" ); } else { callback.onFailure( "请求失败" ); } } catch (InterruptedException e) { callback.onFailure( "请求中断" ); } }).start(); } }

使用方式:

HttpClient client = new HttpClient (); client.get( "http://success" , new HttpCallback () { @Override public void onSuccess (String response) { System.out.println( "请求成功:" + response); } @Override public void onFailure (String error) { System.out.println( "请求失败:" + error); } });

这种方式实现了异步非阻塞调用,并将结果处理交给调用者决定,广泛应用于 GUI、事件驱动、异步编程中。

总结对比
场景 描述 优点
解耦设计 将业务逻辑与实现分离 提高可维护性、可测试性
插件化 动态加载第三方功能模块 易于扩展、支持热插拔
回调机制 异步执行完成后通知调用方 避免阻塞、提高并发能力
最佳实践建议
实践 说明
接口命名清晰 如 Runnable , Comparable ,体现行为意图
单一职责原则 一个接口只定义一类行为
合理使用默认方法 扩展功能时避免破坏已有实现
不要滥用作常量容器 推荐使用枚举或 final class 替代
接口组合优于继承 多个接口组合比多重继承更灵活安全

小结

关键点 说明
接口是更纯粹的抽象 只定义方法签名,不包含实现(JDK8+ 例外)
必须实现所有抽象方法 否则类必须声明为抽象类
接口是行为契约 表示“能做什么”,不是“是什么”
支持多实现 Java 类可以实现多个接口
支持默认方法/静态方法/私有方法 自 JDK8 起增强灵活性
可用于设计解耦、插件化、回调等场景 是构建模块化系统的基石

八、static关键字

什么是 static

在 Java 中,static 是一个关键字,用来修饰 类的成员变量、方法、代码块和内部类。它表示该成员属于类本身,而不是类的实例(对象)。

简单理解:被 static 修饰的成员是“类级别的”,而非“对象级别的”。

static 用法

成员类型 示例 说明
静态变量 public static int count; 类的所有实例共享一份值
静态方法 public static void log(); 只能访问静态成员,不能直接访问非静态成员
静态代码块 static { ... } 类加载时执行一次,用于初始化静态资源
静态内部类 public static class Inner { ... } 不依赖外部类实例即可创建

static 加载机制

JVM 类加载机制:懒加载(Lazy Loading),JVM 在首次使用某个类时才会加载该类。使用方式包括:

  • 创建对象(如 new Person()
  • 调用类的静态方法或访问静态变量
  • 反射调用类
  • 初始化子类(会先初始化父类)

类加载顺序原则,当类被加载时,JVM 按照以下顺序进行初始化:

父类静态成员 → 子类静态成员 → 父类构造器 → 子类构造器

实战分析

class Static1 { static { System.out.println( "静态1号加载" ); } } class Static2 { static { System.out.println( "静态2号加载" ); } public Static2 () { System.out.println( "静态2号构造执行" ); } } class Static3 { static String name = "water" ; static Static2 s2 = new Static2 (); } public class StaticTest { public static void main (String[] args) { String name = Static3.name; System.out.println(name); } }

输出结果:

静态2号加载 静态2号构造执行 water

分析过程

    1. main 方法中访问了 Static3.name,触发 Static3 类的加载。
    1. 加载 Static3 时,优先初始化所有静态变量:name = "water":基本赋值操作,无复杂逻辑;s2 = new Static2():需要创建 Static2 实例;触发 Static2 类加载;
  • 执行构造函数:输出 "静态2号构造执行"

  • 执行静态代码块:输出 "静态2号加载"

    1. 最后输出 name 值为 "water"

使用场景

场景 说明 示例
工具类封装 提供静态方法,无需实例化 StringUtils.isEmpty(...)
全局状态管理 维护类级别状态,如计数器 public static int userCount;
单例模式实现 控制类的唯一实例 private static Singleton instance;
缓存数据 保存类级别的缓存信息 private static Map cache;
静态工厂方法 替代构造器创建对象 public static User createDefaultUser()

注意事项

注意点 说明
无法访问非静态成员 静态方法/代码块不能直接访问非静态字段或方法
不可重写 静态方法不能被重写,只能被隐藏(通过子类定义同名静态方法)
不参与多态 静态方法绑定的是编译时类型,不是运行时类型
线程安全问题 多线程下修改静态变量需注意同步控制
内存泄漏风险 静态引用长生命周期对象可能导致内存泄漏

推荐原则

  • 尽量避免全局状态滥用;
  • 工具类建议配合 final class 和私有构造器使用;
  • 静态方法应保持无副作用,便于测试和复用。

1、尽量避免全局状态滥用,全局状态通常是指通过 public static 变量暴露出来的可变数据,例如:

public class GlobalState { public static int count = 0 ; }

这类变量在程序的任何地方都可以被修改,容易导致:

  • 状态不可控
  • 多线程下并发问题
  • 单元测试困难
  • 难以维护和调试

❌ 反例:滥用静态全局变量

public class UserManager { public static List<User> users = new ArrayList <>(); public static void addUser (User user) { users.add(user); } }

问题分析:

  • 全局可变状态 users 可能被任意代码修改。
  • 多线程环境下可能出现并发异常。
  • 测试时难以控制初始状态。
  • 不利于模块解耦。

✅ 正例:封装状态 + 提供接口访问

public class UserManager { private static final List<User> users = new CopyOnWriteArrayList <>(); public static void addUser (User user) { users.add(user); } public static List<User> getAllUsers () { return new ArrayList <>(users); // 返回副本,防止外部修改 } }

改进点:

  • 使用 private static 封装状态,避免外部直接访问。
  • 提供只读方法或受控操作,增强安全性。
  • 更适合多线程环境(如使用线程安全集合)。

2、工具类建议配合 final class 和私有构造器使用,工具类一般只包含静态方法,不应该被实例化。如果不做限制,可能会出现如下情况:

StringUtils utils = new StringUtils (); // 没有意义,浪费内存

❌ 反例:未限制实例化的工具类

public class StringUtils { public static boolean isEmpty (String str) { return str == null || str.isEmpty(); } }

问题分析:

  • 可以随意 new StringUtils(),虽然不会出错,但语义上不合理。
  • 容易误导其他开发者误用。

✅ 正例:使用 final class + 私有构造器

public final class StringUtils { private StringUtils () { throw new UnsupportedOperationException ( "工具类不能实例化" ); } public static boolean isEmpty (String str) { return str == null || str.isEmpty(); } }

改进点:

  • final 类防止被继承;
  • 私有构造器防止被实例化;
  • 抛异常提示开发者不要尝试创建对象;
  • 更加规范、专业、易于维护。

3、静态方法应保持无副作用,便于测试和复用无副作用的静态方法指的是:

  • 输入相同,输出一定相同;
  • 不依赖外部状态;
  • 不修改外部变量;
  • 不产生隐藏行为(如打印日志、修改数据库等);

这样可以保证方法是纯函数式的,便于:

  • 单元测试;
  • 并发调用;
  • 日后重构;
  • 被多个模块安全复用。

❌ 反例:带副作用的静态方法

public class Logger { private static int logCount = 0 ; public static void log (String message) { System.out.println(message); logCount++; } }

问题分析:

  • 方法内部修改了静态变量 logCount,造成副作用;
  • 同一个输入多次调用结果不同;
  • 不利于并发或单元测试;
  • 隐藏状态,增加维护成本。

✅ 正例:无副作用的静态方法

public final class MathUtils { private MathUtils () {} public static int add ( int a, int b) { return a + b; } public static double square ( double x) { return x * x; } }

优点:

  • 所有方法都是无副作用的;
  • 输入输出明确;
  • 易于测试(比如使用 JUnit);
  • 可放心用于并发场景;
  • 可缓存、可并行执行。

总结对比

对比项 普通成员 静态成员
所属对象 属于对象 属于类
生命周期 与对象一致 与类一致,类加载即存在
访问权限 可访问静态和非静态成员 只能访问静态成员
是否可以重写 ✅ 是 ❌ 否(只能隐藏)
是否支持多态 ✅ 是 ❌ 否
是否推荐滥用 ❌ 否 ❌ 否(需谨慎使用)

设计模式应用

模式 使用方式
单例模式 使用 private static 实例 + 静态方法获取
工厂模式 静态方法返回实例
工具类设计 静态方法 + 私有构造器防止实例化
缓存机制 静态 Map 缓存数据

九、final关键字

final 关键字概述

  • 含义:表示“最终的”、“不可更改的”
  • 作用对象:类、方法、变量(包括局部变量、成员变量、参数)
  • 目的
  • 提高程序安全性
  • 防止继承或重写
  • 增强代码可读性和维护性
  • 优化 JVM 性能(如内联)

final 的使用场景与作用

1. 修饰类(类不能被继承)

public final class MathUtils { // ... }

❌ 不允许有子类:

// 编译错误:Cannot subclass final class 'MathUtils' class MyMath extends MathUtils {}

典型应用StringInteger 等包装类、工具类、安全类。

2. 修饰方法(方法不能被重写)

public class Animal { public final void eat () { System.out.println( "Animal is eating" ); } }

❌ 子类不能重写该方法:

class Dog extends Animal { // 编译错误:Overridden method is final public void eat () {} }

用途

  • 防止子类修改关键逻辑
  • 类似设计模式中的模板方法
  • 可用于性能优化(JVM 可以内联)

3. 修饰变量(值不变)

(1) 基本类型:值不能变

final int MAX_AGE = 100 ; MAX_AGE = 101 ; // 编译错误:cannot assign a value to final variable MAX_AGE

(2) 引用类型:引用地址不能变,但对象内容可以变

final List<String> names = new ArrayList <>(); names.add( "Alice" ); // 允许添加元素 names = new ArrayList <>(); // ❌ 编译错误:不能重新赋值

初始化时机

  • 声明时直接赋值
  • 构造器中赋值(适用于实例变量)
  • 实例初始化块赋值
  • 静态变量可在静态初始化块中赋值

4. 修饰方法参数(参数不可变)

public void print ( final String msg) { msg = "hello" ; // ❌ 编译错误 }

用途

  • 明确参数不可修改,增强方法安全性
  • 在匿名内部类中访问外部方法参数时必须为 final(JDK8 后隐式 final)

5. 与线程安全

  • final 字段保证了初始化安全性(Final Field Safety)
  • JVM 会确保 final 字段在构造完成之后对其他线程可见,防止重排序问题
public class User { private final String name; public User (String name) { this .name = name; } public String getName () { return name; } }

即使没有 synchronizedvolatile,多线程访问 name也是安全的。

设计模式结合使用

模式 使用方式
不可变对象 所有字段设为 final ,构造函数初始化
单例模式 final static 实例 + 静态内部类实现懒加载
模板方法 父类定义 final 方法,子类不能重写
工具类 类设为 final ,构造器私有,防止继承

final 与常量定义

  • 推荐将常量定义为 public static final
public class Constants { public static final String APP_NAME = "MyApp" ; }

注意事项:

  • final 常量是编译时常量,会被编译器优化(内联),如果多个类引用此常量,在修改后需重新编译所有引用类。
  • 若希望运行时动态获取值,避免使用 final

finalstatic 的区别

特性 final static
表示意义 不可变 属于类本身
修饰对象 变量、方法、类 变量、方法、内部类、代码块
是否绑定实例 否(变量) 是(非静态)
多线程安全 final 字段安全 static 需手动同步

常见误区

误区 正确认识
final List 不可变 错,只是引用不可变,内容仍可变
final 一定能提高性能 错,JVM 会自动优化,通常无需手动加
final 能替代 private 错,两者互不冲突,应配合使用
final 方法一定不会被重写 对,但可通过反射绕过(仅限测试/破坏性行为)

推荐使用场景

场景 是否建议使用 final
不可变类字段
单例实例
方法参数 ✅(增强代码可读性)
工具类 ✅(类 + 构造器私有)
模板方法
临时变量 ⚠️ 视情况而定
多态需要重写的方法 ❌ 不适合

代码示例

不可变类+单列+final参数

public final class Person { // 类不可继承 private final String name; // 字段不可变 private final int age; public Person ( final String name, final int age) { // 参数不可变 this .name = name; this .age = age; } public String getName () { return name; } public int getAge () { return age; } // 单例持有者 private static class Holder { static final Person INSTANCE = new Person ( "Tom" , 20 ); // final 实例 } public static Person getInstance () { return Holder.INSTANCE; } }

小结

使用位置 作用 示例
类前 类不能被继承 public final class A {}
方法前 方法不能被重写 public final void doSomething()
变量前 变量值不能变 private final int count = 10;
方法参数 参数不可变 void method(final String s)
内部类 成员内部类可访问 final 变量 final int x = 10; new Runnable() {...}

十、多态性

什么是多态?

多态(Polymorphism) 是面向对象编程的三大特性之一(封装、继承、多态),其含义是:

同一种类型,在不同的上下文中表现出不同的行为。

核心特征:

  • 一个接口,多种实现
  • 一个父类引用指向不同子类对象
  • 方法重写 + 运行时绑定

表现形式

类型 描述 示例
接口多态 一个接口被多个类实现 Animal a = new Dog();
方法重载 同名方法参数列表不同 eat(String food) vs eat(String food, String drink)
继承多态 父类引用指向子类对象 Animal a = new Cat();

多态本质

“编译看左,运行看右”,这是理解多态的关键口诀。

编译阶段

  • 看变量声明的类型(左边)
  • 只能调用该类型中定义的方法和属性

运行阶段

  • 看实际创建的对象类型(右边)
  • 执行的是该对象的方法体(如果子类重写了)
Animal animal = new Dog (); animal.run(); // 调用的是 Dog 的 run()

应用场景

    1. 接口作为方法参数(解耦)
public void testAnimal (Animal animal) { animal.run(); // 支持所有 Animal 实现类 }
    1. 抽象类/接口统一处理多种行为
List<Animal> animals = Arrays.asList( new Dog (), new Cat ()); animals.forEach(Animal::run); // 自动适配具体实现
    1. 配合策略模式、工厂模式等设计模式使用
Payment payment = PaymentFactory.getPayment( "alipay" ); payment.pay( 100 ); // 不同支付方式自动匹配

实战示例

接口定义

interface Animal { void run () ; }

子类实现

class Dog implements Animal { @Override public void run () { System.out.println( "Dog run" ); } } class Cat implements Animal { @Override public void run () { System.out.println( "Cat run" ); } public void talk () { System.out.println( "Cat talks" ); } } class HelloCat extends Cat { @Override public void talk () { System.out.println( "HelloCat say hello" ); } public void eat (String foodName) { System.out.println( "吃 " + foodName); } public void eat (String foodName, String drinkName) { System.out.println( "吃 " + foodName + ";喝 " + drinkName); } }

测试类

public class PolymorphismTest { public static void main (String[] args) { Animal dog = new Dog (); Animal cat = new Cat (); Animal helloCat = new HelloCat (); testAnimal(dog); // 输出: Dog run testAnimal(cat); // 输出: Cat run testAnimal(helloCat); // 输出: Cat run if (cat instanceof Cat) { Cat myCat = (Cat) cat; myCat.talk(); // 输出: Cat talks } if (helloCat instanceof Cat) { Cat myHelloCat = (Cat) helloCat; myHelloCat.talk(); // 输出: HelloCat say hello } if (helloCat instanceof HelloCat) { HelloCat dearHelloCat = (HelloCat) helloCat; dearHelloCat.eat( "鱼" ); // 输出: 吃 鱼 dearHelloCat.eat( "鸡腿" , "可乐" ); // 输出: 吃 鸡腿;喝 可乐 } } public static void testAnimal (Animal animal) { animal.run(); } }

向下转型(Downcasting)

当需要访问子类扩展的方法时,必须进行向下转型。

✅ 安全做法:配合 instanceof 判断

if (animal instanceof Cat) { Cat cat = (Cat) animal; cat.talk(); }

❌ 错误做法:直接强制转换

Cat cat = (Cat) animal; // 若 animal 实际不是 Cat 类型,会抛 ClassCastException

最佳实践

优势 说明
提高代码复用性 通过统一接口操作不同对象
增强扩展性 新增子类无需修改已有逻辑
解耦合 上层逻辑不依赖具体实现类
支持策略化设计 动态切换行为,符合开闭原则

最佳实践建议:

  • 尽量使用接口或抽象类作为方法入参
  • 方法内部如需子类特有方法,应先判断类型再转型
  • 多态结合设计模式(如策略、模板、工厂)效果更佳
  • 避免过多依赖 instanceof,考虑重构为多态结构

多态 vs 重载 vs 重写 对比

特性 多态 重写(Override) 重载(Overload)
发生位置 父类与子类之间 子类覆盖父类方法 同一类中方法名相同
参数要求 不变 参数不变 参数不同
返回值 不变 可协变返回 必须不同签名
访问权限 不能缩小 不能比父类更严格 无关
异常 不能抛出新异常 不能比父类更宽泛 无关
决定时机 运行时动态绑定 运行时决定 编译时决定

设计模式

在实际业务开发中,基于多态构建规则引擎和状态机 是一种非常常见且优雅的设计方式。它通过接口或抽象类定义统一行为,子类实现具体逻辑,使得系统具备良好的 扩展性、可维护性和解耦性

举例说明:

  • 基于多态的规则引擎(Rule Engine)
  • 基于多态的状态机(State Machine)
规则引擎

假设我们有一个促销系统,需要根据不同的用户等级(普通用户、VIP 用户、SVIP 用户)应用不同的折扣策略。

核心设计思想

  • 定义一个 DiscountStrategy 接口
  • 每个等级对应一个实现类
  • 使用工厂类动态获取对应的策略
  • 业务层统一调用策略接口方法

    1. 定义策略接口
public interface DiscountStrategy { double applyDiscount ( double price) ; }
    1. 实现不同策略
public class NormalUserDiscount implements DiscountStrategy { @Override public double applyDiscount ( double price) { return price * 0.95 ; // 95折 } } public class VipUserDiscount implements DiscountStrategy { @Override public double applyDiscount ( double price) { return price * 0.85 ; // 85折 } } public class SuperVipUserDiscount implements DiscountStrategy { @Override public double applyDiscount ( double price) { return price * 0.75 ; // 75折 } }
    1. 策略工厂类
public class DiscountFactory { public static DiscountStrategy getDiscountStrategy (String userType) { switch (userType.toLowerCase()) { case "vip" : return new VipUserDiscount (); case "svip" : return new SuperVipUserDiscount (); default : return new NormalUserDiscount (); } } }
    1. 业务使用示例
public class ShoppingCart { private DiscountStrategy discountStrategy; public void setDiscountStrategy (DiscountStrategy discountStrategy) { this .discountStrategy = discountStrategy; } public double checkout ( double totalPrice) { return discountStrategy.applyDiscount(totalPrice); } }
    1. 调用示例
public class Main { public static void main (String[] args) { String userType = "svip" ; DiscountStrategy strategy = DiscountFactory.getDiscountStrategy(userType); ShoppingCart cart = new ShoppingCart (); cart.setDiscountStrategy(strategy); double finalPrice = cart.checkout( 100 ); System.out.println( "最终价格:" + finalPrice); // 输出:75.0 } }

6.优势分析

特点 描述
扩展性强 新增策略只需添加新类,不修改已有代码
解耦清晰 业务逻辑与具体策略分离
可测试性强 每个策略独立,易于单元测试
易于替换 运行时可动态切换策略
状态机

假设我们要处理订单状态流转:新建(NEW)、已支付(PAID)、已发货(SHIPPED)、已完成(COMPLETED),每种状态有不同的操作限制。

核心设计思想:

  • 定义一个 OrderState 抽象类或接口
  • 每个状态为一个子类,实现对应行为
  • 订单对象持有当前状态引用
  • 状态之间可以互相转换

    1. 定义状态接口
public abstract class OrderState { public abstract void pay (Order order) ; public abstract void ship (Order order) ; public abstract void complete (Order order) ; }
    1. 实现各个状态类
public class NewState extends OrderState { @Override public void pay (Order order) { System.out.println( "订单已支付" ); order.setState( new PaidState ()); } @Override public void ship (Order order) { throw new IllegalStateException ( "订单未支付,无法发货" ); } @Override public void complete (Order order) { throw new IllegalStateException ( "订单未支付,无法完成" ); } } public class PaidState extends OrderState { @Override public void pay (Order order) { System.out.println( "订单已支付,请勿重复支付" ); } @Override public void ship (Order order) { System.out.println( "订单已发货" ); order.setState( new ShippedState ()); } @Override public void complete (Order order) { throw new IllegalStateException ( "订单未发货,无法完成" ); } } public class ShippedState extends OrderState { @Override public void pay (Order order) { System.out.println( "订单已支付" ); } @Override public void ship (Order order) { System.out.println( "订单已发货,请勿重复发货" ); } @Override public void complete (Order order) { System.out.println( "订单已完成" ); order.setState( new CompletedState ()); } } public class CompletedState extends OrderState { @Override public void pay (Order order) { System.out.println( "订单已完成,无需支付" ); } @Override public void ship (Order order) { System.out.println( "订单已完成,无需发货" ); } @Override public void complete (Order order) { System.out.println( "订单已完成" ); } }
    1. 定义订单类
public class Order { private OrderState state; public Order () { this .state = new NewState (); } public void setState (OrderState state) { this .state = state; } public void pay () { state.pay( this ); } public void ship () { state.ship( this ); } public void complete () { state.complete( this ); } }
    1. 调用示例
public class Main { public static void main (String[] args) { Order order = new Order (); order.pay(); // 输出:订单已支付 order.ship(); // 输出:订单已发货 order.complete(); // 输出:订单已完成 } }

5.优势分析

特点 描述
状态隔离 每个状态只关注自己的行为,互不影响
行为封装 不同状态的行为由各自类封装
易于扩展 新增状态只需新增类,不修改已有逻辑
可读性强 状态流转清晰直观,便于理解维护
总结对比
功能 规则引擎 状态机
设计模式 策略模式 状态模式
主要结构 接口+多个实现类 抽象类+多个状态子类
行为控制 根据条件选择策略 根据状态变化执行不同行为
应用场景 折扣计算、风控规则、审批流程等 订单状态、任务状态、权限流转等
优点 扩展灵活、逻辑清晰 状态管理集中、避免 if-else
进阶建议
场景 建议
复杂规则组合 结合责任链(Chain of Responsibility)模式
状态流转复杂 引入状态图配置化或数据库驱动
需要动态加载 使用 Spring IOC 容器注入策略/状态类
性能要求高 缓存策略实例、避免频繁创建

小结

关键点 说明
多态是面向对象的核心机制 表现为“一种类型,多种行为”
“编译看左,运行看右” 是理解多态的核心口诀
多态主要体现在接口和继承中 可用于统一处理多种实现
向下转型要配合 instanceof 防止 ClassCastException
多态适用于策略、插件、事件驱动等场景 提高系统灵活性和扩展性

十一、枚举

枚举的本质与特性

  • 枚举本质是类:每个枚举类型都隐式继承自 java.lang.Enum 类。所以不能显式继承其他类(Java 单继承限制)。
  • 线程安全:JVM 在加载时初始化枚举实例,确保其线程安全。
  • 多例模式:枚举本质上是一个有限的多例集合,适用于状态、行为固定的情况。
public enum Season { SPRING, SUMMER, FALL, WINTER; }

等价于:

public final class Season extends Enum <Season> { public static final Season SPRING = new Season (); public static final Season SUMMER = new Season (); ... }
枚举的成员定义与使用
  • 可以定义字段、构造函数、方法、抽象方法等。
  • 构造器必须是私有或默认访问权限。
  • 每个枚举常量可以有自己的实现(如策略枚举)。

示例:带属性和方法的枚举

public enum TradeStatus { PAYING( 1 , "支付中" ), PAID( 2 , "已支付" ), CANCEL( 3 , "取消支付" ); private final int code; private final String desc; TradeStatus( int code, String desc) { this .code = code; this .desc = desc; } public int getCode () { return code; } public String getDesc () { return desc; } public static TradeStatus getByCode ( int code) { return Arrays.stream(values()) .filter(s -> s.code == code) .findFirst() .orElseThrow(() -> new RuntimeException ( "未找到对应交易状态" )); } }
枚举与策略模式结合
  • 枚举中定义抽象方法,每个枚举值提供具体实现,是一种轻量级策略模式实现方式。
public enum Pay { ALIPAY( "alipay" ) { @Override public void pay (Object request) { // 支付宝支付逻辑 } }, WECHAT( "wechat" ) { @Override public void pay (Object request) { // 微信支付逻辑 } }; private final String name; Pay(String name) { this .name = name; } public abstract void pay (Object request) ; }

调用方式:

Pay.ALIPAY.pay(request);
枚举常用方法
方法名 描述
values() 返回枚举数组,包含所有枚举值
valueOf(String) 根据名称获取枚举实例
name() 获取枚举常量名称
ordinal() 获取枚举常量的索引位置
枚举在 switch 中的应用
TradeStatus status = TradeStatus.PAID; switch (status) { case PAYING: System.out.println( "支付中" ); break ; case PAID: System.out.println( "已支付" ); break ; default : System.out.println( "未知状态" ); }
最佳实践
优势 说明
线程安全 JVM 加载保证唯一性
避免魔法值 替代 int 常量,提高可读性和维护性
封装性强 可封装业务逻辑、属性、方法
易扩展 可添加字段、方法、甚至接口实现
序列化安全 枚举的序列化机制自动处理单例问题

数据库字段映射:使用 code字段与数据库状态码对应,便于持久化;提供 getByCode() 方法用于反向查找。

注意事项

  • 不适合频繁变动的状态或大量数据。
  • 枚举不支持动态添加新的枚举值。
  • 枚举类中不能有 abstract 字段。
  • 枚举中可以有抽象方法。
  • 枚举类不能被继承。
单例模式(枚举版)
public enum Singleton { INSTANCE; public void doSomething () { // 实现逻辑 } }

调用:

Singleton.INSTANCE.doSomething();

枚举版单例模式:线程安全,防止反射攻击(通过枚举反编译发现无法创建新实例),并且自动支持序列化/反序列化。

小结
使用场景 示例
状态表示 订单状态、用户角色
分类标记 支付渠道、日志级别
常量管理 错误码、配置项
策略模式 多种支付方式、多种算法
单例模式 工具类、配置中心

十二、内部类

内部类(Inner Class)是定义在另一个类中的类。它与外部类有强关联性,可以访问外部类的成员,包括私有成员。

内部类分类

类型 定义位置 是否静态 特点
成员内部类 外部类中,方法外 可以访问外部类所有成员,必须依附于外部类实例
静态内部类 外部类中,方法外 不依赖外部类实例,只能访问外部类的静态成员
局部内部类 方法或代码块内 作用域仅限于当前方法或代码块
匿名内部类 表达式中 没有类名,用于简化接口/抽象类实现

成员内部类(非静态)

public class Outer { private String name = "Outer" ; public class Inner { public void show () { System.out.println(name); // 可以访问外部类的私有字段 } } public static void main (String[] args) { Outer outer = new Outer (); Outer. Inner inner = outer. new Inner (); // 必须先创建外部类实例 inner.show(); } }

⚠️ 注意:成员内部类不能拥有静态成员(除非是 static final 常量)

静态内部类

public class Outer { private static String name = "Static Outer" ; public static class StaticInner { public void show () { System.out.println(name); // 只能访问外部类的静态成员 } } public static void main (String[] args) { StaticInner inner = new StaticInner (); // 不需要外部类实例 inner.show(); } }

常用于单例模式(懒加载 + 线程安全)

public class Service { private Service () {} private static class ServiceHolder { static final Service INSTANCE = new Service (); // JVM保证线程安全 } public static Service getInstance () { return ServiceHolder.INSTANCE; } }

局部内部类

定义在方法或代码块中,作用域受限。

public class Test { public void method () { class LocalInner { void sayHi () { System.out.println( "Hello from local inner class" ); } } new LocalInner ().sayHi(); } }

⚠️ 注意:

  • 不能使用访问修饰符(如 private, public
  • 可以访问外部类成员,也可以访问方法中的 final 变量(JDK8+ 可隐式 final)

匿名内部类(Anonymous Inner Class)

没有类名,通常用于一次性使用的接口或抽象类实现。

interface Talkable { void talk () ; } public class Main { public static void talkSome (Talkable talk) { talk.talk(); } public static void main (String[] args) { talkSome( new Talkable () { @Override public void talk () { System.out.println( "说话好听" ); } }); } }

Lambda 表达式是对匿名内部类的简化写法(函数式接口)

talkSome(() -> System.out.println( "说话好听" ));

应用场景

场景 示例说明
单例模式 使用静态内部类实现线程安全、懒加载的单例
事件监听器 GUI 开发中使用匿名内部类处理点击事件
箖合对象 成员内部类可紧密耦合外部类逻辑
简化回调 匿名内部类用于网络请求回调、Runnable 等
工具类封装 静态内部类作为工具辅助类(如 Builder 模式)

注意事项

事项 说明
访问权限 成员内部类可以访问外部类所有成员,包括私有
静态限制 成员内部类不能定义静态成员(除非是 final static 常量)
实例绑定 成员内部类必须绑定一个外部类实例才能创建
编译生成 匿名内部类编译后会生成类似 Main$1.class 的文件
Lambda 替代 匿名内部类可用 Lambda 表达式替代(适用于函数式接口)
性能开销 内部类会增加类数量和内存占用,避免滥用

Builder 模式 + 静态内部类

public class User { private final String name; private final int age; private User (Builder builder) { this .name = builder.name; this .age = builder.age; } public static class Builder { private String name; private int age; public Builder setName (String name) { this .name = name; return this ; } public Builder setAge ( int age) { this .age = age; return this ; } public User build () { return new User ( this ); } } } // 使用 User user = new User .Builder() .setName( "Tom" ) .setAge( 20 ) .build();

小结

类型 是否依赖外部类实例 是否可访问外部类成员 是否常用
成员内部类 ✅ 常用
静态内部类 仅静态成员 ✅ 常用
局部内部类 ❌ 较少
匿名内部类 ✅ 常用

内部类命名建议

  • 静态内部类:推荐使用大驼峰命名法,如 ServiceHolder
  • 局部/匿名内部类:无需显式命名(匿名类)
  • 功能明确:内部类应职责单一,避免复杂逻辑嵌套

十三、总结

Java面向对象的内容非常多,但又非常重要;因为只有积极锻炼面向对象的思维,才能助力写出高内聚低耦合的代码。弄懂:类与对象、封装性、继承性、多态性、抽象类、接口、内部类、this、super、static、final等核心概念是重中之重,保证语法正确,分辨最佳使用场景,警惕各种坑,才能写出优雅安全的代码。这也是读懂其他框架源码的基础,源码也是使用这些核心概念,经过设计者使用各种设计模式一步步反复迭代锤炼起来的。

万丈高楼平台起,夯实基础才能更快登高望远!

时不时回顾本文内容,开发过程中设计类、设计变量时遇到的问题,基本都能在本文找到答案。多敲代码,多思考,多检查。review代码,问问自己:

这里使用接口设计、抽象类设计是不是更有益于解耦,更利于动态扩展;使用static关键字是不是多实例共享,是不是节省资源,提高性能了;使用final是不是值初始化后就不变了,这样修饰变量,该变量在多线程环境下是不是更安全了。

多看看自己的代码,结合Java面向对象的各个核心知识点的最佳实践,一步步修复调整优化,争取写出健壮优雅的漂亮代码,如诗一样。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐