Hello World minimo


Si dice che un linguaggio non sia general purpose se richede un alto numero di istruzioni per scrivere a video Hello World.

Ma in java, di quanti caratteri è composta la più piccola classe che scrive a video “Hello World”? La risposta è 21.

La classe B detiene questo record.

public class B extends A;

Questo è possibile grazie al fatto che eredita dalla classe A tutti i metodi public e protected e quindi anche il metodo main.

public class A { 
  public A() { 
      super(); 
  }  

  public String sayHello() { 
      System.out.println("Hello World"); 
  }  

  public static void main(String[] args) { 
     A a = new A(); 
     a.sayHello(); 
  } 
}//:-

Lo stile non è il massimo, ma lo considero un bell’esempio per spiegare l’ereditarietà.

[ratings]

Advertisements

Serializzazione


La serializzazione, nei linguaggi ad oggetti, è il processo che permette di memorizzare lo stato interno di un oggetto in supporto fisico, sia esso un disco rigido o una connessione di rete, per poi essere “riletto” in un secondo momento (1).

E’ alla base di molte architetture, gli oggetti salvati in una sessione web devono poter essere serializzabili (2) cosi come quelli inviati tramite iiop, anche se è considerata una falla alla sicurezza. In ogni caso per rendere una classe serializzabile non sempre basta implementare l’interfaccia java.io.serializable, bisogna porre attenzione anche ad alcuni aspetti un po’ particolari.

Non tutto è serializzabile

Spesso non sempre lo stato interno di un oggetto può essere salvato e successivamente caricato senza problemi. Pensiamo ad esempio ai riferimenti a strutture dati del sistema operativo, puntatori ai file o thread in esecuzione, che non hanno senso al di fuori del sistema, o del processo, in cui sono attivi. O ancora a dati con un signifcato legato ad un determinato periodo temporale, ad esempio cache o sessioni, dati che a distanza di tempo diventano quindi inutili. La serializzazione permette infatti di salvare dati che possono essere letti anche a distanza di anni.
Tra l’altro un oggetto è serializzabile solo se tutte le strutture dati a cui fa riferimento sono a loro volta serializabili.

Per evitare che un attributo di un oggetto venga salvato è necessario definirlo come transient. Questi attributi devono essere reinizializzati, se sensato, nella fase di deserializzazione.

Costruttori e deserializzazione 

Dobbiamo fare attenzione al fatto che la deserializzazione crea l’oggetto senza passare dal costruttore, non viene utilizzato nemmeno quello di default. Ad esempio questa classe

public class SerializationTest implements Serializable { 
    private int intValue = 1; 
    private String strValue = "init"; 
    private transient int intTransient = 1; 
    private transient String strTransient = "init";             

    public SerializationTest() { 
        intValue = 2; 
        strValue = "constructor"; 
        intTransient = 2; 
        strTransient = "constructor"; 
    }             

    public void touch() { 
        intValue = 3; 
        strValue = "touched"; 
        intTransient = 3; 
        strTransient = "touched"; 
    }             

    @Override 
    public String toString() { 
        return "Value <" + intValue + "," + strValue + "> Transient <" + intTransient + "," + strTransient + ">"; 
    } 
}//:~

Se utilizzato come segue 

    SerializationTest test = new SerializationTest(); 
    System.out.println("Before touch"); 
    System.out.println(test); 
    test.touch(); 
    System.out.println("Before serialization"); 
    System.out.println(test); 
    File tempFile = File.createTempFile("test", "tmp"); 
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(tempFile)); 
    oos.writeObject(test); 
    oos.close();          

    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(tempFile)); 
    SerializationTest test2 = (SerializationTest) ois.readObject(); 
    ois.close();          

    System.out.println("After deserialization"); 
    System.out.println(test2);

uno si aspetterebbe un risultato del tipo: 

Before touch 
Value <2,constructor> Transient <2,constructor> 
Before serialization 
Value <3,touched> Transient <3,touched> 
After deserialization 
Value <3,touched> Transient <1,init>

invece otteniamo come risultato:

Before touch 
Value <2,constructor> Transient <2,constructor> 
Before serialization 
Value <3,touched> Transient <3,touched> 
After deserialization 
Value <3,touched> Transient <0,null>

Lasciando quindi non inizializzati i campi transienti. Infatti uno tra i consigli di thesp0nge è quello di non fidarsi del costruttore. Per superare questo problema si possono definire questi due metodi:

  • private void writeObject(ObjectOutputStream out) throws IOException;
  • private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException;
  • Ad esempio

    private void writeObject(ObjectOutputStream out) throws IOException { 
        out.defaultWriteObject(); 
    }       
    
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { 
        in.defaultReadObject(); 
        intTransient = 0; 
        stringTransient = "init"; 
    }

    Da notare che la prima istruzione da chiamare, in entrambi i metodi, è il metodo di default che permette la costruzione corretta dell’oggetto. Altro aspetto da notare il fatto che i metodi sono private void, in questo modo non è possibile ne invocarli dall’esterno ne farne l’override. Questi due metodi sono ad uso e consumo della JVM.

    Versioni delle classi

    Il processo di serializzazione/deserializzazione funziona solo se la struttura delle classi non cambia. Per gestire le versioni della classe java si appoggia alla Serial Version della classe, versione che può cambiare tra una compilazione e la successiva della classe. Nel caso che la versione sia differente il processo di deserializzazione solleva un eccezione del tipo java.io.InvalidClassException.

    Spesso però ciò che cambia in una classe sono solamente i metodi, mentre gli attributi rimangono costanti. In questi casi è possibile forzare la versione definendo manualmente l’attributo private final static long serialversioneUID.

    Bloccare la serializzazione

    Infine per proteggere la classe, evitando che possa essere serializzata, è necessario definire i metodi writeObject e readObject come segue:

    private void writeObject(ObjectOutputStream out) throws IOException { 
        throw new NotSerializableException(); 
    }     
    
    private void readObject(ObjectInputStream in) throws IOException { 
        throw new NotSerializableException(); 
    }

    Per approfondire

    [ratings]

    Note

    (1) Anche se qualcuno mi ha detto che credeva che fosse il processo di mettere in sequenza gli oggetti 😀
    (2) Alcuni servlet container memorizzano le sessioni su disco o la condividono tra i vari nodi di un cluster.
    (3) Di fatto i valori di default, 1 e init, associati agli attriobuti in fase di definizione degli attributi sono inutili.