97. Do any rules apply when overriding equals()?
When overriding the equals() method, you must comply with the following rules:reflexivity — for any value x, x.equals(x) must always return true (where x != null).
symmetry — for any values x and y, x.equals(y) must return true only if y.equals(x) returns true.
transitivity — for any values x, y and z, if x.equals(y) returns true and y.equals(z) also returns true, then x.equals(z) must return true.
consistency — for any values x and y, repeatedly calling x.equals(y) will always return the same value as long as the fields used to compare the two objects have not changed between each call.
null comparison — for any value x, calling x.equals(null) must return false.
98. What happens if you don't override equals() and hashCode()?
In this case, hashCode() will return a number generated based on the address of the memory cell where the object is stored. In other words, when the original hashCode() method is called on two objects with exactly the same fields, the result will be different (because they are stored in different memory locations). The original equals() method compares references, i.e. it indicates whether the references point to the same object. In other words, the comparison uses the == operator, and it will always return false for different objects, even when their fields are identical. true is returned only be when comparing references to the same object. Sometimes it makes sense to not override these methods. For example, you want all objects of a certain class to be unique — overriding these methods could only spoil the existing guarantee of unique hash codes. The important thing is to understand the nuances of these methods, whether overridden or not, and to use whichever approach the situation calls for.99. Why is the symmetry requirement satisfied only if x.equals(y) returns true?
This question is a bit strange. If object A is equal to object B, then object B is equal to object A. If B is not equal to object A, then how could the reverse be possible? This is common sense.100. What is a HashCode collision? How do you deal with it?
A HashCode collision occurs when two different objects have the same HashCode. How is that possible? Well, the hash code gets mapped to an Integer, which has a range from -2147483648 to 2147483647. That is, it can be one of approximately 4 billion different integers. This range is huge but not infinite. That means there are situations when two completely different objects may have the same hash code. It is highly unlikely, but it is possible. A poorly implemented hash function can make identical hash codes more frequent by returning numbers in a small range, thus increasing the chance of collisions. To reduce collisions, you need to have a good implementation of the HashCode method that uniformly spreads out the values and minimizes the chance of repeated values.101. What happens if the value of an element participating in the hashCode contract changes?
If an element involved in the calculation of a hash code changes, then the object's hash code should change (if the hash function is good). That's why you should use immutable objects as keys in a HashMap, since their internal state (fields) cannot be changed after creation. And it follows that their hash code does change after creation. If you use a mutable object as a key, then when the object's fields change, its hash code will change, and you could lose the corresponding key-value pair in the HashMap. After all, it will be stored in the bucket associated with the original hash code, but after the object changes, you will search for it in a different bucket.102. Write equals() and hashCode() methods for a Student class that has String name and int age fields.
public class Student {
int age;
String name;
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || this.getClass() != o.getClass()) {
return false;
}
final Student student = (Student) o;
if (this.age != student.age) {
return false;
}
return this.name != null ? this.name.equals(student.name) : student.name == null;
}
@Override
public int hashCode() {
int result = this.age;
result = 31 * result + (this.name != null ? this.name.hashCode() : 0);
return result;
}
}
equals():First, we compare the references directly, because if the references point at the same object, what's the point in continuing to check for equality? We already know that the result will be true.
We check for null and whether the class types are the same because if the parameter is null or of another type, then the objects cannot be equal, and the result must be false.
We cast the parameter to the same type (after all, what if it is an object of the parent type).
We compare the primitive fields (a comparison using =! will suffice). If they are not equal, we return false.
We check the non-primitive field to see if it is null and using the equals method (the String class overrides the method, so it will perform the comparison correctly). If both fields are null, or equals returns true, we stop checking, and the method returns true.
We set the hash code's initial value equal to the value of the object's age field.
We multiply the current hash code by 31 (for a greater spread in values) and then add the hash code of the non-primitive String field (if it's not null).
We return the result.
Overriding the method in this way means that objects with the same name and int values will always return the same hash code.
103. What is the difference between using "if (obj instanceof Student)" and "if (getClass() == obj.getClass())"?
Let's take a look at what each expression does:instanceof checks whether the object reference on the left side is an instance of the type on the right side or one of its subtypes.
"getClass() == ..." checks whether types are the same.
104. Give a brief description of the clone() method.
The clone() method belongs to the Object class. Its purpose is to create and return a clone (copy) of the current object. To use this method, you need to implement the Cloneable marker interface:
Student implements Cloneable
And override the clone() method itself:
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
After all, it is protected in the Object class, i.e. it will only be visible within the Student class and not visible to external classes.
105. What special considerations do you need to keep in mind regarding the clone() method and reference variables in an object?
When objects are cloned, only primitive values and the value of the object references are copied. This means that if an object has a field that references another object, then only the reference will be cloned — this other referenced object will not be cloned. This is what is called a shallow copy. So, what if you need a full-fledged copy, where every nested object is cloned? How do you make sure that these are not mere copies of references, but instead full-fledged copies of distinct objects occupying distinct memory addresses in the heap? Actually, it's all quite simple — for each class that is referenced internally, you need to override the clone() method and add the Cloneable marker interface. Once you do this, the clone operation will not copy references to existing objects, but will instead copy the referenced objects, since now they also have the ability to copy themselves.Exceptions
106. What is the difference between an error and an exception?
Exceptions, as well as errors, are subclasses of Throwable. However, they have their differences. The error indicates a problem that mainly occurs due to a lack of system resources. And our application shouldn't see these types of problems. Examples of these errors include a system crash and out-of-memory error. Errors mostly occur at runtime, since they are unchecked. Exceptions are problems that can occur at runtime and at compile time. These problems usually arise in the code that we write as developers. That means that these exceptions are more predictable and more dependent on us. By contrast, errors are more random and more independent of us. Instead, they depend on problems in the system that our application is running on.107. What's the difference between checked, unchecked, exception, throw, and throws?
As I said earlier, an exception is a runtime or compile-time error that occurs in code written by the developer (due to some abnormal situation). Checked is what we call exceptions that a method must always handle by using the try-catch mechanism or rethrow to the calling method. The throws keyword is used in a method header to indicate the exceptions that the method might throw. In other words, it provides us with a mechanism for throwing exceptions to the calling method. Unchecked exceptions don't need to be handled. They tend to be less predictable and are less likely. That said, you can handle them if you want to. We use throw when manually throwing an exception, for example:
throw new Exception();
108. What is the exception hierarchy?
The exception hierarchy is very extensive. There is too much to adequately describe here. So, instead we'll only consider its key branches: Here, at the very top of the hierarchy, we see the Throwable class, which is the general ancestor of the exception hierarchy and in turn divides into:- Errors — critical, unchecked problems.
- Exceptions — exceptions that can be checked.
109. What is are checked and unchecked exceptions?
As I said before:Checked exceptions are exceptions that you must somehow handle. That is, you must either handle them in a try-catch block or throw them to the method above. To do this, after listing the method arguments in the method signature, use throws <exception type> to indicate that the method can throw that exception. This is somewhat like a warning, putting the calling method on notice that it must assume responsibility for handling that exception.
Unchecked exceptions do not need to be handled, since they are not checked at compile time and are usually more unpredictable. Their main difference with checked exceptions is that handling them by using a try-catch block or by rethrowing is optional rather than mandatory.
101. Write an example where you use a try-catch block to catch and handle an exception.
try{ // Start of the try-catch block
throw new Exception(); // Manually throw an exception
} catch (Exception e) { // Exceptions of this type and its subtypes will be caught
System.out.println("Oops! Something went wrong =("); // Display the exception
}
102. Write an example where you catch and handle your own custom exceptions.
First, let's write our own exception class that inherits Exception and override its constructor that takes an error message as an argument:
public class CustomException extends Exception {
public CustomException(final String message) {
super(message);
}
}
Next we will manually throw one and catch it just as we did in the example for the previous question:
try{
throw new CustomException("Oops! Something went wrong =(");
} catch (CustomException e) {
System.out.println(e.getMessage());
}
Once again, when we run our code, we get the following output:
GO TO FULL VERSION