java final用法汇总

final在java中可以声明变量成员,方法,类以及本地变量,final修饰变量表示该变量不能再改变,final修饰的类和方法表示子类无法再覆盖

final关键字的优点

  1. JVM会优化final变量,final关键字提高了性能
  2. final变量可以在无需线程同步的情况下并发使用

final的知识点

  1. final成员变量必须在声明的时候初始化或者在构造器中初始化或者在普通初始化快初始化,否则就会报编译错误。
  2. 局部变量必须在声明时赋值
  3. 在匿名类中所有变量都必须是final变量
  4. final关键字容易与finalize()方法搞混,后者是在Object类中定义的方法,是在垃圾回收之前被JVM调用的方法
  5. 接口中声明的所有变量本身是final的
  6. final和abstract这两个关键字是反相关的,final类就不可能是abstract的。
  7. final方法在编译阶段绑定,称为静态绑定(static binding)。

不可变的String

  1. 不可变得String对象实现了String interning,也使字符串池的实现成为了可能。字符串池的实现节约了内存资源

  2. 字符串是不可变的所以字符串自己就是线程安全的

  3. 字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。

缓存的不可变类

对象的创建需要时间和内存资源,为了避免常用对象的创建对时间和资源的消耗.在java Integer类的jdk设计中,有如下这样的内部类用于缓存-128 ~ 127之间的Integer对象

    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
           ...
           ...
           ...
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }

我们看下面这段代码的运行结果

Integer a = 1000, b = 1000; 
System.out.println(a == b);//1
Integer c = 100, d = 100; 
System.out.println(c == d);//2
false
true

答案可能有点奇怪,不过理解java Integer缓存对象的基础上这个结果也就不难理解了
a,b是创建时声明的对象
c,d引用的是一个缓存

这对我们的程序设计也是一种启发,当程序中经常使用想用的不可变实例就应该考虑缓存这样的不可变实例

接口中的变量

接口中声明的变量都是常量即使不加final也是常量

例如

String str="str";

实际上就是

public static final String str="str";

如果试图在接口实现类中修改接口中定义的变量会报如下错误

Error:(6, 9) java: The final field com.xxx.str cannot be assigned

总的来说接口只是对一类事物的属性和行为更高层次的抽象,不变的东西放接口,会变得东西放在实现类

final参数

方法中的匿名内部类如果要直接使用外部方法的参数(或是局部变量)必须将局部变量声明为final

public static void test(final String name, final int id) {
    new Thread() {
        @Override
        public void run() {
            System.out.println(name + " -> " + id);
        }
    }.start();
}

而在java8中不需要显示的在方法签名中将参数用final修饰,但是参数仍然是final类型的不能够修改

原因
1.当编译器将 java 文件编译为 class 文件时,匿名内部类和其所在的类生成的 class 文件并不是一份,而是两份(这类似于编译器对内部类的处理),用一份单独的 class 文件保存匿名内部类,然而匿名内部类要用到另一份 class 文件中的变量,他俩又是两份独立的 class 文件,此时只能将用到的变量作为匿名内部类构造函数的参数传到匿名内部类中(使用匿名内部类时是无法添加构造函数的),匿名内部类会生成同名的域来保存这些参数。这个工作是由编译器完成的。

2.为什么要用 final 修饰,上面说了编译后是由单独的一份 class 文件保存匿名内部类,然而我们在编写匿名内部类的时候程序“看起来”是直接使用了变量的(没有传参,复制引用的过程),如果匿名内部类修改了变量,那外部的变量也应该相应的做出改变,外部修改了变量那匿名内部类中也应该改变,或者说匿名内部类跟外部的方法是一体的,他们读取到变量的值应该是一样的,如果不要求使用 final 修饰,那外部修改了变量的值,匿名内部类读取时读取到的值却是初始时传入的值,这时就不一致了,这是不符合预期的,我们使用匿名内部类的初衷大多时候就是能直接使用外部的变量,使内外在某一属性上保持一致,因此使用 final 修饰就能保证唯一性,达到一致的效果。

final修饰局部变量

final修饰局部变量更多的还是为了代码的可读性。在一个函数中,更为被关注的是一些与逻辑相关的局部变量,而一些变量就是为了一次性数据存储,那么可以用final修饰这些局部变量。这样读代码的时候就能聚焦到没有final修饰的局部变量更快的理解代码逻辑

你可能感兴趣的:(java)