====== 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]]