62. Java 类和对象 - 访问修饰符

62. Java 类和对象 - 访问修饰符

在 Java 中,访问修饰符用于控制类及其成员(字段、方法、内部类等)的可见性和可访问性。合理地设置访问权限不仅能保护内部实现细节,还能降低类之间的耦合度,从而提高代码的安全性和可维护性。


1. 顶层(类级别)的访问修饰符

对于顶层类(非内部类),Java 只允许使用两种访问修饰符:

  • public

    • 表示类对“全世界”都可见。任何包中的任意类都能访问该类。

    • 示例:

      public class MyClass {
          // 类内容
      }
      
  • 包级私有(package-private)

    (不写修饰符)

    • 如果不加任何修饰符,则该类只在同一包内可见,包外的类无法访问。

    • 示例:

      class MyClass {
          // 仅同包内可见
      }
      

注意:顶层类只能使用 public 或包级私有两种修饰符,不能使用 privateprotected


2. 成员级别的访问修饰符

当修饰符应用于类的成员时(如字段、方法、内部类),可以使用以下四种访问修饰符:

2.1 public

  • 特性
    成员对所有类都可见,无论是在同一包还是其他包中。

  • 示例:

    public class Person {
        public String name;
        public void sayHello() {
            System.out.println("Hello, " + name);
        }
    }
    

2.2 private

  • 特性
    成员只能在所属的类内部访问,外部类(包括子类)都无法直接访问。

  • 用途
    用于隐藏类的内部实现细节,实现封装。

  • 示例:

    public class Person {
        private int age;  // 只能在 Person 类内部访问
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    }
    

2.3 protected

  • 特性
    成员在同一包内可见,并且在其他包中的子类中也可访问。

  • 示例:

    public class Person {
        protected String address;  // 同包或子类中均可访问
    }
    

2.4 包级私有(默认,无修饰符)

  • 特性
    成员仅在当前包内可见,包外的类(包括子类)无法访问。

  • 示例:

    public class Person {
        int score;  // 包级私有,仅限同包使用
    }
    

访问修饰符总结表

修饰符 本类(class) 同包(package) 同包子类 不同包子类(subclass) 任何类(world)
public 访问 访问 访问 访问 访问
protected 访问 访问 访问 访问 不访问
默认(无修饰符) 访问 访问 访问 不访问 不访问
private 访问 不访问 不访问 不访问 不访问

3. 访问修饰符的影响与设计建议

3.1 访问修饰符的影响

  • 对外部类使用
    当使用JDK 或第三方库的类时,访问修饰符决定了你是否可以调用它的方法或访问它的字段。
  • 内部设计
    在自己编写类时,合理设置字段和方法的访问级别,可以防止外部类过度依赖内部实现,从而提高灵活性并降低耦合度。

3.2 设计建议

  1. 最小可见原则
    • 除非确有必要公开,否则尽量将成员设为私有或包级私有,保持最小的可见范围。这可以避免不必要的耦合和误用。
    • 实践:通常将字段设为 private,并通过 publicprotected 方法(如 getter/setter)对外提供访问接口。
  2. 谨慎使用 public 字段
    • 非常量的 public 字段会使其成为公共API 的一部分,后续修改将受到限制,因此应尽量避免。
  3. 封装为核心
    • 内部实现细节应尽可能隐藏,只暴露外部需要的部分,既能保护数据,也能让后续重构更灵活。

4. 示例讲解

下面以一个简单的 Person 类为例,展示如何设置不同的访问修饰符:

public class Person {
    // 私有字段,只能本类访问
    private String name;
    private int age;

    // 受保护字段:同包或子类访问
    protected String address;

    // 包级私有构造函数:仅在本包内可使用
    Person(String name) {
        this.name = name;
    }

    // 公共构造函数:可以从任意地方调用
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 公共 getter/setter 方法,提供对私有字段的访问
    public String getName() {
        return name;
    }
    public void setName(String newName) {
        this.name = newName;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int newAge) {
        this.age = newAge;
    }
}

在这个示例中:

  • nameage 字段被声明为 private,只有 Person 类内部可以直接访问。
  • address 字段使用 protected,允许同包或子类访问。
  • 构造函数中,一个是 public,一个是包级私有,提供不同的对象构造方式。
  • 对外仅通过 gettersetter 方法来访问和修改 nameage,符合封装原则。

示例 1:同一包内的调用

在同一包下的调用示例(文件:TestAccess.java):

// 文件:TestAccess.java
package com.example;

public class TestAccess {
    public static void main(String[] args) {
        Person p = new Person("Alice", 30);
        
        // 通过公共方法访问和修改私有字段(间接访问)
        p.setName("Alice");
        p.setAge(30);
        System.out.println("Name: " + p.getName());
        System.out.println("Age: " + p.getAge());
        
        // 同一包内,受保护字段和默认字段都可以直接访问
        p.address = "123 Main Street";
        p.phone = "555-1234";
        
        // 直接访问私有字段会编译错误(下面的代码注释掉)
        // p.name = "Bob";  // 错误:name 是 private
        
        p.displayInfo();
    }
}

运行结果将会正确输出姓名、年龄、地址和电话等信息,因为在同一包内可以直接访问受保护和默认的成员。


示例 2:跨包(子类)中的调用

假设我们在另一个包 com.other 下创建一个子类 SubPerson,继承自 com.example.Person

// 文件:SubPerson.java
package com.other;

import com.example.Person;

public class SubPerson extends Person {
    public SubPerson(String name, int age) {
        super(name, age);
    }
    
    // 子类可以访问受保护字段,但不能访问包级私有字段
    public void updateAddress(String newAddress) {
        // 受保护字段在子类中可直接访问
        this.address = newAddress;
    }
    
    public void tryAccessPhone() {
        // 下面代码会编译错误,因为 phone 为包级私有(默认),在不同包中不可见
        // this.phone = "555-0000"; // 编译错误
    }
}

在跨包调用中,子类可以访问父类中受保护(protected)的成员,但不能访问默认(包级私有)的成员。以下是跨包调用的测试示例(文件:TestSub.java):

// 文件:TestSub.java
package com.other;

public class TestSub {
    public static void main(String[] args) {
        SubPerson sp = new SubPerson("Bob", 25);
        sp.updateAddress("456 Other Street");
        
        // 通过公共方法仍然可以获取私有数据
        System.out.println("Name: " + sp.getName() + ", Age: " + sp.getAge());
        
        // 无法直接访问 phone,因为 phone 是包级私有,不对 com.other 包中的类开放
        // sp.phone = "555-0000"; // 编译错误
        
        sp.displayInfo();
    }
}

通过这个示例,我们可以看到:

  • public 成员:无论在哪个包中都可以访问。
  • protected 成员:在同一包和跨包的子类中可以访问。
  • 默认(包级私有) 成员:只能在同一包内访问,跨包的子类无法直接访问。
  • private 成员:只能在类内部访问,其他任何地方都无法直接访问。

小结

  1. 同一包内:所有 publicprotected 和默认(无修饰符)的成员都可以直接访问;private 成员只能通过公共方法访问。
  2. 跨包中的子类:子类可以直接访问父类中受保护(protected)的成员,但不能访问默认(包级私有)和 private 成员。
  3. 设计建议:遵循最小可见原则,默认使用 private;仅对外暴露必要的 publicprotected 接口,确保封装性和代码安全性。
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容