Java Safari
CS 3500, Fall 2019
Primitive types in Java: a review
The Primitive types
Type | Size | Description | Range |
---|---|---|---|
boolean | 1 bit | truth value | true or false |
byte | 8 bits | signed integer | \(-2^7\) to \(2^7 - 1\) |
short | 16 bits | signed integer | \(-2^{15}\) to \(2^{15} - 1\) |
char | 16 bits | Unicode character | \(0\) to \(2^{16} - 1\) |
int | 32 bits | signed integer | \(-2^{31}\) to \(2^{31} - 1\) |
float | 32 bits | floating point number | (see *IEEE 754*) |
long | 64 bits | signed integer | \(-2^{63}\) to \(2^{63} - 1\) |
double | 64 bits | floating 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(
return a * b; "performBinop:default");
} }
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 callhandleSeconds(false)
and thenhandleSeconds(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
Type | Size | Description | Range |
---|---|---|---|
boolean | 1 bit | truth value | true or false |
byte | 8 bits | signed integer | \(-2^7\) to \(2^7 - 1\) |
short | 16 bits | signed integer | \(-2^{15}\) to \(2^{15} - 1\) |
char | 16 bits | Unicode character | \(0\) to \(2^{16} - 1\) |
int | 32 bits | signed integer | \(-2^{31}\) to \(2^{31} - 1\) |
float | 32 bits | floating point number | (see *IEEE 754*) |
long | 64 bits | signed integer | \(-2^{63}\) to \(2^{63} - 1\) |
double | 64 bits | floating point number | (see *IEEE 754*) |
What are they?
Boxed primitives
Primitive | Boxed |
---|---|
boolean | Boolean |
byte | Byte |
short | Short |
char | Character |
int | Integer |
float | Float |
long | Long |
double | Double |
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?
Primitive | Boxed | Consideration |
---|---|---|
immediate | reference | boxed takes more space and time |
no null | nullable | whether null is wanted |
no supertype | extends Object | no 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() |
---|---|
shallow | as deep or shallow as you like |
intensional | intended to be extensional |
nominal | as nominal or structural as you like |
physical | logical, 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 8Overriding 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)
andy.equals(z)
thenx.equals(z)
The Rule(s) of
hashCode()
:Compatibility: if
x.equals(y)
thenx.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
Checked | Unchecked |
---|---|
extends Exception | extends Error or RuntimeException |
possibly recoverable | probably bail out |
e.g., network error | e.g., programming error |
must appear in throw clauses | may 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
'sprivate
names are visible toNested
Nested
'sprivate
names are visible toOuter
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<?>