tvata
BankAccount Class Again
class BankAccount {
public void deposit(int amount) {balance += amount;} // no static
public void withdraw(int amount) { // no static
if (status.equals("CLOSED")) {
System.out.println("Withdrawal denied: account is CLOSED");
return;
}
balance -= amount;
}
public void print() {
System.out.println("Account #: " + accountNumber);
System.out.println("Owner: " + owner);
System.out.println("Balance: " + balance + " cents");
System.out.println("Status: " + status);
System.out.println("Opened: " + openedDate);
}
int accountNumber;
String owner;
int balance;
String status;
String openedDate;
}
import java.util.*;
import java.util.Scanner;
public class BankAccountApp {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
BankAccount account = new BankAccount();
account.accountNumber = readAccountNumber(in);
account.owner = readOwner(in);
account.balance = readBalance(in);
account.status = readStatus(in);
account.openedDate = readOpenedDate(in);
account.deposit(5000);
account.withdraw(2000);
account.print();
}
// ----- Input helpers -----
static int readAccountNumber(Scanner in) {
System.out.print("Enter account number: ");
return in.nextInt();
}
static String readOwner(Scanner in) {
System.out.print("Enter owner name: ");
return in.next();
}
static int readBalance(Scanner in) {
System.out.print("Enter starting balance (cents): ");
return in.nextInt();
}
static String readStatus(Scanner in) {
System.out.print("Enter status (ACTIVE/CLOSED): ");
return in.next();
}
static String readOpenedDate(Scanner in) {
System.out.print("Enter opened date: ");
return in.next();
}
}
new operator
BankAccount = new BankAccount();
object.method(arguments)
length field of an array
BankAccount instance … new BankAccount merely allocates memory for the object
BankAccount account = new BankAccount
new operator
new is classified as an operator (the operand is the class name and arg list that follows)); as such
it forms an expression that evaluates to a value (the reference)
int
account.accountNumber = readAccountNumber(in); account.owner = readOwner(in); account.balance = readBalance(in); account.status = readStatus(in); account.openedDate = readOpenedDate(in);
with a possible set of input values:
10010 Weiss 0 ACTIVE 01/01/2026it is because the app is cognizant of the semantics of the application, i.e., that it (or actually the programmer who wrote it) knows what the id, owner, date opened, etc. should be initialized to.
BankAccount account = new BankAccount(); account.print(); account.deposit(5000); account.accountNumber = readAccountNumber(in); account.owner = readOwner(in);
print on the receiving object account, but the object has no been initialized (at least not properly).
status to deal with
public class Counter {
up() {val++;} // Behavior
down() {val--;}
private int val = 0; // State
}
0. When the object is created, this initialization is performed
prior to returning the reference to the object (which is then typically assigned to a reference variable as shown above).
class consisting of a first and last name.
- The values of the first and last names are not known until an instance is actually created.
- This is different than the
Counter class where the val instance variable is always initialized to 0 and thus could be done
at the point of declaration)
public class Name {
String getFormal() {return last + ", " + first;}
String getStandardl() {return first + " " + last;}
String getInitials() {return first.substring(0, 1) + "," + last.substring(0, 1);}
String first, last; // State
}
- As mentioned above, the values of
first and last change from instance to instance,
and are typically unknown until a particular instance is created, so point of declaration initialization
is inapplicable.
- We need an mechanism that will allow us to perform initialization at the point of creation of the object,
before its reference is passed back to the called
We need something other than Initialization at the Point of Declaration to guarantee a valid object upon creation
Constructors
Our solution comes from providing a method that initializes the object's instance variables to valid values that are supplied by the
creator of the object.
- This method must be called automatically, i.e., without an explicit call from the object creator.
- A constructor is a method of a class that is invoked implicitly after an object of the class is created
- The arguments to the constructor are typically values used to initialize the instance variable of the object
- The method name of a constructor is that of the class. This makes sense as:
- The object creator won't be calling the method so there's no reason to give them a choice of name
- The syntax suggests a method call (though, as we'll see it's important to realize it's not a method call
on the part of the creator.
Here is an exaple applie to our Name class:
public class Name {
Name(String l, String f) {
last = l;
first = f;
}
dd
String getFormal() {return last + ", " + first;}
String getStandardl() {return first + " " + last;}
String getInitials() {return first.substring(0, 1) + "," + last.substring(0, 1);}
String first, last; // State
}
- Notice there is no return type (not even
void) specified in the constructor's method header;
it is not being called by the creator of the object, so there' no one to return anything to
- The names of the parameters (i.e.,
f and l are pretty awful; we'll see how to avoid that later.
and here is a sample usage:
Name name = new Name("Weiss", "Gerald"); // Upon return, the Name object is properly initialized
System.out.println(name.getFormal(); // Weiss, Gerald
System.out.println(name.getStandard(); // Gerald Weiss
System.out.println(name.getiInitials(); // G.W.
- Note the operand of
new — Name("Weiss", "Gerald") — looks like a method call to the
constructor, but what it really is is a specification of the name of the class whose object is being created (Name) followed by
the arguments to the constructor (which will be called after the object is created).
- This is a subtle point, but not crucial to you writing proper code at the moment; after a while the difference will
become more understandable.
(As an aside, there's nothing that prevents us from creating nonsensical Names as long as we supply
a first and last String to the constructor:
Name name = new Name("", "**!!");
we will deal with this later.)
Returning to Our BankAccount
Here is a constructor analogous to the one for Name. i.e., one argument per instance variable:
class BankAccount {
BankAccount(int initId, String initOwner, int initBalance, String initiStatus, String initOpenedDate) {
id = initId;
owner = initOwner;
balance = initBalance;
status = initStatus;
openedDate = initOpenedDate;
}
public void deposit(int amount) {balance += amount;} // no static
public void withdraw(int amount) { // no static
if (status.equals("CLOSED")) {
System.out.println("Withdrawal denied: account is CLOSED");
return;
}
balance -= amount;
}
public void print() {
System.out.println("Account #: " + accountNumber);
System.out.println("Owner: " + owner);
System.out.println("Balance: " + balance + " cents");
System.out.println("Status: " + status);
System.out.println("Opened: " + openedDate);
}
int accountNumber;
String owner;
int balance;
String status;
String openedDate;
}
- Again, while this looks like a call (invocation) of the constructor, the correct way to view it is:
- The object is created via the application of
new
- After the object is created, the compiler invokes the constructor passing it the arguments
- A reference to initialized object is returned as the result of the
new
Repeating … the above syntax does not represent a call to the constructor; rather it is a call to 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.
- And as before, the names of the parameters (e.g.
initId is pretty awful; to some extent eve worse than before.
Here is some code to create a BankAccount object and have it initialized with caller-supplied values:
BankAccount account = new BankAccount(10010, "Weiss", 0, "ACTIVE", 01/01/2026");
- Notice
balance is initialized to 0; that did not have to be the case:
- If we wished the semantics of our account to allow an initial non-zero balance, we could pass some other value (say 100)
to the constructor
BankAccount account = new BankAccount(10010, "Weiss", 100, "ACTIVE", 01/01/2026");
- If all accounts are to start with a 0 balance, we could omit it from the constructor parameter list. We would then have two options:
- Have
balance be initialized at point of declaration:
class BankAccount {
BankAccount(int initId, String initOwner, String initiStatus, String initOpenedDate) {
id = initId;
owner = initOwner;
status = initStatus;
openedDate = initOpenedDate;
}
BankAccount
…
int balance = 0;
…
}
or
- Initialize
balance to 0 in the constructor:
class BankAccount {
BankAccount(int initId, String initOwner, String initiStatus, String initOpenedDate) {
id = initId;
owner = initOwner;
balance = 0;
status = initStatus;
openedDate = initOpenedDate;
}
…
int balance;
…
}
- Either is fine; notice that the parameter list no longer has a balance parm
- Assuming balance is always initialized to 0, the would be no need for an argument for it in the constructor (as seen above)
- Actually, several other of the instance variable need not / should not be initialized by the object creator:
status: the object creator should have no control (or knowledge) of this instance variable; it should be set by constructor without external input
- Similarly,
accountNnumber should not be initialized by the object creator, but rather internally by the claslogic
openedData: this should be probably be assigned today's date; regardless, it's definitely not the object creator's responsibility of place to initialize it
- In fact, the only real value externally supplied should be the owner:
class BankAccount {
BankAccount(String initOwner) {
owner = initOwner; // supplied by object creator
id = next available account number can't code this yet
}
…
int balance = 0; // always 0
status = "ACTIVE";
openedDate = new java.util.Date() + ""; // converts Date value for 'now' to a String
…
}
- Some instance variables (
owner) require input from the object creator
- Others take default values (such a
0 for balance and ACTIVE
for .
- Yet others may require logic/methods calls one the part of the class internals; e.g.
openedDate
involves the creation of a Date object (that is then converted to a String,
while id requires a call to a method of the type we don't quiteknow how to write (yet).
- Sometimes this last category can be done at the point of declaration; other time it may need to be
done within the body of the constructor.
- We will be examining this in more detail later.
- We sometimes use the term primary constructor for the constructor that establishes a valid state for the object; accepting any necessary
information from outside the class.
- In consequence, you cannot create a (valid)
BankAccount object without supplying the initial values specified in the constructors parameter list
- The constructor is thus enforcing the rules of object creation for the class
- Since the constructor is automatically called as part of the object creation process, there is no way to avoid it accidentally or not
- Of course one could supply invalid values to the constructor (e.g. a negative balance); we will address that later.
- We will address this issue later once we have the basic mechanics of class definition in place.
- This is the same issue as creating a 'nonsensical'
Name object
Maintaining the Validity of the Object (Encapsulation)
Constructors now provide a way of ensuring we can creating a valid object of a class that will be available to the caller as soon
as the object is created:
Name name = new Name("Weiss", "Gerald");
BankAccount account = new BankAccount("Weiss");
… the question now becomes how do we maintain that validity:
name.last = "";
account.status = "CLOSED"; // 'unofficial' change of status
account.balance += 10000; // 'rogue' deposit of $100
The problem is that the object has no control over how its state is modified., i.e., the user is able to modify the object's state directly,
bypassing any of the rules of the class (such as status or overdraft checking)
We solve this by giving the class exclusive control over access to its internal state. This principle is known as encapsulation
and is enforced through access control.
Access Control
- Java uses access modifiers to specify which fields and methods of a class are visible outside the class.
- These modifiers include the keywords
public and private
public means that the variable or method is accessible from code outside the class.
private indicates that the variable or method is accessible only from within the class itself.
- In general, data is declared private and methods public.
BankAccount Again
class BankAccount {
public BankAccount(String initOwner) {
owner = initOwner; // supplied by object creator
id = next available account number can't code this yet
}
public void deposit(int amount) {balance += amount;} // no static
public void withdraw(int amount) { // no static
if (status.equals("CLOSED")) {
System.out.println("Withdrawal denied: account is CLOSED");
return;
}
balance -= amount;
}
public int getBalance() {return balance;}
public void print() {
System.out.println("Account #: " + accountNumber);
System.out.println("Owner: " + owner);
System.out.println("Balance: " + balance + " cents");
System.out.println("Status: " + status);
System.out.println("Opened: " + openedDate);
}
private int accountNumber;
private String owner;
private int balance = 0;;
private String status = "ACTIVE";
private openedDate = new java.util.Date() + ""; // converts Date value for 'now' to a String
}
class BankAccount {
public BankAccount(String initOwner, int initBalance, String initiStatus, String initOpenedDate) {
id = initId;
owner = initOwner;
balance = initBalance;
status = initStatus;
openedDate = initOpenedDate;
}
public void deposit(int amount) {balance += amount;} // no static
public void withdraw(int amount) { // no static
if (status.equals("CLOSED")) {
System.out.println("Withdrawal denied: account is CLOSED");
return;
}
balance -= amount;
}
public int getBalance() {return balance;}
public void print() {
System.out.println("Account #: " + accountNumber);
System.out.println("Owner: " + owner);
System.out.println("Balance: " + balance + " cents");
System.out.println("Status: " + status);
System.out.println("Opened: " + openedDate);
}
private int accountNumber;
private String owner;
private int balance;
private String status;
private String openedDate;
}
Constructor Overloading
In many situations, the semantics of a class may provide several ways to create an object; often they are related in some manner.
Rational: A Class for Rational Numbers
A rational number is one that can be expressed using integer numerator and denominator (i.e., the ratio between two integers).
o
- Instances of
Rational are thus typically created by supplying a pair of integers for the numerator and denominator:
class Rational {
Rational(int n, int d) {
num = n;
denom = d;
}
…
int num, denom;
}
and instance of the rational number 3/8 is thus created by:
Rational r = new Rational(3, 8);
- Integers are themselves rational numbers (with a denominator of 1). While we could always create an instance of a
Rational that is
(also) an integer using the above constructor:
Rational r = new Rational(5, 1);
that seems to be a bit cumbersome.
- We therefore introduce a second constructor that accepts a single argument corresponding to the numerator:
Rational(int n) {
num = n;
denom = 1;
}
this constructor may then be conveniently used when creating integer-valued objects:
Rational r = new Rational(5); // 5/1
- Recall, an overloaded method is one that shares the same name as one or more others, but has a different signature (parameter list).
Here we have an example of an overloaded constructor.
Leveraging/Delegation
- In the above 1-arg
Rational constructor, we repeated the logic of the 2-arg primary constructor, the only change was the denominator was hardcoded to 1.
- Note the two ARE related somewhat; they both initialize the numerator and denominator
- Being a relatively simple constructor this is not much of an issue; however, more complex class may have more complex logic in their constructors, and if they are
semantically related, it makes sense to avoid the duplication of logic
- We can do this by having the 1-arg constructor involve (call) the 2-arg version, passing
1 as the second argument (the one corresponding to the denominator in the
2-arg version):
Rational(int n) {this(n, 1);}
- We introduce and use the keyword
this to indicate that we are invoking another constructor of the same (Rational) class.
- A better way to say it is that the 1-arg constructor is forwarding initialization data to the 2-arg
- (The constructor is written on a single line due to MY personal style of doing so when 'appropriate'.)
- The relationship between the two constructors is:
- The two-argument constructor does the real work.
- The one-argument constructor leverages that implementation by delegating to it.
- We often refer to the constructor that performs the actual initialization as the workhorse constructor.
We will often encounter situations involving leveraging and delegation.
- Again the basic idea is to avoid duplicate logic
Another Example: A Name Class with a Middle Name
Not all names have a middle name.
class Name {
// Primary constructor
Name(String f, String m, String l) {
first = f;
middle = m;
last = l;
}
// Convenience constructor
Name(String f, String l) {
this(f, "", l); // delegate to the primary constructor
}
private String first;
private String middle;
private String last;
}
Default Constructors
A Window Class
Here is a class modelling a graphic window:
class Window {
public Window() {
width = 800;
height = 600;
x = 100;
y = 100;
title = "Untitled";
color = Color.GRAY;
opacity = 1.0;
}
private int width;
private int height;
private int x;
private int y;
private String title;
private Color color;
private double opacity;
}
Color is a class representing colors (we'' encounter it in a little bit). Besides methods (which we ignore here), it also has values, such as
Color.RED, Color.GREEN, etc.
opacity is a value representing the transparency of the window … a value from 0.0 (totally transparent) to 1.0 (totally opaque)
- Note the constructor accepts no arguments; rather default values are supplied by the class designer to create a 'reasonably' initialized
window.
- The could be other constructors in the class that allow the user (client) to initialize the instance variables to custom values. This constructor
allows the user (say a newbie or casual user) to avoid knowing the details of creating a window.
- A constructor that accepts no arguments is known as a default constructor.
Switch — Another Example
class Switch {
public Switch() {
on = false;
}
public void flip() {
on = !on;
}
private boolean on;
}
Another Flavor of Default Constructor
Sometimes default constructors arose as a result of a sequence of constructor argument reductions:
Back To Rational
- We already have 2-arg and 1-arg constructors for
Rational object creation
- The 'natural' progression is to allow a 0-arg constructor that would create the rational value
0 (actually 0/1):
Rational() {this(0);}
-
- note this constructor delegates to the 1-arg constructor (supplying a numerator of 0) which in turn delegates to the 2-arg constructor
(supplying the denominator of 1).
- Alternatively, the zero-argument constructor could delegate directly to the two-argument constructor:
Rational() {this(0, 1);}
- This would bypass the one-argument constructor in the delegation chain.
- This is legal, but it breaks the simple “stepwise” delegation structure we’ve been using.
While Rational() is a default constructor by virtue of it having no arguments, it arose as a result of a natural delegation chain
(numerator and denominator, numerator only, nothing) rather than a decision on the part of the class designer as to how to provide a reasonably-initialized default object.
- While the result is the same (a 0-arg constructor), they arise from very different motivations.
Finally, a default constructor is appropriate only when the class can establish a valid object without any information from the caller. That rules out:
- default BankAccount (need owner)
- default Name (needs first and last names)
Revisiting the Counter Class
Sometimes, which motivation is at play is not so clear:
class Counter {
public Counter(int initialValue) {
value = initialValue;
}
public Counter() {
this(0);
}
private int value;
}
The default constructor here straddles BOTH motivations:
- A natural (default) starting value for a counter would be 0
- At the same time, given the 1-arg initial value constructor, a natural delegation would be to have a
0-arg constructor that delegates 0 to the 1-arg.
Parameter Names for Constructors
Parameters to constructors typically initialize corresponding instance variables. Up until now we assigned the parameter as different name to avoid
shadowing
- Shadowing occurs when a parameter or local variable has the same name as an instance variable, temporarily hiding the instance variable within that scope
i.e., the method the local variable is declared in)..
- We thus coded:
class BankAccount {
BankAccount(String initOwner) {
owner = initOwner;
…
}
…
String owner;
}
- However, there is a natural correspondence between the parameter (
initOwner and the instance variable (owner) … the former is
used to initialize the latter. That relationship is best explicitly represented by them having the same name.
- To allow that, one can use
this as a way to refer to the instance variable and not the parameter:
BankAccount(String owner) {
this.owner = owner;
…
}
- The right hand side of the assignment is referring to the parameter.
- The left hand side — by virtue of the
this. qualifier refers to the instance variable.
- This is considered best practice in Java
One Last Item: The Implicit Default Constructor
- If no constructors are provided by the class designer, the compiler generates a default constructor that does no initialization
- At object creation time (i.e., before any constructors are called) the runtime initializes all instance variables to type-specific values
(e.g. 0 for
int, false for boolean, etc)
class Counter {
public void up() {val++;}
public void down() {val--;}
private int value = 0;;
}
- Note the absence of a constructor … default of otherwise
- The compiler therefore generates the implicit default constructor, which does no initialization
- The instance variable is initialized to 0 (being
int at object creator time
- Once a constructor is provided by the class designer, implicit default constructor is no longer generated
- Remember, constructors are provided by the class designer to enforce the creation of a valid object.
- Each constructor provides a list of parameters that can be used to created a valid object
- Once those constructors are specified, the compiler cannot assume that one can create an object without supplying any information.
Files Used in This Lecture