标签:
修饰符private提供了最高的保护和最低的可见度:private修饰的域、方法和嵌套类型,只能在它的定义类中访问。
各类书籍中会有一些程序设计上的清规戒律,如:
★以private修饰所有的成员变量(Make all Member Variables private.)。
★以private修饰任一助手方法。(Make any helper methods private)。
这些启发式规则是一种自律条款。对于Java包的设计者而言,包级私有已经是良好的天然边界(不需要刻意遵循上述规则),在某些情况下,如[5.2.1结点]中,包级私有非常合理;但是职业程序员将要使用多种编程语言,对上述规则的遵循是应该养成的习惯,许多语言没有包级私有访问级别。
1. 访问限制适用于类层面
假定PrivateDemo声明了private i域、改写了Object.equals(Object)、定义了private方法isEqual(PrivateDemo),那么,可以在定义private域和方法的类PrivateDemo中测试如下代码:
publicstatic void test(){
PrivateDemo one = new PrivateDemo(8);
PrivateDemo two = new PrivateDemo(8);
System.out.println( one.equals(two) );
here:System.out.println( one.isEqual(two) );
here:System.out.println( one.i==two.i );
}
这段代码说明,在定义类中,对象之间彼此可以访问对方的private成员。
如果将上面的代码搬到任一其他类,编译时报错。
2. 成员变量私有化
成员变量私有化后,外界只能通过存取方法对成员变量进行访问。方便记忆起见,存取方法名通常有这样的约定:读取方法名为get+变量名,设置方法名为set+变量名。俗称getter和setter。
有一个常见的问题:既然为一个private域提供public getter/setter,为什么不直接将该域声明为public?两者有区别吗?使用x.i不比x. getI()更方便吗?
单就这种情形而言——为private域提供最简单的public getter和setter方法,的确可以直接将该域声明为public,而且x.i比x. getI()更直观。但是:
(1)为了 (与各种其他的情形) 统一,最好在Java程序中采用private域+public getter/setter的方式。某些Java开发环境能够按照类体中声明的域,自动添加getter/setter方法。
(2)如果不给private域提供setter方法,或者在setter中添加一些验证代码,例如限定ID必须属于(0,9999)、限定日期的格式必须是yyyy-mm-dd等,private域的价值就凸显出来。
相比较的,在C#语言中,专门就此提供了一种称为属性/特性的玩意,到C#3.0,可以简化成:public int ID{get ; set ;}
语意上,属性是getter/setter,编译器自动提供一个private域如_ID保持数据。
而在写法上,可以int x= ID 和ID =5,如同public域一样使用。换一个角度看,即使C#提供属性这样的语法糖,它仍然将域(隐式地)声明为private。
(3)反正private域是外界不可见的,当一个类提供了public getter/setter方法,可能该类真的有一个private域,也可能压根就没有。
例程 6?3虚拟的域
package accessModifier; public abstract class G2{ protected G2(){} public abstract void setField(int field); public abstract int getField(); }
//可以与G2组成一个源文件。非public类 class Gx extends G2{ private int field; @Override public void setField(int field){ this.field = field; } @Override public int getField(){ return field; } } //另一个.java文件 package accessModifier; public class Framework{ public static void test() { G2 g2 = newClass(); g2.setField(100); System.out.println(g2.getField() + 566); } private static G2 newClass(){return new Gx(); } } |
本例与例程6-2相似。本例主要演示一种设计技巧(模式),不妨称之为虚域模式。对于用户而言,API为抽象类G2,并提供抽象方法setter和getter方法操作其内部状态(虚域)。G2的子类不可见、内部状态不在G2中而由具体子类声明。
注意:本例中没有提供一个public的静态工厂而是一个private方法,包外如何使用Framework和G2类,留在后面解决。
练习6-18:讨论“以private修饰所有的成员变量”这一规则。提示:看见“所有的”就先打个问号。 |
练习6-19.:讨论Java中是否应该引入C#的属性/特性。 |
练习6-20:何谓虚域模式? |
1. 域的继承
[2.1.4访问修饰符与继承]中曾经阐明:子类继承其父类的所有可访问的成员。
对于类层次而言,LSP和IS_A关系关注的重点是父子类型在行为上的一致,而域都被合理地认为是private。域主要涉及对象的内存分配。为了统一起见,也为了与《Java语言规范》这一官方文档一致,因而本书认定父类的private域不被继承。
然而,这改变不了一个事实:创建的子类对象包含一个父类对象。从对象的内存分配看,子类对象既包括父类定义实例变量,也包括了自己声明的实例变量。这也是系统按照固定的顺序(从Object类开始,按继承树的顺序,直到new关键字所指定的类的构造器)自动调用构造器的原因。
不管是否赞同子类不继承父类的private变量[1],即使认为子类继承private变量,子类不能访问这个“自己的”变量。父类对象的实例变量,通常不会给子类带来额外的好处。
★数据向下集中。
2. 域的隐藏
父类中声明的若干域,不管其访问权限如何(或是否被继承),不管是静态的还是实例的,子类将隐藏父类中同名的域,而且两者的数据类型是否相同也无关紧要。
假定鸭子(Duck)和跛脚鸭(LameDuck)为父子类,父类Duck有4种访问级别*2种形式(实例或静态)的成员变量,而子类可以对父类的每一种域,以8种不同方式隐藏(数据类型不考虑)。这64种组合并不有趣,这里介绍一个著名的例子:跛脚鸭问题。
Duck有两条腿和两只脚。LameDuck只有一条腿和一只脚,它是一个鸭子,可以构造出如下继承关系。
例程 6?4域的静态绑定 Vs. 方法的动态绑定
package OO.accessModifier; public class Duck{ int feet = 2; public int legs() { return feet;}//get() }
//另一个.java文件 package OO.accessModifier; public class LameDuck extends Duck{ int feet = 1; //这里的override很特别。 @ Override public int legs() { return feet; } public static void test(){ //Duck d = new Duck(); //LameDuck d = new LameDuck(); Duck d = new LameDuck(); System.out.println( d.getClass()+"对象有 "+d.legs() +" 条腿"); System.out.println( d.getClass()+"对象有 "+d..feet +" 只脚"); } } |
这个例程主要说明域的静态绑定和方法的动态绑定之间的不协调。
test()中,(1) Duck d = new Duck(),输出Duck对象有2 条腿2 只脚;(2) LameDuck d = new LameDuck(),输出LameDuck对象有1条腿1 只脚。(3) Duck d = new LameDuck()向上造型,输出LameDuck对象有 1条腿2只脚。
出现这一问题的原因是:(1)多态变量d指向子类的对象;(2) 域是静态绑定的。如果声明类(这里是父类)的域feet正好能够访问,故d. feet为2;(3) 方法是动态绑定。如果子类改写了legs()——事实上是一个getter方法,可取名getFeet(),执行实际类的代码,返回子类的feet为1。
修复该bug的简单方式是使(父类的)成员变量私有化。从而使得静态绑定的(父类的)域不能够在子类代码中使用,即代码中的d.feet引起编译错误。如此一来,如果需要访问子类中的feet,多态变量d必须向下造型,代码为((LameDuck)d).feet。因而输出:LameDuck对象有1条腿1 只脚。
子类的改写方法legs()很奇怪,完全复制被改写的方法的实现。完全复制式的改写有意思吗?先注释掉再说。输出:LameDuck对象有 1 只脚2条腿。
从1条腿2只脚,到2条腿1只脚;从别扭的((LameDuck)d).feet和完全复制式的改写,可以看出例程6-4的设计不自然。根本原因是父子类存在同名的域feet——即使父类的域为private。
练习6-21:更一般的,在Duck中声明域name、方法getName(),如果子类LameDuck也声明一个name,猜测其设计目的是什么?编写代码时应该注意什么? |
练习6-22:跛脚问题揭示了面向对象技术中存在的哪些问题?导致跛脚问题的基本原因是什么? |
练习6-23:域的访问是否存在多态? |
练习6-24:讨论子类是否继承父类的private域。 |
版权声明:本文为博主原创文章,未经博主允许不得转载。
标签:
原文地址:http://blog.csdn.net/yqj2065/article/details/46807135