Java 學習記錄70 — Sets & HashSet — 2/5

張小雄
6 min readJun 28, 2021

--

Rishabh

what does “this” in the below code actually mean?

(this == obj)

Ulrich

It compares the memory addresses for both the object passed as parameter and the one you call equals from. If they are in the same memory address, they are obviously the same object.

Travis

Hi Tim,

I was running a quick test to make sure I understand the .equals() method and the == operator, but I’m confused by my result.

If I have two strings with the same value but assigned to different variables, I would expect comparing with == to return false since diff objects and comparing with .equals() to return true since same value, but both seem to return true.

Am I missing something? Thanks!

Example:

String test = "Test";String test2 = "Test";test == test2; // returns true unexpectedlytest.equals(test2); // returns true as expected

Tim

The == really has to do with the way Java is creating the constants.

Consider this code

String obj1 = new String("xyz");String obj2 = new String("xyz");if (obj1 == obj2)
System.out.println("obj1==obj2 is TRUE");
else
System.out.println("obj1==obj2 is FALSE");

If you run it, you will see it returns false.

We used new here to be sure a new String was created.

When you just assigned test the value of “Test” and then test2, the Java compiler decided to use only one copy of memory for “Test”

And this == returned true. == is meant to only return true if the object that are pointing to in memory are equal.

Add this code below the above code.

obj2 = obj1;
if(obj1 == obj2)
System.out.println("obj1==obj2 is TRUE");
else
System.out.println("obj1==obj2 is FALSE");

Note how now we have got obj2 pointing to obj1 the value returned is true.

.equals, on the other hands, does not look at memory locations, it looks at what the contents of the strings are and returns true or false if they are equal.

HeavenlyBody.java 改

import java.util.HashSet;
import java.util.Set;
public class HeavenlyBody {
private final String name;
private final double orbitalPeriod;
private final Set<HeavenlyBody> satellites;
public HeavenlyBody(String name, double orbitalPeriod) {
this.name = name;
this.orbitalPeriod = orbitalPeriod;
this.satellites = new HashSet<>();
} public String getName() {
return name;
}
public double getOrbitalPeriod() {
return orbitalPeriod;
}
public boolean addMoon(HeavenlyBody moon) {
return this.satellites.add(moon);
}
public Set<HeavenlyBody> getSatellites() {
return new HashSet<>(this.satellites);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
System.out.println("obj.getClass() is " + obj.getClass());
System.out.println("this.getClass() is " + this.getClass());
if ((obj == null) || obj.getClass() != this.getClass()) {
System.out.println("obj is null or not equal");
return false;
}
String objName = ((HeavenlyBody) obj).getName();
return this.name.equals(objName);
}
}

輸出結果:

Uranus: 30660.0

Venus: 225.0

Pluto: 248.0

Saturn: 10759.0

Mars: 1.3

Jupiter: 4332.0

Earth: 365.0

Mercury: 88.0

Neptune: 165.0

Pluto: 842.0

這次只改 equal() 的情況,還是有重複

因為 hashcode() 判斷為不同

equal() 的改法為

也跟預設的一樣

先判斷是否指向同個 object

新增的判斷有

若是不同類或者為 null 就直接 flase

判斷為同一類後,看要測哪個 fields

此案例中是要用名子判斷是否為相同

還有參數,因為是用 Object 類傳進來

所以還要轉一下 HeavenlyBody 確保有 getName()

最後取得 String 後,就用 String 裡的 equal() 來做判斷

Overriding equals and hashCode in Java — 039

Are two Java objects with same hashcodes not necessarily equal?

HeavenlyBody.java 改

import java.util.HashSet;
import java.util.Set;
public final class HeavenlyBody {
private final String name;
private final double orbitalPeriod;
private final Set<HeavenlyBody> satellites;
public HeavenlyBody(String name, double orbitalPeriod) {
this.name = name;
this.orbitalPeriod = orbitalPeriod;
this.satellites = new HashSet<>();
}
public String getName() {
return name;
}
public double getOrbitalPeriod() {
return orbitalPeriod;
}
public boolean addMoon(HeavenlyBody moon) {
return this.satellites.add(moon);
}
public Set<HeavenlyBody> getSatellites() {
return new HashSet<>(this.satellites);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
System.out.println("equals called");
System.out.println("obj.getClass() is " + obj.getClass());
System.out.println("this.getClass() is " + this.getClass());
if ((obj == null) || (obj.getClass() != this.getClass())) {
System.out.println("obj is null or not equal!");
return false;
}
String objName = ((HeavenlyBody) obj).getName();
return this.name.equals(objName);
}
@Override
public int hashCode() {
System.out.println("hashcode called");
return this.name.hashCode() + 57;
}
}

輸出結果:

hashcode called

hashcode called

hashcode called

hashcode called

hashcode called

hashcode called

hashcode called

hashcode called

hashcode called

hashcode called

hashcode called

hashcode called

hashcode called

hashcode called

hashcode called

hashcode called

hashcode called

equals called

obj.getClass() is class HeavenlyBody

this.getClass() is class HeavenlyBody

Uranus: 30660.0

Mercury: 88.0

Pluto: 248.0

Earth: 365.0

Jupiter: 4332.0

Mars: 1.3

Neptune: 165.0

Saturn: 10759.0

Venus: 225.0

在 MainHeavenlyBody 沒有特別 call equal()hashcode()

看輸出卻還是有被 call 的原因是

hashset 的去重複就是調用最上層 Object 裡的此二功能

簡單的說

先對 object 用 hashcode 得到一個數字

數字相同的放在同一個籃子

這樣就會有好幾個不同數字的籃子

要去重複的話

接著用 equal 檢視籃子

現在要放進去的 object 跟籃子裡面的 object

用 equal 測試看看

如果返回 true 代表是一樣的

就不會放進去了,因此完成了去重複

Working With hashcode() and equals()

Douglas

A collision is when two different objects inadvertently return the same hash. HeavenlyBody was using the hashcode returned from the instance variable “name”. Now, the longer the name is, the less likely of a collision. If two DIFFERENT strings just so happen to equate to the same hashcode (which is an integer), then adding 57 will just take that integer and add 57 to both, resulting in (again) the same number.
The only advantage of adding 57 here is to distinguish between a HeavenlyBody object and a String. Without using 57, if a HeavenlyBody object was inadvertently compared to a String which contained the same value as the instance variable “name” from HeavenlyBody, then yes, adding 57 would make them different.

However, I don’t think this is necessary, so long as the programmer doesn’t make this mistake, which wouldn’t even make sense. Like, what point is there in comparing a String to a user defined object here?

Actually, adding 57 here allows for a false-positive result. If the programmer accidentally compared a String to a non-String, it would put these two different objects in different buckets.

On the other hand, if they were in the same bucket, then there would actually be a chance to catch this mistake.

I think Tim, in this case, has done a pretty big disservice to beginners by doing something unnecessary AND not even thoroughly discussing why he did what he did. If he wanted to introduce a complex concept like this to beginners, he should have at LEAST said not to over-think this and that he will be discussing it greater depth in later chapters.

Simply saying that adding a number to a hash will result in lower collisions also doesn’t really help unless told WHY. Simply repeating a fact without giving an explanation comes off a little self-aggrandizing.

Sergi

The method equals() is defined in the Object class. It is used to compare values for equality and returns a boolean value (true or false). If you don’t override this method in your class, it compares if the references of the objects passed as arguments are equal or not. E.g., in the String class the equals() method has been override and in this case equals() compares the string with the object passed as parameter.

The method compareTo() is defined in the Comparable interface. If your class objects have a natural order you can implement the Comparable<T> interface and define this method. It is used by methods like Collections.sort() to compare the objects during the ordering process. That is, A.equals(B) compares if object A is equal (then return 0), greater than (then return a positive integer) or less than (then return a negative integer) object B. E.g., the class Integer implements Comparable and compareTo() method is defined because the integer has a natural order ( -n, …, -2, -1, 0, 1, 2, …, n). The returned values are useful to order the objects in a collection.

But what’s happens if your application needs to order a collection of objects in different ways. E.g., you have a class Person with two fields (name and age) but sometimes your application must order the objects by name and sometimes by age. Then you need to use the method compare(), defined in the Comparator interface. The typical use is define one or more classes that implements this (e.g. we can define two classes that implements Comparator, one that compare two Person objects using the age and other that compare two Person objects using the name). Once this comparators are defined, those can be passed to method such as sort() then the sort() method uses the comparator to compare and order the objects of the collection.

小練習:

When overriding the equals() method in the HeavenlyBody class, we

were careful to make sure that it would not return true if a HeavenlyBody

was compared to a subclass of itself.

We did that to demonstrate that method, but it was actually

unnecessary in the HeavenlyBody class.

The mini challenge is just a question: why was it unnecessary?

參考答案:

The HeavenlyBody class is declared final, so cannot be subclassed.

The Java String class is also final, which is why it can safely

use the instanceof method without having to worry about

comparisons with a subclass.

--

--

張小雄
張小雄

Written by 張小雄

記錄成為軟體工程師的過程

No responses yet