深入浅出final、finally与finalize&异常

1. final,finally与finalize
1.1 final,finally,finalize区别
  • final 用于声明属性,对象,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。
    final声明对象表示的是对应的引用不可变,但是对象的值可以变
  • finally 是异常处理语句结构的一部分,表示总是执行。
  • finalize 是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。如果必须保证采用特定的顺序,则必须提供自己的特有清理方法。一般实际的时候也不使用
    finalize起什么作用的就是实际的面试题
1.2 详解关键字finally:

说finally之前请先看JVM的一个知识:
在这里插入图片描述
我们知道JVM的五个区域,如图所示,JVM详情请看我的另一篇博客
我们主要讨论虚拟机栈的相关知识,
在这里插入图片描述
如图所示Java虚拟机栈栈帧包含以上内容:编译期的可知的八个基本数据类型、对象的引用和returnAddress类型
请注意加粗的returnAddress,将有助于你理解下面的知识

关键字finally:是异常处理语句结构的一部分,表示总是执行

  • finally对return的影响:return语句并不是函数的最终出口,如果有finally语句,这在return之后还会执行finally(return的值会暂存在栈里面,等待finally执行后再返回)
    参见如下的范例1:
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		System.out.println(test());
	}

	public static int test() {
		int num =10;
		try {
			System.out.println("try");
			return num += 80;
		} catch (Exception ex) {
			System.out.println("error");
		} finally {
			if (num > 20) {
				System.out.println("num < 20 :" +num);
			}
			System.out.println("finally");
		}
		return num;
	}

输出结果为:

try
num < 20 :90
finally
90

参见范例2:

	public static void main(String[] args) {
		System.out.println(test());
	}

	
	public static int test() {
		int num =10;
		try {
			System.out.println("try");
			return num += 80;
		} catch (Exception ex) {
			System.out.println("error");
		} finally {
			if (num > 20) {
				System.out.println("num < 20 :" +num);
			}
			System.out.println("finally");
			return 100;
		}
		
	}

执行结果为:

try
num < 20 :90
finally
100

参见范例3:

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		System.out.println(test());
	}

	public static int test() {
		int num =10;
		try {
			System.out.println("try");
			return num ;
		} catch (Exception ex) {
			System.out.println("error");
		} finally {
			if (num > 20) {
				System.out.println("num < 20 :" +num);
			}
			System.out.println("finally");
			num = 100;
		}
		return num;
	}

输出结果为:

try
finally
10

参见范例4:

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		System.out.println(test().num);
	}

	public static Num test() {
		Num number = new Num();
		try {
			System.out.println("try");
			return number ;
		} catch (Exception ex) {
			System.out.println("error");
		} finally {
			if (number.num > 20) {
				System.out.println("number.num < 20 :" +number.num);
			}
			System.out.println("finally");
			number.num = 100;
		}
		return number;
	}
	
	static class Num {
		public int num = 10;
	}

输出结果为:

try
finally
100
此例中不使用Number,使用Interger,返回是10,why?
1.3 finally与return总结

情况一:如果finally中有return语句,则会将try中的return语句”覆盖“掉,直接执行finally中的return语句,得到返回值,这样便无法得到try之前保留好的返回值。
情况二:如果finally中没有return语句,也没有改变要返回值,则执行完finally中的语句后,会接着执行try中的return语句,返回之前保留的值。

情况三:如果finally中没有return语句,但是改变了要返回的值,这里有点类似与引用传递和值传递的区别,分以下两种情况,:
1).如果return的数据是基本数据类型,则在finally中对该基本数据的改变不起作用,try中的return语句依然会返回进入finally块之前保留的值。
2).如果return的数据是引用数据类型,而在finally中对该引用数据类型的属性值的改变起作用,try中的return语句返回的就是在finally中改变后的该属性的值。(注意:包装类与String内部都是final的,所以结果跟1中的一样)

1.4 final修饰的值一定不变吗?

修饰基本类型值不变,修饰引用类型只是引用不可变,但是引用所指向的地址的内容可以改变

2. 初始化
类的初始化执行顺序

执行顺序:父类静态变量,父类静态代码块→子类静态变量,父类静态代码块→子类非静态代码块(即构造块)→父类构造函数→子类非静态代码快(即构造块)→子类构造函数。注:静态变量,方法按照位置顺序执行

参见如下范例1:

public class B {
    public static B t1 = new B();
    public static B t2 = new B();
    {
        System.out.println("构造块");
    }
    static
    {
        System.out.println("静态块");
    }
    public static void main(String[] args)
    {
        B t = new B();
    }
}

以上代码的输出结果为:构造块 构造块 静态块 构造块
【注意】
1、每调用一次构造方法,则执行一次构造块
2、静态块只在类加载的时候加载一次
3、有多个静态变量或块时,按声明顺序加载

正确的理解是这样的:
并不是静态块最先初始化,而是静态域.
而静态域中包含静态变量、静态块和静态方法,其中需要初始化的是静态变量和静态块.而他们两个的初始化顺序是靠他们俩的位置决定的!
So!
初始化顺序是 t1 t2 静态块

.执行顺序:静态代码块(静态域)>构造代码块>构造方法

参见如下范例2:

public class Test{  
    static{  
		cnt = 6;
    }  
	static int cnt = 100;  
	
	public static void main(String[] args){
        System.out.println("cnt = " + cnt);
        //最后输出是50,如果按照错误说法就应该是3
        //按顺序执行就是cnt=6--->cnt=100---->cnt = 100/2 = 50.
    }  static{ cnt /= 2;
    }
}

参见如下范例3:

static {
    System.out.println(test);     // error cannot reference a field before it is defined
    System.out.println(cheat());  // OK! 
}

private static boolean cheat() {
    return test;
}

private static boolean test = true;

public static void main(String args[]) {}

原因如下:
【解释如下】:
Because class loading works in this order:

1.Loads Class definition (methods, signatures)
2.Allocates the memory for the static variable references (for test) - does not initialize yet
3. Executes the static initializers (for variables) and the static blocks - in order they are defined
So, by the time you have reach static block, you have the method definition ready, but don’t have the variable ready. With cheat() you’re actually reading an uninitialized value.

【解释2如下,要按这个顺序理解初始化前面的过程:】
This is the generic steps by which class loading happens.

1.Loading - Load the class to memory
2.Verification - checks binary representation of a class e is correct
3.Preparation - create the static fields for the class and initialize those fields to their standard default values.
4.Initializing - will invoke static initializers and initializers for static fields
After preparation, your test will be false.Then before you assign static variable to true, your static block will execute.That is why you are getting false.

Try making your static variable final.In that case,you will be getting true.This is because your compiler itself will embed the value in bytecode(since the field is final) as part of optimisation.
即如下结果为true:

static {
    System.out.println(cheat());  // OK! 
}

private static boolean cheat() {
    return test;
}

private static final boolean test = true;

public static void main(String args[]) {}

注意如下范例4:

注意如下的写法中cnt就变成局部变量了
public class TestMain {
	 	static int cnt = 100;
	    static{ 
	    	int cnt = 6;
	    }   
	    
	    public static void main(String[] args){
	        System.out.println("cnt = " + cnt);
	        //最后输出是50,如果按照错误说法就应该是3
	    }  
	    static{ cnt /= 2;
	    }

}
3. 异常
3.1 错误与异常类层次结构图

3.2 详细说明
  • Throwable: 有两个重要的子类:Exception(异常)和 Error(错误)

Error(错误):是程序无法处理的错误,表示运行应用程序中较严重问题。例如JVM需要的内存不足,出现OutOfMemeory或者类找不到ClassNotFoundException; Error不需要代码进行处理

Exception(异常):是程序本身可以处理的异常。 异常分为运行时异常( RuntimeException)和可查的异常(例如IOException)

  • 通常,Java的异常(包括Exception和Error)分为可查的异常(checked exceptions)和不可查的异常(unchecked exceptions)。

除了RuntimeException及其子类以外,其他的Exception类及其子类都属于可查异常。。这种异常的特点是Java编译器会检查它,也就是说,当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。

不可查异常(编译器不要求强制处置的异常):包括运行时异常(RuntimeException与其子类)和错误(Error)

3.3 异常处理机制

异常处理机制:抛出异常 处理异常

  • 捕获异常: try、catch 和 finally
	 try {      // 可能会发生异常的程序代码 } catch (Type1 id1) {  
    // 捕获并处理try抛出的异常类型Type1  } catch (Type2 id2) {  
    // 捕获并处理try抛出的异常类型Type2   } finally {  
    // 无论是否发生异常,都将执行的语句块  } 
  • 抛出异常

方法中throws出异常 methodname throws Exception1,Excep抛tion2,…,ExceptionN {}当方法抛出异常列表的异常时,方法将不对这些类型及其子类类型的异常作处理,而抛向调用该方法的方法,由他去处理。异常流设计思路就是用方法抛出异常实现的。

代码中使用throw抛出异常:可以创建异常类的实例对象通过throw语句抛出。该语句的语法格式为: throw new exceptionname;。异常流设计思路也需要方法内部的异常抛出。

3.4 Throwable类中的常用方法:

getCause():返回抛出异常的原因。如果 cause 不存在或未知,则返回 null。
getMessage():返回异常的消息信息。
printStackTrace():对象的堆栈跟踪输出至错误输出流,作为字段 System.err 的值。

当然,也可以自定义异常。用户自定义异常类,只需继承Exception类即可。

3.5 Error和Exception的区别

Exception是java程序运行中可预料的异常情况,即可以获取到的异常,并且对这种异常进行业务外的处理。

Error是java程序运行中不可预料的异常情况,这种异常发生以后,会直接导致JVM不可处理或者不可恢复的情况。所以这种异常不可能抓取到,比如OutOfMemoryError

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值