Java Safari
1 Java Safari

Java Safari

CS 3500, Fall 2016

Primitive types in Java: a review

The Primitive types

TypeSizeDescriptionRange
boolean1 bittruth valuetrue or false
byte8 bitssigned integer\(-2^7\) to \(2^7 - 1\)
short16-bitssigned integer\(-2^{15}\) to \(2^{15} - 1\)
char16-bitsUnicode character\(0\) to \(2^{16} - 1\)
int32-bitssigned integer\(-2^{31}\) to \(2^{31} - 1\)
float32-bitsfloating point number(see *IEEE 754*)
long64-bitssigned integer\(-2^{63}\) to \(2^{63} - 1\)
double64-bitsfloating point number(see *IEEE 754*)

char: What are they?

Type char includes:

  • letters ('a', 'b', 'A', 'B', 'α', 'β', etc.)

  • digits ('0', '1', '١', '٢', etc.)

  • punctuation ('.', '-', '«', etc.)

  • whitespace (' ', '\n', '\t', '\v', etc.)

Characters: How can we use them?

As a test of character
assertTrue ( Character.isLetter('a') );
assertTrue ( Character.isLetter('β') );
assertFalse( Character.isLetter('8') );

assertFalse( Character.isDigit('a') );
assertFalse( Character.isDigit('β') );
assertTrue ( Character.isDigit('8') );

assertFalse( Character.isLowercase('R') );
assertTrue ( Character.isLowercase('r') );

Characters: How can we use them?

As elements of strings
assertEquals( 'a' , "abcde".charAt(0) );
assertEquals( 'c' , "abcde".charAt(2) );

assertEquals(  0, "abcde".indexOf('a') );
assertEquals(  2, "abcde".indexOf('c') );
assertEquals( -1, "abcde".indexOf('f') );

char[] abc = { 'a', 'b', 'c' };
assertEquals(  abc,  "abc".toCharArray() );
assertEquals( "abc", String.valueOf(abc) );

Characters: How can we use them?

As elements of strings
assertEquals( 'a' , "abcde".charAt(0) );
assertEquals( 'c' , "abcde".charAt(2) );

assertEquals(  0, "abcde".indexOf('a') );
assertEquals(  2, "abcde".indexOf('c') );
assertEquals( -1, "abcde".indexOf('f') );

char[] abc = { 'a', 'b', 'c' };
assertArrayEquals(  abc,  "abc".toCharArray() );
assertEquals     ( "abc", String.valueOf(abc) );

Characters: When should we use them?

  • Processing a string character by character

    • e.g., tokenizing a string into words

    • Duration.format(...)

  • Representing text-like values that are always of length 1

    • e.g., keystrokes in a GUI

Enumerations

What are they?

  • A special kind of class

  • Creates a finite set of named values

  • Represents a small, fixed set of options

What are they?

enum Status {
  Playing, Stalemate, Won
}

enum Binop {
  Add, Sub, Mul, Div, Pow
}

enum Weekday {
  Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday
}

How can we use them?

double performBinop(Binop op, double a, double b) {
  if      (op == Binop.Add) { return a + b; }
  else if (op == Binop.Sub) { return a - b; }
  else if (op == Binop.Mul) { return a * b; }
  else if (op == Binop.Div) { return a / b; }
  else if (op == Binop.Pow) { return Math.pow(a,b); }
  else {
      throw new RuntimeException("performBinop:default");
  }
}

assertEquals( 8.0, performBinop(Binop.Mul, 2.0, 4.0) );

What they can be

enum UsCoin {
  Penny(1), Nickel(5), Dime(10), Quarter(25), HalfDollar(50);
  private final int cents;
  public int getCentsValue() {
    return cents;
  }
  UsCoin(int cents) {
    this.cents = cents;
  }
}

When should we use them?

  • To represent values in a finite set of options

  • The options are known at compile time

  • The options aren't too many to list

  • No need to extend another class

The switch statement

What is it for?

// Don't write this

if (c == 's') {
    handleSeconds(false);
}
else if (c = 'S') {
    handleSeconds(true);
}
else if (c = 'm') {
    handleMinutes(false);
}
else if (c = 'M') {
    handleMinutes(true);
}
else {
    unrecognized();
}

What is it for?

// Don't write this:         Do write this:
                             switch (c) {
if (c == 's') {                case 's':
    handleSeconds(false);        handleSeconds(false);
}                                break;
else if (c = 'S') {            case 'S':
    handleSeconds(true);         handleSeconds(true);
}                                break;
else if (c = 'm') {            case 'm':
    handleMinutes(false);        handleMinutes(false);
}                                break;
else if (c = 'M') {            case 'M':
    handleMinutes(true);         handleMinutes(true);
}                                break;
else {                         default:
    unrecognized();              unrecognized();
}                            }

What is it for?

// Don't write this

if (op == Binop.Add) {
    return a + b;
}
else if (op == Binop.Sub) {
    return a - b;
}
else if (op == Binop.Mul) {
    return a * b;
}
else if (op == Binop.Div) {
    return a / b;
}
else if (op == Binop.Pow) {
    return Math.pow(a,b);
}
else
    throw new RuntimeException("performBinop:default");

What is it for?

// Don't write this:         Do write this:
                             switch (op) {
if (op == Binop.Add) {         case Add: return a + b;
    return a + b;              case Sub: return a - b;
}                              case Mul: return a * b;
else if (op == Binop.Sub) {    case Div: return a / b;
    return a - b;              case Pow: return Math.pow(a, b);
}                              default:
else if (op == Binop.Mul) {    throw new RuntimeException("performBinop:default");
    return a * b;             }
}
else if (op == Binop.Div) {
    return a / b;
}
else if (op == Binop.Pow) {
    return Math.pow(a,b);
}
else
    throw new RuntimeException("performBinop:default");

When should we use it?

A multi-way branch that depends on specific values of a variable

Caveats:

  • Only works on primitive types, enums and (as of Java 7) String

  • Falls through if you omit break:

    case 's':
      handleSeconds(false);
    case 'S':
      handleSeconds(true);
      break;

    For 's' this will call handleSeconds(false) and then handleSeconds(true)

Arrays

What are they?

If τ is a Java type, then an array of type τ[] is a

  • mutable,

  • fixed-length,

  • constant-time–indexed

  • sequence of values of type τ.

Let's unpack that.

How can we use them?

Construction
int[] array1 = new int[] { 2, 4, 6, 8 };

int[] array2 = new int[64]; //auto-initialized to all 0s

String[] array3 = new String[21]; //auto-initialized to all null

Only works in variable declarations:

int[] array3 = { 2, 4, 6, 8 };

How can we use them?

Length
int[] array1 = new int[] { 3, 4, 5 };

assertEquals( 3, array1.length );



int[] array2 = new int[17];

assertEquals( 17, array2.length );

How can we use them?

Indexing
int[] array1 = new int[] { 3, 4, 5 };

assertEquals( 3, array1[0] );
assertEquals( 4, array1[1] );
assertEquals( 5, array1[2] );
array1[1] = -78;

assertEquals( 3, array1[0] );
assertEquals( -78, array1[1] );
assertEquals( 5, array1[2] );

How can we use them?

Aliasing
int[] a1 = new int[16];
int[] a2 = new int[16];
int[] a3 = a1;
aE( 0, a1[7] ); aE( 0, a2[7] ); aE( 0, a3[7] );
a1[7] = 1;
aE( 1, a1[7] ); aE( 0, a2[7] ); aE( 1, a3[7] );
a2[7] = 2;
aE( 1, a1[7] ); aE( 2, a2[7] ); aE( 1, a3[7] );
a3[7] = 3;
aE( 3, a1[7] ); aE( 2, a2[7] ); aE( 3, a3[7] );
Aliasing is not copying

How can we use them?

Copying
int[] a1 = new int[16];
int[] a2 = new int[16];
...
//create copy of a1
int[] a3 = new int[a1.length];
for (int i = 0; i < a1.length; i++) {
   a3[i] = a1[i];
}

/*
// alternative way of copying
   a3 = Arrays.copyOf(a1,a1.length);
*/

a1[7] = 1;
aE( 1, a1[7] ); aE( 0, a2[7] );
aE( 1, a3[7] );

How can we use them?

Testing
int[] a1 = new int[16];
int[] a2 = new int[16];
int[] a3 = a1;
assertEquals( a1, a2 );
assertEquals( a1, a3 );

assertArrayEquals( a1, a2 );

assertArrayEquals( a1, a3 );

How can we use them?

Varargs (and iteration)
public void printInts(int[] intArray) {
    for (int i : intArray) {
        System.out.println(i);
    }
}

printInts(new int[] { 8, 6, 7 });
printInts(8, 6, 7);

How can we use them?

Varargs (and iteration)
public void printInts(int... intArray) {
    for (int i : intArray) {
        System.out.println(i);
    }
}

printInts(new int[] { 8, 6, 7 });
printInts(8, 6, 7);

How can we use them?

Static methods
List<T> Arrays.asList(T... elements);

int Arrays.binarySearch(int[] array, int key);

T[] Arrays.copyOfRange(T[] original, int from, int to);

boolean Arrays.equals(Object[] a1, Object[] a2);

boolean Arrays.deepEquals(Object[] a1, Object[] a2);

See java.util.Arrays for more.

When should we use them?

  • An existing API requires it

  • To ensure sequence length is fixed

  • Efficiency, especially when implementing higher-level data structures

Primitive types versus reference types

What are they?

What are they?

The primitive types
TypeSizeDescriptionRange
boolean1 bittruth valuetrue or false
byte8 bitssigned integer\(-2^7\) to \(2^7 - 1\)
short16-bitssigned integer\(-2^{15}\) to \(2^{15} - 1\)
char16-bitsUnicode character\(0\) to \(2^{16} - 1\)
int32-bitssigned integer\(-2^{31}\) to \(2^{31} - 1\)
float32-bitsfloating point number(see *IEEE 754*)
long64-bitssigned integer\(-2^{63}\) to \(2^{63} - 1\)
double64-bitsfloating point number(see *IEEE 754*)

What are they?

Boxed primitives
PrimitiveBoxed
booleanBoolean
byteByte
shortShort
charCharacter
intInteger
floatFloat
longLong
doubleDouble

What are they?

How can we use them?

You already know how, for the most part.

How can we use them?

How can we use them?

c = f[2];

How can we use them?

c = f[2];

How can we use them?

f[1] = f[0];

How can we use them?

f[1] = f[0];

How can we use them?

f[2] = f[0];

How can we use them?

f[2] = f[0];

How can we use them?

f[3] = f[0];

How can we use them?

f[3] = f[0];

How can we use them?

Equality
long x1 = 7L;
long x2 = 7L;

long y1 = 720_233_830_121_456L;
long y2 = 720_233_830_121_456L;


assertTrue ( x1 == x2 );


assertTrue ( y1 == y2 );

How can we use them?

Equality
Long x1 = 7L;
Long x2 = 7L;

Long y1 = 720_233_830_121_456L;
Long y2 = 720_233_830_121_456L;

assertTrue ( x1.equals(x2) );
assertTrue ( x1 == x2 );

assertTrue ( y1.equals(y2) );
assertFalse( y1 == y2 );

See the docs

How can we use them?

Static versions of Object methods
Double x = 14.573;
double y = 14.573;

assertTrue( x.hashCode() == Double.hashCode(y) );
  • The same for the other boxed types

  • T.compare(t, t), T.toString(t), T.max(t, t), T.min(t, t),

  • Special cases: Double.isNan(double)

When should we use them?

PrimitiveBoxedConsideration
immediatereferenceboxed takes more space and time
no nullnullablewhether null is wanted
no supertypeextends Objectno prims in generic containers
==equals(Object)== might read better
  • Many Java programmers prefer primitives

  • This goes against OO, but has its uses and is idiomatic

Helpful constructs and how to use them (for primitive and reference types)

On null

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.

—Sir Tony Hoare (inventor of quicksort, Hoare logic, and Communicating Sequential Processes)

So what is null

Fundies 2: null is evil

Now: null is a pain

Rules of null

  • Use it only to represent the absence of a reference

  • Use it conscientiously and sparingly!

  • Carefully document where it's allowed

  • Check for it and fail fast

Equality, physical & logical

Types of equality

  • shallow vs. deep (same memory location, or same memory contents but at different locations)

  • intensional vs. extensional (literally the same thing, or calculates/derives to the same thing)

  • nominal vs. structural (are objects of two classes with same instance variables and values equal?)

  • physical vs. logical (Are HMSDuration and CompactDuration objects with same duration equal?)

  • == vs. equals()?

Java equalities

==equals()
shallowas deep or shallow as you like
intensionalintended to be extensional
nominalas nominal or structural as you like
physicallogical, and you choose the logic (so it could be as physical as you like)

You choose the logic

By default: physical equality, a/k/a object identity:

public class Object {
    ...
    public boolean equals(Object obj) {
        return (this == obj);
    }
    ...
}

i.e. they literally refer to the same location of memory and are therefore the same

But we can override...

java.lang.Object source from JDK 8

Overriding equals()

final class Posn {
    ...
    private final int x;
    private final int y;
}

assertEquals( new Posn(2, 4), new Posn(2, 4) );

...because although they are two objects with ''identical'' content they reside separately in memory

Overriding equals()

So we change what it means to be ''equal''
final class Posn {
    ...
    private final int x;
    private final int y;

    public boolean equals(Object obj) {







    }
}

Overriding equals()

So we change what it means to be ''equal''
final class Posn {
    ...
    private final int x;
    private final int y;

    public boolean equals(Object obj) {
        if (this == obj) return true; // Fast path






    }
}

Overriding equals()

So we change what it means to be ''equal''
final class Posn {
    ...
    private final int x;
    private final int y;

    public boolean equals(Object obj) {
        if (this == obj) return true; // Fast path

        if (! (object instanceof Posn)) return false;
        Posn that = (Posn) obj;



    }
}

Overriding equals()

So we change what it means to be ''equal''
final class Posn {
    ...
    private final int x;
    private final int y;

    public boolean equals(Object obj) {
        if (this == obj) return true; // Fast path

        if (! (object instanceof Posn)) return false;
        Posn that = (Posn) obj;

                //what we mean by ''the same points''
        return  this.x == that.x  &&  this.y == that.y;
    }
}

assertEquals( new Posn(2, 4), new Posn(2, 4) );

Overriding equals()

final class Rational {
    ...
    private final int n;
    private final int d;

    public boolean equals(Object obj) {
        if (this == obj) return true; // Fast path

        if (! (object instanceof Rational)) return false;
        Rational that = (Rational) obj;

        return  this.n == that.n  &&  this.d == that.d;
    }
}

assertEquals( new Rational(2, 4), new Rational(2, 4) );

Overriding equals()

final class Rational {
    ...
    private final int n;
    private final int d;

    public boolean equals(Object obj) {
        if (this == obj) return true; // Fast path

        if (! (object instanceof Rational)) return false;
        Rational that = (Rational) obj;

        return  this.n == that.n  &&  this.d == that.d;
    }
}

assertEquals( new Rational(1, 2), new Rational(2, 4) );

...because rational equality depends on the ratio, not the numbers themselves

Overriding equals()

final class Rational {
    ...
    private final int n;
    private final int d;

    public boolean equals(Object obj) {
        if (this == obj) return true; // Fast path

        if (! (object instanceof Rational)) return false;
        Rational that = (Rational) obj;

                //what we mean by ''same ratios''
        return  this.n * that.d  ==  this.d * that.n;
    }
}

assertEquals( new Rational(1, 2), new Rational(2, 4) );

Overriding equals()

final class GridCursor {
    ...
    private int r;
    private int c;

    public boolean equals(Object obj) {
        if (! (object instanceof GridCursor)) return false;
        GridCursor that = (GridCursor) obj;

        return  this.r == that.r  &&  this.c == that.c;
    }
}


assertEquals( new GridCursor(2, 4), new GridCursor(2, 4) );

An equality pitfall

while (! cursor.equals(cursor0)) {
    ...

    if (cursor.equals(cursor0)) {
        ...
    }

    ...
}

An equality pitfall

while (! cursor.equals(cursor0)) {
    ...

    // if (cursor.equals(cursor0)) {
    //     ...
    // }

    ...
}

An equality pitfall

while (! cursor.equals(cursor0)) {
    ... cursor.moveLeft(); ...

    // if (cursor.equals(cursor0)) {
    //     ...
    // }

    ...
}

Overriding equals()

final class GridCursor {
    ...
    private int r;
    private int c;

    public boolean equals(Object obj) {
        if (! (object instanceof GridCursor)) return false;
        GridCursor that = (GridCursor) obj;

        return  this.r == that.r  &&  this.c == that.c;
    }
}


assertEquals( new GridCursor(2, 4), new GridCursor(2, 4) );

Overriding equals()

final class GridCursor {
    ...
    private int r;
    private int c;
}


assertNotEquals( new GridCursor(2, 4), new GridCursor(2, 4) );

But wait, there was a problem with some of our equals overrides!

Equality rules

  • The Rules of equals():

    • Reflexivity: x.equals(x)

    • Symmetry: x.equals(y) *iff* y.equals(x)

    • Transitivity: if x.equals(y) and y.equals(z) then x.equals(z)

  • The Rule(s) of hashCode():

    • Compatibility: if x.equals(y) then x.hashCode() == y.hashCode()

    • Non-injectivity: x.hashCode() == y.hashCode() doesn't contradict !x.equals(y)

  • Rule 9 (corollary): "Always override hashCode when you override equals" ---Joshua Bloch, Effective Java 2/E

So what’s the deal with ==?

Fundies 2: don't use ==

Now: understand what == means and use it appropriately

Remember: == compares the immediate contents of variables (for reference types this means memory locations)

Overriding equals() and hashCode()

final class GridCursor {
    ...
    private int r;
    private int c;
}

Overriding equals() and hashCode()

final class Posn {
    ...
    private final int x;
    private final int y;

    public boolean equals(Object obj) {
        if (! (object instanceof Posn)) return false;
        Posn that = (Posn) obj;

        return  this.x == that.x  &&  this.y == that.y;
    }




}

Overriding equals() and hashCode()

final class Posn {
    ...
    private final int x;
    private final int y;

    public boolean equals(Object obj) {
        if (! (object instanceof Posn)) return false;
        Posn that = (Posn) obj;

        return  this.x == that.x  &&  this.y == that.y;
    }

    public int hashCode() {
        return Objects.hash(x, y);
    }
}

Overriding equals() and hashCode()

final class Rational {
    ...
    public boolean equals(Object obj) {
        if (! (object instanceof Rational)) return false;
        Rational that = (Rational) obj;

        return  this.n * that.d  ==  this.d * that.n;
    }
    public int hashCode() {
        return Objects.hash(n, d);  // WRONG
    }
}
assertEquals( new Rational(1, 2).hashCode(),
              new Rational(2, 4).hashCode());

Overriding equals() and hashCode()

final class Rational {
    ...
    public boolean equals(Object obj) {
        if (! (object instanceof Rational)) return false;
        Rational that = (Rational) obj;

        return  this.n * that.d  ==  this.d * that.n;
    }
    public int hashCode() {
        int gcd = gcd(n, d);
        return Objects.hash(n / gcd, d / gcd);
    }
}
assertEquals( new Rational(1, 2).hashCode(),
              new Rational(2, 4).hashCode());

The instanceof

Fundies 2: instanceof is evil

Now: instanceof should be used sparingly but correctly

A necessary example of instanceof

Overriding equals to ward off any unrelated objects passed to it

The problem with using instanceof

If you design your classes well, you should never have to determine the type of an object

Downcasts would be rare, and therefore instanceof would be rare too

Exceptions

What are they?

A coding world without exceptions
    int fd, result;

    fd = open(path, flags);
    if (fd == -1) goto cleanup;

    result = write(fd, buf1, size1);
    if (result == -1) goto cleanup;

    result = write(fd, buf2, size2);
    if (result == -1) goto cleanup;

    ...
cleanup:
    // cleanup code

What are they?

Java exceptions
try {
  int fd = open(path, flags);
  write(fd, buf1, size1);
  write(fd, buf2, size2);
  ...
} catch (IOException e) {
  // cleanup code
}

What are they?

Two kinds of exceptions
CheckedUnchecked
extends Exceptionextends Error or RuntimeException
possibly recoverableprobably bail out
e.g., network errore.g., programming error
must appear in throw clausesmay appear in throw clauses

How can we use them?

Basics

Extend:

class IllegalMoveException extends IllegalStateException { ... }

Throw:

throw new IllegalMoveException(reason);

Catch:

try { somethingThatMightThrow(); }
catch (IllegalMoveException e) {
    logError(e);
}

How can we use them?

Checked exceptions

IOException is a checked exception, so it appears in method signatures:

void myBigIoMethod(...) throws IOException {
    ... new FileReader(someFile) ...
}

Methods that call a method that throws and don't catch it also throw:

void innocuousLookingMethod() throws IOException {
  myBigIoMethod(3, true);
}

How can we use them?

Checked exceptions

To discard a throws clause, use catch:

void anotherMethod() {
  try {
    innocuousLookingMethod()
  } catch (IOException e) {
    throw new RuntimeException(e);
  }
}

When should we use them?

  • Throw exceptions to indicate exceptional conditions

    • You know them when you see them

  • Catch exceptions where you're prepared to handle exceptional conditions

    • Don't catch-and-ignore—that hides bugs

static in Java

What does it mean?

In general programming
  • static: at compile time

  • dynamic: at run time

What does it mean?

Specifically in OO
  • static: belongs to a class

  • dynamic: belongs to an object

(Note that classes are created at compile time and objects at run time)

Where is it used?

  • On fields:

    private int widgetId;
    private static int widgetIdCounter;

    (Mutable static fields are often a bad idea.)

  • On methods:

    public void getWidgetId();
    public static void resetWidgetIdCounter();
  • On classes:

    class SomeClass {
        public static class AssociatedClass { ... }
        ...
    }

Common sightings of static

  • Character.isLetter('β')

  • Math.pow (All methods of the Math class)

  • Math.PI

What does it mean for a class?

Inner class name is qualified by outer:

SomeClass.AssociatedClass

Everything within the outer class can see each other's stuff:

  • Outer's private names are visible to Nested

  • Nested's private names are visible to Outer

What does it mean for a class?

class Widget {
    private Widget(...) { ... }

    public static class Factory {
        private int widgetIdCounter;

        public Widget create(...) { ... }
    }

    public static Factory factory() { ... }
}

Widget.Factory factObj = Widget.factory();

Widget w1 = factObj.create();
Widget w2 = factObj.create();

When should we use static?

  • Fields: one variable for the whole class rather than one per object

    • Should be rare except for constants

  • Methods: a method associated with the class that doesn't depend on an instance

    • Factory methods are a common case

  • Classes: helper classes with strong associations

    • For example, an iterator class nested in a collection class

Generics, or raw types considered harmful

What are they?

Generic classes:

class BinTree<T> { ... }

Generic methods:

<T> void permute(List<T>);

How can we use them?

Generic classes

When it's a type, use a parameter:

BinTree<Widget> tree = new BinTree<Widget>();

When it's a constructor, <> asks Java to guess:

BinTree<Widget> tree = new BinTree<>();

When using a static member, don't use a parameter:

if (h < BinTree.MAX_HEIGHT) {
    return BinTree.singleton(value);
}

How can we use them?

Generic methods

Typically, Java can infer the type parameter:

permutation.permute(widgetList);

When it can't, you can tell it:

permutation.<Widget>permute(Arrays.asList());

A warning

Java allows omitting the parameter(s) on types and constructors:

BinTree tree = new BinTree();

This is called a raw type.

It's (almost) always wrong.

Why have a feature that’s so bad that no one should use it, ever?

Backward compatibility

  • Java hasn't always had generics

  • It has raw types so pre-generic code can still work

The bad old days

List widgetList = new ArrayList();

widgetList.add(thisWidget);
widgetList.add(thatWidget);

The List.get method returns type Object:

widgetList.get(i).getWidgetId();

So you need to cast:

((Widget) widgetList.get(i)).getWidgetId();

Reliving the bad old days

Java still allows raw types (like on the previous slide)

What you get for it:

  • Casts everywhere

  • Class cast exceptions when you mess it up

  • Unchecked casts that result in an exception later

What if I want a list where I don't know the element type?

Use a wildcard: List<?>

When should we use raw types?

When should we use raw types?

Never.

I know of no Java expert who disagrees.