====== Reflection ====== 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. ==== Třída Object ==== 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ů ==== Metody třídy Object ==== 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) ==== Třída Class ==== 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 ==== 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; ... } ==== Class.forName ==== 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). ==== Class.newInstance ==== 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// ==== invoke - zavolání metody ==== 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 ===== Strom vizuálních prvků ===== Funkce **displayComponents** zobrazí strom grafických prvků, z kterých je vytvořeno naše okne * vytvoříme kořenový prvek stromu * parametrem jsou uživatelská data pro kořenový prvek * jako parametr použijeme odkaz na celé naše okno * //JTree// v uzlu stromu zobrazí text získaný z uživatelských dat pomocí metody **toString ()** * funkce **addComponents** přidá do stromu podřízené grafické prvky * na základě kořenového prvku vytvoříme model dodávající stromu data * stromu přířadíme vytvořený model 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); } } {{reflection_title.png}} ===== Reakce na změnu vybraného prvku ve stromu ===== 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()); } } ===== Tabulka vlastností ===== 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 * jméno vlastnosti, **getName ()** * popis metody **read** pro čtení hodnoty //vlastnosti//, **getReadMethod** 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) { } } ===== Reakce na změnu hodnoty v tabulce ===== 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); ===== Změny hodnot některých vlastností ===== 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) { } } } {{reflection_hide.png}} ==== Celý program ===== 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) { } } } } [[https://gitlab.fjfi.cvut.cz/culikzde/java-priklady/-/tree/master/Reflection|gitlab]] [[http://kmlinux.fjfi.cvut.cz/~culikzde/sos/Reflection2020.zip|zip]]