Tubes nommés et redirections en parallèle

La ligne de commande est bien faite. Cette phrase est souvent entendue dans la bouche des informaticiens. J’en ai encore vérifié la véracité ses jours derniers.

Ma problèmatique était de constituer des statistiques sur des accès HTTP en consultant un fichier de log. La volumétrie des log étant de l’ordre de plusieurs gigaoctets et je ne disposais que de ma ligne de commande en bash.

Je me suis alors dirigé vers une serie de grep avec les bons motifs de recherche couplé à un bon vieux wc:

grep motif fichier.log | wc -l

Pour compliquer il y avait plusieurs motifs differents à compter et il fallait produire un fichier par jour avec le comptage de chaques motifs.

ACCES_COUNT=`grep -e motif_date -e motif_acces fichier.log | wc -l`

Autant de ligne de ce genre que de motif. L’inconvenient est que l’on parcourt la log autant de fois qu’il y a de motif d’acces pour une date donnée. Dans mon cas j’avais 15 motifs soit 1h30 pour obtenir les statistiques pour un jour donné.

Or, une fois que l’on est sur la bonne date dans le fichier de log c’est dommage de le reparcourir depuis le debut pour le motif d’accès suivant. D’où l’idée d’externaliser le parcours du fichier à la recherche de la bonne date puis de dispatcher la ligne aux grep qui compte les motifs d’accès.

tee

On a donc envie de faire un premier grep dont on va diriger la sortie sur d’autres grep en arborscence. La première pièce du puzzle est la commande tee. Voici l’idée :

grep -e motif_date fichier.log | tee -a entree_proc_grep1 entree_proc_grep2 … > /dev/null

Il manque la seconde piece qui est celle qui permet de definir un tube entre le premier grep et les autres qui doivent s’exécuter en parallèle. C’est là que les tube nommés interviennent.

named pipe

La commande mkfifo crée un tube nommé que l’on donne en entrée au grep de motif d’accès. Ces grep seront alors bloqués en attente de lire des donnés dans leurs tubes respectifs.

mkfifo entree_proc_grep1

grep -e motif_acces < entree_proc_grep1 | wc -l > fichier_temp1` &

PID_LIST+=” $!”

Autant de lignes de ce genre qu’il y a de motif d’accès à compter. Remarquons qu’à ce stade on à seulement créé des processus pour faire les grep de comptage et qu’ils sont tous bloqués en attentes sur leur tube. On alimente ensuite les tubes. Voici l’ordre

  1. créer les tube
  2. lancer en arrière plan les grep prenant les tubes en entrée
  3. lancer le grep sur la date pipé avec le tee vers les tubes

Subtilites

On ne peut plus récupérer la valeur des wc car c’est un sous shell qui l’exécute. On passe par un fichier temporaire.

On stocke la liste des pid des grep de comptage car le shell courant qui est leur parent doit attendre qu’ils soient tous finis avant d’aller lire les resultats dans les fichiers temporaires.

wait $PID_LIST

cat fichier_temp1

cat fichier_temp2

….

Dans mon cas particulier je suis passé de 1h30 de traitement pour avoir les statistiques d’un jour donné à seulement 3 minutes !

Finitions

Pour être propre il faut terminer en supprimant les fichiers temporaires (de comptage et les tubes).

Pour pouvoir lancer plusieurs instance du script sur des dates differentes en parallèle, ii faut ajouter le pid du script dans les noms des fichiers temporaires ( tubes nommés et comptage).

Dans mon cas particulier il me fallait les statistiques de plusieurs jours: chaque jour ayant sa propre stat. On peut lancer le script manuellement pour chaque jour ou bien intérer sur les jours, ce qui donne a peu près ceci:

for jour in 1 2 3 4

do

mkfifo entree_proc_grep1

grep -e motif_acces < entree_proc_grep1 | wc -l > fichier_temp1` &

PID_LIST+=” $!”

…..

grep -e motif_date fichier.log | tee -a entre_proc_grep1 entree_proc_grep2 … > /dev/null

wait $PID_LIST

cat fichier_temp1 >> fichier_stat_$jour

echo “” >> fichier_stat_$jour

…..

rm entree_proc_grep1

….

done

On remarque encore que pour chaque jour en parcourt l’ensemble de la log d’accès. Or dès le premier passage on a deja passé sur toutes les dates possibles. Une seule passe doit suffire ! L’idée est que le grep sur le motif de la date ne prenne plus le fichier de log en entrée mais un tube nommé…

for jour in 1 2 3 4

do

mkfifo entree_proc_grep1_$jour

grep -e motif_acces < entree_proc_grep1_$jour | wc -l > fichier_temp1_$jour` &

PID_LIST+=” $!”

…..

mkfifo entree_proc_$jour

PROC_JOUR_LIST+=” entree_proc_$jour”

grep -e motif_date entree_proc_$jour| tee -a entre_proc_grep1_$jour entree_proc_grep2_$jour … > /dev/null &

PID_LIST+=” $!”

done

cat fichier_log | tee -a $PROC_JOUR_LIST > /dev/null

wait $PID_LIST

for jour in 1 2 3 4

do

cat fichier_temp1_$jour >> fichier_stat_$jour

echo “” >> fichier_stat_$jour

…..

rm entree_proc_grep1_$jour

….

rm entree_proc_$jour

done

C’est un peu plus compliqué car pour distinguer tous ces tubes nommés on doit ajouter le numero de jour à leur nom.

Eviter ainsi de reparcourir la log depuis le début pour chaque jour permet d’aller plus vite. Dans mon cas particulier pour les statistiques de 3 jours je suis passé de 2mn50 à 0mn45s et pour les statistiques de 14 journées je suis passé de 22mn à 5mn !

Moralité de l’histoire, l’algorithmie alliée à la ligne de commande et la puissance du shell ça fait des étincelles.

Du bonne usage de la commande LS

Sous Linux, le ls est sans doute une des commandes les plus utilisées. En effet une fois arrivé dans un répertoire, qui ne lance pas un ls pour visualiser son contenu ? Cette commande est un peu le coup d’oeil qu’on lance autour pour juger son environnement.

Usage du ls

La pluspart du temps l’utilisateur fait

ls

ou alors pour avoir plus d’information sur les fichiers

ls -l

Cela correspond à un usage normal de la commande ls qui est d’obtenir la liste du contenu d’un répertoire.

Détournement du ls

Malheureusement cela se gâte quand on est dans un répertoire avec de très nombreux fichiers et que l’on veut juste des information sur certains d’entre eux. On utilise alors un motif de complétion en * pour effectuer ce genre de commande:

ls -l toto*

On obtient ainsi la liste des fichiers (i.e.les informations sur) commençant par toto. L’utilisateur a alors l’impression que ls fait une recherche des fichiers commençant par toto. C’est faux. Pour s’en convaincre il suffit d’activer le débug du shell par

set -x

Cela permet de voir ce que le shell exécute, puis lancez la commande:

ls *

On constate que l’expansion de shell remplace l’étoile par tous les fichiers présents dans le répertoire avant d’evaluer la commande ls suivie des paramètres ainsi constitués. Donc a en fait lancé la commande :

ls fichier1 fichier2 fichier3

C’est l’expansion de shell qui fait la soit disant recherche et ls ne fait que donner des informations sur les fichiers trouvés. Comme on ne passe pas de commutateur tel que -l, on n’obtient ni plus ni mpoins confirmation que les fichiers existent bien (travail effectué par l’expansion de shell).

Mauvais usage du ls

De la se servir de ls pour tester l’existense de fichier, il n’y a qu’un pas qu’il ne faut pas franchir. En effet on vu que c’est l’expansion de shell qui fait le boulot. Cela conduit à un effet désagréable quand l’expansion de shell ramène beaucoup trop de fichiers si bien que le shell ne peut exécuter la commande ls avec tant d’argument. Sur un linux récent faire n ls * dans un répertoire avec 100000 fichiers dont le nom fait 20 caractères chacun, conduit à:

ls *
bash: /bin/ls: Liste d'arguments trop longue

Laissons à ls ce qui appartient à ls: donner des informations sur des fichiers (taille, droits etc.). Pour la recherche de fichier il y a find

find -name "toto*"

En mettant entre guillemet le motif du nom de fichier, on empêche l’expansion de shell. On garde une ligne de commande courte. find interprête le meta-caractère étoile pour faire la recherche.

Existence de fichier selon un motif

Si on veut juste vérifier l’existence de fichier ayant un certain motif, on peut faire sur un Linux récent:

find -name "toto*" -print -quit | wc -l

Cela permet de chercher en s’arrêtant sur le premier trouvé puis de compter le nombre de fichier trouvé: soit 0 soit 1.

Si findutils n’est pas au moins en version 4.2.3 sur votre système alors l’option -quit n’est pas disponible. Son absence aura pour effet de vous priver d’un arrêt de la commande au premier fichier trouvé…. et donc obligera de faire une recherche complète.

Si l’on veut récupérer l’information d’existence où non de fichier via un code retour, on pourra avantageusement utiliser grep combiné à un ls comme ceci

if (ls | grep "2014*" 1>/dev/null 2>&1) then echo "trouve"; else echo "pas trouvé"; fi

Statistique Dollar Universe ou Baby Perl

Je n’apprends de nouveau langage que si nécessaire…. ainsi il y a 4 ans j’ai appris le python qui fut un véritable plaisir ou bien le WLangage (avec moins de plaisir….). Hier je devais produire des statistiques d’exécution de batch lancés par l’ordonnanceur Dollar Universe (connu aussi sous le nom $U), un outil obsolète, propriétaire dont la fonction statistique est vraiment minimaliste.

En effet la commande obscure livrée avec $U permet d’avoir sous forme textuelle, l’état de chaque batch avec la date d’exécution, l’heure de début et l’heure de fin. Quid des traitements qui démarrent avant minuit et finissent après… on aurait aimé avoir la date de fin également, mais bon $U est un outils basique et payant, n’en demandons pas trop.

Afin d’agréger le résultats par batch et faire quelques statistiques (max, min, moyenne dans un premier temps), un petit programme Perl semblait tout indiqué. Voilà ce que cela donne après avoir lu 2 livres: Modern Perl et Impatient Perl , du baby perl tout craché! Le couteaux suisse de l’administrateur Unix/Linux ne dément pas sa réputation: il fait le boulot !

Le script suivant prend en entrée le résultat de la commande d’état des batchs de $U et recrache sur la sortie standard un CSV. Alternativement on peut lui passer en argument le fichier issu de $U et le fichier de sortie CSV.

#!/usr/bin/env perl
use warnings;
use DateTime::Format::Strptime;
use Text::CSV;

my %batch_stat = ();

sub get_input{
    my $num_arg =  $#ARGV + 1;
    unless ($num_arg==0) {
    open my  $input_fd , '<', $ARGV[0]
        or die "Impossible d'ouvrir le fichier $ARGV[0]";
    return $input_fd
    } 
    my $input = STDIN;
    return $input;
}

sub get_output {
  my $num_arg =  $#ARGV + 1;
    unless ($num_arg < 2) {
    open my  $output_fd , '>', $ARGV[1]
        or die "Impossible d'ouvrir le fichier $ARGV[1]";
    $output_fd -> autoflush(1);        
    return $output_fd;

    }
    my $output = STDOUT;
    return $output;
}

sub handle_line {
    my ($line) = @_;
    my $date_format = qr/\d{2}\/\d{2}\/\d{4}/;
    my $heure_format = qr/\d{4}/;
    if (  $line =~ /TERMINE\s*(?<date_exec>$date_format)\s(?<heure_debut>$heure_format)\s(?<heure_fin>$heure_format)/) {
    my @splitted = split(/\s+/,substr($line,19, length($line)));
    my $batch =  $splitted[0];
    my $date =  "$+{date_exec}";
    my $heure_debut =  "$+{heure_debut}";
    my $heure_fin =  "$+{heure_fin}";
    #print "$batch $date $heure_debut $heure_fin\n";

    my $analyseur = DateTime::Format::Strptime ->new (pattern => '%d/%m/%Y %H%M');
    my $dt_debut = $analyseur -> parse_datetime ($date .  ' ' .  $heure_debut);
    my $dt_fin = $analyseur -> parse_datetime ($date . ' ' .  $heure_fin);
    # la commande de stat de dollar univers ne précise pas la date de fin
    # mais uniquement l'heure de fin
    if ($dt_fin < $dt_debut){
        my $un_jour = DateTime::Duration -> new (days => 1);
        $dt_fin -> add_duration($un_jour);
    }
    my $duree = $dt_fin - $dt_debut;
    my $duree_minutes = $duree -> in_units('minutes');

    unless(exists($batch_stat{$batch})){
        #my %info = qw (min 0 max 0 moyenne 0 nb 0);
        $batch_stat{$batch}= {
        min => $duree_minutes,
        max => $duree_minutes,
        moyenne => 0,
        nb => 0        
        };
        #print "nouveau stat de batch cree \n";
    }
    my $current_nb = $batch_stat{$batch}{nb};
    $batch_stat{$batch}{nb} = $current_nb + 1;
        $batch_stat{$batch}{'min'} = $duree_minutes if $duree_minutes < $batch_stat{$batch}{'min'};
        $batch_stat{$batch}{'max'} = $duree_minutes if $duree_minutes > $batch_stat{$batch}{'max'};
    $batch_stat{$batch}{'moyenne'} = ( $batch_stat{$batch}{'moyenne'} * $current_nb + $duree_minutes ) / $batch_stat{$batch}{nb};

    }

}

my $real_input = get_input();

while (<$real_input>)
{
    handle_line($_);

}
#print Dumper \%batch_stat;

my $real_output = get_output();

my @entetes = ('batch','nb_exec','temp_min','temp_max','temp_moy');

$csv = Text::CSV -> new();
$csv -> print($real_output, \@entetes);
$real_output -> say();

foreach my $batch_name (keys(%batch_stat)) {
    my @column = ($batch_name,  $batch_stat{$batch_name}{nb}, $batch_stat{$batch_name}{min}, $batch_stat{$batch_name}{max}, $batch_stat{$batch_name}{moyenne} );
    $csv -> print($real_output, \@column);
    $real_output -> say();
}

Les exceptions Java

Les exceptions en Java constituent un mécanisme puissant de traitement des erreurs aussi bien dans une phase d’exécution normale de l’application que dans les phases de débogage. J’aime comparer Java et Pascal ou C. Dans le premier, tout événement anormal (utilisation d’un pointeur null ou un problème d’entrée/sortie par exemple) doit provoquer un arrêt du flot d’exécution… immédiatement ! Alors que dans le second, il peut se passer de nombreuses instructions entre l’origine d’un problème et sa manifestation, rendant le débogage plus difficile.

La perception des exceptions par le programmeur est malheureusement souvent mauvaise. Le programmeur ressent la gestion des exceptions comme une contrainte et s’en débarrasse en la masquant ou en la laissant remonter, par paresse. Cela entraîne un mauvais usage des exception et c’est bien la mauvaise gestion des exceptions en Java par le programmeur qui font dire à certain que java ne sait pas gérer les exceptions.

Pourtant il suffit de respecter quelques principes simples pour tirer le plus grand bénéfice de ce mécanisme de gestion d’erreur.

Une exception doit être rattrapée si on sait comment la traiter

Par traitement on entend une action spécialement prévue dans le cas de l’exception. Par exemple une IOException pourrait être traitée en réessayant un certain nombre de fois ou en demandant à l’utilisateur de changer un paramètre (chemin de fichier par exemple). Mais attention, rattraper une exception ne suffit pas à la traiter correctement. Il ne suffit pas d’encadrer une un appel pouvant lever une exception avec un try catch pour s’assurer d’une bonne gestion. En effet, considérons le code suivant:

instruction1();  // lève une exception
instruction2()

Il ne suffit pas de faire:

try {
  instruction1();
}
catch (Exception e){
  // on gère l'exception
}
instruction2();

Le code précédent assume que la gestion de l’exception doit permettre l’exécution de instruction2(), mais est-ce que la gestion de l’exception le fait réellement ? un simple log sera bien sûr insuffisante, ne vaudrait-il pas mieux ne pas exécuter instruction2() ? Ou bien mettre la 2e instruction dans le bloc try également ? La réponse dépasse le cadre de la gestion des exceptions pour entrer dans celui de la robustesse du code.

Si on ne sait pas comment réagir face à l’exception la solution la plus simple est de ne pas la rattraper.

Sinon on ne rattrape pas et on la déclare

Déclarer l’exception signifie que la méthode doit la porter dans sa signature. C’est le point le plus important. Déclarer l’exception permet de la remonter correctement. Le code appelant qui peut être la méthode main() devra alors gérer l’exception, ce qui nous garantit un traitement correcte de l’exception: au moins par un arrêt du flot d’exécution.

Trop d’exceptions à déclarer: on encapsule

Les programmeurs non habitués à la gestion des exceptions seront alors gênés de devoir déclarer de nombreuses exceptions en signature de méthode.  Il faut alors prévoir une hiérarchie d’exception applicative plus simples qui va permettre d’encapsuler des exceptions plus techniques afin de limiter le nombre d’exception à déclarer dans les méthodes. Le programme va alors devoir rattraper les exceptions (technique ou de base) afin de les encapsuler dans des exceptions applicatives.

Cas des conteneurs d’application

Arrêter le flot d’exécution voire même la JVM est possible quand le programme est seul au monde. Dans le cas d’application web ou de conteneur d’EJB, le code ne doit pas arrêter la JVM car il s’inscrit dans un serveur d’application: il n’est pas seul au monde. Les conteneurs (tel que tomcat) sont bien écrits et une application ne peut pas arrêter la JVM à cause d’une exception qui remonterait toute le pile d’appel. Il y aura un appel qui va rattraper l’exception et en faire quelque chose comme par exemple l’écrire sur la sortie de la JSP. Cela est souvent vu comme une mauvaise chose pour l’utilisateur mais doit être vue comme une bénédiction par le programmeur. C’est la solution minimale qui assure un débogage de l’application.

Empiler ou découpler ?

Un reproche fait aux piles d’exception de Java c’est sa verbosité. C’est un reproche qui peu s’entendre. En effet prenons le cas d’une application multi-tiers où une erreur d’accès à la base de donnée remonte jusqu’à la couche de présentation sous forme d’une pile d’appel qui dévoile toutes les couches de l’application: action de l’utilisateur, appel de service, appel à la couche de persistance et finalement une belle erreur JDBC avec un code obscure en provenance du SGBD. Vous connaissez ce scénario  ? Il arrive quand on laisse filer l’exception ou que l’on encapsule en faisant référence à l’exception original via un appel à :

public Exception(Throwable cause)

Cette façon de faire permet de conserver l’exception d’origine et ainsi de ne pas perdre la pile d’exécution au moment de la levé de l’exception d’origine. C’est la façon de faire la plus sûr et la plus simple. c’est de l’empilage d’exception. Si on ne souhaite pas remonter dans les couches supérieures (appelantes) la pile d’exécution car on considère que cela n’est pas de leur ressort, on peut découpler i.e simplement lever une exception à destination des couches supérieures sans passer l’exception qui en est la cause mais alors il faut tracer (loguer) la cause de manière complète i.e. loguer la pile d’exécution. Ainsi on résout de manière élégante le problème des piles d’erreurs trop verbeuses dans la log des couches supérieurs tout en gardant dans les log des couches inférieurs la trace complète des exceptions à des fin de debug. Cette technique sera particulièrement efficace quand on choisit de mettre en œuvre des log par couche d’abstraction: une log pour la couche de présentation, une log pour la couche métier et une log pour la couche de persistance… ce qui nous amène à l’épineux problème de la gestion des log et c’est une autre histoire.

 

Lambda expression en Java 8

Lambda calculus is a formal system for functio...
Lambda calculus is a formal system for function definition, function application and recursion introduced by Alonzo Church in the 1930s. (Photo credit: Wikipedia)

Java 8 qui est sur le point de sortir nous promet encore plus de programmation fonctionnelle.

Le parcours  du  tutoriel en ligne justifie cela par l’efficacité de l’approche. Les lambdas expressions permettent en effet de décrire des fonctions anonymes, sans nom et raccrochées à aucun objet. Sans les lambda expression le langage oblige alors à créer une classe (anonyme si nécessaire) et une méthode.  Inutile et superflu parait-il…

Supposons  que l’on veuille pouvoir passer un comportement à une méthode. Comme on n’avait pas de pointeur sur fonction en Java jusque là, on devait définir une interface puis l’implémenter et passer une instance implémentante à la méthode.

Une interface:

public interface Calculator {
        int function(int x); 
}

Une implémentation:

public class InterfaceImplementor implements Calculator {
    Override
    public int function(int x) {
        return 222;
    }
}

définition de la méthode paramétrable par une interface fonctionnelle:

 int calcul(Calculator c){
        return c.function(0);
  }

L’appel se fait alors par un code (encore du code) du genre:

calcul(new InterfaceImplementor());

On peut éviter de définir une classe en créant une instance d’une classe anonyme:

calcul(new Calculator(){

            @Override
            public int function(int x) {
               return 999;
            }

Le JDK 8 arrive pour éviter encore plus de code en proposant de définir une lambda expression c’est-à-dire une méthode anonyme sans classe. Pour pouvoir tester cette future nouvelle fonctionnalité il faut télécharger le JDK 8 (non finalisé) et Netbean 7.4 (non finalisé) pour pouvoir écrire:

calcul((x) -> 555);

Il y a là une petite complexité (pardon nouveauté) sous le clavier. Le compilateur autorise ici 2 raccourcis: pas d’interface, pas de méthode.  La simple définition d’une fonction qui répond au typage de la méthode de l’interface Calculator suffit pour que le compilateur câble tout correctement. On peut se dire que c’est une bonne chose mais là où ça se complique c’est que Java 8 offre la possibilité d’utiliser une méthode existante sans passer par la case refactoring pour faire rentrer la méthode existante dans le moule d’une interface. Ainsi on peut utiliser n’importe quelle méthode ayant la bonne signature fonctionnelle: dans notre exemple int -> int. L’appel ressemble alors à cela:

calcul(ecxistingObject::existingMethod);

Voici le code complet permettant de voir l’ensemble des évolutions du langage abordées dans ce billet:

Une interface pour la manière de faire “old school”:

package lambda;

public interface Calculator {
        int function(int x);   
}

et son implémentation:

package lambda;

public class InterfaceImplementor implements Calculator{
    @Override
    public int function(int x) {   
       return 222;
    }
}

Une classe existante dont on peut réutiliser une méthode sans avoir à implémenter l’interface vue plus haut:

package lambda;

public class ExistingClass {
 private int i = 777;

 public int uneFonction(int x) {
   return i;
 }
}

Et les différentes manières de passer une fonction à une méthode:

package lambda;

public class Lambda {
 /**
  * @param args the command line arguments
  */
 public static void main(String[] args) {
   // TODO code application logic here

   Lambda l = new Lambda();
   int i = 0;

   // old school, plain class creation

   i = l.calcul(new InterfaceImplementor());
   System.out.println(i);

   // old school, anomymous class

   i = l.calcul(new Calculator(){
           @Override
       public int function(int x) {
         return 999;
       }
     });
   System.out.println(i);

   // anonymous method, lambda expression

   i  = l.calcul((x) -> 555);
   System.out.println(i);

   // method reference !

   ExistingClass existingObject = new ExistingClass();
   i = l.calcul(existingObject::uneFonction);
   System.out.println(i);
 }

 int calcul(Calculator c){
   return c.function(0);
 }
}

Dont l’exécution donne:

run:
 222
 999
 555
 777
 BUILD SUCCESSFUL (total time: 1 second)

Code d’initialisation en Java

Coding
Coding (Photo credit: Omer van Kloeten)

La lecture du tutoriel Java mérite que l’on s’y attarde, que ce soit pour le débutant ou bien pour celui qui vise l’excellence. Certains aspects du langage fort intéressants y sont bien expliqués, comme le code d’initialisation. On le trouve il est vrai rarement dans le code d’application métier mais plutôt dans le code de framework ou d’application standalone qui doivent initialiser de la “plomberie” comme une connexion de base de donnée ou l’activation d’un service de nommage. Mais il parait qu’il n’y a rien de tel que la lecture du code d’un bon framework pour apprendre à faire du bon code alors attardons nous sur les mécanisme offert pas le langage en matière d’initialisation.

Dans le tutoriel on distingue 3 types de code d’initialisation:

  • le code d’initialisation static
  • le code d’initialisation d’instance
  • le code d’initialisation de variable

L’initialisation statique pourra être utilisée pour s’occuper de choses globales à l’application et l’initialisation d’instance pour des aspect liée à l’instance comme par exemple pour faire un suivi des instances en utilisant une référence faible (weak reference). Le tutoriel présente une exemple de code d’initialisation de variable par une méthode final arguant que le contraire peu poser des problèmes. Peut-être… Un petit bout de code permet de voir comment tout cela s’articule.

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package initinstancemenber;

/**
 *
 * @author thierry
 */
public class InitInstanceMenber {

    private int i = initMyIMember();

    {
        System.out.println("instance init code");
    }

    static {
        System.out.println("class init code");
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        InitInstanceMenber a = new InitInstanceMenber();
        System.out.println("i=" + a.i);
        a= new InitInstanceMenber(){
            @Override
            protected int initMyIMember(){
                System.out.println("overriden init member");
                return 1;
            }
        };
        System.out.println("i=" + a.i);
    }

    protected int initMyIMember(){
        System.out.println("init member");
        return 0;
    }
}

Dont voici l’exécution:

run:
class init code
init member
instance init code
i=0
overriden init member
instance init code
i=1
BUILD SUCCESSFUL (total time: 0 seconds)

Les étiquettes en Java

English: A snippet of code in the Java program...
English: A snippet of code in the Java programming language written to illustrate syntax highlighting of keywords. (Photo credit: Wikipedia)

Les premières tasses de thé glacé passées, le tutoriel Java arrive sur les instructions de contrôle (branchement et boucle).  A l’époque où je développais couramment en Java, je n’utilisait que très peu les instruction break ou continue dans les boucles. L’utilisation d’étiquette était pour ainsi dire absente du code que je produisais. Pourquoi ? Tout d’abord parce qu’on a appris que les étiquettes (label) étaient utilisés dans d’autre langage pour faire des sauts (goto) et que cela était contraire à la programmation structurée, que l’on devait avoir un point d’entrée et un point de sortie unique, que cela simplifiait ainsi le code etc, etc. Et puis sans doute parce que la complexités des algorithmes de l’informatique de gestion ne nécessitait pas l’optimisation du flot de contrôle pour éviter des tours de boucle superflus.

Mais bien sûr si la performance est un critère comme par exemple en informatique financière, alors il vaut mieux sortir d’une boucle dès qu’on a fini le travail. Est-ce pour autant sacrifier le beauté du code et sa maintenabilité ? Non, car les étiquettes en Java ne servent qu’à nommer les blocs de boucles et on ne peut pas les mettre ailleurs pour faire des sauts dans tous les sens. Le code reste donc structuré. L’extrait de code suivant montre un exemple d’utilisation d’étiquette:

        premierNiveau:
        for (int i=0;i<100;i++){
            deuxiemeNiveau:
            for (int j=0;j<i*100;j++){
                boolean mustBreak = false;
                boolean mustContinueOutsideLoop = false;
                // code that decide if we must break or continue
                if (mustBreak){
                    break deuxiemeNiveau;
                }
                if (mustContinueOutsideLoop){
                    continue premierNiveau;
                }
                // very long running code here
            }
        }

Liste de paramètres variable des méthodes en Java

Depuis le JDK 1.5 qui fut une petite révolution il y a presque 10 ans de cela, il est possible de déclarer des méthodes ayant une liste de paramètre de taille variable afin de pouvoir appeler la méthode avec 1, 2 ou n’importe quel nombre de paramètre. Cela rappelait la fonction printf du langage C.

C’est en effet bien pratique d’appeler une méthode en lui passant le nombre d’argument que l’on veut. Une exemple canonique serait une méthode qui renvoie la concaténation des chaînes en paramètre.

String concat(String... arg){
    String concatenation = "";
    // corps de la méthode
    return concatenation;
}

Que l’on pourra appeler en faisant

String s = null;
s = concat("ma"," ","chaine"," ", "de", " ", "caractères"); // "ma chaine de caractère"

Il s’agit néanmoins d’un sorte de goodie du compilateur pour nous éviter de passer un tableau car c’est comme ça que l’on faisait avant. On déclarait la méthode comme suit:

String concat(String[] arg){
    // corps de la méthode
}

Puis lors de l’appel on s’efforçait de passe un tableau avec le surcoût de création de celui-ci:

String s = contact(new String[]{"ma"," ","chaine"," ","de"," ","caractères"});

Bien vu. On notera la limitation suivante, le paramètre variable doit être le dernier paramètre de la signature. Donc il ne peut y en avoir qu’un seul. On ne peut pas par exemple utiliser les points de suspension pour définir une méthode avec en premier lieu une liste d’un type et en second lieu une liste d’un autre type.

void neCompilePas(Object... lo, String... listStr) // le compilateur n'en veut pas
void compileBien(Object o, String... listStr) // on a le droit

Séparateur underscore dans littéral numérique en Java 7

Dès le début de la  lecture du tutoriel de Java 7 on remarque une nouvelle notation: l’underscore (blanc souligné) que l’on peut utiliser comme séparateur de groupe (millier, millions etc) afin d’augmenter la lisibilité des nombres écrits en valeur littérale. Cela donne :

int i = 0b1111_111001;

Qui est plus lisible que:

int i = 0b1111111001;