文章目录
java中解决问题都是围绕类展开的。不需要重头编写代码,可以通过创建新类来复用代码。
有两种方法重用代码,不破坏以前的代码:
- 组合:在新的类中定义一个现有类的对象引用。由于新的类是由现有类的对象所组成的,所以这种方法叫做组合。
- 继承:使用现有类的类型来创建新的类,不需要改变现有类,使用现有类的形式并向其中添加新代码。这种方法叫做继承。
7.1 组合语法
只需要将对象引用加载新的类中即可。
package com.qww.test01;
public class Test01 {
public static void main(String[] args) {
SprinklerSystem system = new SprinklerSystem();
System.out.println(system);
}
}
// 运行结果
WaterSource()
SprinklerSystem{s='null', x=0, source=hello}
package com.qww.test01;
public class SprinklerSystem {
private String s;
private int x;
private WaterSource source = new WaterSource();
public String toString() {
return "SprinklerSystem{" +
"s='" + s + '\'' +
", x=" + x +
", source=" + source +
'}';
}
}
package com.qww.test01;
public class WaterSource {
private String s;
WaterSource() {
System.out.println("WaterSource()");
s = "hello";
}
public String toString() {
return s;
}
}
初始化引用的位置有:
- 定义对象的地方,这样在构造器被调用之前引用会被初始化。
- 在类构造器中
- 在正要使用对象之前,
package com.qww.test02;
public class A {
// 1. 在定义对象处,初始化引用
// private B b = new B();
// 2. 在类构造器中,初始化引用
private B b;
// 4. 使用实例初始化
{
b = new B();
}
A () {
b = new B();
}
public void f() {
// 3. 在使用对象之前,初始化引用
b = new B();
System.out.println(b);
}
}
class B {}
练习1
package com.qww.exec01;
public class Exec01 {
private A a;
{
a = new A();
}
public static void main(String[] args) {
System.out.println(new Exec01().a);
}
}
class A {}
// 运行结果
com.qww.exec01.A@1b6d3586
7.2 继承语法
Java中我们创建的每一个类都是显式地继承其他类,或者隐式地继承Java的标砖根类Object
使用extends
关键字类表示继承的关系。子类会自动获得父类中的所有域和方法。
``java
class Cleanser {
private String s = “Cleanser”;
// 对字符串 + 代表两个字符串拼接
public void append(String a) { d += a; }
public Cleanser dilute() { append(" dilute()"); return this; }
public Cleanser apply() { append(" apply()"); return this; }
public Cleanser scrub() { append(" scrub()"); return this; }
public String toString() { return s; }
public static void main(String[] args) {
Cleanser x = new Cleanser();
x.dilute().apply().scrub();
System.out.println(x);
}
}
public class Detergent extends Cleanser {
public Detergent scrub() {
append(" Detergent.scrub()");
super.scrub();
return this;
}
public Detergent foam() { append(" foam()"); return this; }
// 虽然一个文件有多个类,每个类中都有main方法,但是只有命令行调用的那个类的main()方法才会被调用。
public static void main(String[] args) {
Detergent x = new Detergent();
x.dilute().apply().scrub().foam();
System.out.println(x);
System.out.println("Testing base class: ");
Cleanser.main(args);
}
}
即使一个类只有包访问权限,它的`public static void main()`方法也仍然是可以被外部访问的。
`Cleanser`中的所有方法都必须是`public`的,如果没有加任何访问权限修饰,那么默认是包访问权限,这样只有在同一个包中的类可以访问。如果要让其他包中的子类访问成员,就需要改为`public`或者`protected`。**为了继承,父类成员的访问权限一般规则是将所有的数据成员指定为`private`,将所有的方法指定为`public`(`protected`很少用)。**
`Cleanser`接口中有一组方法:`append()`、`dilute()`、`apply()`、`scrub()`和`toString()`方法。因为`Detergent`是`Cleanser`的子类,所以接口中会自动获得这些接口方法。
在Java中使用`super`关键字表示超类。在子类中`super.scrub()`会调用基类中的`scrub()`方法、在子类中并不一定必须使用父类的方法,我们也可以在子类中添加任意新的方法。
> 覆盖重写的规则
1. 重写是子类对父类的允许访问的方法的实现过程进行的重新编写, 形参都不能改变,返回值类型可以是协变类型(Covariant return)。
2. 子类重写方法不能抛出新的检查异常或者比父类被重写方法声明更加宽泛的异常。
例如:父类方法声明一个`IOException`,子类重写方法不能抛出`Exception`,只能抛出`IOException`及其子类异常。
3. 重写方法的参数列表必须跟父类被重写方法的参数列表完全相同。
4. 重写方法的返回值类型必须跟被重写方法的返回值类型完全一样(jdk5和以前的版本);可以不一样,但是必须是父类被重写方法返回值类型的子类(jdk7+)。
5. 重写方法的访问权限不能比父类被重写方法的访问权限更低。
6. 父类的成员方法只能被它的子类重写,其他类不可以重写。
7. 父类中声明为`final`的方法不能被子类重写。
8. 父类中声明为`static`的方法不能被重写,但是可以再次声明。
9. 如果子类和父类在同一个包中,那么子类可以重写父类中声明为 默认、`protected`、`public`的非`final`或`static`的成员方法。
10. 如果子类和父类不在同一个包中,那么子类可以重写父类中声明为`protected`、`public`的非`static`或`final`的成员方法。
11. 重写方法可以抛出任何异常,但是不能抛出比父类被重写方法声明更广泛的异常。
12. 父类的构造方法不能被子类重写。
### 练习2
```java
package com.qww.exec02;
public class SubClass extends Detergent {
public SubClass scrub() {
append(" SubClass.scrub()");
return this;
}
public SubClass sterilize() {
append(" sterilize()");
return this;
}
public static void main(String[] args) {
SubClass x = new SubClass();
x.scrub().sterilize().foam();
System.out.println(x);
System.out.println("Testing Detergent base class: ");
Detergent.main(args);
}
}
/* 运行结果
Cleanser SubClass.scrub() sterilize() foam()
Testing Detergent base class:
Cleanser foam() dilute() apply() Detergent.scrub() scrub()
Testing base class:
Cleanser dilute() apply() scrub()
*/
7.2.1 初始化基类
继承不只是复制基类的接口,当创建了一个子类对象时,这个对象包含了一个基类的子对象。这个子对象和使用基类直接创建的对象是一样的。区别是基类的子对象是被包装在了子类对象的内部;而直接用基类创建的对象在外部,它是没有在子类对象的内部。
对基类的子对象正确的初始化方式:通过在子类构造器中调用基类的构造器,而基类构造器可以对基类完成基本的初始化,这样可以对基类的子对象进行初始化了。
如果我们没有在子类构造器中编写调用基类构造器的代码,那么编译器会在子类构造器中的第一行自动添加代码super()
,调用基类的无参构造器。
package com.qww.test04;
class Art {
Art() {
System.out.println("Art constructor");
}
}
class Drawing extends Art {
Drawing() {
System.out.println("Drawing constructor");
}
}
public class Cartoon extends Drawing {
private String name;
Cartoon() {
System.out.println("Cartoon constructor");
}
Cartoon(String name) {
System.out.println("Cartoon constructor " + name);
this.name = name;
}
public static void main(String[] args) {
// Cartoon cartoon1 = new Cartoon();
Cartoon cartoon2 = new Cartoon("aaa");
}
}
/* 运行结果
Art constructor
Drawing constructor
Cartoon constructor aaa
*/
子类对象的创建过程是从基类向外扩散到子类的,所以基类的构造器最先执行。在子类的访问构造器之前,基类的子对象就已经完成初始化了。
如果我们不在Cartoon
类中编写构造器,编译器会添加一个默认的构造器,在这个默认的无参构造器中调用基类的构造器,来完成基类对象的初始化。
练习3
package com.qww.exec03;
class A {
A() { System.out.println("A constructor"); }
}
public class Exec03 extends A {
public static void main(String[] args) {
Exec03 e = new Exec03();
}
}
/* 运行结果
A constructor
*/
虽然我们没有为Exec03
类编写构造器,但是代码是可以运行的,在运行时会调用基类的无参构造器。这说明在运行之前子类Exec03
是有无参构造器的,这应该是在编译过程中添加的无参构造器。经过反编译可以看到是确实有一个无参构造器。
练习4
package com.qww.exec04;
class BaseClass {
BaseClass() {
System.out.println("基类 的 无参构造器 被调用了");
}
}
public class Exec04 extends BaseClass {
private String s;
Exec04() {
System.out.println("子类 的 无参构造器 被调用了");
}
Exec04(String s) {
this.s = s;
System.out.println("子类 的 有参构造器 被调用了");
}
public static void main(String[] args) {
new Exec04();
new Exec04("hello");
}
}
/* 运行结果
基类 的 无参构造器 被调用了
子类 的 无参构造器 被调用了
基类 的 无参构造器 被调用了
子类 的 有参构造器 被调用了
*/
练习5
package com.qww.exec05;
class B {
B() { System.out.println("B constructor"); }
}
class A {
A() { System.out.println("A constructor"); }
}
class C extends A {
public B b = new B();
}
public class Exec05 {
public static void main(String[] args) {
new C();
}
}
/*运行结果
A constructor
B constructor
*/
带参数的构造器
如果基类没有默认的构造器,或者想要调用基类的带参构造器,就必须使用super
关键字,显式地调用基类的构造器。
练习6
package com.qww.exec06;
import java.util.zip.DeflaterOutputStream;
class Game {
Game(int i) {
System.out.println("Game constructor");
}
}
class BoardGame extends Game {
/*
// There is no default constructor available in 'com.qww.exec06.Game'
BoardGame(int i) {
System.out.println("BoardGame constructor");
}
*/
BoardGame(int i) {
super(i);
System.out.println("BoardGame constructor");
}
}
public class Chess extends BoardGame{
Chess() {
super(200);
System.out.println("Chess constructor");
}
public static void main(String[] args) {
new Chess();
}
}
/* 运行结果
Game constructor
BoardGame constructor
Chess constructor
*/
练习7
package com.qww.exec07;
class B {
B(int i) { System.out.println("B constructor"); }
}
class A {
A(int i) { System.out.println("A constructor"); }
}
class C extends A {
public B b = new B(100);
C() {
super(1);
}
}
public class Exec07 {
public static void main(String[] args) {
new C();
}
}
/* 云心结果
A constructor
B constructor
*/
练习8
package com.qww.exec8;
class A {
A(int i) {
System.out.println("A constructor " + i);
}
}
class B extends A {
B() {super(0);}
B(int i) {super(i);}
}
public class Exec08 {
public static void main(String[] args) {
new B();
new B(100);
}
}
/* 运行结果
A constructor 0
A constructor 100
*/
练习9
package com.qww.exec9;
public class Stem extends Root {
Stem() {
System.out.println("Stem");
}
public static void main(String[] args) {
new Stem();
}
}
/* 运行结果
Component1
Component2
Component3
Root
Stem
*/
package com.qww.exec9;
public class Root {
Component1 component1 = new Component1();
Component2 component2 = new Component2();
Component3 component3 = new Component3();
Root() {
System.out.println("Root");
}
}
package com.qww.exec9;
public class Component1 {
Component1() {
System.out.println("Component1");
}
}
package com.qww.exec9;
public class Component2 {
Component2() {
System.out.println("Component2");
}
}
package com.qww.exec9;
public class Component3 {
Component3() {
System.out.println("Component3");
}
}
练习10
package com.qww.exec10;
public class Stem extends Root {
Stem(int i) {
super(i);
System.out.println("Stem " + i);
}
public static void main(String[] args) {
new Stem(10);
}
}
/* 运行结果
Component1 1
Component2 2
Component3 3
Root 10
Stem 10
*/
package com.qww.exec10;
public class Root {
Component1 component1 = new Component1(1);
Component2 component2 = new Component2(2);
Component3 component3 = new Component3(3);
Root(int i) {
System.out.println("Root " + i);
}
}
package com.qww.exec10;
public class Component1 {
Component1(int i) {
System.out.println("Component1 " + i);
}
}
package com.qww.exec10;
public class Component2 {
Component2(int i) {
System.out.println("Component2 " + i);
}
}
package com.qww.exec10;
public class Component3 {
Component3(int i) {
System.out.println("Component3 " + i);
}
}
7.3 代理
代理是继承和组合之间的一个折中方案。
public 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() { }
}
/*
SpaceShip并不是真正的SpaceShipControls,其实SpaceShip包含SpaceShipControls
SpaceShip只是通过继承将SpaceShipControls的方法全部暴露了出来
*/
public 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) {
SpaceShip protector = new SpaceShip("NSA Protector");
protector.forword(100);
}
}
代理解决了这个问题:
可以在不改变SpaceShipControls
中的代码的前提下,对其方法进行增强。
public class SpaceShipDelegation {
private String name;
private SpaceShipControls controls = new SpaceShipControls();
public SpaceShipDelegation(String name) { this.name = name; }
public void up(int velocity) {
contrils.up(velocity);
}
public void down(int velocity) {
controls.down(velocity);
}
public void left(int velocity) {
controls.left(velocity);
}
public void right(int velocity) {
controls.right(velocity);
}
public void forward(int velocity) {
controls.forward(velocity);
}
public void back(int velocity) {
controls.back(velocity);
}
public void turboBoost() {
controls.turboBoost();
}
}
练习11
package com.qww.exec11;
public class Cleanser {
private String s = "Cleanser ";
public void append(String a) { s += a; }
public void dilute() { append("dilute() "); }
public void apply() { append("apply() "); }
public void scrub() { append("scrub() "); }
public String toString() { return s; }
public static void main(String[] args) {
Cleanser x = new Cleanser();
x.dilute();
x.apply();
x.scrub();
System.out.println(x);
}
}
package com.qww.exec11;
public class Detergent {
private Cleanser x = new Cleanser();
public void append(String a) { x.append(a); }
public void dilute() { x.dilute(); }
public void apply() { x.apply(); }
public void scrub() {
x.append("Detergent.scrub() ");
x.scrub();
}
public void foam() {
x.append("foam() ");
}
public String toString() {
return x.toString();
}
public static void main(String[] args) {
Detergent x = new Detergent();
x.dilute();
x.apply();
x.scrub();
x.foam();
System.out.println(x);
System.out.println("Testing base class: ");
Cleanser.main(args);
}
}
7.4 结合使用组合和继承
class Plate {
Plate(int i) { System.out.println("Plate constructor"); }
}
class DinnerPlate extends Plate {
DinnerPlate(int i) {
super(i);
System.out.println("DinnerPlate constructor");
}
}
class Utensil {
Utensil(int i) {
System.out.println("Utensil constructor");
}
}
class Spoon extends Utensil {
Spoon(int i) {
super(i);
System.out.println("Spoon constructor");
}
}
class Fork extends Utensil {
Fork(int i) {
super(i);
System.out.println("Fork constructor");
}
}
class Knife extends Utensil {
Knife(int i) {
super(i);
System.out.println("Knife constructor");
}
}
class Custom {
Custom(int i) {
System.out.println("Custom constructor");
}
}
public class PlaceSetting extends Custom {
private Spoon sp;
private Fork frk;
private Knife kn;
private DinnerPlate pl;
public PlaceSetting(int i) {
super(i + 1);
sp = new Spoon(i + 2);
frk = new Fork(i + 3);
kn = new Knife(i + 4);
pl = new DinnerPlate(i + 5);
}
public static void main(String[] args) {
PlaceSetting x = new PlaceSetting(9);
}
}
/* 运行结果
Custom constructor
Utensil constructor
Spoon constructor
Utensil constructor
Fork constructor
Utensil constructor
Knife constructor
Plate constructor
DinnerPlate constructor
*/
7.4.1 确保正确清理
一般是将清理动作放在finally
子句中。
和C++一样,清理动作是和调用顺序相反的。首先对子类做清理动作,依次按顺序清理基类(要求基类存活,最后被清理)。
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 Triangle extends Shape {
Triangle(int i) {
super(i);
System.out.println("Drawing Triangle");
}
void dispose() {
System.out.println("Erasing Triangle");
super.dispose();
}
}
class Line extends Shape {
private int start;
private int end;
Line(int start, int end) {
super(start);
this.start = start;
this.end = end;
System.out.println("Drawing Line: " + start + ", " + end);
}
void dispose() {
System.out.println("Erasing Line: " + start + ", " + end);
super.dispose();
}
}
public class CADSystem extends Shape {
private Circle c;
private Triangle t;
private Line[] lines = new Line[3];
public 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);
t = new Triangle(1);
System.out.println("Combined constructor");
}
public void dispose() {
System.out.println("CADSystem dispose()");
t.dispose();
c.dispose();
for(int i = lines.length - 1; i >= 0; i--)
lines[i].dispose();
super.dispose();
}
public static void main(String[] args) {
CADSystem x = new CADSystem(47);
try {
// Code and exception handing
}catch(Exception e) {
// ...
}finally {
x.dispose();
}
}
}
/* 运行结果
Shape constructor
Shape constructor
Drawing Line: 0, 1
Shape constructor
Drawing Line: 1, 2
Shape constructor
Drawing Line: 2, 3
Shape constructor
Drawing Circle
Shape constructor
Drawing Triangle
Combined constructor
CADSystem dispose()
Erasing Triangle
Shape dispose
Erasing Circle
Shape dispose
Drawing Line: 2, 3
Shape dispose
Drawing Line: 1, 2
Shape dispose
Drawing Line: 0, 1
Shape dispose
Shape dispose
*/
不要使用finalize()
方法,最好编写自己的清理方法。因为调用finalize()
方法,垃圾回收器可能永远都不可能调用finalize()
方法,即使调用它,也可能以任何顺序来回收对象。
练习12
public class Component1 {
Component1() {
System.out.println("Component1 constructor");
}
public void dispose() {
System.out.println("Component1 dispose");
}
}
public class Component2 {
Component2() {
System.out.println("Component2 constructor");
}
public void dispose() {
System.out.println("Component2 dispose");
}
}
public class Component3 {
Component3() {
System.out.println("Component3 constructor");
}
public void dispose() {
System.out.println("Component3 dispose");
}
}
public class Root {
Component1 component1 = new Component1();
Component2 component2 = new Component2();
Component3 component3 = new Component3();
Root() {
System.out.println("Root constructor");
}
public void dispose() {
System.out.println("Root dispose");
component3.dispose();
component2.dispose();
component1.dispose();
}
}
public class Stem extends Root {
Stem() {
System.out.println("Stem constructor");
}
public void dispose() {
System.out.println("Stem dispose");
super.dispose();
}
public static void main(String[] args) {
Stem stem = new Stem();
try {
// ...
}finally {
stem.dispose();
}
}
}
/* 运行结果
Component1 constructor
Component2 constructor
Component3 constructor
Root constructor
Stem constructor
Stem dispose
Root dispose
Component3 dispose
Component2 dispose
Component1 dispose
*/
7.4.2 名称屏蔽
在基类中如果有多个重载方法,在子类中重新定义该方法,不会屏蔽器在基类中的任何重载方法。(与C++不同)
class A {
void f1(int i) {}
void f1(double x) {}
}
public class B extends A {
// @Override Method does not override method from its superclass
void f1(Object obj) {}
public static void main(String[] args) {
new B().f1(new Object());
new B().f1(1);
new B().f1(1.0);
}
}
使用注解@Override
在重写覆盖时,可以检查是否重写了父类方法。
练习13
class A {
void f(int x) { System.out.println(x); }
void f(double x) { System.out.println(x); }
void f(int x1, double x2) { System.out.println(x1 + ", " + x2); }
}
public class B extends A {
void f(String s) { System.out.println(s); }
public static void main(String[] args) {
B b = new B();
b.f("asdfghjkl;");
b.f(1);
b.f(1.2);
b.f(1, 2);
}
}
/* 运行结果
asdfghjkl;
1
1.2
1, 2.0
*/
7.5 在组合与继承之间选择
首先考虑使用组合(has-a),然后再考虑使用继承(is-a)。
练习14
class Engine {
public void service() {}
}
public class Car {
public Engine engine = new Engine();
public Wheel[] wheel = new Wheel[4];
public Door left = new Door();
public Door right = new Door();
public Car() {
for (int i = 0; i < 4; i++) {
wheel[i] = new Wheel();
}
}
public static void main(String[] args) {
Car car = new Car();
car.left.window.rollup();
car.wheel[0].inflate(72);
car.engine.service();
}
}
7.6 protected关键字
protected
关键字,是基类给予不在同一个包中的子类提供访问权限的方法。·protected·可以修饰方法和字段。但是我们一般不用它修饰字段。
练习15
package com.qww.exec15;
import com.qww.exec15.test.A;
public class C extends A {
public static void main(String[] args) {
new C().f();
// 'f()' has protected access in 'com.qww.exec15.test.A'
// new A().f();
}
}
package com.qww.exec15.test;
public class A {
protected void f() {}
}
package com.qww.exec15.test;
public class A {
protected void f() {}
}
7.7 向上转型
子类可以自动向上转型为基类。
class Instrument {
public void play() {}
static void tune(Instrument i) {
// ...
i.play();
}
}
public class Wind extends Instrument {
public static void main(String[] args) {
Wind flute = new Wind();
Instrument.tune(flute);
}
}
7.7.1 为什么称为向上转型
向上转型:向上转型是从继承图从下到上移动的,即从一个具体类型向一个通用类型转换,这是安全的。
7.7.3 再论组合与继承
什么时候用组合,什么时候用继承呢?
最简单地判断方法是:先问问自己是否需要从新类向基类向上转型。如果必须向上转型,那就必须使用继承。如果不是必须的,就应该好好考虑是否需要继承。
练习16
class Amphibian {
public void f() { System.out.println("fffffff"); }
}
public class Frog extends Amphibian {
public static void main(String[] args) {
Amphibian a = new Frog();
a.f();
}
}
/* 运行结果
fffffff
*/
练习17
class Amphibian {
public void f() { System.out.println("fffffff"); }
}
public class Frog extends Amphibian {
public void f() { System.out.println("gggggggg"); }
public static void main(String[] args) {
Amphibian a = new Frog();
a.f();
}
}
/* 运行结果
gggggggg
*/
7.8 final关键字
final
可以修饰类、数据、方法。
7.9.1 final数据
对于基本类型final
会使数据恒定不变,对于对象引用类型final
会使引用很定不变,不能指向其他对象。但是对象本身是可以被修改的。static final
l的字段,用来表示编译时常量,名称一般用大写字母,单词之间用下划线隔开(和C中常量名一样)。
// 典型的常量定义方式
public static final int VAL_ONE = 1;
final
数值定义为静态和非静态的区别,只有在运行时内被初始化才会出现。因为编译器在编译时对数值一视同仁(并且他们可能会因为优化而消失)。当程序运行时,静态变量在类加载时初始化,非静态变量在创建对象时初始化。
练习18
public class Exec18 {
static final int x1 = f1();
final int x2 = f2();
private static int f1() {
System.out.println("x1=" + x1);
return 1;
}
private int f2() {
System.out.println("x1=" + x1 + ", x2=" + x2);
return 2;
}
public static void main(String[] args) {
Exec18 exec18 = new Exec18();
}
}
/* 运行结果
x1=0
x1=1, x2=0
*/
非静态的final字段不可以在静态final字段之前初始化。
非静态的final在执行构造器之前完成初始化,静态final字段在类加载时完成初始化。
空白final
在定义处没有对final字段初始化,而在每个构造器或代码块中对用表达式对其进行赋值。
练习19
class A {
int x = 1;
}
class B {
final A a;
String s;
B() { a = new A(); }
B(String s) {
this.s = s;
a = new A();
}
public void f() {
// Cannot assign a value to final variable 'a'
// a = new A();
}
}
public class Exec19 {
public static void main(String[] args) {
B b = new B();
// Cannot assign a value to final variable 'a'
// b.a = new A();
b.a.x = 10;
}
}
final参数
在方法中的参数列表中,以声明的方法将参数指明为final。那么我们就无法再方法中丢该参数引用所指向的0对象。对于基本类型的参数声明为final,在方法中只能读取参数,无法修改它。这个特性用类向匿名内部类中传递数据。
7.8.2 final方法
- 将方法锁定,防止任何继承类修改它,明确禁止子类覆盖它。处于设计考虑,想要确保继承中的使方法的行为保持不变。
- 效率。(一般不用)
final和private关键字
**类中的所有的private方法都隐式指定为final。**所以没必要去给private方法添加final关键字修饰(虽然编译器不会提示任何信息)。
练习20
class A {
void f() {}
}
public B {
@Override
void f() {}
}
练习21
class A {
final void f() {
System.out.println("aaaaa");
}
}
public class Exec21 extends A {
// f()' cannot override 'f()' in 'com.qww.exec21.A'; overridden method is final
// void f() {}
}
7.8.3 final类
在定义一个类时,出于安全考虑,不希望它有子类。可以使用final关键字修饰这个类。
final类的字段可以是final或不是final的,根据个人需求来定。但是final类中的方法都隐式地声明为了final方法,因为无法覆盖它们。
练习22
final class A { }
// Cannot inherit from final 'com.qww.exec22.A'
//public class Exec22 extends A {
// public static void main(String[] args) {
//
// }
//}
7.8.4 关于final的忠告
如果静一个方法指定为final,可能会妨碍其他程序员通过继承来复用你的类。
Vector、Hashtable是一个很典型的例子。
7.9 初始化及类的加载
类的字节码在初次使用时,才会被加载,并且只加载一次。定义为static的字段值会被加载一次。
在创建对象时,构造器是静态方法,它会使类进行加载。
7.9.1 继承与初始化
在创建子类对象时:
- 首先,编译器先加载main方法所在的类,在加载该类的基类,加载基类的基类,直到加载完根基类
- 接下来,根基类的static字段初始化,下一个子类static字段初始化,直到最底层的子类static字段初始化。
- 所有必要的类都加载完毕后,对象就可以创建了。首先对象的所有基本类型吃实话为默认值,引用类型初始化为null。
- 然后,基类的构造器执行。
- 实例变量按其次序被初始化。
练习23
class A {
static int x = f();
static int f() {
System.out.println("initial ... ");
return 100;
}
}
public class B extends A {
public static void main(String[] args) {
System.out.println(A.x);
B b1 = new B();
}
}
/*运行结果
initial ...
*/
练习24
class Insect {
private int i = 9;
protected int j;
Insect() {
System.out.println("i=" + i + "m j=" + j)
j = 39;
}
}
public class Beetle extends Insect {
}
7.10 总结
组合和继承:一般应该优先选择使用组合(或代理),只有确实必要时才使用继承,因为组合更灵活。通过对成员类型使用继承,可以在运行时改变那些成员对象的类型和行为。