2008-12-28 18 views
10

Dla mojego serwera gier Java wysyłam identyfikator akcji pakietu, który zasadniczo informuje serwer, do czego służy pakiet. Chcę zmapować każdy identyfikator akcji (liczbę całkowitą) do funkcji. Czy jest sposób na to bez użycia przełącznika?Wskaźniki funkcji/delegatów w Javie?

+0

Ogólnie rzecz biorąc, zmiana będzie lepsza. – TheSoftwareJedi

+0

Tak, przełącznik prawie na pewno będzie dużo szybszy, mniejszy, czyściejszy. –

+1

możliwy duplikat [Jaki jest najbliższy substytut wskaźnika funkcji w Javie?] (Http://stackoverflow.com/questions/122407/whats-the-nearest-substitute-for-a-function-pointer-in-java) – Raedwald

Odpowiedz

21

Co z tym jednym?

HashMap<Integer, Runnable> map = new HashMap<Integer, Runnable>(); 
map.put(Register.ID, new Runnable() { 
    public void run() { functionA(); } 
}); 
map.put(NotifyMessage.ID, new Runnable() { 
    public void run() { functionB(); } 
}); 
// ... 
map.get(id).run(); 

(Jeśli trzeba przejść jakieś argumenty, określić swój własny interfejs z funkcją posiadające odpowiedni parametr, a użycie że zamiast Runnable).

+3

Myślę, że przełącznik, w którym funkcja A i functonB zostaną wywołane, będzie znacznie jaśniejszy kod. Spowoduje to przeniesienie mapowania identyfikatorów na funkcje do sekcji kodu, w której ładujesz mapę, zamiast miejsca, w którym wywołujesz funkcję. –

+0

tak, zgadzam się, że zmiana byłaby czystsza. również użyłbym przełącznika. ale myślę, że powinieneś powiedzieć to oryginalnemu pytającemu (w sekcji komentarzy OP) :) –

+0

Zamiast tego umieściłem to w mojej odpowiedzi. –

1

Java nie ma wskaźników pierwszej klasy. Aby osiągnąć podobną funkcjonalność, musisz zdefiniować i zaimplementować interfejs. Możesz to ułatwić dzięki anonimowym klasom wewnętrznym, ale nadal nie jest to piękne. Oto przykład:

public interface PacketProcessor 
{ 
    public void processPacket(Packet packet); 
} 

... 

PacketProcessor doThing1 = new PacketProcessor() 
{ 
    public void processPacket(Packet packet) 
    { 
     // do thing 1 
    } 
}; 
// etc. 

// Now doThing1, doThing2 can be used like function pointers for a function taking a 
// Packet and returning void 
+0

Aby uzyskać więcej informacji na temat "wskaźników funkcyjnych" w Javie, zobacz: http://stackoverflow.com/q/122407/545127 – Raedwald

1

Czy kiedykolwiek korzystałeś z Swing/AWT? Ich hierarchia zdarzeń rozwiązuje podobny problem. Droga przechodzi funkcje Java wokół jest z interfejsem, na przykład

public interface ActionHandler { 
    public void actionPerformed(ActionArgs e); 
} 

Następnie, jeśli chcesz mapować liczb całkowitych na tych obiektów, można użyć coś jak java.util.HashMap<Integer,ActionHandler> zarządzać tym. Rzeczywiste implementacje mogą iść w anonimowych klasach (najlepsze przybliżenie Javy "lambda") lub gdzieś w odpowiednich klasach. Oto anonimowy klasa sposób:

HashMap<Integer,ActionHandler> handlers; 
handlers.put(ACTION_FROB, new ActionHandler() { 
    public void actionPerformed(ActionArgs e) { 
     // Do stuff 
     // Note that any outer variables you intend to close over must be final. 
    } 
}); 
handlers.get(ACTION_FROB).actionPerformed(foo); 

(edit) Jeśli chcesz być jeszcze bardziej obraźliwych, można zainicjować HashMap tak:

HashMap<Integer,String> m = new HashMap<Integer,String>() {{ 
    put(0,"hello"); 
    put(1,"world"); 
}}; 
2

Java tak naprawdę nie ma wskaźników funkcji (my zamiast tego otrzymałem anonimowe klasy wewnętrzne). Nie ma jednak nic złego w korzystaniu z przełącznika, o ile włączasz wartość, a nie typ. Czy jest jakiś powód, dla którego nie chcesz używać przełącznika? Wygląda na to, że będziesz musiał wykonać mapowanie między identyfikatorami akcji i działaniami w swoim kodzie, więc dlaczego nie zachować prostoty?

+0

Tak, moja myśl była taka sama. Ja sam użyłbym przełącznika. Wierzę, że za kulisami przełączniki są obsługiwane w sposób losowy, a nie sekwencyjnie sprawdzają przypadki, jak gdybyś musiał/musiała zrobić/if. Tyle tylko, że wskaźniki przypadku powinny być kolejne, myślę ...? –

+0

Nie muszą być kolejne. Najpierw możesz umieścić najbardziej prawdopodobne przypadki, jeśli chcesz zoptymalizować. (Zawsze mierz przed optymalizacją.) –

+0

Instrukcja switch nie pozwala, aby klasy w różnych częściach swojego kodu dynamicznie ładowały wywołania zwrotne. Zapobiega to zaawansowanym funkcjom, takim jak ładowanie wywołań zwrotnych z plików konfiguracyjnych w środowisku wykonawczym i innych.Polecam przeciwko oświadczeniom przełącznika na rzecz bardziej wydajnego rozwiązania z tego powodu. –

0

Można to zrobić za pomocą wzoru łańcucha odpowiedzialności.

Jest to wzorzec, który łączy różne obiekty w celu utworzenia podobnej listy. Oznacza to, że każdy obiekt ma odniesienie do następnego w łańcuchu. Obiekty w łańcuchu zwykle obsługują jedno określone zachowanie. Przepływ między obiektami jest bardzo podobny do instrukcji switch-case.

Istnieje kilka błędów, takich jak: rozdziela logikę, nadmiernie długi łańcuch może powodować problemy z wydajnością. Ale wraz z tymi gargami zyskujesz na zwiększonej zdolności do testowania i większej spójności. Również nie jesteś ograniczony do używania wyrażeń enum, bajtów, int short i char jako wyzwalaczy dla rozgałęzień.

+0

Słodki. Zostałem przegłosowany bez żadnego wyjaśnienia. Sądzę, że ta osoba nie spodobała się temu, w jaki sposób wpisałem swoją odpowiedź. Może to, co powiedziałem, było złe. Zgaduję, że osoba ma problem z KR-em. Ale zgaduję, ponieważ nie było komentarza, dlaczego. –

+0

Co to za wzór? W jaki sposób rozwiązuje/omija problem używania kodu do przełączania wielu funkcji? Przykład lub przynajmniej link, pozwoliłby na zapisanie tej odpowiedzi. Ale tak, nie podobało mi się, w jaki sposób wpisałeś swoją odpowiedź. –

+0

Dziękuję. To wszystko, o co proszę. Jeśli masz odwagę, by zająć miejsce, ktoś ma odwagę podać powód. –

1

Tak, ale przy użyciu interfejsu znaczy, trzeba utworzyć interfejs dla każdego wywołania zwrotnego, co oznacza każdą funkcję, którą chcesz przekazać go ustawić. Utworzenie klasy delegatów do obsłużenia tego daje (nie prawdziwy wskaźnik funkcji), ale funkcję, która ma zostać przekazana, a jeśli nadużywasz generycznego, aby być typem zwrotnym, nie musisz rzucać, co zadaje wąskie gardło prawie do zera.

Delegat C# (MultiCastDelegate za poprawny) pobiera informacje z metody MethodInfo, która jest tym samym, co należy zrobić dla klasy Delegat przy użyciu metody java.lang.reflect.method. Wysłałem swój kod do klasy Delegata (T) na innej formie tej strony, zajmującej się tym problemem. Robię to, ponieważ (tak) będąc z C++, potrzebuję lepszego sposobu przekazywania funkcji (zwłaszcza Void), a następnie tworzenia interfejsu dla funkcji lub więcej. Teraz mogę wybrać funkcję wypełniającą informacje o parametrze. Voila`! Ładne i użyteczne bez zauważalnego spadku prędkości od JIT lub JVM. A jeśli miałem tylko programowanie java na tylko tydzień, każdy programista java może to zrobić.

Służy również bardzo dobrze podczas tworzenia podstawowego obiektu nasłuchującego i interfejsu podstawowego do przekazania w odbiorniku. Koniec z koniecznością napisania innego słuchacza, ponieważ zmieniła się nazwa funkcji. Tworzenie klasy delegatów ma ogromne zalety, ponieważ jest bardzo przydatne i łatwe.

0

Można połączyć metody statyczne. Ta metoda pozwala również określić parametry. Zadeklarować swój interfejs ...

public interface RouteHandler { 
    void handleRequest(HttpExchange t) throws IOException; 
} 

i Twojego mapy ...

private Map<String, RouteHandler> routes = new HashMap<>(); 

następnie wdrożyć metody statyczne, które pasują do interfejsu/params ...

public static void notFound(HttpExchange t) throws IOException { 
    String response = "Not Found"; 

    t.sendResponseHeaders(404, response.length()); 
    OutputStream os = t.getResponseBody(); 
    os.write(response.getBytes()); 
    os.close(); 
} 

Można następnie dodać te metody na mapie ...

routes.put("/foo", CoreRoutes::notFound); 

i zadzwoń do nich w następujący sposób ...

RouteHandler handler = routes.get("/foo"); 
handler.handleRequest(exchange);