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);
        }
    }

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) {  }
        }
    }

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) {  }
        }
    }
}

gitlab zip

 
java/reflection.txt · Last modified: 2020/12/14 13:19 by 88.103.111.44
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki