Java Things

CSC-430

Phillip Wright

String Representation

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.

String Representation II

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.

Building Strings

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?

Building Strings II

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);

Building Strings III

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();

Review

What is the difference between calling

p1.equals(p2)

and

p1==p2

What Does Equality Mean?

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.

Equality

For instance, all of the following could meet the definition of two points being equal:

  • They are stored at the same memory location
  • They have the same x and y components
  • They represent the same point in space

Which one is correct?

Equality II

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!

Structural Equality

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.

Equals

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;
  } 
}

Equals II

So, is our equals method good enough?

Equals III

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!

Equals IV

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;
  }
}

Equals V

We can also short circuit the check by adding the following:

if(this==other){
  return true;
}

Semantic Equivalence

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);

Equals In General

Generally speaking, however we choose to define equals, it should be reflexive, symmetric and transitive.

Which you, of course, remember from CSC-300, right?

Reflexive

\[\forall x : x \equiv x\]

In other words, an object must equal itself.

Symmetric

\[\forall a, b : a \equiv b \iff b \equiv a\]

In other words, objects must agree on being equal.

Transitive

\[\forall a,b,c : ( a \equiv b \land b \equiv c ) \Rightarrow a \equiv c\]

In other words, we can infer equivalence via other equivalences which have an “overlapping” element.

Inheritance

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

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)

Immutable Objects II

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.

Immutable Objects III

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!

Immutable Objects IV

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.

Don’t Forget Hash Code

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!)

Hash Code

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.

Hash Code II

With more exotic definitions of equality, this becomes very difficult.

Also, with immutable objects, we only really need to compute the code once!

Gotchas

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.

Equals

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”?

Equals II

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.

Casting Arrays

How’s this code?

final Object[] objects = {"hello","ola","Hallo"};
final String[] strings = (String[])objects;

Casting Arrays II

It’s awful and it’s guaranteed to throw an exception!

final String[] strings = 
  Arrays.asList(objects).toArray(new String[]);

Threading

public class X extends Thread{
  private boolean go = true;
  public void run(){
    while(go){ 
      // do something
    }
  }

  public void dontgo(){
    go = false;
  }
}

Threading II

public static void main(final String[] args){
  final Thread t = new X();
  t.start();
  t.dontgo();
}

Threading III

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.

More Threading

public class Y{
  private int n;
  public Y(final int n){
    this.n=n;
  }

  public void test(){
    if(n!=n){
      throw new AssertionError();
    }
  }
}

More Threading II

Assuming we are sharing this object with other threads, what will happen when we call test?

Are you sure?

More Threading III

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?

LOL

public class Lol{
  public double value;
}

final Lol = new Lol();
Lol.value = 12121.0;

LOL II

Assuming this object is shared with other threads, what possible values might they see?

LOL III

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 **

Immutable Objects II

Of course, immutable objects using final variables avoids all of these problems!

Takeaway

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.

Also

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!