Skip to content

Commit 9cf274c

Browse files
authored
Merge pull request lingcoder#100 from xiangflight/master
[feast 08](截至 final 忠告)
2 parents 35dcbb9 + 381ac5d commit 9cf274c

File tree

1 file changed

+188
-1
lines changed

1 file changed

+188
-1
lines changed

docs/book/08-Reuse.md

Lines changed: 188 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -970,16 +970,203 @@ fd1: i4 = 15, INT_5 = 18
970970
fd2: i4 = 13, INT_5 = 18
971971
```
972972

973-
因为 **valueOne****VALUE_TWO** 都是带有编译时值的 **final** 基本类型,它们都可用作编译时常量,没有多大区别。
973+
因为 **valueOne****VALUE_TWO** 都是带有编译时值的 **final** 基本类型,它们都可用作编译时常量,没有多大区别。**VALUE_THREE** 是一种更加典型的常量定义的方式:**public** 意味着可以在包外访问,**static** 强调只有一个,**final** 说明是一个常量。
974+
975+
按照惯例,带有恒定初始值的 **final** **static** 基本变量(即编译时常量)命名全部使用大写,单词之间用下划线分隔。(源于 C 语言中定义常量的方式。)
976+
977+
我们不能因为某数据被 **final** 修饰就认为在编译时可以知道它的值。由上例中的 **i4****INT_5** 可以看出,它们在运行时才会赋值随机数。示例部分也展示了将 **final** 值定义为 **static** 和非 **static** 的区别。此区别只有当值在运行时被初始化时才会显现,因为编译器对编译时数值一视同仁。(而且编译时数值可能因优化而消失。)当运行程序时就能看到这个区别。注意到 **fd1****fd2****i4** 值不同,但 **INT_5** 的值并没有因为创建了第二个 **FinalData** 对象而改变,这是因为它是 **static** 的,在加载时已经被初始化,并不是每次创建新对象时都初始化。
978+
979+
**v1****VAL_3** 变量说明了 **final** 引用的意义。正如你在 `main()` 中所见,**v2****final** 的并不意味着你不能修改它的值。因为它是引用,所以只是说明它不能指向一个新的对象。这对于数组具有同样的意义,数组只不过是另一种引用。(我不知道有什么方法能使数组引用本身成为 **final**。)看起来,声明引用为 **final** 没有声明基本类型 **final** 有用。
980+
981+
### 空白 final
982+
983+
空白 final 指的是没有初始化值的 **final** 属性。编译器确保空白 final 在使用前必须被初始化。这样既能使一个类的每个对象的 **final** 属性值不同,也能保持它的不变性。
984+
985+
```java
986+
// reuse/BlankFinal.java
987+
// "Blank" final fields
988+
class Poppet {
989+
private int i;
990+
991+
Poppet(int ii) {
992+
i = ii;
993+
}
994+
}
995+
996+
public class BlankFinal {
997+
private final int i = 0; // Initialized final
998+
private final int j; // Blank final
999+
private final Poppet p; // Blank final reference
1000+
// Blank finals MUST be initialized in constructor
1001+
public BlankFinal() {
1002+
j = 1; // Initialize blank final
1003+
p = new Poppet(1); // Init blank final reference
1004+
}
1005+
1006+
public BlankFinal(int x) {
1007+
j = x; // Initialize blank final
1008+
p = new Poppet(x); // Init blank final reference
1009+
}
1010+
1011+
public static void main(String[] args) {
1012+
new BlankFinal();
1013+
new BlankFinal(47);
1014+
}
1015+
}
1016+
```
1017+
1018+
你必须在定义时或在每个构造器中执行 final 变量的赋值操作。这保证了 final 属性在使用前已经被初始化过。
1019+
1020+
### final 参数
1021+
1022+
在参数列表中,将参数声明为 final 意味着在方法中不能改变参数指向的对象或基本变量:
1023+
1024+
```java
1025+
// reuse/FinalArguments.java
1026+
// Using "final" with method arguments
1027+
class Gizmo {
1028+
public void spin() {
1029+
1030+
}
1031+
}
1032+
1033+
public class FinalArguments {
1034+
void with(final Gizmo g) {
1035+
//-g = new Gizmo(); // Illegal -- g is final
1036+
}
1037+
1038+
void without(Gizmo g) {
1039+
g = new Gizmo(); // OK -- g is not final
1040+
g.spin();
1041+
}
1042+
1043+
//void f(final int i) { i++; } // Can't change
1044+
// You can only read from a final primitive
1045+
int g(final int i) {
1046+
return i + 1;
1047+
}
1048+
1049+
public static void main(String[] args) {
1050+
FinalArguments bf = new FinalArguments();
1051+
bf.without(null);
1052+
bf.with(null);
1053+
}
1054+
}
1055+
```
1056+
1057+
方法 `f()``g()` 展示了 **final** 基本类型参数的使用情况。你只能读取而不能修改参数。这个特性主要用于传递数据给匿名内部类。这将在”内部类“章节中详解。
9741058

9751059
### final 方法
9761060

1061+
使用 **final** 方法的原因有两个。第一个原因是给方法上锁,防止子类通过覆写改变方法的行为。这是出于继承的考虑,确保方法的行为不会因继承而改变。
1062+
1063+
过去建议使用 **final** 方法的第二个原因是效率。在早期的 Java 实现中,如果将一个方法指明为 **final**,就是同意编译器把对该方法的调用转化为内嵌调用。当编译器遇到 **final** 方法的调用时,就会很小心地跳过普通的插入代码以执行方法的调用机制(将参数压栈,跳至方法代码处执行,然后跳回并清理栈中的参数,最终处理返回值),而用方法体内实际代码的副本替代方法调用。这消除了方法调用的开销。但是如果一个方法很大代码膨胀,你也许就看不到内嵌带来的性能提升,因为内嵌调用带来的性能提高被花费在方法里的时间抵消了。
1064+
1065+
在最近的 Java 版本中,虚拟机可以探测到这些情况(尤其是 *hotspot* 技术),并优化去掉这些效率反而降低的内嵌调用方法。有很长一段时间,使用 **final** 来提高效率都被阻止。你应该让编译器和 JVM 处理效率问题,只有在防止方法ß覆写时才使用 **final**
9771066

1067+
### final 和 private
1068+
1069+
类中所有的 **private** 方法都隐式地指定为 **final**。因为不能访问 **private** 方法,所以不能覆写它。可以给 **private** 方法添加 **final** 修饰,但是并不能给方法带来额外的含义。
1070+
1071+
以下情况会令人困惑,当你试图覆写一个 **private** 方法(隐式是 **final** 的)时,看上去奏效,而且编译器不会给出错误信息:
1072+
1073+
```java
1074+
// reuse/FinalOverridingIllusion.java
1075+
// It only looks like you can override
1076+
// a private or private final method
1077+
class WithFinals {
1078+
// Identical to "private" alone:
1079+
private final void f() {
1080+
System.out.println("WithFinals.f()");
1081+
}
1082+
// Also automatically "final":
1083+
private void g() {
1084+
System.out.println("WithFinals.g()");
1085+
}
1086+
}
1087+
1088+
class OverridingPrivate extends WithFinals {
1089+
private final void f() {
1090+
System.out.println("OverridingPrivate.f()");
1091+
}
1092+
1093+
private void g() {
1094+
System.out.println("OverridingPrivate.g()");
1095+
}
1096+
}
1097+
1098+
class OverridingPrivate2 extends OverridingPrivate {
1099+
public final void f() {
1100+
System.out.println("OverridingPrivate2.f()");
1101+
}
1102+
1103+
public void g() {
1104+
System.out.println("OverridingPrivate2.g()");
1105+
}
1106+
}
1107+
1108+
public class FinalOverridingIllusion {
1109+
public static void main(String[] args) {
1110+
OverridingPrivate2 op2 = new OverridingPrivate2();
1111+
op2.f();
1112+
op2.g();
1113+
// You can upcast:
1114+
OverridingPrivate op = op2;
1115+
// But you can't call the methods:
1116+
//- op.f();
1117+
//- op.g();
1118+
// Same here:
1119+
WithFinals wf = op2;
1120+
//- wf.f();
1121+
//- wf.g();
1122+
}
1123+
}
1124+
```
1125+
1126+
输出:
1127+
1128+
```
1129+
OverridingPrivate2.f()
1130+
OverridingPrivate2.g()
1131+
```
1132+
1133+
"覆写"只发生在方法是基类的接口时。也就是说,必须能将一个对象向上转型为基类并调用相同的方法(这一点在下一章阐明)。如果一个方法是 **private** 的,它就不是基类接口的一部分。它只是隐藏在类内部的代码,且恰好有相同的命名而已。但是如果你在派生类中以相同的命名创建了 **public****protected** 或包访问权限的方法,这些方法与基类中的方法没有联系,你没有覆写方法,只是在创建新的方法而已。由于 **private** 方法无法触及且能有效隐藏,除了把它看作类中的一部分,其他任何事物都不需要考虑到它。
9781134

9791135
### final 类
9801136

1137+
当说一个类是 **final****final** 关键字在类定义之前),就意味着它不能被继承。之所以这么做,是因为类的设计就是永远不需要改动,或者是出于安全考虑不希望它有子类。
1138+
1139+
```java
1140+
// reuse/Jurassic.java
1141+
// Making an entire class final
1142+
class SmallBrain {}
1143+
1144+
final class Dinosaur {
1145+
int i = 7;
1146+
int j = 1;
1147+
SmallBrain x = new SmallBrain();
1148+
1149+
void f() {}
1150+
}
1151+
1152+
//- class Further extends Dinosaur {}
1153+
// error: Cannot extend final class 'Dinosaur'
1154+
public class Jurassic {
1155+
public static void main(String[] args) {
1156+
Dinosaur n = new Dinosaur();
1157+
n.f();
1158+
n.i = 40;
1159+
n.j++;
1160+
}
1161+
}
1162+
```
1163+
1164+
**final** 类的属性可以根据个人选择是或不是 **final**。这同样适用于不管类是否是 **final** 的内部 **final** 属性。然而,由于 **final** 类禁止继承,类中所有的方法都被隐式地指定为 **final**,所以没有办法覆写它们。你可以在 final 类中的方法加上 **final** 修饰符,但不会增加任何意义。
1165+
1166+
### final 忠告
9811167

9821168
<!-- Initialization and Class Loading -->
1169+
9831170
## 类初始化和加载
9841171

9851172
<!-- Summary -->

0 commit comments

Comments
 (0)