Pedant
, UpperCaser
and LowerCaser
rather than StringTransformer
. For example, we could
write version of StringTrans2
which didn't use
StringTransformer
:
1 import java.io.*; 2 3 class StringTrans2f 4 { 5 // String transform using UpperCaser then Pedant 6 7 public static void main(String[] args) throws IOException 8 { 9 String phrase1,phrase2,phrase3; 10 BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 11 Uppercaser st1 = new UpperCaser(), 12 Pedant st2 = new Pedant(); 13 System.out.println("Type a phrase to be transformed:"); 14 phrase1=in.readLine(); 15 phrase2=st1.transform(phrase1); 16 phrase3=st2.transform(phrase2); 17 System.out.println("\nTransformed phrase is:"); 18 System.out.println(phrase3); 19 } 20 21 }The advantage of the separate superclass
StringTransformer
is that it allows us to write code which is generalised. For example,
you could be told "write some code which takes two
StringTransformer
s and a string, and applies the
transform
method of the first StringTransformer
then the transform
method of the second
StringTransformer
and returns the resulting string. Here
is how you could write it:
1 class Doubler 2 { 3 public static String transform(StringTransformer first,StringTransformer second,String phrase) 4 { 5 return second.transform(first.transform(phrase)); 6 } 7 }It's just a class with a single static method in it. You will see more use of static methods (which can also be called "functions") in the "TwoNums" examples. The important thing is that you can write this method without knowing whether each of
st1
and st2
is going to be
an UpperCaser
, a LowerCaser
or a Pedant
.
Indeed, it can be reused with them set to objects of different types, so long
as the types are always subtypes of StringTransformer
.
Here is how you would use Doubler
in a main
method which behaves just like the main
method in
StringTrans2
:
1 import java.io.*; 2 3 class StringTrans12 4 { 5 // String transform using UpperCaser then Pedant via Doubler 6 7 public static void main(String[] args) throws IOException 8 { 9 String phrase1,phrase2; 10 BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 11 StringTransformer st1 = new UpperCaser(), st2 = new Pedant(); 12 System.out.println("Type a phrase to be transformed:"); 13 phrase1=in.readLine(); 14 phrase2=Doubler.transform(st1,st2,phrase1); 15 System.out.println("\nTransformed phrase is:"); 16 System.out.println(phrase2); 17 } 18 19 }Of course, you could apply the
transform
method of
Pedant
followed by the transform
method of
UpperCaser
if you wrote line 14 of StringTrans12.java
as:
phrase2=Doubler.transform(st2,st1,phrase1);
UpperCaser
,
LowerCaser
and Pedant
is that it only really
makes sense to have one object of each type around. There is no way of
varying a Pedant
object (just like the other two): each
time the transform
method is called on any Pedant
object it does exactly the same thing - returns the argument with
"Obviously "
added to its front. You could have two
Pedant
objects about, for example line 11 in
StringTrans12
above could be:
StringTransformer st1 = new Pedant(), st2 = new Pedant();and then the program would be one that asked for a phrase and printed it out with
"Obviously Obviously "
in front of it. However,
that does not make sense, it would be simpler to have just one
Pedant
object about and use it twice, so line 11 would
be:
StringTransformer st = new Pedant();and line 14 would be:
phrase2=Doubler.transform(st,st,phrase1);Note this means that when this call is made, on line 5 of
Doubler.java
,
both first
and second
refer to the same object, a
form of aliasing. Sometimes (though it causes no harm in this case) when
you write methods you have to be careful to consider that two arguments
may both refer to the same object.
However, now let's consider a simple StringTransformer
which is parameterised. A NameDropper
object has
a transform
method which adds "Fred says "
,
"John says "
or whatever to the phrase argument, where the
particular name ("Fred"
, "John"
etc) is set
when the NameDropper
object is created. Here's the code
for it:
1 class NameDropper implements StringTransformer 2 { 3 private String myName; 4 5 public NameDropper(String aName) 6 { 7 myName=aName; 8 } 9 10 public String transform(String thePhrase) 11 { 12 return myName+" says "+thePhrase; 13 } 14 }Every
NameDropper
object has its own field, defined in line
3 of NameDropper.java
, where it stores the name it "drops".
There was no constructor for UpperCaser
, LowerCaser
or Pedant
because there was nothing a constructor had to do.
However, NameDropper
needs a constructor, and lines 5-8 are
the constructor, because when a new NameDropper
is created,
its myName
field has to be set to the name to be dropped.
That name is given to it as an argument to the constructor. So, for example,
StringTransformer st1 = new NameDropper("Fred"), st2 = new NameDropper("John");will create two
NameDropper
objects, referred to by st1
and st2
, one of which drops the name "Fred
and the
other the name "John"
. If you replaced line 11 in
StringTrans12.java
by the above line, you would get a
program which asks for a phrase, and then prints it out with
"John says Fred says "
added to the front. Now it is clear
that we may need any number of NameDropper
objects,
because each may have a different name to drop.
String-transformers with arguments can be used as an alternative way of
dealing with the problem that led us to introduce class Doubler
above. We could have a type of StringTransformer
object
which when created takes two further StringTransformer
objects
as arguments and stores them in fields. Then its own transform
method could consist of applying the transform
method of these
two fields. We call this type of StringTransformer
object
a Joiner
object because it joins the transform
methods of two other objects to form a single transform
method.
Here is the code for it:
1 class Joiner implements StringTransformer 2 { 3 StringTransformer first,second; 4 5 public Joiner(StringTransformer st1,StringTransformer st2) 6 { 7 first=st1; 8 second=st2; 9 } 10 11 public String transform(String thePhrase) 12 { 13 String phrase1 = first.transform(thePhrase); 14 return second.transform(phrase1); 15 } 16 }Here is a program which has the same effect as
StringTrans2
but
uses Joiner
1 import java.io.*; 2 3 class StringTrans12a 4 { 5 // String transform using UpperCaser then Pedant via Joiner 6 7 public static void main(String[] args) throws IOException 8 { 9 String phrase1,phrase2; 10 BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 11 StringTransformer st1 = new UpperCaser(), st2 = new Pedant(); 12 StringTransformer st3 = new Joiner(st1,st2); 13 System.out.println("Type a phrase to be transformed:"); 14 phrase1=in.readLine(); 15 phrase2=st3.transform(phrase1); 16 System.out.println("\nTransformed phrase is:"); 17 System.out.println(phrase2); 18 } 19 20 }
NameDropper
object is created, its behaviour
never changes. Once we have created a NameDropper
object
using new NameDropper("Fred")
, whenever we call its
transform
method it will always return the argument
with "Fred says "
added to it. This is because a
NameDropper
object is immutable: there is no way to change
its state. It does not have any method of its own which changes the sole
thing in its state, its myName
field. Neither may anything
else change that field since it is declared as private
.
So once we have assigned:
StringTransformer st = new NameDropper("Fred");we know that a call
st.transform("hello")
will always return
"Fred says hello"
, at least until st
is
assigned to refer to another object.
It can be valuable to have the assurance that an object type is immutable.
It can make a program difficult to follow if a call to an object method has
different effects in different places even though its arguments are identical.
However, some objects need to be mutable. Here is an example of a class
which provides StringTransformer
objects which are mutable:
1 class SayingCounter implements StringTransformer 2 { 3 private String myName; 4 private int count; 5 6 public SayingCounter(String aName) 7 { 8 myName=aName; 9 } 10 11 public String transform(String thePhrase) 12 { 13 count++; 14 return myName+"'s saying no."+count+" is "+thePhrase; 15 } 16 }Here is a program that makes use of objects of this type:
1 import java.io.*; 2 3 class StringTrans13 4 { 5 // Demonstration of behaviour of SayingCounter objects 6 7 public static void main(String[] args) throws IOException 8 { 9 String phrase1,phrase2; 10 BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 11 StringTransformer st1 = new SayingCounter("Fred"), st2 = new SayingCounter("John"); 12 System.out.println("Type first phrase:"); 13 phrase1=in.readLine(); 14 System.out.println("Type second phrase:"); 15 phrase2=in.readLine(); 16 System.out.println("\nTransformed phrases are:"); 17 System.out.println(st1.transform(phrase1)); 18 System.out.println(st2.transform(phrase1)); 19 System.out.println(st1.transform(phrase2)); 20 System.out.println(st1.transform(phrase1)); 21 } 22 23 }A
SayingCounter
object is like a NameDropper
object,
in that it has a name which it adds to the phrase it takes in when its
transform
method is called. But it also has a field called
count
which it increments by 1 each time the
transform
method is called. The field is declared on line
4 of SayingCount.java
and the increment is on line 13. Note that
the count
field doesn't have to be initialised. That is
because int
fields in objects are automatically initialised to
0 when the objects are created. Because the object referred to by
st1
in StringTrans13.java
is mutable, the
method call on that object in line 17 does not return the same value as
the identical method call in line 20 even thought the code in between
shows no changes to either what st1
refers to or what the
argument to the method, phrase1
refers to. But the state of the
object referred to by st1
is changed each time its
transform
method is called, on lines 17, 18 and 20.
Note that each SayingCounter
object has its own count
field, so the call to transform
on line 18 causes the
count
field of the object referred to by st2
to
change, but not that of the object referred to by st1
.
If line 4 of SayingCounter.java
were:
private static int count;this would not be the case. The word
static
here indicates that
there is a single variable called count
shared by all
SayingCounter
objects, so whenever one of them updated it the
update would apply to all of them. A variable declared as static
in a class like this so that it is shared by all objects of that class is known
as a class variable.
Last modified: 4 Oct 1999