Java012Java访问权限控制

Java012Java访问权限控制

Why?为何要进行访问权限控制呢?

How?怎么进行访问权限控制?

1包:库单元

1.1代码组织

1.2创建独一无二的包名

1.3定制工具库

1.4用import改变行为

1.5对使用包的忠告

2. Java访问权限修饰词

2.1包访问权限(有时也表示为friendly)

2.2public接口访问权限

2.3private你无法访问

2.4protected继承访问权限

3. 接口和实现

4. 类的访问权限

5. 总结

 

Java012Java访问权限控制

Why?为何要进行访问权限控制呢?

访问控制(或隐藏具体的实现)与“最初的实现并不恰当”有关。

所有优秀的作者,包括那些编写软件的程序员,都清楚其著作的某些部分直至重新创作的时候才变得趋于完美,有时甚至可能要反复重写多次。如果你把一个代码放到了某个位置,等过一会儿回头再看时,有可能会发现有更好的方式去实现相同的功能。这正是重构的原动力之一,重构即重写代码,以使得它更可读、更易理解,并因此而更具可维护性。(其实对于项目所需的时间和资金来说,最大的部分并非投入到了最初的代码编写上,而是投入到了代码的维护上。因此,使代码更加易于理解和维护就意味着节省了大量的金钱。)

但是,在这种修改和完善代码的愿望之下,这存在着巨大的压力。通常总是会有一些消费者(客户端程序员)需要你的代码在某些方面保持不变。因此你想改变代码,而他们却想让代码保持不变。由此而产生了在面向对象设计中需要考虑的一个基本问题:“如何把变动的事物与保持不变的事物区分开来”。

这对类库(library)而言尤为重要。该类库的消费者必须依赖它所使用的那部分类库,并且能够知道如果类库出现了新版本,他们并不需要改写代码。从另一个方面来说,类库的开发者必须有权限进行修改和改进,并确保客户代码不会因为这些改动而受到影响。

这一目标可以通过约定来达到。例如,类库开发者必须同意在改动类库中的类时不得删除任何现有方法,因为那样会破坏客户端程序员的代码。但是,与之相反的情况会更加棘手。在有域(即数据成员)存在的情况下,类库开发者要怎样才能知道究竟都有哪些域已经被客户端程序员所调用了呢?这对于方法仅为类的实现的一部分,因此并不想客户端程序员直接使用的情况来说同样如此。如果程序开发者想要移除旧的实现而要添加新的实现时,结果将会怎样呢?改动任何一个成员都有可能破坏客户端程序员的代码。于是类库开发者会手脚被缚,无法对任何事物进行改动。

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

How?怎么进行访问权限控制?

1包:库单元

包内包含一组类,它们在单一的名字空间之下被组织在了一起。Java使用关键字package加以控制,而访问权限修饰词会因类是存在于一个相同的包,还是存在于一个单独的包而受到影响。

1.1代码组织

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

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

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

如果使用package语句,它必须是文件中除注释以外的第一句程序代码。即表示你正在声明该编译单元中的public类名称是位于该package名称的保护伞下。任何想要使用该名称的人都必须:指定全面或者与相应package名称结合使用关键字import。(请注意,Java包的命名规则全部使用小写字母,包括中间字也是如此。)

我们之所以要导入,就是要提供一个管理名字空间的机制。所有类成员的名称都是必须隔离的。由于名字之间的潜在冲突,在Java中对名称空间进行完全控制并未每个类创建唯一的标识符组合就成为了非常重要的事情。

如果准备编写对在同一及其上共存的其他Java程序友好的类库或程序的话,就需要考虑如何防止类名称之间的冲突问题。

身为一名类库设计员,很有必要牢记:package和import关键字允许你做的,是将单一的全局名字空间分割开,使得无论多少人使用Internet以及Java开始编写类,都不会出现名称冲突问题。(在使用了冲突名字的情况下【在导入的多个路径下存在相同的类名,并且不同路径下的该类都会使用-此种情况出现的根本原因在于不同人员维护一套代码时没有注意关注其他开发人员是否以及在其他路径下已经创建过了同样名称的类-太忙+不专业】,必须使用指定全名的方式。)

1.2创建独一无二的包名

一个包并未真正将被打包的东西装成单一的文件,并且一个包可以由许多.class文件构成。特定包的所有.class文件都位于同一个目录下。利用操作系统的层次化的文件结构将特定包的所有文件收入到一个子目录。

通过将.class文件所在的路径位置编码成package的名称。按照惯例,package名称的第一部分是类的创建者的反顺序的Internet域名。如果你遵照惯例,Internet域名应该是独一无二的,因此你的package名称也将是独一无二的,也就不会出现名称冲突的问题了(也就是说,只有在你将自己的域名给了别人,而他又以你曾经使用过的路径名称来编写Java程序代码时,才会出现冲突)。当然,如果你没有自己的域名,你就得够早一组不大可能与他人重复的组合(例如你的名字),来创立一个独一无二的package名称,如果你打算发布你的Java程序代码,稍微花点力气去取得一个域名,还是很有必要的。其次,把package的第二部分分解为你机器上的一个目录。所以当Java程序运行并且需要加载.class文件时,就可以确定.class文件所在目录位置。

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

对于使用Java的新手而言,设立CLASSPATH是很麻烦的一件事情(我最初使用时就是这样的);为此,Sun将Java2中的JDK改造的更聪明一些。在安装后你会发现,即使你未设立CLASSPATH,你也可以编译并运行基本的Java程序。然而,要编译和运行其他自定义的源码包,就得向你的CLASSPATH中添加相关代码树中的基目录了。

1.3定制工具库

具备以上知识后,就可以定制自己的工具库来减少或消除重复的程序代码了!(豁然开朗!)如,我们经常使用的System.out.println(),可以创建一个print类:

package net.mindview.util;

import java.io.*;

public class Print{

public static void print(Object obj){

System.out.println(obj);//打印后换行

}

public static void print(){

System.out.println();//打印一空行

}

public static void printnb(Object obj){

System.out.print(obj);//打印后不换行

}

}

在其他地方要打印时,即可这么写:

import static net.mindview.util.Print.*;

public class PrintTest{

public static void main(String[] args){

print(“Available from  now on!”);

print(100);

}

}

从现在开始,无论何时创建了有用的新工具,都可以将其添加到你自己的类库中啦!

1.4用import改变行为

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

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

1.5对使用包的忠告

务必记住,无论何时创建包,都已经在给定包的名称的时候隐含地指定了目录结构。这个包必须位于其名称所指定的目录之中,而该目录必须是在以CLASSPATH开始的目录中可以查询到的。

注意,编译过的代码通常放置在与源代码不同的目录中,但是必须保证JVM使用CLASSPATH可以找到该路径。

2.Java访问权限修饰词

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

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

2.1包访问权限(有时也表示为friendly)

类所在当前包中的所有其他类对该类的包访问权限成员都有访问权限,但是对于这个包之外的所有类,类的包访问权限成员是private的。由于一个编译单元(即一个文件),只能隶属于一个包,所以经由包访问权限,处于同一个编译单元中的所有类彼此之间都是自动可访问的。

包访问权限允许将包内所有相关的类组合起来,以使它们彼此之间可以轻松地相互作用。当把类组织起来放进一个包内之时,也就是给它们的包访问权限的成员赋予了相互访问的权限,你“拥有”了该包内的程序代码。“只有你拥有的程序代码才可以访问你拥有的其他程序代码”,这是合理的。

类控制着哪些代码有权访问自己的成员,其他类取得对某成员的访问权的唯一途径是:

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

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

3)继承而来的类既可以访问public成员也可以访问protected成员(但不能访问private成员)。只有在两个类都处于同一个包内时,它才可以访问包访问权限的成员。

4)提供访问器(accessor)和变异器(mutator)方法(也称get/set方法),以读取和改变数值。对OOP而言,这是最优雅的方式,而且这也是JavaBeans的基本

2.2public接口访问权限

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

2.3private你无法访问

关键字private的意思是,除了包含该成员的类之外,其他任何类都无法访问该成员。

(若一个类的默认构造器是唯一定义的构造器,并且它是private的,那么将不允许对此类继承)

class Sundae{

private Sundae(){

static Sundae makeASundae(){

return new Sundae();

}

}

}

public class IceCream{

public static void main(String[] args){

//!Sundae x = new Sundae();

Sundae x = Sundae.makeASundae();

}

}

这是一个说明private终有其用武之地的示例:可用想控制如何创建对象,并组织别人直接访问某个特定的构造器(或全部构造器)。在上面的例子中,不能通过构造器来创建Sundae对象,而必须调用makeASundae()方法来达到此目的。

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

2.4protected继承访问权限

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

有关继承技术的一个很有趣的事情:如果类A存在一个protected方法a(),那么该方法同时也存在于任何一个从A继承而来的类中。但是由于a()有包访问权限而且它位于另一个包内,所以我们在这个包内是无法使用它的。当然,也可以把它指定为public,但是这样做所有的人就都有了访问权限,而且很可能这并不是你所希望的。这时其他包内继承自A类的其他类想访问方法a(),则可用通过导入类A所在路径的方式实现,否则即使是继承类也无法直接访问到protected的父类A的a()方法。

3.接口和实现

访问权限的控制常被称为是具体实现的隐藏。把数据和方法包装进类中,以及具体实现的隐藏,常共同被称作是封装。

出于两个很重要的原因,访问权限控制将权限的边界划在了数据类型的内部。第一个原因是要设定客户端程序员可用使用和不可用使用的界限。可用在结构中建立自己的内部机制,而不必担心客户端程序员会偶然地将内部机制当作是他们可用使用的接口的一部分。

这个原因直接引出了第二个原因,即接口和具体实现进行分离。如果结构是用于一组程序之中,而客户端程序员除了可以向接口发送信息之外什么也不可以做的话,那么就可以随意更改所有不是public的东西(例如有包访问权限、protected和private的成员),而不会破坏客户端代码。

4.类的访问权限

在Java中,访问权限修饰词也可以用于确定库中的哪些类对于该库的使用者是可用的。

请注意,外部类即不可以是private的(这样会使得除该类之外,其他任何类都不可用访问它),也不可以是protected的。(内部类可用这么做)所以,对于外部类的访问权限,只有两个选择:public或包访问权限。如果不希望其他任何人对该类拥有访问权限,可用把所有的构造器都指定为private,从而阻止任何二创建该类的对象,但是有一个例外,就是你在该类的static成员内部可以创建。

5.总结

无论在什么样的关系之中,设立一些为各成员所遵守的界限始终是很重要的。当建立一个类库,也就与该类库的用户建立了某种关系,这些用户就是客户端程序员,他们是另外一些程序员,他们将你的类库聚合成为一个应用程序,或是运用你的类库来创建一个更大的类库。

控制对成员的访问权限有两个原因:

① 第一个原因,为了使用户不要触碰那些他们不该触碰的部分,这些部分对于类内部的操作是必要的,但是它并不属于客户端程序员所需接口的一部分。因此,将方法或域指定成private,对客户端程序员来说是一种服务。因为这样他们可以很清楚地看到什么对他们重要,什么是他们可以忽略的。这样简化了他们对类的理解。

② 第二个也是最重要的原因,为了让类库设计者可以更改类的内部工作方式,而不必担心这样会对客户端程序员产生重大影响。例如,最初可能会以某种方式创建一个类,然后发现有其他方式可以大大提高运行速度。如果接口和实现可以被明确地隔离和加以保护,那么就可以实现这一目的,而不必强制客户端程序员重写编写代码。访问权限控制可以确保不会有任何客户端程序员依赖于某个类的底层实现的任何部分。

当具备了改变底层实施细节的能力时,不仅可以随意地改善设计,也有了犯错的可能性。无论如何细心地计划并设计,都有可能犯错。当了解到你所犯的错误是相对安全的时候,就可以更加放心地进行实验,也就可以更快地学会,更快地完成项目。

类的公共接口是用户真正能够看到的,所以这一部分是在分析和设计的过程中决定该类是否正确的最重要的部分。尽管如此,你仍然有改进的空间。如果在最初无法创建出正确的接口,那么只要不删除任何客户端程序员在他们的程序中已经用到的东西,就可以在以后添加更多的方法。

注意

访问权限控制专注于类库创建者和该类库的外部使用者之间的关系,这种关系也是一种通信方式。然后,在许多情况下事情并非如此。例如,你自己编写了所有的代码,或者你在一个组员聚集在一起的项目组中工作,所有东西都放在同一个包中。这些情况是另外一种通信方式,因此严格地遵循访问权限规则并不一定是最佳选择,默认包访问权限也许只是可行而已。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dylanioucn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值