JavaRush /Blog Java /Random-FR /Pause café #130. Comment travailler correctement avec les...

Pause café #130. Comment travailler correctement avec les tableaux Java - conseils d'Oracle

Publié dans le groupe Random-FR
Source : Oracle L'utilisation de tableaux peut inclure des expressions de réflexion, génériques et lambda. Je parlais récemment avec un collègue qui développe en C. La conversation a porté sur les tableaux et leur fonctionnement en Java par rapport au C. J'ai trouvé cela un peu étrange, étant donné que Java est considéré comme un langage de type C. En fait, ils présentent de nombreuses similitudes, mais il existe également des différences. Commençons simplement. Pause café #130.  Comment travailler correctement avec les tableaux Java - conseils d'Oracle - 1

Déclaration de tableau

Si vous suivez le tutoriel Java, vous verrez qu'il existe deux manières de déclarer un tableau. La première est simple :

int[] array; // a Java array declaration
Vous pouvez voir en quoi cela diffère du C, où la syntaxe est :

int array[]; // a C array declaration
Revenons à Java. Après avoir déclaré un tableau, vous devez l'allouer :

array = new int[10]; // Java array allocation
Est-il possible de déclarer et d’initialiser un tableau à la fois ? En fait non:

int[10] array; // NOPE, ERROR!
Cependant, vous pouvez déclarer et initialiser le tableau immédiatement si vous connaissez déjà les valeurs :

int[] array = { 0, 1, 1, 2, 3, 5, 8 };
Et si vous ne connaissez pas la signification ? Voici le code que vous verrez le plus souvent pour déclarer, allouer et utiliser un tableau int :

int[] array;
array = new int[10];
array[0] = 0;
array[1] = 1;
array[2] = 1;
array[3] = 2;
array[4] = 3;
array[5] = 5;
array[6] = 8;
...
Notez que j'ai spécifié un tableau int , qui est un tableau de types de données primitifs Java . Voyons ce qui se passe si vous essayez le même processus avec un tableau d'objets Java au lieu de primitives :

class SomeClass {
    int val;
    // …
}
SomeClass[] array = new SomeClass[10];
array[0].val = 0;
array[1].val = 1;
array[2].val = 1;
array[3].val = 2;
array[4].val = 3;
array[5].val = 5;
array[6].val = 8;
Si nous exécutons le code ci-dessus, nous obtiendrons une exception immédiatement après avoir essayé d'utiliser le premier élément du tableau. Pourquoi? Même si le tableau est alloué, chaque segment du tableau contient des références d'objet vides. Si vous entrez ce code dans votre IDE, il remplira même automatiquement le .val pour vous, l'erreur peut donc prêter à confusion. Pour corriger le bug, procédez comme suit :

SomeClass[] array = new SomeClass[10];
for ( int i = 0; i < array.length; i++ ) {  //new code
    array[i] = new SomeClass();             //new code
}                                           //new code
array[0].val = 0;
array[1].val = 1;
array[2].val = 1;
array[3].val = 2;
array[4].val = 3;
array[5].val = 5;
array[6].val = 8;
Mais ce n'est pas élégant. Je me demandais pourquoi je ne pouvais pas facilement allouer un tableau et les objets qu'il contenait avec moins de code, peut-être même sur une seule ligne. Pour trouver la réponse, j'ai mené plusieurs expériences.

Trouver le nirvana parmi les tableaux Java

Notre objectif est de coder avec élégance. En suivant les règles du « code propre », j'ai décidé de créer du code réutilisable pour nettoyer le modèle d'allocation des tableaux. Voici le premier essai :

public class MyArray {
    
    public static Object[] toArray(Class cls, int size) 
      throws Exception {
        Constructor ctor = cls.getConstructors()[0];
        Object[] objects = new Object[size];
        for ( int i = 0; i < size; i++ ) {
            objects[i] = ctor.newInstance();
        }
        
        return objects;
    }

    public static void main(String[] args) throws Exception {
        SomeClass[] array1 = (SomeClass[])MyArray.toArray(SomeClass.class, 32); // see this
        System.out.println(array1);
    }
}
La ligne de code marquée « voir ceci » ressemble exactement à ce que je voulais, grâce à l' implémentation de toArray . Cette approche utilise la réflexion pour trouver le constructeur par défaut de la classe fournie, puis appelle ce constructeur pour instancier un objet de cette classe. Le processus appelle le constructeur une fois pour chaque élément du tableau. Fabuleux! C'est juste dommage que ça ne marche pas. Le code se compile correctement, mais génère une erreur ClassCastException lors de son exécution. Pour utiliser ce code, vous devez créer un tableau d'éléments Object , puis convertir chaque élément du tableau en une classe SomeClass comme ceci :

Object[] objects = MyArray.toArray(SomeClass.class, 32);
SomeClass scObj = (SomeClass)objects[0];
...
Ce n'est pas élégant ! Après plus d'expérimentations, j'ai développé plusieurs solutions utilisant la réflexion, des génériques et des expressions lambda.

Solution 1 : utiliser la réflexion

Ici, nous utilisons la classe java.lang.reflect.Array pour instancier un tableau de la classe que vous spécifiez au lieu d'utiliser la classe de base java.lang.Object . Il s'agit essentiellement d'un changement de code sur une seule ligne :

public static Object[] toArray(Class cls, int size) throws Exception {
    Constructor ctor = cls.getConstructors()[0];
    Object array = Array.newInstance(cls, size);  // new code
    for ( int i = 0; i < size; i++ ) {
        Array.set(array, i, ctor.newInstance());  // new code
    }
    return (Object[])array;
}
Vous pouvez utiliser cette approche pour obtenir un tableau de la classe souhaitée, puis travailler avec comme ceci :

SomeClass[] array1 = (SomeClass[])MyArray.toArray(SomeClass.class, 32);
Bien qu'il ne s'agisse pas d'un changement obligatoire, la deuxième ligne a été modifiée pour utiliser la classe de réflexion Array pour définir le contenu de chaque élément du tableau. Ceci est incroyable! Mais il y a encore un détail qui ne semble pas tout à fait correct : le casting vers SomeClass[] n'a pas l'air très joli. Heureusement, il existe une solution avec les génériques.

Solution 2 : utiliser des génériques

Le framework Collections utilise des génériques pour la liaison de type et élimine les transtypages dans bon nombre de ses opérations. Les génériques peuvent également être utilisés ici. Prenons java.util.List par exemple .

List list = new ArrayList();
list.add( new SomeClass() );
SomeClass sc = list.get(0); // Error, needs a cast unless...
La troisième ligne de l'extrait ci-dessus générera une erreur à moins que vous ne mettiez à jour la première ligne comme ceci :

List<SomeClass> = new ArrayList();
Vous pouvez obtenir le même résultat en utilisant des génériques dans la classe MyArray . Voici la nouvelle version :

public class MyArray<E> {
    public <E> E[] toArray(Class cls, int size) throws Exception {
        E[] array = (E[])Array.newInstance(cls, size);
        Constructor ctor = cls.getConstructors()[0];
        for ( int element = 0; element < array.length; element++ ) {
            Array.set(array, element, ctor.newInstance());
        }
        return arrayOfGenericType;
    }
}
// ...
MyArray<SomeClass> a1 = new MyArray(SomeClass.class, 32);
SomeClass[] array1 = a1.toArray();
Ça à l'air bon. En utilisant des génériques et en incluant le type cible dans la déclaration, le type peut être déduit dans d'autres opérations. De plus, ce code peut être réduit à une seule ligne en procédant ainsi :

SomeClass[] array = new MyArray<SomeClass>(SomeClass.class, 32).toArray();
Mission accomplie, non ? Eh bien, pas tout à fait. C'est très bien si vous ne vous souciez pas du constructeur de classe que vous appelez, mais si vous souhaitez appeler un constructeur spécifique, cette solution ne fonctionne pas. Vous pouvez continuer à utiliser la réflexion pour résoudre ce problème, mais le code deviendra alors complexe. Heureusement, il existe des expressions lambda qui offrent une autre solution.

Solution 3 : utiliser des expressions lambda

J'avoue que je n'étais pas particulièrement enthousiasmé par les expressions lambda auparavant, mais j'ai appris à les apprécier. En particulier, j'ai aimé l' interface java.util.stream.Stream , qui gère les collections d'objets. Stream m'a aidé à atteindre le nirvana des tableaux Java. Voici ma première tentative d'utilisation de lambdas :

SomeClass[] array = 
    Stream.generate(() -> new SomeClass())
    .toArray(SomeClass[]::new);
J'ai divisé ce code en trois lignes pour une lecture plus facile. Vous pouvez voir qu'il coche toutes les cases : il est simple et élégant, crée un tableau rempli d'instances d'objet et vous permet d'appeler un constructeur spécifique. Faites attention au paramètre de la méthode toArray : SomeClass[]::new . Il s'agit d'une fonction génératrice utilisée pour allouer un tableau du type spécifié. Cependant, en l’état, ce code présente un petit problème : il crée un tableau de taille infinie. Ce n'est pas très optimal. Mais le problème peut être résolu en appelant la méthode limit :

SomeClass[] array = 
    Stream.generate(() -> new SomeClass())
    .limit(32)   // calling the limit method
    .toArray(SomeClass[]::new);
Le tableau est désormais limité à 32 éléments. Vous pouvez même définir des valeurs d'objet spécifiques pour chaque élément du tableau, comme indiqué ci-dessous :

SomeClass[] array = Stream.generate(() -> {
    SomeClass result = new SomeClass();
    result.val = 16;
    return result;
    })
    .limit(32)
    .toArray(SomeClass[]::new);
Ce code démontre la puissance des expressions lambda, mais le code n'est ni soigné ni compact. À mon avis, appeler un autre constructeur pour définir la valeur serait bien mieux.

SomeClass[] array6 = Stream.generate( () -> new SomeClass(16) )
    .limit(32)
    .toArray(SomeClass[]::new);
J'aime la solution basée sur l'expression lambda. C'est idéal lorsque vous devez appeler un constructeur spécifique ou travailler avec chaque élément d'un tableau. Lorsque j'ai besoin de quelque chose de plus simple, j'utilise généralement une solution basée sur les génériques car c'est plus simple. Cependant, vous pouvez constater par vous-même que les expressions lambda offrent une solution élégante et flexible.

Conclusion

Aujourd'hui, nous avons appris à déclarer et à allouer des tableaux de primitives, à allouer des tableaux d' éléments Object , à utiliser la réflexion, les génériques et les expressions lambda en Java.
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION