Object[] o = "a;b;c".split(";");
o[0] = 42;
jette
Java.lang.ArrayStoreException: Java.lang.Integer
tandis que
String[] s = "a;b;c".split(";");
Object[] o = new Object[s.length];
for (int i = 0; i < s.length; i++) {
o[i] = s[i];
}
o[0] = 42;
non.
Existe-t-il un autre moyen de gérer cette exception sans créer de tableau temporaire String[]
?
En Java un tableau est aussi un objet.
Vous pouvez mettre un objet de sous-type dans une variable de supertype. Par exemple, vous pouvez placer un objet String
dans une variable Object
.
Malheureusement, la définition du tableau dans Java est en quelque sorte cassée. String[]
est considéré comme un sous-type de Object[]
, mais c'est faux! Pour une explication plus détaillée, lisez "covariance et contravariance", mais l'essentiel c'est ceci: Un type ne devrait être considéré comme un sous-type d'un autre type que si le sous-type remplit toutes les obligations du supertype. Cela signifie que si vous obtenez un objet de sous-type au lieu d'un objet de supertype, vous ne devez pas vous attendre à un comportement contraire au contrat de supertype.
Le problème est que String[]
ne prend en charge qu'une partie de Object[]
Contrat. Par exemple, vous pouvez lireObject
les valeurs de Object[]
. Et vous pouvez également lireObject
valeurs (qui se trouvent être des objets String
) à partir de String[]
. Jusqu'ici tout va bien. Le problème est avec l'autre partie du contrat. Vous pouvez mettre n'importe lequelObject
dans Object[]
. Mais vous ne pouvez pas mettre anyObject
dans String[]
. Donc, String[]
ne doit pas être considéré comme un sous-type de Object[]
, mais Java le dit. Et nous avons donc des conséquences comme celle-ci.
(Notez qu'une situation similaire est apparue à nouveau avec les classes génériques, mais cette fois, elle a été résolue correctement. List<String>
est pas un sous-type de List<Object>
; et si vous voulez avoir un supertype commun pour ceux-ci, vous avez besoin de List<?>
, qui est en lecture seule. C'est ainsi que cela devrait être aussi avec les tableaux; mais ce n'est pas. Et à cause de la rétrocompatibilité, il est trop tard pour la changer.)
Dans votre premier exemple, le String.split
la fonction crée un String[]
objet. Vous pouvez le mettre dans un Object[]
variable, mais l'objet reste String[]
. C'est pourquoi il rejette une valeur Integer
. Vous devez créer un nouveau Objects[]
array et copiez les valeurs. Vous pouvez utiliser le System.arraycopy
pour copier les données, mais vous ne pouvez pas éviter de créer le nouveau tableau.
Non, il n'y a aucun moyen d'éviter de copier le tableau renvoyé par split
.
Le tableau que split
renvoie est en fait un String[]
et Java vous permet d'assigner cela à une variable de type Object[]
. C'est encore vraiment un String[]
cependant, lorsque vous essayez de stocker autre chose qu'un String
, vous obtiendrez un ArrayStoreException
.
Pour des informations générales, voir 4.10.3. Sous-typage parmi les types de tablea dans la spécification de langage Java Java.
Ceci est le résultat de quelque chose d'une bonne affaire de la part de Java développeurs il y a de nombreuses lunes. Bien que cela puisse sembler étrange, cette fonctionnalité est importante pour de nombreuses méthodes, telles que Arrays.sort
(qui se trouve également être invoqué dans Collections.sort
). Fondamentalement, toute méthode qui prend un Object [] comme paramètre cesserait de fonctionner comme prévu si X [] (où X est une sous-classe d'Object) n'était pas considéré comme un sous-type. Il est possible que des tableaux aient pu être retravaillés de telle sorte que, dans certaines circonstances, ils étaient en lecture seule, par exemple, mais la question devient alors "quand?".
D'une part, la création de tableaux qui ont été passés dans une méthode en tant qu'arguments en lecture seule pourrait entraver la capacité du codeur à apporter des modifications in situ. D'un autre côté, faire une exception pour quand un tableau est passé en argument permettrait au codeur de faire des modifications illégales, telles que le stockage d'une chaîne lorsqu'un tableau entier est ce qui a été passé par l'appelant.
Mais le résultat de dire "Integer [] (par exemple) n'est pas un sous-type d'Object []" est une crise dans laquelle il faut créer une méthode distincte pour Object [] et Integer []. Par extension d'une telle logique, nous pouvons en outre dire qu'une méthode distincte doit être créée pour String [], Comparable [], etc. Chaque type de tableau nécessiterait une méthode distincte, même si ces méthodes étaient autrement exactement les mêmes.
C'est exactement le genre de situation pour laquelle nous avons un polymorphisme.
Permettre ici le polymorphisme permet malheureusement de tenter de stocker illégalement une valeur dans un tableau et un ArrayStoreException
est levé si une telle instance se produit. Cependant, c'est un petit prix à payer, et pas moins évitable qu'un ArrayIndexOutOfBoundsException
.
ArrayStoreException
peut être facilement évité dans la plupart des cas de deux manières (bien que vous ne puissiez pas contrôler ce que font les autres).
1)
N'essayez pas de stocker des objets dans un tableau sans savoir que c'est réel type de composant. Lorsque le tableau avec lequel vous travaillez a été passé dans la méthode, vous ne savez pas nécessairement d'où il vient, vous ne pouvez donc pas supposer qu'il est sûr sauf si la classe du type de composant est finale (c'est-à-dire pas de sous-classes).
Si le tableau est renvoyé par la méthode, comme dans la question ci-dessus, apprenez à connaître la méthode. Est-il possible que le type réel soit une sous-classe de type de retour? Si c'est le cas, vous devez en tenir compte.
2)
Lorsque vous initialisez pour la première fois un tableau qui fonctionne localement, utilisez le formulaire X[] blah = new X[...];
ou X[] blah = {...};
ou (à partir de Java 10) var blah = new X[...];
. Ensuite, toute tentative de stockage d'une valeur non X dans ce tableau entraînera une erreur de compilation. Ce que vous ne devriez pas dites est Y[] blah = new X[...];
, où X est une sous-classe de Y.
Si vous avez un tableau, comme dans la question ci-dessus, où vous souhaitez stocker des composants de type incorrect, alors comme d'autres l'ont suggéré, vous devez soit créer un nouveau tableau du type approprié et copier les informations dans ...
Object[] o = Arrays.copyOf(s, s.length, Object[].class); //someone demonstrate System.arrayCopy. I figure I show another way to skin cat. :p
o[0] = 42;
ou vous devez en quelque sorte convertir les composants que vous souhaitez stocker dans le type approprié.
s[0] = String.valueOf(42);
Notez que 42! = "42" donc en prenant une décision sur le chemin à prendre, vous devez considérer comment cela affectera le reste de votre code.
Je voudrais juste terminer sur une note concernant les génériques (comme indiqué dans une réponse précédente). Les génériques sont en fait tout aussi capables de surprendre le codeur sans méfiance. Considérez l'extrait de code suivant, (modifié à partir de ici ).
import Java.util.List;
import Java.util.ArrayList;
public class UhOh {
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
WildcardFixed.foo(list);
list.add(6);
System.out.println(list); // ¯\_(ツ)_/¯ oh well.
int i = list.get(0); //if we're going to discuss breaches of contract... :p
}
}
class WildcardFixed /*not anymore ;) */ {
static void foo(List<?> i) {
fooHelper(i);
}
private static <T> void fooHelper(List<T> l) {
l.add((T)Double.valueOf(2.5));
}
}
Génériques, mesdames et messieurs. : p
Il existe bien sûr d'autres options, comme celle d'implémenter votre propre méthode de fractionnement, qui renvoie directement un tableau d'objets. Je ne suis pas sûr que ce qui vous dérange réellement avec le tableau String temporaire?
BTW, vous pouvez raccourcir votre code avec quelques lignes en utilisant System.arrayCopy au lieu d'implémenter votre propre boucle pour copier les éléments du tableau:
System.arrayCopy(s, 0, o, 0, s.length);