Java基础:访问权限控制

来源:互联网 发布:易语言反编译成源码 编辑:程序博客网 时间:2024/05/22 03:20

前言

在review其他人写的代码时,会发现存在一些个人觉得不好的编码风格,比如域定义为public或默认的包访问权限等,这会使某些不必要的细节暴露出来,而且如果以后删掉该域时可能会由于某些类对该与的直接引用的原因而失败。从以上的种种问题分析来看,很有必要重新认识一下Java的权限控制的知识点。

正题

Java提供了访问权限修饰词,以供类库开发人员向客户端程序员指明哪些是可用的,哪些是不可用的。访问权限控制的等级,从最大权限到最小权限依次为:public、protectd、包访问权限(没有关键字)和private。根据前述内容,读者可能会认为:作为一名类库设计者,你会尽可能将一切方法都定位private,而仅向客户端程序员公开你愿意让他们使用的方法。这样做是完全正确的,尽管对于那些经常使用别的语言(特别是C语言)编写程序并在访问时不受任何限制的人而言,这与他们的直觉相违背。

不过,构件类库的概念以及对于谁有权取用该类库构件的控制问题都还是不完善的。其中仍旧存在着如何将构件捆绑到一个内聚的类库单元中的问题。对于这一点,Java用关键字package加以控制,而访问权限修饰词会因类是存在于一个相同的包,还是存在于一个单独的包而受到影响。

包:库单元

包内包含有一组类,他们在单一的名字空间之下被组织在了一起。

package com.access;import java.util.ArrayList;public class Fish {public static void main(String[] args) {ArrayList<String> list = new ArrayList<String>();}}
通过使用improt关键字导入ArrayList类。在import语句中命名该类。

之所以要导入,就是要提供一个管理名字空间的机制。所有类成员的名称都是彼此隔离。A类中的方法与B类中具有相同特征标记(参数列表)的方法不会彼此冲突。

当编写一个Java源代码文件时,此文件通常被称为编译单元(有时也被称为转译单元)。每个编译单元都必须有一个后缀名.java,而在编译单元内则可以有一个public类,该类的名称必须与文件的名称相同(包括大小写,但不包括文件的后缀名.java)。每个编译单元只能有一个public类,否则编译器就不会接受。如果在该编译单元之中还有额外的类的话,那么在包之外是无法看到这些类的,这是因为它们不是public类,而且它们主要是用来为主public类提供支持的。

当编译一个.java文件时,在.java文件中的每一个类都会有一个输出文件,而该输出文件名称与.java文件中每个类的名称相同,只是多了一个后缀名.class。因此,在编译少量.java文件之后,会得到大量的.class文件。如果用编译型语言编写过程序,那么对于编译器产生一个中间文件(通常是一个obj文件),然后在通过链接器(用以创建一个可执行文件)或类库产生器(用以创建一个类库)产生的其他同类文件捆绑在一起的情况,可能早就司空见惯。但这并不是Java的工作方式。Java可运行程序是一组可以打包并压缩为一个Java文档文件(JAR,使用Java的jar文档生成器)的.class文件。Java解释器负责这些文件的查找、装载和解释。

类库实际上是一组类文件。其中每个类文件都有一个public类,以及任意数量的非public类。因此每个文件都有一个构件。如果希望这些构件(每一个都有他们自己独立的.java和.class文件)从属同一个群组,就可以使用关键字package。

package com.access;public class Fish {}class AFish {}
对应的字节码:

Constant pool:   #1 = Methodref          #3.#10         //  java/lang/Object."<init>":()V   #2 = Class              #11            //  com/access/AFish   #3 = Class              #12            //  java/lang/Object   #4 = Utf8               <init>   #5 = Utf8               ()V   #6 = Utf8               Code   #7 = Utf8               LineNumberTable   #8 = Utf8               SourceFile   #9 = Utf8               Fish.java  #10 = NameAndType        #4:#5          //  "<init>":()V  #11 = Utf8               com/access/AFish  #12 = Utf8               java/lang/Object{  com.access.AFish();    flags:    Code:      stack=1, locals=1, args_size=1         0: aload_0         1: invokespecial #1                  // Method java/lang/Object."<init>":()V         4: return}
AFish类的访问标志位:00 20。默认是包访问权限。

如果使用package语句,它必须是文件中除注释以外的第一句程序代码。在文件起始处写:

package com.access;

就表示你在声明该编译单元是名为com.access的类库的一部分。或者换种说法,你正在声明该编译单元中的public类名称位于access名称的保护伞下。任意想要使用该名称的人必须使用前面给出的选择,指定全名或者与com.access组合使用关键字import。(请注意,Java包的命名规则全部使用小写字母,包括中间的字母也是如此。)

按照惯例,package名称的第一部分是类的创建者的反顺序的Internet域名。如果你按照惯例,Internet域名应该是独一无二的,因此你的package名称也将是独一无二的,也就不会出现名称冲突的问题了。第二部分是把package名称分解为你的机器上的一个目录。所以当Java程序运行并需要加载.java文件的时候,它就可以确定.class文件在目录上所处的位置。

Java解析器的运行过程入下:首先,找出环境变量CLASSPATH(可以通过操作系统来设置)。CLASSPATH包含一个或多个目录,用来查找.class文件的根目录。从根目录开始,解析器获取包的名称并将每个句点替换成反斜杠,以从CLASSPATH根中产生一个路径名称(于是,package foo.bar.baz就变成为foo\bar\baz或foo/bar/baz或其他,这一切取决于操作系统)。得到的路径会与CLASSPATH中的各个不同的项相连接,解析器就在这些目录中查找与你所要创建的类名称相关的.class文件。(解析器还会去查找某些涉及Java解析器所在位置的标准目录。)

用import改变行为

Java没有C的条件编译功能,该功能可以使你不必更改任何程序代码,就能够切换开关并产生不同的行为。Java去掉此功能的原因可能是因为C在绝大多数情况下是用此功能解决跨平台问题的。即程序代码的不同部分是根据不同的平台来编译的。由于Java自身可以自动跨越不同的平台,因此这个功能对Java而言是没有必要的。

然而条件编译还有其他一些有价值的用途。调试就是一个很常见的用途。调试功能在开发过程中是开启的,而在发布的产品中时禁用的。可以通过修改被导入的package的方法来实现这一目的,修改的方法就是将你程序中用到的代码从调试版改为发布版,这一技术可以适用于任何种类的条件代码。

务必记住,无论何时创建包,都已经在给定包的名称的时候隐含的指定了目录结构。这个包必须位于其名称所指定的目录之中,而该目录必须是在以CLASSPATH开始的目录中可以查询到的。最初使用关键字package,可能会有一点不顺,因为除非遵守“包的名称对应目录路径”的规则,否则将会收到许多出乎意料的运行时信息,告知无法找到特定的类,哪怕是这个类就位于同一个目录之中。如果你收到的类似信息,就用注释掉package语句的方法调试一下,如果这样程序就能运行的话,你就可以知道问题出在哪里了。

Java访问权限修饰词

public、protected和private这几个Java访问权限修饰词在使用时,是置于类中每个成员的定义之前的—无论它是一个域还是一个方法。每个访问权限修饰词仅控制它所修饰的特定定义的访问权。

如果不提供任何访问权限修饰词。则意味着它是“包访问权限”。因此无论如何,所有事物都具有某种形式的访问权限控制。

包访问权限

默认访问权限没有任何关键字,但通常是指包访问权限(有时也表示成friendly)。这就意味着当前的包中所有其他类对那个成员都有访问权限,但对于这个包之外的所有类,这个成员却是private。由于一个编译单元(即一个文件),只能隶属于一个包,所以经由包访问权限,处于同一个编译单元中的所有类彼此之间都是自动可访问的。

包访问权限允许将包内所有相关类组合起来,以使他们彼此之间可以轻松的相互作用。当把类组织起来放进一个包内之时,也就给他们的包访问权限的成员赋予了相互访问的权限,你“拥有”了该包内的程序代码。“只有你拥有的程序代码才可以访问你所拥有的其他程序代码”,这是合理的。应该说,包访问权限把类聚集在一个包中的做法提供了意义和理由。在许多语言中,在文件内组织定义的方式是任意的,但在Java中,则要强制你以一种合理的方式对它们加以组织。另外,你可能还想要排除这样的类—它们不应该访问在当前包中所定义的类。

public class Dog {String name;}
对应的字节码:
Constant pool:   #1 = Methodref          #3.#12         //  java/lang/Object."<init>":()V   #2 = Class              #13            //  com/access/Dog   #3 = Class              #14            //  java/lang/Object   #4 = Utf8               name   #5 = Utf8               Ljava/lang/String;   #6 = Utf8               <init>   #7 = Utf8               ()V   #8 = Utf8               Code   #9 = Utf8               LineNumberTable  #10 = Utf8               SourceFile  #11 = Utf8               Dog.java  #12 = NameAndType        #6:#7          //  "<init>":()V  #13 = Utf8               com/access/Dog  #14 = Utf8               java/lang/Object{  java.lang.String name;    flags:  public com.access.Dog();    flags: ACC_PUBLIC    Code:      stack=1, locals=1, args_size=1         0: aload_0         1: invokespecial #1                  // Method java/lang/Object."<init>":()V         4: return}
name字段访问标志位:00 00。从class字节码可以看出默认包访问权限是00 00。

取得对某成员的访问权的唯一途径是:

1)使该成员称为public。于是,无论是谁,无论在哪里,都可以访问该成员。

2)通过不加访问权限修饰词并将其他类放置于同一个包内的方式给成员赋予包访问权。于是包内的其他类也就可以访问该成员了。

3)通过使用继承方式使类可以访问public成员也可以访问protected成员。

4)提供访问器(accessories)和变异器(mutator)方法(也称作get/set方法),以读取和改变数值。

public:接口访问权限

使用关键字public,就意味着public之后紧跟着的成员声明自己对每个人都是可用的。尤其是使用类库的客户端程序员更是如此。

public String name;

对应的字节码:

{  public java.lang.String name;    flags: ACC_PUBLIC   .......}
protected:继承访问权限

关键字protected处理的是继承的概念,通过继承可以利用一个现有类—我们将其称为基类,然后将新成员添加到该现有中而不必碰到该现有类。还可以改变该类的现有成员的行为。为了从现有类中继承,需要声明新类extends(扩展)了一个现有类。

有时,基类的创建者会希望有某个特定成员,把对它的访问权限赋予派生类而不是所有类。这就需要protected来完成这一工作。protected也提供包访问权限,也就是说,相同包内的其他类可以访问protected元素。

private:你无法访问

关键字private的意思是,除了包含该成员的类之外,其他任何类都无法访问这个成员。由于处于同一个包内的其他类是不可以访问private成员的,因此这等于说是自己隔离了自己。从另一方面说,让许多人共同合作来创建一个包也是不太可能的,为此private就允许你随意改变该成员,而不必考虑这样做是否会影响到包内其他的类。

默认的包访问权限通常已经提供了充足的隐藏措施。请记住,使用类的客户端程序员是无法访问包访问权限成员的。这样做很好,因为默认访问权限是一种我们常用的权限,同时也是一种忘记添加任何访问权限控制时能够自动得到的权限,因此通常考虑的是,哪些成员是想要明确公开给客户端程序员使用的,从而将他们声明为public,而在最初,你可能不会认为自己经常会需要使用关键字private,因为没有它,照样可以工作然而,事实就很快可以证明,对private的使用时多么的重要,在多线程环境下更是如此。

任何可以肯定只是该类的一个“助手”方法的方法,都可以把它指定为private,以确保不会在包内的其他地方吴用它,于是也就防止了你会去改变或删除这个方法。将方法指定为private确保了你拥有这种选择权。

对于类中的private域同样适用,除非必须公开底层实现细目,否则就应该将所有域指定为private。然而,不能因为在类中某个对象的引用是private,就认为其他的对象无法拥有该对象的public引用。