java里的双等号运算到底做了什么

2018-11-25  本文已影响0人  RaffaelloJoe

写代码经常会遇到if(xx==xx)这样的判断语句,那么什么情况下用==什么时候不该用,用了之后是什么效果,这种效果是不是我们想要的呢?以下是以编译指令的角度分析一下双等号运算什么情况下会相等,为什么会这样。

先看两个int也就是值类型的比较。

public class Foo{
    int  i1 = 100;
    int  i2 = 100;
    System.out.println(i1 == i2);
}

上面这段代码会输出true还是false呢,毫无疑问是true。但是为什么呢?
看一下编译后的操作指令

bipush  100;  //将int类型数字100压入栈中
istore_1;     //将100弹出保存到当前本地变量1中
bipush  100;  //将int类型数字100压入栈中
istore_2;     //将100弹出保存到当前本地变量2中
getstatic       Field java/lang/System.out:"Ljava/io/PrintStream;";
iload_1;      //将本地变量1压入栈中
iload_2;      //将本地变量2压入栈中
if_icmpne       L18;//比较两个int变量是否相同

执行过程就是把100这个数字压入栈中然后弹出保存到本地变量1,再把这个数字压入栈中保存到本地变量2。然后用if_icmpne把两个100进行比较,所以相同。
那再来看看下面这段代码输出的是true还是false呢?

public static void main(String[] args){
        int i1 = 100;
        Integer i2 = 100;
        System.out.println( i1 == i2 ); 
}

如果根据第一个代码的分析,第一个是将int类型数字直接存在了本地变量1中,第二个将引用类型Integer的引用存在类本地变量2中。那么二者是否还相等呢?来看看编译后的指令

bipush  100;
istore_1;
bipush  100;
invokestatic    Method java/lang/Integer.valueOf:"(I)Ljava/lang/Integer;";
astore_2;   //确实将Integar的引用存在了第二个本地变量里
getstatic       Field java/lang/System.out:"Ljava/io/PrintStream;";
iload_1;
aload_2;
invokevirtual   Method java/lang/Integer.intValue:"()I";   //但是在比较前执行了这个指令 调用了intValue这个方法把int值100拿了出来 所以最终还是两个int值100进行if_icmpne的比较 故而输出还是true
if_icmpne       L24;

由上面的编译后指令可以得知,我们再做=赋值和==比较运算的时候编译器是会为我们做一些额外工作的。接下来我们在比较一下int和float吧,看看编译器喵悄的做了什么

public static void main(String[] args){
        int i = 100;
        float f = 100f;
        System.out.println(i == f); 
}

对应的编译后指令

bipush  100;
istore_1;
ldc     float 100.0f;
fstore_2;
getstatic       Field java/lang/System.out:"Ljava/io/PrintStream;";
iload_1;  //把本地变量1中的int类型数字100拿到栈中
i2f;      //---将int类型转换成float类型等待比较---
fload_2;
fcmpl;    //对两个float类型进行比较

可以看到编译指令将int类型的数字转换成了float类型 结果自然也就相等了。
为什么编译器要进行转换工作呢?因为不同类型的变量找不到指定的编译指令让他们进行运算。
根据上面的实例,那么两个Integer呢?

public static void main(String[] args){
        Integer i1 = new Integer(100);
        Integer i2 = new Integer(100);
        System.out.println(i1 == i2);   
}

可以推断编译器会把两个引用变量的引用放在本地变量中然后拿出来进行比较,因为是两个不同引用变量所以虽然值相同(都是100)但是引用不同所以输出false;
那么Integer和Flaot呢(两个都是引用类型)?

public static void main(String[] args){
        Integer i1 = new Integer(100);
        Float f1 = new Float(100);
        System.out.println(i1 == f1);
}

对不起不同的引用类型不能用==运算符,编译会报错~~~
下面这样呢?

public static void main(String[] args){
        int i1 =  new Integer(100);
        Float f1 = new Float(100);
        System.out.println(i1 == f1);
}

根据上面的那些理论,编译器会做些转换所以不仅编译通过而且会输出true(生成Integer变量->执行intValue方法存入本地变量1中->生成Float变量将变量引用存入本地变量2->本地变量1int值拿出来转换成float类型->本地变量2拿到栈中->执行floatVlaue方法->两个float100值比较)

两个字符串比较

public static void main(String[] args){
        String s1 = "aaa";
        String s2 = "aaa";
        System.out.println(s1 == s2);
}

编译后的指令

ldc     String "aaa";//将aaa这个字符串的引用压入栈中
astore_1;            //保存aaa的引用到本地变量1
ldc     String "aaa";//将aaa这个字符串的引用压入栈中
astore_2;            //保存aaa的引用到本地变量2
getstatic       Field java/lang/System.out:"Ljava/io/PrintStream;";
aload_1;             //取得本地变量1入栈
aload_2;             //取得本地变量2入栈
if_acmpne       L18; //比较连个引用是否相等

两个相同的引用比较,结果固然相等
如果一个是new的String对象呢?

public static void main(String[] args){
        String s1 = "aaa";
        String s2 = new String("aaa");
        System.out.println(s1 == s2);
}

编译后指令

ldc     String "aaa";
astore_1;
new     class java/lang/String; //为new的String对象开辟内存空间
dup;
ldc     String "aaa"; //将aaa的引用押入栈中给接下来的String构造方法使用
//调用String构造方法,方法执行完将会返回一个String对象的引用
invokespecial   Method java/lang/String."<init>":"(Ljava/lang/String;)V";
astore_2; //将新的对象引用存在本地变量2中
getstatic       Field java/lang/System.out:"Ljava/io/PrintStream;";
aload_1;
aload_2;
if_acmpne       L25;

可以看到两个变量引用不再是同一个所以进行比较时得到的结果就是false
如果有一个变量有相加运算呢?

public static void main(String[] args){
        String s1 = "aaa";
        String s2 = "aa"+"a";
        System.out.println(s1 == s2);
}

编译后指令

ldc     String "aaa";
astore_1;
ldc     String "aaa";
astore_2;
getstatic       Field java/lang/System.out:"Ljava/io/PrintStream;";
aload_1;
aload_2;
if_acmpne       L18;

看来编译器直接在编译的时候就把加法算完了。我们看到这个编译结果和上面第一个字符串比较的例子相同。
如果是一个字符串和一个new出来的String相加呢?结果如何?

public static void main(String[] args){
        String s1 = "aaa";
        String s2 = "aa"+new String("a");
        System.out.println(s1 == s2);
}

最主要的就是看+这个运算这次怎么做的

ldc     String "aaa";
astore_1;
new     class java/lang/StringBuilder;
dup;
invokespecial   Method java/lang/StringBuilder."<init>":"()V";
ldc     String "aa";
invokevirtual   Method java/lang/StringBuilder.append:"(Ljava/lang/String;)Ljava/lang/StringBuilder;";
new     class java/lang/String;
dup;
ldc     String "a";
invokespecial   Method java/lang/String."<init>":"(Ljava/lang/String;)V";
invokevirtual   Method java/lang/StringBuilder.append:"(Ljava/lang/String;)Ljava/lang/StringBuilder;";
invokevirtual   Method java/lang/StringBuilder.toString:"()Ljava/lang/String;";
astore_2;
getstatic       Field java/lang/System.out:"Ljava/io/PrintStream;";
aload_1;
aload_2;
if_acmpne       L43;

结果就是s2以StringBuilder的方式最后调用toString方法保存了下来那么toString方法有是做了什么呢

public synchronized String toString() {
        if (toStringCache == null) {
            toStringCache = Arrays.copyOfRange(value, 0, count);
        }
        return new String(toStringCache, true);
}

最后是return了一个new String 那么和第二个字符串比较的例子是相同的了。
总结一下:
如果是基本类型会直接进行比较。
封装基础类型的引用类型和基本类型比较会把指向的基本类型拿出来进行比较。
引用类型就是把引用类型的引用拿来比较。
String也是引用类型,所以也是把引用拿来比较。
不同的类型进行比较时用到的比较指令是不同的;如int用if_icmpne而引用类型用if_acmpne不过这对外在的结果并不太重要。

上一篇下一篇

猜你喜欢

热点阅读