什么是封装
封装需要分为两部分理解:
1. 将对象的状态信息隐藏在内部,不允许外部程序直接访问
2. 通过公开的方法让外部实现对内部信息的访问和操作
之所以要这样做,是因为在公开的方法里,程序员可以设计程序逻辑,阻止外界的非法访问,让访问可以控制在有效的范围内。
访问控制符
java是通过访问控制符来实现封装。
java有4个访问控制级别,它们的权限从小到大依次为:private、default、protected、public。提供了3个访问控制符:private、protected、public。如果什么都不写,就是default权限。
访问控制级别介绍:
- private(当前类内部访问权限):private表明是私有的,private可以修饰成员变量、方法、构造器、内部类等,表明这些私有成员只能在当前类的内部访问。private不能修饰类,因为这是一个与类内部成员有关的访问修饰符,类的访问修饰符只有默认的default和public两种。private一般用于修饰成员变量,表明类变量或者实例变量是类或对象的私有状态。private修饰构造器是比较少见的用法,表明不允许外部直接用该构造器创建对象,比如单例类。private也会用于修饰一些工具方法,这些方法只是为内部的其他方法服务,不希望被外界公开使用。
- default(包访问权限):如果没有访问控制符,那就表明是default权限(默认权限),default可以用于修饰类、成员变量、方法、构造器等,如果修饰类或对象的成员,就表明这些成员可以被同一个包里的任意类访问,如果修饰外部类本身,就表明同一个包的其他类可以访问这个类。
- protected(子类访问权限):protected可以修饰成员变量、方法、构造器等,它的权限在包权限的基础上,增加了不同包的子类内部可以访问,注意:是子类的内部能访问,不可以用子类的对象访问父类的protected修饰的方法。protected的作用就是和继承相关,protected修饰方法表明,希望子类重写父类的方法。protected修饰构造器表明,希望子类调用父类的构造器又不想外界直接使用父类的构造器。protected修饰成员变量表明,希望子类可以继承父类的成员变量,但不希望其他包直接访问父类的成员变量。protected不能修饰类,因为protected是希望子类能访问父类内部的成员,与类本身没有关系。
- public(公共访问权限):public等于没有限制,public可以修饰成员变量、方法、构造器、类,表明这些成员或者类本身可以在任何地方被访问。
访问控制符和局部变量无关,因为局部变量只在方法内部有效,不需要访问控制符限制。
访问修饰符针对的是类,而不是对象。比如一个类的方法的形参包含自身类型的参数,这样就可能发生a对象的方法传入了b对象的实参,但是a对象的方法内部允许直接访问b对象的私有变量。
举例:
public class Employee {
private String name;
public Employee(String name) {
this.name = name;
}
public boolean equals(Employee other) {
// 如果2个员工对象名字相同,就返回true,否则返回false
return name.equals(other.name);
}
public static void main(String[] args) {
var one = new Employee("Cauchy");
var two = new Employee("Cauchy");
var boss = new Employee("Lin");
System.out.println(one.equals(two));
// 这里one对象的equals方法直接访问了boss的私有实例变量name
System.out.println(one.equals(boss));
}
}
这件事咋看起来有点奇怪,但并不危险,因为这些代码都是在类的内部定义的,是同一个开发者完成的类的设计。开发者自身不会在自身设计的方法内部进行不合法的访问。
访问修饰符真正防止的是,另一个使用类的人员做非法访问和修改。
private与static无关,private的含义就是在类的内部有效。static的方法内部可以创建一个自身类的对象,然后直接访问该对象的私有实例变量。因为static的方法在类的内部,在类的内部就可以直接访问private修饰的成员变量。
举例:
public class Student {
private String name;
public static void main(String[] args) {
// 类自身的静态方法可以直接创建自身类的对象,再通过点运算符直接为私有变量赋值
Student s1 = new Student();
s1.name = "Cauchy";
}
}
public与文件名关系
java有一条规定,如果某个java文件里有public修饰的类,那么java文件名必须和这个类的类名相同。如果某个java文件里没有一个public修饰的类,那它的文件名可以任意取,只要文件名合法。
而且,一个java文件只能有一个public类。可以有很多个不是public的类,但public的类只能有一个。
但在实际开发中,一般一个文件里面只有一个类,只需要让文件名和类名相同即可。
setter和getter方法
因为类的成员变量通常需要封装,也就是设置为private权限,所以需要与之相关的setter和getter方法,这些公开的读取和写入的方法,可以按照设计者的意图,给外界提供可控的访问。
我们一般的命名规则为set+成员变量名(变量名首字母大写),比如age这个实例变量的setter和getter方法为:setAge方法和getAge方法。
这样做和直接公开成员变量访问权限的区别是,方法内部可以通过代码设计访问的逻辑,让成员变量的访问是可控制的。
访问控制符的使用规则
规则如下:
- 除了一些类似全局变量的静态常来那个(很少使用),绝大部分成员变量都要用private修饰,保证类或对象的状态信息不对外界公开。一些只为类内部的其他方法调用的工具方法,应该用private修饰。
- 如果类里的方法只希望被子类重写,不希望被其他包直接调用,应该用protected修饰。
- 希望暴露出来给外界使用的方法,应该使用public修饰。
- 有的类希望只能通过静态方法创建对象,比如单例类。它的构造器应该设置为private。
封装的好处
把成员变量设置为私有,同时提供公开的setter和getter方法,这看起来会更麻烦,但却带来了很多好处:
- 可以把数据的读取和修改控制在合理的范围内
将成员变量隐藏后,外界没办法直接察觉到成员变量的存在,不能随心所欲的随意访问和修改它们。而公开的访问器方法和修改器方法本身可以设计程序逻辑,让成员变量的访问和修改变得可控。 - 可以在类的内部修改成员变量的组成,只要对外公布的部分不发生改变
比如private String name; 名字这个成员变量非常常见,如果改成private String firstName; 和 private String lastName; 虽然改变了内部成员变量的组成,但只要public String getName() {方法体省略} 这个公开的访问器方法的返回值不变,外界根本察觉不到内部的变化。原本方法体内是return name; 只需要改成return firstName + lastName; 对外界来说,根本没区别。
隐晦的破坏封装的方法
并不是给成员变量设置为private,再提供公开的getter和setter方法就算完成封装了。
如果成员变量是可变对象的引用,提供该引用的访问器方法,就可能破坏封装。
private只能保证引用变量的值是私有的。但是只要提供公开的访问器方法,即使没有提供公开的修改器方法,也可以得到引用指向的可变对象的索引。
只要外界可以得到可变对象的索引,也就可以通过这个索引改变可变对象内部的数据。
即使把这个成员变量设置为final也没用,因为final只能限制引用本身一旦初始化就不被修改,但是我们可以通过引用来改变可变对象内部的数据。