文章目录

  • 6.1 包:库单元(the library unit)
  • 6.1.1 代码组织
  • 6.1.2 创建独一无二的包名
  • 练习1
  • 练习2
  • 6.1.3 定制工具类
  • 6.1.4 用import改变行为
  • 练习3
  • 6.1.5 对使用包的忠告
  • 6.2 Java访问权限修饰词
  • 6.2.1 包访问权限
  • 6.2.2 public:接口访问权限
  • 6.2.3 private:你无法访问
  • 6.2.4 protected:继承访问权限
  • 练习4
  • 练习5
  • 练习6
  • 6.3 接口和实现
  • 6.4 类的访问权限
  • 练习7
  • 练习8
  • 练习9



Access control (or implementation hiding) is about “not getting it right the first time.”

通常类库开发者需要不断重构代码,来不断完善和改进程序。但是一般对应的接口代码应该尽量保持不变。类库开发者怎样才能知道哪些程序会别前端调用?这就需要将哪些不想让前端掉用的程序和需要调用的区分开来。

解决这个问题的方法就是使用权限访问控制。用不同的权限修饰符来表示不同的访问权限等级,从最大权限到最小权限为:

publicprotected、包访问权限(默认,没有关键字)、private

Java使用package,来管理类名的命名空间。package将功能相似的类组织到同一类中,便于管理。即使同一名字的多个类出现,只要它们在不同的package中,就就没有名字冲突。package也有访问权限,某个类需要包访问权限才能访问其他包中的类。

6.1 包:库单元(the library unit)

一个java源代码文件,叫做一个编译单元。每个编译单元必须且只能有一个public修饰的类。这个类名必须和文件名一模一样(区分大小写,不包括.java后缀名)。

6.1.1 代码组织

使用javac对一个.java文件编译时,这个文件里面的所有类,都会产生一个.class字节码文件。和C/C++不一样,Java的可运行程序是一组可以打包并压缩为一个Java文档文件(JAR,使用Java的 jar文档生成器)的.class字节码文件。Java解释器负者对这些文件的查找、装载和解释(Java中并不要求必须使用解释器,因为存在用来生成一个单一的可执行文件的本地代码Java编译器)。

类库是一组class文件。每个文件中都有一个public的类和任意数量的非public的类。通过package可以将他们放到一个组中。

6.1.2 创建独一无二的包名

将某个包的所有.class文件都放到同一个目录下,利用操作系统的层次化的文件结构类解决,多个类库中许多.class文件

Java解释器的运行过程:

  1. 首先,当编编译器碰到import语句时,就开始从CLASSPATH目录中查找。将环境变量CLASSPATH作为查找.class文件的根目录。CLASSPATH一般包含一个或多个目录。
  2. 解释器从根目录开始,获取包名称并将每个句点替换为反斜杠,然后将根路径和这个路径拼接到一块。然后到这个目录下查找相关的.xlass字节码文件。例如CLASSPATH=/root/code,那么解释器将/root/code作为根目录,去这个目录里找包,加入找到一个名为foo.bar.baz的包,然后就把foo.bar.baz转换为foo/bar/baz,然后将它和根目录拼接到一块为/root/code/fo/.bar/baz,然后到/root/code/fo/.bar/baz目录下查找相关的.class文件。

package语句必须是文件中的第一行非注释程序代码。

CLASSPATH对于JAR文件,需要连写文件命由写秦楚。

练习1

package com.qww.exec01.simple;

public class Vector { }
package com.qww.exec01;

import com.qww.exec01.simple.Vector;

public class Exec01 {

    public static void main(String[] args) {
        Vector vector = new Vector();
    }
}

冲突

如果导入方式是:

import com.qww.exec02.simple.*;
import java.util.*;

那么在使用Vector类时,就会有冲突。

编译器提示错误:Error:(9, 9) java: 对Vector的引用不明确 java.util 中的类 java.util.Vector 和 com.qww.exec02.simple 中的类 com.qww.exec02.simple.Vector 都匹配

练习2

package com.qww.exec02;

import com.qww.exec02.simple.Vector;
import java.util.*;

public class Exec01 {

    public static void main(String[] args) {
        Vector vector = new Vector();
    }
}

6.1.3 定制工具类

public class PrintUtil {
    
    private static final PrintStream out = System.out;
    
    public static void println(Object obj) {
        out.println(obj);
    }
    public static void println(int x) {
        out.println(x);
    }
    public static void println(Long x) {
        out.println(x);
    }
    public static void println(float x) {
        out.println(x);
    }
    public static void println(double x) {
        out.println(obj);
    }
    public static void println(String s) {
        out.println(obj);
    }
}
package com.qww.test01;

public class Range {

    public static int[] range(int n) {
        return range(0, n);
    }

    public static int[] range(int start, int end) {
        return range(start, end, 1);
    }

    public static int[] range(int start, int end, int step) {
        int size = (end - start) / step;
        int[] ret = new int[size];
        for (int i = 0; i < ret.length; i++) {
            ret[i] = start + i * step;
        }

        return ret;
    }
}

6.1.4 用import改变行为

java没有C的条件编译问题。因为C的条件编译功能是解决跨品台问题的。

条件编译还有其他用体:调试程序,调试功能在开发时开启,在发布时禁用。可以通过修改导入的package来实现。修改方法是将程序中拥到的代码从调试版改为发布版,

练习3

package com.qww.exec03.debug;

public class A {
    private int a;

    public static void debug(String s) {
        System.out.println(s);
    }
    public int getA() {
        return a;
    }
    public void setA(int a) {
        this.a = a;
    }
}
package com.qww.exec03.debugoff;

public class A {
   private int a;
   
   public static void debug(String s) {}
   public int getA() {
      return a;
   }
   public void setA(int a) {
      this.a = a;
   }
}
package com.qww.exec03;

import com.qww.exec03.debug.A;
// import static com.qww.exec03.debug.A.*;
import static com.qww.exec03.debugoff.A.*;

public class Exec03 {

    public static void main(String[] args) {
        A a = new A();
        a.setA(10);
        debug(a.getA() + "");
    }
}

6.1.5 对使用包的忠告

无论什么时候创建包,都已经将在给定包的名称中的时候隐含地指定了目录结构(包名称对应目录结构)。该目录必须在以CLASSPATH开始的目录中可以查找到。如果出现错误说找不到某个类,或者类似信息,试试注释掉package语句。

通常编译的代码和源代码不会放在同一个目录,但是也必须让JVM通过CLASSPATH可以找到编译的代码。

6.2 Java访问权限修饰词

publicprotected、没有权限修饰符(包访问权限)、private可以修饰方法、字段的定义。

6.2.1 包访问权限

默认访问权限没有任何权限修饰符,是指包访问权限(有时用friendly表示)。意味着当前包中的其他类都可以访问,但是这个包外面的类不能访问。因为一个编译单元(也就是写在一个文件中的多个类),只能属于一个包,所以同一个文件内的没有修饰多个类可以互相访问。

在同一个包中的其他类如何访问这个类的成员呢?

  • 使用public修饰这个类的成员。public修饰的字段或方法,无论在哪里都可被其他类访问。
  • 将这个类的成员的权限修饰符改为默认的包访问权限,或者通过子类继承这个类,且这个类的成员的修饰符为publicprotected。子类可以访问父类中publicprotected的成员,但不能访问private修饰的成员。只有两个类都在同一个包中,才可以访问包访问权限的成员。
  • 在这个类中编写“accessor”和“mutator”f方法(也叫Get和Set方法)。

6.2.2 public:接口访问权限

package com.qww.test02.access.dessert;

public class Cookie {

    public Cookie() {}
    void bite() {}
}
package com.qww.test02.access;

import com.qww.test02.access.dessert.Cookie;

public class Cake {

    public static void main(String[] args) {
         Cookie cookie = new Cookie();
         // Error:(12, 17) java: bite()在com.qww.test02.access.dessert.Cookie中不是公共的; 无法从外部程序包中对其进行访问
         // cookie.bite();
    }
}

Cookie.javacom.qww.test02.access.dessert中,而Cake.javacom.qww.test02.access包中,public修饰Cookie()构造器并且Cookie类也是puvlic的。所以Cake类中是有权访问Cookie()构造器的。而bite()方法被默认包访问权限所修饰,CookieCake不在同一个包中,Cake是没有权限访问bite()的。

默认包

// Pie.java
class Pie {
    void f() {
        System.out.println("f()");
    }
}
// Cake.java
class Cake {
    public static void main(String[] args) {
        Pie pie = new Pie();
        pie.f();
    }
}
// 运行结果
f()

看起来这两个文件(A.javaB.java)好像毫不相关。但是可以Cake中可以通过创建的Pie对象调用f()方法。原因是Cake.java和Pie.java在同一个相同的目录里,并且我们没有改给它们设定任何包名。java会将这样的文件看做是该目录的默认包中。也就都有了包访问权限,从而可以互相访问。

6.2.3 private:你无法访问

private修饰的成员,只有本类可以访问,其他类都没哟权限访问。因此可以随意改变本尅的成员,而不用担心会影响到其他类。

package com.qww.test03;

public class Test03 {

    public static void main(String[] args) {
        // Error:(6, 15) java: A() 在 com.qww.test03.A 中是 private 访问控制
        // A a = new A();

        A a = A.getInstance();
    }
}
package com.qww.test03;

public class A {

    private A() {}

    static A getInstance() {
        return new A();
    }
}

private可以控制如何创建对象,并阻止别人方法某个特定的构造器(或全部构造器)。A的构造器是private的,并且A只有这一个无参构造器,所以它还可以阻止别的继承它。

不能因为在类中的某个对象的引用是private,就认为其他类的对象无法拥有该对象的public引用。

6.2.4 protected:继承访问权限

protected关键字处理的是继承的访问权限,继承要解决的问题:通过extends来扩展一个基类,而不需要修改这个类。子类会继承父类的所有成员。

如果创建了另一个新的包,一个包里吗有父类,而另一个包里面有子类,那么子类可访问的成员是父类中public修饰的成员。protected解决的问题:在不同的包内,父类需要为所有子类提供其特定成员的包访问权限。相同包内的其他类可以访问protected修饰的成员、

package com.qww.test04.cookie;

public class Cookie {

    protected void bite() {
        System.out.println("bite()");
    }
}
package com.qww.test04;

import com.qww.test04.cookie.Cookie;

public class ChocolateChip extends Cookie {

    public void chomp() {
        /* 
        bite()方法没有权限修饰符修饰时
        Error:(8, 10) java: 找不到符号
        符号:   方法 bite()
        位置: 类 com.qww.test04.ChocolateChip
        */
        bite();
    }

    public static void main(String[] args) {
        ChocolateChip x = new ChocolateChip();
        x.chomp();
    }
}
package com.qww.test04.cookie2;

import com.qww.test04.Cookie;

public class ChocolateChip2 extends Cookie {

    public void chomp() {
        bite();
    }

    public static void main(String[] args) {
        ChocolateChip2 x = new ChocolateChip2();
        x.chomp();
    }
}

Cookie类中有一个默认权限访问的方法,并且子类ChocolateChip和父类Cookie不在同一个包内,虽然子类会将父类的字段和方法都继承了过来,但是因为父类bite()方法包访问权限的,所以子类是无法访问bite()的。这就需要父类使用protected关键字为所有子类提供包访问权限。只有只有不同包的子类或者同一个包中的其他类可以访问bite()方法了。

练习4

java如何实现权限分配 java实现权限分配思想_java如何实现权限分配

package com.qww.exec04.hello2;

import com.qww.exec04.BaseClass;

public class Exec04 {
    public static void main(String[] args) {
        BaseClass baseClass = new BaseClass();
        // Error:(14, 19) java: f() 在 com.qww.exec04.BaseClass 中是 protected 访问控制
        // 如果protected是public 这行代码就不会报错了。
        // baseClass.f();
    }
}
package com.qww.exec04;

public class BaseClass {
    protected void f() {
        System.out.println("f()");
    }
}
package com.qww.exec04.hello;

import com.qww.exec04.BaseClass;

public class SubClass extends BaseClass {
    public static void main(String[] args) {
        SubClass subClass = new SubClass();
        subClass.f();
    }
}

练习5

package com.qww.exev05;

public class A {

    public  int a;
    protected  int b;
    int c;
    private int d;

    public void f1() {}
    protected void f2() {}
    void f3() {}
    private void f4() {}

}
package com.qww.exev05;

public class Exec05 {

    public static void main(String[] args) {
        A a = new A();
        System.out.println(a.a);
        System.out.println(a.b);
        System.out.println(a.c);
        // Error:(10, 30) java: d 在 com.qww.exev05.A 中是 private 访问控制
        // System.out.println(a.d);
        a.f1();
        a.f2();
        a.f3();
        // Error:(15, 10) java: f4() 在 com.qww.exev05.A 中是 private 访问控制
        // a.f4();
    }
}

同一个包里面,不能访问private修饰的成员。

练习6

6.3 接口和实现

使用权限访问控制,有两个原因:

  1. 设定客户端程序员可以使用和不可以使用的界限,可以修改哪些客户端不是呀的代码,不用担心会影响客户端的代码。
  2. 将借口和具体实现进行分离。客户端如果只是仅仅向接口发送消息的话,那就可以随意修改所有非public的代码,而不会影响客户端的代码。

6.4 类的访问权限

练习7

package com.qww.exec07;

import com.qww.exec07.access.Widget;

public class Exec07 {

    public static void main(String[] args) {
        Widget widget = new Widget();
    }
}
package com.qww.exec07.access;

public class Widget {
}

类不可以是private的和protected的,只有两个选择:包访问权限或者public.不希望别问对访问该类,可以将该类的每个构造器都改为private的,这样就可以阻止其他人创建该类的对象。一个例外是,可以在该类的static成员内部创建对象。

特例:内部类可以是private或者protected的。

package com.qww.test05;

public class Lunch {

    public static void main(String[] args) {
        // Error:(6, 23) java: Soup1() 在 com.qww.test05.Soup1 中是 private 访问控制
        // Soup1 soup1 = new Soup1();
        Soup1 soup1 = Soup1.makeSoup();

        Soup2.access().f1();
    }
}

class Soup1 {
    private Soup1() {}
    public static Soup1 makeSoup() {
        return new Soup1();
    }
}

class Soup2 {
    private Soup2() {}
    private static Soup2 ps1 = new Soup2();
    public static Soup2 access() {
        return ps1;
    }

    public void f1() {}
}

构造器私有化,这样别人就无法创建该类的对象了。该怎么使用这个类的对象呢?有两中做法:

  1. 在Soup1中,创建一个static方法,这个静态方法创建了一个新的Soup1对象并且返回了它的引用。我们可以在返回这个引用之前做一下额外的工作,比如记录创建了多个对象。
  2. 使用单例(singleton)设计模式,只能创建一个对象。Soup类的构造器私有化,通过ptivate static的字段来创建对象,所有有且只有一个对象,并且只能通过public的access方法访问它。

练习8

package com.qww.exec08;

public class ConnectionManager {

    private static int initCapacity = 6;
    private static Connection[] conns = new Connection[initCapacity];
    private static int size;
    private static ConnectionManager manager = new ConnectionManager();

    private  ConnectionManager() {}
    public static ConnectionManager access() {
        return manager;
    }

    public static Connection[] getConns() {
        return size==0 ? null : conns;
    }

    public  Connection getConnection() {
        Connection conn = Connection.getInstance();
        // 数组扩容两倍
        if (size > initCapacity >> 1) copyArr();
        conns[size++] = conn;
        return conn;
    }

    private void copyArr() {
        Connection[] tmp = new Connection[initCapacity << 1];
         for (int i = 0; i < size; i++) {
            tmp[i] = conns[i];
        }
        conns = tmp;
    }

    public int size() {
        return size;
    }

}
package com.qww.exec08;

public class Exec08 {

    public static void main(String[] args) {
        ConnectionManager manager = ConnectionManager.access();
        Connection conn = manager.getConnection();
        Connection conn2 = manager.getConnection();
        Connection conn3 = manager.getConnection();
        Connection conn4 = manager.getConnection();
        Connection conn5 = manager.getConnection();
        Connection[] conns = ConnectionManager.getConns();
        if (conns  != null) {
            for (Connection connection : conns) {
                System.out.println(connection);
            }
        }
    }
}
package com.qww.exec08;

public class Connection {

    private Connection() {}

    public static Connection getInstance() {
        return new Connection();
    }
}

练习9

package com.qww.exec09.access.foreign;

import com.qww.exec09.access.local.*;

public class Foreign {

    public static void main(String[] args) {
        // Error:(12, 9) 
      	// java: com.qww.exec09.access.local.PackagedClass在com.qww.exec09.access.local中不是公共的; 无法从外部程序包中对其进行访问
        // PackagedClass pc = new PackagedClass();
    }
}
package com.qww.exec09.access.local;

class PackagedClass {

    public PackagedClass() {
        System.out.println("Creating a packaged class");
    }
}

PackagedClass类没有权限访问修饰符修饰,说明是默认权限修饰,包访问权限。位于foreign包中的Foreign类是没有权限访问不同包(local)中的PackagedClass的。将两个类放到同一个包中,可以解决问题,编译器不会报错。