Java provides a default toString() method for all Objects, but it leaves much to be desired.
So, it’s usually a good idea to override the toString method to give a better String representation.
This is particularly useful when debugging your code, as you can quickly get a summary of the state of an Object without digging into the debugger data.
So, how should you build complex Strings?
For simple cases, it might be fine to do
final String s = "Point(" + x + "," + y + ")";
But it’s mainly fine because the compiler can optimize it. Why might this be bad?
To make things a bit cleaner, though, you may want to use String.format:
final String template = "Point(%d,%d)";
final String s = String.format(template,x,y);
For building more complex strings that involve concatenation and iteration, you’re probably better off using a StringBuilder:
final StringBuilder sb = new StringBuilder();
sb.append("Points:");
for(final Point p : points){
sb.append(p.toString());
sb.append(",");
}
sb.setLength(sb.length()-1);
final String s = sb.toString();
What is the difference between calling
p1.equals(p2)
and
p1==p2
When we talk about values being equal, we are usually making a lot of assumptions about what that means.
In reality, there is no good, universal definition of equality.
For instance, all of the following could meet the definition of two points being equal:
Which one is correct?
Java, by default, assumes that equals means two items are stored at the same memory location. So, in the following code:
final Point p1 = new SimplePoint(10,10);
final Point p2 = new SimplePoint(10,10);
The objects p1 and p2 are not equal!
Intuitively, this may seem odd. Accordingly, Java allows you to override the equals method and make it work however you want.
Often, the best decision will be to use structural equality: two objects are equivalent if they have the same structure and values.
For example, we could write the following method:
public boolean equals(final Point other){
if(getX() == other.getX() && getY() == other.getY()){
return true;
}else{
return false;
}
}
So, is our equals method good enough?
The equals method is actually defined with a parameter of type Object, so we can’t expect to receive a Point!
Also, we have not checked for the possibility of receiving a null value!
public boolean equals(final Object other){
if(!(other instanceof SimplePoint)){
return false;
}else{
final SimplePoint p = (SimplePoint)other;
// Why ==?
return x==p.x && y==p.y;
}
}
We can also short circuit the check by adding the following:
if(this==other){
return true;
}
We could instead attempt to use some sort of semantic equivalence, but it is often dangerous to do so and may not return the intuitive result for your users. For example,
final Currency brl = new BRL(1.0);
final Currency usd = new USD(0.247114);
// Should this be true due to the current fx rate?
brl.equals(usd);
Generally speaking, however we choose to define equals, it should be reflexive, symmetric and transitive.
Which you, of course, remember from CSC-300, right?
In other words, an object must equal itself.
In other words, objects must agree on being equal.
In other words, we can infer equivalence via other equivalences which have an “overlapping” element.
When we use inheritance and add fields to an object, it almost guarantees that we have broken the equals method if we expect it to work with all the parent and child classes.
Immutable objects also work really nicely with structural equality, because you can literally swap equal instances with no impact on your code.
(assuming you don’t use ==, or reflection, or something else nasty)
It also means that objects can be re-used. We could, for example, use the exact same Point(0,0) object for every instance of a Duck at that location over time.
If we have a small, finite, number of possibilities, we can just create a pool of Points and never have to allocate a new one!
This explains the curious Integer.valueOf(int i) method. This:
final Integer x = new Integer(10);
Allocates a new Integer on every call. On the other hand:
final Integer x = Integer.valueOf(10);
Can cache common values and reuse them.
The hashCode and equals methods are expected to work in concert:
If two objects are “equal”, then they must return the same hash code!
(This does not mean they must have different hash codes if they are unequal!)
With structural equality, this is not difficult: you just need to pass all fields used for equality testing to Objects.hash(…) and it will compute a good hash code for you.
With more exotic definitions of equality, this becomes very difficult.
Also, with immutable objects, we only really need to compute the code once!
There are a lot of nice ways to shoot yourself in the foot when using Java. Let’s take a look at some reasonable looking code and figure out why it may not be so reasonable.
public boolean test(final String x){
return x=="something"
}
// ...
System.out.println(test(x));
What output will we see if x has the value “something”?
Depending on the code and how the compiler optimizes, we may get true or false. If x gets its value from a literal and the compiler optimizes the code so that duplicate String literals are stored at the same location, we’ll get true. Otherwise, false.
How’s this code?
final Object[] objects = {"hello","ola","Hallo"};
final String[] strings = (String[])objects;
It’s awful and it’s guaranteed to throw an exception!
final String[] strings =
Arrays.asList(objects).toArray(new String[]);
public class X extends Thread{
private boolean go = true;
public void run(){
while(go){
// do something
}
}
public void dontgo(){
go = false;
}
}
public static void main(final String[] args){
final Thread t = new X();
t.start();
t.dontgo();
}
This code may never stop, because the memory modification from the main thread is not guaranteed to be observed by all other threads (including the actual thread itself!)
It’s only guaranteed to work if we make the variable volatile or insert synchronization code.
public class Y{
private int n;
public Y(final int n){
this.n=n;
}
public void test(){
if(n!=n){
throw new AssertionError();
}
}
}
Assuming we are sharing this object with other threads, what will happen when we call test?
Are you sure?
In fact, this may throw an assertion error, because there is no guarantee that both accesses to n will see the same value, even though only one value was assigned! Why?
public class Lol{
public double value;
}
final Lol = new Lol();
Lol.value = 12121.0;
Assuming this object is shared with other threads, what possible values might they see?
They could see 0.0, which is the default for a double.
They could see 12121.0, which is what it’s updated to.
…or they could see whatever you get from taking half the bits from 0.0 and half the bits from 12121.0
** LOLOLOLOLOLOL **
Of course, immutable objects using final variables avoids all of these problems!
Programming is awful and there’s no way you can write correct code without a lot of hard work and help from tools, best practices, etc.
Buy Java Concurrency in Practice before you try to write any multi-threaded Java code in the real world.
If you aren’t using Java, make sure you look into the “memory model” of your language: it’s almost guaranteed it’s a horror show as well!