An summary of many (but not all) of the major topics we studied in the second half of CSE1020.
The Java primitive types are boolean
, char
,
byte
, short
, int
, long
,
float
, and double
. A variable of primitive type
stores a numeric value, or in the case of boolean
, the value
true
or false
.
You check two primitive values for equality using the operators
==
and !=
:
// x and y have the same value? if (x == y) { // x and y have the same value; do something }
// x and y have different values? if (x != y) { // x and y have different values; do something }
A reference variable refers to an object in memory. The value of a reference variable is (something like) the address of the object in memory.
You almost never want to use ==
and !=
with reference
variables. The operator ==
checks if the values of two references
are the same i.e. if the two referenced objects are really the same object.
You almost always want to use equals
with reference variables.
equals
checks if the state of the two references are the same. For
example, the fractions 1/2
and 50/100
are mathematically
equal; in Java, you would write:
Fraction f = new Fraction(1, 2); Fraction g = new Fraction(50, 100); if (f.equals(g)) { // f and g have the same state; do something } if (f == g) { // does not happen; f and g are different objects }
The
API for the String
class is long, but you only
need to familiarize yourself with some of the methods:
Sentinel-based user input continuously accepts user-input until the
user types in a special string (the sentinel). For example, suppose
you want to read in a sequence of strings until the user enters
the string "."
:
import java.io.PrintStream;
import java.util.Scanner;
public class SentinelInput
{
public static void main(String[] args)
{
PrintStream output = System.out;
Scanner input = new Scanner(System.in);
output.print("Enter a string (. to finish): ");
String userInput = input.next();
while (!userInput.equals("."))
{
// do something with userInput here
output.print("Enter a string (. to finish): ");
userInput = input.next();
}
}
}
You could use a for
instead (see textbook Section 5.2.3).
User terminated input continuously accepts user-input until the
user terminates the input stream by entering Ctrl-d
under
Linux or Ctrl-z
at the beginning of a line under Windows.
When the user terminates the input stream, the scanner hasNext
methods will return false
:
import java.io.PrintStream;
import java.util.Scanner;
public class UserTerminatedInput
{
public static void main(String[] args)
{
PrintStream output = System.out;
Scanner input = new Scanner(System.in);
output.print("Enter a string (Ctrl-d to finish): ");
while (input.hasNext())
{
String userInput = input.next();
// do something with userInput here
output.print("Enter a string (Ctrl-d to finish): ");
}
}
}
The StringTokenizer
breaks a string into tokens
(substrings) based on a set of delimiters (characters that
separate tokens). The default behavior is to split a string
at any sequence of whitespace characters (space, tab, newline).
import java.io.PrintStream; import java.util.Scanner; import java.util.StringTokenizer; public class Tokenizer { public static void main(String[] args) { PrintStream output = System.out; Scanner input = new Scanner(System.in); output.println("Enter a line of text:"); output.println(); String aLine = input.nextLine(); output.println(); StringTokenizer tok = new StringTokenizer(aLine); int numTokens = tok.countTokens(); output.printf("\"%s\" has %d tokens%n%n", aLine, numTokens); for (int i = 1; i <= numTokens; i++) { output.printf("Token %d: %s%n", i, tok.nextToken()); } } }
If you want different delimiters, you just specify them in the constructor. For example, the following tokenizer will break a string at any sequence of spaces and punctuation marks (except for the apostrophe):
StringTokenizer tok = new StringTokenizer(aLine, " \".,;:?!");
Aggregation describes the has-a relationship between two classes. A class is an aggregation if it has a reference to an object (other than a string according to the textbook).
An
Investment
has a reference to Stock
.
A
CreditCard
has two references to Date
.
A
List<Fraction>
has zero or more references
to Fraction
Aggregation seems like an implementation detail; therefore,
it should not be the client's concern. Unfortunately, aggregation
can affect the client. For example, suppose that you had a
list of fractions that you want to copy into another list.
A shallow copy of the list will share the Fraction
objects between the two lists:
import java.io.PrintStream;
import java.util.List;
import java.util.ArrayList;
import java.util.Random;
import type.lib.Fraction;
public class ShallowCopy
{
public static void main(String[] args)
{
PrintStream output = System.out;
final int NUM_FRACTIONS = 3;
final int MAX_VALUE = 100;
List<Fraction> fractions = new ArrayList<Fraction>();
// Fill the list with random fractions
Random rng = new Random();
for (int i = 0; i < NUM_FRACTIONS; i++)
{
fractions.add(new Fraction(rng.nextInt(MAX_VALUE),
rng.nextInt(MAX_VALUE)));
}
output.println("Original : ");
output.println(fractions);
output.println();
// Shallow copy the list fractions
List<Fraction> copy = new ArrayList<Fraction>();
for (Fraction f : fractions)
{
copy.add(f);
}
copy.add(new Fraction(0, 0));
output.println("Copy : ");
output.println(copy);
output.println();
// Modify the fractions in the copy
for (Fraction f : copy)
{
f.setNumerator(-1);
}
output.println("Copy after setNumerator: ");
output.println(copy);
output.println();
output.println("Original after setNumerator: ");
output.println(fractions);
}
}
The client must be aware that a shallow copy of an aggregate (the list) shares the aggregated references (the fractions). When the references are mutated through the copy, the original aggregate will see the change.
You should review your notes and the textbook Section 8.1.3 for aliasing, shallow copying, and deep copying.
Inheritance describes the is-a relationship between classes. The header:
public class RewardCard extends CreditCard
means that a RewardCard
is-a CreditCard
.
From the client's point of view, this means that RewardCard
has every public field and method (but not constructors) that
CreditCard
has.
Because
RewardCard
is-a CreditCard
,
you can use a RewardCard
anywhere a
CreditCard
is needed. We say that
RewardCard
is substitutable for
CreditCard
.
import java.io.PrintStream; import java.util.Random; import type.lib.CreditCard; import type.lib.RewardCard; import type.lib.GlobalCredit; public class Substitutable { public static void main(String[] args) { PrintStream output = System.out; GlobalCredit gc = new GlobalCredit(); // Randomly populate the GlobalCredit collection // with credit and reward cards. final int NUM_CARDS = 10; Random rng = new Random(); for (int i = 1; i <= NUM_CARDS; i++) { if (rng.nextBoolean()) { // Ok, add takes a CreditCard gc.add(new CreditCard(i, "Some Name")); } else { // Ok, add takes a CreditCard and // RewardCard is substitutable for CreditCard gc.add(new RewardCard(i, "Another Name")); } } } }
Polymorphism means an object can appear to be a different
type of object. Because RewardCard
is substitutable
for CreditCard
, a RewardCard
can
appear to be a CreditCard
. For example, we can
charge all of the cards in the GlobalCredit
collection
even though some of them are really RewardCard
s:
import java.io.PrintStream;
import java.util.Random;
import type.lib.CreditCard;
import type.lib.RewardCard;
import type.lib.GlobalCredit;
public class Polymorphism
{
public static void main(String[] args)
{
PrintStream output = System.out;
GlobalCredit gc = new GlobalCredit();
// Randomly populate the GlobalCredit collection
// with credit and reward cards.
final int NUM_CARDS = 10;
Random rng = new Random();
for (int i = 1; i <= NUM_CARDS; i++)
{
if (rng.nextBoolean())
{
// Ok, add takes a CreditCard
gc.add(new CreditCard(i, "Some Name"));
}
else
{
// Ok, add takes a CreditCard and
// RewardCard is substitutable for CreditCard
gc.add(new RewardCard(i, "Another Name"));
}
}
// Charge $100 to every card in gc and
// output the card
for (CreditCard cc = gc.getFirst();
cc != null;
cc = gc.getNext())
{
cc.charge(100.00);
output.println(cc.toString());
}
}
}
If you compile and run the program, you will find that the
CreditCard
s and RewardCard
s behaved
differently, even though the same methods (charge
and toString
) were used. The Java Virtual Machine
is able to determine the proper version of these methods based
on the actual type of the object that cc
refers
to (either a CreditCard
or a RewardCard
).
A list supports indexed access to its elements, and the
client can control the ordering of elements in a list. The
default choice for a list is ArrayList
.
A set enforces uniqueness of its elements, and the client
cannot directly modify the ordering of elements in a set.
The default choice for a set is HashSet
unless
the elements of the set must be sorted, in which case the
choice is TreeSet
.
Lists and sets both implement the Collection
interface.
A map uses a key to store and retrieve a value (you can think
of a map as storing the pair key-value). The keys must
be unique (they form a set), but the values can be duplicated
(they form a collection). The default choice for a map is
HashMap
unless the keys of the map must be sorted,
in which case the choice is TreeMap
.
Maps do not implement the Collection
interface.
There are two methods for traversing the elements of any collection.
The easiest way to traverse a collection is to use a for-each loop. You can use this approach if you want to traverse only one collection, and if you do not have to remove any elements from the collection.
The following program reads a line of text from the user, separates the line into words using a tokenizer, and adds the words to a collection. It then traverses the collection, printing the number of letters in each word and each word.
import java.io.PrintStream;
import java.util.Scanner;
import java.util.StringTokenizer;
import java.util.Collection;
import java.util.ArrayList;
public class ForEachLoop
{
public static void main(String[] args)
{
PrintStream output = System.out;
Scanner input = new Scanner(System.in);
Collection<String> coll = new ArrayList<String>();
output.print("Enter a line of text: ");
String aLine = input.nextLine();
StringTokenizer tok = new StringTokenizer(aLine);
while (tok.hasMoreTokens())
{
coll.add(tok.nextToken());
}
// for each String s in coll
for (String s : coll)
{
output.printf("%2d letters : %s%n", s.length(), s);
}
}
}
If you need to traverse more than one collection at the same time, access more than one element in the loop, or remove an element from the collection, you must use iterators.
The following program fills a list with 10 random integers between 0 and 100, and computes the average. It then removes all of the elements that are less than the average value from the list using an iterator-based traversal.
import java.io.PrintStream;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;
public class IteratorLoop
{
public static void main(String[] args)
{
PrintStream output = System.out;
// Create list of random numbers with
// values between 0 and 100 inclusive
Random rng = new Random();
final int NUM_ELEMENTS = 10;
final int MAX_NUMBER = 100;
double average = 0.0;
List<Integer> numbers = new ArrayList<Integer>();
for (int i = 0; i < NUM_ELEMENTS; i++)
{
int num = rng.nextInt(MAX_NUMBER + 1);
numbers.add(num);
average += num;
}
average /= NUM_ELEMENTS;
output.println("Values : " + numbers.toString());
output.println("Avg value : " + average);
// remove all elements smaller than the average
Iterator<Integer> iter = numbers.iterator();
while (iter.hasNext())
{
int num = iter.next();
if (num < average)
{
iter.remove();
}
}
output.println("New values: " + numbers.toString());
}
}
You cannot traverse a map, because its interface does not support traversal. Instead, you must ask the map for its set of keys, and traverse the keys to retrieve the values:
The following program reads in a text file and counts the number of times each unique word appears in the text file. It does so by using each word as a key in a map. It then outputs each word and its word count by traversing the key set of the map and retrieving the word count corresponding to each word.
The text file used in this example.
import java.io.*;
import java.util.Scanner;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
public class WordCount
{
public static void main(String[] args) throws IOException
{
Map<String, Integer> wordCount =
new HashMap<String, Integer>();
Scanner fInput = new Scanner(new File("hogfather.txt"));
while (fInput.hasNext())
{
String word = fInput.next().toLowerCase();
// remove punctuation
// (except for hyphens and apostrophes)
String regex = "[^a-z-']";
word = word.replaceAll(regex, "");
// is word already in the map?
Integer count = wordCount.get(word);
if (count == null)
{
wordCount.put(word, 1);
}
else
{
wordCount.put(word, count + 1);
}
}
// get the set of words in the map
Set<String> words = wordCount.keySet();
// for each String word in words
for (String word : words)
{
System.out.printf("%-15s", word);
Integer count = wordCount.get(word);
System.out.println(" " + count);
}
}
}