Source: Wikipedia
Let's review our progress with our Card class design:
Card
v0.1).Card
v1.0) with mutator methods (Card
v1.2) to set their values.Card
v1.3).Card
v2.0.1 has a constructor, which ensures that instance variables are initialized when an instance of Card
v2.0.1 is created.public class Card {
private final String[] VALID_RANKS =
{"2", "3", "4", "5", "6", "7", "8", "9",
"10", "jack", "queen", "king", "ace"};
private final String[] VALID_SUITS =
{"diamonds", "clubs", "hearts","spades"};
Do we need a separate instance of VALID_RANKS
and VALID_SUITS
for each instance of our Card class?
static
members are shared with all instances of a class:
public static final String[] VALID_RANKS =
{"2", "3", "4", "5", "6", "7", "8", "9",
"10", "jack", "queen", "king", "ace"};
public static final String[] VALID_SUITS =
{"diamonds", "clubs", "hearts","spades"};
Given the declarations above:
VALID_RANKS
and a single copy of VALID_SUITS
final
, we can safely make them public
so clients of our Card class can use themThis code should bother you.
public class Dealer {
public static void main(String[] args) {
Card c = new Card("Queen", "Bohemian Rhapsody");
System.out.println(c);
}
}
Why does it bother you?
An Enum defines a class and all instances of the class in one definition.
public enum Rank {
TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING, ACE
}
public enum Suit {
DIAMONDS, CLUBS, HEARTS, SPADES
}
There's more to Enum types that we won't cover in class but may test on exams.
Card
v3.0public class Card {
private Rank rank;
private Suit suit;
public Card(Rank rank, Suit suit) {
setRank(rank);
setSuit(suit);
}
public void setRank(Rank rank) {
this.rank = rank;
}
public void setSuit(Suit suit) {
this.suit = suit;
}
public String toString() {
return rank + " of " + suit;
}
}
No need for hand-written validation code. No more run-time validation errors. Yay static types!
And using the Card
v3.0 class is simpler:
public class Dealer {
public static void main(String[] args) {
Card c = new Card(Rank.QUEEN, Suit.HEARTS);
System.out.println(c);
}
}
You'll even get auto-complete if your editor or IDE is set up to do that.
Card
v3.0 is pretty good, but we can write code like this (check out v3.1 for this Dealer
):
public class Dealer {
public static void main(String[] args) {
Card c = new Card(Rank.QUEEN, Suit.HEARTS);
System.out.println(c);
c.setRank(Rank.JACK); // modifying c
System.out.println(c);
}
}
Does this make sense? Should Card objects be mutable?
Card
, v4.0Card objects don't change. We can model this behavior by removing the setters and putting the initialization code in the constructor:
public class Card {
private Rank rank;
private Suit suit;
public Card(Rank aRank, Suit aSuit) {
rank = aRank;
suit = aSuit;
}
public String toString() {
return rank + " of " + suit;
}
}
Note the use of another idiom for disambiguating constructor paramters from instance variables (as opposed to using this
).
An immutable class is a class whose instances cannot be modified. To make a class immutable:
final
so it can't be extended (there's another way to accomplish this, but making the class final
is good enough for now)final
private
In general, make your classes immutable unless you have a good reason to make them mutable. Why? Because immutable objects
Card
v4.1==
means identity equality (aliasing) for reference types (objects).equals(Object)
tests value equality for objects.public class Card {
...
public boolean equals(Object other) {
if (null == other) { return false; }
if (this == other) { return true; }
if (!(other instanceof Card)) { return false; }
Card that = (Card) other;
return this.rank.equals(that.rank) && this.suit.equals(that.suit);
}
}
You'll learn how to write equals
methods later. For now just understand the meaning.
With Card
's properly implemented equals
method, this code:
Card c1 = new Card(Rank.ACE, Suit.SPADES);
Card c2 = new Card(Rank.ACE, Suit.SPADES);
Card c3 = c1;
System.out.println("c1 == c2 returns " + (c1 == c2));
System.out.println("c1.equals(c2) returns " + c1.equals(c2));
System.out.println("c1 == c3 returns " + (c1 == c3));
System.out.println("c1.equals(c3) returns " + c1.equals(c3));
produces this output:
c1 == c2 returns false
c1.equals(c2) returns true
c1 == c3 returns true
c1.equals(c3) returns true
We have a nice Card
library, but it's in the "default" (no-name) package. This will make it hard to use in other code. So let's put it in a package by add ing this declaration at the top of each source file:
package edu.gatech.cs1331.card;
Java requires the directory structure to match the package structure. So we need to move our classes into a edu/gatech/cs1331/card
directory from our "source root."
Source Directories
src/main/java
for Java source files ("source root")src/main/resources
for resources that will go on the classpath, like image filesOutput Directories
build/classes
for javac
output and resources copied from src/main/resources
More details on the de-facto standard Java project directory layout: Maven's directory layout guide.
So we'll put our Card source files in src/main/java/edu/gatech/cs1331/card/
Now we're separating our source files from compiler output. Here's how to use the new directory layout:
Make a directory for compiler output:
[chris@nijinsky ~/cs1331/card]
$ mkdir -p build/classes
Tell javac
where to put compiler output with the -d
switch:
[chris@nijinsky ~/cs1331/card]
$ javac -d build/classes/ src/main/java/edu/gatech/cs1331/card/*.java
Specify a classpath and fully-qualified class name to run:
[chris@nijinsky ~/cs1331/card]
$ java -cp ./build/classes/ edu.gatech.cs1331.card.Dealer
Just as your operating system shell looks in the PATH
environment variable for executable files, JDK tools (such as javac
and java
) look in the CLASSPATH
for Java classes.
A classpath specification is a list of places to find .class
files and other resources. Two kinds of elements in this list:
.class
files on the filesystem, or.jar
files that contain archives of directory trees containing .class
files and other files (more later).To specify a classpath:
CLASSAPTH
, or-cp
switch. The classpath set with -cp
overrides the CLASSPATH
environment variable.Don't use the CLASSPATH
environment variable. If it's already set, clear it with (on Windows):
C:\> set CLASSPATH=
or (on Unix):
$ unset CLASSPATH