Trying to override the hashcode()

  • Thread starter STEMucator
  • Start date
In summary, the conversation discusses the process of overriding the hashcode(), equals(), and compareTo() methods in a Java class. The code for a SongEntry class is provided, and suggestions are given for improving the implementation of these methods. The conversation also mentions the use of hashmaps and hashsets, and the difficulty in understanding the Collection interface and its documentation. The objective of loading data from a text file is also mentioned.
  • #1
STEMucator
Homework Helper
2,076
140

Homework Statement



I'm trying to override the hashcode(), equals() and compareTo() methods in a class.

Homework Equations



Java.

The Attempt at a Solution



Here's the code :

Code:
package data_cleaner;

import java.util.*;

public class SongEntry implements Comparable<SongEntry> {

    private String studentNumber;
    private String bookCode;
    private int pageNumber;
    private String songTitle;

    public SongEntry(String studentNumber, String bookCode, int pageNumber, String songTitle) {
        this.studentNumber = studentNumber;
        this.bookCode = bookCode;
        this.pageNumber = pageNumber;
        this.songTitle = songTitle;
    }
    
    /*Getters and setters*/
    public String getSongTitle() { return songTitle; }
    public String getStudentNumber() { return studentNumber; }
    public String getBookCode() { return bookCode; }
    public int getPageNumber() { return pageNumber; }
    public void setSongTitle(String newSongTitle) { songTitle = newSongTitle; }

    @Override
    /*Returns a String representation of a SongEntry*/
    public String toString() {
        return (studentNumber + " " + bookCode + " " + pageNumber + " " + songTitle);
    }

    @Override
    /*Compares two songs to see if they are equal to each other by comparing song titles, book code and page numbers*/
    public boolean equals(Object obj) {
        if (!(obj instanceof SongEntry))
            return false;
        return(getBookCode().equals(((SongEntry)obj).getBookCode()) && getPageNumber() == ((SongEntry)obj).getPageNumber() && getSongTitle().equals( ((SongEntry)obj).getSongTitle()));
    }

    @Override
    /*Retrieve the name of the song and loop through the name adding on (i+1)*ASCIIVALUE of each char
    to the hashvalue which will be returned*/

    public int hashCode() {
    	int hashValue = 0;
    	for(int i=0;i<getSongTitle().length();i++)
    		hashValue += ((int)getSongTitle().charAt(i))*(Integer.parseInt(getStudentNumber()))*(getPageNumber());
    	return hashValue;
    }
    
    @Override
    /*Recall x.equals(y) iff x.hashcode() == y.hashcode(). Also, x.equals(y) iff x.compareTo(y) == 0.
    Hence x.compareTo(y) == 0 iff x.hashcode() - y.hashcode() == 0.
This implies we can subtract the hashcode of y from x to compare each songEntry and return it.*/
    public int compareTo(SongEntry aSongEntry) {
    	return (hashCode() - aSongEntry.hashCode());
    }
}

I don't believe the methods I wrote are correct? I believe there are rules which I've missed, but I cannot see where I've made a mistake?
 
Last edited by a moderator:
Physics news on Phys.org
  • #2


I didn't look in too much detail, but I propose you look for the documentation of .equals() and .hashCode() again. They can be a bit intimidating, but if you are writing such code it will be useful to read it thoroughly and understand it. And you can use it as a sort of checklist to make sure you satisfy the requirements.

Two of the flaws that I noticed first were that hashCode() can change during the lifetime of the object, and I think if you want to stick it in hashmaps that's not such a good idea. Since the student number, book number and page number have no setters, I would use one of those instead (or a combination of them, of course).

The second thing is that you wrote " x.equals(y) iff x.hashcode() == y.hashcode()". The "only if" is true (two equal objects have the same hash code) but the "if" is not. Two non-equal objects can perfectly well share a hash code, although for efficiency in storing data in hashmaps it is recommended that they are spread out as uniformly through the allowed range of hash codes as possible (though this is the least critical requirement, I think, because although it can degrade your maps performance to linear search of a single list, at least it it won't break them; since it is often hard to satisfy all requirements for a general type this is the most disregarded item.)
 
  • #3


CompuChip said:
I didn't look in too much detail, but I propose you look for the documentation of .equals() and .hashCode() again. They can be a bit intimidating, but if you are writing such code it will be useful to read it thoroughly and understand it. And you can use it as a sort of checklist to make sure you satisfy the requirements.

Two of the flaws that I noticed first were that hashCode() can change during the lifetime of the object, and I think if you want to stick it in hashmaps that's not such a good idea. Since the student number, book number and page number have no setters, I would use one of those instead (or a combination of them, of course).

The second thing is that you wrote " x.equals(y) iff x.hashcode() == y.hashcode()". The "only if" is true (two equal objects have the same hash code) but the "if" is not. Two non-equal objects can perfectly well share a hash code, although for efficiency in storing data in hashmaps it is recommended that they are spread out as uniformly through the allowed range of hash codes as possible (though this is the least critical requirement, I think, because although it can degrade your maps performance to linear search of a single list, at least it it won't break them; since it is often hard to satisfy all requirements for a general type this is the most disregarded item.)

I'm not using hashmaps, I'm using hashset.

The JAVA Api and examples are not clear at all about the Collection interface. I've read them 8 times at least.

I'll be more clear about my objective, perhaps it would make things easier to communicate.

I want to be able to load data from a text file ( which I've done using a BufferedReader ) which is in the format :

studentnumber,bookcode,page#,songname

Example : 10101010,AAAA,999,"Hello"
Example : 10000001,BBBB,000,That's, cool

line by line. Each line is one after the other in the text file so I can use readLine() to read it into a String or StringBuilder.

Then I want to be able to format the data ( The songname portion in particular since everything else doesn't matter ) and create a SongEntry object using the constructor.

I'll then store those SongEntry objects into my hashset and use PrintWriter to output this newly formatted data to a new text file.

Here's my code from my other class if it helps ( main at the bottom ) :

Code:
package data_cleaner;

import java.util.*;
import java.io.*;

public class DataCleaner {

    //student number to identify student
    private String studentNumber = "100804018";
    //set to hold song entries
    private HashSet<SongEntry> songDataSet = new HashSet<SongEntry>();
    
    //default constructor
    public DataCleaner() {
    }

    //Getters and setters
    public HashSet<SongEntry> getSongDataSet() { return songDataSet; }
    public String getStudentNumber() { return studentNumber; }
    
    //Load data, clean it and then put it into the hashset
    public void loadData(String dataFile) {
    	int numberOfSongsRead = 0;

    	try {
    		//Load the text file
	    	BufferedReader file = new BufferedReader(new FileReader(dataFile));
	    	
	    	//Read each line until EOF and clean it up
	    	while(file.ready()){
	    		StringBuilder songData = new StringBuilder((file.readLine()).trim());
	    		numberOfSongsRead++;
	    		boolean isThe = false;
	    		int counter = 0;
	    	
	    		//Eliminates unnecessary commas 
	    		for(int i=0;i<songData.length();i++) {
	    			if(songData.charAt(i) == ',' && counter < 3)
	    				counter++;
	    			else if(songData.charAt(i) == ',')
	    				songData.setCharAt(i, ' ');
	    		}

	    		//Split the newly formatted data for ease of manipulation and reset the string builder for usage
	    		String[] splitSongData = (songData.toString()).split(",");
    			splitSongData[3] = splitSongData[3].toLowerCase();
	    		songData = new StringBuilder();
	    		songData.append('"');
	    		
	    		//Loop through each song name. Clean it and put it inside of the hashset
	    		for(int i=0;i<splitSongData[3].length();i++) {

	    			//Check for the word 'the' in any form at the start of a song name.
	    			if(i<2)
	    				if(splitSongData[3].charAt(i) == 'T' || splitSongData[3].charAt(i) == 't')
	    					if( (i+1 < splitSongData[3].length()) && (splitSongData[3].charAt(i+1) == 'H' || splitSongData[3].charAt(i+1) == 'h') )
	    						if( ( i+2 < splitSongData[3].length() ) && ( splitSongData[3].charAt(i+2) == 'E' || splitSongData[3].charAt(i+2) == 'e' ) )
	    							isThe = true;
	    		
		    		//These next two ifs will handle capitalization at the start of a song name	
		    		if(i==0 && Character.isLowerCase(splitSongData[3].charAt(i))) {
		    			songData.append(Character.toUpperCase(splitSongData[3].charAt(i)));
		    			continue;
		    		}
		    			
		    		if(i<2 && Character.isLowerCase(splitSongData[3].charAt(i)))
		    			if(splitSongData[3].charAt(i-1) == '"' || splitSongData[3].charAt(i-1) == '(') {
		    				songData.append(Character.toUpperCase(splitSongData[3].charAt(i)));
		    				continue;
		    			}
		    		
		    		//These next two ifs will handle capitalization mid-name as well as default appending behaviour
		    		if(Character.isLowerCase(splitSongData[3].charAt(i)) && Character.isWhitespace(splitSongData[3].charAt(i-1))) {
		    			songData.append(Character.toUpperCase(splitSongData[3].charAt(i)));
		    			continue;
		    		}
		    			
		    		if(splitSongData[3].charAt(i) != '"')
		    			songData.append(splitSongData[3].charAt(i));
		    	}
		    		
	    		//Some minor touch ups as per formatting
		    	if(isThe)
		    		songData.append(", The\"");
		   		else
		   			songData.append('"');
	    		
		    	getSongDataSet().add(new SongEntry(splitSongData[0], splitSongData[1], (int)(Integer.parseInt(splitSongData[2])), songData.toString()));
	    	}	
    	}
    	
    	catch (FileNotFoundException e) {  
    		System.out.println("Error: Cannot find file");  
    	} 
    	catch (IOException e) {  
    		System.out.println("Error: Cannot read from file");  
    	}
    	
    	System.out.println(getSongDataSet().size());
    	//for(SongEntry s : getSongDataSet())
    		//System.out.println(s);
    	
    	System.out.println("A data file has been read! Ammount of songs read from data file : " + numberOfSongsRead);

    }

    public void cleanData() {
    	TreeSet<SongEntry> alphaTree = new TreeSet<SongEntry>();
    	alphaTree.addAll(getSongDataSet());
    	//for(SongEntry s : alphaTree)
    		//System.out.println(s);
    }

    public void exportData(String toFileName) {
    	try {
	    	PrintWriter	aFile = new PrintWriter(new FileWriter("outputdata.txt"));
	    	for(SongEntry s : getSongDataSet())
	    		aFile.println(s.toString());
	    	aFile.close();
    	}
    	catch (FileNotFoundException e) {  
    		System.out.println("Error: Cannot find file");  
    	} 
    	catch (IOException e) {  
    		System.out.println("Error: Cannot read from file");  
    	}
    }
    
    public void exportMyData(String toFileName) {
    	try {
	    	PrintWriter	aFile = new PrintWriter(new FileWriter("myoutputdata.txt"));
	    	for(SongEntry s : getSongDataSet()) {
	    		if(s.getStudentNumber().equals(studentNumber))
	    			aFile.println(s.toString());
	    	}
	    	aFile.close();
    	}
    	catch (FileNotFoundException e) {  
    		System.out.println("Error: Cannot find file");  
    	} 
    	catch (IOException e) {  
    		System.out.println("Error: Cannot read from file");  
    	}    
    }
    
    public void resetData() {
        //reset all gathered data
        songDataSet.clear();
    }

    public void runDataCleaner() {
        //Perform the steps of the data cleaner exercise
        System.out.println("Running Data Cleaner");

        //Step 1: Read master data into a set
        loadData("fakebookdata.txt");
        
        //Step 2: Read Student contribution file into set
        loadData("mydata.txt");
        
        //Step 3: Do additional cleaning not done during loading
        cleanData();
        
        //Step 4: Export sorted and cleaned data
        exportData("outputdata.txt");
        
        //Step 5: Export only the data I contributed
        exportMyData("myoutputdata.txt");
    }

    public static void main(String[] args) {
        System.out.println("Starting Fake Book Data Cleaner");
        DataCleaner app = new DataCleaner();
        app.runDataCleaner();
    }
}

Also I believe I'm supposed to be able to use the hashcode(), equals() and compareTo() to my advantage to reduce the amount of code I needed?
 

Related to Trying to override the hashcode()

1. What is the purpose of overriding the hashcode() method?

The hashcode() method is used to generate a unique integer value for an object. By overriding this method, you can provide a custom implementation that better reflects the unique properties of your object. This is important when using objects in data structures like HashMaps, where the hashcode is used to determine the object's position in the data structure.

2. When should I override the hashcode() method?

You should override the hashcode() method whenever you override the equals() method. This is important because the hashcode is used to determine if two objects are equal, and if the equals() method is overridden, the default hashcode() implementation will not accurately reflect this equality.

3. What happens if I don't override the hashcode() method?

If you don't override the hashcode() method, the default implementation provided by the Object class will be used. This implementation generates a unique hashcode based on the memory address of the object. This can cause unexpected behavior when using objects in data structures, as two objects that are considered equal may have different hashcodes.

4. How do I create a good hashcode() implementation?

A good hashcode() implementation should generate a unique hashcode for each unique combination of object properties. This means considering all relevant properties and combining their hashcodes in a way that minimizes the chance of collisions (two different objects having the same hashcode). It's also important to ensure that your hashcode() implementation is consistent with your equals() implementation.

5. Can I change the hashcode() of an object after it has been created?

No, the hashcode of an object should be immutable (unchangeable). This is because the hashcode is used to determine the object's position in data structures, and changing it could result in the object being lost or inaccessible in the data structure. It's also important to note that changing the hashcode after an object has been added to a HashMap or other data structure may cause unexpected behavior.

Similar threads

  • Engineering and Comp Sci Homework Help
Replies
2
Views
1K
  • Engineering and Comp Sci Homework Help
Replies
1
Views
1K
  • Programming and Computer Science
Replies
3
Views
3K
  • Engineering and Comp Sci Homework Help
Replies
1
Views
3K
  • Engineering and Comp Sci Homework Help
Replies
9
Views
3K
  • Engineering and Comp Sci Homework Help
Replies
1
Views
1K
  • Programming and Computer Science
Replies
3
Views
2K
  • Engineering and Comp Sci Homework Help
Replies
1
Views
2K
  • Programming and Computer Science
Replies
5
Views
2K
  • Engineering and Comp Sci Homework Help
Replies
1
Views
2K
Back
Top