Thinking in Java 访问权限控制(第六章)

构件类库的概念以及对于谁有权取用该构件类库的控制问题还不是完善的。对于这一点,Java用关键字pacage加以控制,而访问权限修饰词会因类是存在于一个相同的包,还是存在于一个单独的包而受到影响。

如何将类库构件置于包中
什么是包

例如做一碗油泼面, 需要先和面, 擀面, 扯出面条, 再烧水, 下锅煮熟, 放调料, 泼油.
但是其中的 “和面, 擀面, 扯出面条” 环节难度比较大, 不是所有人都能很容易做好. 于是超市就提供了一些直接已
经扯好的面条, 可以直接买回来下锅煮. 从而降低了做油泼面的难度, 也提高了制作效率.
程序开发也不是从零开始, 而是要站在巨人的肩膀上.
像我们很多程序写的过程中不必把所有的细节都自己实现, 已经有大量的标准库(JDK提供好的代码)和海量的第
三方库(其他机构组织提供的代码)供我们直接使用. 这些代码就放在一个一个的 “包” 之中. 所谓的包就相于卖面条的超市. 只不过, 超市的面条只有寥寥几种, 而我们可以使用的 “包” , 有成千上万.

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

代码组织
当编译一个.java文件时,在.java文件中每个类都会有一个输出文件,而该输出文件的名称与.java文件中每个类的名称相同,只是多了一个后缀名.class。因此,在编译少量.java文件之后,会得到大量.class文件。Java可运行程序是一组可以打包并压缩为一个Java文档文件的.class文件。Java解释器负责这些文件的查找、装载和解释。

类库实际上是一组类文件,每个文件中都有一个public类,以及任意数量的非public类。因此每个文件都有一个构件。如果希望这些文件属于同一个群组,就可以使用关键字package。

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

package access

就表示你在声明该编译单元是名为access的类库的一部分。或者换种说法,你正在声明该编译单元中的public类名称是位于access名称的保护伞下。任何想要使用该名称的人都必须使用前面给出的选择,指定全名或者与access结合使用关键字import。
例如,假设文件的名称是MyClass.java ,这就意味着在该文件中有且只有一个public类,该类的名称必须是MyClass
现在,如果有人想用MyClass或者是access中的任何其他public类,就必须使用关键字import来使access中的名称可用。

import access.mypackage.*;
public class QualifiedMyClass{
	public static void main(String[] args){
		MyClass m=new MyClass();
	}
}
	

身为一名类库设计员,很有必要牢记:package和import关键字允许你做的,是将局名字空间分隔开,使得无论多少人使用Internet以及Java开始编写类,都不会出现名称冲突问题

Java访问权限修饰词

public、protected和private这几个Java访问权限修饰词在使用时,是置于类中每个成员的定义之前的—无论它是一个域还是一个方法。每个访问权限修饰词仅控制它所修饰的特定定义的访问权。
如果不提供任何访问权限修饰词,则意味它是“包访问权限”。因此无论如何,所有的事物都具有某种形式的访问权限控制。

1.包访问权限
默认访问权限没有任何关键字,但通常是指包访问权限(有时也表示成friendly)。这就意味着当前包中的所有其他类对那个成员都有访问权限,但对于这个包之外的所有类,这个成员确实private。由于一个编译单元,只能隶属一个包,所以经由包访问权限,处于同一编译单元中的所有类彼此之间都是自动可访问的。
包访问权限允许将包内所有相关的类组合起来,以使它们之间可以轻松地相互作用。当把类组织起来放进一个包内之时,也就给它们的包访问权限的成员赋予了相互访问的权限,你“拥有”了该包内的程序代码。“只有你拥有的程序代码才可以访问你所拥有的其他程序代码”这是合理的。应该说,包访问权限为了把类群聚在一个包中的做法提供了意义和理由。
类控制着哪些代码有权访问自己的成员。其他包的类不能刚一上来就说:“嗨,我是Bob的朋友”,并且还想看到Bob的protected,包访问权限和private成员。取得对某成员的访问权的唯一途径是:

1.使该成员成为public。于是无论是谁,无论在哪里,都可以访问该成员。
2.通过不加访问权限修饰词并将其他类放置于同一包内的方式给成员赋予包访问权。于是包内的其他类也就可以访问该成员了。
3.继承而来的类既可以访问public成员也可以访问protect成员。只有在两个类都处于同一个包内时,它才可以访问包访问权限的成员

2.public:接口访问权限
使用关键字public,就意味着public之后紧跟着的成员声明自己对每个人都是可用的,假设定义了一个包含下面编译单元的dessert包:

package access.dessert:

public class Cookie{
	public Cookie(){
		System.out.println("Cookie constructor");
	}
	void bite(){
		System.out.println("bite");
	}
}

记住,Cookie.java文件必须位于名为dessert的子目录中,该子目录在access下,
先如果创建了一个使用Cookie的程序:

import access.dessert.*;
public class Dinner{
	public static void main(String[] args){
		Cookie x=new Cookie();
	}
}

就可以创建创建一个Cookie对象,因为它的构造器是public而且类也是public。但是,由于bite()只向dessert包中的类提供访问权,所以bite()成员在Dinner.java之中时无法访问的,因此编译器也禁止你使用它

默认包

class Cake{
	public static void main(String[] args){
		Pie x=new Pie();
		x.f();
	}
}/*Output:
pie.f()
*///

在第二个处于相同目录的文件中:
class Pie{
void f(){
Sysout.out.println(“Pie.f()”);
}
}

最初或许会认为这两个文件毫不相干,但Cake却可以创建一个Pie对象并调用它的f()方法!通常会认为Pie和f()享有包访问权限,因而使不可以为Cake所用的。它们的确享有包访问权限,但这只是部分正确。Cake.java可以访问它们的原因是它们同处于相同的目录并且没有给自己设定任何包名称。Java这样的文件自动看作是隶属于该目录的默认包之中,于是它们为该目录中所有其他文件都提供了包访问权限。

3.private:你无法访问

关键字private的意思是,除了包含该成员的类之外,其他任何类都无法访问这个成员。由于处于同一包内的其他类是不可以访问private成员的,因此这等于说是自己隔离了自己。从另一方面说,让许多人共同合作来创建一个包也是不太可能的,为此private就允许你随意改变该成员,而不必考虑这样做是否会影响到包内其他的类。
默认的包访问权限通常已经提供了充足的隐藏措施,请记住,使用类的客户端程序员是无法访问包访问权限成员。这样做很好,因为默认访问权限是一种我们常用的权限,同时也是一种忘记添加任何访问权限控制时能够自动得到的权限。因此,通常考虑的是,哪些成员是想要明确公开给客户端程序员使用的,从而将它们声明为public,而在最初,你可能不会认为自己会经常需要使用关键字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确保了你拥有这种选择权。

3.protected:继承访问权限
关键字protected处理的是继承的概念,通过继承可以利用一个现有类—我们将其称为基类,然后将新成员添加到该现有类中而不必碰该现有类。还可以改变该类的现有成员的行为。为了从现有类中继承,需要声明新类extends(扩展)了一个现有类,就像这样:
class Foo extends Bar {
类定义中的其他部分看起来都是一样的。
如果创建了一个新包,并自另一个包中继承类,那么唯一可以访问的成员就是源包的public成员。有时,基类的创建者会希望某个特定成员,把对它的访问权限赋予派生类而不是所有类。这就需要protected来完成这一工作。protected也提供包访问权限,也就是说,相同包内的其他类可以访问protected元素。
回顾一下先前的例子Cookie.java 就可以得知下面的类是不可以调用拥有包访问权限的成员bite()的:

import access.dessert.*
public class ChocolateChip extends Cookie{
	public ChocolateChip(){
		System.out.println("ChocolateChip constructor");
	}
	public void chomp(){
	}
	public static void main(String[] args){
		ChocolateChip x=new ChocolateChip();
		x.chomp();
		}
	}/*Output:
	Cookie constructor
	ChocolateChip constructor
	

有关继承技术的一个很有趣的事情是,如果类Cookie中存在一个方法bite()的话,那么该方法同时也存在于任何一个从Cookie继承而来的类中。但是由于bite()有包访问权限而且它位于另一个包内,所有我们在这个包内是无法使用它的。当然,也可以把它指定为public,但是这样做所有的人就都有了访问权限,而且很可能,而且很可能这并不是你所希望的。如果我们将类Cookie像这样加以更改:

public class Cookie{
	public Cookie(){
		System.out.println("Cookie constructor");
	}
	protected void bite(){
		System.out.println("bite");
	}
}

现在bite()对于所有继承自Cookie的类而言,也是可以使用的。
im

6.4类的访问权限

在Java中,访问权限修饰词也可以用于确定库中的哪些类对于该库的使用者是可用的。如果希望某个类可以为某个客户端程序员所用,就可以通过把关键字public作用于整个类的定义来到达目的。这样做甚至可以控制客户端程序员是否能创建一个该类的对象。
为了控制某个类的访问权限,修饰词必须出现于关键字class之前。因此可以像下面这样声明:

public class Widget{

现在如果库的名字是access,那么任何客户端程序员都可以通过下面的声明访问Widget:

import access.Widget

然而,这里还有一些额外的限制:

  1. 每个编译单元(文件)都只能有一个public类。这表示,每个编译单元都有单一的公共接口,用public类来表现。该接口可以按要求包含众多的支持包访问权限的类。如果在某个编译单元内有一个以上的public类,编译器就会给出错误信息。
  2. public类的名称必须完全与含有该编译单元的文件名相匹配,包括大小写。

在创建一个包访问权限的类时,仍旧是在将该类的域声明为private时才有意义—应尽可能地总是将域指定为私有,但是通常来说,将与类相同的访问权限赋予方法也是合理的。既然一个有包访问权限的类通常只能被用于包内,那么如果对你有强制要求,在此种情况下,编译器会告诉你,你只需要将这样的类的方法设定为public就可以了。
请注意,类既不可以是private(这会使得除该类之外,其他任何类都不可以访问它),也不可以是protected的。所以对于类的访问权限,仅有两个选择:包访问权限或者public。如果不希望其他任何人对该类拥有访问权限,可以把所有的构造器都指定为private,从而组织任何人创建该类的对象,

正如前面所提到的,如果没能为类访问权限指定一个访问修饰符,它就会默认得到包访问权限。这就意味着该类的对象可以由包内任何其他类来创建,但是在包外则是不行的。然而如果该类的某个static成员public的话,则客户端程序员仍旧可以调用该static成员,尽管它们并不能生成该类的对象。

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

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

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值