App
)action.text
Each entry in the actions file consists of an action A
(for add) or S
(for subtract), followed by the amount to be added/subtracted
from the value.
import java.io.File; import java.util.Scanner; class App { public static void main(String [] args) throws Exception { Scanner scanner = new Scanner(new File("actions.text")); int value = 0; while (scanner.hasNext()) { String action = scanner.next(); int amount = scanner.nextInt(); if (action.equals("A")) { value += amount; System.out.println("Value after add of " + amount + ": " + value); } else if (action.equals("R")) { value -= amount; System.out.println("Value after removal of " + amount + ": " + value); } } } }
Scanner
object, reading in the actions and amount,
and testing for termination
value
) as well as equally generic action descriptions
(add and subtract). The latter is not surprising, given the former has no real semantics.
actions.text
A 200
S 50
S 70
A 10
Initial value: 0 Value after add of 200: 200 Value after subtraction of 50: 150 Value after subtraction of 70: 80 Value after add of 10: 90
01-BankAccount
)deposit
and withdraw
, both of which accept a balance and an amount
and returns the amount added-to/subtracted-from the balance respectively. The main
method should illustrate the methods
by reading in actions (deposit and withdrawal) from a while and apply them to the account.
import java.io.File; import java.util.Scanner; class BankAccountApp { public static void main(String [] args) throws Exception { Scanner scanner = new Scanner(new File("bank_actions.text")); int balance = 0; while (scanner.hasNext()) { String action = scanner.next(); int amount; switch (action) { case "D": amount = scanner.nextInt(); balance = deposit(balance, amount); System.out.println("Balance after deposit of $" + amount + ": " + balance); break; case "W": amount = scanner.nextInt(); balance = withdraw(balance, amount); System.out.println("Balance after withdrawal of $" + amount + ": " + balance); break; default: System.out.println("*** Error *** unknown action: " + action); } } } public static int deposit(int balance, int amount) {return balance + amount;} public static int withdraw(int balance, int amount) {return balance - amount;} }
App
) modulo the names of the variables, and the
extraction of the deposit/withdrawal logic into methods)
switch
just to show a switch
(it's also the right construct to
use in this context).
balance
) puts a whole new face on the
app
static
(we're going to discuss this more later)
public static void deposit(int balance, int amount) {balance += amount;}
static void f(int i) {
i++;
}
The call to f:
int i = 10; f(i); // does NOT change i System.out.println(i); // prints 10
return
statement.
static int f(int i) {
return i+1;
}
The call to f:
int i = 10; i = f(i); // assigns the new (returned value to i) System.out.println(i); // prints 11
main
balance += amount
balance
needs to be sent to the methods together with the amount of the transaction (and then assigned its new value from the return value of the method)
static
public static int withdraw(int balance, int amount) { if (balance >= amount) return balance - amount; else return balance; }
import java.io.File; import java.util.Scanner; class FuelTankApp { Scanner scanner = new Scanner(new File("fueling_actions.text")); int tank = 0; System.out.println("Initial tank: " + tank + " gallons"); while (scanner.hasNext()) { String action = scanner.next(); int gallons; switch (action) { case "F": gallons = scanner.nextInt(); tank = fill(tank, gallons); System.out.println("Tank after fueling " + gallons + ": " + tank + " gallons"); break; case "C": gallons = scanner.nextInt(); tank = consume(tank, gallons); System.out.println("Tank after consuming " + gallons + ": " + tank + " gallons"); break; default: System.out.println("*** Error *** unknown action: " + action); } } } public static int fill(int tank, int gallons) {return tank + gallons;} public static int consume(int tank, int gallons) {return tank - gallons;} }
class BankAccount { public static int deposit(int balance, int amount) {return balance + amount;} public static int withdraw(int balance, int amount) {return balance - amount;} }
import java.io.File; import java.util.Scanner; class BankAccountApp { public static void main(String [] args) throws Exception { Scanner scanner = new Scanner(new File("bank_actions.text")); int balance = 0; while (scanner.hasNext()) { String action = scanner.next(); int amount; switch (action) { case "D": amount = scanner.nextInt(); balance = BankAccount.deposit(balance, amount); System.out.println("Balance after deposit of $" + amount + ": " + balance); break; case "W": amount = scanner.nextInt(); balance = BankAccount.withdraw(balance, amount); System.out.println("Balance after withdrawal of $" + amount + ": " + balance); break; default: System.out.println("*** Error *** unknown action: " + action); } } } }
class BankAcct { static int deposit(… …we could invoke this method from elsewhere (e.g.
main
via:
BankAccount.deposit(…as you can see in the above
BankAccountApp
code
main
is busy dealing with the value of the 'thing' we are modelling.
Now imaging an app which requires bank accounts AND fuel tanks. main
would have to make
sure it properly handles the two things, i.e., not sending a balance to a fillUp method, or a fuel tank level
to a withdraw method.
A more realistic scenario would be two different sorts of bank accounts:
class SavingsAccount { public static int deposit(int balance, int amount) {return balance + amount;} public static int withdraw(int balance, int amount) {return (amount <= balance) ? balance - amount : balance;} }
class CheckingAccount { public static int deposit(int balance, int amount) {return balance + amount;} public static int withdraw(int balance, int amount) {return balance - amount;} }
import java.io.File; import java.util.Scanner; class BankAccountApp { public static void main(String [] args) throws Exception { Scanner scanner = new Scanner(new File("bank_actions.text")); int savingsBalance = 0, checkingBalance = 0; while (scanner.hasNext()) { String action = scanner.next(); int amount; switch (action) { case "SD": amount = scanner.nextInt(); savingsBalance = SavingsAccount.deposit(savingsBalance, amount); System.out.println("Balance after deposit of $" + amount + ": " + savingsBalance); break; case "SW": amount = scanner.nextInt(); savingsBalance = SavingsAccount.withdraw(savingsBalance, amount); System.out.println("Balance after withdrawal of $" + amount + ": " + savingsBalance); break; case "CD": amount = scanner.nextInt(); checkingBalance = CheckingAccount.deposit(checkingBalance, amount); System.out.println("Balance after deposit of $" + amount + ": " + checkingBalance); break; case "CW": amount = scanner.nextInt(); checkingBalance = CheckingAccount.withdraw(checkingBalance, amount); System.out.println("Balance after withdrawal of $" + amount + ": " + checkingBalance); break; default: System.out.println("*** Error *** unknown action: " + action); } } } }
main
method trying to work with 2 Savings Accounts and three Checking Accounts.
import java.io.File; import java.util.Scanner; class BankAccountsApp { public static void main(String [] args) throws Exception { Scanner scanner = new Scanner(new File("bank_actions.text")); final int CAPACITY = 100; int size = 0; int [] balances = new int[CAPACITY]; int [] ids = new int[CAPACITY]; while (scanner.hasNextInt()) { int id = scanner.nextInt(); String action = scanner.next(); int amount; int index = search(ids, size, id); if (index == -1) { size = add(ids, balances, size, id, CAPACITY); index = size-1; System.out.println("Added account " + id + " with balance of $0 at location " + index); } switch (action) { case "D": amount = scanner.nextInt(); balances[index] = BankAccount.deposit(balances[index], amount); System.out.println("Balance of account " + id + " after deposit of $" + amount + ": $" + balances[index]); break; case "W": amount = scanner.nextInt(); balances[index] = BankAccount.withdraw(balances[index], amount); System.out.println("Balance of account " + id + " after withdrawal of $" + amount + ": $" + balances[index]); break; default: System.out.println("*** Error *** unknown action: " + action); } } System.out.println(); print(ids, balances, size); } static int search(int [] ids, int size, int id) { for (int i = 0; i *lt; size; i++) if (ids[i] == id) return i; return -1; } static int add(int [] ids, int [] balances, int size, int id, int capacity) { if (size == capacity) { System.err.println("Capacity reached; make arrays larger"); System.exit(1); } ids[size] = id; balances[size] = 0; return size+1; } static void print(int [] ids, int [] balances, int size) { for (int i = 0; i *lt; size; i++) System.out.println(ids[i] + ": $" + balances[i]); } }
BankAccount
class with the two methods is as above
print
method to print out the accounts after all action processing is completed
print
methods requires all the information about the bank account: the id and the balance, and thus
two arrays need to be sent to the method (only one size is necessary as they are parallel arrays). If there was more information
accociated with the account (e.g., overfraft limits), an array would be sent as an argument for each such piece of information.
print
method is making its own decision on what it means to print out an account.
public static void deposit(int [] balance, int index, int amount) {balances[index] += amount;}we could then call it as follows:
BankAccount.deposit(balances, index, amount);
BankAccount
class … it limits itself to
elements of an array of BankAccount
BankAccounts
class (which handles arrays of BankAccount
s) and have that method
call the corresponding method in BankAccount
:
// Inthis makes the interface cleaner for users ofBankAccounts
void deposit(nt [] balances, int index, int amount) { balances[index] = BankAccount.deposit(balances[index], amount); the usualBankAccount
method }
BankAccounts
(which is probably the usual way people use BankAccount
,
i.e., via an array of them
BankAccount
app, main
is doing way more than it should; in that case, maintaining the bank account's data
(the balance); here, main
is responsible for the array, again, too much.
main
is dealing with: a bank balance on in the one case,
and a collection of bank accounts in the other
actions.text
1001 D 200 1030 D 100 1030 W 50 1001 W 70
Added account 1001 with balance of $0 at location 0 Balance of account 1001 after deposit of $200: $200 Added account 1030 with balance of $0 at location 1 Balance of account 1030 after deposit of $100: $100 Balance of account 1030 after withdrawal of $50: $50 Balance of account 1001 after withdrawal of $70: $130 1001: $130 1030: $50
Here are the semantics of defining and using a class in a nutshell:
new
keyword
public class BankAccount { public void deposit(int amount) {balance += amount;} public void withdraw(int amount) {balance -= amount;} public int getBalance() {return balance;} private int balance = 0; }
balance
,
are called instance variables
private
indicates that the only methods that can access the variable (balance) are the methods of the
class itself.
public
means the method may be called from outside the class.
balance
is initialized at the point of declaration to 0.
getBalance
is added to allow outside code (main
in our case) to
get information from the object (the variable cannot be accessed directly since it is private).
Here is the app using the class:
import java.io.File;
import java.util.Scanner;
class BankAccountApp {
public static void main(String [] args) throws Exception {
Scanner scanner = new Scanner(new File("bank_actions.text"));
BankAccount account = new BankAccount();
while (scanner.hasNext()) {
String action = scanner.next();
int amount;
switch (action) {
case "D":
amount = scanner.nextInt();
account.deposit(amount);
System.out.println("Balance after deposit of $" + amount + ": " + account.getBalance());
break;
case "W":
amount = scanner.nextInt();
account.withdraw(amount);
System.out.println("Balance after withdrawal of $" + amount + ": " + account.getBalance());
break;
default:
System.out.println("*** Error *** unknown action: " + action);
}
}
}
}
new
and use the resulting reference to
initialize the reference variable
receiver.method(arguments)for example:
account.deposit(100)
deposit(100)
to the receiving object (i.e., the receiver) account
.
SavingsAccount
and CheckingAccount
examples using classes. We will also introduce the approach for printing out
objects in general.
getBalance
was to allow the app to access the balance in order to print it.
toString
in the class:
public String toString();
println
to print out the 'value' of the object.
class SavingsAccount {
public void deposit(int amount) {balance += amount;}
public void withdraw(int amount) {if (amount <= balance) balance -= amount;}
public String getBalance() {return "$" + balance;}
private int balance = 0;
}
class CheckingAccount {
public void deposit(int amount) {balance += amount;}
public void withdraw(int amount) {balance -= amount;}
public String getBalance() {return "$" + balance;}
private int balance = 0;
}
toString
is generally defined on every class
String
($
in this case) with a non-String (the int balance
here),
the non-String is converted to String
import java.io.File; import java.util.Scanner; class BankAccountApp { public static void main(String [] args) throws Exception { Scanner scanner = new Scanner(new File("bank_actions.text")); SavingsAccount savingsAccount = new SavingsAccount(); CheckingAccount checkingAccount = new CheckingAccount(); while (scanner.hasNext()) { String action = scanner.next(); int amount; switch (action) { case "SD": amount = scanner.nextInt(); savingsAccount.deposit(amount); System.out.println("Balance after deposit of $" + amount + ": " + savingsAccount); break; case "SW": amount = scanner.nextInt(); savingsAccount.withdraw(amount); System.out.println("Balance after withdrawal of $" + amount + ": " + savingsAccount); break; case "CD": amount = scanner.nextInt(); checkingAccount.deposit(amount); System.out.println("Balance after deposit of $" + amount + ": " + checkingAccount); break; case "CW": amount = scanner.nextInt(); checkingAccount.withdraw(amount); System.out.println("Balance after withdrawal of $" + amount + ": " + checkingAccount); break; default: System.out.println("*** Error *** unknown action: " + action); } } } }
println
is overloaded to accept any object as its argument, in which case it invokes the object's toString
method and
prints the resulting String
.
static
keyword from their header — see below). Most of the other changes are then a consequence of this change.
Summarizing the introductory bank account example above:
main
(where it was a local variable)
and into the body of the BankAccount
class.
classname identifier = new classname(…);for example:
BankAccount bankAccount = new BankAccount();
static
on the method headers; these are known as instance methods and
will operate on a particular instance's variables.
bankAccount.deposit(amount);
balance
that is used in the method is the one associated with the instance referenced by bankAccount
private
public
so that they can be invoked from outside the class.
toString
. This method returns a String
representation of our object
balance
preceded by a $
toString
must be declared public
main
does not implement an operation of the class; rather it is a method that uses the class
main
in the following manner:
BankAccount bankAccount = mew BankAccount(); bankAccount.main();
main
a class that we are using to illustrate use of our BankAccount
class.
main
is 'separate' from the class (i.e., it does not operate directly on the instance variables
of the class), and to indicate that, we declare it static
BankAccount
objects:
BankAccount account1 = new BankAccount(); BankAccount account2 = new BankAccount();
BankAccount
objects:
BankAccount [] accounts = new BankAccount[100];
object.method(arguments)
length
field of an array
class classname { methods variables }
public class Name { public String getFirst() {return first;} // Behavior public String getLast() {return last;} public void setFirst(String f) {first = f;} public void setLast(String l) {last = l;} private String first, last; // State }
public class Counter {
public up() {val++;} // Behavior
public int getVal() {return val;}
private int val = 0; // State
}
getFirst
/getLast
are known as getter or accessor
methods, while setFirst
/setLast
are called setter or mutator methods.
first
or val
we are referring to
those instance variables or the object upon which the method was invoked; e.g. if we had written
name.getFirst()
(i.e., calling getFirst
on the objectname, the reference to
first
in the method is thefirst
instance variable associated with the instance name
.
Name name1, name2; Counter c1;
SomeClass sc = new SomeClass();
new
operator
new Name() new Counter()
new
is done, it returns a reference to the newly created
object.
Name name = new Name(); Counter c = new Counter();
Name name1 = new Name(), name2 = new Name(); Counter c1 = new Counter(), c2 = new Counter();
name1
and name2
are two instance with their own copies of the first
and last
instance variables of the class.
c1
and c2
are two instance with their own copies of the val
instance variable of the class.
name1.getFirst(); // name1 is the receiver here, getFirst will access name1's instance variables name2.getLast(); // name2 is the receiver here, getLast will access name2's instance variables System.out.println(c1.getVal()); // c1 is the receiver System.out.println(c2.getVal()); // c2 is the receiver
public static void main(String [] args) { Name name1 = new Name(); name1.setLast("Arnow"); name1.setFirst("David"); System.out.println(name1.getFirst()); System.out.println(name1.getLast()); Name name2 = new Name(); name2.setLast("Weiss"); name2.setFirst("Gerald"); System.out.println(name2.getFirst()); System.out.println(name2.getLast()); Name name3 = name1; System.out.println(name3.getFirst()); System.out.println(name3.getLast()); }
public static void main(String [] args) { Counter c = new Counter(); for (int i = 1; i <= 10; i++) c.up(); System.out.println(c); }
class NameApp { public static void main(String [] args) { Name name1 = new Name(); … } }
class CounterApp { public static void main(String [] args) { Counter c = new Counter(); … } }
class Name { … public static void main(String [] args) { Name name1 = new Name(); … } }
class Counter { … public static void main(String [] args) { Counter c = new Counter(); … } }
main
method in a separate app class
main
main
Method as Appmain
method which must be declared as
public static void main(String [] args) { body }
main
method. Execution begins at that method.
reference
types/values/variables and primitive
types/values/variables.
operators
: +
, *
,
!
, &&
, etc..
Name name1 = new Name("Weiss", "Gerald"); Name name2 = name1;
name1
and name2
both contain references to it
new
is the only way of creating objects
Name name1 = new Name("Weiss", "Gerald"); Name name2 = new Name("Weiss", "Gerald");
Name
example, equality means having the same last and first names
equals
is usually defined (by the class designer) to test for (semantic) equality):
name1.equals(name2)
Name name1 = new Name("Weiss", "Gerald"); Name name2 = new Name("Arnow", "David");
BankAccount account = new BankAccount(); account.setBalance(100);
balance
instance variable is initialized to 0 at the point of the instance variable's declaration.
setBalance
now opens the door for any code
to modify balance
, toally violating encapsulation
setBalance
after the creation of the object , rather than
having the balance initialized when the object is created.
BankAccount account = new BankAccount(); account.setBalance(100);While this preserves encapsulation (i.e., we are still limited to performing a 'sanctioned' operation , i.e.,
deposit
, rather than
the 'free-for-all' setBalance
), there are still some drawbacks to this approach:
deposit
after object creation
Constructors
— Initializing Objectsnew
creates the object it invokes a method, known as a constructor
that initializes the object
(i.e., its instanceV variables)
Counter c = new Counter();
Name name = new Name();
new
-- the programmer does not call it directly.
new
passing it
the name of the class whose object is to be created, as well as the arguments to the constructor which will be called
as part of the creation process.
Counter() {val = 0;}
Name() { first = ""; last = ""; }
class Counter { … private int val = 0; }
class Name { … private String first = "", last = ""; }in which case, there would be no need for any code in the constructor. In such an instance, we can often omit the constructor entirely (more on that below).
default constructors
Name(String l, String f) { last = l; first = f; )
Counter
class, one might want to be able to create a counter with an initial value, or simply
create a counter whose value defaults to 0.
class Counter { … private int val; // value of the counter }
Counter c1 = new Counter();while the former case would be:
Counter c2 = new Counter(10); // create a Counter instance with an initial value of 10
Counter(int v) {val = v;} Counter() {val = 0;}However, this approach often leads to duplication of logic, and if the logic is involved or lengthy that leads to maintainability and readability issues.
Point
: we create Point
objects by specifying their x and y values. One could imagine that
not specifying any x/y might create a point at the origin (0, 0):
Point() { this.x = 0; this.y = 0;}
this
Counter(int v) {val = v;}
Counter() {this(0);}
this
to specify that we are calling
another constructor from the current constructor.
this(0)
, it is clear to the compiler that we are
invoking a different constructor (the one that accepts an int parm).
this
later on.
v
vs val
; f
, first
, last
).
this
to refer to the receiving object within a method
this
informs the compiler (and us) to skip the locals and begin the resolution process at the receiving object.
Point
: we create Point
objects by specifying their x and y values. One could imagine that
not specifying any x/y might create a point at the origin (0, 0):
Point() { this.x = 0; this.y = 0; }
Point() {this(0, 0);}
x
and y
to 0 by calling the two-argument constructor (the one we
placed in the Point
class above)
this
(rather than Point
to indicate that we are invoking another constructor of the object we are initializing.
Point p = new Point(10, 20); Point p2 = p;
p2
become an alias of p
, i.e., the two reference variables are now referring to the same object.
p
and p2
refer to separate objects), we can write:
Point p2 = new Point(p.getX(), p.getY());but that requires:
getX
and getY
, and
Point
objects
p
to a new object and have p2
refer to it,
i.e.:
Point p2 = new Point(p);
Point(Point p) { x = p.x; y = p.y; }or (leveraging):
Point(Point p) {this(p.x, p.y);}
toString
MethodtoString
defined by their designers.
public String toString()
==
and the equals
Methodnull
— see below — in the absence of such a reference)
==
operator compares the primitive values, which is the correct semantics; i.e.,
i == 4, d == 12.4
==
operator also compares the values, however, in this case, it's a reference
so we are effectively comparing if the two value (references) refer to the same object.
equals
instance method for the class:
Counter
class this might mean whether the val
fields are equal
public boolean equals(Counter otherCounter) {return val == otherCounter.val;}
Name
class this might mean whether the first and last name fields are equal:
public boolean equals(Name otherName) {return this.first.equals(otherName.first) && this.last.equals(otherName.last);}
public boolean equals(Name otherName) {return first.equals(otherName.first) && last.equals(otherName.last);}
equals
is more than a 'good' suggestion; later we will understand why it
should (must?) be named null
valuenull
in Java is a literal representing the absence of a reference.. When assigned to a reference variable it signals that the variables
is not pointing at any object.
BankAccount ba = null;
null
may be assigned to a reference variable of any type (i.e., any class or array type).
null
results in a NullPointerException
being thrown.
BankAccount ba = null;
ba.deposit(100); // results in NullPointerException
null
in the absence of any explicit initialization:
class SomeClass { … private String s; //s
is initialized tonull
null
if the object does not appear inthe array.
static
for the first time in a context we can explain.
read
Methodstatic
method — we'll call it read
that accepts a Scanner
and returns an object of the class for which we are writing
this method
null
class BankAccount { … static BankAccount read(Scanner scanner) { if (!scanner.hasNextInt()) return null; int id = scanner.nextInt(); int balance = scanner.nextInt(); return new BankAccount(id, balance); } … private int id; private int balance; }
read
method creates and object from input, upon entry to the method, no such object yet exists.
static
null
is returned
read
MethodBankAccount ba = BankAccount.read(scanner); while (ba != null) { System.out.println(ba); ba = BankAccount.read(scanner);
null
is returned