为了账号安全,请及时绑定邮箱和手机立即绑定

final,finally和finalize

标签:
Java

要想区分这几个关键字的不同之处,就需要弄清楚每一个关键字的本来含义,也就是说为什么想要设置这样的一个关键字,只能搞清楚了一个关键字的本来含义才能明白他们的本质区别。

final

从作用上来看,final是可以用来修饰类,方法,变量。并且都有不同的含义。

修饰Class

final修饰的类是不可以被继承的,也就是该类不可扩展,不可变了。

修饰方法

对于被final修饰的方法是不可以被重写的,也就是该方法不可变了。

修饰变量

对于被final修饰的变量是不可以被改变的。这也是说不可变。

引用与非引用

package example.testfinal;
import java.util.ArrayList;
import java.util.List;
public class FinalDemo {
    public static void main(String[] args) {
        final int i = 6;
        final String a;
        //i = 10; 错误
        final List<String> list = new ArrayList<>();
        list.add("10");
        list.add("20");
        //list = new ArrayList<>(); 错误
        a = "hello";
        //a = "world"; 错误
    }
}

可以看到上面的代码,无论是什么类型,只要是被final修饰就只能赋值一次。但是对于引用类型和非引用类型还是有一点区别的,可以看到非引用类型就是变量的值不可变。而引用类型如图所示:

图片描述

不可变指的是栈中的变量保存的指向堆中的地址是不可变的,但是堆中的对象可以变,就像上面的代码中写的一样,list依然可以添加元素。

共同点

从上面的描述中可以都可以看到一个共同的字眼,就是不可变。其实这也是final关键字的本来含义,就是只有是被final修饰的内容是不想被改变的。要知道我们不是一个人在开发,既然很多人要协作,那么有些不想被别人改变的内容就可以通过final关键字来修饰,这样别人就知道这些变量或方法或类是不想被改变的。

finally

finallyjava为了保证重点代码一定被执行,通常是用来进行释放资源的操作。但是java7之后推荐使用try-with-resource来释放资源。

finally的本质

上面说finally的作用是保证重点代码都可以被执行,那么是如何保证的呢。

package example.testpackage;
public class FinallyDemo4 {
    public static void main(String[] args) {
        one();
    }
    private static void one() {
        int i = 0;
        try {
            i = 10;
        } catch (Exception e) {
            i = 20;
        } finally {
            i = 30;
        }
    }
}

现在使用反编译命令 javap -p -v -l FinallyDemo4.class来看一下反编译之后的字节码文件。

图片描述

通过分析编译后的字节码可知,之所以说finally中的代码一定会被执行到,是因为其实是将finally中的代码既拷贝到了try当中,也拷贝到了catch当中,所以无论代码有没有发生异常,finally中的代码都会执行到。

try-with-resources

jdk7之前

平时我们经常会使用的输入流和输出流的,这两个流使用完成之后都是需要进行关闭,一般的操作如下面的代码

package example.testpackage;

import java.io.*;

public class CloseResource {
    public static void main(String[] args) {
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            inputStream = new FileInputStream("D:/axis.log");
            outputStream = new FileOutputStream("E:/axis.log");
            // 缓冲区
            byte[] bytes = new byte[1024];
            int len = 0;
            while ((len = inputStream.read(bytes)) != -1) {
                outputStream.write(bytes, 0, len);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();

                }
            }
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

可以看到上面有很多与业务代码无关的代码。

jdk7及以后

public class CloseResource {
    public static void main(String[] args) {
        withResource();
    }

    private static void withResource() {
        try(InputStream inputStream = new FileInputStream("D:/axis.log");
        OutputStream outputStream = new FileOutputStream("E:/axis.log")){
            // 缓冲区
            byte[] bytes = new byte[1024];
            int len = 0;
            while ((len = inputStream.read(bytes)) != -1) {
                outputStream.write(bytes, 0, len);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

使用try-with-resource代码会简单很多,做法也很简单,就是将需要关闭的资源定义在try()的括号中。

finally不执行

虽然说finally的目的是为了让重要的代码 一定可以执行到,但是这里的一定也不是绝对的,比如下面的代码

package example.testpackage;

public class FinallyDemo {

    public static void main(String[] args) {
        try {
            System.out.println("try块");
            System.exit(1);
        } finally {
            System.out.println("finally");
        }
    }
}

如果在try块里面执行了 System.exit(1)之后,在finally中的内容就不会执行了,因为执行了System.exit(1)实际上是已经退出了jvm虚拟机。

多个位置的return的问题

注意:如果在finally中写了return会吞掉异常。

只有try中有return

package example.testpackage;
public class FinallyDemo4 {
    public static void main(String[] args) {
        System.out.println(one());
    }
    private static int one() {
        int i = 0;
        try {
            i = 10;
            return i;
        } catch (Exception e) {
            i = 20;
            throw new RuntimeException();
        } finally {
            i = 30;
        }
    }
}

上面的代码中只有在try中有return,但是在finally中修改了i的值,那么返回的结果是什么呢?看一下反编译后的字节码文件 javap -p -v -l FinallyDemo4.class
图片描述

通过分析编译后的字节码可以返回的结果是10,也就是说虽然finally中修改了变量的值,但是返回的还是try块中的值。

try和finally中都有return

package example.testpackage;

public class FinallyDemo4 {
    public static void main(String[] args) {
        System.out.println(one());
    }
    private static int one() {
        int i = 0;
        try {
            i = 10;
            return i;
        } catch (Exception e) {
            i = 20;
            throw new RuntimeException();
        } finally {
            i = 30;
            return i;
        }
    }
}

看下编译后的字节码文件
图片描述

由上面的分析可知,当tryfinally中都有return的时候,最终返回的是finally中修改的值。

try和catch和finally中都有

package example.testpackage;

public class FinallyDemo4 {
    public static void main(String[] args) {
        System.out.println(one());
    }
    private static int one() {
        int i = 0;
        try {
            i = 10;
            int b = 1 / 0;
            return i;
        } catch (Exception e) {
            i = 20;
            return i;
        } finally {
            i = 30;
            return i;
        }
    }
}

看上面的代码,三个块中都有return,并且发生了异常,此时来看下编译后的字节码

图片描述

通过上面的分析可知,最终返回的是30

finally中使用return吞掉异常

package example.testpackage;

public class FinallyDemo3 {
    public static void main(String[] args) {
        int i = one();
        System.out.println(i);
    }
    private static int one() {
        try {
            int a = 1 /0;
            return 10;
        } finally {
            return 20;
        }
    }
}

上面的代码会输出的结果是20,而且也不会有异常信息。看一下编译后的字节码中one方法的信息
图片描述

现在去掉finally中的return,再反编译看一下
图片描述

可以看到最后是用athrow的,所以我们写代码是最好是不要在finally中写return语句。

finalize

java垃圾回收中,使用的可达性分析算法来判断一个垃圾是不是可以被回收,但是即使在可达性分析中该对象是不可达的,也不会立即被回收。此时他们暂时处于缓刑阶段,要宣告一个对象死亡,至少需要经历两次标记过程

  • 如果对象在进行可达性分析后发现没有与GC Root相连接的引用链,那么将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法,当对象没有覆盖finalize()方法时,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情形视为**“没有必要执行”**
  • 如果该对象判断为有必要执行finalize()方法,那么该对象将中被放置在一个叫做F-Queue队列中,并且在稍后由一个虚拟机自动建立的,优先级低的Finalizer线程去执行它。finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模的标记,如果对象在finalize()中成功拯救自己(只要重新与引用链上的任何一个对象建立关联即可,比如将自己(this关键字)赋值给某个类变量或对象的成员变量,那么在第二次标记的时候会被将它移除“即将回收”的集合),如果对象在此时还没有逃脱,那就说明需要被回收了。

所以finalize可以用于对象自救,但是这种方式最好不要使用,效率很低。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消