第6章 访问权限控制
6.4 类的访问权限
Java的访问权限控制修饰符,从最大权限到最小权限依次是:public、protected、包访问权限(默认,没有关键字)和private。对于类的访问权限只能是:public和包访问权限(但内部类可以是private或protected的);对于类中的成员的访问权限可以是上述的四种。下面是各种访问权限的作用。public修饰类
作用域 | 当前类 | 同一package普通类 | 其他package普通类 | 同一package子类 | 其他package子类 |
public | √ | √ | √ | √ | √ |
protected | √ | √ | × | √ | √ |
默认 | √ | √ | × | × | × |
private | √ | × | × | × | × |
作用域 | 当前类 | 同一package普通类 | 其他package普通类 | 同一package子类 | 其他package子类 |
public | √ | √ | × | √ | × |
protected | √ | √ | × | √ | × |
默认 | √ | √ | × | √ | × |
private | √ | × | × | × | × |
第7章 复用类
7.2.1 初始化基类
class Game {
Game(int i) {
System.out.println("Game constructor");
}
}
class BoardGame extends Game{
BoardGame(int i) {
super(i);
// TODO Auto-generated constructor stub
System.out.println("BoardGame constructor");
}
}
public class Chess extends BoardGame {
Chess() {
super(11);
// TODO Auto-generated constructor stub
System.out.println("Chess constructor");
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Chess x = new Chess();
}
}
如果不在BoardGame中调用基类构造器,编译器将“抱怨”无法找到符合Game形式的构造器。导出类构造器中要调用基类构造器。
7.3 代理
class SpaceShipControls{
void up(int velocity){}
void down(int velocity){}
void left(int velocity){}
void right(int velocity){}
void forward(int velocity){}
void back(int velocity){}
void turboBoost(){}
}
class SpaceShip extends SpaceShipControls{
private String name;
public SpaceShip(String name){
this.name = name;
}
public String toString(){
return name;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
SpaceShip protector = new SpaceShip("NESA Protector");
protector.forward(100);
}
}
public class SpaceShipDelegation {
private String name;
private SpaceShipControls controls = new SpaceShipControls();
public SpaceShipDelegation(String name){
this.name = name;
}
void up(int velocity){
controls.up(velocity);
}
void down(int velocity){
controls.down(velocity);
}
void left(int velocity){
controls.left(velocity);
}
void right(int velocity){
controls.right(velocity);
}
void forward(int velocity){
controls.forward(velocity);
}
void back(int velocity){
controls.back(velocity);
}
void turboBoost(int velocity){
controls.turboBoost();
}
public static void main(String[] args) {
SpaceShipDelegation protector = new SpaceShipDelegation("NESA Protector");
protector.forward(100);
}
}
使用代理可以拥有更多的控制力,因为可以选择只提供在成员对象中的方法的某个子集。7.4.1 确保正确清理
class Shape{
Shape(int i){
System.out.println("Shape constructor");
}
void dispose(){
System.out.println("Shape dispose");
}
}
class Circle extends Shape{
Circle(int i){
super(i);
System.out.println("Drawing Circle");
}
void dispose(){
System.out.println("Erasing Circle");
super.dispose();
}
}
class Line extends Shape{
private int start, end;
Line(int start, int end) {
super(start);
this.start = start;
this.end = end;
System.out.println("Line: " + start + ", " + end);
}
void dispose(){
System.out.println("Erasing Line: " + start + ", " + end);
super.dispose();
}
}
public class CADSystem extends Shape {
private Circle c;
private Line[] lines = new Line[3];
CADSystem(int i) {
super(i+1);
for(int j = 0; j < lines.length; ++j){
lines[j] = new Line(j, j +1);
}
c = new Circle(1);
System.out.println("CADSystem constructor");
}
public void dispose(){
c.dispose();
for(int j = 0; j < lines.length; ++j){
lines[j].dispose();
}
super.dispose();
}
public static void main(String[] args) {
CADSystem s = new CADSystem(47);
try{
//code ...
}finally{
s.dispose();
}
}
}
在清理方法(dispose)中,还必须注意对基类清理方法和成员对象清理方法的调用顺序,以防某个子对象依赖于另一个子对象情形的发生。垃圾回收器可能永远也无法被调用,即使被调用,它也极可能以任何它想要的顺序来收回对象,最好的办法是除了内存之外,不能依赖垃圾回收器去做任何事。如果需要进行清理,最好是编写自己的清理方法,但不要使用finalize()。7.4.2 名称屏蔽
如果java的基类拥有某个已经被多次重载的方法名称,那么在子类中重新定义该方法名称并不会屏蔽其在基类中的任何版本。因此,无论在该层或者它的基类中对方法进行定义,重载机制都可以正常工作:
class Homer{
char doH(char c){
System.out.println("doH(char)");
return 'd';
}
float doH(float f){
System.out.println("doH(float)");
return 1.0f;
}
}
class Milhouse{ }
class Bar extends Homer{
void doH(Milhouse m){
System.out.println("doH(Milhouse)");
}
}
public class Hide {
public static void main(String[] args) {
Bar b = new Bar();
b.doH(1);
b.doH('x');
b.doH(1.0f);
b.doH(new Milhouse());
}
}
结果为:
doH(float)
doH(char)
doH(float)
doH(Milhouse)
Java SE5新增了@Override注解,它不是关键字,但可以当关键字使用。当你想覆写某个方法时,可以选择添加这个注解,在不小心重载而并非覆写了这个方法时,编译器就会生成错误信息。class Bar extends Homer{
@Override
void doH(Milhouse m){
System.out.println("doH(Milhouse)");
}
}
会提示错误,The method doH(Milhouse) of type Bar must override or implement a supertype method
这样@Overrride注解可以防止你在不想重载时而意外地进行了重载。
7.7.2 再论组合与继承
在面向对象编程中,生成和使用程序代码最有可能采用的方法就是直接将数据和方法包装进一个类中,并使用该类的对象,也可以运用组合技术使用现有类来开发新的类,而继承不太常用。因此,虽然OOP中多次强调继承,但并不是说要多用继承,而是慎用继承。一个最清晰的判断搬到就是看是否需要新类向基类进行向上转型。如果需要,则继承是必要的,如果不需要则好好考虑是否需要继承。
7.8 final关键字
对于基本类型,final使数值恒定不变;而对于对象引用,final使引用恒定不变。一旦引用被初始化指向一个对象,就无法把它改为指向另一个对象。然而,对象本身却是可以被修改的。Java并未提供使任何对象恒定不变的途径,这一限制适用数组,它也是对象。
final参数
class Gizmo{
public void spin(){}
}
public class FinalArguments {
void with(final Gizmo g){
//g = new Gizmo(); //Illegal -- g is final
}
void without(Gizmo g){
g = new Gizmo();
g.spin();
}
//void f(final int i){ i++;} // can not change i
int g(final int i){ return i+1;}
public static void main(String[] args) {
FinalArguments fa = new FinalArguments();
fa.with(null);
fa.without(null);
}
}
方法f()和g()展示了当基本类型的参数被指明为final时所出现的结果,可以读参数但是不能修改。这一特性主要用来向匿名内部类传递参数。7.8.2 final方法
使用final方法的两个原因:1.把方法锁定,以防任何继承类修改它的含义2.确保在继承中使用方法行为保持不变,并且不会被覆盖。
在想要明确禁止覆盖时,才将方法设置为final的。
final类
将类整体设为final,表明不打算继承该类并且不允许别人这样做。final类禁止继承,所以类中的所有方法汇都隐式指定为final,因为无法覆盖他们,所以在final类中给方法加final修饰词是毫无意义的。
第八章 多态
8.2.1 方法调用绑定
绑定:
一个方法的调用与方法所在的类(方法主体)关联起来
前期绑定:
在程序执行之前方法已被绑定,此时由编译器或其他连接程序实现,例如:C。
后期绑定(动态绑定/运行时绑定):
在运行时根据对象的类型进行绑定
在java中,几乎所有的方法都是后期绑定的,在运行时动态绑定方法属于子类还是基类。但是也有特殊,针对static方法和final方法由于不能被继承,因此在编译时就可以确定他们的值,他们是属于前期绑定的。特别说明的一点是,private声明的方法和成员变量不能被子类继承,所有的private方法都被隐式的指定为final的(由此我们也可以知道:将方法声明为final类型的一是为了防止方法被覆盖,二是为了有效的关闭java中的动态绑定)。java中的后期绑定是有JVM来实现的,我们不用去显式的声明它,而C++则不同,必须明确的声明某个方法具备后期绑定。
8.2.2 产生正确的行为
import java.util.Random;
class Shape{
public void draw(){}
public void erase(){}
}
class Circle extends Shape{
public void draw(){
System.out.println("draw circle");
}
public void erase(){
System.out.println("erase circle");
}
}
class Square extends Shape{
public void draw(){
System.out.println("draw square");
}
public void erase(){
System.out.println("erase square");
}
}
class Triangle extends Shape{
public void draw(){
System.out.println("draw triangle");
}
public void erase(){
System.out.println("erase triangle");
}
}
class RandomShapeGenerator{
private Random random = new Random(47);
public Shape next(){
switch(random.nextInt(3)){
default:
case 0: return new Circle();
case 1: return new Square();
case 2: return new Triangle();
}
}
}
public class Shapes {
private static RandomShapeGenerator gen = new RandomShapeGenerator();
public static void main(String[] args) {
Shape[] s = new Shape[9];
for(int i = 0; i < s.length; ++i){
s[i] = gen.next();
}
for(Shape shape:s){
shape.draw();
}
}
}
结果是:
draw triangle
draw triangle
draw square
draw triangle
draw square
draw triangle
draw square
draw triangle
draw circle
8.2.4 缺陷:“覆盖”私有方法
普通方法才有多态,private方法由于被屏蔽,被默认为final方法。
域访问变量(getXXX)方法在编译期间被解析
8.2.5 缺陷:域与静态方法
class Super{
public int field = 0;
public int getField() { return field;}
}
class Sub extends Super{
public int field = 1;
public int getField() { return field;}
public int getSuperFiedld(){ return super.field;}
}
public class FieldAccess {
public static void main(String[] args){
Super sup = new Sub();
System.out.println("sup.field = " + sup.field + ",sup.getField = " +
sup.getField());
Sub sub = new Sub();
System.out.println("sub.field = " + sub.field + ",sub.getFiled = "
+ sub.getField() + ",sub.getSuperField = " + sub.getSuperFiedld());
}
}
结果是:sup.field = 0,sup.getField = 1
sub.field = 1,sub.getFiled = 1,sub.getSuperField = 0
当Sub对象转型为Super引用时,任何域访问操作都将由编译器解析,因此不是多态的。上述例子中,为Super.field和Sub.field分配了不同的存储空间,这样Sub包含了2个称为field的域,它自己的和从Super得到的。因此在引用Sub中的field时,为默认自己的field,要用Super的field,须明确指明super.field。
class StaticSuper{
public static String staticGet(){
return "Base staticGet";
}
public String dynamicGet(){
return "Base dynamicGet";
}
}
class StaticSub extends StaticSuper{
public static String staticGet(){
return "Derived staticGet";
}
public String dynamicGet(){
return "Derived dynamicGet";
}
}
public class StaticPolymorphism {
public static void main(String[] args){
StaticSuper sup = new StaticSub();
System.out.println(sup.staticGet());
System.out.println(sup.dynamicGet());
}
}
结果是:
Base staticGet
Derived dynamicGet
static方法是与类,而并非与单个的对象相关联的。8.3 构造器和多态
8.3.1 构造器的调用顺序
1).调用基类构造器,这个步骤会不断地反复递归下去,首先是构造这种层次结构的根,然后是下一层子类,等等,最后是最底层的子类
2).按声明顺序调用成员的初始化发方法
3).调用导出类构造器的主体
8.3.2 继承与清理
销毁的顺序应该和初始化顺序相反。对于基类,应该是先对子类进行清理,然后是基类。这是因为子类的清理可能会调用基类中的某些方法,所以基类中的构件仍起作用而不应过早地被销毁。
8.3.3 构造器内部的多态方法的行为
初始化的实际过程:
1).在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制的0。
2).如前所述调用基类构造器。此时,调用被覆盖后的draw()方法(要在调用RoundGlyph构造器之前调用),由于步骤1的缘故,我们此时会发现radius的值为0。
3).按照声明的顺序调用成员的初始化方法。
4).调用子类的构造器主体。
class Glyph{
void draw(){
System.out.println("Glyph.draw();");
}
Glyph(){
System.out.println("Glyph() before draw");
draw();
System.out.println("Glyph() after draw");
}
}
class RoundGlyph extends Glyph{
private int radius = 1;
RoundGlyph(int r){
radius = r;
System.out.println("RoundGlyph.RoundGlyph(), radius=" + radius);
}
void draw(){
System.out.println("RoundGlyph.draw(), radius=" + radius);
}
}
public class PolyConstructor {
public static void main(String[] args){
new RoundGlyph(5);
}
}
结果:
Glyph() before draw
RoundGlyph.draw(), radius=0
Glyph() after draw
RoundGlyph.RoundGlyph(), radius=5
编写构造器时有一条有效的准则:用尽可能简单的方法是对象进入正常状态,如果可以的话,避免调用其他方法。在构造器内唯一能够安全调用的那些方法是基类中的final方法(适用于private方法)。这些方法不能被覆盖,所以也不会出现上述的问题。8.4 协变返回类型
在Java1.4及以前,子类方法如果要覆盖超类的某个方法,必须具有完全相同的方法签名,包括返回值也必须完全一样。
Java5.0放宽了这一限制,只要子类方法与超类方法具有相同的方法签名,或者子类方法的返回值是超类方法的子类型,就可以覆盖。
注意:"协变返回(covariant return)",仅在subclass(子类)的返回类型是superclass(父类)返回类型extension(继承)时才被容许。
class Grain{
public String toString(){
return "Grain";
}
}
class Wheat extends Grain{
public String toString(){
return "Wheat";
}
}
class Mill{
Grain process(){
return new Grain();
}
}
class WheatMill extends Mill{
Wheat process(){
return new Wheat();
}
}
public class CovariantReturn {
public static void main(String[] args){
Mill m = new Mill();
Grain g = m.process();
System.out.println(g);
m = new WheatMill();
g = m.process();
System.out.print(g);
}
}
8.5 用继承进行设计
用继承表达行为之间的差异,并用字段表达状态上的变化。