Methodenpointer...oder etwas Vergleichbares?

White_Fox

Top Contributor
Guten Abend allerseits

Folgendes Problem: Ich bräuchte etwas in der Art wie das hier:
Java:
private void theProblemDemo(Object anyObject){
    switch(anyObject instanceof){
        case ClassA:
            methodForClassA();
            break;
        case ClassB:
            methodForClassB();
            break;
        //Und so weiter...
    }
}

Ja, ich weiß daß das nicht geht...das ist das Problem. Ich hab auf StackOverflow eine interessante Lösung gefunden, nämlich eine Map zu erstellen mit einem Class-Objekt als Key und der Methode als Value. Die Frage ist: wie krieg ich eine private Methode als Value in die Map gepackt? Kann mir mal jemand den Code hier bitte erklären?
Java:
Map<Class,Runnable> doByClass = new HashMap<>();
doByClass.put(Foo.class, () -> doAClosure(this));
doByClass.put(Bar.class, this::doBMethod);
doByClass.put(Baz.class, new MyCRunnable());

In C würde man jetzt einfach einen Funktionspointer verwenden, aber in Java...?


Um mal das Problem etwas zu beschreiben: Ich habe ein (abstrakte) TreeItemklasse. Um einen Baum aufzubauen. Jetzt möchte ich Nachrichten durch den Baum schicken. Ich kann zwar über Address-Objekte mir jedes TreeItem geben lassen, aber manche Operationen werden mir damit zu mühselig. Z.B. möchte ich mir die Addressobjekte aller Kindobjekte eines bestimmten TreeItems geben lassen. Nachrichten können gebroadcastet oder an bestimmte TreeItems gesendet werden.
Ich hätte am liebsten eine abstrakte Enumklasse deklariert von der ich eine spezielisierte Enumklasse für mein Projekt abgeleitet hätte. Dummerweise ist Enum aber final, da kann ich nix ableiten. (Bisher hab ich die TreeItems sehr generisch gelassen, sodaß ich sie auch für etwas anderes verwenden kann. Und mit einer festgenagelten Enumeration versaue ich mir das.)

Also hab ich eine abstrakte Klasse TreeitemMessage gebaut. Diese Klasse kann jetzt beliebig abgeleitet und auch als Antwort verwendet werden. Je nachdem, was ich von den TreeItemobjekten will.

Ich will also z.B. ein sendAddressOfAllYourChildren()-Objekt an ein bestimmtes TreeItemobjekt senden. Zurück kommt ein HashSet<AddressrequestReply> (AddressrequestReply extends TreeitemMessage).

Das ganze ist so aufgebaut:


Java:
public abstract class TreeItem {

    private HashMap<UUID, TreeItem> children;
    private final UUID ME;
    private TreeItem parent;

    //...
    
    public HashSet<TreeitemMessage> brodcastMessageToTreeItems(TreeitemMessage msg){
        HashSet<TreeitemMessage> hashSet = new HashSet<>();
        
        hashSet.add(processMessage(msg));
        
        for (Map.Entry<UUID, TreeItem> entry : children.entrySet()) {
            hashSet.addAll(entry.getValue().brodcastMessageToTreeItems(msg));
        }
        
        return hashSet;
    }
    
    public HashSet<TreeitemMessage> sendMessageToTreeitems(TreeitemMessage msg, Address... addresses){
        HashSet<TreeitemMessage> hashSet = new HashSet<>();
        
        for(Address a : addresses){
            if(itMeansMe(a)){
                hashSet.add(processMessage(msg));
            }
            else{
                for (Map.Entry<UUID, TreeItem> entry : children.entrySet()) {
                    if(entry.getKey().equals(a.getNext(ME))){
                        hashSet.addAll(entry.getValue().sendMessageToTreeitems(msg, a));
                    }
                }
            }
        }
        return hashSet;
    }
    
    /**
     * Hier werden von der konkreten Klasse später Nachrichten verarbeitet.
     * Und hier will ich durch verschiedene TreeitemMessageobjekte andere
     * Aktionen auslösen.
     */
    protected abstract TreeitemMessage processMessage(TreeitemMessage msg);
}

Ich könnte das Problem notfals ja mit einem Riesenhaufen if( msg instanceof ...) erschlagen, aber ist doch Mist, das muß einfach besser gehen.
 

thecain

Top Contributor
In deinem StackOverflowBeispiel hast doch doch bei Bar.class eine Methodenereferenz. Das scheint ja das zu sein was du suchst
 

White_Fox

Top Contributor
Aha...der Doppeltedoppelpunktoperator holt die Methodenreferenz...ich hab mich schon gewundert was der macht. Mal sehen ob ich was dazu finde...
 
K

kneitzel

Gast
Kann mir mal jemand den Code hier bitte erklären?
Java:
Map<Class,Runnable> doByClass = new HashMap<>();
doByClass.put(Foo.class, () -> doAClosure(this));
doByClass.put(Bar.class, this::doBMethod);
doByClass.put(Baz.class, new MyCRunnable());
Also einfach erst einmal die Erklärung.
Du hast eine Map, welche von Klasse zu einer Runnable Instance "mapt".
Runnable ist ein Functional Interface, d.h. Du kannst da:
1. Einen Lambda Ausdruck verwenden. Die Methode in Runnable ist run(), also keine Parameter, daher ist der Lambda Ausdruck () -> ...
2. Eine Methodenreferenz kann verwendet werden. Die Methode doBMethod muss also auch keinen Parameter nehmen.
3. ist am einfachsten. MyCRunnable muss natürlich Runnable implementieren. Aber damit hast Du halt eine Instanz von Runnable.

Aber da hast Du einen Unterschied. instanceof gilt auch füe SuperKlassen. Aber da muss es jetzt die korrekte Klasse sein. Das kannst Du natürlich auch mit einem Switch darstellen:
switch(anyObject.getClass().getName())
wäre z.B. möglich um dann im case die Strings anzugeben.
Aber die map finde ich generell schöner, da es einfacher zu erweitern ist.

Aber generell sollte man sich immer überlegen, ob es nicht auch eine bessere Umsetzung gibt. Ich habe das mit der Anwendung nicht ganz verstanden, aber ist da evtl. nicht etwas wie das Strategy Pattern oder so interessant? Die Klassen, die Du überprüfst, haben ja so wie ich es verstanden habe eine gemeinsame Superklasse. Und dann können die ja ein Verhalten bekommen, dass dann in eigenen Klassen implementiert wird. Das wäre dann wie das Beispiel mit dem Runner, nur eben hast Du das halt mit eigenem Interface aufgebaut.

Um ein Beispiel aus einem Buch zu nehmen: Du hast alle möglichen Enten. Und Enten können quaken. Wie diese quaken ist von Ente zu Ente unterschiedlich. Die Holzente quakt anders (gar nicht) als das Enten-Tier und die quakt natürlich wieder anders als die Bade-Ente. Aber Ente hat dann natürlich ein QuakVerhalten. quaken() in der Ente ruft als quaken von dem gespeicherten Quakverhalten auf. Und das Interface QuakVerhalten hast Du dann halt in vielen Klassen implementier: KeinQuaken, QuietschendesQuaken, ...
Ist das etwas deutlich geworden? Vielleicht ist sowas in der Art sinnvoll. Dann brauchst Du auch keine Map, weil es in der Klasse selbst enthalten ist.
 

M.L.

Top Contributor
Ein weiteres Stichwort könnte aus der .NET-Welt kommen: Delegates (aber das lässt sich in Java noch nicht "auf Knopfdruck" realisieren)
 

temi

Top Contributor
Reflection wäre auch eine Möglichkeit, wenn vermutlich auch nicht die beste, aber das hängt von deiner genauen Anwendung ab.

Ich hab da mal vor längerer Zeit versucht eine Art EventBus zu programmieren. Es wird dabei in einer als Nachrichtenempfänger registrierten Klasse nach einer Methode mit einem bestimmten Namen gesucht. Also ein convention over configuration Ansatz. Hier ist der Code dafür. Kannst ja mal drüberschauen.
[CODE lang="java" highlight="76-98"]import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import java.util.Objects;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;


public final class EventManager implements IEventManager
{
private static String DEFAULT_METHOD = "handle";
private final String defaultMethod;

private List<Handler> handlers = new ArrayList<>();

public EventManager()
{
this(DEFAULT_METHOD);
}

public EventManager(final String defaultMethod)
{
if (defaultMethod == null || defaultMethod.trim().length() == 0)
{
throw new IllegalArgumentException("Default method must not be null or empty");
}

String removedSpaces = defaultMethod.replaceAll("\\s+","");

this.defaultMethod = removedSpaces;
}

@Override
public synchronized void subscribe(final Object subscriber)
{
Objects.requireNonNull(subscriber, "Subscriber");

for (Handler handler : handlers)
{
if (handler.isSubscriberFor(subscriber))
{
throw new IllegalArgumentException("Subscriber is already registered: '" + subscriber.getClass().getName() + "'");
}
}

handlers.add(new Handler(subscriber));
}

@Override
public synchronized void remove(final Object subscriber)
{
Objects.requireNonNull(subscriber, "Subscriber");

handlers.removeIf(h -> h.isSubscriberFor(subscriber));
}

@Override
public void publish(final Object event)
{
Objects.requireNonNull(event, "Event");

ArrayList<Handler> toNotify;

synchronized (this)
{
handlers.removeIf(h -> !h.isAlive());

toNotify = new ArrayList<>(handlers);
}

toNotify.forEach(h -> h.handle(event));
}

private class Handler
{
final WeakReference<Object> weakReference;
final Map<Class, Method> supportedEvents = new HashMap<>();

Handler(final Object handler)
{
weakReference = new WeakReference<>(handler);

Method[] declaredMethods = handler.getClass().getDeclaredMethods();

for (Method method : declaredMethods)
{
if (method.getName().equals(defaultMethod))
{
Class[] parameterTypes = method.getParameterTypes();
supportedEvents.put(parameterTypes[0], method);
}
}

if (supportedEvents.isEmpty())
throw new IllegalArgumentException("Method '" + defaultMethod + "' is not implemented: '" + handler.getClass().getName() + "'");
}

void handle(final Object event)
{
Object target = weakReference.get();

if (target == null) return;

Method handler = supportedEvents.get(event.getClass());

if (handler != null)
{
try
{
handler.invoke(target, event);
}

catch (IllegalAccessException e)
{
// todo: find a better way
System.err.println(e);
}

catch (InvocationTargetException e)
{
// todo: find a better way
System.err.println(e);
}
}
}

boolean isSubscriberFor(final Object subscriber)
{
Object target = weakReference.get();

return target != null && target.equals(subscriber);
}

boolean isAlive()
{
return weakReference.get() != null;
}
}
}[/CODE]
 
Zuletzt bearbeitet:

temi

Top Contributor
In einer neueren Version verwende ich auch Methodenreferenzen (FxEvent ist nur ein Markerinterface):
Java:
import java.util.*;
import java.util.function.Consumer;

public final class FxEvents implements FxEventBus {
    private final static Object LOCK = new Object();
    private final static FxEventBus instance = new FxEvents();
    private final Map<Class<? extends FxEvent>, List<Consumer<FxEvent>>> listeners = new HashMap<>();

    private FxEvents() {}

    public static FxEventBus getInstance() {
        return instance;
    }

    @Override
    public <E extends FxEvent> void publish(final E event) {
        Objects.requireNonNull(event);

        List<Consumer<FxEvent>> listeners;
        synchronized (LOCK) {
            listeners = this.listeners.get(event.getClass());
        }
    
        listeners.forEach(listener -> listener.accept(event));
    }

    @Override
    @SuppressWarnings("unchecked")
    public <E extends FxEvent> void register(final Class<E> eventClass, final Consumer<E> consumer) {
        Objects.requireNonNull(eventClass);
        Objects.requireNonNull(consumer);

        synchronized (LOCK) {
            if (!listeners.containsKey(eventClass)) {
                listeners.put(eventClass, new LinkedList<>());
            }

            listeners.get(eventClass).add((Consumer<FxEvent>) consumer);
        }
    }
}

Damit muss man beim Registrieren explizit eine Methode angeben. Hier ein Beispiel, wo eine Klasse selbst zwei ihrer Methoden als Empfänger registriert:
Java:
events.register(TextChangeEvent.class, this::onTextChangeEvent);
events.register(PositionChangeEvent.class, this::onPositionChangeEvent);

// die beiden Methoden dazu
private void onTextChangeEvent(final TextChangeEvent event) {
    label1.setText(event.getSender().toString());
}

private void onPositionChangeEvent(final PositionChangeEvent event) {
    label2.setLayoutX(event.newX);
}
 
Zuletzt bearbeitet:

White_Fox

Top Contributor
Hm...das Strategy Pattern kenne ich, ich sehe aber nicht wie ich es hier sinnvoll anwenden kann. Das "Antwortverhalten" in eine Klasse auszulagern funktioniert hier nicht, da das Objekt Informationen über sich zurückliefern soll. Aber das Beispiel mit den Enten kommt mir sehr bekannt vor...der Entensimulator, sehr schön. :)

Reflection...dann kann ich auch einfach über die Klassennamen gehen. Aber ok, wäre auch eine Möglichkeit.

Mir gefällt das mit der Map eigentlich ganz gut. Dann komme ich endlich mal dazu, mich mit Lambdaausdrücken zu befassen. Auch nicht schlecht. Da hätte ich nur noch eine Frage: Wenn die Methode etwas zurückgibt - wie komme ich dann da heran?
 
K

kneitzel

Gast
Wenn du ein Interface nimmst, das einen Wert zurück gibt, dann müssen die Methoden und Co auch etwas zurück geben...

Der Namespace java.util.function wäre bestimmt interessant für Dich. Supplier wäre ein Interface mit einer Methode, die keine Parameter hat und etwas zurück gibt ...
 

White_Fox

Top Contributor
Hm...ich hab gerade mal in das Paket reingeschaut und stelle fest, daß ich mich da so rein gar nicht zurechtfinde. Die Aussage, daß ein Interface einen Wert zurückliefert - hä? Edit: Ok - FunctionalInterface scheint etwas anders zu sein, ich habe mal gerade über ein anderes Tutorial quergelesen. Ich denke, das ist doch nicht genau das, was ich suche.

Ich schreibe mal auf, was ich will und wie ich das implementieren könnte (auch wenn es häßlich wird). Vielleicht gibt das einen besseren Einblick in mein Vorhaben.

Java:
public class AnyTreeitem extends TreeItem{        //TreeItem: Siehe Post oben
    //...jede Menge Zeugs, hier aber unwichtig
   
    @Override
    protected TreeitemMessage processMessage(TreeitemMessage msg){
        if(msg.getClass.equals(AddressRequest.class)){
            Address a = super.getAddress();
            return new AddressRequestAnswer(a);
        }

        if(msg.getClass.equals(AllSubchildAddressRequest.class)){
            return super.broadcastMessage( new AddressRequest());    //Für den Fall, daß von einem ganz bestimmten Element die Kinderaddressen
                                //geliefert werden sollen, schickt man ein AllSubchildAddressRequest-Objekt
                               //an ein bestimmtes Element.
        }
    return null;
}

public class AddressRequest{}

public class AddressRequestAnswer{
    public Address a;    //Auf eine Gettermethode verzichte ich hier der Übersicht halber,
                        //die Klasse ist ein simpler Datencontainer ohne weitere Intelligenz oder Funktion.
}

public class AllSubchildAddressRequest{}

Würde ich jetzt an der Baumwurzel die broadcastMessage()-Methode mit einem AddressRequest-Objekt aufrufen, würde ich ein HashSet<AddressRequestAnswer> zurückbekommen, das die Addressobjekte aller Kinderelemente enthält.

Und so etwas könnte ich noch für ganz andere Sachen wollen...das weiß ich jetzt aber noch nicht.
 

temi

Top Contributor
Eigentlich könntest du meine zweite Lösung unmittelbar verwenden. Du übergibst den EventBus an die Klasse, damit sie auch selbst Nachrichten verschicken kann und reagierst auf Anforderungen.

Java:
class TreeItem {
    
    private final FxEventBus events;
    
    public TreeItem(final FxEventBus events) {
        this.events = events;
        events.register(TreeDataRequest.class, this:onTreeDataRequest)
    }
    
    private void onTreeDataRequest(final TreeDataRequest event) {
        // hier entsprechend der Daten im Event reagieren    
    }
    
    private void sendAnswer(final TreeData data) {
        events.publish(data);
    }
}

Java:
public class TreeDataRequst implements FxEvent {
    
    // hier entsprechend Daten einfügen, die ein TreeItem braucht, um richtig zu reagieren.
    
}
 
B

BestGoalkeeper

Gast
Also Funktionspointer wie in C gibt es in Java nicht (abgesehen von Reflection)...

Aber es gibt ein Idiom, um an Methodenreferenzen zu gelangen: funktionale Interfaces, Lambdas oder der :: Operator...

Alle 3 haben natürlich Nachteile.

Hier mal ein Beispiel:
Java:
@FunctionalInterface
public interface MyInterface {
    int temp();

    default int doSomethingWithVoid() {
        System.out.println("void");
        return -1;
    }

    default int doSomethingWithInt(int i) {
        System.out.println(i);
        return i;
    }

    default int doSomethingWithString(String s) {
        System.out.println(s);
        return s.hashCode();
    }
}


public class Test {
    public static void theSolve(Object anyObject) {
        MyInterface i = () -> 0;
        if (anyObject == null) {
            i.doSomethingWithVoid();
        } else if (anyObject instanceof Integer) {
            i.doSomethingWithInt((int) anyObject);
        } else if (anyObject instanceof String) {
            i.doSomethingWithString((String) anyObject);
        }
    }

    public static void main(String[] args) {
        theSolve(null);
        theSolve(123);
        theSolve("Muh");
    }
}
 
K

kneitzel

Gast
Also ein Interface gibt nichts zurück. Da habe ich auf die schnelle etwas Unsinn geschrieben. Gemeint war natürlich: Ein (functional) Interface mit einer Methode, die etwas zurück gibt. Beispiel ist z.B. Supplier<T>, welches eine Methode hat, die ein T zurück gibt. Da sind halt universell nutzbare Interfaces drin, so dass man ggf. keine eigenen Interfaces schreiben muss.

Je nach Nutzung kann das aber durchaus lesbarer sein, weil man dann am Namen etwas den Zweck erkennen kann und auch die Methode sagt dann mehr über das aus, was man macht. Das Quakverhalten könnte auch einfach Runnable sein. Aber dann wäre das quaken() auch ein run(). Und ein QuietschendesQuacken implements Runnable und dann ein run() macht nicht wirklich Sinn ... Oder Parameter / Variablen sind dann nur Runnable, d.h. die Typkontrolle geht verloren. Neben dem Quakverhalten hat die Ente auch ein Flugverhalten mit fliegen(). => wenn man es nur über Runnable abbildet kann man das nicht unterscheiden... Das ist etwas, das aus meiner Sicht gegen java.util.function.* spricht.
 
Ähnliche Java Themen
  Titel Forum Antworten Datum
berserkerdq2 run-methode eines Threads so programmieren, dass 30x die Sekunde etwas ausgeführt wird. Allgemeine Java-Themen 44
W Collections Suche etwas Sorted-List-Artiges...hat jemand eine Idee? Allgemeine Java-Themen 13
N Gibt es etwas allgemeineres as Object? Allgemeine Java-Themen 16
M Problem mit (etwas komplizierterem) Java Programm Allgemeine Java-Themen 14
T "Java lernen" in etwas mehr als 8 Tagen Allgemeine Java-Themen 13
J Zweiter Prozess der alle x Sekunden etwas abfragen soll Allgemeine Java-Themen 2
G Bringt es etwas System.gc() nach großen Aufgaben aufzurufen? Allgemeine Java-Themen 2
E Gibt es so etwas wie einen Windows Listener? Allgemeine Java-Themen 6
F Generics: spricht etwas dagegen raw types zu verwenden? Allgemeine Java-Themen 31
U if Abfrage macht etwas falsch Allgemeine Java-Themen 2
E in einem Untermenü (Baltt) etwas speichern? Allgemeine Java-Themen 3
A Thema JAR-Erstellung (mal wieder) => etwas komplizierter Allgemeine Java-Themen 8
G Gibt es etwas ähnliches wie den ReadKey bei Pascal? Allgemeine Java-Themen 3
S Etwas Pipe-Ähnliches zur Prozesskommunikation Allgemeine Java-Themen 14
H [Array als Text?] bräuchte etwas hilfe bzw erklärung Allgemeine Java-Themen 5
M Etwas in der Art von JRun? Allgemeine Java-Themen 4
S ein taschenrechner, aber etwas anders. Allgemeine Java-Themen 2
H Wie stellt ein JTree fest, wo etwas eingehängt werden soll? Allgemeine Java-Themen 2
L Plugins in Java realisieren: Wie könnte man so etwas machen? Allgemeine Java-Themen 7

Ähnliche Java Themen

Neue Themen


Oben