3. 访问权限控制
在面对对象的程序设计中你需要考虑如何把变动的事务与保持不变的事物区分开来,其次是保证类库的使用者只能使用开发者提供的接口,权限控制就是用来实现上面这些的。
3.1 包:库单元
包内有一组类,它们在单一的命名空间下被组织在一起。Java可运行程序是一组可以打包并压缩为一个jar包的.class
文件,Java解释器负责这些类文件的查找、加载和解释。
在我们使用某个类库的时候需要通过Import
语句将某个包导入。这一步实际上就是要提供一个命名空间的机制,使得不同类下具有相同方法签名的方法能被区分开来。
import A;
import B;
public class C {
public static void main(String args[]){
A.print();
B.print();
}
}
类库实际上是一组类文件。这些文件中都必须含有一个public类,其命名与文件的命名相同,一个类中还可以有多个非public类。而在编写代码时,通过package
语句可以将不同的类规划在同一个包(命名空间)下,必须在文件的第一句使用package
。
package site.leric.Pojo;
public class People{
}
3.1.1 独一无二的包名
包并未打包成为单一的文件,而实由许多.class
文件组成,并且这些文件都被防止在了以包名命名的文件夹下,利用操作系统的层次化文件结构来统一管理。
按照惯例,package名称的第一部分是域名的反序命名如site.leric
;第二部分则分解为为机器上的一个目录;这样就可以定位到一个.class
文件。
而在实际导入包时,Java解释器会从环境变量执行的CLASSPATH路径着手,根据包名查找对应的类文件。
package test;
import site.leric.Pojo;
public class T1{
public static void main(String args[]){
People p = new People();
System.out.println(p.toString);
}
}
当编译器碰到import语句时,就开始在CLASSPATH中查找,查找目录为site\leric\Pojo
下的People.class
文件。
静态导入
当你通过import static
导入了某个类之后,就能直接通过方法名调用方法了。
package Test;
import static Test.Import.*;
public class T1 {
public static void main(String[] args) {
show(); // this is Test.Import.show
}
}
3.2 权限访问修饰符
在Java中有public、protected、default(包访问权限)以及private四种访问权限,每种访问权限都对其修饰的对象提供不同的访问行为。
同一类 | 同一包 | 子类 | 不同包 | ||
---|---|---|---|---|---|
public | ✔ | ✔ | ✔ | ✔ | 所有 |
protected | ✔ | ✔ | ✔ | 继承关系 | |
default | ✔ | ✔ | 包内 | ||
private | ✔ | 同一类 |
public > protected > default > private;需要注意的时在程序中实际上时通过引用来调用对象成员,因此权限访问修饰符修饰的是时引用而非对象(即java编译器会分该引用声明的位置与访问的类的关系),实际上你无法直接访问到某个对象。
package SuperTest.Test;
public class T {
public String public_i = "public";
String default_i = "default";
protected String protected_i = "protected";
private String private_i = "private";
public T() {
System.out.println(this.public_i);
System.out.println(this.default_i);
System.out.println(this.protected_i);
System.out.println(this.protected_i);
}
public static void main(String[] args) {
T t = new T();
}
}
class TT{
public static void main(String[] args) {
// 在TT类中的t引用已经不与T类处于同一个类下
T t = new T();
// t.private_i(); // 找不到
}
}
//=======================================================
package SuperTest;
import SuperTest.Test.T;
public class SupetT {
public static void main(String[] args) {
T t = new T();
// 这里的t引用已经与T类型不在同一个包下也非继承关系
System.out.println(t.public_i); // 只能访问到public_i
}
}
若两个文件处于相同的目录但没有使用package设置包,那它们默认也是处于同一包下。
构造函数并不要求是public的,可以借助其他能够访问到构造函数的类来构造该对象。
package SuperTest.Test;
public class Test {
String name ;
protected Test(String name) {
this.name = name;
}
@Override
public String toString() {
return "Test{" +
"name='" + name + '\'' +
'}';
}
}
//==================================================
package SuperTest.Test;
public class TestCreate {
public static Test createTest(){
return new Test("1001");
}
}
/===================================================
package SuperTest;
import SuperTest.Test.Test;
import SuperTest.Test.TestCreate;
public class Main {
public static void main(String[] args) {
Test t = TestCreate.createTest();
System.out.println(t.toString());
}
}
3.3 接口和实现
使用访问权限实际上是为了满足封装性,把数据和方法包装进类中,只暴露想要给类使用者使用的接口,而将具体功能的实现方式隐藏,从而实现接口与实现的分离。
public class Test {
private int i ;
private void increce() {
this.setI(++i);
}
public void setI(int i) {
this.i = i;
}
public void add() {
increce();
}
public int getI() {
return i;
}
}
class Main {
public static void main(String[] args) {
Test t = new Test();
for (int i = 0; i < 5; i++) {
t.add();
}
System.out.println(t.getI());
}
}
3.4类的访问权限
访问修饰符也可以用于确定库中的那些类对于该库的使用者来说是可用的,甚至可以用于控制类库的使用者是否能创建某类的对象。
/*
test类位于superTest包下
T1 T2 位于superTest/Test包下 T2是default修饰
*/
public class test {
public static void main(String[] args) {
T1 t1 = new T1(); // public
// T2 t2 = new T2(); default
}
}
.java
文件在编译之后就变成了同名的.class
文件
- 每个类文件内都只能有一个public类,但是可以有多个或没有非public类
- 类只能使用public、default两种访问权限修饰符,若不显式的指定访问修饰符则默认是default(包权限)
- 被public修饰的类,其类名必须与
.class
文件的命名完全一致 - 若类文件内没有public修饰的类,这也是合法的,此时可以随意命名类文件
类不能是private但是构造器可以是private的,这时想要创建该类的实例对象只能通过静态方法创建。
public class T1 {
public static T1 getT1(String name){
return new T1(name);
}
private String name = "T1";
private T1(String name) {
this.name = name;
}
@Override
public String toString() {
return "T1{" +
"name='" + name + '\'' +
'}';
}
}
//===========================================
public class Main {
public static void main(String[] args) {
System.out.println(T1.getT1("Leric").toString());
System.out.println(T1.getT1("Byte").toString());
}
}
第二种方法是直接在在类内初始化一个对象,并且使用static语句修饰该成员,这种方法也成为单例模式
public class T1 {
static T1 t = new T1("Leric");
public static T1 getT1(){
return t;
}
private String name;
private T1(String name) {
this.name = name;
}
@Override
public String toString() {
return "T1{" +
"name='" + name + '\'' +
'}';
}
}
//=========================================
public class Main {
public static void main(String[] args) {
T1 t1 = T1.getT1();
T1 t2 = T1.getT1();
System.out.println((t1 == t2)); // true
}
}