Objects & Classes
Object-Oriented Programming (OOP) is a way of organizing data (aka State) and code (aka methods, functions, Behavior) into classes and objects.
Classes are like stamps or templates, in that you can create many instances of the class, called objects.
A Tale of two BankAccounts
// I did not have a lot of money to spend in College.
BankAccount me = new BankAccount("Myself", 3.00);
// But I had friends with lots of money. Like, $50.
BankAccount myRichFriend = new BankAccount("Friend", 50.00);
// To make it worse, they could add money to their card all the time, like $20!
myRichFriend.deposit(20.00);
// I can't afford $60 worth of anything.
System.out.println(me.canAfford(60.00)); // prints: false
// But my friend could.
System.out.println(myRichFriend.canAfford(60.00)); // prints: true
// That is, until they spent $13.87 on a fancy coffee of some kind.
myRichFriend.spend(13.87);
// Now, they can't afford a $60 thing.
System.out.println(myRichFriend.canAfford(60.00)); // prints: false
Running this code will give the following output:
false
true
false
In this previous example -- there are two objects of type BankAccount
which models a person's bank account. We could have written a similar sassy story using variables of type double
but numbers don't have nice methods like canAfford
, deposit
and spend
. Also, in a bigger example, we'd want to associate names with accounts and maybe keep a list of transactions.
- To build this class, I would first create a new file, named
BankAccount.java
. - Then I would add fields to this class, a
double balance
and maybe aString owner
. - Then we need a constructor -- something that will create a new object when we call
new BankAccount(...)
. - Then we would design the methods:
canAfford
,deposit
andspend
.
Creating the BankAccount class: fields and a constructor
public class BankAccount {
/** Who owns this card? */
String owner;
/** How much money is stored here? */
double balance;
public BankAccount(String person, double money) {
this.owner = person;
this.balance = money;
}
// ... methods later go here ...
}
Fields live on the class itself, and by convention, we put them at the top, although we could put them anywhere.
This code is the constructor. Note that it's not quite a normal method definition -- we have public
but the name and the return-type have been merged to be BankAccount
the name of the class. This marks the method as being special: a constructor.
public BankAccount(String person, double money) {
this.owner = person;
this.balance = money;
}
This method has two parameters: String person
and double money
. In the longer code example, person
is first "Myself"
and then "Friend"
, and money
changes similarly.
We could create yet another instance of a BankAccount
by running the following code:
BankAccount empty = new BankAccount("unknown", 0.00);
Then, we have effectively created empty.balance = 0.00;
and empty.owner = "unknown";
by calling the constructor via the new
keyword.
deposit
and canAfford
The following is a possible definition of both deposit
and canAfford
on our BankAccount
class. Notice that nothing on this class uses the static
keyword -- this is important. We want access to owner
and balance
in these methods; but we need to go through the this
keyword, which is a special variable that represents the current instance of the class. When we call something like me.canAfford(...)
the variable me
becomes this
for the body of the method canAfford
in this case, that gets called.
public class BankAccount {
/** Who owns this card? */
String owner;
/** How much money is stored here? */
double balance;
public BankAccount(String person, double money) {
this.owner = person;
this.balance = money;
}
public void deposit(double amount) {
this.balance += amount;
}
public boolean canAfford(double amount) {
return this.balance > amount;
}
// we'll put spend here...
}
Here we get a clue to the meaning of void
-- something that's part of the main method we've learned to type. void
means nothing -- so when we put it in the return-value position for a method, it means that our method returns nothing. Adding money to a bank account or a BankAccount
should always succeed, so I don't know what we would return here (maybe the new balance?) but it's appropriate to return nothing.
spend (to demonstrate exceptions!)
Spend is an interesting operation -- because it should be able to fail if you don't have enough money. This will be true when we get to the data structures portion of the class (imagine asking to delete from an empty list, or asking for item number 5 of a 3 item list.).
public void spend(double amount) {
if (!this.canAfford(amount)) {
throw new RuntimeException(this.owner + " can't afford "+amount);
}
this.balance -= amount;
}
There are many types of exceptions (or errors) in Java. We saw one in the GuessingGame. When we want to create our own error, and crash the program, we will use RuntimeException
.
Language: | Response: |
---|---|
English | "You can’t do that!" |
Python | raise ValueError(message) |
Java | throw new RuntimeException(message); |
It may be a better design to make a spend that returns a true/false value, i.e.,
public boolean spend(double amount) {
if (!this.canAfford(amount)) {
return false;
}
this.balance -= amount;
return true;
}
Because then you can choose how to handle not being able to spend the money in the code that is interacting with teh BankAccount
class.
So then, what's a static method?
All of the methods presented above are instance methods, which means they depend on a particular instance of a class. Static methods, like the public static void main(String[] args) { }
we use to run code do not need or have access to a this
special variable; and that can be a good thing!
For the BankAccount
example, imagine that we wanted a set of vendors that gave extra points when the BankAccount
sends them money. This would not be different for each BankAccount
, but we may want that set to be associated with the class anyway:
public class BankAccount {
// everything we've seen.
/** Does this vendor earn extra points? Not dependent on bank account. */
public static boolean isExtraPoints(String vendor) {
// use your imagination ...
}
}
Vocabulary
In this section, we try to define a lot of common terminology for classes.
An Object is an instance of a Class.
Classes are the template, and objects are generated from the template.
Behavior: Vocabulary
We will refer to methods on a class as "methods" but there are static
methods on a class, and so sometimes we will refer to them as "instance methods" to tell them apart from static methods.
- Method
- Instance Method
- Action
- Messages / Signals
State: Vocabulary
There are many names for the variables associated with a class.
- Instance Variables
- Member Variables
- Members
- Properties
- Attributes
- Fields
Constructors
Constructors run code on the new
keyword. The parameters they ask for are used to set up the class (but they need not be instance variables.)
Parameters vs. Variables
In a hypothetical Dog
class, we might want to store the age of a dog in so-called "dog-years" rather than "human-years."
public class Dog {
private int dogYears;
public String name;
public Dog(String name, int yearsOld) {
this.name = name;
this.dogYears = yearsOld * 7;
}
public int getDogYears() {
return this.dogYears;
}
public int getHumanYears() {
return this.dogYears / 7;
}
}
By then writing methods for getting to our dogYears
instance variable, users of this class don't need to know whether human years or dog years are stored, merely that they have access to both!
Class keywords
There are a lot of other keywords that go with classes. I'll try to define them here.
public vs. private
For now, our definition of public
is that it allows you to see variables, methods, and classes marked with it from other .java
source code files. Therefore, when in doubt, we can lean toward making things public
.
For now, our definition of private
is that it allows you to hide variables, methods, and classes marked with it from other .java
source code files: in our earlier example about Parameters and Variables where we discussed the private int dogYears
field, it made sense to hide it from the user and make them call the public methods to get either getHumanYears()
or getDogYears()
.
Note that there are two other levels of visibility in Java: package
and protected
. package
-level things can be seen from files and classes the same folder as the current class (this is what happens when you just write int dogYears
on a field -- there's no package
keyword despite it being a level). And protected
makes things visible to classes that inherit directly from this class -- we will discuss inheritance later in the course.
static vs. instance
I have the opinion that all dogs are cute. Having a variable to represent a specific dog, e.g., buddy
is not necessary for knowing whether a dog is cute.
Signature | Has this reference? | How do I call it? |
---|---|---|
public static boolean isCute() |
No this involved. |
if (Dog.isCute()) { … } |
public boolean isCute() |
this=buddy or whichever object you call it on. |
if (buddy.isCute()) { … } |
Methods that are in a class (because Java requires everything to be in a class) but don't really need to be are called static methods. You can also have static variables. Notably, you don't need to create an object in order to call them.
This is useful to Java as well as to us -- recall our main method:
public static void main
public static void main(String[] args) {
// public
// lets us start the program from outside the file it is defined.
// static
// lets us run the code in main without creating the object that owns it.
// void
// means that it doesn't return anything.
// String[] args
// is an array of strings from the user to start this program;
// although it will always be blank in this class.
}