zoukankan      html  css  js  c++  java
  • Java final关键字

    2017-11-05 15:08:47

    • final关键字

    Java关键字final有“这是无法改变的”或者“终态的”含义,它可以修饰非抽象类、非抽象类成员方法和变量。

    使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。                           --Java编程思想

    一、final修饰符的作用:

    1. final类不能被继承,没有子类,final类中的方法默认是final的。
    2. final方法不能被子类的方法覆盖,但可以被继承。
    3. final成员变量表示常量,只能被赋值一次,赋值后值不再改变。
    4. final不能用于修饰构造方法。
    5. 用来修饰方法参数,表示在变量的生存期中它的值不能被改变。

    注意类的private方法会隐式地被指定为final方法。

       final变量存放在方法区的常量池中

    ~ final类:

    final类不能被继承,因此final类的成员方法没有机会被覆盖,默认都是final的。在设计类时候,如果这个类不需要有子类,类的实现细节不允许改变,并且确信这个类不会载被扩展,那么就设计为final类。

    ~ final方法:

    如果一个类不允许其子类覆盖某个方法,则可以把这个方法声明为final方法。
    使用final方法的原因有二:
    第一、把方法锁定,防止任何继承类修改它的意义和实现。
    第二、高效。编译器在遇到调用final方法时候会转入内嵌机制,大大提高执行效率。

    ~ final变量(常量):

    用final修饰的成员变量表示常量,值一旦给定就无法改变!
    final修饰的变量有三种:静态变量、实例变量和局部变量,分别表示三种类型的常量。
    另外,final变量定义的时候,可以先声明,而不给初值,这中变量也称为final空白,无论什么情况,编译器都确保空白final在使用之前必须被初始化。但是,final空白在final关键字final的使用上提供了更大的灵活性,为此,一个类中的final数据成员就可以实现依对象而有所不同,却有保持其恒定不变的特征。

    ~ final参数:

    当函数参数为final类型时,你可以读取使用该参数,但是无法改变该参数的值。

    二、深入理解final关键字

    ~ final变量与普通变量的区别

    当用final作用于类的成员变量时,成员变量(注意是类的成员变量,局部变量只需要保证在使用之前被初始化赋值即可)必须在定义时或者构造器中进行初始化赋值,而且final变量一旦被初始化赋值之后,就不能再被赋值了。

            String a = "hello2";
            String t = "hello2";
            final String b = "hello";
            String d = "hello";
            String c = b + 2;
            String e = d + 2;
            String f = "hello" + 2;
            System.out.println(e);
            System.out.println((a == c));  //true
            System.out.println((a == e));  //false
            System.out.println((a == t));  //true
            System.out.println((a == f));  //true
    

    首先来解释一下为什么第二个为false,第三个为true。

    其实这两个是很简单的,第二个为false是因为Java中的+其实在底层是调用了new StringBuilder()操作,这样的操作不是直接从常量池中取数据的地址,所以自然最后比较地址的时候就不一样了。

    第三个为true的原因是,这两个的地址值均为在常量池中的地址值,因此是一样的。

    那么为什么第一个也是true呢?

    这里面就是final变量和普通变量的区别了,当final变量是基本数据类型以及String类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用。也就是说在用到该final变量的地方,相当于直接访问的这个常量,不需要在运行时确定。这种和C语言中的宏替换有点像。因此在上面的一段代码中,由于变量b被final修饰,因此会被当做编译器常量,所以在使用到b的地方会直接将变量 b 替换为它的值。

    其实这就和最后的f是同样的,这里是做了一个编译器的优化操作的结果。

    只有在编译期间能确切知道final变量值的情况下,编译器才会进行这样的优化,比如下面的这段代码就不会进行优化:

    public class Test {
        public static void main(String[] args)  {
            String a = "hello2"; 
            final String b = getHello();
            String c = b + 2; 
            System.out.println((a == c));
     
        }
         
        public static String getHello() {
            return "hello";
        }
    }
    

    这段代码的输出结果为false。这里要注意一点就是:不要以为某些数据是final就可以在编译期知道其值,通过变量b我们就知道了,在这里是使用getHello()方法对其进行初始化,他要在运行期才能知道其值。

    ~ 被final修饰的引用变量指向的对象内容可变吗

    在上面提到被final修饰的引用变量一旦初始化赋值之后就不能再指向其他的对象,那么该引用变量指向的对象的内容可变吗?看下面这个例子:

    public class Test { 
        public static void main(String[] args)  { 
            final MyClass myClass = new MyClass(); 
            System.out.println(++myClass.i); 
      
        } 
    } 
      
    class MyClass { 
        public int i = 0; 
    }
    

    这段代码可以顺利编译通过并且有输出结果,输出结果为1。这说明引用变量被final修饰之后,虽然不能再指向其他对象,但是它指向的对象的内容是可变的。

    ~ final参数

    在实际应用中,我们除了可以用final修饰成员变量、成员方法、类,还可以修饰参数、若某个参数被final修饰了,则代表了该参数是不可改变的。如果在方法中我们修改了该参数,则编译器会提示你:The final local variable i cannot be assigned. It must be blank and not using a compound assignment。看下面的例子:

    public class TestFinal {
        
        public static void main(String[] args){
            TestFinal testFinal = new TestFinal();
            int i = 0;
            testFinal.changeValue(i);
            System.out.println(i);
            
        }
        
        public void changeValue(final int i){
            //final参数不可改变
            //i++;
            System.out.println(i);
        }
    }
    

    上面这段代码changeValue方法中的参数i用final修饰之后,就不能在方法中更改变量i的值了。值得注意的一点,方法changeValue和main方法中的变量i根本就不是一个变量,因为java参数传递采用的是值传递,对于基本类型的变量,相当于直接将变量进行了拷贝。所以即使没有final修饰的情况下,在方法内部改变了变量i的值也不会影响方法外的i。

    再看下面这段代码:

    public class TestFinal {
        
        public static void main(String[] args){
            TestFinal testFinal = new TestFinal();
            StringBuffer buffer = new StringBuffer("hello");
            testFinal.changeValue(buffer);
            System.out.println(buffer);
            
        }
        
        public void changeValue(final StringBuffer buffer){
            //final修饰引用类型的参数,不能再让其指向其他对象,但是对其所指向的内容是可以更改的。
            //buffer = new StringBuffer("hi");
            buffer.append("world");
        }
    }
    

    运行这段代码就会发现输出结果为 helloworld。很显然,用final进行修饰虽不能再让buffer指向其他对象,但对于buffer指向的对象的内容是可以改变的。现在假设一种情况,如果把final去掉,结果又会怎样?看下面的代码:

    public class TestFinal {
        
        public static void main(String[] args){
            TestFinal testFinal = new TestFinal();
            StringBuffer buffer = new StringBuffer("hello");
            testFinal.changeValue(buffer);
            System.out.println(buffer);
            
        }
        
        public void changeValue(StringBuffer buffer){
            //buffer重新指向另一个对象
            buffer = new StringBuffer("hi");
            buffer.append("world");
            System.out.println(buffer);
        }
    }
    

    运行结果:

    hiworld
    hello
    

    运行结果可以看出,将final去掉后,同时在changeValue中让buffer指向了其他对象,并不会影响到main方法中的buffer,原因在于java采用的是值传递,对于引用变量,传递的是引用的值,也就是说让实参和形参同时指向了同一个对象,因此让形参重新指向另一个对象对实参并没有任何影响。

    换句话说,引用类型其实是一种弱化的指针,final限定的引用类型的变量其实是限定了这个指针的指向,但指针指向的内存中地址的值确是可以自行改变的。

              

            

  • 相关阅读:
    SQL Server 2016,2014 “无法找到数据库引擎启动句柄”
    SCCM Collection 集合获取计算机最后启动时间
    在Office 365 添加就地保留用户邮箱
    SQL Server数据库log shipping 灾备(Part2 )
    SQL Server数据库log shipping 灾备(Part1 )
    领域驱动设计案例之实现业务3
    领域驱动设计案例之业务实现2
    领域驱动设计案例之业务实现1
    领域驱动设计案例之仓储顶层实现
    领域驱动设计案例之领域层实体与聚合根实现
  • 原文地址:https://www.cnblogs.com/hyserendipity/p/7787508.html
Copyright © 2011-2022 走看看