跳转至

常量与修饰符

在Java中,常量和修饰符是控制程序行为和访问权限的重要机制。理解它们的使用方法对编写高质量的Java代码至关重要。

常量

常量是在程序执行过程中值不会改变的量。Java中使用 final 关键字来声明常量。

final 关键字

final 关键字可以用于修饰变量、方法和类,表示"最终的"、"不可改变的"。

final 变量(常量)

public class FinalVariables {
    // 类常量(静态常量)
    public static final double PI = 3.14159265359;
    public static final String COMPANY_NAME = "ABC科技有限公司";
    public static final int MAX_USERS = 1000;

    // 实例常量
    private final String id;
    private final long createdTime;

    // 局部常量示例
    public void demonstrateFinalVariables() {
        // 局部常量
        final int MAX_ATTEMPTS = 3;
        final String ERROR_MESSAGE = "操作失败";

        // MAX_ATTEMPTS = 5; // 编译错误!不能修改final变量

        System.out.println("最大尝试次数: " + MAX_ATTEMPTS);
        System.out.println("错误信息: " + ERROR_MESSAGE);
    }

    public FinalVariables(String id) {
        this.id = id;  // final字段必须在构造方法中初始化
        this.createdTime = System.currentTimeMillis();
    }

    public String getId() {
        return id;
    }

    public long getCreatedTime() {
        return createdTime;
    }
}

final 引用类型

对于引用类型的final变量,引用本身不能改变,但对象的内容可以改变。

import java.util.ArrayList;
import java.util.List;

public class FinalReference {
    public static void main(String[] args) {
        // final引用
        final List<String> names = new ArrayList<>();

        // 可以修改对象内容
        names.add("张三");
        names.add("李四");
        names.remove("张三");

        System.out.println("名单: " + names);

        // names = new ArrayList<>(); // 编译错误!不能重新赋值

        // final数组
        final int[] numbers = {1, 2, 3, 4, 5};
        numbers[0] = 10; // 可以修改数组元素
        System.out.println("第一个元素: " + numbers[0]);

        // numbers = new int[10]; // 编译错误!不能重新赋值
    }
}

常量的命名约定

public class Constants {
    // 常量命名:全大写,单词间用下划线分隔
    public static final int DEFAULT_TIMEOUT = 30000;
    public static final String DATABASE_URL = "jdbc:mysql://localhost:3306/mydb";
    public static final double TAX_RATE = 0.08;
    public static final boolean DEBUG_MODE = true;

    // 枚举常量(更好的选择)
    public enum Status {
        PENDING, PROCESSING, COMPLETED, FAILED
    }

    // 接口中的常量(隐式为public static final)
    public interface DatabaseConfig {
        String DRIVER_CLASS = "com.mysql.cj.jdbc.Driver";
        int CONNECTION_POOL_SIZE = 20;
        int QUERY_TIMEOUT = 5000;
    }
}

常量类的设计

public final class AppConstants {
    // 私有构造方法,防止实例化
    private AppConstants() {
        throw new AssertionError("不能实例化常量类");
    }

    // 应用配置常量
    public static final class Config {
        public static final String APP_NAME = "MyApplication";
        public static final String VERSION = "1.0.0";
        public static final int DEFAULT_PORT = 8080;
    }

    // 错误码常量
    public static final class ErrorCode {
        public static final int SUCCESS = 0;
        public static final int INVALID_PARAMETER = 1001;
        public static final int UNAUTHORIZED = 1002;
        public static final int NOT_FOUND = 1003;
        public static final int INTERNAL_ERROR = 1004;
    }

    // 消息常量
    public static final class Messages {
        public static final String WELCOME = "欢迎使用系统";
        public static final String LOGIN_SUCCESS = "登录成功";
        public static final String LOGIN_FAILED = "登录失败";
        public static final String ACCESS_DENIED = "访问被拒绝";
    }
}

// 使用示例
public class ConstantUsage {
    public static void main(String[] args) {
        System.out.println("应用名称: " + AppConstants.Config.APP_NAME);
        System.out.println("版本: " + AppConstants.Config.VERSION);
        System.out.println("默认端口: " + AppConstants.Config.DEFAULT_PORT);

        int errorCode = AppConstants.ErrorCode.SUCCESS;
        String message = AppConstants.Messages.LOGIN_SUCCESS;

        System.out.println("错误码: " + errorCode);
        System.out.println("消息: " + message);
    }
}

访问修饰符

访问修饰符控制类、字段、方法和构造方法的可见性。

四种访问级别

修饰符 同一类 同一包 子类 不同包
private
默认(package-private)
protected
public

private 修饰符

public class PrivateExample {
    private String secretData = "机密信息";
    private int internalCounter = 0;

    // 私有方法
    private void internalProcess() {
        System.out.println("执行内部处理");
        internalCounter++;
    }

    // 私有构造方法(单例模式)
    private PrivateExample() {
        // 防止外部实例化
    }

    // 公共方法访问私有成员
    public void publicMethod() {
        System.out.println("访问私有数据: " + secretData);
        internalProcess();
    }

    public int getCounter() {
        return internalCounter;
    }

    // 静态工厂方法
    public static PrivateExample createInstance() {
        return new PrivateExample();
    }
}

protected 修饰符

// 父类
public class Animal {
    protected String name;
    protected int age;

    protected Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    protected void eat() {
        System.out.println(name + " 正在吃东西");
    }

    protected void sleep() {
        System.out.println(name + " 正在睡觉");
    }

    // 受保护的方法,子类可以重写
    protected void makeSound() {
        System.out.println(name + " 发出声音");
    }
}

// 子类
public class Dog extends Animal {
    public Dog(String name, int age) {
        super(name, age); // 调用父类的protected构造方法
    }

    @Override
    protected void makeSound() {
        System.out.println(name + " 汪汪叫");
    }

    public void playWithOwner() {
        // 可以访问父类的protected成员
        System.out.println(name + " 和主人玩耍");
        eat(); // 调用父类的protected方法
    }
}

包访问权限(默认)

// 文件1: PackageClass.java
package com.example.demo;

class PackageClass {
    String packageField = "包字段";

    void packageMethod() {
        System.out.println("包方法");
    }
}

// 文件2: SamePackageClass.java
package com.example.demo;

public class SamePackageClass {
    public void testAccess() {
        PackageClass obj = new PackageClass(); // 同一包,可以访问
        System.out.println(obj.packageField);  // 可以访问包字段
        obj.packageMethod();                   // 可以调用包方法
    }
}

// 文件3: DifferentPackageClass.java
package com.example.other;

import com.example.demo.PackageClass; // 编译错误!不能导入包私有类

public class DifferentPackageClass {
    public void testAccess() {
        // PackageClass obj = new PackageClass(); // 编译错误!
    }
}

public 修饰符

public class PublicExample {
    public String publicField = "公共字段";
    public static final String PUBLIC_CONSTANT = "公共常量";

    public PublicExample() {
        // 公共构造方法
    }

    public void publicMethod() {
        System.out.println("公共方法");
    }

    public static void staticPublicMethod() {
        System.out.println("静态公共方法");
    }
}

// 任何地方都可以访问
public class AnywhereAccess {
    public void testAccess() {
        PublicExample obj = new PublicExample();
        System.out.println(obj.publicField);
        obj.publicMethod();
        PublicExample.staticPublicMethod();
        System.out.println(PublicExample.PUBLIC_CONSTANT);
    }
}

其他修饰符

static 修饰符

static 修饰符表示静态的,属于类而不是实例。

public class StaticExample {
    // 静态字段
    private static int instanceCount = 0;
    public static final String CLASS_NAME = "StaticExample";

    // 实例字段
    private String name;
    private int id;

    // 静态初始化块
    static {
        System.out.println("静态初始化块执行");
        instanceCount = 0;
    }

    // 实例初始化块
    {
        instanceCount++;
        System.out.println("实例初始化块执行,当前实例数: " + instanceCount);
    }

    public StaticExample(String name) {
        this.name = name;
        this.id = instanceCount;
    }

    // 静态方法
    public static int getInstanceCount() {
        return instanceCount;
    }

    public static void printClassInfo() {
        System.out.println("类名: " + CLASS_NAME);
        System.out.println("实例数量: " + instanceCount);
        // System.out.println(name); // 编译错误!静态方法不能访问实例字段
    }

    // 实例方法
    public void printInstanceInfo() {
        System.out.println("实例名称: " + name);
        System.out.println("实例ID: " + id);
        System.out.println("总实例数: " + instanceCount); // 可以访问静态字段
    }
}

abstract 修饰符

abstract 修饰符用于声明抽象类和抽象方法。

// 抽象类
public abstract class Shape {
    protected String color;

    public Shape(String color) {
        this.color = color;
    }

    // 具体方法
    public void setColor(String color) {
        this.color = color;
    }

    public String getColor() {
        return color;
    }

    // 抽象方法
    public abstract double getArea();
    public abstract double getPerimeter();

    // 具体方法可以调用抽象方法
    public void printInfo() {
        System.out.println("颜色: " + color);
        System.out.println("面积: " + getArea());
        System.out.println("周长: " + getPerimeter());
    }
}

// 具体子类必须实现所有抽象方法
public class Circle extends Shape {
    private double radius;

    public Circle(String color, double radius) {
        super(color);
        this.radius = radius;
    }

    @Override
    public double getArea() {
        return Math.PI * radius * radius;
    }

    @Override
    public double getPerimeter() {
        return 2 * Math.PI * radius;
    }
}

public class Rectangle extends Shape {
    private double width;
    private double height;

    public Rectangle(String color, double width, double height) {
        super(color);
        this.width = width;
        this.height = height;
    }

    @Override
    public double getArea() {
        return width * height;
    }

    @Override
    public double getPerimeter() {
        return 2 * (width + height);
    }
}

synchronized 修饰符

synchronized 修饰符用于线程同步。

public class SynchronizedExample {
    private int count = 0;
    private final Object lock = new Object();

    // 同步方法
    public synchronized void incrementSync() {
        count++;
        System.out.println("同步方法 - 当前计数: " + count);
    }

    // 同步代码块
    public void incrementBlock() {
        synchronized (this) {
            count++;
            System.out.println("同步代码块 - 当前计数: " + count);
        }
    }

    // 使用自定义锁对象
    public void incrementWithLock() {
        synchronized (lock) {
            count++;
            System.out.println("自定义锁 - 当前计数: " + count);
        }
    }

    // 静态同步方法
    public static synchronized void staticSyncMethod() {
        System.out.println("静态同步方法");
    }

    public int getCount() {
        return count;
    }
}

volatile 修饰符

volatile 修饰符确保变量的可见性。

public class VolatileExample {
    private volatile boolean running = true;
    private volatile int counter = 0;

    public void start() {
        new Thread(() -> {
            while (running) {
                counter++;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
            System.out.println("工作线程结束,计数器值: " + counter);
        }).start();
    }

    public void stop() {
        running = false; // volatile确保这个改变对其他线程可见
        System.out.println("停止信号已发送");
    }

    public int getCounter() {
        return counter;
    }
}

修饰符组合使用

常见的修饰符组合

public class ModifierCombinations {
    // public static final - 公共静态常量
    public static final String VERSION = "1.0.0";

    // private static final - 私有静态常量
    private static final int MAX_SIZE = 100;

    // private final - 私有实例常量
    private final String id;

    // public static - 公共静态方法
    public static void utilityMethod() {
        System.out.println("工具方法");
    }

    // private static - 私有静态方法
    private static void internalUtility() {
        System.out.println("内部工具方法");
    }

    // protected final - 受保护的最终方法
    protected final void templateMethod() {
        System.out.println("模板方法,不能被重写");
    }

    // public synchronized - 公共同步方法
    public synchronized void threadSafeMethod() {
        System.out.println("线程安全方法");
    }

    public ModifierCombinations(String id) {
        this.id = id;
    }
}

修饰符的使用原则

public class BestPractices {
    // 1. 字段应该是私有的,通过方法访问
    private String name;
    private int age;

    // 2. 常量应该是public static final
    public static final int DEFAULT_AGE = 18;

    // 3. 工具方法应该是static
    public static String formatName(String firstName, String lastName) {
        return firstName + " " + lastName;
    }

    // 4. 构造方法的访问级别要合适
    public BestPractices(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 5. getter/setter方法通常是public
    public String getName() {
        return name;
    }

    public void setName(String name) {
        if (name != null && !name.trim().isEmpty()) {
            this.name = name;
        }
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        if (age >= 0 && age <= 150) {
            this.age = age;
        }
    }

    // 6. 内部使用的方法应该是private
    private boolean isValidAge(int age) {
        return age >= 0 && age <= 150;
    }

    // 7. 可能被子类重写的方法使用protected
    protected void displayInfo() {
        System.out.println("姓名: " + name + ", 年龄: " + age);
    }
}

最佳实践总结

1. 常量定义

// 好的做法
public static final String DATABASE_URL = "jdbc:mysql://localhost:3306/db";
public static final int MAX_RETRY_ATTEMPTS = 3;

// 更好的做法:使用枚举
public enum DatabaseType {
    MYSQL("com.mysql.cj.jdbc.Driver"),
    POSTGRESQL("org.postgresql.Driver"),
    ORACLE("oracle.jdbc.driver.OracleDriver");

    private final String driverClass;

    DatabaseType(String driverClass) {
        this.driverClass = driverClass;
    }

    public String getDriverClass() {
        return driverClass;
    }
}

2. 访问控制

public class AccessControlBestPractice {
    // 字段私有化
    private String data;

    // 构造方法公开
    public AccessControlBestPractice(String data) {
        this.data = data;
    }

    // 提供必要的公共接口
    public String getData() {
        return data;
    }

    // 内部方法私有化
    private void validateData(String data) {
        if (data == null || data.isEmpty()) {
            throw new IllegalArgumentException("数据不能为空");
        }
    }

    // 子类可能需要的方法使用protected
    protected void processData() {
        validateData(data);
        // 处理逻辑
    }
}

3. 不可变对象设计

public final class ImmutablePerson {
    private final String name;
    private final int age;
    private final List<String> hobbies;

    public ImmutablePerson(String name, int age, List<String> hobbies) {
        this.name = name;
        this.age = age;
        // 防御性复制
        this.hobbies = new ArrayList<>(hobbies);
    }

    public String getName() { return name; }
    public int getAge() { return age; }

    public List<String> getHobbies() {
        // 返回不可修改的视图
        return Collections.unmodifiableList(hobbies);
    }
}

理解和正确使用常量与修饰符是编写高质量Java代码的基础。合理的访问控制能够提高代码的安全性和可维护性,而恰当的常量定义能够让代码更加清晰和易于管理。记住要遵循最小权限原则,只暴露必要的接口。