Refleksja

Struktura pliku .class, dostęp do informacji o typie, polach i metodach

Dynamiczne ładowanie klas


dr inż. Aleksander Smywiński-Pohl

apohllo@o2.pl

http://apohllo.pl/dydaktyka/programowanie-obiektowe

class DummyIterator implements Iterator {
    private int index = 0;

    @Override
    public boolean hasNext(){
        return false;
    }

    @Override
    public Object next(){
        return null;
    }
}
javac DummyIterator.java
cat DummyIterator.class
����4
    indexI<init>()VCodeLineNumberTablehasNext()Znext()Ljava/lang/Object;
SourceFileDummyIterator.java

DummyIteratorjava/lang/Objectjava/util/Iterator      
&
*�*��



�
       
�
%
javap DummyIterator
Compiled from "DummyIterator.java"
class DummyIterator implements java.util.Iterator {
  DummyIterator();
  public boolean hasNext();
  public java.lang.Object next();
}

Informacje o typie

Class<?> klass = null;
klass = int.class;
klass = Integer.class;
klass = "Ala ma kota".getClass();
klass = new String[0].getClass();
klass = new DummyIterator().getClass();

Klasa nadrzędna

Class<?> klass = null;
klass = new DummyIterator().getClass();
klass = klass.getSuperclass();

Wspólna klasa nadrzędna

class A {}
class B1 extends A {}
class C1 extends B1 {}
class B2 extends A {}
class C2 extends B2 {}
List<Class<?>> ancestors(Class<?> klass){
    List<Class<?>> result = new LinkedList<>();
    while(klass != Object.class){
        result.add(klass);
        klass = klass.getSuperclass();
    }
    result.add(Object.class);
    Collections.reverse(result);
    return result;
}
ancestors(C2.class);
Class<?> commonAncestor(Class<?> aClass, Class<?> bClass){
    List<Class<?>> aAncestors = ancestors(aClass);
    List<Class<?>> bAncestors = ancestors(bClass);
    for(int i = 0; i < aAncestors.size() && i < bAncestors.size(); i++){
        if(aAncestors.get(i) != bAncestors.get(i)){
            return aAncestors.get(i-1);
        }
    }
    return aAncestors.get(aAncestors.size()-1);
}
commonAncestor(C1.class, C2.class);

Dynamiczne ładowanie klas

interface IVehicle {
    void go();
}
class Car implements IVehicle {
    public void go(){
        System.out.println("Brum, brum");
    }
}
class Airplane implements IVehicle {
    public void go(){
        System.out.println("Bzzzzzz");
    }
}
class Rocket implements IVehicle {
    public void go(){
        System.out.println("BUUUUUUUUUUUM");
    }
}
class VehicleSystem {
    public static void main(String[] args){
        List<IVehicle> vehicles = new LinkedList<>();
        for(String vehicleName : args){
            try {
                Class<IVehicle> klass = (Class<IVehicle>) 
                    Class.forName(vehicleName);
                vehicles.add(klass.newInstance());
            } catch(ClassNotFoundException ex) {
                System.out.println("The class " + vehicleName + 
                    " has not been found. ");
                return;
            } catch(InstantiationException | IllegalAccessException ex) {
                System.out.println("The class " + vehicleName + 
                    " cannot be instantiated. ");
                return;
            }

        }
        for(IVehicle vehicle : vehicles) {
            vehicle.go();
        }
    }
}
java VehicleSystem Car Rocket Airplane Car

Dostęp do składowych

  • newInstance()
  • getMethod(...)
  • getMethods()
  • getConstructor(...)
  • getConstructors()
  • getField(...)
  • getFields()
void prettyPrint(Object[] elements){
    for(Object element : elements)
        System.out.println(element);
}
Class<?> klass = DummyIterator.class;

prettyPrint(klass.getMethods());
prettyPrint(klass.getConstructors());
prettyPrint(klass.getFields());

Dynamiczna instancjacja

import java.lang.reflect.*;

Constructor constructor = klass.getConstructor(new Class<?>[0]);
Iterator dummyIterator = (Iterator) constructor.newInstance();
class DummyIterator implements Iterator {
    private int index = 0;

    public DummyIterator(){
    }

    public DummyIterator(int index){
        this.index = index;
    }

    @Override
    public boolean hasNext(){
        return false;
    }

    @Override
    public Object next(){
        return null;
    }
}
Class<?> klass = DummyIterator.class;

Constructor constructor = klass.getConstructor(new Class<?>[]{int.class});
Iterator dummyIterator = (Iterator) constructor.newInstance(1);

Dynamiczne wywołanie metody

Method hasNextMethod = klass.getMethod("hasNext", new Class<?>[0]);
hasNextMethod.getName();
hasNextMethod.getParameterTypes();
Constructor constructor = klass.getConstructor(new Class<?>[]{int.class});
Iterator dummyIterator = (Iterator) constructor.newInstance(1);

Method hasNextMethod = klass.getMethod("hasNext", new Class<?>[0]);
hasNextMethod.invoke(dummyIterator);

Dynamiczny dostęp do pól

Field indexField = klass.getField("index");
indexField.getName();
indexField.getModifiers();
Field indexField = klass.getField("index");
|  java.lang.NoSuchFieldException thrown: index
|        at Class.getField (Class.java:1891)
|        at (#300:1)
Field indexField = klass.getDeclaredField("index");
indexField.getName();
indexField.getModifiers();
Constructor constructor = klass.getConstructor(new Class<?>[]{int.class});
Iterator dummyIterator = (Iterator) constructor.newInstance(1);

Field indexField = klass.getDeclaredField("index");
indexField.get(dummyIterator);
indexField.get(dummyIterator);
|  java.lang.IllegalAccessException thrown: class REPL.$JShell$151 cannot access a member of class REPL.$JShell$19D$DummyIterator with modifiers "private"
|        at Reflection.throwIllegalAccessException (Reflection.java:405)
|        at Reflection.throwIllegalAccessException (Reflection.java:396)
|        at Reflection.ensureMemberAccess (Reflection.java:98)
|        at AccessibleObject.slowCheckMemberAccess (AccessibleObject.java:355)
|        at AccessibleObject.checkAccess (AccessibleObject.java:347)
|        at Field.get (Field.java:406)
|        at (#313:1)
indexField.setAccessible(true);
indexField.get(dummyIterator);
indexField.set(dummyIterator,2);

Dostęp do wszystkich składowych prywatnych

  • getDeclaredMethod(...)
  • getDeclaredMethods()
  • getDeclaredConstructor(...)
  • getDeclaredConstructors()
  • getDeclaredField(...)
  • getDeclaredFields()

Uwaga: składowe dziedziczone nie są zwracane

Dynamiczne proxy

import java.lang.reflect.*;

class MethodReplayer implements InvocationHandler {
    private List<String> list = new LinkedList<>();
    private List<Method> invokedMethods = new LinkedList<>();
    private List<Object[]> invokedArguments = new LinkedList<>();

    public Object invoke(Object proxy, Method method, Object[] args) 
            throws Throwable{
        if(!method.getName().equals("toString")){
            invokedMethods.add(method);
            invokedArguments.add(args);
        }
        return method.invoke(list, args);
    }

    public void replay() throws Throwable {
        for(int i = 0; i < invokedMethods.size(); i++){
            invokedMethods.get(i).invoke(list, invokedArguments.get(i));
        }
    }
}
import static java.lang.reflect.Proxy.newProxyInstance;

MethodReplayer replayer = new MethodReplayer();
List<String> list = (List<String>) newProxyInstance(List.class.getClassLoader(),
        new Class[] { List.class },
        replayer);

list.add("dog");
list.add("cat");
System.out.println(list);

replayer.replay();
System.out.println(list);

Uchwyty do metod

  • w API refleksji sygnatura argumentów metody jest wskazywana przez Class[]
  • w nowym API mamy nowy typ MethodType:
    • brak nazwy metody
    • brak typu adresata
    • tylko typy argumentów oraz typ zwracanej wartości
import java.lang.invoke.*;
import java.lang.invoke.MethodHandles.Lookup;

// np. String toString()
MethodType toStringType = MethodType.methodType(String.class);

// np. Integer parseInt(String str)
MethodType fromStringToInt = MethodType.methodType(Integer.class, String.class);
class CallMeMaybe {
    public String toString(){
        return "call me maybe";
    }

    public Lookup getLookup(){
        return MethodHandles.lookup();
    }
}
CallMeMaybe maybe = new CallMeMaybe();
Lookup maybeLookup = maybe.getLookup();

MethodHandle maybeHandle = maybeLookup.findVirtual(CallMeMaybe.class, 
    "toString", toStringType);
maybeHandle.invoke(maybe);

Pytania?