Java面试题2——==和equals

一、==和equals的区别

  1. ==可以用来比较基本类型和引用类型,分别判断内容和内存地址

  2. equals只能用来比较引用类型,它只判断内容。该函数存在于 java.lang.Object

java中的数据类型,可分为两类:
1.基本数据类型,也称原始数据类型。如byte,short,char,int,long,float,double,boolean 等,他们之间的比较,应用双等号比较。不能使用equals比较。
2.复合数据类型(类)
当他们用(==)进行比较的时候,比较的是他们在内存中的存放地址,
所以,除非是同一个new出来的对象,他们的比较后的结果为true,否则比较后结果为false。
当它们用equals进行比较时,也是比较对象的内存地址,所以equals方法的比较结果和双等号是相等的,但在一些类库当中这个方法被覆盖掉了。如很多不可变类String,Integer,Date中equals方法重写了,不再是比较类在堆内存中的存放地址了。而是两个数组的值是否相等。以下单独叙述String/Integer类的比较。

一、integer比较
注意Integer是int的包装类,其是一个对象,默认值为null(对象的默认值都是null)int类型默认值是0.
(1) “equals”比较
在equals(Object obj)方法中,会先判断参数中的对象obj是否是Integer同类型的对象,如果是则判断值是否相同,值相同则返回true,值不同则返回false。equals参数会默认为int类型。如果obj不是Integer类的对象,则返回false。
需要注意的是:当参数是基本类型int时,编译器会给int自动装箱成Integer类,然后再进行比较。这个比较方法适用于基本类型的包装类。

  1. 基本类型(值类型)之间无法使用equals比较。
  2. equals参数为值类型,则参数会进行自动装箱为包装类型。
  3. equals参数为包装类型,则先比较是否为同类型,非同类型直接返回false,同类型再比较值。

示例:
new Long(0).equals(0) 为 false,equals参数默认为int类型,装箱为Integer类型。类型不同所以返回false。
new Integer(500).equals(500) 为 true,equals参数默认为int类型,装箱为Integer类型,相同类型再比较值返回true。
new Integer(500).equals((byte)500) 为 false,equals参数为byte类型,装箱为Byte类型,不同类型直接返回false。
new Long(0).equals(0L) 为 true,equals参数为long类型,装箱为Long类型,相同类型再比较值返回true

(2)“==”比较

  1. 基本类型之间互相比较,比较值是否相等
  2. 一边是基本类型,一边是包装类型
    1. 同类型的进行比较,如Integer 与int,Long与long进行==比较时,会自动拆箱比较值
    2. 不同类型之间进行比较,则会自动拆箱,且会进行自动向上转型再比较值(低级向高级是隐式类型转换如:byte<short<int<long<float<double,高级向低级必须强制类型转换)
  3. 两边都是包装类型则直接比较引用地址,但是要注意IntegerCache除外。这是由于包装类存在高频区间(-128-127)的数据缓存现象,高频区域的数值会直接使用已有对象,非高频区域的数值会重新 new 一个新的对象。所以如果比较的两个值的大小相等,且又在高频区间,那么用双等号判断时结果就为true.看以下例子。
    public class Test03 {
    public static void main(String[] args) {
    Integer f1 = 100, f2 = 100, f3 = 150, f4 = 150;
    System. out.println( f1 == f2); //true
    System. out.println( f3 == f4); //false
    }
    }
    以下是IntegerCache的原理:
    当我们给一个 Integer 赋予一个 int 类型的时候会调用 Integer 的静态方法 valueOf。
    Integer f1 = Integer.valueOf(100);
    Integer f2 = Integer.valueOf(100);
    Integer f3 = Integer.valueOf(150);
    Integer f4 = Integer.valueOf(150);
    思考:那么 Integer.valueOf()返回的 Integer 是不是是重新 new Integer (num)来创建的呢?如果是这样的话,那么 == 比较返回都是 false,因为他们引用的堆地址不一样。
    具体来看看 Integer.valueOf 的源码
    public static Integer valueOf(int i) {
    //范围-128到127
    if (i >= IntegerCache.low && i <= IntegerCache.high)
    return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
    }
    从上面我们可以知道给 Interger 赋予的 int 数值在 - 128 - 127 的时候,直接从 cache 中获取,这些 cache 引用地址是不变的,但是不在这个范围内的数字,则 重新创建一个对象,所以用双等号比较后结果为FALSE。

让我们来看一下包装类及其高频区间

包装类高频区间
Byte-128~127
Short-128~127
Character0~127
Integer-128~127
Long-128~127
Boolean使用final定义,值不能被修改,会复用

float和double没有高频区间。Integer 是唯一一个可以修改缓存范围的包装类,在 VM optons 加入参数:
-XX:AutoBoxCacheMax=666 即修改缓存最大值为 666 。

二、string的比较
先来看一下基础知识。
在 java 中有一个 “字符数据池” 的内存管理机制。
String str=“abc”,执行这句话时,会先去 “字符数据池” 搜索是否有 “abc” 这个字符串,如果有,则直接将字符串的首地址赋值给 str,如果没有,在字符数据池中生成一个。
String str=new String (“abc”),执行这句话时,不管字符常量池是否有该字符串,都会先在堆里面生成一个对象,将对象的引用地址赋给str,同时,会去查看字符常量池中是否存在该字符串,如果存在就不处理,如果不存在,还会在字符常量池中生成一个相同的字符串。
由以上分析可知,String str=“abc” 效率要高于 String str=new String (“abc”),因为如果有重复的字符串时,第一种方式可以节省空间。

下面举例说明一下:
public class Test{
public static void main(String args[]){
String s1=new String (“abc”);
String s2=new String (“abc”);
String s3=“abc”;
String s4=“abc”;//
System.out.println(s1== s2);
System.out.println(s1== s3);
System.out.println(s2== s3);
System.out.println(s3==s4);
}
}

仔细分析一下,结果是什么呢?
1.s1和s2比较,肯定不相等,因为都在堆中开辟了内存空间,地址肯定不同。
2.s1和s3比较,s1保存的是abc在堆中的内存地址,s3保存的是字符串在常量池中的首地址,是不同的。
3.s2和s3比较,s2保存的是新开辟的内存空间地址,而s3保存的是字符串在常量池中的首地址,是不同的。
4.s3和s4比较,保存的都是字符串在常量池中的首地址,是相同的。
所以答案为:false、fasle、false、true

总结并且补充几点:
1.String 对象的不可变性
一旦一个String对象在内存中创建,它将是不可改变的,所有的String类中方法并不是改变String对象自己,而是重新创建一个新的String对象。并把这个对象的引用赋值给了原变量。但之前的变量仍然存在
2." " 引号创建的字符串是String对象,保存在常量池中。
3.new 创建字符串时首先查看池中是否有相同值的字符串,如果有,则拷贝一份到堆中,然后返回堆中的地址;如果池中没有,则在池中和堆中各创建一份,然后返回堆中的地址。
4.几个例子
(1)String s1 = new String (“abc”); 这句话创建了几个字符串对象?
答:将创建 1 或 2 个字符串。如果池中已存在字符串文字 “abc”,则堆中只会创建一个字符串 “s1”。如果池中没有字符串文字 “abc”,那么它将首先在池中创建,然后在堆空间中创建,因此将创建总共 2 个字符串对象。
(2)String s = “aaa” + new String(“bbb”);创造了几个对象?ps: 字符串池中都没有这2个字符串
答: 4 个。
“aaa” 、new Sring () 、“bbb” 、s
字符串相加时,会先在常量区添加两个字符串变量,然后在堆内存中产生一个对象,接收拼接值,而之前的两个常量会等待垃圾回收。
(3)
String s = “aaa”+new String(“bbb”);
String s1 = “aaabbb”;
System.out.println(s == s1);
System.out.println(s1.equals(s));
前者为false,因为比较的是内存地址。后者为true,比较的是是值,值相等。
(4)
String str1=”java”; // 指向字符串池的地址
String str2=”blog”; // 指向字符串池的地址
String s=str1+str2; //+ 运算符会从字符串池中复制这两个值,然后在堆中创建再建立对象 s, 然后将对象s的堆地址赋给 s.所以s指向的是堆中的地址。这条语句创建了3个对象。
System.out.println (s==“Javablog”);
// 堆对象和常量池对象比较内存地址,结果为false。Jvm 确实对型如 String str1=”java”的 String 对象放在常量池里,但是它是在编译时那么做的,而 String s=str1+str2; 是在运行时刻才能知道,也就是说 str1+str2 是在堆里创建的,所以结果为 false 了。
如果改成以下两种方式:
String s=“java” + “blog”;
// s指向常量池中值为Javablog的对象,+直接将 “javablog” 放入字符串池中,System.out.println (s==”javablog”)的结果为 true
String s=str1+ “blog”;
// 不放入字符串池,而是在堆中分配,因为str1对象的值不确定.
System.out.println (s==”javablog”)的结果为 False

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值