CSC207 Software Design
Lectures
Reflection

Background

Turing's great insight: programs are just another kind of data

Source code is text

Manipulate it line by line, or by parsing expressions

Compiled programs are data, too

Integers and strings are bytes in memory that you interpret a certain way

Instructions in methods are just bytes too

No reason why a program can't inspect itself, i.e. read its own data

How Objects Work

The Class Class

Instances of the class Class store information about classes

Class name

Inheritance

Interfaces implemented

Methods, members, etc.

Can look up instances:

By name

From an object

Showing a Type

public static void showType(PrintStream out, String className) throws ClassNotFoundException {
    Class thisClass = Class.forName(className);
    String flavor = thisClass.isInterface() ? "interface" : "class";
    out.println(flavor + " " + className);
    Class parentClass = thisClass.getSuperclass();
    if (parentClass != null) {
        out.println(" extends " + parentClass.getName());
    }
    Class[] interfaces = thisClass.getInterfaces();
    for (int i=0; i<interfaces.length; ++i) {
        out.println("  implements " + interfaces[i].getName());
    }
    out.println();
}

Output for Type Example

class java.lang.Object

class java.lang.Integer
 extends java.lang.Number
  implements java.lang.Comparable

class java.util.HashMap
 extends java.util.AbstractMap
  implements java.util.Map
  implements java.lang.Cloneable
  implements java.io.Serializable

class Point
 extends java.lang.Object

More About Classes

Examining Class Contents

public static void showContents(PrintStream out, boolean hideObject, String name)
    throws ClassNotFoundException {
    Class cls = Class.forName(name);
    out.println(name);
    showMembers(out, hideObject, name + " fields",       cls.getFields());
    showMembers(out, hideObject, name + " constructors", cls.getConstructors());
    showMembers(out, hideObject, name + " methods",      cls.getMethods());
}

public static void showMembers(PrintStream out, boolean hideObject,
                               String title, Member[] members) {
    out.println("  " + title);
    for (int i=0; i<members.length; ++i) {
        if (members[i].getDeclaringClass() == Object.class) {
            if (hideObject) {
                continue;
            }
        }
        out.println("\t" + members[i]);
    }
}

Output for Class Content Example

Note: things belonging to java.lang.Object are hidden

java.util.Map
  java.util.Map fields
  java.util.Map constructors
  java.util.Map methods
    public abstract int java.util.Map.hashCode()
    public abstract java.lang.Object java.util.Map.put(java.lang.Object,java.lang.Object)
    public abstract boolean java.util.Map.equals(java.lang.Object)
    public abstract java.lang.Object java.util.Map.get(java.lang.Object)
    public abstract int java.util.Map.size()
    ...etc...
Point
  Point fields
  Point constructors
    public Point(java.lang.String,int,int)
  Point methods
    public java.lang.String Point.toString()
    public java.lang.String Point.getName()
    public void Point.setName(java.lang.String)
    public int Point.getX()
    public void Point.setX(int)
    public int Point.getY()
    public void Point.setY(int)

Getting at Members

How to access members of a specific object?

Without making raw pointers into memory part of the language

They are a rich source of errors in C/C++

Introduce a class Field

Encapsulates access to a particular field of instances of a class

Knows "where the field is" in objects of that class

Use its get() and set() methods to inspect and modify the object

Examining Fields

public static void main(String[] args) {
    PublicPoint p = new PublicPoint("center", 3, 3);
    showField(System.out, p, "fName");
    showField(System.out, p, "fX");
    showField(System.out, p, "fY");
    showField(System.out, p, "fZ");
}

public static void showField(PrintStream out, Object obj, String fieldName) {
    try {
        Class cls = obj.getClass();
        Field field = cls.getField(fieldName);
        Object value = field.get(obj);
        out.println(fieldName + ": " + value);
    }
    catch (NoSuchFieldException e) {
        System.err.println(e);
    }
    catch (IllegalAccessException e) {
        System.err.println(e);
    }
}

Output for Fields Example

fName: center
fX: 3
fY: 3
java.lang.NoSuchFieldException: fZ

Getting at Methods

Examine a class's methods in a similar way

The class Method subclasses Member to represent a callable method

Reports the method's return type by handing back a Class instance

Reports parameter types using an array of Class instances

Examining Methods

public static void showMethods(PrintStream out, Object obj)
    throws NoSuchMethodException, IllegalAccessException,
           InvocationTargetException {
    Class cls = obj.getClass();
    out.println(cls.getName());
    Member[] members = cls.getMethods();
    for (int im=0; im<members.length; ++im) {
        Method meth = (Method)members[im];
        if (meth.getDeclaringClass() == cls) {
            showMethod(out, (Method)members[im]);
        }
    }
}

public static void showMethod(PrintStream out, Method meth) {
    Class returnType = meth.getReturnType();
    out.print("\t" + returnType.getName() + " " + meth.getName() + "(");
    Class[] paramTypes = meth.getParameterTypes();
    String prefix = "";
    for (int i=0; i<paramTypes.length; ++i) {
        out.print(prefix + paramTypes[i].getName());
        prefix = ", ";
    }
    out.println(")");
}

Output of Methods Example

Point
        java.lang.String toString()
        java.lang.String getName()
        void setName(java.lang.String)
        int getX()
        void setX(int)
        int getY()
        void setY(int)

Calling Methods

Need to:

Look up a method based on its name and signature

Call a method, passing in parameters and capturing return value

Specify a signature as an array of Class objects

Specifies the types of arguments

Special values for non-reference types like int and boolean

Note: cannot select based on return type

Specify parameters as an Object array

Use Integer instead of int, etc.

Java will extract values as necessary

Three Classes With Similar Methods

public class Red {
    public Red() {
    }

    public void left(Integer i) {
        System.out.println("Red with Integer " + i);
    }

    public void right(String s) {
        System.out.println("Red with String " + s);
    }
}
public class Green {
    public Green() {
    }

    public String left(Integer i) {
        System.out.println("Green with Integer " + i);
        return "Green string";
    }

    public void right(Integer i) {
        System.out.println("Green with Integer " + i);
    }
}
public class Blue {
    public Blue() {
    }

    public Integer left(Integer i) {
        System.out.println("Blue with Integer " + i);
        return new Integer(99);
    }

    private void right(Integer i) {
        System.out.println("Blue with Integer " + i);
    }
}

Example of Calling Methods

    public static void main(String[] args) {

	// Look for method taking single integer parameter
	String methodName = args[0];
	Class[] signature = new Class[1];
	signature[0] = Integer.class;

	// Look up named method for each class
	for (int i=1; i<args.length; ++i) {
	    try {
		String clsName = args[i];
		Class cls = Class.forName(clsName);
		Method meth = cls.getMethod(methodName, signature);
		System.out.println("Calling " + methodName + " for " + clsName);
		Object obj = cls.newInstance();
		Object[] params = new Object[1];
		params[0] = new Integer(i);
		Object result = meth.invoke(obj, params);
		System.out.println("Result: '" + result + "'");
	    }
	    catch (ClassNotFoundException e) {
		System.out.println("Class not found: " + e);
	    }
	    catch (NoSuchMethodException e) {
		System.out.println("No such method: " + e);
	    }
	    catch (InstantiationException e) {
		System.out.println("Cannot instantiate class: " + e);
	    }
	    catch (IllegalAccessException e) {
		System.out.println("Illegal access: " + e);
	    }
	    catch (InvocationTargetException e) {
		System.out.println("Bad target for method call: " + e);
	    }
	    System.out.println("\n");
	}
    }

Output of Method Calling Example

$ java ...args... CallMethods left Red Green Blue
Calling left for Red
Red with Integer 1
Result: 'null'

Calling left for Green
Green with Integer 2
Result: 'Green string'

Calling left for Blue
Blue with Integer 3
Result: '99'

$ java ...args... CallMethods right Red Green Blue
No such method: java.lang.NoSuchMethodException: Red.right(java.lang.Integer)

Calling right for Green
Green with Integer 2
Result: 'null'

No such method: java.lang.NoSuchMethodException: Blue.right(java.lang.Integer)

Switching on Type

Often have to handle basic types case-by-case

For example, an object editor in an IDE

Pattern:

Inspect Object to find out what type it is

Cast it to that type

Do something with integers

Something else with strings

Everything else expressed in terms of these

Doubling Up

Example: double fields of an object without knowing what they are

public static void main(String[] args) {
    ObjPoint p = new ObjPoint("center", 3, 3);
    System.out.println("Object before doubling: " + p);
    doubleField(p, "X");
    doubleField(p, "Y");
    doubleField(p, "Name");
    System.out.println("Object after doubling: " + p);
}

$ java ...args... Double
Object before doubling: [center : 3, 3]
Object after doubling: [centercenter : 6, 6]

Getting the Old Value

public static Object getOldValue(Object obj, String fieldName)
    throws NoSuchMethodException, IllegalAccessException,
           InvocationTargetException {
    Class cls = obj.getClass();
    String getterName = "get" + fieldName;
    
    Class[] emptyParams = new Class[]{};
    Method getter = cls.getMethod(getterName, emptyParams);
    
    Object[] emptyArgs = new Object[]{};
    Object oldValue = getter.invoke(obj, emptyArgs);
    
    return oldValue;
}

Setting the New Value

public static void setNewValue(Object obj, String fieldName, Object newValue)
    throws NoSuchMethodException, IllegalAccessException,
           InvocationTargetException {
    String setterName = "set" + fieldName;
    Class cls = obj.getClass();
    Class[] params = new Class[]{newValue.getClass()};
    Method setter = cls.getMethod(setterName, params);
    Object[] setterArgs = new Object[]{newValue};
    setter.invoke(obj, setterArgs);
}

Doubling a Field

Note: this is the only method that needs to be changed to handle more types

public static Object doubleValue(Object original) {
    Class cls = original.getClass();
    Object result = null;
    if (cls == Integer.class) {
        int nv = ((Integer)original).intValue() * 2;
        result = new Integer(nv);
    }
    else if (cls == String.class) {
        result = ((String)original) + ((String)original);
    }
    return result;
}

Key Points

There is no magic

A class is just a data structure

A method is just a data structure, too

It just happens to contain bytes that look like instructions for the interpreter

The call stack is another data structure

With libraries to give you access to it at runtime

Many programming tools make use of reflection

We'll see one in the next lecture


$Id: reflect.html,v 1.1.1.1 2004/01/04 05:02:31 reid Exp $