JP chapters 24 and 25
Slide handouts 6.
The java tutorial on
reflection available online at: http://java.sun.com/docs/books/tutorial/reflect/index.html or here in print-friendly
pdf
Reflection in programming
languages is the programs capability to treat programs themselves as objects.
In Java this means to treat methods, classes, constructors, fields etc. as
objects which can be queried. This means that there is a language shared among
all objects, so that if you get a reference to an Object, you can find out
which class it is an instance of, which interfaces that class implements, which
fields the class has, what is its super class, and what methods it has. You can
also find out what constructors exist for that class, and ask the class to make
a new instance using that constructor.
You can also find out what
methods an object has, and then call a method on that object. Now, you can do
this already you think. But here you can do it differently; in essence you can
have the following piece of code:
String name = "myMethod";
anObject.call(name, 7);
which would then correspond to the
call "anObject.myMethod(7)". The big difference here is that one can
change the variable name at run-time, thus making it possible to call different
methods depending on what the value of the variable name is.
Reflection is a programming
technique which allows very general programs to be written. Typically
reflection is not used when you write applications to a customer, but when you
write software libraries. The two exercises below are examples of such general
libraries.
Annotations have been added to Java
5.0 and give the possibility of adding meta information to code. Annotations
can be accessed either by compilation tools or at runtime using reflection. Again
writing the typical application for a customer will not involve creating or
accessing annotation interfaces. You might however encounter useful tools for
application programmers that that depend on you marking code with annotations
they provide.
The test framework presented at
class is a rough and unstable outline of such a program. This exercise is about
making it more mature. If you have not read the lecture notes, now is the time
to do this, or skip to exercise 2, and return to this one later. The test
framework is modelled after the testing framework called JUnit, which
is used as a very important part of eXtreme Programming and test driven development.
a)
(Hand
in) The present implementation of UnitTester finds all methods that start
with "test", but really only those that take no arguments should be
chosen. Rewrite the program so that only those with no arguments are called.
And give a warning (just print to System.out) if there is a method that starts
with "test", but takes parameters.
b)
(Hand
in) Each test method should be public. Give a warning if a test method is
not public.
c)
(Hand
in) Change the code so also methods that start with "check" as
well as "test" are executed.
d)
(Hand
in) The TestClass has two checkMethods. Very often the test methods are
about comparing two values, the expected and the observed. Add three new
methods to TestClass, check(int expected, int observed), check(String expected,
String observed), and check(Object expected, Object observed). They should work
in similar fashion to the existing check methods. a) Argue why you have made
the methods like you have. b) Argue in particular about the choice of equality
test done inside the three methods.
e)
(Hand
in) The way we have implemented the UnitTester depends on a naming
convention that all methods to be tested have method names starting with “test”
or “check”. A different approach is to attach a @Test annotation to methods
that are to be tested. In this exercise you have to:
1.
Declare a @Test annotation that has
a single int valued member called “level” and a default value of 0. The idea is
to make it possible to use different levels of testing. If testing is done at
level 0 all tests are run, if testing is done at level 2 all tests with level
values of 2 or above are run and so on.
2.
Add a new method getMethods(int
level) that uses @Test annotations and their test levels rather than method
names as a selection criteria. Change testTheClass() to use the new method
taking a test level as parameter.
3.
Add annotations with different level
values to MyTestClass and try running it at different levels.
Exercise 2.
Normally it is up to the programmer
to write a toString() method for each class one creates. This exercise is about
writing a general toString method once and for all. As part of the reflection
API for java, it is possible to find out which fields exist for a given object,
and to get their values. The purpose of this exercise is to make a toString
method that gives a printed representation of any object, in such a manner that
all fields are printed, and references to other objects are handled as
well.
To solve the exercises, you will
need to examine the java.lang.reflect API. Also, the tutorial mentioned in the
reading section above contains small code-bits which are useful. All in all,
you will end up with around 50-60 lines of code, including that necessary for
trying it out. You are welcome to hand in only the final classes (ToString and
ToStringTest), we do not need the intermediate solutions.
a)
(Hand
in) Write a class ToString with one static method "String
toString(Object o)". The first version should just return the name of the
class the object is an instance of. Write another class, ToStringTest, which
prints the result of calling "ToString.toString("Hans")".
b)
(Hand
in) Extend the toString method. This time it should find out which fields
exist in the object, and return a string of the format
"classname{fieldName1, fieldName2,....,fieldNameN}". When this works,
make sure you do not print out a superfluous comma just before the closing
brace.
c)
(Hand
in) Extend the toString method, so that each field is printed in the form
"fieldName: fieldType".
d)
(Hand
in) We do not want static fields to be included in the printout. Make sure
no static fields are printed. Keep testing the method using the ToStringTest
method.
e)
(Hand
in) Extend the method to print out the values of each field using Javas built
in toString method. The format for each field should now be "fieldName:
fieldType = value". Note, the value of a private field can be read after
you use the "setAccessible(true)" method that fields inherit from
AccessibleObject.
f)
(Hand in) A field might be an Array. If
it is, write each value in the array as "[val1, val2, val3,...,
valN]". Optional extra. If the array has more than 15 elements,
only the first 15 should be printed, and the rest should be printed as
"...". Hint on retrieving values from an Array: Try looking at the documentation
of the Array class in the API.
g)
(Optional)
In the above, we used Javas toString method to print out the value of the
fields. The next step is to use Javas toString method for the primitive values
only, and to call recursively for other values. However, there is a problem if
the object references form a cycle; as for instance in the Car-Person exercise
(lecture 2), where a car object refers to a person object which in turn refers
to the same car object. One way to avoid this is to give an extra parameter to
our toString method, an integer which tells which depth to go to. At depth 0 is
the object is not printed, but the method just returns "...". Depth 1
means that the fields of the objects are printed, and those fields that are of primitive
type or String have
their values printed, but references are printed as "..."; Extend the
toString method with a Depth parameter. A Person with name, age and a Car is
thus printed as follows:
At depth 1: Person{name:String="Hans",age:int=34,
myCar:Car=...}
At depth 2: Person{name:String="Hans",age:int=34,
myCar:Car=
Car{make:String="Ford",owner:Person=...}}
h)
(Optional) So far we have not worried about the
format of the string we return. This exercise is about formatting the output.
The format should be so that each field is printed on a line of its own, and
indented so that fields belonging to the same object starts at the same
indentation, and objects which are referred to are indented further. Each
indentation step should be three space characters. The easiest way to do this
is to add an extra parameter, String indent, which is used to indent with, and
to increase the indentation each recursive call.