疯狂java


您现在的位置: 疯狂软件 >> 新闻资讯 >> 正文

Java基础面试题


 

   

  这是一道经典的面试题,在java面试中出现频率极高,网络上流传的答案多半是不严谨的或者根本就是错误的。

  一个典型的错误答案是,”==”比较的是地址,”equals()”比较的是内容。

  class Point {

  private int x;

  private int y;

  public Point(int x, int y) {

  this.x = x;

  this.y = y;

  }

  }

  public class EqualsTest {

  public static void main(String[] args) {

  Point p1 = new Point(1, 2);

  Point p2 = new Point(1, 2);

  System.out.println(p1.equals(p2));

  }

  }

  按照上面的理论上面代码输出结果为 true,但是真实的结果却是false。这到底是为什么呢?

  首先我们来分析一下这个 “equals” 方法,该方法是java.lang.Object 类的方法。因为 java 中的 class 都隐含继承自 Object类, 所以别的类也都包含该方法,由于我们在上面的 Point 类中并没有对 “equals” 方法进行重写,于是其表现行为是由其父类即 Object 决定的。

  下面我们来看看objcet类中的equals方法到底是个什么样子

  public boolean equals(Object obj) {

  return (this == obj);

  }

  看到没有,如果我们在子类中不对equals方法进行重写,它的默认行为是和“==”是一样的。比较的都是该引用是否真的指向物理内存中的同一个对象。因为p1,p2是指向不同的对象,这就解释了为什么上面的代码会输出false。

  为什么那么多的程序员会认为“equals”比较的是内容呢?根据我的观察,绝大多数初学者是被“String”类给误导了。

  因为:

  String s1 = new String("ustc");

  String s2 = new String("ustc");

  System.out.println(s1.equals(s2));

  输出结果为true。

  为了解释上面的代码的输出结果,我们得看一下String类的equals方法是怎么实现的。

  public boolean equals(Object anObject) {

  if (this == anObject) {

  return true;

  }

  if (anObject instanceof String) {

  String anotherString = (String) anObject;

  int n = value.length;

  if (n == anotherString.value.length) {

  char v1[] = value;

  char v2[] = anotherString.value;

  int i = 0;

  while (n-- != 0) {

  if (v1[i] != v2[i])

  return false;

  i++;

  }

  return true;

  }

  }

  return false;

  }

  我们不妨细致的看一下上面的代码,你会发现正是由于 String 类重写了父类 Object 中的“equals”方法才让其具备了:String 类的equals方法比较的是内容这一行为。 也正式这样误导了很多的初学者。

  现在让我们回到刚才那个 Point 的例子,我们只需要重写 equals方法,就可以得到我们期望的“相等性”。

  修改后的代码如下:

  class Point {

  private int x;

  private int y;

  public Point(int x, int y) {

  this.x = x;

  this.y = y;

  }

  @Override

  public boolean equals(Object that) {

  if (this == that)

  return true;

  if (that instanceof Point) {

  Point p = (Point)that;

  return p.x == this.x && p.y == this.y;

  }

  return false;

  }

  }

  public class EqualsTest {

  public static void main(String[] args) {

  Point p1 = new Point(1, 2);

  Point p2 = new Point(1, 2);

  System.out.println(p1.equals(p2));

  }

  }

  这样一来,输出结果即是true了。

  到目前为止,我们对equals做了不少的描述,相信大家也有了个整体的认识。在java中 equals() 方法实现的是一种“逻辑上的相等性”,这个可以是由我们程序员来控制的。 比如在现实世界中经常說,这对双胞胎姐妹长得一模一样。身高、发型、脸蛋都长的一样,其实这里我们就是用equals来进行“相等性”的判断。但是其父母还是可以很容易的分辨她们的,她们确实不是同一个人,她们的身份证是不一样的。现实生活中的唯一标识,这里隐含着她们父母用的是“==”来判断。

  其实,我们还可以非常变态的重写“equals”方法。(这种写法是错误的,为了让大家印象深刻,笔者故意写了如下的版本)

  class Point {

  private int x;

  private int y;

  public Point(int x, int y) {

  this.x = x;

  this.y = y;

  }

  @Override

  public boolean equals(Object that) {

  return false;

  }

  }

  public class EqualsTest {

  public static void main(String[] args) {

  Point p1 = new Point(1, 2);

  System.out.println(p1 == p1);

  System.out.println(p1.equals(p1));

  }

  }

  p1 == p1 // true

  p1.equals(p1) // false

  明明是同一个对象,用equals方法判断竟然是false。

  现在大家应该很明白了吧,equals的控制权完全由你决定。

  但是重写 equals 方法还是要遵循一定的准则的,不能像我们上面那种极端的做法。

  下面是冲JDK帮助文档中摘下来的片段,也正是重写equals方法所要遵循的准则。

  equals 方法实现了等价关系:

  自反性,对任何非空引用x, x.equals(x) 应该返回 true。

  对称性,对任何非空引用x,y,x.equals(y) 应该返回 true,当且仅当 y.equals(x)返回true。

  传递性,对任何非空引用x,y,z,如果x.equals(y)为true而且y.equals(z)为true,则x.equals(z)为true。

  一致性,对任何非空引用x,y,对x.equals(y)多次调用的结果应该相同,如果返回true,每次调用结果都为true,为false,则都为false。

  非空性,对任何非空引用x,x.equals(null)始终返回false。

  上面的版本,违反了上述多种准则。

  结论:

  在 java 中对于原生数据类型,比如 int, boolean 之类的,“==”就是和我们日常在算术中的等于号类似。

  对于引用类型,“==”用来判断该引用是否真的指向内存中的同一个对象。

  equals 方法是 Object 类中的方法,程序员通过重写该方法从而实现期望的相等性,完全由程序员控制,但是要遵循上面的五个准则。

  一言以蔽之:“==”用来判断物理意义上的相等性;“equals”用来判断逻辑上的相等性。

  进一步阅读:

  《Effective java》条目:重写equals()时,总要重写hashcode()方法。

  最后给出一道题来考察下大家的学习效果:

  下面代码的输出结果是什么,true or false?

  public class Person {

  private String name;

  private int age;

  public Person(String name, int age) {

  this.name = name;

  this.age = age;

  }

  public boolean equals(Object o) {

  if (this == o)

  return true;

  if (o instanceof Person) {

  Person p = (Person)o;

  return this.name == p.name && this.age == p.age;

  }

  return false;

  }

  public static void main(String[] args) {

  String name1 = new String("a");

  String name2 = new String("a");

  Person p1 = new Person(name1, 1);

  Person p2 = new Person(name2, 1);

  System.out.println(p1.equals(p2));

  }

  }