Quelle est la raison pour laquelle je ne peux pas créer de types de tableaux génériques en Java?


273

Quelle est la raison pour laquelle Java ne nous permet pas de faire

private T[] elements = new T[initialCapacity];

Je pouvais comprendre que .NET ne nous permettait pas de le faire, car dans .NET, vous avez des types de valeur qui, au moment de l'exécution, peuvent avoir des tailles différentes, mais en Java, toutes sortes de T seront des références d'objet, ayant ainsi la même taille ( Corrige moi si je me trompe).

Quelle est la raison?


29
Qu'est-ce que tu racontes? Vous pouvez absolument le faire dans .NET. - J'essaie ici de comprendre pourquoi je ne peux pas le faire en Java.
BrainSlugs83

@ BrainSlugs83 - veuillez ajouter un lien vers un exemple de code ou un tutoriel qui le montre.
MasterJoe2


1
@ MasterJoe2 le code ci-dessus dans la question de l'OP est ce à quoi je fais référence. Cela fonctionne bien en C #, mais pas en Java. - La question indique qu'il ne fonctionne dans aucun des deux, ce qui est incorrect. - Je ne suis pas sûr qu'il soit utile d'en discuter davantage.
BrainSlugs83

Réponses:


204

C'est parce que les tableaux de Java (contrairement aux génériques) contiennent, lors de l'exécution, des informations sur son type de composant. Vous devez donc connaître le type de composant lorsque vous créez le tableau. Comme vous ne savez pas ce qui Tse passe au moment de l'exécution, vous ne pouvez pas créer le tableau.


29
Mais qu'en est-il de l'effacement? Pourquoi cela ne s'applique-t-il pas?
Qix - MONICA A ÉTÉ MANQUÉ

14
Comment fait ArrayList <SomeType>-il alors?
Thumbz

10
@Thumbz: Tu veux dire new ArrayList<SomeType>()? Les types génériques ne contiennent pas le paramètre type lors de l'exécution. Le paramètre type n'est pas utilisé dans la création. Il n'y a aucune différence dans le code généré par new ArrayList<SomeType>()ou new ArrayList<String>()ou pas new ArrayList()du tout.
newacct

8
Je demandais plus sur la façon dont ArrayList<T>fonctionne avec son ' private T[] myArray. Quelque part dans le code, il doit avoir un tableau de type générique T, alors comment?
Thumbz

21
@Thumbz: Il n'a pas de tableau de type d'exécution T[]. Il a un tableau de type d'exécution Object[], et soit 1) le code source contient une variable de Object[](c'est comme ça dans la dernière source Oracle Java); ou 2) le code source contient une variable de type T[], qui est un mensonge, mais ne cause pas de problèmes en raison de Tson effacement à l'intérieur de la portée de la classe.
newacct

137

Citation:

Les tableaux de types génériques ne sont pas autorisés car ils ne sont pas sains. Le problème est dû à l'interaction des tableaux Java, qui ne sont pas statiquement solides mais vérifiés dynamiquement, avec des génériques, qui sont statiquement solides et non vérifiés dynamiquement. Voici comment vous pourriez exploiter l'échappatoire:

class Box<T> {
    final T x;
    Box(T x) {
        this.x = x;
    }
}

class Loophole {
    public static void main(String[] args) {
        Box<String>[] bsa = new Box<String>[3];
        Object[] oa = bsa;
        oa[0] = new Box<Integer>(3); // error not caught by array store check
        String s = bsa[0].x; // BOOM!
    }
}

Nous avions proposé de résoudre ce problème en utilisant des tableaux à sécurité statique (alias Variance) qui ont été rejetés pour Tiger.

- gafter

(Je crois que c'est Neal Gafter , mais je ne suis pas sûr)

Voir en contexte ici: http://forums.sun.com/thread.jspa?threadID=457033&forumID=316


3
Notez que j'en ai fait un CW puisque la réponse n'est pas la mienne.
Bart Kiers

10
Cela explique pourquoi il n'est peut-être pas sûr pour les types. Mais les problèmes de sécurité des types pourraient être avertis par le compilateur. Le fait est qu'il n'est même pas possible de le faire, pour presque la même raison pour laquelle vous ne pouvez pas le faire new T(). Chaque tableau en Java, de par sa conception, stocke le type de composant (c'est T.class-à- dire ) à l'intérieur; par conséquent, vous avez besoin de la classe de T au moment de l'exécution pour créer un tel tableau.
newacct

2
Vous pouvez toujours utiliser new Box<?>[n], ce qui peut parfois être suffisant, même si cela n'aiderait pas dans votre exemple.
Bartosz Klimek

1
@BartKiers Je ne comprends pas ... cela ne compile toujours pas (java-8): Box<String>[] bsa = new Box<String>[3];est-ce que quelque chose a changé en java-8 et plus je suppose?
Eugene

1
@Eugene, les tableaux de types génériques spécifiques ne sont tout simplement pas autorisés car ils peuvent entraîner une perte de sécurité du type, comme le montre l'exemple. Il n'est autorisé dans aucune version de Java. La réponse commence comme "les tableaux de types génériques ne sont pas autorisés car ils ne sont pas sains."
grenat

47

En omettant de fournir une solution décente, vous vous retrouvez avec quelque chose de pire à mon humble avis.

Le travail commun autour est le suivant.

T[] ts = new T[n];

est remplacé par (en supposant que T étend Object et non une autre classe)

T[] ts = (T[]) new Object[n];

Je préfère le premier exemple, mais plus de types académiques semblent préférer le second, ou préfèrent simplement ne pas y penser.

La plupart des exemples de pourquoi vous ne pouvez pas simplement utiliser un Object [] s'appliquent également à List ou Collection (qui sont pris en charge), donc je les considère comme des arguments très pauvres.

Remarque: c'est l'une des raisons pour lesquelles la bibliothèque de collections elle-même ne compile pas sans avertissements. Si vous ce cas d'utilisation ne peut pas être pris en charge sans avertissements, quelque chose est fondamentalement cassé avec le modèle générique IMHO.


6
Vous devez être prudent avec le second. Si vous renvoyez le tableau créé de cette manière à quelqu'un qui attend, par exemple, un String[](ou si vous le stockez dans un champ de type accessible au public T[], et que quelqu'un le récupère), alors il obtiendra une exception ClassCastException.
newacct

4
J'ai voté contre cette réponse parce que votre exemple préféré n'est pas autorisé en Java et votre deuxième exemple peut lever une exception ClassCastException
José Roberto Araújo Júnior

5
@ JoséRobertoAraújoJúnior Il est clair que le premier exemple doit être remplacé par le deuxième exemple. Il serait plus utile d'expliquer pourquoi le deuxième exemple peut lever une exception ClassCastException car cela ne serait pas évident pour tout le monde.
Peter Lawrey

3
@PeterLawrey J'ai créé une question auto-répondue montrant pourquoi T[] ts = (T[]) new Object[n];est une mauvaise idée: stackoverflow.com/questions/21577493/…
José Roberto Araújo Júnior

1
@MarkoTopolnik Je devrais recevoir une médaille pour avoir répondu à tous vos commentaires pour expliquer la même chose que j'ai déjà dit, la seule chose qui a changé par rapport à ma raison d'origine est que je pensais qu'il avait dit que T[] ts = new T[n];c'était un exemple valable. Je vais garder le vote car sa réponse peut causer des problèmes et des confusions à d'autres développeurs et est également hors sujet. Aussi, je vais arrêter de commenter à ce sujet.
José Roberto Araújo Júnior

38

Les tableaux sont covariants

Les tableaux sont censés être covariants, ce qui signifie essentiellement que, compte tenu des règles de sous-typage de Java, un tableau de type T[]peut contenir des éléments de type Tou tout sous-type de T. Par exemple

Number[] numbers = new Number[3];
numbers[0] = newInteger(10);
numbers[1] = newDouble(3.14);
numbers[2] = newByte(0);

Mais non seulement cela, les règles de sous-typage de Java indiquent également qu'un tableau S[]est un sous-type du tableau T[]si Sest un sous-type de T, par conséquent, quelque chose comme ceci est également valide:

Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;

Parce que selon les règles de sous-typage en Java, un tableau Integer[]est un sous-type d'un tableau Number[]car Integer est un sous-type de Number.

Mais cette règle de sous-typage peut conduire à une question intéressante: que se passerait-il si nous essayions de le faire?

myNumber[0] = 3.14; //attempt of heap pollution

Cette dernière ligne se compilerait très bien, mais si nous exécutons ce code, nous obtiendrions un ArrayStoreExceptioncar nous essayons de mettre un double dans un tableau d'entiers. Le fait que nous accédions au tableau via une référence numérique n'est pas pertinent ici, ce qui importe, c'est que le tableau est un tableau d'entiers.

Cela signifie que nous pouvons tromper le compilateur, mais nous ne pouvons pas tromper le système de type d'exécution. Et c'est parce que les tableaux sont ce que nous appelons un type réifiable. Cela signifie qu'au moment de l'exécution, Java sait que ce tableau a été réellement instancié comme un tableau d'entiers auquel il est simplement possible d'accéder via une référence de type Number[].

Donc, comme nous pouvons le voir, une chose est le type réel de l'objet, une autre chose est le type de référence que nous utilisons pour y accéder, non?

Le problème avec les génériques Java

Maintenant, le problème avec les types génériques en Java est que les informations de type pour les paramètres de type sont ignorées par le compilateur après la compilation du code; par conséquent, ces informations de type ne sont pas disponibles au moment de l'exécution. Ce processus est appelé effacement de type . Il existe de bonnes raisons pour implémenter des génériques comme celui-ci en Java, mais c'est une longue histoire, et cela a à voir avec la compatibilité binaire avec du code préexistant.

Le point important ici est que, car au moment de l'exécution, il n'y a pas d'informations de type, il n'y a aucun moyen de s'assurer que nous ne commettons pas de pollution de tas.

Considérons maintenant le code dangereux suivant:

List<Integer> myInts = newArrayList<Integer>();
myInts.add(1);
myInts.add(2);
List<Number> myNums = myInts; //compiler error
myNums.add(3.14); //heap polution

Si le compilateur Java ne nous empêche pas de le faire, le système de type d'exécution ne peut pas nous arrêter non plus, car il n'y a aucun moyen, au moment de l'exécution, de déterminer que cette liste était censée être uniquement une liste d'entiers. L'exécution Java nous permettrait de mettre tout ce que nous voulons dans cette liste, alors qu'elle ne devrait contenir que des entiers, car lorsqu'elle a été créée, elle a été déclarée comme une liste d'entiers. C'est pourquoi le compilateur rejette la ligne numéro 4 car elle n'est pas sûre et si elle est autorisée, elle pourrait casser les hypothèses du système de type.

En tant que tel, les concepteurs de Java se sont assurés que nous ne pouvons pas tromper le compilateur. Si nous ne pouvons pas tromper le compilateur (comme nous pouvons le faire avec les tableaux), nous ne pouvons pas non plus tromper le système de type d'exécution.

En tant que tels, nous disons que les types génériques ne sont pas réifiables, car au moment de l'exécution, nous ne pouvons pas déterminer la véritable nature du type générique.

J'ai ignoré certaines parties de ces réponses, vous pouvez lire l'article complet ici: https://dzone.com/articles/covariance-and-contravariance


32

La raison pour laquelle cela est impossible est que Java implémente ses génériques uniquement au niveau du compilateur et qu'un seul fichier de classe est généré pour chaque classe. C'est ce qu'on appelle l' effacement de type .

Au moment de l'exécution, la classe compilée doit gérer toutes ses utilisations avec le même bytecode. Donc, new T[capacity]n'aurait absolument aucune idée du type à instancier.


17

La réponse a déjà été donnée, mais si vous avez déjà une instance de T, vous pouvez le faire:

T t; //Assuming you already have this object instantiated or given by parameter.
int length;
T[] ts = (T[]) Array.newInstance(t.getClass(), length);

J'espère que je pourrais aider, Ferdi265


1
C'est une bonne solution. Mais cela obtiendra des avertissements non vérifiés (transtypés de l'objet en T []). Une autre solution « plus lent » , mais « sans avertissement » serait: T[] ts = t.clone(); for (int i=0; i<ts.length; i++) ts[i] = null;.
midnite

1
De plus, si ce que nous conservions est T[] t, ce serait le cas (T[]) Array.newInstance(t.getClass().getComponentType(), length);. j'ai passé quelques temps à comprendre getComponentType(). J'espère que cela aide les autres.
midnite

1
@midnite t.clone()ne reviendra pas T[]. Parce que tn'est pas Array dans cette réponse.
xmen

6

La raison principale est due au fait que les tableaux en Java sont covariants.

Il y a un bon aperçu ici .


Je ne vois pas comment vous pourriez supporter le "nouveau T [5]" même avec des tableaux invariants.
Dimitris Andreou

2
@DimitrisAndreou Eh bien, le tout est plutôt une comédie d'erreurs dans la conception Java. Tout a commencé avec la covariance des tableaux. Ensuite, une fois que vous avez covariance de tableau, vous pouvez lancer String[]à Objectet stocker un Integerdedans. Ils ont donc dû ajouter une vérification du type d'exécution pour les magasins de tableaux ( ArrayStoreException) car le problème ne pouvait pas être détecté au moment de la compilation. (Sinon, un Integerpourrait réellement être coincé dans un String[], et vous obtiendriez une erreur lorsque vous essayez de le récupérer, ce qui serait horrible.) ...
Radon Rosborough

2
@DimitrisAndreou… Ensuite, une fois que vous avez mis une vérification de l'exécution à la place d'une vérification au moment de la compilation plus sonore, vous rencontrez un effacement de type (également une faille de conception malheureuse - incluse uniquement pour la compatibilité descendante). L'effacement de type signifie que vous ne pouvez pas effectuer de vérification de type à l'exécution pour les types génériques. Par conséquent, pour éviter le problème du type de stockage de tableau, vous ne pouvez tout simplement pas avoir de tableaux génériques. S'ils avaient simplement rendu les tableaux invariants en premier lieu, nous pourrions simplement faire des vérifications de type au moment de la compilation sans manquer d'effacement.
Radon Rosborough

… Je viens de découvrir la période de montage de cinq minutes pour les commentaires. Objectaurait dû être Object[]dans mon premier commentaire.
Radon Rosborough

3

J'aime la réponse indirectement donnée par Gafter . Cependant, je propose que ce soit faux. J'ai un peu changé le code de Gafter. Il compile et il fonctionne pendant un certain temps, puis il bombarde là où Gafter avait prédit

class Box<T> {

    final T x;

    Box(T x) {
        this.x = x;
    }
}

class Loophole {

    public static <T> T[] array(final T... values) {
        return (values);
    }

    public static void main(String[] args) {

        Box<String> a = new Box("Hello");
        Box<String> b = new Box("World");
        Box<String> c = new Box("!!!!!!!!!!!");
        Box<String>[] bsa = array(a, b, c);
        System.out.println("I created an array of generics.");

        Object[] oa = bsa;
        oa[0] = new Box<Integer>(3);
        System.out.println("error not caught by array store check");

        try {
            String s = bsa[0].x;
        } catch (ClassCastException cause) {
            System.out.println("BOOM!");
            cause.printStackTrace();
        }
    }
}

La sortie est

I created an array of generics.
error not caught by array store check
BOOM!
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    at Loophole.main(Box.java:26)

Il me semble donc que vous pouvez créer des types de tableaux génériques en java. Ai-je mal compris la question?


Votre exemple est différent de ce que j'ai demandé. Ce que vous avez décrit, ce sont les dangers de la covariance des tableaux. Découvrez-le (pour .NET: blogs.msdn.com/b/ericlippert/archive/2007/10/17/… )
dévoré elysium

J'espère que vous obtenez un avertissement de sécurité de type du compilateur, oui?
Matt McHenry

1
Oui, je reçois un avertissement de sécurité de type. Oui, je vois que mon exemple ne répond pas à la question.
emory

En fait, vous obtenez plusieurs avertissements en raison d'une initialisation bâclée de a, b, c. En outre, cela est bien connu et affecte la bibliothèque principale, par exemple <T> java.util.Arrays.asList (T ...). Si vous passez un type non réifiable pour T, vous obtenez un avertissement (car le tableau créé a un type moins précis que le code ne le prétend), et c'est super moche. Il serait préférable que l'auteur de cette méthode reçoive l'avertissement, au lieu de l'envoyer sur le site d'utilisation, étant donné que la méthode elle-même est sûre, elle n'expose pas le tableau à l'utilisateur.
Dimitris Andreou

1
Vous n'avez pas créé de tableau générique ici. Le compilateur a créé un tableau (non générique) pour vous.
newacct

2

Je sais que je suis un peu en retard à la fête ici, mais je me suis dit que je pourrais peut-être aider les futurs googleurs car aucune de ces réponses n'a résolu mon problème. La réponse de Ferdi265 a énormément aidé.

J'essaie de créer ma propre liste liée, donc le code suivant est ce qui a fonctionné pour moi:

package myList;
import java.lang.reflect.Array;

public class MyList<TYPE>  {

    private Node<TYPE> header = null;

    public void clear() {   header = null;  }

    public void add(TYPE t) {   header = new Node<TYPE>(t,header);    }

    public TYPE get(int position) {  return getNode(position).getObject();  }

    @SuppressWarnings("unchecked")
    public TYPE[] toArray() {       
        TYPE[] result = (TYPE[])Array.newInstance(header.getObject().getClass(),size());        
        for(int i=0 ; i<size() ; i++)   result[i] = get(i); 
        return result;
    }


    public int size(){
         int i = 0;   
         Node<TYPE> current = header;
         while(current != null) {   
           current = current.getNext();
           i++;
        }
        return i;
    }  

Dans la méthode toArray () se trouve la façon de créer un tableau d'un type générique pour moi:

TYPE[] result = (TYPE[])Array.newInstance(header.getObject().getClass(),size());    

2

Dans mon cas, je voulais simplement un tableau de piles, quelque chose comme ceci:

Stack<SomeType>[] stacks = new Stack<SomeType>[2];

Comme cela n'était pas possible, j'ai utilisé ce qui suit comme solution:

  1. Création d'une classe de wrapper non générique autour de Stack (disons MyStack)
  2. MyStack [] stacks = le nouveau MyStack [2] fonctionnait parfaitement bien

Moche, mais Java est content.

Remarque: comme mentionné par BrainSlugs83 dans le commentaire de la question, il est totalement possible d'avoir des tableaux de génériques dans .NET


2

À partir du didacticiel Oracle :

Vous ne pouvez pas créer de tableaux de types paramétrés. Par exemple, le code suivant ne compile pas:

List<Integer>[] arrayOfLists = new List<Integer>[2];  // compile-time error

Le code suivant illustre ce qui se produit lorsque différents types sont insérés dans un tableau:

Object[] strings = new String[2];
strings[0] = "hi";   // OK
strings[1] = 100;    // An ArrayStoreException is thrown.

Si vous essayez la même chose avec une liste générique, il y aurait un problème:

Object[] stringLists = new List<String>[];  // compiler error, but pretend it's allowed
stringLists[0] = new ArrayList<String>();   // OK
stringLists[1] = new ArrayList<Integer>();  // An ArrayStoreException should be thrown,
                                            // but the runtime can't detect it.

Si des tableaux de listes paramétrées étaient autorisés, le code précédent ne parviendrait pas à lever l'exception ArrayStoreException souhaitée.

Pour moi, cela semble très faible. Je pense que quiconque ayant une compréhension suffisante des génériques serait parfaitement bien, et même s'attendrait à ce que l'arrayStoredException ne soit pas levée dans ce cas.


0

Il doit sûrement y avoir un bon moyen de contourner cela (peut-être en utilisant la réflexion), car il me semble que c'est exactement ce qui ArrayList.toArray(T[] a)fait. Je cite:

public <T> T[] toArray(T[] a)

Renvoie un tableau contenant tous les éléments de cette liste dans le bon ordre; le type d'exécution du tableau renvoyé est celui du tableau spécifié. Si la liste tient dans le tableau spécifié, elle y est renvoyée. Sinon, un nouveau tableau est alloué avec le type d'exécution du tableau spécifié et la taille de cette liste.

Donc, une solution consiste à utiliser cette fonction, c'est-à-dire à créer un ArrayListdes objets que vous souhaitez dans le tableau, puis à utiliser toArray(T[] a)pour créer le tableau réel. Ce ne serait pas rapide, mais vous n'avez pas mentionné vos besoins.

Alors, quelqu'un sait comment toArray(T[] a)est mis en œuvre?


3
List.toArray (T []) fonctionne parce que vous lui donnez essentiellement le type de composant T lors de l'exécution (vous lui donnez une instance du type de tableau souhaité, à partir de laquelle il peut obtenir la classe de tableau, puis la classe de composant T ). Avec le type de composant réel lors de l'exécution, vous pouvez toujours créer un tableau de ce type d'exécution à l'aide de Array.newInstance(). Vous trouverez cela mentionné dans de nombreuses questions qui demandent comment créer un tableau avec un type inconnu au moment de la compilation. Mais l'OP demandait spécifiquement pourquoi vous ne pouvez pas utiliser la new T[]syntaxe, ce qui est une question différente
newacct

0

C'est parce que les génériques ont été ajoutés à java après leur création, donc c'est un peu maladroit parce que les fabricants originaux de java pensaient que lors de la création d'un tableau, le type serait spécifié lors de sa création. Donc, cela ne fonctionne pas avec les génériques, vous devez donc faire E [] array = (E []) new Object [15]; Ceci compile mais il donne un avertissement.


0

Si nous ne pouvons pas instancier des tableaux génériques, pourquoi le langage a-t-il des types de tableaux génériques? Quel est l'intérêt d'avoir un type sans objet?

La seule raison pour laquelle je peux penser, c'est varargs - foo(T...). Sinon, ils auraient pu complètement nettoyer les types de tableaux génériques. (Eh bien, ils n'avaient pas vraiment besoin d'utiliser un tableau pour les varargs, car les varargs n'existaient pas avant la 1.5 . C'est probablement une autre erreur.)

C'est donc un mensonge, vous pouvez instancier des tableaux génériques, via varargs!

Bien sûr, les problèmes avec les tableaux génériques sont toujours réels, par exemple

static <T> T[] foo(T... args){
    return args;
}
static <T> T[] foo2(T a1, T a2){
    return foo(a1, a2);
}

public static void main(String[] args){
    String[] x2 = foo2("a", "b"); // heap pollution!
}

Nous pouvons utiliser cet exemple pour réellement démontrer le danger d' un tableau générique .

D'un autre côté, nous utilisons des varargs génériques depuis une décennie, et le ciel ne tombe pas encore. Nous pouvons donc affirmer que les problèmes sont exagérés; ce n'est pas grave. Si la création explicite de tableaux génériques est autorisée, nous aurons des bugs ici et là; mais nous avons été habitués aux problèmes d'effacement, et nous pouvons vivre avec.

Et nous pouvons pointer du foo2doigt pour réfuter l'affirmation selon laquelle la spécification nous empêche des problèmes dont ils prétendent nous empêcher. Si Sun avait eu plus de temps et de ressources pour 1.5 , je pense qu'ils auraient pu atteindre une résolution plus satisfaisante.


0

Comme d'autres l'ont déjà mentionné, vous pouvez bien sûr créer via quelques astuces .

Mais ce n'est pas recommandé.

Parce que l' effacement de type et, plus important encore, le covariancetableau in qui autorise simplement un tableau de sous-types peuvent être attribués à un tableau de supertype, ce qui vous oblige à utiliser un transtypage de type explicite lorsque vous essayez de récupérer la valeur, ce qui provoque l'exécution, ClassCastExceptionce qui est l'un des principaux objectifs que les génériques essaient d'éliminer: des contrôles de type plus forts au moment de la compilation .

Object[] stringArray = { "hi", "me" };
stringArray[1] = 1;
String aString = (String) stringArray[1]; // boom! the TypeCastException

Un exemple plus direct peut être trouvé dans Effective Java: Item 25 .


covariance : un tableau de type S [] est un sous-type de T [] si S est un sous-type de T


0

Si la classe utilise comme type paramétré, elle peut déclarer un tableau de type T [], mais elle ne peut pas instancier directement un tel tableau. Au lieu de cela, une approche courante consiste à instancier un tableau de type Object [], puis à effectuer un transtypage réduit en type T [], comme illustré ci-dessous:

  public class Portfolio<T> {
  T[] data;
 public Portfolio(int capacity) {
   data = new T[capacity];                 // illegal; compiler error
   data = (T[]) new Object[capacity];      // legal, but compiler warning
 }
 public T get(int index) { return data[index]; }
 public void set(int index, T element) { data[index] = element; }
}

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.