final在Java中扮演着"终结者"的角色,它可以从三个维度对代码进行约束:
类级别控制:当final修饰类时,这个类将成为继承体系的终点站。比如Java中的String类就被设计为final,确保所有字符串对象都遵循统一的行为规范。我在实际开发中,通常会将工具类(如DateUtils、StringHelper)声明为final,防止被意外继承导致工具方法被篡改。
方法级别保护:final方法如同上锁的保险箱,子类无法修改其内部实现。这在模板方法模式中尤为关键——父类通过final限定核心算法步骤,同时保留可扩展的抽象方法供子类实现。例如:
java复制public abstract class PaymentProcessor {
// 支付流程不可篡改
public final void processPayment() {
validate();
deductFunds();
updateLedger();
}
protected abstract void deductFunds();
}
java复制final List<String> colors = new ArrayList<>();
colors.add("Red"); // 合法操作
colors = new ArrayList<>(); // 编译错误
static final组合形成的常量,在项目中通常承担着配置中心的角色。根据多年项目经验,我总结出常量的最佳实践:
java复制// 数据库配置常量
public class DbConstants {
public static final String JDBC_URL = "jdbc:mysql://localhost:3306";
public static final int MAX_POOL_SIZE = 20;
}
// 业务规则常量
public class BizRules {
public static final int MAX_LOGIN_ATTEMPTS = 5;
}
性能真相:虽然常说常量会被"宏替换",但实际编译后的class文件中仍保留常量符号引用。真正的优化发生在JIT编译阶段,热点代码中的常量会被直接内联。
类型选择:除了基本类型,枚举往往是更好的常量载体。比如状态码定义:
java复制// 反例:整数常量
public static final int ORDER_PENDING = 1;
public static final int ORDER_PAID = 2;
// 正例:枚举常量
public enum OrderStatus {
PENDING, PAID, CANCELLED
}
重要提示:Android开发中要避免在接口中定义常量,这会导致所有实现类携带常量引用。应该使用专门的常量类或枚举。
设计模式不是银弹,而是前辈们总结的"问题-解决方案"对。以单例模式为例,它解决的是资源协调问题:当多个线程需要共享某个服务时(如数据库连接池),单例确保大家访问的是同一实例。
传统饿汉式写法存在类加载即初始化的缺点,现代改进方案结合了静态内部类的特性:
java复制public class DatabasePool {
private static class Holder {
static final DatabasePool INSTANCE = new DatabasePool();
}
private DatabasePool() {
// 初始化连接池
}
public static DatabasePool getInstance() {
return Holder.INSTANCE;
}
}
这种实现既保证了线程安全,又实现了延迟加载——只有当首次调用getInstance()时才会加载Holder类并创建实例。
面试常考的DCL(Double-Checked Locking)写法需要特别注意volatile关键字:
java复制public class Logger {
private static volatile Logger instance;
private Logger() {}
public static Logger getInstance() {
if (instance == null) {
synchronized (Logger.class) {
if (instance == null) {
instance = new Logger();
}
}
}
return instance;
}
}
volatile在这里防止指令重排序,避免其他线程获取到未初始化完成的对象。但实际开发中,我更推荐使用枚举实现单例:
java复制public enum ConfigManager {
INSTANCE;
private Properties config;
ConfigManager() {
// 加载配置
}
public String getConfig(String key) {
return config.getProperty(key);
}
}
枚举单例天然防反射攻击,且序列化安全,是Joshua Bloch在《Effective Java》中推荐的方式。
枚举本质是语法糖,编译后会生成继承Enum的final类。但现代Java中的枚举已经演变为功能完备的类:
java复制public enum HttpStatus {
OK(200, "Success"),
NOT_FOUND(404, "Resource not found");
private final int code;
private final String message;
HttpStatus(int code, String message) {
this.code = code;
this.message = message;
}
// 业务方法
public boolean isSuccess() {
return code >= 200 && code < 300;
}
}
通过枚举可以实现轻量级的策略模式,避免创建多个策略类:
java复制public enum Calculator {
ADD {
@Override
public int compute(int a, int b) {
return a + b;
}
},
SUBTRACT {
@Override
public int compute(int a, int b) {
return a - b;
}
};
public abstract int compute(int a, int b);
}
抽象类是不完整的蓝图,它通过抽象方法定义子类必须实现的契约。与接口不同,抽象类可以:
模板方法模式展现了抽象类的最大价值——定义算法骨架。以支付流程为例:
java复制public abstract class PaymentGateway {
// 模板方法
public final void process() {
validate();
preProcess();
executePayment();
postProcess();
}
private void validate() {
// 通用验证逻辑
}
protected abstract void preProcess();
protected abstract void executePayment();
protected void postProcess() {
// 默认实现
}
}
子类只需关注支付的核心差异,无需重复处理验证等通用逻辑。
Java 8之后,接口已经突破传统界限,支持:
java复制public interface DataExporter {
// 抽象方法
void export(Data data);
// 默认方法
default void exportAsJson(Data data) {
String json = convertToJson(data);
writeToFile(json);
}
// 私有方法
private String convertToJson(Data data) {
// 转换逻辑
}
// 静态方法
static DataExporter getInstance() {
return new DefaultDataExporter();
}
}
| 特性 | 抽象类 | 接口 |
|---|---|---|
| 多继承 | 不支持 | 支持多实现 |
| 方法实现 | 可有可无 | Java 8后支持默认实现 |
| 状态维护 | 可以包含成员变量 | 只能有静态常量 |
| 构造方法 | 有 | 无 |
| 访问控制 | 任意访问修饰符 | 默认public |
选择抽象类当:
选择接口当:
在微服务架构中,我通常使用接口定义服务契约,用抽象类提供公共实现基础。例如:
java复制public interface UserService {
User getUserById(String id);
}
public abstract class AbstractUserService implements UserService {
@Autowired
protected UserRepository repository;
@Override
public User getUserById(String id) {
return repository.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
}
}
@Service
public class DatabaseUserService extends AbstractUserService {
// 可以只实现特殊方法
}
这种组合既保证了接口的清晰定义,又通过抽象类避免了重复代码。