阅读源码时常常会看到一些例如被这样修饰的方法参数
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#那样的条件编译已经有了概念~