ECS658 U03 OOPBasic Aspects

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 65

Further

Object Oriented Programming


“FOOP”

Basic Java Programming


Aspects
ECS658U
Matthew Huntbach
matthew.huntbach@qmul.ac.uk
Method Call Environment
• When a method call is made, its environment is the variables it
has direct access to
• For a static method, its environment starts off as just the
variables in its method signature (parameter variables)
• Every method call has its own variables, just because a variable
has the same name as a variable in another method call does
not mean it is the same variable
• The variables of a method call are local variables, they exist
just while the method call runs, and disappear when the method
call finishes
• A method may declare further local variables inside it which are
added to its environment as the method runs
• The environment of a non-static method also includes the
variables of the object it is called on, and this which is like a
variable set to refer to the method itself but cannot be changed
to refer to anything else
2
Instance and static variables
• A method that is not static is called an instance method
• A variable that is inside an object is called an instance
variable, it needs to be declared with its type and its name
outside any method in the class of the object
• Another name for an instance variable is a field
• What was in the previous slide missed out something else that
is in a method environment, that is static variables
• A static variable is declared like an instance variable but with
the word static in front of it
• When a variable is declared as static, instead of every object
of the class having its own variable of that name, there is just
one variable of that name which every object of that class
shares, and static methods in the class can also use
• If a local variable is used without giving it a value, that will cause
a compiler error, but an instance variable not given a value is by
default set to 0 if it is of a primitive type, otherwise set to null

3
static methods and variables
• A method that just does some work on the values passed to it
should be declared as static
• In some cases, a class may be given just to provide a collection
of methods to perform various tasks rather than to define a
particular type of object, in that case all its methods are declared
as static
• A static method in one class can be called in a method in
another class by attaching the call to the name of the class
instead of to a variable referring to an object of that class, so
long as the method is declared as public
• It is considered best not to have static variables that can have
their values changed
• The reason for this is that objects sharing a static variable
can then interact by changing the value of that variable, a form
of interaction that can be easily missed and so cause problems
• Instead, interaction should be just by method calls
4
final methods and variables
• If the word final is put in front of a method header, that means
the method cannot be overridden in a subclass, that is
sometimes done to avoid the problems of overriding methods
which are what the Liskov Substitution Principle is about
• If the word final is put in front of the declaration of a variable,
that means the variable cannot be reassigned a different value,
so really it is a constant rather than a variable
• It is considered good to use static final variables, as that is
a way to give a name to a constant value that is used in code
• For example, rather than use the number 100 directly in code,
declare a variable final static max = 100 and then use max
• The reason for this is that it makes it more clear what a number
is being used for if it is given a name that describes its purpose,
and you would only need to change the code in one place if the
constant value was changed in a new version of the code

5
Simple Example of Environments
• As a simple example, suppose we have the following methods:
static int meth1(Thing obj, int val1, int val2) {
val2 = meth2(obj,val2);
int val3 = val2 + val1;
return meth2(obj,val3);
}

static int meth2(Thing ath, int num)



• A call of meth1 will start with the environment consisting of variables
called obj, val1 and val2
• It will then go to an environment consisting of ath and num, which are
new variables, ath refers to the same object that obj refers to in the
previous environment, num stores a copy of what val2 stores
• It will return to the environment consisting of obj, val1 and val2,
change what is stored in val2 to what meth2(obj,val2) returns and
then add val3 to the environment
• It will then go to a new environment with new variables ath and num,
ath refers to what obj refers to and num stores what val3 stores

6
Stack of Environments
• An important thing to understand is that there is a stack of
environments, with the environment at the top of the stack being
the one currently being used
• When a method call is made in a method call, a new
environment is created and put on top of the stack
• When a method call finishes, the environment at the top of the
stack is removed, and computation returns to using the
environment that was below it in the stack
• The local variables in a method call environment will always be
new variables, not the same variables as in a previous call to
the same method
• A method call may create additional variables in its environment
• Primitive values like int are copied to the variables in the new
environment when they are used as arguments
• Otherwise, the new variables refer to the same objects as the
variables used when the method call was made

7
Environments of Instance Methods
• Now suppose we have the following methods in a class which has
declared an instance variable int amount
int meth1(Thing obj, int val1, int val2) {
val2 = meth2(obj,val1)+amount;
int val3 = val2 + val1;
return meth2(obj,val3);
}

int meth2(Thing ath, int num)



• These are not static methods, so the environments will be as
previously, but will also have the variable amount (and any other
instance variables in the class they are in)
• In this case, the variable amount in the environments of the calls of the
method meth2 will be the same variable each time, and the same
variable as in the environment of the call of meth1
• This would also be the case if meth1 and meth2 were static methods,
but amount was declared as static int amount

8
Argument of same type as method’s class
• Now suppose this method is declared inside the class Thing
int meth1(Thing obj, int val1, int val2) {
val2 = meth2(obj,val1)+amount;
int val3 = val2 + val1;
return meth2(obj,val3);
}
• Then if a method call thA.meth1(thB,m,n) was made, obj refers to
the Thing object that thB refers to, and amount is the amount
variable inside the object that thA refers to
• If this is the code for meth2
int meth2(Thing ath, int num) {
return (ath.amount+amount)*num;
}
then ath here refers to what thB refers to in the environment that
includes thA, thB, m and n
• So amount is the amount variable in the object referred to by thA and
ath.amount is the amount variable referred to by thB
• This can be made more clear by using this.amount rather than just
amount
9
Use of this
• To consider more about the use of this, supposed the method meth1
declared inside the class Thing was:
int meth1(Thing obj, int val1, int val2) {
val2 = meth2(obj,val1)+amount;
int val3 = val2 + val1;
return obj.meth2(this,val3);
}
• Then if the call thA.meth1(thB,m,n) was made with the code for
meth2: int meth2(Thing ath, int num) {
return ath.amount+num*amount;
}
then in the first call of meth2 the variable ath refers to the Thing
object that thB refers to, ath.amount is the variable amount in what
thB refers to, and amount in num*amount is the variable amount in
what thA refers to
• In the second call of meth2 the variable ath refers to the Thing object
that thA refers to, ath.amount is the variable amount in what thA
refers to, and amount in num*amount is the variable amount in what
thB refers to

10
private variables
• An important thing to be aware of is that this code:
int meth2(Thing ath, int num) {
return ath.amount+amount*num;
}
will work inside class Thing even if the variable amount is declared
as private int amount;
• That is, an instance variable declared as private can only be used
directly by methods in its own class
• Other code uses the instance variable indirectly by calling methods that
use it
• Note, however, that method code in the class can use directly not just
the private variables of the object of that class it is called on, but also
the private variables of the same name in other objects of the same
class it has reference to, as in ath.amount here
• If a call meth2(this,val) was made, then in the environment of that
call, ath.amount and amount would be two names for the same
variable

11
Why private variables?
• If obj is a variable that refers to an object, then obj.val can
be used directly as a variable, it is the variable called val inside
the object referred to by obj
• It needs to be understood that obj1.val and obj2.val can
be two names for the same variable, as obj1 and obj2 could
be two separate variables that refer to the same object
• The reason for declaring instance variables as private is
because then you can be sure that the only code that can make
use of them directly is the code of methods inside the class
• If objects could interact in other ways than making public
method calls, it would make it harder to be sure a program with
lots of classes is working correctly
• Also, if you decided to change the internal representation of a
class because you worked out a more efficient way of making it
work, you can remove private variables without causing
problems to other code

12
protected variables
• Suppose we have private int amount in class Thing, and
then a subclass class SpecialThing extends Thing
• An object of type SpecialThing then has a variable amount
inside it, but code in class SpecialThing cannot access it
directly, like any other code it can only use the variable amount
by calling a method declared in class Thing that uses it
• The reason for this is that if any subclass of Thing could
access its private variables, then it could do something
different with them, and again that makes it harder to be sure
code is correct, and harder to modify code
• However, if you did want any subclass of Thing to be able to
make direct use of the variable amount, you can do that by
declaring it as protected int amount
• Another way to protect a class from subclasses that make
damaging changes to it is to declare it as final before the
word class in its header, then no subclasses of it can be made

13
Java Packages
• If an instance variable in a class is not declared as private,
you might think any other code can make use of it directly
• What it really means is that only code in any class in the same
package can make use of it directly
• When you learn to program and start dividing code into separate
classes, they will all be in the same package
• It is only with much larger scale code, professional code that
could consist of hundreds or thousands of classes, where you
would need to consider dividing code into packages
• However, you should have made some use of separate
packages when making use of Java’s built-in code, as that is not
all in the same package
• For example, to make use of Java’s built-in class ArrayList,
you have to write import java.util.ArrayList; before
the header of the class that uses it, or import java.util.*;
to make use of all the code in the built-in package java.util

14
Creating your own Packages
• To create your own package called myPackage, put
package myPackage;
before the class header for every class that you want to be
considered as part of myPackage
• Then for code in other classes to make use of a class Thing
which is in that class it must have import myPackage.Thing;
or import myPackage.*; before its class header
• Or use myPackage.Thing instead of just Thing for the type of
a variable that refers to a Thing object and for a call of a
constructor to create an object of that class
• The real point of protected is that a subclass could be in a
different package than its superclass, and protected allows a
subclass in any class to access the variable directly
• Only if an instance variable is declared as public can any code
in any class access it directly by attaching its name to a variable
that refers to an object of that class

15
Local Variables
• Going back to local variables, they are not declared as private,
protected or public, as they are for use only inside the
method where they are declared, with a separate variable of that
name existing in every call of the method, and disappearing when
the method call terminates
• The local variables are the variables declared in the method
signature, and further variables declared inside the method’s
code
• Note that a variable declared inside a compound statement exists
only when that statement is executed, so with:
while(test) {
int num=0;
… }
each time round the loop, a new variable called num is created
and set to 0, there is no variable called num in the code that
comes after the while statement, and a value num was changed
to is not saved for use next time round the loop
16
for-loop
• A loop of the form
for(initialise; test; update)
statement
is equivalent to:
initialise;
while(test) {
statement;
update;
}
except that any variable declared in initialise disappears
once the for-loop has finished, so it can’t be accessed in code
that comes after the for-loop
• It is considered making it more clear how the loop works when a
variable used in test is declared in initialise, has its value
changed in update, and is not changed anywhere in
statement

17
break in loop
• In the code:
while(test1)
statement1
statement2
With statement1 inside the loop and statement2 after it, it
would normally be considered that test1 evaluated to false
when statement2 is executed
• However, if it is of the form (applies also to for loops):
while(test1) {

if(test2) break;

}
statement2
the loop would exit if test2 is true even if test1 is also true
• So break should only be used if the code inside the loop is
small, so it can easily be seen

18
continue and return in loop
• A loop of the form:
while(test1) {
statement1A
if(test2) continue;
statement1B
}
means that if test2 evaluates to true, the loop goes straight to
its next time round without doing statement1B
• So continue should only be used if the code inside the loop is
small, so it can easily be seen
• A loop of the form:
while(test1) {
statement1A
if(test2) return val;
statement1B
}
means that the method call it is in halts and returns val

19
return
• It is important to understand that a return statement always
halts the method call it is in
• Whereas break in a loop halts the loop, but carries on with code
after the loop, return in a loop does not just halt the loop, it
does not carry on with any more code in the method
• So: while(test1) {
statement1A
if(test2) return val1;
statement1B
}
return val2
would only return val2 if there was no case in the loop where
test2 was reached and evaluated to true
• Some seem to think that a method call can return something and
continue, but that is not the case
• Unless a method has return type void, its code must be such
that it will always reach a return statement
20
Exceptions
• A method with return type void either just halts with code
returning to where the method call was made, or it throws an
exception
• Otherwise, a method call either returns a value or throws an
exception, it cannot do both
• If a method call throws an exception, then code execution does
not return to where the method call was made
• Instead, if the method call is in the try part of a try-catch
statement with a catch part that catches the exception, code
execution goes to that catch part
• If the method call is not in the try part of a try-catch statement
with a catch part that catches the exception, the method call that
it is in also halts and throws the same exception
• Once a method call has thrown an exception, it is finished, code
cannot go back and continue with the same method call

21
Throwing on an exception
• If we have Rtype meth1(Type1 a) {
statement1;
x=meth2(b);
statement2;
}
and the call meth2(b) throws an exception of type MException then
statement2 will not be executed, and the call meth1(v) that led to
this will throw on the same MException
• Whereas: Rtype meth1(Type1 a) {
statement1;
try { x=meth2(b); }
catch(MException e) { statement3; };
statement2;
}
will execute statement3 followed by statement2, where
statement2 must return a value of type Rtype
• Variable x in statement3 will have the value it was given in
statement1, as the exception throw means meth2(b) did not return a
value for it to be set to

22
Catching an exception
• With this: Rtype meth1(Type1 a) {
statement1;
try { x=meth2(b); }
catch(MException e) { statement3; };
statement2;
}
if statement1 threw an MException the method call meth1(v)
would throw on the MException as statement1 is not in the try part
• Whereas with: Rtype meth1(Type1 a) {
try { statement1;
x=meth2(b);
}
catch(MException e) { statement3; };
statement2;
}
It would catch the MException and do statement3 followed by
statement2 without attempting x=meth2(b) as the MException
was thrown before it was reached

23
Checked and Unchecked Exceptions
• If we have Rtype meth1(Type1 a) {
statement1;
x=meth2(b);
statement2;
}
and the call meth2(b) could throws an exception of type MException
then if MException is a checked exception the method header would
have to be:
Rtype meth1(Type1 a) throws MException
• Whereas if it is an unchecked exception, that is not needed
• When an exception is thrown, an object is created to give information on
the exception, and the variable in the header of the catch part of a
try-catch refers to that object
• It is the actual type of that object which determine whether the exception
is a checked exception or an unchecked exception
• If a method call is made that could throw a checked exception, it must
either be in the try part of a try-catch statement with a catch part to
catch it, or the method it is in must indicate, by using throws in its
header, that it could throw on the exception

24
Unchecked Exceptions
• If a method call could throw an unchecked exception, you do not have to
put it in a try-catch statement with a catch part to catch it or indicate
that it can be thrown on
• For example, any method call attached to a variable could cause an
exception to be thrown because obj.meth(arg) will throw an
exception of type NullPointerException if obj is set to null rather
than referring to an object
• You can catch an unchecked exception by putting the code that can
throw it in the try part of a try-catch statement with a catch part to
catch it
• For example, you could write:
try { x=obj.meth(arg); }
catch(NullPointerException e) { x=0; }
but it would be better to write:
if(obj==null) x=0;
else x=obj.meth(arg);
which could also be written as:
x = obj==null ? 0 : obj.meth(arg);

25
Care with try-catch
• One reason for not using:
try { x=obj.meth(arg); }
catch(NullPointerException e) { x=0; }
is that it is less efficient and gives more complex code to use it
• Note that with try-catch statements, unlike if-statements, the { and }
cannot be taken out if they surround just one statement
• Another reason is that you might write this assuming that the
NullPointerException was thrown due to obj being set to null,
but it could also have been thrown by an error inside the code for the
method meth, or a method it calls, and then using try-catch means
you would miss finding out about that error and correcting it
• That is why writing:
try { code1 } catch(Exception e) { code2 }
is never a good idea
• This will catch any exception thrown by code1 and move to code2, you
may think it is how to deal with exceptions you know code1 could throw,
but it will also catch exceptions caused by errors in code1 you did not
know were there and you may not realise that

26
Using try-catch
• Here is a case where it does make sense to use try-catch even
though it is an unchecked type of exception:
try { n=Integer.parseInt(str); }
catch(NumberFormatException e) { n=0; }
• Integer.parseInt(str) is a built-in static method called on the
class Integer (we will look at this in more detail later) that returns the
int value that the String referred to by str represents, or throws a
NumberFormatException if it is not a String that represents an
integer
• So if str refers to "9304", n gets set to 9304
• It would be complex and inefficient to have a separate test of whether
str represents an integer before going through it again to get the integer
• Mostly unchecked exception represent errors in the code, so instead of
catching them it is better to correct the code to stop them happening
• The example above illustrates this is not always the case
• If an exception is not caught at all, the program it in gets halted
• Checked exceptions are more likely to represent issues that are not code
errors but something to do with what the code represents

27
Using Exceptions
• Here is an example of code that makes use of exceptions:
Product p=null;
try { p=shop.buy(acc, name); }
catch(NotEnoughException e) { … }
catch(NotAvailableException e) { … }
• Consider this a method called on an object representing a shop making
use of a object representing an account and returning an object
representing a product bought using the account
• There are various reasons why a product could not be bought, such as
not enough money in the account, the product asked for unavailable
• It makes sense for each of these to cause a separate type of exception
to be thrown
• These are not built-in exception types, you can define your own
exception types to be used in code like this
• This illustrates that a try-catch statement can have more than one
catch part, with different catch parts for different exception types
• p has to be declared before the try-catch and assigned a value to
enable it to be used in the code following the try-catch statement

28
Defining Exceptions
• Here is how to define an exception type:
class NotEnoughException extends Exception {
public NotEnoughException(String str)
{ super(str); }
}
• Then you can write your own statement in code that causes an exception
of this type to be thrown, such as:
if(acc.balance()<cost)
throw new NotAvailableException("Cost is "+cost);
• The String passed in the constructor can be returned by
e.getMessage(), where e refers to the Exception object, so that can
be used in the catch part if something needs to be done there involving
further information on why the exception was thrown
• If the class is declared as extends RuntimeException rather than
extends Exception, then the exception type it creates is an
unchecked exception

29
More on try-catch
• You can write a catch part of a try-catch statement in the form:
catch(ExceptionA | ExceptionB e) { code }
if the same code is to be used if an exception of type ExceptionA or of
type ExceptionB is thrown
• If there are several catch parts, the one that will be used is the first one
whose exception type is the type of the exception thrown or a supertype
of that type
• A try-catch statement can also have a finally part:
try {
code1;
}
catch(MException e) { code2 }
finally { code3 }
• What happens is that the code in the finally part is always executed
whether or not an exception was thrown in the try part. So what
happens in the above example is that code1 will be followed by code3 if
it does not throw an MException, otherwise if only part of code1 was
executed because it halted due to an MException, code2 will be
executed followed by code3.
30
Loops with try-catch
• Compare: int sum=0;
try {
for(int i=0 i<strs.length; i++)
sum = sum+Integer.parseInt(strs[i]);
}
catch(NumberFormatException e) {}
• With: int sum=0;
for(int i=0 i<strs.length; i++)
try {
sum = sum+Integer.parseInt(strs[i]);
}
catch(NumberFormatException e) {}
• If str refers to an array ["13","6","hello","-2","100"] the first
will leave sum set to 19 and the second will leave sum set to 117
• In the first, the loop is inside the try part, meaning that when the
NumberFormatException is thrown, the loop exits, this shows that an
exception is another way a loop can exit regardless of its test
• In the second, the try-catch is inside the loop, meaning the exception
is caught inside the loop, so the loop continues
31
Avoid using null
• As noted previously
p=shop.buy(acc, name);
is best implemented by the method buy, which has return type
Product, throwing an exception to deal with cases where it represents
trying to buy a product that for some reason cannot be bought
• Many might think of programming this by returning null and printing a
message saying what the problem is
• That works in simple code when you are learning to program, but more
realistic code needs to do something other than just printing a message
on the computer that controls the code
• Another issue is that just returning null means the variable p gets set to
null, but it is very easy to forget that could happen, and so code
continues with p set to null
• That can cause problems, because it maybe only somewhere later that
the value of the variable p, perhaps passed to another variable, is used
by calling a method on the variable
• This will then cause a NullPointerException to be thrown, but it can
be hard to work out where the setting of null that caused this was

32
Optional<T>
• Having the method buy throw checked exceptions means when the code
p=shop.buy(acc, name);
is used, it is necessary to do something to deal with the exceptions
• However, p was initiated to null and if there was nothing to set it to
something else in the catch parts, it would still be set to null
• In Java 8, a new way of dealing with this was introduced, it would mean
that p is of type Optional<Product>
• In general, Optional<T> with T set to a type can be used for something
that may or may not have a value rather than just a variable of that type
which may or may not be set to null
• Then p=Optional.empty() is the equivalent of setting it to null, but
it is still an object, and p=Optional.of(thing) is the way to set it to
represent the Product object referred to by thing
• The test p.isPresent() returns true if p does store an object,
otherwise it returns false
• The call p.get() returns the Product object stored inside the
Optional<Product> object that p refers to

33
Using arrays (1)
• If aarr refers to an array, all you have is aarr.length to get
its size, aarr[i]=val to change what is stored at position i to
what val evaluates to, and aaarr[i] used elsewhere to give
what is stored at position i
• Here i is not necessarily a variable, it can be anything that
evaluates to an integer value
• Arrays have a content type, so if aarr is declared as type
Atype[] then val is either a variable of type Atype, or
something that evaluates to a value of type Atype
• Actually val could be a variable of, or something that evaluates
to, a subtype of Atype
• aarr[i] can be used anywhere where a value of type Atype
is needed, or a value of a supertype of Atype

34
Using arrays (2)
• aarr needs to be declared of type Atype[] to be an array of
Atype objects
• aarr = new Atype[n] where n evaluates to an integer value
is how to set aarr to refer to an array of length n
• After this, each position in the array referred to by aarr stores
null, until it is assigned a value by aarr[i]=val
• If Atype was a primitive type, each position in aarr would start
as set to 0, or false if it was boolean
• So this is just like variables in any objects that are not set by
their constructor
• Also, as with any other object type, variables of an array type
store a reference to an object, so if barr is another variable of
type Atype[] then barrA=aarr means barr refers to the
same array that aarr refers to

35
Using arrays (3)
• barr=aarr means barr then refers to the same array that
aarr refers to, so then barr[i]=valB will change aarr[i]
to what valB refers to, as barrA[i] is then another name for
aarr[i]
• If we then did aarr=carr, where carr is another variable of
type Atype[], barr[i]=valC will no longer change what
aarr[i] refers to, as barr continue to refer to what aarr used
to refer to, but aarr then refers to a different array
• Also, aarr = new Atype[m] sets aarr to refer to a new array,
and leaves barr referring to what it used to refer to
• Similarly, if we have a method with signature
Rtype useArray(Atype[] coll, int v)
then a call useArray(aarr,k) means that coll in the
method call is a separate variable which is set to refer to the
same array that aarr refers to, and v is a separate variable set
to the value in k

36
Variables referring to objects (1)
• What we are writing here should by now be obvious, it is the
general concept of variables referring to objects, and the
consequences of that
• Arrays are just objects, the difference being that if obj refers to
an object then obj.avar always means the same variable
called avar in the object referred to by obj, but if aarr refers
to an array while aarr[i] can be considered a variable in what
aarr refers to, the actual variable it means changes if what i
evaluates to changes, which it does for example if i is a
variable of type int in a loop where it gets changed each time
round the loop
• As noted previously, it is best in general programming to use
ArrayList<E>s rather than arrays
• ArrayList<E>s can have their size changed, while arrays are
of fixed size, but arrays are not immutable, as the variables
inside can get changed with assignments like aarr[i]=val

37
Variables referring to objects (2)
• If lista is of type ArrayList<Atype> then
lista.set(i,val) is the equivalent of aarr[i]=val where
aarr is of type Atype[], that is it changes what is at position i
to what val refers to
• However, there is also lista.add(i,val) which also puts
what is in val at position i, but instead of removing what was
there previously, it moves it up to position i+1 and everything
after it up by one position, we will look at this in more detail later
• If there was an assignment listb=lista, similar applies to
what happens after the assignment barr=aarr, the two
variables refer to the same object so listb.set(i,valB) will
also change what is at position i in what lista refers to, and
lista=listc will stop that happening
• As lista.add(i,val) also changes the actual object that
lista refers to, the assignment listb=lista means that
listb.add(i,val) will then change what is in what lista
refers to, as it is the same object
38
Subtypes
• As noted previously, if aarr is of type Atype[], the assignment
aarr[i]=val means that val has to be of type Atype or a
subtype of Atype
• What this means is that val must be a variable of type Atype,
or a method call with Atype as its return type, or a variable of a
subtype of Atype, or a method call with a subtype Atype as its
return type
• If Atype is a class, then a subtype is a class declared as
extends Atype or a subtype of such a class
• If Atype is an interface type, then a subtype is a class declared
as implements Atype or another interface type declared as
extends Atype or a subtype of such a type
• The general point here is that a variable can be assigned a
value of the actual type the variable was declared as, or of a
subtype of that type

39
Supertypes
• As noted previously, aarr[i] can be used anywhere where a
value of type Atype is needed, or a value of a supertype of
Atype
• What this means is an assignment v=aarr[i], then v has to
be of type Atype or a supertype of it
• Or an argument to a method call, meth(…,aarr[i],…), where
the parameter in the method header at the position of aarr[i]
has to be declared as type Atype or a supertype of Atype
• Or a method call aarr[i].meth(…) where meth with the
argument types is in the type Atype or a supertype of Atype
• A supertype of Atype is a class Sclass where the header of
Atype is class Atype extends Sclass, or an interface
Stype where Atype is declared as implements Stype, or a
supertype of such a type
• What is written about aarr[i] here applies to any variable of
type Atype or method call with return type Atype

40
Types
• The types int, double, boolean and a few other numerical
types are primitive types, variables of those types store their
actual values
• All other variables are of object types, they store either null or
a reference to an object
• An object has a fixed actual type, which is the constructor used
to create it
• A variable of an object type may refer to an object whose actual
type is a subtype of the variable type
• A call to a method whose return type is an object type can return
what a variable of that type can store, so an object whose actual
type is its type or a subtype of that type or null
• No objects van have an actual type which is an interface type,
so a variable of that type must refer to an object whose actual
type is a subtype of its type, same for a method call whose
return type is an interface type

41
Method calls and variables (1)
• For an assignment var=val, the left hand side var must be an
actual variable, that includes a variable in an object, so
obj.var where obj refers to an object and var is an instance
variable of its class, or a variable in an array, so arr[i], but
var cannot be a method call
• Apart from this, in any place where a variable of a particular type
can be used, a method call whose return type is that type can be
used
• So with method header Rtype meth1(Atype a, Btype B)
the first argument could be a method call with return type Atype
or subtype of that, and the second argument similar with Btype
• If we have a method with header Atype meth2(Ctype c), and
we have variables myB of type Btype and myC of type Ctype,
we can have a method call meth1(meth2(myC),myB) used
where a variable of type Rtype can be used,

42
Method calls and variables (2)
• Note that the call meth2(myC) can be written like that if it is a
static method, or if it is in call of a method that is called on an
object that meth2 could be called on (same with meth1)
• Otherwise it needs to be obj.meth2(myC) where obj is of a
type that the method meth2 can be called on
• Instead of meth1(meth2(myC)) you could have:
Atype anA = meth2(myC);
meth1(anA,myB);
• Using a method call directly rather than setting a variable to its
return and using the variable can make code a bit shorter and
easier to follow
• However, excessive use of method calls rather than splitting
their use by having temporary variables to hold their values can
lead to code becoming harder to follow
• It makes no difference to efficiency if the variable storing the
result is only used once

43
Method calls and variables (3)
• If the result of a method call is needed to be used more than
once, and it will always return the same result, and doesn’t have
some necessary side effects that are needed each time it is
called, then it should be called once, its value stored in a
variable, and then the variable used more than once
• For example:
Etype val1=meth1(meth2(myC),myB);
return meth3(val1,meth2(myC));
could be written as
return meth3(meth1(meth2(myC),myB),meth2(myC));
• But it would be better as
Atype anA = meth2(myC);
return meth3(meth1(anA,myB),anA);
because otherwise meth2(myC) is unnecessarily called twice,
and that could cause inefficiency if it takes a long time to
calculate and return what it returns

44
Using List<E>
• Here is an example:
static List<E> bigger(List<E> list, E val,
Comparator<? super E> comp) {
List<E> rlist = new ArrayList<>();
for(int i=0; i<list.size() i++)
if(comp.compare(list.get(i),val)>0)
rlist.add(list.get(i))
return rlist;
}
• It is common for list.get(i) to be repeated, as arr[i] is
for arr referring to an array
• This would not be a problem if the actual type of list was
ArrayList<E>, as then list.get(i) does just involve using
arr[i] underneath, where arr refers to the array inside the
ArrayList<E> object
• It is a problem if the actual type is LinkedList<E>, as then
list.get(i) can take a considerable amount of time to
evaluate 45
Using List<E>
• So here is a more efficient version:
static List<E> bigger(List<E> list, E val,
Comparator<? super E> comp) {
List<E> rlist = new ArrayList<>();
for(int i=0; i<list.size() i++) {
E next=list.get(i);
if(comp.compare(next),val)>0)
rlist.add(next);
}
return rlist;
}
• The issue here is that List<E> is an interface type, it can also
refer to an object of type LinkedList<E> where
list.get(i) takes more time
• This is something we will look at later in more detail
• Also, what Comparator<? super E> means

46
For-each loop
• Here is another way it can be done:
static List<E> bigger(List<E> list, E val,
Comparator<? super E> comp) {
List<E> rlist = new ArrayList<>();
for(E obj : list)
if(comp.compare(obj),val)>0)
rlist.add(obj);
return rlist;
}
• This is using what is called a for-each loop
• It takes the form
for(Type v : coll) statement
where coll is a collection of objects of type Type.
• Then statement is executed repeatedly, each time v set to a
different element of the collection, it works for all Java’s built-in
collection types, so Set<Type> as well as List<Type> and it
would also work if coll was of type Type[]
47
Two Simple Class Examples
class Rectangle { class Colour {
private int height, width; private int red, green, blue;

public Rectangle(int h, int w) { public Colour(int r, int g, int b) {


height=h; red=Math.max(0,Math.min(255,r));
width=w; green=Math.max(0,Math.min(255,g));
} blue=Math.max(0,Math.min(255,b));
}
public int getHeight() {
return height; public int getRed() {
} return red;
}
public void setHeight(int h) {
height=h; public int getGreen() {
} return green;
public int getWidth() { }
return height; public int getBlue() {
} return blue;
public void setWidth(int w) { }
width=w; public Colour tint(double t){
} int r = (int) Math.round(red*t);
public int area() { int g = (int) Math.round(green*t);
return height*width; int b = (int) Math.round(blue*t);
} return new Colour(r,g,b);
}
} }
Mutable and Immutable
• Class Rectangle and class Colour are two simple examples
of classes, in both cases their contents are just several integers
variables (two for Rectangle, three for Colour)
• However, class Rectangle is mutable because it has methods
that can change the value of its instance variables
• Class Colour is immutable as it has no methods that change
the value of its instance variables
• Class Colour has a method tint that implements changing a
colour, but it does it by creating and returning a new Colour
object rather than changing what is in the actual Colour object
it is called on
• The integers in class Colour are limited to have values
between 0 and 255, so this is a case where memory use could
be made more efficient by using a different primitive type
• Java’s type short uses 16 bits (binary values) whereas int
uses 32, so short can be used if a number will never be
greater than 32767 or less than -32768
49
Primitive Types
• Here the type byte is class Colour {
used, which has just 8 private byte red, green, blue;
bits, so can store from - public Colour(int r, int g, int b) {
128 to 127 red=(byte)(Math.max(0,Math.min(255,r))-128);
green=(byte)(Math.max(0,Math.min(255,g))-128);
• So note how it is used to blue=(byte)(Math.max(0,Math.min(255,b))-128);
store numbers that can }
be from 0 to 255 public int getRed() {
return red+128;
• (byte) actually }
converts an int to a public int getGreen() {
byte, so it is different return green+128;
}
from how (Type)
public int getBlue() {
works for an object type return blue+128;
• (int) is needed as }
Math.round(x) public Colour tint(double t){
int r = (int) Math.round(red*t);
returns a long value, int g = (int) Math.round(green*t);
an integer using 64 bits int b = (int) Math.round(blue*t);
return new Colour(r,g,b);
• Colour objects here }
would use less memory }
Cells and Pointers Diagrams
• Can be used to show dynamic structure of object oriented
programs at variable and object level
• A small box represents a variable
• A large box, or other shape, represents an objects
• The representation of objects may contain boxes representing
the variables inside them
• Arrows represent variables referring to objects
• So an arrow comes from inside a box representing a variable
and points to the outside of a box representing an object
• Only one arrow can come from a box
• More than one arrow can point to a box
Cells and Pointers Diagrams Example (1)
• The code
Rectangle rec1 = new Rectangle(5,8);
Rectangle rec2 = new Rectangle(20,7);
Rectangle rec3 = rec2;
produces what can be shown as the cells and pointers diagram:

5 20 What we have here is


height height boxes representing
variables, with arrows
8 7 pointing to shapes
width width representing objects
that variables refer to,
and boxes representing
the instance variables
that are inside objects
rec1 rec2 rec3
Cells and Pointers Diagrams Example (2)
• If the assignment rec2=rec1 took place, the diagram would
change to:

5 20
height height

7
8
width width

rec1 rec2 rec3

• It should be clear from this why a method call such as


rec1.setWidth(25) would also change what is in what rec2
refers to, as it is the same object
Inheritance
class ColouredRectangle extends Rectangle {
private Colour colour;
public ColouredRectangle(int h, int w, Colour c) {
super(h,w);
colour=c;
}
public Colour getColour() {
return colour;
}
public void setColour(Colour c) {
colour=c;
}
}
• The class ColouredRectangle is a subclass of Rectangle
• ColouredRectangle has all the methods of Rectangle,
plus getColour and setColour
• It does not override any of the methods of Rectangle

54
Cells and Pointers Diagrams Example (3)
• The code
Colour col = new colour(100,50,230);
ColouredRectangle crec =
new ColouredRectangle(20,7,col);
Rectangle rec = crec;
produces what can be shown as the cells and pointers diagram:

20
100
height
red
7
width
50
green

colour 230
blue

rec crec col


Object and Inheritance Issues
• The object of class ColouredRectangle has three variables
inside it, two of which store integers and the other stores a
reference to a Colour object
• A variable of type Rectangle can refer to an object of type
ColouredRectangle, although a method that is in
ColouredRectangle but not in Rectangle cannot be called
on that variable
• In this case we do have an instance variable inside an object
referring to an object that is also referred to by a variable outside
the object
• However, this will not cause a problem in this case, because
Colour is immutable so it is not possible to call a method on
the variable col outside the ColouredRectangle object that
would cause the ColouredRectangle object to have its colour
changed
Further Inheritance
• Here is an example of a subclass of a subclass:
class ShadedRectangle extends ColouredRectangle {
private double shade;
public ShadedRectangle(int h, int w, Colour c) {
super(h,w,c);
shade=0.0;
}
public Colour getColour() {
return super.getColour().tint(1-shade);
}
public void setLight(double s) {
shade=s;
}
}
• In this case it both adds a new method setLight, and
overrides the existing method getColour
• Subclasses can override methods or add new methods or do
both

57
Direct and Indirect Superclasses
• In Java, a class can only be declared as extends one other class
• In some other programming languages, C++ for example, a class
can be declared as extends more than one class, this is known
as multiple inheritance
• However, in Java a class can still be a subclass of more than one
class because of the way a class can be declared a subclass of
one which is itself a subclass of another
• So ShadedRectangle is a subclass of both
ColouredRectangle and Rectangle, it could override the
methods of Rectangle as well as those introduced in
ColouredRectangle, a variable of type Rectangle could refer
to an object of actual type ShadedRectangle
• To distinguish this, if you want to state that a class actually
extends another class, we can call it a direct subclass,
whereas if there are more than one layers of extends, it is an
indirect subclass

58
Java’s class Object
• In Java, any class that is not declared as extends another class
will be a direct subclass of Java’s class called Object
• That means that all classes are subclasses of Object, as they
must be either a direct subclass of Object or a subclass of a
class that is a direct subclass of Object, or a subclass of a
subclass of a class that is a direct subclass of Object, and so on
• One reason for this is that it enables a variable to be declared that
could refer to an object of any class, to do so give it type Object
• This can be used for generalised code, but it is usually better to
declare variables who type is a type variable rather than directly
use the type Object
• The other reason for having all classes subclasses of Object is
that methods in class Object can be called on any object
• Any class can override these methods, so the actual code used
will be the overridden code if a method in class Object is called
on an object which has a closer superclass that overrides it

59
Overriding Object Methods (1)
• The method toString is in class Object, but it is usual to
override it so that something like System.out.println(obj)
prints something that is useful showing what obj represents
• For example adding this method in class Rectangle:
public String toString() {
return "Rectangle ("+height+","+width+")";
}
means that System.out.println("This is: "+rec) will
print This is Rectangle (5,10) when rec refers to a
Rectangle with height set to 5 and width set to 10
• The other commonly overridden method in Object is equals
• Without being overridden, obj1.equals(obj2) will return true
only if obj1 and obj2 refer to the same object
• We might want to override equals so that rec1.equals(rec2)
returns true if rec1 and rec2 refer to separate Rectangle
objects which have the same height and width values
• More on overriding equals to be covered later
60
Overriding Object Methods (2)
• Suppose we had the following method:
static Rectangle biggest(Rectangle r1, Rectangle r2) {
if(r2.area()>r1.area()) return r2;
return r1;
}
with variables
Rectangle aRec = new Rectangle(5,8);
ColouredRectangle colRec =
new ColouredRectangle(6,4, new Colour(70,0,200));
and later we did:
Rectangle bigRec = biggest(aRec,colRec);
• Then bigRec might or might not refer to a ColouredRectangle
object
• It would if just colRec.setWidth(10) had occurred, so then
System.out.println("Biggest is: "+bigRec);
would print: Biggest is: Rectangle: (6,10)
if class ColouredRectangle had not overridden toString()
61
Overriding Object Methods (3)
• If also class ColouredRectangle had this method:
public String toString() {
return "Coloured Rectangle ("+
height+","+width+")"+colour;
}
and class Colour had this method:
public String toString() {
return "("+red+","+green+","+blue+")";
}
• Then, with the previous example what would be printed is:
Biggest is: Coloured Rectangle: (6,10)(70,0,200)
• So although variable rec is of type Rectangle, if it refers to a
ColouredRectangle object and toString() is called on it, it
uses the method that overrides toString in ColouredMethod
• That is dynamic binding, use the method of the actual object, or
its closest superclass
• This example shows a method may be overridden more than once
62
Overloading Methods (1)
• Suppose that instead of overriding toString in Rectangle and
ColouredRectangle we had a class Printer which had these
methods in it:
public String printStr(Rectangle r) {
return "Rectangle ("+
r.getHeight()+","+r.getWidth()+")";
}

public String printStr(ColouredRectangle cr) {


return "Coloured Rectangle ("+
cr.getHeight()+","+cr.getWidth()+
")"+cr.getColour();
}
• This is an example of overloading a method, which means
having more than one method with the same name
• The methods differ, either by their number of parameters, or by
the type of their parameters
• This code assumes toString is overridden in class Colour
63
Overloading Methods (2)
• Then if pr is a variable of type Printer (and it doesn’t refer to an
object of a subclass that overrides these methods), what does:
System.out.println("Biggest is: "+pr.print(bigRec));
cause to be printed when bigRec refers to the same
ColouredRectangle object that colRec refers to?
• The answer is Biggest is: Rectangle (6,10)
• Whereas :
System.out.println("2nd was: "+pr.print(colRec));
will print 2nd was: Coloured Rectangle (6,10)(70,0,200)
• The issue here is that when a method is overloaded, the choice of
which method to use is done when the code is compiled, and not
when it is run
• So as bigRec is of type Rectangle, the method that has
parameter type Rectangle will be used for pr.print(bigRec)
• That is, when there is a choice of overloaded methods, a static
choice is made rather than the dynamic choice with overridden
methods
64
Summary
• Everything covered in these slides is a basic aspect of Java, that
anyone who claims they are a Java programmer should be
familiar with
• Fully understanding the concept of variables referring to objects,
and what that means in practice is very important
• As is full understanding of how subclasses can work by overriding
methods, and what that means when a variable could refer to a
object of a subclass
• It is important to understand these concepts in general, and the
extent to which they apply to other programming languages
• The general concept of variables referring to objects, and dynamic
binding of methods due to the way variables can refer to objects of
different types applies just as much to Python
• Python also makes use of exceptions, as covered here
• The next thing we need to look at to give a general coverage of
basic Java programming is its built-in Collection types

65

You might also like