閱讀源碼時常常會看到一些例如被這樣修飾的方法參數
public void print(final Object obj){
System.out.println(obj);
}
你可能會疑問這個final有什麼用,那麼請看下面代碼
食用final前
Object o = "Hello";
foo(o)
System.out.println(o);//打印結果依舊是"Hello"
void foo(Object o) { o = "Goodbye"; } //此時已經進入棧區!o變量的修改只在此方法內有效
食用final後
Object o = "Hello";
foo(o);
System.out.println(o);//打印結果是"Hello"
void foo(final Object o) {
o = "Goodbye"; //oh shit,編譯報錯
}
在一些不會出現被修改情況的變量前加final,這樣就可以有效防止我們在通宵加班的日子裏寫出各種奇異的bug了。
但這時候你可能會問,既然加final是個好習慣,爲什麼Java不給方法參數直接強制加上final呢???
因爲有時你會遇到如下圖這種操作
void print(String msg, int count) {
msg = msg != null ? msg : "DefaultValue";
while(--count >= 0) {
System.out.println(msg);
}
}
如果不直接修改方法參數我們就要聲明更多變量去實現這種操作,如果參數不是對象而是一個巨大無比的數組,那我豈不是要開闢一倍的空間?
要知道在Java中不存在C#的ref out這種類似指針的操作
那麼問題來了,如果我需要讓這個方法來修改我傳入的參數再返回給我,又不想用return的方式,那該怎麼辦?
public static void main(String[] args) {
String[] arg = new String[2];
set(arg);
System.out.println(arg[0]);//輸出結果是"233"
}
public static void set(String[] args){
args[0] = "233";
}
上圖通過數組的特性實現了你想要的操作,而且有了這種操作我們就可以在lambda裏修改方法體外部的變量了。
關於類加載的話題:
A類
public class A {
static {
System.out.println("A類被初始化");
}
public static final int X = 100;
}
B類
public class B {
public static void main(String[] args) {
System.out.println(A.X);
}
}
如上這種使用static加final修飾的變量在如下這種情況中不會觸發父類的初始化,因爲B類被編譯時把對A.X這個變量的引用的值直接寫到B類中了,來javap對B.class一探究竟
E:\PersonalProject\Algorithm\target\classes\mairuis\algorithm>javap -verbose B.class
Classfile /E:/PersonalProject/Algorithm/target/classes/mairuis/algorithm/B.class
Last modified 2019-7-18; size 533 bytes
MD5 checksum 8b4bcec45904f68b2bd63c0212a80a9f
Compiled from "B.java"
public class mairuis.algorithm.B
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#20 // java/lang/Object."<init>":()V
#2 = Fieldref #21.#22 // java/lang/System.out:Ljava/io/PrintStream;
#3 = Class #23 // mairuis/algorithm/A
#4 = Methodref #24.#25 // java/io/PrintStream.println:(I)V
#5 = Class #26 // mairuis/algorithm/B
#6 = Class #27 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lmairuis/algorithm/B;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 SourceFile
#19 = Utf8 B.java
#20 = NameAndType #7:#8 // "<init>":()V
#21 = Class #28 // java/lang/System
#22 = NameAndType #29:#30 // out:Ljava/io/PrintStream;
#23 = Utf8 mairuis/algorithm/A
#24 = Class #31 // java/io/PrintStream
#25 = NameAndType #32:#33 // println:(I)V
#26 = Utf8 mairuis/algorithm/B
#27 = Utf8 java/lang/Object
#28 = Utf8 java/lang/System
#29 = Utf8 out
#30 = Utf8 Ljava/io/PrintStream;
#31 = Utf8 java/io/PrintStream
#32 = Utf8 println
#33 = Utf8 (I)V
{
public mairuis.algorithm.B();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 9: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lmairuis/algorithm/B;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: bipush 100 //喔,在這裏!這是A類中的常量X的值
5: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
8: return
LineNumberTable:
line 12: 0
line 13: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
}
SourceFile: "B.java"
可以看到常量池中的FieldRef只有一個System.out並沒有A.X這個變量,然後我們在後面的main方法字節碼中發現了A.X的值100。
聰明的你看到這裏一定會想到如下問題:
(1)如果我用反射修改了A.X,B類對A.X引用的值會跟着改變嗎?
(2)如果我寫了像是這樣的語句
public static void main(String[] a){
if (A.X == 100) {
System.out.println("是100");
} else {
System.out.println("不是100");
}
}
那最終編譯結果會是什麼樣?如果我在這種情況下反射修改了A.X呢?
相信有了答案之後你對Java該如何實現c++/c/c#那樣的條件編譯已經有了概念~