Evropský sociální fond Praha & EU: Investujeme do vaší budoucnosti Programování v jazyku Java RMI, vnitřní a vnořené třídy, generika BI-PJV Programování v jazyku Java Katedra teoretické informatiky Miroslav Balík Fakulta informačních technologií České vysoké učení technické
RMI = Remote Method Invocation Tato technologie využívá vzdálené objekty tím, že volá jejich metody. Argumenty metod jsou požadavky a metoda případně vrátí výsledek. Vzdálený objekt existuje v jiné JVM - povětšinou na jiném počítači. Klient komunikuje se vzdáleným objektem pouze přes interfejs, který: musí být potomkem interfejsu Remote všechny jeho metody throws RemoteException Vlastní vzdálený objekt, tj. implementace, musí být: realizací programátorem definovaného interfejsu potomkem třídy - java.rmi.server.unicastremoteobject či Activatable ( ta zařídí export ) - anebo musí být explicitně exportován metodou java.rmi.server.unicastremoteobject.export(...).
Klient - Server Vývojář odpovídá pouze za definice interfejsů a implementujících tříd -nemusí se starat o komunikační protokol. Původně musel zajistit generaci stubu a sketetonu pomocí dodatečného kompilátoru rmic od verze 1.5 se tyto vytvářejí automaticky. JVM Java RMI client (application layer) stub RRL Transport layer socket factory socket JRMP on TCP/IP JVM Java RMI server (application layer) skeleton RRL Transport layer RRL = Remote Reference Layer
RMI Unicast RemoteObject Remote Security Manager registry MojeTrida MujInterfejs Registry stub rmic client server new p gor MujInterfejs stub RMI / IIOP skel eton object mar Pro v1.2 je skeleton integrován v UnicastRemoteObject
Spouštění RMI 1. Server vytvoří jeden vzdálený objekt a předá serializovaný stub do registry k registraci. Ten realizuje výpočet i pro více klientů. 2. Klient získá od registry handle ke vzdálenému objektu. 3. Registry mu vrátí serializovanou kopii stubu. Klient z ní vytvoří instanci stubu. Pro další komunikaci klient-server není registry nutná. 1. Klientský program zavolá nějakou metodu stubu. 2. Stub kontaktuje skeleton v serveru. 3. Skeleton zavolá metodu vzdáleného objektu. Při každém zavolání vytvoří server nové vlákno, které provede výpočet v metodě. 4. Metoda vrátí výsledek skeletonu. 5. Skeleton vrátí výsledek stubu. 6. Stub vrátí výsledek klientskému programu.
Remote ( tag interface ) javax.management.remote.rmi RMIServer RMIServer Impl RMIIIOP ServerImpl java.rmi.* RMIJRMP java.rmi.registry Registry Locate Registry ServerImpl Distributed Garbage Collection java.rmi.dgc Marshalled DGC Lease VMID Object java.rmi.activation Naming ActivationInstantiator Activation Activation java.lang ActivationMonitor Desc GroupDesc Security Manager ActivationSystem Activator Activation ID Activation GroupID RMISecurity Manager
tag interface java.rmi.server java.rmi Remote javax.rmi.corba.stub _Remote_Stub Remote Stub Remote Object Remote Server java.rmi.activation Activable java.io Externalizable RemoteRef Unreferenced Unicast ServerRef RMIConnection Impl RMIConnection Impl_Stub RemoteObject java.rmi.activation Activation Group RMIClass LoaderSpi RMIClass Loader RMIServer javax.management.remote.rmi Activation Group_Stub java.rmi.activation ActivationInstantiator java.rmi.activation UID ObjID RMIClientSocketFactory RMISocket Factory RMIServerSocketFactory Portable RemoteObject RMIFailureHandler
interfejs java.rmi.registry public void bind( String name, Remote obj ) throws RemoteException, AlreadyBoundException, AccessException public void unbind( String name ) throws RemoteException, NotBoundException, AccessException public void rebind( String name, Remote obj ) throws RemoteException, AccessException public String[ ] list( ) throws RemoteException, AccessException public Remote lookup( String name ) throws RemoteException, NotBoundException, AccessException
Registry čili registratura umožňuje evidovat vzdálené objekty pod jednoduchým jménem. Klient jejím prostřednictvím může navázat spojení se serverem. Registry není perzistentní. Může být spuštěna v rámci OS nebo JVM. Každý servrový proces může udržovat vlastní registry nebo sdílet jedinou podporující všechny virtuální stroje na lokálním systému. Registry se vytvoří zavoláním rmiregistry.exec [ port 1099 ], z adresáře classes anebo se tam zkopíruje jdk/bin. ( Ve Windows je třeba zkopírovat také jli.dll, tj. application extension. ) Registry se ovládá statickými metodami třídy java.rmi.naming: bind, unbind, rebind, list a lookup
Použití Registry reg = LocateRegistry.createRegistry( port ); //SINGLETON reg.bind( "Moje", new MojeTrida( ) ); Spuštění zevnitř: Runtime.getRuntime( ).exec( "rmiregistry" ); Runtime.getRuntime( ).exec( "rmid J-Djava.security.policy=rmid.policy" ); javaw background in Win
Využití RMI Naming Service Klient vyhledá vzdálený objekt jménem takto: rmi://host:port/name v metodě Naming.lookup. ( Defaultně: rmi localhost 1099.) Např.: String id = "rmi://" + host + ":" + port + "/Moje" ; MujInterfejs z = ( MujInterfejs ) Naming.lookup(); z.metoda1(... );
java.rmi.marshalledobject Po deserializaci instance MarshalledObject není její obsah deserializován. Obsahuje totiž pole bytů, které může být později interpretováno jako objekt. Codebase URL je uložen uvnitř této struktury a ukazuje kde jsou příslušné classy uloženy pro automatické stažení. Tedy lze deserializovat na strojích nemající patřičnou třídu v classpath. Objekty mohou být předávány přes více strojů aniž jsou plně deserializovány. ArrayList data =... MarshalledObject mo = new MarshalledObject( data ); Object o = mo.get( );
Dřívější příprava stubu a skeletonu Pro uskutečnění spojení mezi klientem a serverem je třeba na straně klienta vytvoří tzv. stub. ( V Javě 1.1 bylo nutné ještě na straně serveru vytvářet tzv. skeleton. ) To zařizuje speciální kompilátor rmic, který se nalézá v j2sdk/bin/... Stub vytváří rmic podle třídy vzdáleného objektu. V JBuilderu je proto nutno u těchto tříd zvolit: -> Properties Generate RMI stub/skeleton Options: -keep v1.2 Při rebuild se v patřičném podadresáři balíčku v classes\...\ vytvoří MojeTrida_Stub.class a v adresáři classes\generated Source\...\MojeTrida_Stub.java.
Aktivace objektů Aktivatabilní objekty jsou zpravidla vedeny na separátní(ch) JVM. Tyto JVM se definují jako Activation Group - v ní je Activation Monitor, který sleduje všechny příslušné objektu a i tu grupu. Objekty i grupy mají ID a deskriptor. Activation group ActivationGroupID ActivationGroupDesc Activation Monitor Activatable object ActivationID ActivationDesc
Aktivační grupy Aktivátor vytváří aktivační grupy. Tím je RMI démon v jdk/bin/rmid.exe Aktivační grupy se vytvoří pomocí tříd z java.rmi.activation: ActivationGroupDesc gd = new ActivationGroupDesc( null, null ); ActivationGroupID gi = ActivationGroup.getSystem( ).registergroup( gd ); ActivationGroup.createGroup( gi, de, 0 ); // 0 na kterémkoli portu
Aktivatabilní objekty Implementace vzdáleného objektu je buď potomkem třídy Activatable ActivationGroup.create(... ) viz výše String[ ] names = {,, ; MarshalledObject mo = new MarshalledObject( names ); ActivationDesc desc = new ActivationDesc( gid, " ", null, mo ); Registrovat takto: RemoteObject stub = ( RemoteObject ) Activatable.register(desc); Naming.rebind( "MyRENOBject", stub ); anebo je exportován: V konstruktoru se přijme ActivationID id a MarshalledObject mo names = ( String[ ] ) mo.get( ); Activatable.export( this, id, 0 ); // export do RMIRegistry Tyto úpravy se týkají pouze serveru. Pro klienta je to transparentní.
Vnořené a vnitřní členy Třídy či interfejsy mohou být členy tříd či interfejsů. Je-li X obalem Y a Y obalem Z, pak Z získá jméno X$Y$Z - kompilací vzniknou classy X, X$Y a X$Y$Z. Z vidí členy všech svých obalů - nehledě na jejich modifikátory přístupu. [ public protected private ] [ strictfp ] - [ static ] [ abstract ] interface - vnořený (nested) interfejs - static [ abstract final ] class - vnořená (nested) třída - -------------------------------- - vnitřní interfejsy neexistují - [ abstract final ] class - vnitřní (inner) třída - lokální: bez dalších modifikátorů - anonymní: bez hlavičky Krom anonymní třídy lze uvést extends resp. implements Vnořený člen má přístup jen ke static členům svých obalů. Může mít statické i nestatické členy. I statická třída má konstruktory. Vnitřní třídy nejsou static a kromě static final konstant nemohou deklarovat žádné statické členy - mohou je však zdědit.
Lokální třída je vnitřní třída deklarovaná v metodě, bloku či v dynam. inicializátoru. -jen s modifikátory [ strictfp ] [ abstract final ] -typ lokální třídy je známý v bloku až za její deklarací. -lze ji instanciovat pouze z vnitřku metody - není přístupná z vnějšku. -k lokálním proměnným a parametrům metody má přístup jen jsou-li finální, neboť nefinální sídlí jen dočasně na zásobníku. -sice return umožňuje objekt vrátit, avšak jeho vlastní typ není vně znám, neb v hlavičce metody nelze zadat jméno lokální třídy jako návratový typ. Lokální třída získá jméno s prefixem ze jmen vnějších tříd a pořadovým číslem metody $n v níž jsou deklarovány stejnojmenné lokální třídy. Tento typ, např. Out$3Loc, je vně neznámý, tudíž nelze jím přetypovat. Lze užít vně známých typů, které lokální třída rozšiřuje či implementuje. Lokální interfejs není možný - vnitřní interfejsy neexistují.
Anonymní třída je vnitřní třída bez hlavičky a tudíž: nemá jméno, modifikátory ani nelze použít extends či implements, nemůže mít potomky, nelze definovat žádné její konstruktory, má jen defaultní konstruktor neznámého jména, roli konstruktorů event. nahrazují nestatické inicializátory, instance anonymní třídy má jméno dle obalů ukončené $i, i>=1. TX x = new T( ) { // T je třída či interfejs // TX je T či jeho nadtyp či Object... // nestatické členy... // jen statické finální atributy ;
Adapter je obecně třída, implementující nějaký interfejs tak, že zděděné abstraktní metody přepíše na konkrétní - avšak prázdné. Adapter mívá triviální funkcionalitu a bývá s modifikátorem abstract. Slouží totiž jako předek potomkům, kteří mají některé metody přepsány na neprázdné, čímž získají žádanou funkcionalitu aniž programátor musí psát nepotřebné metody. Např.: interface java.awt.windowlistener má sedm metod. Adapter definovaný takto: abstract class WindowAdapter implements WindowListener je všechny metody konkretizuje prázdnými metodami. Ovládat okénko ikonou lze pomocí tzv. handleru, což je objekt vytvořený buď dle normální anebo anonymní třídy. Důležité je, že v obou případech je splněn interfejs WindowListener.
Použití adapteru Pojmenovaně: WindowListener wl = new WindowHandler( ); kde: class WindowHandler extends WindowAdapter { public void WindowClosing( WindowEvent ev ) { System.exit(0); // end of application či anonymně: WindowListener wl = new WindowAdapter( ) { public void WindowClosing( WindowEvent ev ) { System.exit(0); // end of application ;
Inner class Vnitřní třída umožňuje seskupit věcně související kódy a případně je skrýt. public class Outer { private int x; public class Inner { stack heap public void m( ) { x++; public void test( ) { Inner Outer.this Inner n = new Inner( ); m( this n.m( ); test( main( n this Outer x public static void main(... ) { Outer o = new Outer( ); o.test( );
Inner class public class Outer { private int x; stack heap public class Inner { public void m( ) { x++; Inner Outer.this public class OuterTest { public static void main(... ) { m( main( this n p Outer x Outer p = new Outer( ); Outer.Inner n = p.new Inner( ); // n = new Outer( ). new Inner( ); n.m( ); Instanciovat Inner lze jen prostřednictvím instance Outer.
Inner class public class Outer { private int x; public class Inner { private int x; public void m( int x ) { x++; this.x++; Outer.this.x++; x m( n main( p Inner Outer x Outer.this x
Local class public class Outer { private int x = 5; public Object get( final int y ) { final int z = 6; class Local { public String tostring ( ) { return ""+(x+y+z); return new Local( ); public static void main( String [ ] args ) { Outer p = new Outer( ); Object obj = p.get( 700 ); System.out.println( obj ); // vytiskne 711
Využití vnořených tříd Bod, úsečku, obdélník, elipsu atd. v rovině pojednávají balíčky java.awt s přesností int a java.awt.geom s přesností double a float abstract class Point2D { // nedeklaruje atributy static class Point2D.Float extends Point2D { float x, y;... get static class Point2D.Double extends Point2D { double x, y;... get public double distance( Point2D pt ) { return public abstract double getx( ); // getry a setry class Point extends Point2D { int x, y; public double getx( ) { Od třídy Point2D dědí ostatní metody distance, distancesq a další. Díky polymorfismu lze: Point2D p1 = new Point2D.Double( 1.1, 2.2 ); Point p2 = new Point( 3, 4 ); double d = p1.distance( p2 );
Generika (od v. 1.5) Parametrizované datové typy podporující typovou kontrolu při kompilaci těmi lze vyjádřit jasněji záměry, zlepšit čitelnost a robustnost programu. Parametrické typy pojednává jen kompilátor a do class se nedostanou ( tzv. erasure ) v běhu jsou již jen hrubé ( raw ) typy. Generiku užívají zejména kolekce k vymezení přijatelných typů prvků. Parametrizovat lze jen referenční typy tzv. typovými parametry. Leč nikoli: - pole a vytvoření pole: E[ ] e= ( E[ ] ) new Object[5]; new E[3] new ArrayList<String>[7]; - primitivní typy: List<int> - statické atributy: static T t; - vytvořit instanci: new E( ); - potomky třídy Throwable: class MyEx<T> extends Exception { ; Podrobněji: The Java Language Specification (Third Edition) 4.4-4.9, 18 Pecinovský R.: Java 5.0, CP Books, Brno, 2005, s. 152, ISBN 80-251-0615-2
Syntax hlaviček... class JménoTřídy [ < Deklarace > ] [ extends JménoNadTřídy [ < TypArgument,... > ] ] [ implements JménoInterfejsu [ < TypArgument,... > ],... ] {...... interface JménoInterfejsu [ < Deklarace > ] [ extends JménoNadInterfejsu [ < TypArgument,... > ],... ] {...... [ < Deklarace > ] JménoTřídy // konstruktor ( [ ( Typ TypParm ) JménoParametru,... ] )... [ [ static] [ final ] [ abstract ] ] // metoda [ < Deklarace > ] ( void NávratovýTyp ) JménoMetody ( [ ( Typ TypParm ) JménoParametru,... ] )
Typové proměnné ( 4.4 ) Jen referenční typy (decorated types) lze ozdobit deklarací typových parametrů v hlavičkách tříd, interfejsů, konstruktorů či metod ve tvaru: < T 1, T 2,... T n > kde T i může být: T [ extends java.lang.object ] - T se chápe jako potomek Objectu. T extends P - P je známá typová proměnná. T extends R [ & I & I... ] - R třída nebo interfejs, I interfejs, & - logický součin Typové proměnné jsou nekvalifikované identifikátory typicky: T,E,K,V,N pro Type, Element, Key, Value, Number Příklad: class Demo < T, E extends A & K, N extends J & K > { kde A je nějaká třída J a K jsou nějaké interfejsy.
Parametrizovaný objekt vytvoří konstruktor s typovými argumenty v počtu a s omezeními formálních parametrů dle definice typu: new Demo < A, B, C > ( ) ; kde A, B, C jsou vhodné referenční typy, tj. třídy nebo interfejsy. Parametrizovaný objekt lze referovat čtyřmi způsoby: Demo r0 ; // raw r0: Object, A, Object Demo < A,B,C > r1 ; // r1: A,B,C r1 = new Demo < A,B,C,... > ( ) ; // decorated type r0 = r1 ; // erasure r0 = new Demo ( ) ; // raw type r1 = r0 ; // unchecked conversion Reference, tj. proměnné i parametry metod, lze vymezit i žolíkem:
Parametrizovaný Interval public class Interval<E extends Comparable<E>> implements Comparable<Interval<E>> { E low, high; public Interval( E low, E high ) { if ( low.compareto( high ) >0 ) throw new IllegalArgumentException( ); this.low=low; this.high=high; @Override public int compareto( Interval<E> that ) { return this.low.compareto( that.low ); @Override public String tostring( ) { return "Interval["+low+","+high+"]"; class XI<E extends Comparable<E>> extends Interval<E> { public XI( E a, E b) { super(a,b);
? čili žolík Reference, tj. proměnné a parametry metod, lze vymezit i žolíkem. Symbolizuje množinu neznámých typů takto: <? > - bez omezení čili <? [ extends java.lang.object ] > <? extends Typ > - shora, tj. Typ a jeho potomci či implementace - extends se zde chápe i jako implements <? super Typ > - zdola, tj. Typ a všechny jeho supertypy Reference s žolíkem umožňuje přístup, ale nedovolí přidat nebo změnit ( kromě null ) avšak odstranit ano. Pro výpis jakékoli kolekce lze použít neomezený žolík: void printany( List<? > c ) { // ne < Object > for ( Object o : c ) System.out.println( o ); c.get( 0 ); c.add( new Object( ) ); // lze - nemá param. typ // má param. E - compile error
? v referenci Budiž třída na výrobu parametrizovatelných krabic: class Box<E> { E e; // nelze inicializovat jinak než = null a věci tříd A, B, C, D extends C extends B extends A extends Object. Kompilátor umožní do new Box<B>( ) vložit B resp. C, D pomocí referencí: Box < [? super ] B > Box - leč pak s hláškou unchecked assignment. Přistoupit k věci uložené v krabici lze pomocí referencí: Box<? [ extends ( Object A B ) ] > Box<? super B > Box a referovat ji jako B či jen kterýmkoli předkem.
Příklad syntaxe class Demo < X, Y extends Number, Z eabstractmap < K, V > implements NavigableMap < K, V > { private Comparator<? super K > comparator = null ; public TreeMap( Comparator<? super K > c) { comparator = c ; public TreeMap( Map<? extends K,? extends V > m ) { putall( m );... Map < String, Integer > m = new TreeMap< String, Integer > ( ) ;
TreeMap jako příklad class TreeMap< K, V > extends AbstractMap < K, V > implements NavigableMap < K, V > { private Comparator<? super K > comparator = null ; public TreeMap( Comparator<? super K > c) { comparator = c ; public TreeMap( Map<? extends K,? extends V > m ) { putall( m );... Map < String, Integer > m = new TreeMap< String, Integer > ( ) ; m.put ( "AAA", 1); int I = m.get ( "AAA" );
Příklad parametrizované metody Parametrizovat metody lze i v neparametrizovaných třídách i interfejsech.... String s = m ( "AAA", 666.999 ); // čísla převádí autoboxing Integer i = m ( 777, "BBB" ); Object o = <Double,Number>m( 3.14, 3L ); // s vymezením public static <U, V> U m( U u, V v ) { U x = u; V y = v; System.out.println( u.getclass( ) +" "+ v.getclass( ) ); System.out.println( x+" "+y ) ; return x; Lze parametrizovat i konstruktor - to má smysl jen v parametrizovaných třídách.
Generika a subtypy Důležitá, byť neituitivní, zábrana proti narušení vymezení Pro vztah Subtyp --> Supertyp ( extends / implements ) neplatí: L<Subtyp> --> L<Supertyp> Příklad: class Driver extends Person { List<Driver> drivers = new ArrayList<Driver> ( ); List<Person> people = drivers; // error people.add( new Person( ) ); // non-driver in drivers
Erasure Collection<String> cs = new ArrayList <String>( ); Collection co = cs; // raw type co.add( 666 ) ; // unsafe operation Collection<?> cx = cs; Collection<? extends String > cy = cs; Collection<Object> co = cs; // incompatible types