2010-04-09 1 views
5

Sto sviluppando una classe di utilità per gestire azioni da componenti Java Swing; Vorrei sapere se esiste un modo per verificare se esiste un determinato nome di metodo (a cui si accederà con i riflessi) in fase di compilazione e mostrare un errore del compilatore in caso contrario?Java: verifica se esiste un determinato nome di metodo in fase di compilazione

--update

Ok, sembra che io non sono stato chiaro, consente di parlare di dettagli:

ho una classe denominata TomAction che uso per semplificare le dichiarazioni azioni semplici nel mio progetto. Invece di scrivere qualcosa di simile:

class MyClass { 

    public MyClass() { 
    Icon icon = null; // putting null to simplify the example 
    JButton btn = new JButton(new AbstractAction("Click to go!", icon) { 
     public void actionPerformed(ActionEvent ev) { 
     try { 
      go(); 
     } catch (Exception e) { 
      e.printStackTrace(); 
      String msg = "Fail to execute 'go' task."; 
      JOptionPane.showMessageDialog(null, msg, "Fail", JOptionPane.ERROR_MESSAGE); 
     } 
     } 
    }); 
    } 

    private void go() { 
    // do task 
    } 

} 

..Io basta scrivere:

class MyClass { 

    public MyClass() { 
    String msg = "Fail to execute 'go' task."; 
    Icon icon = null; // putting null to simplify the example 
    TomAction act = new TomAction("go", this, "Click to go!", msg, icon); 
    JButton btn = new JButton(act); 
    } 

    private void go() { 
    // do task 
    } 

} 

..e il programma hanno lo stesso comportamento.

Il problema è che se scrivo un nome di metodo errato come argomento per TomAction, lo vedrò solo in fase di esecuzione. Mi piacerebbe vederlo in fase di compilazione. Fatto? :)

A proposito, questa classe sta funzionando molto bene, voglio solo fare questo miglioramento.

--update

studiando le annotazioni avvicinarsi

+0

Quindi questo nome non è qualcosa che si conosce solo in fase di esecuzione? Cioè, non è quello che l'utente digita, o quello che ti dice una bacchetta magica? Conosci _ il nome in fase di compilazione e vuoi che il compilatore (o qualcosa) ti controlli se il metodo (il cui nome è noto al momento della compilazione) esiste al momento della compilazione? Perché hai bisogno di riflessione, quindi ??? – polygenelubricants

+0

@polygenelubrificanti si prega di consultare il mio aggiornamento –

+0

@ Tom: la risposta di Joachim si applica ancora. Utilizza le interfacce. – BalusC

risposta

2

È possibile utilizzare l'annotazione anziché il valore letterale stringa per indicare l'azione richiamata e quindi utilizzare APT per verificare che il metodo esista. APT viene invocato automaticamente da javac.

http://java.sun.com/javase/6/docs/technotes/tools/windows/javac.html#processing http://java.sun.com/j2se/1.5.0/docs/guide/apt/GettingStarted.html http://java.sun.com/j2se/1.5.0/docs/guide/apt/mirror/overview-summary.html

Prendendo il tuo esempio, vorrei suggerire di progettazione come segue - si inserisce un'annotazione obbligatorio in codice e aggiungere il codice di azione di istanza nel processore annotazione. Le azioni in tal caso devono essere membri della classe, che è comunque una buona pratica. Assicurarsi di utilizzare pacchetti di risorse per le icone e il testo e si risparmia un sacco di dolore nel lungo periodo:

class MyClass { 
    // the keys should be looked up from resource bundle 
    @TomActionBinding("go", "text-key", "msg-key", "icon-key"); 
    Action act; 

    public MyClass() { 
     JButton btn = new JButton(act); 
    } 

    private void go() { 
     // do task 
    } 

} 
+0

Interessante, leggerò e tornerò qui per commentare .. –

+1

Controlla anche il framework di applicazione Better Swing.Sebbene non compili il tempo, genera un'eccezione nel tempo di costruzione dell'interfaccia utente, che è abbastanza buono per la maggior parte dei casi: http://kenai.com/projects/bsaf/pages/Action_Annotation_HowTo – ddimitrov

3

No. Non v'è alcun modo per farlo.

+0

Le annotazioni consentono di eseguire il controllo del tempo di compilazione. –

+2

+1. Semplice come quella. Le riflessioni non sono tempo di compilazione per quel tipo di controllo. Usa l'interfaccia. –

+1

Non sta chiedendo di usare reflection in fase di compilazione, vuole verificare che esista un nome di metodo associato ad alcune classi. –

14

Sembra che si desideri impostare un contratto per implementare un metodo specifico.

In Java si fa di solito questo attraverso un'interfaccia:

public interface Frobnicator { 
    public void frobnicate(); 
} 

Poi, nel tuo altro codice, è sufficiente si aspettano di ottenere un oggetto di quel tipo, in questo modo il compilatore verificherà che il metodo esiste:

public void frobnicateorize(Frobnicator forb) { 
    frob.frobnicate(); 
} 

In questo modo è anche possibile evitare l'uso del riflesso per chiamare il metodo.

Modifica in merito all'aggiornamento: No, non è possibile controllare il tipo di codice da parte del compilatore Java. Potrebbe essere necessario scrivere il proprio strumento per fare questo controllo per te.

+0

per favore, vedi il mio aggiornamento –

+0

forse qualcosa con annotazioni, come proposto –

2

Non conosco un modo per farlo con qualsiasi software esistente.

Tuttavia, presumendo che il nome del metodo possa essere risolto in fase di compilazione (nel qual caso sembra improbabile che si utilizzi la riflessione) si potrebbe creare un plug-in IDE per farlo.

0

E 'possibile con la riflessione:

 Object o = object; 
     try { 
      Method m = o.getClass().getMethod("methodName"); 
      m.invoke(o); 
     } catch (NoSuchMethodException e) { 
      //Method is not available 
      e.printStackTrace(); 
     } catch (InvocationTargetException e) { 
      e.printStackTrace(); 
     } catch (IllegalAccessException e) { 
      e.printStackTrace(); 
     } 
+3

Che non funzionerà in fase di runtime, non in fase di compilazione (se il il metodo non è presente) – pkaeding

+0

Se il tuo metodo ha bisogno di argomenti, devi specificare gli argomenti-tipi nella chiamata 'getMethod' (egogetClass(). getMethod ("methodName", Integer.class, String.class)) e nel invoke-method (egminvoke (o, 5, "Second param")) –

0

Usa "costanti"

Il compilatore non si lamenterà quando passa una stringa letterale fintanto che è il tipo corretto.

Tuttavia, quando si passa una variabile come parametro, la variabile deve essere definita almeno.

È possibile definire alcune "costanti" di variabile di classe per ogni azione e suggerire che il chiamante usi quelle. Il compilatore ti darà un errore se il parametro variabile non è definito.

public class TomAction { 
    public static final String GO_ACTION = "go"; 

    ... 
} 

chiamarlo, invece di fare questo:

TomAction act = new TomAction("go", ...); 

Fate questo:

TomAction act = new TomAction(TomAction.GO_ACTION, ...); 

Se si tenta di utilizzare una variabile non definita:

TomAction act = new TomAction(TomAction.WRONG_ACTION, ...); 

È riceverai l'errore:

TestClass.WRONG_CONSTANT cannot be resolved 

tipi di oggetti Usa

Per estendere l'idea di passare variabili, perché non passare oggetti, e richiedono l'oggetto ad essere il tipo corretto? In questo modo, non passeranno più String, quindi non potranno assolutamente passare qualcosa che non è definito.

public class ActionType { 
    public String action; 
} 

public class GoAction extends ActionType { 
    public GoAction() { 
    this.action = "go"; 
    } 
} 

public class TomAction { 

    public TomAction(ActionType actionType, ...) { 
    // Use actionType.action 
    ... 
    } 
    ... 

} 

di chiamarlo, si esegue questa operazione:

TomAction act = new TomAction(new GoAction(), ...); 

Naturalmente questo ci porta a ...

estendere la classe

Estendere la classe, invece di utilizzando un parametro.

public class GoAction extends TomAction { 
} 

poi fare questo:

TomAction act = new GoAction(...); 
+0

Ancora una volta, questo può trarre vantaggio dall'annotazione della costante e dall'utilizzo di APT per verificare che esista un metodo con questo nome. – ddimitrov

+0

Utilizzando le costanti, l'utente dovrebbe implementare un'interfaccia, ciò che questa classe sta cercando di evitare. Usando il tipo di oggetto, non posso usare 2 pulsanti con 2 azioni diverse. Estendendo la classe, è meglio usare l'azione astratta predefinita. –

+0

Non riesco a vedere perché è necessaria un'interfaccia per dichiarare costanti String in una classe. Puoi elaborare per favore? – ddimitrov

2

Non v'è alcun modo per verificare la presenza di un metodo dinamico in fase di compilazione.Tuttavia, potrebbe essere possibile riprogettare la classe del framework in qualche modo per fare ciò che desidera. L'unica funzionalità che la tua classe TomAction offre oltre la AbstractAction è la gestione degli errori e la possibilità di rinominare il metodo actionPerformed, al costo (elevato, IMHO) dell'utilizzo del riflesso e della perdita della sicurezza del tipo.

avrei implementare in questo modo:

public class TomAction extends AbstractAction { 

    public static interface Command { 
     // CommandException is implemented elsewhere 
     public void execute() throws CommandException; 
    } 

    private Command cmd; 
    // other members omitted for brevity 

    public TomAction(Command cmd, String name, String failMsg, Icon icon) { 
     super(name, icon); 
     this.cmd = cmd; 
     this.failMsg = failMsg; 
    } 

    public void actionPerformed(ActionEvent e) { 
     try { 
      cmd.execute(); 
     } 
     catch (CommandException e) { 
      // handle failure 
     } 
    } 

    // remaining implementation 
} 

Poi, per usarlo:

TomAction.Command goCmd = new TomAction.Command() { 
    public void execute() throws CommandException { 
     go(); 
    } 
}; 

TomAction act = new TomAction(goCmd, "Click to go!", msg, icon); 
JButton btn = new JButton(act); 

è un po 'più dettagliato di quanto l'implementazione esistente, ma ti dà il tempo di compilazione desiderato digitare sicurezza.

+1

Se seguendo questo approccio, dichiarerei il derivato action class (invece di avere una classe anon e una variabile membro) e istanziarla al punto di utilizzo. In questo modo gli stacktrace leggono meglio e tu controlli l'accesso all'azione. – ddimitrov

0

Penso solo bisogno di dichiarare un'interfaccia di callback per complimentarmi con la vostra classe TomAction come parte della vostra TomAction "quadro":

interface TACB { // TomActionCallback 
    void go(); 
} 

Quindi l'uso del campione diventa:

class MyClass implements TACB { 

    public MyClass() { 
     String msg = "Fail to execure 'go' task."; 
     Icon icon = null; // putting null to simplify the example 
     TomAction act = new TomAction(this, this, "Click to go!", msg, icon); 
     JButton btn = new JButton(act); 
    } 

    public void go() { 
     // do task 
    } 

} 

alternativa:

class MyClass { 

    public MyClass() { 
     String msg = "Fail to execure 'go' task."; 
     Icon icon = null; // putting null to simplify the example 
     TACB tacb1 = new TACB() { public void go() { this.go1() } }; 
     TomAction act1 = new TomAction(tacb1, this, "Click to go!", msg, icon); 
     JButton btn1 = new JButton(act1); 
     TACB tacb2 = new TACB() { public void go() { this.go2() } }; 
     TomAction act2 = new TomAction(tacb2, this, "Click to go!", msg, icon); 
     JButton btn2 = new JButton(act2); 
    } 

    private void go1() { 
     // do task 
    } 

    private void go2() { 
     // do task 
    } 

} 

Sì, è un po 'più complicato, ma questo è il costo di e controllo del compilatore. Se ancora non ti piace questa soluzione, l'unica cosa che posso pensare è scrivere il tuo strumento che analizza i file di classe e analizza le chiamate al costruttore TomAction per trovare il primo argomento per verificare l'esistenza del metodo corrispondente .