java基础知识之(==与equals,try catch finally return ,装箱与拆箱)

一:==和equals区别

在初学Java时,可能会经常碰到下面的代码:

 String str1 = new String("hello");
 String str2 = new String("hello");

 System.out.println(str1==str2);
 System.out.println(str1.equals(str2));

为什么第4行和第5行的输出结果不一样?==和equals方法之间的区别是什么?如果在初学Java的时候这个问题不弄清楚,就会导致自己在以后编写代码时出现一些低级的错误。今天就来一起了解一下==和equals方法的区别之处。

一.关系操作符“==”到底比较的是什么?

  下面这个句话是摘自《Java编程思想》一书中的原话:

  “关系操作符生成的是一个boolean结果,它们计算的是操作数的值之间的关系”。

  这句话看似简单,理解起来还是需要细细体会的。说的简单点,==就是用来比较值是否相等。下面先看几个例子:

public class Main {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub

        int n=3;
        int m=3;

        System.out.println(n==m);

        String str = new String("hello");
        String str1 = new String("hello");
        String str2 = new String("hello");

        System.out.println(str1==str2);

        str1 = str;
        str2 = str;
        System.out.println(str1==str2);
    }

}

输出结果为 true false true

  n==m结果为true,这个很容易理解,变量n和变量m存储的值都为3,肯定是相等的。而为什么str1和str2两次比较的结果不同?要理解这个其实只需要理解基本数据类型变量和非基本数据类型变量的区别。

  在Java中游8种基本数据类型:

  浮点型:float(4 byte), double(8 byte)

  整型:byte(1 byte), short(2 byte), int(4 byte) , long(8 byte)

  字符型: char(2 byte)

  布尔型: boolean(JVM规范没有明确规定其所占的空间大小,仅规定其只能够取字面值”true”和”false”)

  对于这8种基本数据类型的变量,变量直接存储的是“值”,因此在用关系操作符==来进行比较时,比较的就是 “值” 本身。要注意浮点型和整型都是有符号类型的,而char是无符号类型的(char类型取值范围为0~2^16-1).

  也就是说比如:

  int n=3;

  int m=3; 

  变量n和变量m都是直接存储的”3”这个数值,所以用==比较的时候结果是true。

  而对于非基本数据类型的变量,在一些书籍中称作为 引用类型的变量。比如上面的str1就是引用类型的变量,引用类型的变量存储的并不是 “值”本身,而是于其关联的对象在内存中的地址。比如下面这行代码:

  String str1;

  这句话声明了一个引用类型的变量,此时它并没有和任何对象关联。

  而 通过new String(“hello”)来产生一个对象(也称作为类String的一个实例),并将这个对象和str1进行绑定:

  str1= new String(“hello”);

  那么str1指向了一个对象(很多地方也把str1称作为对象的引用),此时变量str1中存储的是它指向的对象在内存中的存储地址,并不是“值”本身,也就是说并不是直接存储的字符串”hello”。这里面的引用和C/C++中的指针很类似。

  因此在用==对str1和str2进行第一次比较时,得到的结果是false。因此它们分别指向的是不同的对象,也就是说它们实际存储的内存地址不同。

  而在第二次比较时,都让str1和str2指向了str指向的对象,那么得到的结果毫无疑问是true。

二.equals比较的又是什么?

equals方法是基类Object中的方法,因此对于所有的继承于Object的类都会有该方法。为了更直观地理解equals方法的作用,直接看Object类中equals方法的实现。

该类的源码路径为:C:\Program Files\Java\jdk1.6.0_14的src.zip 的java.lang路径下的Object.java(视个人jdk安装路径而定)。

下面是Object类中equals方法的实现:
这里写图片描述

很显然,在Object类中,equals方法是用来比较两个对象的引用是否相等,即是否指向同一个对象。
但是有些朋友又会有疑问了,为什么下面一段代码的输出结果是true?

public class Main {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub

        String str1 = new String("hello");
        String str2 = new String("hello");

        System.out.println(str1.equals(str2));
    }
}

要知道究竟,可以看一下String类的equals方法的具体实现,同样在该路径下,String.java为String类的实现。
下面是String类中equals方法的具体实现:
这里写图片描述

可以看出,String类对equals方法进行了重写,用来比较指向的字符串对象所存储的字符串是否相等。
其他的一些类诸如Double,Date,Integer等,都对equals方法进行了重写用来比较指向的对象所存储的内容是否相等。
总结来说:
1、对于==,如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等;
如果作用于引用类型的变量,则比较的是所指向的对象的地址

2、对于equals方法,注意:equals方法不能作用于基本数据类型的变量
如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;
诸如String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容。

二:try catch finally return 执行顺序

情况一:try{} catch{} finally{} return
很显然按着顺序执行
例子

public static int test() {
        int i=10;
        try{
            System.out.println("i in try block is:"+i);
            i=i/0;
        }
        catch(Exception e){
            System.out.println("i in catch - form try block is:"+i);
        }
        finally{

            System.out.println("i in finally - from try or catch block is:"+i);
            --i;
            --i;
            System.out.println("i in finally block is:"+i);
        }
        return --i;
    }

执行结果:

i in try block is10
i in catch - form try block is10
i in finally - from try or catch block is10
i in finally block is8
7

情况二:try{ return; }catch(return; ){} finally{ return ;} return;

  • try中没有抛出异常:
    例子:
        int i=10;
        try{
            System.out.println("i in try block is:"+i);
            return --i;
        }
        catch(Exception e){
            --i;
            System.out.println("i in catch - form try block is:"+i);
            return --i;
        }
        finally{
            System.out.println("i in finally - from try or catch block is:"+i);
            return --i;
        }

运行结果:

i in try block is10
i in finally - from try or catch block is9
8

分析:执行try块,执行到return语句时,先执行return的语句,–i,但是不返回到main方法,执行finally块,遇到finally块中的return语句,执行–i,并将值返回到main方法,这里就不会再回去返回try块中计算得到的值。
结论:try-catch-finally都有return语句时,没有异常时,返回值是finally中的return返回的。

  • try中抛出异常:
    例子:
            int i=10;
            try{
                System.out.println("i in try block is:"+i);
                i = i/0;
                return --i;
            }
            catch(Exception e){
                System.out.println("i in catch - form try block is:"+i);
                --i;
                System.out.println("i in catch block is:"+i);
                return --i;
            }
            finally{           
                System.out.println("i in finally - from try or catch block is--"+i);
                --i;
                System.out.println("i in finally block is--"+i);
                return --i;
            }

执行结果:

i in try block is10
i in catch - form try block is10
i in catch block is9
i in finally - from try or catch block is--8
i in finally block is--7
6

分析:
抛出异常后,执行catch块,在catch块的return的–i执行完后,并不直接返回而是执行finally,因finally中有return语句,所以,执行,返回结果6。
结论:
try块中抛出异常,try、catch和finally中都有return语句,返回值是finally中的return。

情况三:try{ return; }catch{return;} finally{ } 仅try catch中有return

  • try中没有异常
    例子:
        int i=10;
        try{
            System.out.println("i in try block is:"+i);
            return --i;
        }
        catch(Exception e){
            --i;
            System.out.println("i in catch - form try block is:"+i);
            return --i;
        }
        finally{
            System.out.println("i in finally - from try or catch block is:"+i);
            --i;
            System.out.println("i in finally block is:"+i);
        }

执行结果:

i in try block is10
i in finally - from try or catch block is9
i in finally block is8
9

分析:
try中执行完return的语句后,不返回,执行finally块,finally块执行结束后,返回到try块中,返回i在try块中最后的值。
结论:try-catch都有return语句时,没有异常时,返回值是try中的return返回的。

  • try中抛出异常
    例子:
        int i=10;
        try{
            System.out.println("i in try block is:"+i);
            i=i/0;
            return --i;
        }catch(Exception e){
            System.out.println("i in catch - form try block is:"+i);
            return --i;
        }finally{

            System.out.println("i in finally - from try or catch block is:"+i);
            --i;
            System.out.println("i in finally block is:"+i);
        }

执行结果:

i in try block is10
i in catch - form try block is10
i in finally - from try or catch block is9
i in finally block is8
9

分析:
抛出异常后,执行catch块,执行完finally语句后,依旧返回catch中的执行return语句后的值,而不是finally中修改的值。
结论:返回的catch中return值。

情况四:try、catch中都出现异常,在finally中有返回
例子:

            int i=10;
            try{
                System.out.println("i in try block is:"+i);
                i=i/0;
                return --i;
            }
            catch(Exception e){
                System.out.println("i in catch - form try block is:"+i);
                int j = i/0;
                return --i;
            }
            finally{

                System.out.println("i in finally - from try or catch block is:"+i);
                --i;
                --i;
                System.out.println("i in finally block is:"+i);
                return --i;

执行结果:

i in try block is10
i in catch - form try block is10
i in finally - from try or catch block is10
i in finally block is8
7

分析:
try块中出现异常到catch,catch中出现异常到finally,finally中执行到return语句返回,不检查异常。
结论:返回finally中return值。

总结:return语句并不是函数的最终出口,如果有finally语句,这在return之后还会执行finally(return的值会暂存在栈里面,等待finally执行后再返回)

三:深入剖析java中的装箱与拆箱

在Java SE5之前,如果要生成一个数值为10的Integer对象,必须这样进行:

Integer i=new Integer(10);

但是在Java SE5开始就提供了自动装箱的特性,如果要生成一个数值为10的Integer对象,只需要这样就可以了:

Integer i = 10;

这个过程会自动根据数值去创建Integer对象,把基本数据类型自动转换成对应的包装器类型,这就是装箱。

那么什么是拆箱呢?顾名思义,跟装箱对应,就是自动将包装器类型转换为基本数据类型:

Integer i = 10; //装箱
int j = i;      //拆箱

下面是基本类型所对应的包装器类型:

先来一个例子,来引出装箱与拆箱,看下面的例子:

基本数据类型 对应的包装器类型
byte(1个字节) Byte
short(2个字节) Short
int(4个字节) Integer
long(8个字节) Long
float(4个字节) Float
double(8个字节) Double
boolean(未定) Boolean
char(2个字节) Character

上面理解了装箱和拆箱的基本概念后,我们接下来去看看拆箱和装箱时怎么实现的
我们以下面的代码为例:

public class Main {
    public static void main(String[] args) {

        Integer i = 10;
        int n = i;
    }
}

接下来我们反编译Main的class文件看一下:

这里写图片描述

从反编译得到的字节码内容可以看出,在装箱的时候自动调用的是Integer的valueOf(int)方法。而在拆箱的时候自动调用的是Integer的intValue方法。

其他的也类似,比如Double、Character,不相信的朋友可以自己手动尝试一下。

因此可以用一句话总结装箱和拆箱的实现过程:
装箱过程是通过调用包装器的valueOf方法实现的,而拆箱过程是通过调用包装器的 xxxValue方法实现的。(xxx代表对应的基本数据类型)。

下面我们列举一下装箱和拆箱的例题

  • 我们看下面一个例子
public class Main {
    public static void main(String[] args) {

        Integer i1 = 100;
        Integer i2 = 100;
        Integer i3 = 200;
        Integer i4 = 200;

        System.out.println(i1==i2);
        System.out.println(i3==i4);
    }
}

输出结果:true false
哇偶,是不是很神奇,为什么会出现这种情况呢?输出结果表明i1和i2指向的是同一个对象,而i3和i4指向的是不同的对象。此时只需一看源码便知究竟,下面这段代码是Integer的valueOf方法的具体实现:

    public static Integer valueOf(int i) {
        return  i >= 128 || i < -128 ? new Integer(i) : SMALL_VALUES[i + 128];
    }

    /**
     * A cache of instances used by {@link Integer#valueOf(int)} and auto-boxing
     */
    private static final Integer[] SMALL_VALUES = new Integer[256];

    static {
        for (int i = -128; i < 128; i++) {
            SMALL_VALUES[i + 128] = new Integer(i);
        }
    }

从这2段代码可以看出,在通过valueOf方法创建Integer对象的时候,如果数值在[-128,127]之间,便返回指向SMALL_VALUES 中已经存在的对象的引用;否则创建一个新的Integer对象。

上面的代码中i1和i2的数值为100,因此会直接从SMALL_VALUES 中取已经存在的对象,所以i1和i2指向的是同一个对象,而i3和i4则是分别指向不同的对象。

  • 我们继续再看下面一个例子
public class Main {
    public static void main(String[] args) {

        Double i1 = 100.0;
        Double i2 = 100.0;
        Double i3 = 200.0;
        Double i4 = 200.0;

        System.out.println(i1==i2);
        System.out.println(i3==i4);
    }
}

输出结果:false false

至于具体为什么,我们可以去查看Double类的valueOf的实现。

public static Double valueOf(double d) {
        return new Double(d);
    }

哦,好吧,这家伙为每一个都new 了一个对象。

注意,Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的。
Double、Float的valueOf方法的实现是类似的。

  • 下面这段代码输出什么
public class Main {
    public static void main(String[] args) {

        Boolean i1 = false;
        Boolean i2 = false;
        Boolean i3 = true;
        Boolean i4 = true;

        System.out.println(i1==i2);
        System.out.println(i3==i4);
    }
}

输出结果:true true

至于为什么是这个结果,同样地,看了Boolean类的源码也会一目了然。下面是Boolean的valueOf方法的具体实现:

public static Boolean valueOf(boolean b) {
        return b ? Boolean.TRUE : Boolean.FALSE;
    }

至此,大家应该明白了为何上面输出的结果都是true了。

  • 谈谈Integer i = new Integer(xxx)和Integer i =xxx;这两种方式的区别。

    当然,这个题目属于比较宽泛类型的。但是要点一定要答上,我总结一下主要有以下这两点区别:

    1)第一种方式不会触发自动装箱的过程;而第二种方式会触发;

    2)在执行效率和资源占用上的区别。第二种方式的执行效率和资源占用在一般性情况下要优于第一种情况(注意这并不是绝对的)。

  • 下面程序的输出结果是什么?

    public class Main {
    public static void main(String[] args) {

        Integer a = 1;
        Integer b = 2;
        Integer c = 3;
        Integer d = 3;
        Integer e = 321;
        Integer f = 321;
        Long g = 3L;
        Long h = 2L;

        System.out.println(c==d);
        System.out.println(e==f);
        System.out.println(c==(a+b));
        System.out.println(c.equals(a+b));
        System.out.println(g==(a+b));
        System.out.println(g.equals(a+b));
        System.out.println(g.equals(a+h));
    }
}

先别看输出结果,读者自己想一下这段代码的输出结果是什么。这里面需要注意的是:当 “==”运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)。另外,对于包装器类型,equals方法并不会进行类型转换。明白了这2点之后,上面的输出结果便一目了然:

true
false
true
true
true
false
true

第一个和第二个输出结果没有什么疑问。第三句由于 a+b包含了算术运算,因此会触发自动拆箱过程(会调用intValue方法),因此它们比较的是数值是否相等。而对于c.equals(a+b)会先触发自动拆箱过程,再触发自动装箱过程,也就是说a+b,会先各自调用intValue方法,得到了加法运算后的数值之后,便调用Integer.valueOf方法,再进行equals比较。同理对于后面的也是这样,不过要注意倒数第二个和最后一个输出的结果(如果数值是int类型的,装箱过程调用的是Integer.valueOf;如果是long类型的,装箱调用的Long.valueOf方法)。

猜你喜欢

转载自blog.csdn.net/u013277209/article/details/75339805