isOverlap method would be a good choice then,
as well at a more motivated implementation of Comparable.
An important part of Object-Oriented Design is to represent "things", sometimes called business objects, in your problem by classes and objects of these classes. Classes in the design process are often described by their "behavior", "responsibilities", or "messages" they pass. These are all ways describing the methods that are defined in a class. The data that is stored in classes is usually not emphasized in the design process.
In contrast to this, Object-Oriented Programming is concerned with implementing classes - writing the code. At the programming level you must be concerned with the data as well as the methods that constitute a class.
These notes are about programming and Java language features necessary for OOP, and do not discuss much about design. Ultimately you will be more concerned with the methods than the data, but it is generally easier to understand classes by starting with data and working up to methods. These notes start with data and ultimately show how methods can be used to completely hide the data implementation.
Classes are most often used to group a number of related, named data values. For example, if you are storing information about students, you need their name, id, etc. A class describes the names and types of the data. For example, the following is the proposed definition of a class for a student. Follow the tutorial and you'll see how this can be improved, but it shows some of the basic ideas. See 2. Data - Student for an introduction to defining the classes and creating objects from them.
public class Student1 {
public String firstName;
public String lastName;
public int id;
}
Some classes contain no data, only static methods work.
Every application that you write has
at least one static method in it, main.
If you're not already familiar with these methods, see the
Static Method Tutorial.
Grouping related data with methods lets you not only model the values associated with business objects, but also the their behavior.
Classes are usually given the name of a noun, and contain the data
associated with that noun.
Some predefined Java classes that you have
used are String, which contains the characters in the string,
JOptionPane, which contains the information necessary to produce a JOptionPane
on the screen, etc. These system classes are about programming, but the
classes you write will be mostly be about your problem domain
where they represent business objects (Student, TimeOfDay, Order, ...).
The class which contains main is often an exception
to the noun-naming convention, and is typically named
after the problem you are trying to solve.
To solve your problem, you need to define classes to represent your data. Often, the data is not a simple primitive value. For example, if you are writing a program that does something with university information, you may define classes like Student, (class names start with an uppercase character), Course, etc. Each of these classes defines attributes, for example, the student name, etc.
Let's define a class that represents information about a student.
Typically there is one class definition per file, so this would be
stored in Student1.java. There are going to be several iterations on this
class,
// File : oop/dataclass/Student1.java
// Purpose: Information about a student.
public class Student1 {
public String firstName; // First name
public String lastName; // Last name
public int id; // Student id
}
new to create a new objectThis class definition tells which data a class can hold (three different values),
and is a description of what must be created when the new operation
is performed to create a new object.
Student1 tatiana;
tatiana = new Student1(); // Create Student1 object with new.
To create a new object, write new followed by the
name of the class (eg, Student1), followed by parentheses.
Later we'll see how to define our own constructor with parameters.
The instance variables (firstName, lastName, ..)
name data which is stored in each object.
Another term for instance variable is field.
All public fields can be referenced using dot notation
(later we'll see some better ways to set and access fields).
To reference the field, write the object name, then a dot, then the
field name.
Student1 tatiana;
//... Create new Student1 object with new.
tatiana = new Student1();
tatiana.firstName = "Tatiana";
tatiana.lastName = "Johnson";
tatiana.id = 9950842;
JOptionPane.showMessageDialog(null, "One student is named: "
+ tatiana.lastName + ", " + tatiana.firstName);
Awkward? If the above example seems like an awkward way to store information about a student, you're right. The intention of the above example is simply to show the mechanics of classes. We'll eventually get to examples that show real advantages.
The class defines which fields an object has in it, but
you need to use new to create a new object from the class
by allocating memory and assigning a default value to each field.
A little later you will learn how to write a constructor to
control the initialization.
It's not very interesting to create only one object from a class, so the following example creates a couple of them.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
// File : oop/dataclass/TestStudent1.java
import javax.swing.*;
public class TestStudent1 {
public static void main(String[] args) {
Student1 tatiana;
Student1 pupil;
//... Create new Student1 object with new.
tatiana = new Student1();
tatiana.firstName = "Tatiana";
tatiana.lastName = "Johnson";
tatiana.id = 9950842;
//... Create another Student1 object.
pupil = new Student1();
pupil.firstName = JOptionPane.showInputDialog(null, "First name");
pupil.lastName = JOptionPane.showInputDialog(null, "Last name");
pupil.id = Integer.parseInt(JOptionPane.showInputDialog(null, "ID"));
JOptionPane.showMessageDialog(null, "One student is named: "
+ tatiana.lastName + ", " + tatiana.firstName
+ "\n and another is named: "
+ pupil.lastName + ", " + pupil.firstName);
}
}
|
Avoid bad initializations. One problem with the Student1 class is that the user has to explicitly initialize all of the fields. This requires some extra typing, but more importantly, it is error-prone. If we forget to initialize a field, the default value could have bad consequences.
Convenience. Defining a constructor makes creation of an object a little easier to write, altho this is not the main advantage.
Syntax. A constructor is similar to a method, it's called like a method and returns. But you don't specify a return value and the name must be the same as the class name. Here is the Student1 class rewritten with a constructor which does nothing, which is exactly the same as the default constructor which is automatically created for every class.
The example below shows how to define a constructor, altho this one doesn't do any more than the default constructor that Java automatically generates.
// File : oop/dataclass/Student2.java
// Purpose: Information about a student. Defines constructor.
public class Student2 {
public String firstName; // First name
public String lastName; // Last name
public int id; // Student id
//======================================== constructor
public Student2(String fn, String ln, int idnum) {
firstName = fn;
lastName = ln;
id = idnum;
}
}
Similar to method. A constructor is similar to a method: it is called, it has a body of Java statements, and it returns. The most notable differences are in the header, which does NOT have a return type, and the name, which must be the same as the class name.
Here is the first program rewritten to use the above class definition with a constructor.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
// File : oop/dataclass/TestStudent2.java
// Purpose: Tests Student2 constructor.
import javax.swing.*;
public class TestStudent2 {
public static void main(String[] args) {
Student2 tatiana;
Student2 pupil;
//... Create new Student2 object with new.
tatiana = new Student2("Tatiana", "Johnson", 9950842);
//... Create another Student2 object.
String first = JOptionPane.showInputDialog(null, "First name");
String last = JOptionPane.showInputDialog(null, "Last name");
int studID= Integer.parseInt(JOptionPane.showInputDialog(null, "ID"));
pupil = new Student2(first, last, studID);
JOptionPane.showMessageDialog(null, "One student is named: "
+ tatiana.lastName + ", " + tatiana.firstName
+ "\n and another is named: "
+ pupil.lastName + ", " + pupil.firstName);
}
}
|
if you define a constructor in a class, the Java compiler no longer defines the default constructor. All object creation therefore must use an explicitly defined constructor.
This means that the constructor can be sure all fields are defined with legal values. We're not going to extend the Student2 class any further to test for legal values, but we will in the next example.
For the moment we'll leave the Student class, and move to something different to show the same ideas.
Let's say that we want to represent a time of day necessary for recording when an appointment is scheduled. It won't represent the day, only the time as hour and minute. Here's a reasonable start using a 24 hour clock (00:00 to 23:59).
1 2 3 4 5 6 7 8 9 10 |
// File : oop/timeofday/TimeOfDay1.java
// Purpose: A 24 hour time-of-day class to demo intro OOP concepts.
// Author : Fred Swartz
// Date : 2005-05-30
// Issues : No validity check.
public class TimeOfDay1 {
public int hour;
public int minute;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// File : oop/timeofday/TimeTest1.java
// Purpose: Test the TimeOfDay1 class..
// Author : Fred Swartz
// Date : 2005-05-04
import javax.swing.*;
public class TimeTest1 {
public static void main(String[] args) {
//... Create a new TimeOfDay object.
TimeOfDay1 now = new TimeOfDay1();
//... Set the fields.
now.hour = 10;
now.minute = 32;
//... Display it.
JOptionPane.showMessageDialog(null, now.hour + ":" + now.minute);
}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// File : oop/timeofday/TimeOfDay1c.java
// Purpose: A time-of-day class with constructor.
// Author : Fred Swartz
// Date : 2005-05-04
public class TimeOfDay1c {
public int hour;
public int minute;
//=================================== constructor
public TimeOfDay1c(int h, int m) {
hour = h;
minute = m;
}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// File : oop/timeofday/TimeTest1c.java
// Purpose: Test the TimeOfDay1c class..
// Author : Fred Swartz
// Date : 2005-05-04
import javax.swing.*;
public class TimeTest1c {
public static void main(String[] args) {
//... Create a TimeOfDay1c object for 10:30 in the morning.
TimeOfDay1c now = new TimeOfDay1c(10,30);
//... Display it.
JOptionPane.showMessageDialog(null, now.hour + ":" + now.minute);
}
}
|
It's possible to overload constructors - define multiple constructors which differ in number or types of parameters. For example, exact hours are common, so an additional constructor could be defined which takes only the hour parameter.
. . .
//=================================== constructor
public TimeOfDay1c(int h) {
hour = h;
minute = 0; // Always set to zero.
}
TimeOfDay1c dentistAppointment = new TimeOfDay1c();
TimeOfDay1c wakeup = new TimeOfDay1c(6);
By defining a constructor, all TimeOfDay objects must be created with your constructor. This is good because it gives you total control over how the parameters are used, but most importantly it can prevent the creation of illegal TimeOfDay values.
What to do if there are errors. Errors can be grouped into two general classes:
assert statement
(see Assertions)
to stop execution if an illegal situation is encountered.
Crash please. The TimeOfDay class is called by a programmer, not the user. If illegal values are passed to the constructor, it's because the programmer isn't doing their job, so the program should be stopped immediately.
The program below should crash because an illegal number of minutes is passed to the second call on the constructor. If you have assertion checking enabled, as you always should, the result should be an error message something like the following.
Exception in thread "main" java.lang.AssertionError: TimeOfDay: Bad minute 99
at TimeOfDay2a.<init>(TimeOfDay2a.java:17)
at TimeTest2a.main(TimeTest2a.java:13)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// File : oop/timeofday/TimeOfDay2a.java
// Purpose: A 24 hour time-of-day class to demo intro OOP concepts.
// Author : Fred Swartz
// Date : 2005-05-30
// Issues : Validity checking with assert.
public class TimeOfDay2a {
//========================================= instance variables
public int hour;
public int minute;
//================================================ constructor
public TimeOfDay2a(int h, int m) {
//... Check values for validity.
assert h >= 0 && h <= 23 : "TimeOfDay: Bad hour " + h;
assert m >= 0 && m <= 59 : "TimeOfDay: Bad minute " + m;
hour = h;
minute = m;
}
}
|
The following program should not run. If assertions are turned on, then the second call to the constructor should produce an AssertionError.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// File : oop/timeofday/TimeTest2a.java
// Purpose: Test the TimeOfDay2 class assertion check.
// Author : Fred Swartz
// Date : 2005-04-30
// Issues : When you run this test, it should produce an error.
import javax.swing.*;
public class TimeTest2a {
public static void main(String[] args) {
TimeOfDay2a now = new TimeOfDay2a(8 , 35); // OK
TimeOfDay2a never = new TimeOfDay2a(14, 99); // ILLEGAL VALUE.
JOptionPane.showMessageDialog(null,
"You should never see this message."
+"\nTurn assertions on when you run this program.");
}
}
|
Risk. Public instance variables put the integrity of objects at great risk. Nothing prevents the following bad statements from being executed.
TimeOfDay3 goodTime = new TimeOfDay3(11, 11, 11);
goodTime.minute = 99; // AVOIDS CONSTRUCTOR VALIDITY CHECK
But who would do that? Of course no programmer would intentionally set the value to something illegal like this, but there could be several reasons to mistakenly set it. Here are two examples.
goodTime.minute = userValue; // FAILED TO CHECK INPUT VALUE FIRST. goodTime.minute++; // WHAT HAPPENS AFTER 59?
Private. The key to making classes safer and easier to change is to
declare instance variables private. Constructors and methods in
the same class can use the variables, but no one outside the class can see
them. However, if they're private, something must be done so that whoever
is using the class can get and set the values. This is sometimes done with so called
getter and setter methods.
Naming. It's common to write public methods that allow getting and setting instance variables. The Java convention is to begin such method names with "get" or "set" followed by the attribute that should be obtained or set..
Getters and setters can be evil. Don't simply write getter and setter methods for every private instance variable -- altho these can filter out bad values, they make your code inflexibly dependent on a particular implementation. Getter and setter methods do not even need to correspond to specific fields -- they provide a user interface to your class, but internally you are free to make changes. We'll make such a change to the TimeOfDay class shortly.
It seems awkward to write these, and some languages have made this either automatic or easier to do. There is controversy about how easy it should be go create getters and setters, because they shouldn't exist for all instance variables. For Java just accept that you will have to type these definitions. It's really only a couple of minutes of work.
When you start working with methods that have more code in them, you will find that it isn't always obvious whether a variable is a parameter, local variable, or instance variable. Because instance variables have such a distinct meaning, it is not uncommon to prefix their names with something special.
hour Instance variable looks like other variables. Common.
m_hour A C++ convention for "member" variables.
mHour Another C++ convention.
fHour The "f" denotes "field" variable.
_hour Common. Legal altho it violates Java naming conventions.
myHour Less common, but I use it in many programs.
I highly recommend naming instance variables differently than local variables. When you collaborate with others on a project there will often be a naming convention which you must use. Therefore be flexible on this topic.
Here's the TimeOfDay class, rewritten with private instance variables and getters and setters.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
// File : oop/timeofday/TimeOfDay3.java
// Purpose: A 24 hour time-of-day class to demo intro OOP concepts.
// Author : Fred Swartz
// Date : 2005-05-30
// Issues : Make variables private, rename them, add getters and setters
public class TimeOfDay3 {
//========================================= instance variables
private int myHour;
private int myMinute;
//================================================ constructor
public TimeOfDay3(int h, int m) {
//... Check values for validity.
assert h >= 0 && h <= 23 : "TimeOfDay: Bad hour " + h;
assert m >= 0 && m <= 59 : "TimeOfDay: Bad minute " + m;
myHour = h;
myMinute = m;
}
//================================================== getHour
public int getHour() {
return myHour;
}
//================================================== setHour
public void setHour(int h) {
assert h >= 0 && h <= 23 : "TimeOfDay: Bad hour " + h;
myHour = h;
}
//================================================ getMinute
public int getMinute() {
return myMinute;
}
//================================================ setMinute
public void setMinute(int m) {
assert m >= 0 && m <= 59 : "TimeOfDay: Bad minute " + m;
myMinute = m;
}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// File : oop/timeofday/TimeTest3.java
// Purpose: Test the TimeOfDay3 class..
// Author : Fred Swartz
// Date : 2005-05-30
import javax.swing.*;
public class TimeTest3 {
public static void main(String[] args) {
TimeOfDay3 then = new TimeOfDay3(8, 35);
TimeOfDay3 now = new TimeOfDay3(14, 5);
//... Print the hours and minutes of the times.
JOptionPane.showMessageDialog(null,
"From " + then.getHour() + ":" + then.getMinute()
+ " to " + now.getHour() + ":" + now.getMinute());
// THE FOLLOWING WOULD BE ILLEGAL
// now.myHour = 99; // Can't reference private field.
}
}
|
null or the empty string and the id must be in
a certain range).
toString instance methodJust as the constructor accesses the instance variables (fields), you can
also write methods that use the fields.
A common method to write is toString, which returns a
string representation of an object. This is convenient
for displaying the value of an object.
String concatenation of objects.
If you try to convert an arbitrary object to a String,
as when you concatenate anything with a String, Java calls
the object's toString method. If you didn't define
this method, then your class inherited toString from
the Object class. Object's version of toString doesn't produce anything
very useful -- it gives the name of the class and
the hexadecimal location in memory, for example, "TimeOfDay@82ba41".
toString methodThis class is the same as in the previous example,
except that the toString method has been defined.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
// File : oop/timeofday/TimeOfDay2.java
// Purpose: A 24 hour time-of-day class to demo intro OOP concepts.
// Author : Fred Swartz
// Date : 2005-05-30
// Issues : Add toString method.
public class TimeOfDay2 {
//========================================= instance variables
private int myHour;
private int myMinute;
//================================================ constructor
public TimeOfDay2(int h, int m) {
//... Check values for validity.
assert h >= 0 && h <= 23 : "TimeOfDay: Bad hour " + h;
assert m >= 0 && m <= 59 : "TimeOfDay: Bad minute " + m;
myHour = h;
myMinute = m;
}
//================================================== getHour
public int getHour() {
return myHour;
}
//================================================== setHour
public void setHour(int h) {
assert h >= 0 && h <= 23 : "TimeOfDay: Bad hour " + h;
myHour = h;
}
//================================================ getMinute
public int getMinute() {
return myMinute;
}
//================================================ setMinute
public void setMinute(int m) {
assert m >= 0 && m <= 59 : "TimeOfDay: Bad minute " + m;
myMinute = m;
}
//================================================= toString
public String toString() {
return myHour + ":" + myMinute;
}
}
|
toStringNote how convenient the output is.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// File : oop/timeofday/TimeTest2.java // Purpose: Test the TimeOfDay2 class. // toString called automatically for concatenation. // Author : Fred Swartz // Date : 2005-04-30 import javax.swing.*; public class TimeTest2 { public static void main(String[] args) { TimeOfDay2 then = new TimeOfDay2(8, 35); TimeOfDay2 now = new TimeOfDay2(14, 5); JOptionPane.showMessageDialog(null, "From " + then + " to " + now); } } |
Enhance the TimeOfDay3 class by writing some of the following methods.
Two digit times
We normally expect to see time values expressed with two digits,
but the version of toString above formats numbers
less than 10 as single digits, eg 12:7 altho
we would expect to see 12:07.
Change toString to do this.
isAM()
Write a method, isAM, that returns a boolean true if the
time is in the AM, and false in the PM. Assume that 12:00 is PM.
to12HString(). Write a method, to12HString, that returns the
time as a string in 12-hour form, appending AM or PM as appropriate. For example, for
the time 22:37 would be returned as "10:37 AM". There are three cases:
incHour() is a void method that adds one (increments)
to the hour field by 1. If the hour goes over 23, it should wrap around to
0.
incMinute() is a void method that adds one (increments)
to the minute field by 1. If the minutes go over 59, the "carry" should go
into the hour field (by calling on incHour naturally.