final关键字的字面意思是最终的, 不可修改的. 这似乎是一个看见名字就大概能知道怎么用的语法, 但你是否有深究过final在各个场景中的具体使用方法, 注意事项, 以及背后涉及的Java设计思想呢?
首先要介绍一点: 整数-127-128是默认加载到常量池里的, 也就是说如果涉及到-127-128的整数操作, 默认在编译期就能确定整数的值. 所以这里我故意选用数字2019(大于128), 避免数字默认就存在常量池中.
上面的代码运作过程是这样的:首先根据final修饰的常量会在编译期放到常量池的原则, n2会在编译期间放到常量池中.然后s变量所对应的"20190522"字符串会放入到字符串常量池中, 并对外提供一个引用返回给s变量.这时候拼接字符串s1, 由于n1对应的数据没有放入常量池中, 所以s1暂时无法拼接, 需要等程序加载运行时才能确定s1对应的值.但在拼接s2的时候, 由于n2已经存在于常量池, 所以可以直接与"0522"拼接, 拼接出的结果是"20190522". 这时系统会查看字符串常量池, 发现已经存在字符串20190522, 所以直接返回20190522的引用. 所以s2和s指向的是同一个引用, 这个引用指向的是字符串常量池中的20190522.当程序执行时, n1变量才有具体的指向.当拼接s1的时候, 会创建一个新的String类型对象, 也就是说字符串常量池中的20190522会对外提供一个新的引用.所以当s1与s用"=="判断时, 由于对应的引用不同, 会返回false. 而s2和s指向同一个引用, 返回true.
总结: 这个例子想说明的是: 由于被final修饰的常量会在编译期进入常量池, 如果有涉及到该常量的操作, 很有可能在编译期就已经完成.
提示: 在JDK1.8以后, 通过内部类访问外部局部变量时, 无需显式把外部局部变量声明为final. 不是说不需要声明为final了, 而是这件事情在编译期间系统帮我们做了. 但是我们还是有必要了解为什么要用final修饰外部局部变量.
public class Outter { public static void main(String[] args) { final int a = 10; new Thread(){ @Override public void run() { System.out.println(a); } }.start(); } } 在上面这段代码, 如果没有给外部局部变量a加上final关键字, 是无法通过编译的. 可以试着想想: 当main方法已经执行完后, main方法的栈帧将会弹出, 如果此时Thread对象的生命周期还没有结束, 还没有执行打印语句的话, 将无法访问到外部的a变量.那么为什么加上final关键字就能正常编译呢? 我们通过查看反编译代码看看内部类是怎样调用外部成员变量的.我们可以先通过javac编译得到.class文件(用IDE编译也可以), 然后在命令行输入javap -c .class文件的绝对路径, 就能查看.class文件的反编译代码. 以上的Outter类经过编译产生两个.class文件, 分别是Outter.class和Outter$1.class, 也就是说内部类会单独编译成一个.class文件. 下面给出Outter$1.class的反编译代码. Compiled from "Outter.java" final class forTest.Outter$1 extends java.lang.Thread { forTest.Outter$1(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Thread."<init>":()V 4: return public void run(); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: bipush 10 5: invokevirtual #3 // Method java/io/PrintStream.println:(I)V 8: return } 定位到run()方法反编译代码中的第3行:3: bipush 10我们看到a的值在内部类的run()方法执行过程中是以压栈的形式存储到本地变量表中的, 也就是说在内部类打印变量a的值时, 这个变量a不是外部的局部变量a, 因为如果是外部局部变量的话, 应该会使用load指令加载变量的值. 也就是说系统以拷贝的形式把外部局部变量a复制了一个副本到内部类中, 内部类有一个变量指向外部变量a所指向的值.但研究到这里好像和final的关系还不是很大, 不加final似乎也可以拷贝一份变量副本, 只不过不能在编译期知道变量的值罢了. 这时该思考一个新问题了: 现在我们知道内部类的变量a和外部局部变量a是两个完全不同的变量, 那么如果在执行run()方法的过程中, 内部类中修改了a变量所指向的值, 就会产生数据不一致问题.正因为我们的原意是内部类和外部类访问的是同一个a变量, 所以当在内部类中使用外部局部变量的时候应该用final修饰局部变量, 这样局部变量a的值就永远不会改变, 也避免了数据不一致问题的发生.
参考资料https://www.cnblogs.com/ChenLLang/p/5316662.htmlhttp://www.cnblogs.com/xrq730/p/4857820.htmlhttps://gitbook.cn/books/5c6e1937c73f4717175f7477/index.htmlhttp://www.cnblogs.com/xrq730/p/4844915.htmlhttp://www.cnblogs.com/dolphin0520/p/3811445.htmlhttps://www.cnblogs.com/dolphin0520/p/3736238.html
