Java类的复用、初始化及多态性解析
立即解锁
发布时间: 2025-08-20 00:12:45 阅读量: 1 订阅数: 14 


Java编程思想:面向对象编程的精髓与实践
### Java类的复用、初始化及多态性解析
#### 1. final关键字的使用
在Java编程中,`final`关键字有着重要的作用,它可用于修饰变量、方法和类。
##### 1.1 final参数
可以将`null`引用赋值给`final`参数,编译器不会捕获此类操作,这和非`final`参数的情况相同。当基本类型的参数被声明为`final`时,只能读取该参数,无法对其进行修改。
##### 1.2 final方法
使用`final`方法主要有两个原因:
- **防止方法被重写**:确保方法在继承过程中的行为不变,避免子类改变其含义。
- **提高效率**:编译器可以将`final`方法的调用转换为内联调用,从而消除方法调用的开销。不过,如果方法体较大,内联可能会导致代码膨胀,不一定能带来性能提升。
以下是一个示例代码,展示了`final`方法的使用:
```java
class WithFinals {
// 等同于 "private"
private final void f() {
System.out.println("WithFinals.f()");
}
// 自动为 "final"
private void g() {
System.out.println("WithFinals.g()");
}
}
class OverridingPrivate extends WithFinals {
private final void f() {
System.out.println("OverridingPrivate.f()");
}
private void g() {
System.out.println("OverridingPrivate.g()");
}
}
class OverridingPrivate2 extends OverridingPrivate {
public final void f() {
System.out.println("OverridingPrivate2.f()");
}
public void g() {
System.out.println("OverridingPrivate2.g()");
}
}
public class FinalOverridingIllusion {
public static void main(String[] args) {
OverridingPrivate2 op2 = new OverridingPrivate2();
op2.f();
op2.g();
// 可以向上转型
OverridingPrivate op = op2;
// 但不能调用方法
//! op.f();
//! op.g();
// 同样的情况
WithFinals wf = op2;
//! wf.f();
//! wf.g();
}
}
```
在上述代码中,虽然看起来子类重写了父类的私有方法,但实际上并没有真正重写,只是创建了新的方法。因为私有方法不属于基类接口,不可达且不可见,不会影响到其他类。
##### 1.3 final类
当一个类被声明为`final`时,表示不允许其他类继承该类。这可能是出于设计原因,确保类的设计无需更改,或者是为了安全和效率考虑。
以下是一个`final`类的示例:
```java
class SmallBrain {}
final class Dinosaur {
int i = 7;
int j = 1;
SmallBrain x = new SmallBrain();
void f() {}
}
// 错误: 无法继承 final 类 'Dinosaur'
//! class Further extends Dinosaur {}
public class Jurassic {
public static void main(String[] args) {
Dinosaur n = new Dinosaur();
n.f();
n.i = 40;
n.j++;
}
}
```
需要注意的是,`final`类的数据成员可以是`final`或非`final`的,类被声明为`final`仅防止继承,其中的方法会隐式地成为`final`方法。
#### 2. 类的初始化和加载
在传统语言中,程序启动时会一次性加载并初始化所有内容,需要小心控制静态变量的初始化顺序,否则可能会出现问题。而Java采用了不同的加载方式,每个类的编译代码存在于单独的文件中,只有在需要时才会加载。
一般来说,“类代码在首次使用时加载”,这通常发生在创建该类的第一个对象或访问其静态字段或方法时。静态初始化也在首次使用时进行,所有静态对象和静态代码块将按照类定义中的文本顺序进行初始化,且静态变量只初始化一次。
以下是一个包含继承的初始化示例:
```java
class Insect {
int i = 9;
int j;
Insect() {
prt("i = " + i + ", j = " + j);
j = 39;
}
static int x1 = prt("static Insect.x1 initialized");
static int prt(String s) {
System.out.println(s);
return 47;
}
}
public class Beetle extends Insect {
int k = prt("Beetle.k initialized");
Beetle() {
prt("k = " + k);
prt("j = " + j);
}
static int x2 = prt("static Beetle.x2 initialized");
public static void main(String[] args) {
prt("Beetle constructor");
Beetle b = new Beetle();
}
}
```
该程序的输出如下:
```
static Insect.x1 initialized
static Beetle.x2 initialized
Beetle constructor
i = 9, j = 0
Beetle.k initialized
k = 47
j = 39
```
程序执行时,首先尝试访问`Beetle.main()`方法,加载`Beetle`类的编译代码,发现其有基类`Insect`,则加载`Insect`类。接着进行根基类的静态初始化,再依次进行派生类的静态初始化。之后创建对象,将对象的基本类型设置为默认值,对象引用设置为`null`,调用基类构造函数,再按文本顺序初始化实例变量,最后执行构造函数的其余部分。
#### 3. 继承和组合的使用
继承和组合都可以从现有类型创建新类型。通常,组合用于将现有类型作为新类型的底层实现的一部分,而继承用于复用接口。由于派生类具有基类接口,因此可以将其向上转型为基类,这对于多态性至关重要。
在设计时,建议优先考虑使用组合,只有在必要时才使用继承。组合更加灵活,通过在成员类型中使用继承技巧,可以在运行时改变成员对象的类型和行为,从而改变组合对象的行为。
以下是一个简单的流程图,展示了类的加载和初始化过程:
```mermaid
graph TD;
A[开始] --> B[访问静态方法或创建对象];
B --> C[加载类文件];
C --> D{是否有基类};
D -- 是 --> E[加载基类];
E --> F[基类静态初始化];
F --> G[派生类静态初始化];
D -- 否 --> G;
G --> H[创建对象];
H --> I[设置默认值];
I --> J[调用基类构造函数];
J --> K[初始化实例变量];
K --> L[执行构造函数其余部分];
L --> M[结束];
```
#### 4. 多态性简介
多态性是面向对象编程语言的第三个基本特征,它提供了接口与实现分离的另一个维度,有助于提高代码的组织性和可读性,创建可扩展的程序。
多态性允许将多个派生自同一基类的类型视为一种类型,使一段代码可以平等地处理这些不同类型。通过基类调用的多态方法可以表达不同类型之间的区别。
以下是一个简单的多态性示例:
```java
class Note {
private int value;
private Note(int val) { value = val; }
public static final Note
MIDDLE_C = new Note(0),
C_SHARP = new Note(1),
B_FLAT = new Note(2);
}
class Instrument {
public void play(Note n) {
System.out.println("Instrument.play()");
}
}
class Wind extends Instrument {
public void play(Note n) {
System.out.println("Wind.play()");
}
}
public class Music {
public static void tune(Instrument i) {
i.play(Note.MIDDLE_C);
}
public static void main(String[] args) {
Wind flute = new Wind();
tune(flute); // 向上转型
}
}
```
在上述代码中,`tune`方法接受一个`Instrument`类型的参数,但可以传入`Wind`类型的对象,这就是向上转型的体现。调用`play`方法时,会根据对象的实际类型调用相应的方法,实现了多态性。
#### 5. 总结与建议
在Java编程中,合理使用`final`关键字、理解类的初始化和加载过程以及掌握多态性的概念对于编写高效、可维护的代码至关重要。以下是一些总结和建议:
- **final关键字**:谨慎使用`final`方法和类,避免过度限制类的复用性。只有在确定方法不需要重写或类不需要继承时才使用`final`。
- **类的初始化和加载**:了解类的加载和初始化顺序,确保静态变量和实例变量的正确初始化,避免因初始化顺序不当导致的问题。
- **继承和组合**:优先使用组合,因为它更加灵活。只有在明确需要复用接口时才使用继承。
- **多态性**:利用多态性提高代码的可扩展性和可维护性,使代码能够更好地适应变化。
通过不断实践和学习,深入理解这些概念和技术,将有助于提升Java编程能力。
#### 6. 练习题
为了巩固所学知识,以下是一些练习题:
1. 创建两个类`A`和`B`,具有默认构造函数,再从`A`继承一个新类`C`,并在`C`中创建`B`的成员,不创建`C`的构造函数,创建`C`的对象并观察结果。
2. 修改练习1,使`A`和`B`具有带参数的构造函数,为`C`编写构造函数并在其中完成所有初始化。
3. 创建一个简单类,在另一个类中定义该类的对象字段,使用懒初始化来实例化该对象。
4. 从`Detergent`类继承一个新类,重写`scrub()`方法并添加一个新方法`sterilize()`。
5. 注释掉`Cartoon.java`中`Cartoon`类的构造函数,解释发生了什么。
6. 注释掉`Chess.java`中`Chess`类的构造函数,解释发生了什么。
7. 证明编译器会为你创建默认构造函数。
8. 证明基类构造函数(a)总是被调用,(b)在派生类构造函数之前被调用。
9. 创建一个只有非默认构造函数的基类和一个具有默认和非默认构造函数的派生类,在派生类构造函数中调用基类构造函数。
10. 创建一个名为`Root`的类,包含`Component1`、`Component2`和`Component3`的实例,从`Root`派生一个`Stem`类,也包含这些组件的实例,所有类都有默认构造函数并打印消息。
11. 修改练习10,使每个类只有非默认构造函数。
12. 为练习11中的所有类添加适当的`cleanup()`方法层次结构。
13. 创建一个具有三次重载方法的类,继承一个新类并添加新的方法重载,证明派生类中可以使用所有四个方法。
14. 在`Car.java`中为`Engine`添加`service()`方法,并在`main()`中调用该方法。
15. 在包中创建一个类,该类包含一个受保护的方法,在包外尝试调用该方法并解释结果,然后从该类继承并在派生类的方法中调用受保护的方法。
16. 创建一个名为`Amphibian`的类,从中继承一个`Frog`类,在基类中添加适当的方法,在`main()`中创建`Frog`对象并将其向上转型为`Amphibian`,证明所有方法仍然有效。
17. 修改练习16,使`Frog`重写基类的方法定义,注意`main()`中发生了什么。
18. 创建一个具有静态`final`字段和`final`字段的类,演示两者之间的区别。
19. 创建一个具有空白`final`对象引用的类,在使用前在方法(非构造函数)中初始化该空白`final`,证明`final`必须在使用前初始化且初始化后不能更改。
20. 创建一个具有`final`方法的类,从该类继承并尝试重写该方法。
21. 创建一个`final`类并尝试从它继承。
22. 证明类加载只发生一次,证明加载可能由创建该类的第一个实例或访问静态成员引起。
2
0
0
复制全文
相关推荐










