Java 學習記錄60 — final

張小雄
4 min readMay 1, 2021

今天要學的是 final

SomeClass.java

public class SomeClass {
private static int classCounter = 0;
public final int instanceNumber;
private final String name;
public SomeClass(String name) {
this.name = name;
classCounter++;
instanceNumber = classCounter;
System.out.println(name + " created, instance is " + instanceNumber);
}
public int getInstanceNumber() {
return instanceNumber;
}
}

MainSomeClass.java

public class MainSomeClass {
public static void main(String[] args) {
SomeClass one = new SomeClass("one");
SomeClass two = new SomeClass("two");
SomeClass three = new SomeClass("three");
System.out.println(one.getInstanceNumber());
System.out.println(two.getInstanceNumber());
System.out.println(three.getInstanceNumber());
}
}

輸出結果:

one created, instance is 1

two created, instance is 2

three created, instance is 3

1

2

3

classCounter 是 static 不會隨著 instance 做變化

instanceNumber 是 final 從第一次賦值後,就不能再改變

MainSomeClass.java 改

public class MainSomeClass {
public static void main(String[] args) {
SomeClass one = new SomeClass("one");
SomeClass two = new SomeClass("two");
SomeClass three = new SomeClass("three");
System.out.println(one.getInstanceNumber());
System.out.println(two.getInstanceNumber());
System.out.println(three.getInstanceNumber());

// one.instanceNumber = 4; // Cannot assign a value to final variable 'instanceNumber'
}
}

想要重新把 one 的 instanceNumber 賦值 4,就會直接報錯

所以 final 是給那些需要各自 instance 保持各自數據,但一旦賦值又不想讓人修改,就可以考慮使用這個

例如:個人ID,個人銀行帳號等等

此外,也可以看到 constants 常常會搭配 static 使用

優點是只會有一個數據且不允許其他人修改

例如:public static final double PI = 3.14159265358979323846;

這些數據是恆定不變的,且不需要各個 instance 都存一份,所以就很合適

還有若把 class 設成 fianl,那麼其他類就無法繼承這個類,避免掉從子類去覆寫父類資料的可行性

public class MainSomeClass extends Math  
// There is no default constructor available in 'java.lang.Math'
// Cannot inherit from final 'java.lang.Math'

甚至連 new 也不行

Math math = new Math(); // 'Math()' has private access in 'java.lang.Math'

他的 instructor 已經寫作 private Math() {}

Password.java

public class Password {
private static final int key = 987654321;
private final int encryptedPassword;
public Password(int password) {
this.encryptedPassword = encryptDecrypt(password);
}
private int encryptDecrypt(int password) {
return password ^ key;
}
public void storePassword() {
System.out.println("Saving password as " + this.encryptedPassword);
}
public boolean letMeIn(int password) {
if (encryptDecrypt(password) == this.encryptedPassword) {
System.out.println("Welcome!");
return true;
} else {
System.out.println("Not Correct!");
return false;
}
}
}

MainPassword.java

class MainPassword {
public static void main(String[] args) {
int pw = 123456;
Password password = new Password(pw);
password.storePassword();
password.letMeIn(65);
password.letMeIn(-65);
password.letMeIn(6565);
password.letMeIn(0);
password.letMeIn(123456);
}
}

輸出結果:

Saving password as 987728625

Not Correct!

Not Correct!

Not Correct!

Not Correct!

Welcome!

模擬加密密碼跟儲存密碼

ExtendedPassword.java

public class ExtendedPassword extends Password{
private int decryptedPassword;
public ExtendedPassword(int password) {
super(password);
this.decryptedPassword = password;
}
@Override
public void storePassword() {
System.out.println("Saving password as " + this.decryptedPassword);
}
}

MainPassword.java 改

class MainPassword {
public static void main(String[] args) {
int pw = 123456;
Password password = new ExtendedPassword(pw); // change here

password.storePassword();
password.letMeIn(65);
password.letMeIn(-65);
password.letMeIn(6565);
password.letMeIn(0);
password.letMeIn(123456);
}
}

輸出結果:

Saving password as 123456

Not Correct!

Not Correct!

Not Correct!

Not Correct!

Welcome!

寫了一個子類繼承 來破解密碼

Saving password as 987728625 // 加密後

Saving password as 123456 // 加密前

可以看到透過繼承,把加密前的密碼給破解出來了

但這段怎麼進行的我就看不懂了

Password.java 改

public class Password {
private static final int key = 987654321;
private final int encryptedPassword;
public Password(int password) {
this.encryptedPassword = encryptDecrypt(password);
}
private int encryptDecrypt(int password) {
return password ^ key;
}
public final void storePassword() {
// only change here
System.out.println("Saving password as " + this.encryptedPassword);
}
public boolean letMeIn(int password) {
if (encryptDecrypt(password) == this.encryptedPassword) {
System.out.println("Welcome!");
return true;
} else {
System.out.println("Not Correct!");
return false;
}
}
}

ExtendedPassword.java 改

public class ExtendedPassword extends Password{
private int decryptedPassword;
public ExtendedPassword(int password) {
super(password);
this.decryptedPassword = password;
}
@Override
public void storePassword() {
// error
// 'storePassword()' cannot override 'storePassword()' in 'Password'; overridden method is final
System.out.println("Saving password as " + this.decryptedPassword);
}
}

簡單的在不想被覆寫的 Method 前面加個 final 即可

SIBTest.java

public class SIBTest {
public static final String owner;
static {
owner = "tim";
System.out.println("SIBTest static initialization block called");
}
public SIBTest() {
System.out.println("SIB constructor called");
}
static {
System.out.println("2nd initialization block called");
}
public void someMethod() {
System.out.println("someMethod called");
}
}

MainSIBTest.java

public class MainSIBTest {
public static void main(String[] args) {
System.out.println("Main method called");
SIBTest test = new SIBTest();
test.someMethod();
System.out.println("Owner is " + SIBTest.owner);
}
}

輸出結果:

Main method called

SIBTest static initialization block called

2nd initialization block called

SIB constructor called

someMethod called

Owner is tim

此範例中看到兩處,只有標一個 static 的,那個也是 Method,且第一個執行

簡單的總結:

static fileds 跟 method 在 class 創建時就一起被創建了

且因為一開始就創建,自然不能在該區塊中,使用那些後創建的資料,如 instance of class 的 object,因為他們此時根本還沒被創建

Ivana 補充

constructor is always related to an instance, it is a way of initializing variables when creating an instance with “new” keyword, it has nothing to do with static context.

when class is loaded, everything marked as “static” will be created. But at this point, you still don’t have an instance (main method isn’t called yet). So, you cannot initialize instance variables (fields for objects) inside static context (static initializer), because every object had it’s own values of instance variables, and you just don’t have any object.

On the other hand, you can initialize static variables from instance context (ex. constructor or initalization block), because by the time you get to object creation, those static variables already exist, and you can access them and set the value to be some other than default value or previously assigned value.

Third point, for final variables (static or instance) JVM doesn’t provide default value, which means you have to initialize them. Back to what I previously said, you can’t do that inside a constructor or instance initialization block, because they (static variables) are created during class loading phase, and god knows when you will instantiate your class, so they have to be instantiated right there and then. This leaves only declaration statement or static initalizer to initialize static final variables.

--

--