再谈final这个关键字

xiaoxiao2021-02-28  94

转载于:http://www.cnblogs.com/dolphin0520/p/3736238.html

在这篇之前转过一篇关于final的文章(链接:http://blog.csdn.net/tt_twilight/article/details/71036543),那时还没有学到final这里,只是粗略了解一下,今天java中看到这一部分,所以深入理解了一下。(在看了很多博客之后,慢慢意识到不看书学知识还是挺不明智的一个决定,无奈书本实在啃不下去。。。)

之前认为final和const,宏定义的作用差不多,现在感觉这是一个大大的错误,二者用法相差甚远(比如修饰对象时,const:只能访问对应类中的public,protected和const成员,但是不能修改, 而 final可以访问成员方法;再如c++中可以将指针内容以及指针指向的内容都设定为不可修改的,但是java中的final只能固定引用值,而引用指向的对象的内容始终可以改变。。)

转载大神博客,感觉学习final挺经典的,加了自己的一些补充,如有不对,望及时指正。

谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字。另外,Java中的String类就是一个final类,那么今天我们就来了解final这个关键字的用法。下面是本文的目录大纲:

  一.final关键字的基本用法

  二.深入理解final关键字

一.final关键字的基本用法

  在Java中,final关键字可以用来修饰类、方法和变量(包括成员变量和局部变量)。下面就从这三个方面来了解一下final关键字的基本用法。

  1.修饰类

  当用final修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用final进行修饰。final类中的成员变量可以根据需要设为final,不设为final的成员变量还是可以多次改变,但是要注意final类中的所有成员方法都会被隐式地指定为final方法,不能被覆盖(补充:类中的构造方法不能被继承(自动调用),所以不能被final 修饰)。

  在使用final修饰类的时候,要注意谨慎选择,除非这个类真的在以后不会用来继承或者出于安全的考虑,尽量不要将类设计为final类。

  2.修饰方法

  下面这段话摘自《Java编程思想》第四版第143页:

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

  因此,如果只有在想明确禁止 该方法在子类中被覆盖的情况下才将方法设置为final的。

  注:类的private方法会隐式地被指定为final方法。(补充:private方法也不能够被继承)

  3.修饰变量

  修饰变量是final用得最多的地方,也是本文接下来要重点阐述的内容。首先了解一下final变量的基本语法:

  对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。

  举个例子:

  

  上面的一段代码中,对变量i和obj的重新赋值都报错了。

二.深入理解final关键字

  在了解了final关键字的基本用法之后,这一节我们来看一下final关键字容易混淆的地方。

1.类的final变量和普通变量有什么区别?

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

  那么final变量和普通变量到底有何区别呢?下面请看一个例子:

1 2 3 4 5 6 7 8 9 10 11 public  class  Test {      public  static  void  main(String[] args)  {          String a =  "hello2" ;           final  String b =  "hello" ;          String d =  "hello" ;          String c = b +  2 ;           String e = d +  2 ;          System.out.println((a == c));          System.out.println((a == e));      } }

  

true false

  大家可以先想一下这道题的输出结果。为什么第一个比较结果为true,而第二个比较结果为fasle。这里面就是final变量和普通变量的区别了,当final变量是基本数据类型以及String类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用。也就是说在用到该final变量的地方,相当于直接访问的这个常量,不需要在运行时确定。这种和C语言中的宏替换有点像。因此在上面的一段代码中,由于变量b被final修饰,因此会被当做编译器常量,所以在使用到b的地方会直接将变量b 替换为它的  值。而对于变量d的访问却需要在运行时通过链接来进行。想必其中的区别大家应该明白了,不过要注意,只有在编译期间能确切知道final变量值的情况下,编译器才会进行这样的优化,比如下面的这段代码就不会进行优化:

1 2 3 4 5 6 7 8 9 10 11 12 13 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。

补充实例:

public class Item{ final static String str1="123"; final static String str2; static{ str2="123"; } public static void main(String[] args) { System.out.println(str1==str2);//true System.out.println(str1+str1=="123123");//true System.out.println(str2+str2=="123123");//false } } 1) 上面第一个比较是关于基本赋值比较, 这时 如果字符串常量池中存在123,则赋值语句并不会创建对象,只是讲字符串常量池中的引用返回而已。

如果字符串常量池中不存在123,则会创建并放入字符串常量池,并返回引用,此时会有一个对象进行创建,因为常量池中相同的字符串只会被创建一次,所以相等。

2)第二个和第三个就是关于final变量初始化问题,只要定义时没有初始化,在编译时就不会被认定为常量。。

3)涉及到了字符串赋值和字符串拼接:

举个栗子:

String a="a"+"b"+"c" 这里通过编译器优化后,得到的效果是 String a="abc"

所以:

String a="abc";

String b="a"+"b"+"c";

System.out.println(a==b);//true

但是:

String a="abc";

String b="c";

String c="ab"+b;

String d="ab"+"c";

System.out.println(a==d);//true

System.out.println(a==c);//false

这里第一个输出还是比较了字符串直接拼接的栗子;

对于第二个解释:

首先比较的是 引用地址 而不是里面的值。字符串常量:由相同序列的字符组成的两个字符串属于同一对象,位于内存中的同一个位置(常量池中)。 所以在编译期间a 是字符串常量"helloworld"的地址(字符串池中)。因为String d="ab"+"c";在编译期间也能直接执行(编译成:String d="abc";),故d也是指向字符串常量"helloworld"的地址(还是字符串池中)。 而c是在运行期间创建,因为“ab”在字符串池中,但是b所指向的对象在堆中,所创建的"helloworld"字符串作为String对象重新存储在内存中的一个独立位置。

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

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

1 2 3 4 5 6 7 8 9 10 11 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修饰引用:引用指向的对象是终态;final修饰类:类不能被继承,而指向final类对象的引用是可以改变的。

举个栗子:

String类是final类;

1 String s = "abc"; 2 System.out.println("s:" + s); // 输出s:abc 3 s = "def"; 4 System.out.println("s:" + s); // 输出s:def

s的值发生了变化,但是s只是指向堆内存中的引用,存储的是对象在堆中的地址,而非对象本身,s本身存储在栈内存中。

实际上,此时堆内存中依然存在着"abc"和"def"对象。对于"abc"对象本身而言,对象的状态是没有发生任何变化的。

3.final和static

  很多时候会容易把static和final关键字混淆,static作用于成员变量用来表示只保存一份副本,而final的作用是用来保证变量不可变。看下面这个例子:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public  class  Test {      public  static  void  main(String[] args)  {          MyClass myClass1 =  new  MyClass();          MyClass myClass2 =  new  MyClass();          System.out.println(myClass1.i);          System.out.println(myClass1.j);          System.out.println(myClass2.i);          System.out.println(myClass2.j);        } }   class  MyClass {      public  final  double  i = Math.random();      public  static  double  j = Math.random(); }

  运行这段代码就会发现,每次打印的两个j值都是一样的,而i的值却是不同的。从这里就可以知道final和static变量的区别了。

(补充:static修饰变量时是为了让所有这个类的对象共享的,所以static变量并不属于某个对象,它只在类中定义一次,所以无论多少对象调用都是相当于类直接调用的,而final就是属于对象的,它的作用在于让已经初始化的变量不再有改变的机会)

4.匿名内部类中使用的外部局部变量为什么只能是final变量?

这个问题请参见上一篇博文中《Java内部类详解》中的解释,在此处不再赘述。

(呃,还没看到匿名类呢,弱也不赘述了。。。)

5.关于final传参问题 觉得原文描述不是很清楚,自己理了一下。 首先一个传基本变量的栗子:

public class Item{ KK aa=new KK(); int i=0; aa.changeValue(i); } } class KK{ public void changeValue(final int i) { i++; } }上面的栗子编译是错误的,有语言基础的人都应该知道形参i是final的,而调用方法时,已经初始化i(将主方法中的变量i赋值给形参i),所以在方法里面不可以再对i进行额外数值改变操作。

下面一个栗子:

public class Item{ KK aa=new KK(); StringBuffer buffer=new StringBuffer("hello "); aa.changeValue(buffer); System.out.println(buffer.toString()); } } class KK{ public void changeValue(final StringBuffer buffer) { buffer.append("world"); } }

上面栗子运行正确。传递的参数是对象象引用,引用里面的值不可以改变,但是对象内容是可以改变的。(这其实又追溯到了上面第二点)

转载请注明原文地址: https://www.6miu.com/read-62515.html

最新回复(0)