面试中经常看到但又不是很全面回答的对比区别问题

在Java的学习中,我们经常发现一个问题:
    很多时候这个关键字/方法长的很相似,也能够说出之间的差异,但总是感觉很模糊,
    说知道吧,又说不全面,说不知道吧,你又不服气。哈哈,学习过程遇到这个很正常;
    所以这篇文章也是为了梳理一下相关的疑难杂症吧,一起学习。

一、final、finally、finalize之间的区别

1、final
final关键字主要用于三个地方:变量方法

  • 对于一个final 变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在初始化之后不能再指向另外一个对象,但是其被引用对象的值可以任意改变(如数组:不能对数组进行重新指向,但是可以对其数组内容进行改变)。
  • 当final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式的指定为final方法。
  • 使用final修饰的原因有两个:1、把方法锁定,以防任何继承类修改它的涵义;2、效率。在早期的Java实现版本中,会将final方法转换为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在Java已经不需要使用fianl方法进行这些优化了)。
  • 对final修饰的变量赋值问题。如果一个变量被final修饰,对该变量初始化方式有两种:1、直接在变量声明时对其赋值;2、使用构造器,在构造器中赋值。

2、finally
finally是一种异常处理机制。
一般情况下,finally是都需要执行的(这里指的一般是排除了如:强制终端程序System.exit(1)等非正常的操作,这样是没有意义的)。排除这些因素之后,即使程序抛出异常,也会先执行fianlly语句块中的代码,再抛出异常。**finally可能更擅长对于维护对象的内部状态比较有意义,如关闭数据库连接这方面,**可以大大降低程序出错的几率。

如下面的程序:

    try {
			int i = 0;
			i = i+1;
			return i;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
		    i = 2; 
			return i;
		}

程序返回结果:2
哪怕程序执行完try里面的语句没有异常,最终执行了finally里的语句,重新对 i 的值进行改变,并返回,这个值会被改变;注意:如果在finally里没有return语句时,对 i 的值进行改变,不会改变前面的return的值。

3、finalize

  • finalize是Object()的protected方法,子类可以覆盖方法以实现资源清理工作,GC在回收对象之前调用该方法。
  • finalize调用具有不确定性
  • Java语言规范并不保证finalize方法被及时执行、而且根本不会保证它们会被执行。finalize最多被GC执行一次(用户当然可以手动调用对象的finalize方法,但并不影响GC对finalize的行为)
  • finalize生命周期:finalize的流程:当对象变成GC不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则将其回收。若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法,执行finalize方法完毕后,GC会再次判断对象是否可达,若不可达,则进行回收,否则,对象“复活”。

二、wait、notify、notifyAll之间的区别

我在想:看到这个应该很熟悉,因为这个是在多线程中经常用到,下面我们就一起学习一下它的用法。
wait与notify、notifyAll是Java同步机制中重要的组成部分。结合与synchronized关键字使用,可以建立很多优秀的同步模型。
1、wait
wait()作用是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法用来将当前线程置入“预执行队列”中,并且在wait所在的代码处停止执行,直到接到通知或被中断为止。

  • 不管是wait、notify还是notifyAll都必须在同步方法或者同步代码块中才能调用;
  • 调用wait方法之后,该对象所拥有的锁被释放,同时线程进入阻塞状态
  • 如果调用wait()时没有持有适当的锁,则会抛出IllegalMonitorStateException.

2、notify

  • notify方法也是Object对象的方法,该方法的作用是唤醒处于阻塞状态的线程,并使其变成“Runnable”状态,但是从Runnable到Running状态还是需要看该线程有没有获取到对象锁,获取到了,就可以执行。
  • 当唤醒在等待对象的同步锁的线程(如果有多个,只唤醒一个),并不能决定是唤醒哪一个,而是由JVM确定唤醒哪一个线程,和优先级无关。
  • 当某个线程调用notify()时,虽然是可以唤醒在等待的一个线程,但是调用notify方法的线程还是需要执行完才能释放锁,其它的线程才有机会执行
package FinalTest;

/*
 * 测试wait 与 notify之间的用法
 * 
 * */
class ThreadA extends Thread {
	public Object obj;

	public ThreadA(Object obj) {
		this.obj = obj;
	}

	@Override
	public void run() {

		synchronized (obj) {
			System.out.println("ThreadA  start!");
			try {
				obj.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("ThreadA  end!");
		}
	}

}

class ThreadB extends Thread {
	public Object obj;

	public ThreadB(Object obj) {
		this.obj = obj;
	}

	@Override
	public void run() {

		synchronized (obj) {
			System.out.println("ThreadB  start!");
			obj.notify();
			System.out.println("ThreadB  end!");
		}
	}
}

public class FinalTest2 {
	public static void main(String[] args) {
		Object obj = new Object();
		ThreadA ta = new ThreadA(obj);
		ta.start();
		ThreadB tb = new ThreadB(obj);
		tb.start();
	}
}

运行结果:
	ThreadA  start!
	ThreadB  start!
	ThreadB  end!
	ThreadA  end!

在这段代码中,我们可以发现:当ThreadA执行wait之后,线程被阻塞,同时释放锁,ThreadB获取锁,并执行notify,此时还没有释放锁,所以ThreadA还不能继续执行,只有等ThreadB执行完毕之后,才能继续回到wait的代码块执行后面的代码。

3、notifyAll
notifyAll其实和notify之间的区别不是很大,只是调用notifyAll每次唤醒的是所有等待中的线程,不过一定要注意:唤醒不代表就能够立马执行,能不能执行,还需要共同争取同步锁的权限,谁拿到了锁,谁就可以执行;

三、equals 与 “==” 之间的区别

Java中equals与“==”之间的区别可以简单分为几类:

  • 对象是String字符串类型

当对象是字符串类型时,“==”比较的是两个字符串对象的内存地址;equals比较的是两个对象的内容。如:

    String s1 = "123";
    String s2 = "abc";
    String s3 = new String("123");
	String s4 = new String("123");
	String s5 = "abc";
	System.out.println(s1.equals(s3));//true
	System.out.println(s1 == s3);//false
	System.out.println(s2.equals(s5));//true
	System.out.println(s3.equals(s4));//true
	System.out.println(s3 == s4);//false
	System.out.println(s2 == s5);//true

只要看见使用new 关键字生成的对象,“= =” 一定是不相等的!因为是在堆里面重新生成的对象;s2、s5的 “= =”之所以为true是因为,在常量池中有一个"abc"变量,只是s2和s5同时指向了该变量的地址,其实都是表示的同一个对象,所以equals与==都是true。

  • 对象是基本数据类型
    基本数据类型的对象只能使用==进行数值比较,并且基本数据类型没有equals方法。常见的比较方式:
    int i = 1;
	int j = new Integer(1);
	int a = new Integer(1);
	System.out.println(i == j);//true
	System.out.println(a == j);//true
  • 对象是包装类型

如果对象是包装类型,情况如下:

    Integer a1 = 1;
	Integer a2 = new Integer(1);
	int a3 = 1;
	Integer a4 = new Integer(1);
		
	System.out.println(a1.equals(a2));//true
	System.out.println(a1 == a2);//false
		
	System.out.println(a1.equals(a3));//true
	System.out.println(a1==a3);//true
	System.out.println(a2==a3);//true
		
	System.out.println(a2==a4);//false
	System.out.println(a2.equals(a4));//true

  • 对象是StringBuffer StringBuilder等

凡是看见StringBuffer和StringBuilder的字符串对象,它们的== 和equals都是比较地址!很特殊的两个对象。

    String str = "abc";
	StringBuffer sBuffer0 = new StringBuffer("abc");
	StringBuffer sBuffer1 = new StringBuffer("abc");
	StringBuilder sBuilder = new StringBuilder("abc");
	
	System.out.println(sBuffer0.equals(sBuilder));//false
	System.out.println(sBuffer0.equals(str));//false
	System.out.println(sBuilder.equals(str));//false
	System.out.println(sBuffer0==sBuffer1);//false
	System.out.println(sBuffer0.equals(sBuffer1));//false

总结:在比较 == 和 equals之时,应该注意:

  • 如果遇到StringBuffer和StringBuilder这两个对象会比较特殊,它们的== 和equals都是比较的地址,所以一般都是不相等的
  • 包装类型与基本类型之间的==、equals是比较的数值包装类型与包装类型之间的 = =比较的是地址equals比较的是对象数值

你可能感兴趣的:(Java面试,==与equals之间的区别)