The final keyword
Java 中 final 关键字表示不可被改变,无外乎两个理由:定制或者效率。但这两个理由很不同,它很可能造成不当使用final关键字。
以下分别讨论三个final关键字可被使用的地方:data,methods 和 classes.
final data
许多编程语言都有一个方法来告诉编译器某块数据是“常量”。有两个理由说明一个常量非常有用:
1、它可做为一个编译时从不改变的常量。
2、它可做为一个运行时你不想改变的初始化了的值。
它在编译时可减少开销,提高效率。Java程序,这些常量必须为原始数据类型并且用final关键字声明,在定义的同时给其付值。
某个属性既是static又是final仅有一块不可改变的存储域。
When final is used with object references rather than primitives,the meaning can be confusing.With a primitive,final makes the value a constant,but with an object refernce,final makes the reference a constant.Once the reference is initialized to an object,it can never be changed to point to another object.However,the object itself can be modified;Java does not provide a way to make any arbitrary object a constant.(You can,however,write your class so that objects have the effect of being constant.)This restriction includes arrays,which are also objects.
Here's an example that demonstrates final fields.Note that by convention,fields that are both static and final(that is,compile-time constants)are capitalized and use underscores to separate words.
示例:
import java.util.*;
class Value{
int i;
public Value(int i){this.i=i;}
}
public class FinalData {
private static Random rand = new Random(47);
private String id;
public FinalData(String id){this.id = id;}
// Can be compile-time constants;
private final int valueOne = 9;
private static final int VALUE_TWO = 99;
// Typical public constant:
public static final int VALUE_THREE = 39;
// Cannot be compile-time constants:
private final int i4 = rand.nextInt(20);
static final int INT_5 = rand.nextInt(20);
private Value v1 = new Value(11);
private final Value v2 = new Value(22);
private static final Value VAL_3 = new Value(33);
//Arrays:
private final int[] a = {1,2,3,4,5,6};
public String toString(){
return id + ": "+"i4 = "+i4+", INT_5 = "+ INT_5;
}
public static void main(String[] args) {
FinalData fd1 = new FinalData("fd1");
//!fd1.valueOne++;//Error:Can't change value
fd1.v2.i++; // Object isn't constant!
fd1.v1 = new Value(9); // OK -- not final
for(int i = 0; i < fd1.a.length; i++)
fd1.a[i]++; // Objects isn't constant!
//!fd1.v2 = new Value(0); //Error:Can't
//!fd1.VAL_3 = new Value(1); //change refrence
//!fd1.a = new int[3];
System.out.println(fd1);
System.out.println("Creating new FinalData");
FinalData fd2 = new FinalData("fd2");
System.out.println(fd1);
System.out.println(fd2);
}
}
输出:
fd1: i4 = 15, INT_5 = 18
Creating new FinalData
fd1: i4 = 15, INT_5 = 18
fd2: i4 = 13, INT_5 = 18
注意以上final 与 static final 在编译时后编译后的值有无变化。
Blank finals
没有初始化的final 。
示例:
class Poppet{
private int i;
Poppet(int ii){i=ii;}
}
public class BlankFinal {
private final int i = 0; //Initialized final
private final int j; //Blank final
private final Poppet p; //Blank final reference
//Blank finals MUST be initialized in the onstructor;
public BlankFinal(){
j = 1; // Initialize blank final
p = new Poppet(1); //Initialize blank final reference
}
public BlankFinal(int x){
j = x; //Initialize blank final
p = new Poppet(x); //Initialize blank final reference
}
public static void main(String[] args) {
new BlankFinal();
new BlankFinal(47);
}
}
上例显示:final属性必须在使用前初始化。
final arguments
使形参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(); //OK -- g not final
g.spin();
}
// void f(final int i){i++;} //Can't change
// You can only read from a final primitive;
int g(final int i){return i + 1;}
public static void main(String[] args) {
FinalArguments bf = new FinalArguments();
bf.without(null);
bf.with(null);
}
}
上例显示:原始数据类型的形参可以读取,但不可改变。
final methods
给方法加载final有两个原因:其一是给方法加锁,防止任何继承的类改变其本意(不被重写);其二是老版本Java出于提高效率的考虑。如今已不提倡这样做了。
final and private
类中任何私有的方法明显是final型的,(类外)你不能存取一个私有的方法,你不能重写它。你可以给其加一个final标识符,但它不能给那个方法带来更多的意义。
这个问题也会引起迷惑不解,因为你如果试着重写一个私有的方法,编译器不会给出一个出错信息:
class WithFinals{
//Identical to "private" alone:
private final void f(){System.out.println("WithFinals.f()");}
//Also automatically "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();
// You can upcast:
OverridingPrivate op = op2;
//But you can't call the methods:
//!op.f();
//!op.g();
WithFinals wf = op2;
//!wf.f();
//!wf.g();
}
}
输出:
OverridingPrivate2.f()
OverridingPrivate2.g()
...If a method is private,it isn't part of the base-class interface.It is just some code that's hidden away inside the class,and it just happens to have that name,but if you create a public,protected,or package-access method with the same name in the derived class,there's no connection to the method that might happen to have that name in the base class.You haven't overridden the method;you've just created a new method.
final classes
When you say that an entire class is final(by preceding its definition with the final keyword),you state that you don't want to inherit from this class or allow anyone else to do so:
class SmallBrain{}
final class Dinosaur{
int i = 7;
int j = 1;
SmallBrain x = new SmallBrain();
void f(){}
}
//!class Further extends Dinosaur{}
//error:Cannot extend final class 'Dinosaur'
public class Jurassic {
public static void main(String[] args) {
Dinosaur n = new Dinosaur();
n.f();
n.i=40;
n.j++;
}
}
final caution
If you define a method as final.you might prevent the possibility of reusing your class through inheritance in some other programmer's pproject simply because you couldn't imagine it being used that way.
Initialization and class loading
...the compiled code for each class exists in its own separate file.That file isn't loaded until the code is needed.
...at the point of loading.The statics,of course,are initialized only once.
Initializaion with inheritance
示例:
class Insect{
private int i = 9;
protected int j;
Insect(){
System.out.println("i = " + i + ", j = " + j);
j = 39;
}
private static int x1 = printInit("static Insect.x1 initialized");
static int printInit(String s){
System.out.println(s);
return 47;
}
}
public class Beetle extends Insect{
private int k = printInit("Beetle.k initialized");
public Beetle(){
System.out.println("k = " + k);
System.out.println("j = " + j);
}
private static int x2 = printInit("static Beetle.x2 initialized");
public static void main(String[] args) {
System.out.println("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
The first thing that happens when you run Java on Beetle is that you try to acess Beetle.main()(a static method),so the loader goes out and finds the compiled code for the Beetle class(in a file called Beetle.class).In the process of loading it,the loader notices that it has a base class(that's what the extends keyword says),which it then loads.This will happen whether or not you're going to make an object of that base class.If the base class has its own base class,that second base class would then ber loaded,and so on.Next,the static initialization in the root base(in this case,Insect)is performed,and then the next derived class,and so on.This is important because the derived-class static initialization might depend on the base-class member being initialized properly.
Summary
Composition reuses existing types as part of the underlying implementation of the new type,and inheritance reuses the interface.
...when you start a design you should generally prefer composition(or possibly delegation)during the first cut and use inheritance only when it is clearly necessary.
...If your designs become too complex,it's often helpful to add more objects by breaking down existing ones into smaller parts.