Java进阶---对象与内存控制(四)(磨灭你的自信)

Java进阶---对象与内存控制()(磨灭你的自信)

4final修饰符

final修饰符是使用“语法口诀”如下:

>  final可以修饰变量,被final修饰的变量被赋初始值之后,不能对它重新赋值;

>  final可以修饰方法,被final修饰的方法不能被重写;

>  final可以修饰类,被final修饰的类不能派生子类。

上面这只是一个泛的概念,对于真正掌握final修饰符的用法依然是不够的,下面我们来具体分析final修饰符的功能。

1)final修饰的变量

final修饰的实例变量不能被系统执行默认初始化,必须显式指定初始值,而且只能在如下3个位置指定初始值:

>  定义final实例变量时指定初始值;

>  在非静态初始化块中为final实例变量指定初始值;

>  在构造器中为final实例变量指定初始值。

虽然final实例变量有上述3中赋值方法,但是本质上final实例变量是在构造器中被赋初始值的,赋值顺序与程序员编写代码的顺序一致。

对于final类变量而言,同样必须显式指定初始值,而且final类变量只能在2个地方指定初始值:

>  定义final类变量时指定初始值;

>  在静态初始化块中为final类变量指定初始值。

虽然final类变量有上述2种赋值方法,但是本质上final类变量是在该类的静态初始化块中被赋初始值的。如果程序员没有指定静态初始化块,系统会隐式的将其放在默认的初始化块中执行。执行顺序与代码顺序一致。

但是这里一定要注意一种情况,就是程序默认的静态初始化块与程序员指定的初始化块共存的情况。这种情况出现的条件是:在定义final修饰的变量时指定了它的初始值,且该初始值可以在编译时就被确定下来(例如数字23,字符串“小武”),那么该变量的初始化过程会在默认的静态初始化块中执行,程序会先执行默认的静态初始化块(没有先后顺序),再按照代码顺序执行程序员指定的初始化块。

如果这样讲难以理解,那么我用一种通俗公用的方式可以这样说:在定义final修饰的类变量时指定了它的初始值,且该初始值可以在编译时就被确定下来,那么这个final变量将不再是一个变量,而是相当于一个直接量,系统会将其当成“宏变量”处理,所有出现该变量的地方,系统将直接把它当成对应的值处理。例如,下面这个类执行初始化后,currentPrice的结果为40.0truefalse

class Man {

       final static Man discount = new Man(0.8F);

       final static float bookPrice = 50.0F;

       double currentPrice;

       final String str1 = "小武";

       final String str2 = "灵灵";

       final String str3 = "小武" + "灵灵";

       final String str4 = str1 + str2;

       String st1 = "小武";

       String st2 = "灵灵";

       String st3 = "小武" + "灵灵";

       String st4 = st1 + st2;

       public Man(float discount) {

              currentPrice = bookPrice*0.8;

System.out.println("currentPrice=" + currentPrice);

              System.out.println("str3=" + str3 +(str3==str4) + str4 + "=str4");

              System.out.println("st3=" + st3 + (st3==st4)+ st4 + "=st4");

       }

}

2)final修饰的方法不能被重写

这里给大多数人纠正一个错误:当final修饰某个方法时,该方法不可被它的子类重写。重写的概念是什么?是子类访问到父类某一方法并其在自身中改写该方法的内容。访问是什么意思?是父类中这个方法被子类获取到。所以应该这样改:final修饰某个方法时,该方法将不可能被它的子类访问到(这与private类似),因此方法不可被它的子类重写,但是子类可以创建与其相同的方法。例如下面的Man中创建的都是普通的方法而非重写的方法:

class Persion{

       final void a(){}

       private void b(){}

       private final void c(){}

}

 

class Man {

       public void a(){}

       public void b(){}

       public void c(){}

}

3)、内部类中的局部变量

大家都知道如果程序需要在匿名内部类中使用局部变量,那么这个局部变量必须使用final修饰符修饰。但是实际上,不仅仅是匿名内部类,即使是普通内部类,在任何局部内部类中访问的局部变量都应该使用final修饰。看一个例子:

interface Man {

       String output();

}

public classtest01 {

       public static void main(String[] args) {

              test01 test01= new test01();

              final String name = "小武";

              class man implements Man{

                     @Override

                     public String output() {

                            return "我的名字叫:" + name;

                     }

              }

              System.out.println(newman().output());

       }

}

上面程序里的普通内部类访问了name局部变量,那么该变量也应该使用final修饰。

Java要求所有被内部类访问的局部变量都是用final修饰也是有原因的:对于普通局部变量而言,它的作用域就是停留在该方法内,当方法执行结束,该局部变量也随之消失;但内部类则可能产生隐式的“闭包(Closure)”,闭包将使得局部变量脱离它所在的方法继续存在。下面看一个例子:

public class test01{

       public static void main(String[] args) {

              final String name = "小武灵灵";

              new Thread(new Runnable() {

                     @Override

                     public void run() {

                            for (int i = 0; i< 100; i++) {

                                   System.out.println(name+ i + "岁了");

                                   try {

                                          Thread.sleep(100);

                                   } catch(Exception e) {}

                            }

                     }

              }).start();

       }

}

正常情况下,当程序执行完start()之后,main方法的生命周期就结束了,局部变量name的作用域也会随之结束。但实际上上个程序中只要新线程里的run方法没有执行完,匿名内部类的实例的生命周期就没有结束,将一直可以访问name局部变量的值,这就是内部类会夸大局部变来那个作用域的实例。

由于内部类可能扩大局部变量的作用域,如果再加上这个被内部类访问的局部变量没有final修饰,也就是说该变量的值可以随意改变,那么将引起极大的混乱,因此Java编译器要求所有被内部类访问的局部变量必须使用final修饰符修饰。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值