Reflexe poskytuje typové informace za běhu programu.
Klasické kompilované jazyky, jako například C, za běhu programu neskladují informace o použitých deklaracích.
Různým grafickým vývojovým prostředím se například hodí seznam deklarací v jednotlivých třídách.
V Javě jsou všechny třídy a všechna pole odvozena ze standardní třídy java.lang.Object.
Balíček java.lang je vždy dostupný a překladač automaticky “doplní” import java.lang.*
Proměnnou typu Object můžeme použít pro uložení ukazatele na libovolnou třídu nebo pole.
Také libovolnou proměnnou typu rozhraní (interface) můžeme uložit do proměnné typu Object,
neboť instance implementující rozhraní také musí odvozena z typu Object
Pro primitivní typy int, short, long, byte, boolean, char, float a double,
existují obalové typy (wrapper types) Integer, Short, Long, Byte, Boolean, Character, Float a Double (z balíčku java.lang),
které skladují jednu hodnotu příslušného primitivního typu a jsou odvozené z třídy Object.
Současné verze jazyka Java dovolují přiřadit do proměnné typu Object výraz primitivního typu a
překladač vytvoří instanci odpovídajícího obalového typu a uloží do něj primitivní hodnotu (automatic boxing).
Object obj = 1;
nahradí
Object obj = new Integer (1);
Proměnné typu Object tedy mohou skladovat i hodnoty primitivních typů
Třída Object obsahuje některé metody, které jsou tedy dostupné ve všech objektech.
class Object { ... public String toString (); public boolean equals (Object obj); // porovnání obsahu dvou objektů, nikoliv jen porovnání ukazatelů public int hashCode (); // obdoba adresy v Java virtuálním stroji public Class getClass (); // popis typu, uvidíme v následujícím odstavci ... }
Například metodu toString používá překladač při sčítání,
pokud je jeden operandů řetěz znaků a další sčítanec není typy String, je na odlišný sčítanec zavolána metoda toString ().
( Přesněji pokud je odlišný sčítanec null, přičte se řetězec tvořený čtyřmi písmeny “null” a jinak se zavolá toString)
Z libovolné instance lze získat popis typu voláním metody getClass.
Popis typu je skladován v objektu typu java.lang.Class.
Object obj; // nějaký nenulový ukazatel Class cls = obj.getClass ();
Proměnnou typu Class (s velkým písmenem C) používáme pro typové informace.
Například jméno třídy včetně balíčků získáme pomocí metody getName
String name = cls.getName ();
Takto je definována metoda toString ve třídě Object.
Všechny třídy, které si metodu toString nepředefinovaly, používají tuto implementaci.
class Object { public String toString() { return getClass().getName() + "@" + Integer.toHexString (hashCode()); } }
Třídy Field a Method poskytují informace o proměnných a metodách uvnitř třídy.
Třídy Field a Method jsou z balíčku java.lang.reflect, obvykle používáme import java.lang.reflect.*;
class Class { public Field [] getFields() throws SecurityException; public Method [] getMethods() throws SecurityException; public Method getMethod (String name, Class[] parameterTypes) throws NoSuchMethodException, SecurityException; ... }
O jednotlivých metodách se můžeme dozvědět jméno funkce, počet a typy parametrů a typ výsledku funkce.
package java.lang.reflect; public final class Method { public String getName(); public int getParameterCount(); // počet parametrů public Class [] getParameterTypes(); // pole typů jednotlivých parametrů public Class getReturnType(); // typ výsledku funkce public Object invoke (Object obj, Object [] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException; ... }
V příkladu Drag and Drop z minulého týdne jsem použili metodu Class.forName
pro nalezení již přeložené třídy na disku a načtení přeloženého kódu do našeho programu.
class Class { public static Class forName (String className) throws ClassNotFoundException; ... }
forName je statická metoda ve třídě Class, stačí zavolat
try { Class cls = Class.forName ("java.swing.JButton"); } catch (ClassNotFoundException exc) { System.out.println (exc); }
a získáme popis typu zadané třídy.
Pokud by třída nebyla ještě součástí našho programu,
tak se Java pokusí najít odpovídající .class soubor s přeloženým kódem.
Pomocí tříd na disku se v Javě dají napodobit dynamické knihovny (DLL).
Metoda newInstance se pokusí vytvořit novou instanci zadané třídy.
( pokud třída má verějně dostupný konstruktor bez parametrů )
newInstance není statická metoda, volá se popis_typu.newInstance ()
try { Object obj = cls.newInstance (); } catch (InstantiationException ex) { System.out.println (ex); } catch (IllegalAccessException ex) { System.out.println (ex); }
Je to vlastně obdoba new identrifikátor třídy (), ale pro třídu zadanou popisem typu Class
Pokud máme popis metody, můžeme metodu zavolat.
Při hledání popisu metody zadáváme jméno metody a pole typů parametrů (Class).
Při volání pomocí invoke použijeme popis metody a jako parametry zadáváme ukazatel na objekt a pole hodnot jednotlivých parametrů (Object)
Object obj; // nějaký nenulový ukazatel na objekt libovolného typu Class cls = obj.getClass (); // popis typu String methodName = "identifikátor metody"; Class [] parameter_types = { int.class, String.class }; // první parametr int, druhý String Method method = p.getMethod (method_name, parameter_types); // vyhledání popisu metody Object [] paramete_values = { first_parameter, second_parameter }; Object result = method.invoke (obj, parameter_values); // zavoláme metodu
Funkce displayComponents zobrazí strom grafických prvků, z kterých je vytvořeno naše okne
private void displayComponents () { DefaultMutableTreeNode root = new DefaultMutableTreeNode (this); addComponents (root, this); DefaultTreeModel model = new DefaultTreeModel (root); tree.setModel (model); }
Grafické prvky odvozené ze třídy Container nám poskytnou seznam vnořených prvků pomocí metody getComponents.
Získáme pole s prvky typu Component.
Pro jednotlivé prvky vytvoříme uzel stromu.
A pokud jsou prvky také odvozené ze třídy Container zavoláme rekurzivně funkci addComponents
private void addComponents (DefaultMutableTreeNode target, Container container) { for (Component c : container.getComponents()) { DefaultMutableTreeNode node = new DefaultMutableTreeNode (c); if (c instanceof Container) { addComponents (node, (Container) c); } target.add (node); } }
Pokud “klikneme” do stromu,
uložíme do proměnné node příslušný uzel stromu,
metoda getUserObject získá uživatelská data a
funkce displayProperties zobrazí tabulku na pravé straně okna.
private void treeValueChanged(javax.swing.event.TreeSelectionEvent evt) { DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent(); if (node != null) { displayProperties (node.getUserObject()); } }
Připravíme model pro tabulku, přidáme do něho dva sloupce.
Jednotlivé řádky přidáme voláním model.addRow (line). kde line je pole typu Object.
Jedotlivé objekty se převádějí na text opět pomocí toString
a zobrazují se v jednotlivých políčkách tabulky.
DefaultTableModel model = new DefaultTableModel (); model.addColumn ("name"); model.addColumn ("value"); ... String name = ... ; String value = ... ; Object [] line = {name, value}; model.addRow (line); ... propTable.setModel (model);
V tabulce zobrazíme vlastnosti (properties),
které jsou tvořeny dvojicemi funkcí get… a set…,
které používáme v našich programech.
Seznam vlastností nám poskytne objekt info typu BeanInfo.
BeanInfo pro třídu s popisem typu cls získéme pomocí info = Introspector.getBeanInfo (cls).
Metoda getPropertyDescriptors nám vrátí pole s prvky typu PropertyDescriptor. Z PropertyDescriptoru získáme
Pomocí read.invoke (obj) přečteme hodnotu zadané vlastnosti uložené v objektu obj
Přičtením k prázdnému řetězci převedeme hodnotu na řetězec znaků.
Pokud by hodnota byla null získáme řetězec obsahující čtyři písmena “null”
Class cls = obj.getClass (); BeanInfo info = Introspector.getBeanInfo (cls); PropertyDescriptor [] properties = info.getPropertyDescriptors (); for (PropertyDescriptor p : properties) { String name = p.getName (); Method read = p.getReadMethod(); String value = "" + read.invoke (obj); }
Během invoke může vzniknoot celá řada výjimek.
Na konci funkce model předáme tabulce.
A ještě nastavíme reakci na změnu hodnot v tabulce.
Object propObject; private void displayProperties (Object obj) { try { DefaultTableModel model = new DefaultTableModel (); model.addColumn ("name"); model.addColumn ("value"); propObject = obj; // remember, needed in propTableChanged Class cls = obj.getClass (); BeanInfo info = Introspector.getBeanInfo (cls); PropertyDescriptor [] properties = info.getPropertyDescriptors (); for (PropertyDescriptor p : properties) { String name = p.getName (); String value = ""; try { Method read = p.getReadMethod(); if (read != null) value = "" + read.invoke (obj); } catch (IllegalAccessException ex) { } catch (IllegalArgumentException ex) { } catch (InvocationTargetException ex) { } Object [] line = {name, value}; model.addRow (line); } propTable.setModel (model); TableModelListener listener = new TableModelListener() { @Override public void tableChanged (TableModelEvent evt) { propTableChanged (evt); } }; model.addTableModelListener(listener); } catch (IntrospectionException ex) { } }
K tabulce připojíme TableModelListener reagující na změny hodnot.
V něm předefinujeme virtální metodu tableChanged,
tak aby zavolala naší funkci propTableChanged.
TableModelListener listener = new TableModelListener() { @Override public void tableChanged (TableModelEvent evt) { propTableChanged (evt); } }; model.addTableModelListener(listener);
Pokud chceme změnit hodnotu jedné z vlastností,
vyhledáme write metodou a
zavoláme pomocí invoke tuto metodu
a novou hodnotu předánme jako jedinný parametr typu Object
Object obj; // objekt, jehož vlastnost chceme změnit PropertyDescriptor prop; // vlastnost String value; // nová hodnota Method write = prop.getWriteMethod (); write.invoke (obj, new Object [] { value });
PropertyDescriptor nám poskytne popis typu vlastnosti. Pokud by vlastnost byla typu bolean převedeme řetezec znaků z tabulky na true nebo false.
Podobně pro celočíselné vlastnosti převedeme uživatelem zadaný text na celé číslo.
V těchto případech se při přiřazení hodnoty typu boolean nebo int
do proměnné object_value typu Object
uloží objekt obalového typu Boolean nebo Integer.
Object object_value = value; // hodnota jako řetězec znaků type = prop.getPropertyType (); // typ vlastnosti if (type == boolean.class) { if (value.equals ("true")) object_value = true; if (value.equals ("false")) object_value = false; } else if (type == int.class) { int n = Integer.valueOf (value); object_value = n; } write.invoke (obj, new Object [] { object_value });
Zde je celá funkce reagující na změnu textu v tabulce.
Můžete zkusit změnit vlastnost title celého okna.
Případně vlastnost visible některého grafického prvku.
private void propTableChanged (TableModelEvent evt) { int line = evt.getFirstRow (); int col = evt.getColumn (); if (col == 1) { TableModel model = (TableModel) evt.getSource(); String name = (String) model.getValueAt (line, 0); String value = (String) model.getValueAt (line, 1); Object obj = propObject; Class cls = obj.getClass (); BeanInfo info; try { info = Introspector.getBeanInfo (cls); for (PropertyDescriptor prop : info.getPropertyDescriptors ()) if (prop.getName () == name) { Method write = prop.getWriteMethod (); if (write != null) { Object object_value = value; // String Class type = prop.getPropertyType (); if (type == boolean.class) { if (value.equals ("true")) object_value = true; if (value.equals ("false")) object_value = false; } else if (type == int.class) { try { value = value.trim (); int n = Integer.valueOf (value); object_value = n; } catch (NumberFormatException ex) { System.out.println (ex); } } try { System.out.println ("SET " + name + " = " + object_value + " : " + object_value.getClass ()); write.invoke (obj, new Object [] { object_value }); } catch (IllegalAccessException ex) { System.out.println (ex); } catch (IllegalArgumentException ex) { System.out.println (ex); } catch (InvocationTargetException ex) { System.out.println (ex);} } } } catch (IntrospectionException ex) { } } }
package reflection; import java.lang.reflect.*; import java.beans.*; import java.awt.*; import java.util.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.tree.*; import javax.swing.table.*; public class ReflectionWindow extends JFrame { public ReflectionWindow() { initComponents(); displayComponents(); } private void addComponents (DefaultMutableTreeNode target, Container container) { for (Component c : container.getComponents()) { DefaultMutableTreeNode node = new DefaultMutableTreeNode (c); if (c instanceof Container) { addComponents (node, (Container) c); } target.add (node); } } private void displayComponents () { DefaultMutableTreeNode root = new DefaultMutableTreeNode (this); addComponents (root, this); DefaultTreeModel model = new DefaultTreeModel (root); tree.setModel (model); } private void treeValueChanged(javax.swing.event.TreeSelectionEvent evt) { DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent(); if (node != null) { displayProperties (node.getUserObject()); } } Object propObject; private void displayProperties (Object obj) { try { DefaultTableModel model = new DefaultTableModel (); model.addColumn ("name"); model.addColumn ("value"); propObject = obj; // remember, needed in propTableChanged Class cls = obj.getClass (); BeanInfo info = Introspector.getBeanInfo (cls); PropertyDescriptor [] properties = info.getPropertyDescriptors (); for (PropertyDescriptor p : properties) { String name = p.getName (); String value = ""; try { Method read = p.getReadMethod(); if (read != null) value = "" + read.invoke (obj); } catch (IllegalAccessException ex) { } catch (IllegalArgumentException ex) { } catch (InvocationTargetException ex) { } Object [] line = {name, value}; model.addRow (line); } propTable.setModel (model); TableModelListener listener = new TableModelListener() { @Override public void tableChanged (TableModelEvent evt) { propTableChanged (evt); } }; model.addTableModelListener(listener); } catch (IntrospectionException ex) { } } private void propTableChanged (TableModelEvent evt) { int line = evt.getFirstRow (); int col = evt.getColumn (); if (col == 1) { TableModel model = (TableModel) evt.getSource(); String name = (String) model.getValueAt (line, 0); String value = (String) model.getValueAt (line, 1); Object obj = propObject; Class cls = obj.getClass (); BeanInfo info; try { info = Introspector.getBeanInfo (cls); for (PropertyDescriptor prop : info.getPropertyDescriptors ()) if (prop.getName () == name) { Method write = prop.getWriteMethod (); if (write != null) { Object object_value = value; // String Class type = prop.getPropertyType (); if (type == boolean.class) { if (value.equals ("true")) object_value = true; if (value.equals ("false")) object_value = false; // System.out.println ("BOOL " + object_value + " : " + object_value.getClass ()); } else if (type == int.class) { try { value = value.trim (); int n = Integer.valueOf (value); object_value = n; } catch (NumberFormatException ex) { System.out.println (ex); } } try { System.out.println ("SET " + name + " = " + object_value + " : " + object_value.getClass ()); write.invoke (obj, new Object [] { object_value }); } catch (IllegalAccessException ex) { System.out.println (ex); } catch (IllegalArgumentException ex) { System.out.println (ex); } catch (InvocationTargetException ex) { System.out.println (ex);} } } } catch (IntrospectionException ex) { } } } }