Java比较问题详细分析
作者:DemonFS 发布时间:2023-11-20 14:30:48
Java中的比较问题是一个很基础又很容易混淆的问题。今天就几个容易出错的点作一个比较详细的归纳与整理,希望对大家的学习与面试有帮助。
一、==与equals()的区别
首先,我们需要知道==与equals()的区别,==号比较的一直是地址值,对于基本数据类型来说,==比较实际上就是变量数值是否相等,而对于引用数据类型,比较的则是地址值。这里特别需要注意的是String类型,很容易想当然的使用==,很容易出错。equals()方法是Object类里的方法,我们知道Java中一切类都会默认继承Object类,所以类对象都会有equals()方法。Object类中equals()方法如下图所示:
由源码可以看出,Object类里的equals()方法底层也是用的==,所以它比较的其实也是地址值。所以如果想用equals()方法去作其他比较,我们需要重写equals()方法。
二、基本数据类型及其包装类
我们都知道,byte、short、int、long、boolean、char、double、float这八个是基本数据类型,它们声明的变量存放在栈内存中。而它们对应的包装类型(Byte、Short、Integer、Long、Boolean、Character、Double)定义的变量则存在于堆内存中。对于基本数据类型,它们的比较相对而言较为简单,即判断是否相等用==,比较大小用<、>、<=、>=即可。而对于包装类型,却有些不同。
首先对于判断是否相等,看如下代码的执行结果:
package dailytest;
import org.junit.Test;
/**
* Java中的比较总结
* @author yrr
*/
public class JavaCompareTest {
/**
* Integer类型判断是否相等
*/
@Test
public void test01() {
int n3 = 48;
System.out.println("--------使用new对象时,当值在[-127,128]之间时---------");
Integer n7 = new Integer(48);
Integer n8 = new Integer(48);
System.out.println(n7 == n8); //false
System.out.println(n7 == n3); //true
System.out.println("--------直接赋值方式,当值在[-128,127]之间时---------");
Integer n1 = 48;
Integer n2 = 48;
System.out.println(n3 == n1); //true
System.out.println(n1 == n2); //true
System.out.println(n1.equals(n2)); //true
System.out.println(n1.equals(n3)); //true
System.out.println(n1.intValue() == n2.intValue()); //true
System.out.println("--------直接赋值方式,当值不在[-127,128]之间时---------");
Integer n4 = 128;
Integer n5 = 128;
int n6 = 128;
System.out.println(n4 == n5); //false
System.out.println(n4 == n6); //true
System.out.println(n4.equals(n5)); //true
System.out.println(n4.equals(n6)); //true
System.out.println(n4.intValue() == n5.intValue()); //true
//使用Integer.intValue()方法时需要注意验证是否为null,防止出现NullPointException
}
/**
* Long类型判断是否相等
*/
@Test
public void test02() {
//这里需要注意,使用long定义时,不需要加L或者l,而使用Long时必须加,否则会报错
//建设都加上,以示区别
long n3 = 48L;
System.out.println("--------使用new对象时,当值在[-127,128]之间时---------");
Long n7 = new Long(48);
Long n8 = new Long(48);
System.out.println(n7 == n8); //false
System.out.println(n7 == n3); //true
System.out.println("--------直接赋值方式,当值在[-127,128]之间时---------");
Long n1 = 48L;
Long n2 = 48L;
System.out.println(n3 == n1); //true
System.out.println(n1 == n2); //true
System.out.println(n1.equals(n2)); //true
System.out.println(n1.equals(n3)); //true
System.out.println(n1.intValue() == n2.intValue()); //true
System.out.println("--------直接赋值方式,当值不在[-127,128]之间时---------");
Long n4 = 128L;
Long n5 = 128L;
long n6 = 128;
System.out.println(n4 == n5); //false
System.out.println(n4 == n6); //true
System.out.println(n4.equals(n5)); //true
System.out.println(n4.equals(n6)); //true
System.out.println(n4.intValue() == n5.intValue()); //true
//使用Long.intValue()方法时需要注意验证是否为null,防止出现NullPointException
}
}
针对上面的执行结果,作如下说明:
首先,对于new方法来声明一个Integer或者Long对象,因为new对象都是在堆里开辟一块空间,所以即便两者的数值相同,但对于==来说,比较的是地址值,所以会返回false。 对于基本数据类型的包装类,都重写了equals()方法,会比较数值大小,所以用equals()方法是可以根据数值大小进行判断的。 对于Integer变量与int变量比较的问题,会发现也是基于数值大小得出来的比较值,这是因为在比较时,Integer类型做了自动拆箱,转成了int类型。 前三点的解释,对所有包装类型都是适用的 对于直接赋值方式,值为48的两个Integer变量,用==号判断是true,而当值为128后,却为false。这是因为在底层,对于Integer n1 = 48;这种直接赋值的方式,其实调用了Integer.value()方法。我们可以简单看一下Integer.value()方法的源码,如下图所示:
我们可以看到,这里有个if判断,当输入的i在[-128,127]的范围内时,直接从IntegerCache数组中返回了。所以,对于在这个范围内的数值,返回的都是这个数组对应的地址值,因此用==号判断会返回true。而不在这个范围内的,是new出的对象,因此会返回false。这个结论对于Byte、Short、Integer、Long类型都成立(感兴趣的可以去看下它们对应的value()方法的源码),因为Byte类型的范围就是[-128,127],所以对于Byte类型来说,使用==与equals()没有区别。
而对于大小比较,使用>、<、<=、>=是没有问题的,它们会进行自动拆箱。但是我们通常建议使用以下两种方式来进行大小比较:
调用xxxValue()方法转成基本数据类型进行比较 使用compareTo()方法进行比较,在包装类中,都重写了compareTo()方法。查看compareTo()源码,可以看出,其实它底层使用的也是通过自动拆箱转成了对应的基本数据类型再进行比较的。
二、Java对象的比较
有了上面的介绍之后,对象的比较就比较容易了。原理都是一样的。
1. String类型的比较
需要注意的是,String类型不能直接使用>、<=、>=、<,会报编译异常。
package dailytest;
import org.junit.Test;
/**
* Java中的比较总结
* @author yrr
*/
public class JavaCompareTest {
@Test
public void test03() {
String s1 = new String("123");
String s2 = new String("123");
System.out.println(s1 == s2); //false
System.out.println(s1.equals(s2));
String s3 = "234";
String s4 = "234";
System.out.println(s3 == s4); //true
System.out.println(s3.equals(s4)); //true
//System.out.println(s1 <= s3); //The operator < is undefined for the argument type(s) java.lang.String, java.lang.String
System.out.println(s1.compareTo(s3) < 0); //true
}
}
2. 类对象的比较
类对象比较结论也是一样的,但是相对于基本数据类型和String类型,较为复杂一点。
根据某一规则,判断两个对象是否相等,需要在被判断类中重写equals()方法,示例代码如下:
package dailytest;
import org.junit.Test;
/**
* Java中的比较总结
* @author yrr
*/
public class JavaCompareTest {
@Test
public void test04() {
Person p1 = new Person("yrr",18);
Person p2 = new Person("yrr",18);
System.out.println(p1 == p2); //false
System.out.println(p2.equals(p1)); //true
}
}
class Person{
private String name;
private Integer age;
public Person() {
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public Integer getAge() {
return age;
}
@Override
public boolean equals(Object obj) {
Person person = (Person) obj;
return name.equals(person.getName()) && age.equals(person.getAge());
}
}
而如果要比较两个对象的大小(这也是常会问到的面试题),有两种方式:
被比较类实现Comparable接口,并重写compareTo()方法 自己定义实现了一个Comparator接口的类或者利用内部类,重写compare()方法 两者的区别:前者定义在被比较类上,而后者定义在被比较类外。通过这种区别,两者的优缺点也很明显,前者简单,但需要对被比较类进行修改,而后者则不需要修改原代码,更加灵活。
第一种方式,示例代码如下:
package dailytest;
import org.junit.Test;
/**
* Java中的比较总结
* @author yrr
*/
public class JavaCompareTest {
@Test
public void test5() {
Person p1 = new Person("yrr",18);
Person p2 = new Person("wx",19);
System.out.println(p1.compareTo(p2) < 0);
}
}
class Person implements Comparable<Person>{
private String name;
private Integer age;
public Person() {
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public Integer getAge() {
return age;
}
@Override
public int compareTo(Person o) {
return this.getAge() - o.getAge();
}
}
第二种方式,示例代码如下:
package comparator;
import java.util.Arrays;
import java.util.Comparator;
public class MyComparator {
public static void main(String[] args) {
User[] users = new User[] { new User("u1001", 25),
new User("u1002", 20), new User("u1003", 21) };
Arrays.sort(users, new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
return o1.getAge() - o2.getAge();
}
});
for (int i = 0; i < users.length; i++) {
User user = users[i];
System.out.println(user.getId() + " " + user.getAge());
}
}
}
class User {
private String id;
private int age;
public User(String id, int age) {
this.id = id;
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
来源:https://my.oschina.net/demonfs/blog/1580084


猜你喜欢
- 本文实例讲述了Android编程开发中的正则匹配操作。分享给大家供大家参考,具体如下:在Android开发中,可能也会遇到一下输入框的合法性
- 前言:我们都知道Android开发者的必备工具:AndroidStudio。是开发人员用来编译、测试的专用工具。今天在使用And
- equals函数在基类object中已经定义,源码如下 public boolean equals(Object obj) { return
- 一、说明 添加视图文件的时候有两种方式:1、通过在xml文件定义layout;2、java代码编写二、前言说明1.构造xml文件2.Layo
- Android自定义SwipeLayout仿QQ侧滑条目,供大家参考,具体内容如下先看动图 看布局文件activity_main.xml&l
- 1.简介其实这个效果几天之前就写了,但是一直没有更新博客,本来想着把芝麻分雷达图也做好再发博客的,然后今天看到鸿洋的微信公众号有朋友发了芝麻
- 【题目】 汉诺塔问题比较经典,这里修改一下游戏规则:现在限制不能从最左侧的塔直接移动到最右侧,也不能从最右侧直接移动到最左侧,而是必须经过中
- 前言在实际生活中,地图是我们经常使用的一种工具,通常我们会用它进行导航,输入一个出发城市,输入一个目的地城市,就可以把路线规划好,而在规划好
- 一、什么是单例模式?单例设计模式(Singleton Design Pattern)理解起来非常简单。一个类只允许创建一个对象(或者实例),
- Netty设置为Https访问SSLContextFactorypublic class SSLContextFactory {
- /// <summary> /// 队列多线程,T 代表处理的单个类型~&nbs
- 1.泛型概念泛型就是将类型参数化所谓类型参数化就是将类型定义成参数的形式,然后在使用此类型的时候的时候再传入具体的类型到这我们可以看出来:泛
- Java goto语句妙用今天和朋友聊天的时候,无意间聊到了 goto 语句,但是在 Java 中, goto 是保留关键字,但是朋友说 J
- 本文实例为大家分享了Java打印指定年月日历的具体代码,供大家参考,具体内容如下日历如下:程序如下://打印指定年月的日历public cl
- import android.app.ListActivity; import android.database.Cursor; impor
- 序列化与反序列化Java对象是有生命周期的,当生命周期结束它就会被回收,但是可以通过将其转换为字节序列永久保存下来或者通过网络传输给另一方。
- 本文实例讲述了Java Swing组件编程之JTable表格用法。分享给大家供大家参考,具体如下:表格是GUI编程中使用较多,但也是最麻烦的
- 刚开始用的Mouse_up,虽然能捕获事件,但是没有KeyPress事件的Handled属性。发现一个相对简单的方法。1.先让窗体类继承IM
- 前言本文主要给大家介绍了关于JDK8新增的原子性操作类LongAdder的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的
- 引言垃圾收集技术并不是Java语言首创的,1960年诞生于MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言。垃圾收集技术需要