乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Java概述与环境搭建

    • Java概述与环境搭建
    • Java语言概述
    • 解释型语言与编译型语言对比
    • JDK安装与配置
    • JDK、JRE、JVM 详解
    • HelloWorld程序详解
    • IDE 介绍
  • 第2章 标识符与基本数据类型

    • 章节导读
    • 变量概述
    • 常量概述
    • 基本类型与包装类
    • 字节型 byte
    • 短整型 short
    • 整型 int
    • 长整型 long
    • 单精度浮点型 float
    • 双精度浮点型 double
    • 字符型 char
    • 布尔型 boolean
    • 类型转换
  • 第3章 运算符与表达式

    • 章节导读
    • 算术运算符
    • 赋值运算符
    • 关系运算符
    • 逻辑运算符
    • 位运算符
    • 条件运算符
    • 运算符优先级
    • 表达式
  • 第4章 流程控制

    • 章节导读
    • 常见的程序运行流程
    • if-else 选择结构
    • switch 多分支选择
    • while 循环
    • do-while 循环
    • for 循环
    • break 与 continue
  • 第5章 数组

    • 章节导读
    • 一维数组
    • 多维数组
    • Arrays 工具类
  • 第6章 类与对象

    • 章节导读
    • 类与对象
    • 方法定义与调用
    • 构造方法
    • 封装
    • 访问修饰符
    • package 与 import
    • static 关键字
    • this 关键字
    • 参数传递 详解
    • 枚举
    • 成员内部类
    • 局部内部类
    • 静态内部类
    • 匿名内部类
  • 第7章 接口与继承

    • 章节导读
    • 继承
    • super 关键字
    • final 关键字
    • 多态
    • 向上转型与向下转型
    • 抽象类
    • 接口
    • 抽象类与接口对比
  • 第8章 注解

    • 章节导读
    • 注解基础
    • 元注解详解
    • 自定义注解
  • 第9章 常用类

    • 章节导读:Java 常用类
    • Object 类:万类之祖
    • 包装类:基本类型的对象化
    • String:不可变的字符串
    • StringBuffer:线程安全的可变字符串
    • StringBuilder:可变的字符串构建器
    • Math:数学运算工具类
    • Random:伪随机数生成器
    • 大数值运算 详解
    • 日期时间API 详解
  • 第10章 异常机制

    • 章节导读
    • 异常体系与分类
    • try-catch-finally
    • try-with-resources
    • throws 与 throw
    • 自定义异常
  • 第11章 泛型

    • 章节导读
    • 泛型基础
    • 通配符与PECS原则
    • 类型擦除
  • 第12章 集合框架

    • 章节导读
    • 集合框架概述
    • ArrayList
    • LinkedList
    • HashMap 详解
    • LinkedHashMap 详解
    • TreeMap 详解
    • HashSet
    • TreeSet 详解
    • TreeSet 与 Comparable
    • Collections 工具类详解
  • 第13章 IO流

    • 章节导读
    • IO流概述
    • 字节流
    • 字符流
    • 缓冲流
    • 转换流 详解
    • 序列化 详解
    • NIO与Files 详解
    • NIO与Files工具类
  • 第14章 多线程与并发

    • 第十六章 多线程与并发 —— 章节导读
    • 线程基础详解
    • synchronized 详解
    • Lock 与显式锁详解
    • volatile 详解
    • wait 与 notify 详解
    • ThreadLocal详解
    • 原子类详解
    • 并发工具类详解
    • 线程池详解
  • 第15章 反射

    • 章节导读
    • 反射概述与 Class 对象
    • Constructor 与对象创建
    • Field与Method详解
    • 反射应用详解
  • 第16章 JDK8新特性

    • 章节导读
    • Lambda 表达式
    • Stream API 基础
    • Stream API 高级详解
    • Optional 详解
    • 新日期时间API详解
  • 第17章 JDK9-11新特性

    • 章节导读
    • 模块化系统 — Project Jigsaw(JDK 9)
    • var 局部变量类型推断(JDK 10)
    • 集合工厂方法与增强(JDK 9 / 10 / 11)
    • 接口增强:private 方法(JDK 9)
    • Stream API 增强(JDK 9)
    • Optional 增强(JDK 9 / 10 / 11)
    • String 新增方法(JDK 11)
    • HTTP Client 与 Files 增强(JDK 11)
    • 直接运行 Java 源文件 — JEP 330(JDK 11)
  • 第18章 JDK12-17新特性

    • 章节导读
    • Switch 表达式(JDK 12 预览 / JDK 14 正式)
    • 文本块 Text Blocks(JDK 13 预览 / JDK 15 正式)
    • Records 记录类(JDK 14 预览 / JDK 16 正式)
    • 密封类 Sealed Classes(JDK 15 预览 / JDK 17 正式)
    • instanceof 模式匹配(JDK 14 预览 / JDK 16 正式)
    • Switch 模式匹配 — Pattern Matching for switch(JDK 17 预览 / JDK 21 正式)
    • Helpful NPE 与 String 增强(JDK 12 / JDK 14 / JDK 15)
    • Stream 增强(JDK 12 / JDK 16)
    • 日期时间增强 — Day Period 支持(JDK 16)
  • 第19章 JDK18-21新特性

    • 章节导读
    • 虚拟线程(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 序列集合(JDK 21 正式)
    • Switch 模式匹配(JDK 17 预览 / JDK 18 第二预览 / JDK 20 第四预览 / JDK 21 正式)
    • Record 模式匹配(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 未命名模式与变量(JDK 21 预览 / JDK 22 正式)
  • 第20章 JDK 22-25 新特性

    • 章节导读
    • 字符串模板(JDK 22 预览 / JDK 23 第二预览 / JDK 24 第三预览)
    • Stream Gatherers(JDK 22 预览 / JDK 24 第二预览)
    • 隐式声明类与实例方法(JDK 23 预览 / JDK 24 第二预览)
    • 原始类型模式匹配(JDK 24 预览)
  • 附录

    • Java 核心知识点
    • Java SE 专业术语
    • Java特性索引(JDK 8 → 25)

访问修饰符

引语:小崔写完 Employee 类后,发现测试类访问不到 salary 字段,急得直挠头。架构师白歌走过来:"别慌,不是 Bug——是 private 在保护数据。来,我给你画一张'访问权限地图',四种修饰符各自的领地一清二楚。"


四种访问修饰符概述

Java 提供四种访问修饰符,从严格到宽松依次为:

修饰符中文名称同类同包子类同包非子类不同包子类不同包非子类
private私有✅❌❌❌❌
default(无修饰符)包级私有✅✅✅❌❌
protected受保护✅✅✅✅❌
public公开✅✅✅✅✅

记忆口诀:private 仅自己,default 同包亲,protected 子可及,public 天下行。


private:仅本类可见

定义

private 是最严格的访问级别。被 private 修饰的成员只能在本类内部访问,对外完全透明。

适用场景

  • 类的内部状态字段(如 salary、password)
  • 仅供本类使用的辅助方法(如校验逻辑、内部计算)
  • 不希望被子类继承或覆盖的方法

示例

package com.feixiang.hr;

public class SalaryCalculator {
    private double baseSalary;       // 基本工资,外部不可见
    private double bonusRate = 0.1;  // 奖金系数

    // ✅ 本类内部可以访问 private 字段
    public double calculateMonthlySalary() {
        return baseSalary * (1 + bonusRate);  // 同级访问 OK
    }

    // ✅ 本类内部可以调用的私有辅助方法
    private double getTaxDeduction() {
        return baseSalary * 0.05;  // 假设 5% 税
    }

    public double getNetSalary() {
        return calculateMonthlySalary() - getTaxDeduction();
    }
}

// ======== 另一个类 ========
class HRAssistant {
    public void checkSalary(SalaryCalculator calc) {
        // ❌ 编译错误:bonusRate 在 SalaryCalculator 中是 private 访问控制
        // System.out.println(calc.bonusRate);

        // ❌ 编译错误:getTaxDeduction() 在 SalaryCalculator 中是 private 访问控制
        // calc.getTaxDeduction();

        // ✅ 只能通过 public 方法访问
        System.out.println("实发工资:" + calc.getNetSalary());
    }
}

default(包级私有):无修饰符

定义

当一个成员没有任何访问修饰符时,它具有包级私有(Package-Private)访问权限——只能被同一包内的类访问。

重要:default 不是关键字!不写任何修饰符就是 default 访问级别。很多人误写 default void method(),这是编译错误。

适用场景

  • 包内部使用的工具类和方法
  • 不希望对外暴露但同包类需要的功能
  • 包的实现细节(包是天然的内聚单元)

示例

// 文件:com/feixiang/hr/InternalAuditor.java
package com.feixiang.hr;

/**
 * 内部审计工具 —— 包级私有,仅供 hr 包内部使用
 */
class InternalAuditor {  // 注意:类本身也是 default 级别

    // default 方法 —— 同包可调用
    void auditSalary(Employee emp) {
        System.out.println("【审计】正在审查 " + emp.getName() + " 的薪资记录...");
        // 同包内可以访问其他类的 default 成员
    }
}

// 文件:com/feixiang/hr/HRManager.java
package com.feixiang.hr;

public class HRManager {
    public void runAudit(Employee emp) {
        InternalAuditor auditor = new InternalAuditor();  // ✅ 同包,可访问
        auditor.auditSalary(emp);                          // ✅ 同包,可调用
    }
}

// 文件:com/feixiang/tech/TechManager.java
package com.feixiang.tech;

public class TechManager {
    public void tryAudit() {
        // ❌ 编译错误:InternalAuditor 在 com.feixiang.hr 中不是公共的;
        // 无法从外部包访问
        // InternalAuditor auditor = new InternalAuditor();
    }
}

protected:同包 + 子类可见

定义

protected 的访问权限是 default 权限的超集:除了同包可见外,还允许不同包的子类通过继承访问。

核心特性

特性说明
同包访问与 default 一致,同包内所有类均可访问
跨包子类访问不同包的子类可以继承并访问 protected 成员
跨包非子类不可访问(这是与 public 的关键差异)
静态 protected静态成员也可以用 protected,同样遵循上述规则

示例:protected 的跨包继承访问

// ====== 父类:com.feixiang.hr.Employee ======
package com.feixiang.hr;

public class Employee {
    protected String empName;      // protected:子类可访问
    protected double salary;       // protected:子类可访问
    private   String idCard;       // private:子类也不可访问

    protected double calculateBonus() {
        return salary * 0.15;
    }

    public Employee(String empName, double salary) {
        this.empName = empName;
        this.salary = salary;
    }
}

// ====== 子类:com.feixiang.tech.TechEmployee ======
package com.feixiang.tech;

import com.feixiang.hr.Employee;

/**
 * 技术部员工 —— 不同包的子类
 */
public class TechEmployee extends Employee {

    private String techStack;  // 技术栈

    public TechEmployee(String empName, double salary, String techStack) {
        super(empName, salary);
        this.techStack = techStack;
    }

    public void printInfo() {
        // ✅ 跨包子类:可以访问父类的 protected 字段
        System.out.println("姓名:" + empName);    // protected 继承
        System.out.println("薪资:" + salary);      // protected 继承

        // ❌ 编译错误:idCard 在 Employee 中是 private 访问控制
        // System.out.println("身份证:" + idCard);

        // ✅ 跨包子类:可以调用父类的 protected 方法
        double bonus = calculateBonus();
        System.out.println("奖金:" + bonus);
    }
}

// ====== 不同包的非子类 ======
package com.feixiang.finance;

import com.feixiang.hr.Employee;

public class FinanceAuditor {
    public void audit(Employee emp) {
        // ❌ 编译错误:empName 在 Employee 中是 protected 访问控制
        // System.out.println(emp.empName);

        // ❌ 编译错误:calculateBonus() 在 Employee 中是 protected 访问控制
        // emp.calculateBonus();

        // ✅ 只能通过 public 接口访问
        System.out.println("审计完成");
    }
}

protected 细节:子类中不能通过父类引用访问

这是面试高频陷阱题!子类继承 protected 成员后,可以在子类代码中通过子类引用访问它,但不能通过父类引用访问另一个对象的 protected 成员。

package com.feixiang.tech;

import com.feixiang.hr.Employee;

public class TechEmployee extends Employee {

    public TechEmployee(String empName, double salary, String techStack) {
        super(empName, salary);
    }

    // ✅ 通过子类引用(this)访问
    public void methodA() {
        System.out.println(this.empName);  // OK —— this 是 TechEmployee 类型
    }

    // ✅ 通过另一个同包子类引用访问
    public void methodB(TechEmployee other) {
        System.out.println(other.empName);  // OK —— 同类型
    }

    // ❌ 通过父类引用访问另一个对象的 protected 成员
    public void methodC(Employee other) {
        // 编译错误:empName 在 Employee 中是 protected 访问控制
        // System.out.println(other.empName);
    }
}

原理:Java 语言规范规定,protected 成员的跨包访问必须通过子类类型或其子类型的引用进行。这样设计的目的是防止恶意子类通过继承来"窃取"其他对象的 protected 数据。


public:完全公开

定义

public 是最宽松的访问级别。被 public 修饰的成员可以被任何类在任何地方访问。

适用场景

  • 类的公开 API(如 getter/setter)
  • 工具类中的静态方法
  • 常量(public static final)
package com.feixiang.hr;

public class CompanyConstant {
    public static final String COMPANY_NAME = "飞翔科技";
    public static final String COMPANY_ADDRESS = "北京市朝阳区飞翔大厦";
    public static final int MAX_EMPLOYEES = 5000;
}

访问修饰符选择原则

最佳实践:

  1. 字段永远 private——这是封装的底线
  2. 方法优先 default——只在需要的时候扩大访问级别
  3. protected 慎用——它让子类与父类实现细节耦合
  4. public 即承诺——public API 一旦发布就不能轻易修改,因为可能有无数外部调用方

完整示例:飞翔科技多模块访问控制

场景

飞翔科技有三个包:hr(人力资源)、tech(技术部)、finance(财务部)。不同部门对员工数据有不同的访问权限:

  • HR 内部:可以查看和修改所有信息
  • 技术部经理(继承自 HR 的员工类):可以查看基本信息,不能看薪资明细
  • 财务部:只能通过公开报表接口查看汇总数据

代码

// ====== 文件1:com/feixiang/hr/Employee.java ======
package com.feixiang.hr;

public class Employee {
    private   String idCard;        // 身份证号:仅本类
              String phoneNumber;   // 电话号码:包内可见(default)
    protected String empName;       // 姓名:子类可见
    protected double salary;        // 薪资:子类可见
    public    String empId;         // 工号:完全公开

    public Employee(String empId, String empName, double salary, String idCard, String phoneNumber) {
        this.empId = empId;
        this.empName = empName;
        this.salary = salary;
        this.idCard = idCard;
        this.phoneNumber = phoneNumber;
    }

    // private 字段通过 getter 受控暴露
    public String getIdCardMasked() {
        // 脱敏:只显示前3后4
        if (idCard == null || idCard.length() < 7) return "***";
        return idCard.substring(0, 3) + "****" + idCard.substring(idCard.length() - 4);
    }

    public String getEmpName() { return empName; }
    public double getSalary()  { return salary; }
}

// ====== 文件2:com/feixiang/hr/HRService.java(同包) ======
package com.feixiang.hr;

public class HRService {
    public void printFullInfo(Employee emp) {
        // ✅ 同包:可访问 private 以外的所有成员
        System.out.println("工号:" + emp.empId);            // public
        System.out.println("姓名:" + emp.empName);          // protected
        System.out.println("薪资:" + emp.salary);            // protected
        System.out.println("电话:" + emp.phoneNumber);      // default —— 同包可访问

        // ❌ 编译错误:idCard 在 Employee 中是 private 访问控制
        // System.out.println("身份证:" + emp.idCard);

        // ✅ 通过 getter 获取脱敏数据
        System.out.println("身份证:" + emp.getIdCardMasked());
    }
}

// ====== 文件3:com/feixiang/tech/TechManager.java(不同包的子类) ======
package com.feixiang.tech;

import com.feixiang.hr.Employee;

public class TechManager extends Employee {

    private String techField;

    public TechManager(String empId, String empName, double salary,
                       String idCard, String phoneNumber, String techField) {
        super(empId, empName, salary, idCard, phoneNumber);
        this.techField = techField;
    }

    public void printTechInfo() {
        // ✅ 跨包子类:可访问 public 和 protected
        System.out.println("工号:" + empId);        // public
        System.out.println("姓名:" + empName);      // protected
        System.out.println("技术方向:" + techField);

        // ❌ 编译错误:phoneNumber 在 Employee 中不是 public 的;无法从外部包访问
        // System.out.println("电话:" + phoneNumber);  // default —— 跨包不可见

        // ❌ 编译错误:idCard 在 Employee 中是 private 访问控制
        // System.out.println("身份证:" + idCard);
    }
}

// ====== 文件4:com/feixiang/finance/FinanceReport.java(不同包的非子类) ======
package com.feixiang.finance;

import com.feixiang.hr.Employee;

public class FinanceReport {

    public void printReport(Employee emp) {
        // ✅ 跨包非子类:只能访问 public
        System.out.println("工号:" + emp.empId);     // public
        System.out.println("姓名:" + emp.getEmpName());  // public getter

        // ❌ 以下全部编译错误
        // System.out.println(emp.empName);     // protected —— 跨包非子类不可见
        // System.out.println(emp.salary);      // protected —— 跨包非子类不可见
        // System.out.println(emp.phoneNumber); // default —— 跨包不可见

        // ✅ 通过 public getter 受控访问
        System.out.println("薪资(脱敏):" + "***");
        System.out.println("身份证(脱敏):" + emp.getIdCardMasked());
    }
}

测试代码

package com.feixiang;

import com.feixiang.hr.Employee;
import com.feixiang.hr.HRService;
import com.feixiang.tech.TechManager;
import com.feixiang.finance.FinanceReport;

public class AccessControlTest {
    public static void main(String[] args) {
        // 创建员工
        Employee emp = new Employee("FX-1001", "白歌", 35000.00,
                                     "110101199001011234", "13800138000");

        TechManager techMgr = new TechManager("FX-2001", "小崔", 28000.00,
                                               "110101199502021234", "13900139000",
                                               "Java 后端");

        System.out.println("========== HR 视角(同包)==========");
        HRService hr = new HRService();
        hr.printFullInfo(emp);

        System.out.println("\n========== 技术经理视角(跨包子类)==========");
        techMgr.printTechInfo();

        System.out.println("\n========== 财务报表视角(跨包非子类)==========");
        FinanceReport report = new FinanceReport();
        report.printReport(emp);
    }
}

运行输出

========== HR 视角(同包)==========
工号:FX-1001
姓名:白歌
薪资:35000.0
电话:13800138000
身份证:110****1234

========== 技术经理视角(跨包子类)==========
工号:FX-2001
姓名:小崔
技术方向:Java 后端

========== 财务报表视角(跨包非子类)==========
工号:FX-1001
姓名:白歌
薪资(脱敏):***
身份证(脱敏):110****1234

易错场景

反例一:接口中的方法不加 public

// ❌ 错误认知:接口方法默认 public,不需要写
interface EmployeeService {
    void hire(Employee emp);  // 虽然默认为 public,但不写容易造成误解
}

// 实现类可能误写成 default 级别
class EmployeeServiceImpl implements EmployeeService {
    // ❌ 编译错误:EmployeeServiceImpl 中的 hire(Employee) 无法实现
    //             EmployeeService 中的 hire(Employee);正在尝试分配更弱的访问权限;
    //             以前为 public
    void hire(Employee emp) { }  // 接口方法隐式为 public,实现类不能缩小权限
}

纠正:

// ✅ 明确写出 public,语义清晰
interface EmployeeService {
    public void hire(Employee emp);
}

class EmployeeServiceImpl implements EmployeeService {
    @Override
    public void hire(Employee emp) {  // 必须写 public
    }
}

原理:Java 要求方法重写时,子类方法的访问权限不能低于父类方法。接口方法默认为 public,所以实现类必须用 public。

反例二:误以为 protected == public

package com.feixiang.hr;

public class Employee {
    protected String empName;
}

package com.feixiang.finance;

import com.feixiang.hr.Employee;

public class Auditor {
    public void check(Employee emp) {
        // ❌ 编译错误!Auditor 不是 Employee 的子类
        //    且不同包,不能访问 protected
        // System.out.println(emp.empName);
    }
}

纠正:不同包的非子类要访问,只能用 public。


面试考点

Q1:Java 中有哪几种访问修饰符?分别的权限范围是什么?

Java 有四种访问修饰符:① private——仅本类可见;② default(无修饰符)——同包可见;③ protected——同包 + 不同包子类可见;④ public——所有类可见。

权限从小到大:private < default < protected < public。

Q2:protected 和 default 有什么区别?

default(包级私有):只能被同包内的类访问,不同包的子类无法访问。 protected:同包内所有类 + 不同包的子类都可以访问。protected 比 default 多了一个"跨包子类访问"的能力。这是两者的唯一区别。

Q3:为什么子类中不能通过父类引用来访问另一个对象的 protected 成员?

这是 Java 语言规范(JLS 6.6.2)的保护机制。protected 设计的初衷是让子类继承并使用父类的成员,而非让子类成为"万能钥匙"去访问任意对象的 protected 数据。如果允许通过父类引用访问,一个恶意的子类就可以读取/修改同类型其它对象的 protected 数据,破坏封装。

Q4:接口中的方法可以不用 public 修饰吗?

可以省略,因为接口中的方法隐式为 public。但显式写出 public 是更好的实践——它让代码意图更明确,避免实现类误写成 default 级别导致编译错误。从 Java 9 开始,接口还支持 private 方法(用于默认方法间的复用)。

Q5:类的访问修饰符有哪些?

顶级类(非内部类)只有两种访问级别:public 和 default。public 类可以被任何包访问;default 类只能被同包访问。private 和 protected 不能用于顶级类。

上一页
封装
下一页
package 与 import