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 }
}

  针对上面的执行结果,作如下说明:

  1. 首先,对于new方法来声明一个Integer或者Long对象,因为new对象都是在堆里开辟一块空间,所以即便两者的数值相同,但对于==来说,比较的是地址值,所以会返回false。
  2. 对于基本数据类型的包装类,都重写了equals()方法,会比较数值大小,所以用equals()方法是可以根据数值大小进行判断的。
  3. 对于Integer变量与int变量比较的问题,会发现也是基于数值大小得出来的比较值,这是因为在比较时,Integer类型做了自动拆箱,转成了int类型。
  4. 前三点的解释,对所有包装类型都是适用的
  5. 对于直接赋值方式,值为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;
}
}

  

 

Java中的比较总结的更多相关文章

  1. java中的锁

    java中有哪些锁 这个问题在我看了一遍<java并发编程>后尽然无法回答,说明自己对于锁的概念了解的不够.于是再次翻看了一下书里的内容,突然有点打开脑门的感觉.看来确实是要学习的最好方式 ...

  2. java中的字符串相关知识整理

    字符串为什么这么重要 写了多年java的开发应该对String不陌生,但是我却越发觉得它陌生.每学一门编程语言就会与字符串这个关键词打不少交道.看来它真的很重要. 字符串就是一系列的字符组合的串,如果 ...

  3. Java中的Socket的用法

                                   Java中的Socket的用法 Java中的Socket分为普通的Socket和NioSocket. 普通Socket的用法 Java中的 ...

  4. java中Action层、Service层和Dao层的功能区分

    Action/Service/DAO简介: Action是管理业务(Service)调度和管理跳转的. Service是管理具体的功能的. Action只负责管理,而Service负责实施. DAO只 ...

  5. Java中常用集合操作

    一.Map 名值对存储的. 常用派生类HashMap类 添加: put(key,value)往集合里添加数据 删除: clear()删除所有 remove(key)清除单个,根据k来找 获取: siz ...

  6. java中的移位运算符:<<,>>,>>>总结

    java中有三种移位运算符 <<      :     左移运算符,num << 1,相当于num乘以2 >>      :     右移运算符,num >& ...

  7. 关于Java中进程和线程的详解

    一.进程:是程序的一次动态执行,它对应着从代码加载,执行至执行完毕的一个完整的过程,是一个动态的实体,它有自己的生命 周期.它因创建而产生,因调度而运行,因等待资源或事件而被处于等待状态,因完成任务而 ...

  8. Java中的进程和线程

     Java中的进程与线程 一:进程与线程 概述:几乎任何的操作系统都支持运行多个任务,通常一个任务就是一个程序,而一个程序就是一个进程.当一个进程运行时,内部可能包括多个顺序执行流,每个顺序执行流就是 ...

  9. Java中的进程与线程(总结篇)

    详细文档: Java中的进程与线程.rar 474KB 1/7/2017 6:21:15 PM 概述: 几乎任何的操作系统都支持运行多个任务,通常一个任务就是一个程序,而一个程序就是一个进程.当一个进 ...

  10. 初探java中this的用法

    一般this在各类语言中都表示“调用当前函数的对象”,java中也存在这种用法: public class Leaf { int i = 0; Leaf increment(){ i++; retur ...

随机推荐

  1. awk内置函数

    gsub(r,s,t) 在字符串t中,用字符串s替换和正则表达式r匹配的所有字符串.返回替换的个数.如果没有给出t,缺省为$0 index(s,t) 返回s 中字符串t 的位置,不出现时为0 leng ...

  2. Centos6.8 安装tomcat8.5.11

    1.下载 安装包 wget http://mirrors.aliyun.com/apache/tomcat/tomcat-8/v8.5.11/bin/apache-tomcat-8.5.11.tar. ...

  3. 万能日志数据收集器 Fluentd - 每天5分钟玩转 Docker 容器技术(91)

    前面的 ELK 中我们是用 Filebeat 收集 Docker 容器的日志,利用的是 Docker 默认的 logging driver json-file,本节我们将使用 fluentd 来收集容 ...

  4. 第三方软件内嵌IE出现纵向滚动条消失的BUG,奇葩的IE BUG 真是无奇不有

    混了这么久 竟然还有这样难以解决的BUG,最后都跑到英文的MSDN上提问了,因为谷歌都谷不出朕的忧伤了,有木有. 提问原文如下:https://social.msdn.microsoft.com/Fo ...

  5. RE:考勤系统的复盘

     一大早看了 <美团旅行前端技术体系的思考与实践> 这篇文,恰巧又在昨天完成了一个项目.确实让我忍不住码篇总结,为自己做一个复盘. 历时两个月,考勤系统 这个项目总算能够称得上完成了.项目 ...

  6. Container With Most Water 容器最大水容量

    描述 Given n non-negative integers a1, a2, ..., an, where each represents a point at coordinate (i, ai ...

  7. 浅谈如何使用swfupload工具与struts2无缝相接

    笔者在网上查找流行的上传组件,swfupload引入眼帘,受到JavaEye的一篇文章启发,历时三天,加以研究,现将心得奉上,献礼JavaEye. 由于笔者才疏学浅,经验匮乏,介绍不深入,仅供菜鸟参考 ...

  8. 堆排序HeapSort

    堆排序,顾名思义,是采用数据结构堆来进行排序的一种排序算法. 研究没有规律的堆,没有任何意义.特殊的堆有最大堆(父节点值大于等于左右字节点值),最小堆(父节点值小于等于子节点值).一般采用最大堆来进行 ...

  9. [转载] Bitmap的秘密

    转载自http://www.infoq.com/cn/articles/the-secret-of-bitmap/ 之前已经参加过几次QCon峰会,不过今年QCon 2014 上海峰会对我来说比较特别 ...

  10. 如何添加title左侧的图标

    在titile标签上方插入以下代码,图片要求格式必须是.ico. <link rel="shortcut icon" href="images/favicon.ic ...