Lecture 2: Arrays, Classes, Objects, and Strings - Part 2
Part 2: Objects, Classes, and Methods Now that we know about the very basic constructs in Java, we are ready to look at the most prominent features of the language. You are probably used to using functions and/or procedures in most of your programs to do most of the work. Java has no functions and procedures, and uses in fact a different paradigm for programming: objects, classes, fields, and methods. With Java, you have no choice: you must follow this paradigm, and there is no alternative. So, what is this "Object-Oriented Program" anyway ? Object oriented programming revolves around six concepts:
Classes: the fundamental structure of every Java program, containing data fields and mechanisms to manipulate that data; classes can provide blueprints to construct software objects | |
Instances: objects that were constructed according to class blueprints and that exist in the memory of the computer; one class can be used to instantiate any number of objects | |
Encapsulation: allowing or disallowing access to the data and mechanisms that a class or object contains | |
Overloading: providing multiple definitions for certain mechanisms, allowing them to adjust automatically to different situations | |
Inheritance: a mechanism to impose relationships between classes and their corresponding objects | |
Polymorphism: the ability to deal with multiple related classes based on a common feature, giving an entire class hierarchy the ability to adjust to an appropriate situation |
We'll discuss these concepts for a while, before we will be getting into the 'fun' part of the class. First, let's start out with "methods":
Methods are well-defined segments of Java code that are responsible for certain, limited tasks. Methods are defined via a method header, and they are implemented in a method body. Methods can take one or more input variable, or no input, and they always return a data type. The types that a method can return are any of the elementary types, the special type 'void', or any Java object that is defined via a class file.
Methods are similar to functions, procedures, or subroutines in other programming languages.
If a method has a return type different from 'void' it must use the special Java keyword 'return' in the implementation body. If it retuns the type void, the keyword return does not appear in the method body..
Syntax:
[modifier] returnType methodName(inputList) { // method implementation }
where
modifier is one or more of the following: public, private, protected, final, static, synchronized, abstract, or none. They will be explained in detail below, except the keyword synchronized which will be explain in the chapter on multi-threaded programming. |
Example:
Define three methods. The first method should simply double its input value and return it. Input and output should be of type double. The second method should find the sum of its input array and return it. The input type is an array of doubles, and the ouput type is a double number. The third method should print out the sum of its three input values. The input types are double numbers, the output type is void. Here's the code:
public double doubleIt(double x) { return 2*x; }
This method has the input variable x of type double, and in the method body it doubles that x and returns it in one line. Recall that first the left-most statement is evaluated (i.e. 2 * x), then the result of that computation is returned.
public double findSum(double[] A) { double theSum = 0; for (int i = 0; i < A.length; i++) { theSum += A[i]; } return theSum; }
This method has as input an array A of doubles. Note that the array could be of any length. This is the standard way to use an array as input for a method; you never specify the actual size of the array. Inside the method body, a new variable theSum is declared and initialized to zero in one line. Then a for loop computes the sum of all values in the array. Note that the size of the array can be determined by using the special keyword ".length"; that works for arrays of any type. Inside the for loop, the shortcut notation theSum += A[i] is used instead of the longer form theSum = theSum + A[i], which would have worked also. Once the sum is computed, its value is returned via the return statement.
public void display(double x, double y, double z) { double theSum = x + y + z; System.out.println("The Sum is " + theSum); }
This time, the function has a void return type, which really means that it returns nothing at all. Therefore, there is no return statement inside the method body. There are three input variables x, y, z, and they are declared seperately. You can not declare them all at once when you define the method header. Inside the method body, a temporary variable theSum receives the sum of the input variables, which is then printed out using the standard System.out.println() method.
Note: Some of you may know the difference between reference and value parameters. In Java, that does not apply: all input variables to methods are passed by reference. However, that - as you will soon see - is not really important.
Note: These methods are pretty short. That, generally, is the goal: methods should do - if at all possible - only one specific task. There's nothing wrong with a program that uses 50 methods, and each method body consists of only a couple of lines. However, a program that has one method consisting of 50 lines or more is probably a poorly designed program.
Before we can come up with complete examples, we need to explore the next two important concepts for object-oriented programming: classes and objects.
data fields to contain data, and | |
methods to manipulate that data or perform certain actions |
Every Java program is composed of one or more classes that manipulate each others fields and use their respective methods to accomplish their tasks. The data fields of a method can be elementary types, arrays, or other defined classes, and there can be any number of data fields of any possible type or types.
Syntax:
where
modifier is one or more of the following: public, private, protected, final, static, synchronized, abstract, or none. They will be explained in detail below, except the keyword synchronized which will be explain in the chapter on multi-threaded programming. The modifiers private and protected should not be used for classes, but can be used for fields and methods. | |
fieldList is a list of variables of elementary type or other class types | |
methodList is a list of mechanisms to manipulate the fields of a class or to perform certain actions | |
extends indicates that the current class is derived from another class; details will be explained below | |
implements indicates that the current class has certain attributes in common with other Interfaces; details will be explained in the chapter on multi-threaded programming. |
When creating classes, we should initially be less concerned with the inner workings of the class and rather think about all possible uses that our class should allow. In other words, we should first worry about what we want our class to accomplish, and then we should think about how to accomplish it. The time spend up-front while thinking about the potential uses of a class will be more than recovered later when the class is actually used.
Every class that is defined can be used as a input or return data type for any method, or to define any field in other classes. Hence, using classes you can quickly expand the data types that Java knows about - which is exactly the point of defining classes!
Example:
Our Length class needs at least two data fields: one to contain the numeric value and a second one to contain the scale. We also need at least two methods: one to convert the current length to meters, the other to convert to feet. Therefore, our first approach to designing a "Length" class might be:
Since both, the methods and the data fields, are part of the class, the conversion methods do not need to take any input. They will simply convert the length of the class they are part of, i.e. the value and scale stored inside the class, to the respective new values.
As with most classes, it is fairly clear which fields to use, and what data types they should have. Deciding on the appropriate methods, however, is usually difficult, and there are almost always several different possibilities. It is very important to think carefully about designing the methods to be used:
can they accomplish what our class needs | |
are they flexible enough to work in most possible situations | |
are they easy to use in most possible situations | |
are they restricted to one particular aspect of our class | |
are the input and output parameters clearly defined and meaningful | |
do the class and method names adequately reflect their function |
Looking at these questions we might want to reconsider the design of our Length class: the methods convertToMeters and convertToFeet currently return a double number; they should, however, return another Length. Moreover, in addition to the conversion methods that we specified in the task description we might also need mechanism to retrieve a Length and display it on the screen. Thus, a more useful Length class might look like this:
Note that we have not used any modifiers for our class at this time, and we have not actually implemented any of the methods. Therefore, our class, of course, can not yet accomplish what it needs to accomplish. We will later implement how the methods will accomplish their task but we have already solved the most important problem: to specify exactly what exactly our class should be able to do. We have also chosen names so that it is reasonably clear what the various fields and methods will accomplish when implemented.
A few more concepts are necessary before we can get into complete examples: Objects, Instantiation, and Constructors:
Classes describe the data that an object can carry, and the methods by which it can be manipulated. However, in most cases a class is only an abstraction of the actual existing entity: the object. In other words, a class can be used as a blueprint to manufacture objects through a process called instantiation:
The relationship between objects and classes is, therefore, similar to the relationship between the blueprints of a car (the class) and the actual car manufactured on the assembly line according to the specifications of those blueprints (the object). In many situations the distinction between class and object is not necessary, and we will frequently use the word object when we really should have used class.
Of course, one class can be used to manufacture many objects of its type. Those objects will all have identical properties, but different names. On the other hand, there can be only one class with a particular set of fields and methods.
Now that the distinction between classes and objects is clear, how do we actually go about manufacturing an object from a particular class:
Syntax:
where
ClassName is the name of the class providing the blueprint for the object | |
fieldName is the name of the object to be instantiated. |
This will allocate all memory necessary to store the fields and methods of the object in memory, and perform possibly other resource allocation and initializations. Each object gets its own set of data fields - unless specified otherwise using a static modifier - while methods are created only once and shared by objects instantiated from the same class.
Objects can be instantiated as fields of other objects (in which case the
appropriate modifier can be used). They can also be used as needed inside the
implementation of any method (in which case they are valid only inside the block where
they have been defined), similar to the elementary data types.
During instantiation, a special method is called to initialize fields, allocate resources,
and perform other housekeeping tasks. That method is called constructor.
Syntax:
where
modifier is public or protected, or none, as explained later. |
Example:
To provide our previous Length class with a constructor, we would add the following method:
where we again have not yet bothered to implement this method.
Example:
To instantiate objects from our class, i.e. to manufacture the real existing objects according to the class blueprint, we would use the following syntax:
In the first line the fields E and F are declared, but no memory is allocated to store any information in these objects. That would not be an example of instantiation. In the next two lines, the objects E and F are instantiated, assigning memory to their fields and, if necessary, methods. Note that each object will receive its one copy of the class fields while the methods will be shared among them to conserve memory. During instantiation, the constructor method will automatically execute. An additional field G is declared, and the corresponding object is instantiated at the same time. Therefore, E, F and G are now existing objects for which the appropriate memory has been allocated. Finally, two more objects A and B are declared but not instantiated. Therefore, A and B can not yet be used.
class Length { // fields double value; char scale; // constructor Length() { // implementation } // methods Length convertToMeter() { // implementation } Length convertToFeet() { // implementation } void display() { // implementation } }
The constructor is the new method when a new Length object is created. Therefore, we want the constructor to initialize the fields of a Length object, say the value to 0 and the scale to 'n' for none. Here's what we do:
Length() { value = 0; scale = 'n'; }
Note that all three things, the Length, the value, and the scale are part of the class. Therefore, the Length constructor can simply say 'value', refering to the field value defined within the class. Next, let's take a look at the method to convert to Meters.
Length convertToMeter() { if (scale == 'm') return this; else { Length newLength = new Length(); newLength.value = value / 3.2809; newLength.scale = 'm'; return newLength; } }
Here's what's going on: the method convertTo is part of a Length object. That object contains its own scale. If that scale is already 'm', I don't need to convert anything. The current Length is already in 'meters', so the method return 'this'.
The keyword this is a special Java keyword that is used to refer to the current object in its entirety.
On the other hand, if the scale of the current object is not 'm', it must be 'f' (for feet). Therefore, we need to create a new Length object and try to coax it into being the right amount of inches. The new Length object is called 'newLength', and is instantiated via the 'new' keyword. Next, its value and scale need to be set accordingly.
To access the fields and methods of an object that is instantiated and has been given a variable name, you use the object's variable name (not the class name), a dot, and then the field name or method name of the object.
Hence, newLength.value will be set to 'value * 3.2809' and 'newLength.scale = 'f'. When that's done, the newly constructed object newLength will be return (to who ever is asking for it).
Similarly, we do the 'convertToFeet' method as follows:
Length convertToFeet() { if (scale == 'f') return this; else { Length newLength = new Length(); newLength.value = value * 3.2809; newLength.scale = 'f'; return newLength; } }
The code works similar to before. Finally, there's the display method, which is supposed to print out a nice representation of the current Lenght.
void display() { if (scale == 'm') System.out.println(value + " meter "); else System.out.println(value + " feet "); }
This method is easy. It simply looks at the scale and prints out the value of the length together with the correct unit of measurement.
To wrap it up, here's the complete definition of the Length class:
class Length { // fields double value; char scale; // constructor Length() { value = 0; scale = 'n'; } // methods Length convertToMeter() { if (scale == 'm') return this; else { Length newLength = new Length(); newLength.value = value / 3.2809; newLength.scale = 'm'; return newLength; } } Length convertToFeet() { if (scale == 'f') return this; else { Length newLength = new Length(); newLength.value = value * 3.2809; newLength.scale = 'f'; return newLength; } } void display() { if (scale == 'm') System.out.println(value + " meter "); else System.out.println(value + " feet "); } }
Now, the above source code should be saved in a file called, appropriately, Length.java. You can compile it, and fix any errors if necessary. However, you can not run it by typing 'java Length' after compiling - try it, just to be sure. Why not ? Well, because that class does not contain the 'public static void main' method that every Java program needs to contain in order to execute. So, have we accomplished nothing, then ?
What we have accomplished now is to extend Java's knowledge of data types: we have added a new type called 'Length', and we can, from now on, use that 'Length' thing in any of our other programs, without ever having to worry about the details of that class.
For example, to create a working program that deals with our new Length class, create a new file, in the same directory where the 'Length.java' file is, and name that file, say, LengthTest.java. It could look as follows:
public class LengthTest { public static void main(String args[]) { System.out.println("Length example:"); Length A = new Length(); A.value = 1.0; A.scale = 'f'; Length B = A.convertToMeter(); A.display(); B.display(); // we can do the following, even: Since the 'convertToFeet' method // returns a Length type, that type again has a 'display' method. So // we can say: A.convertToMeter().display(); // or A.convertToFeet().display(); // or even (which should give us back the original number, more or less A.convertToMeter().convertToFeet().display(); } }
Alright, that's probably enough for now. To pratice this stuff, here's your assignment:
Assignment: Create a Temperature class that can convert between degrees Celcius and degrees Fahrenheit. Design that class similar to the above Length class, and save it in a file Temperature.java. Then create another class 'TempTest.java' that contains a class with the 'standard' main method. That class can execture, and is supposed to instantiate a few 'Temperature' objects and test if the conversions work correctly.
Note: If 'C' is a temperature in Celcuis, and 'F' the temperature in Fahrenheit, then the conversion formulas are:
F = 9/5 * C + 32 (to convert from Celcius to Fahrenheit) | |
C = 5/9 * (F - 32) (to convert from Fahrenheit to Celcius) | |
(Bert G. Wachsmuth)