Qu'entend-on par immuable?


400

Cela pourrait être la question la plus stupide jamais posée, mais je pense que c'est assez déroutant pour un débutant Java.

  1. Quelqu'un peut-il clarifier ce que l'on entend par immuable ?
  2. Pourquoi un Stringimmuable?
  3. Quels sont les avantages / inconvénients des objets immuables?
  4. Pourquoi devrait- StringBuilderon préférer un objet mutable tel que String et vice-versa?

Un bel exemple (en Java) sera vraiment apprécié.


73
Vous voyez, ce n'était pas une question aussi stupide. Heureux que vous ayez demandé!
DOK

2
Soit dit en passant, je ne pense pas que ce soit la question la plus stupide jamais :) Je pense que c'est un concept assez important à comprendre
Jason Coco

1
Quand vous avez dit StringBuilder, ne vouliez-vous pas dire la classe mutable StringBuffer? String et StringBuffer sont plus similaires en fonction que String et StringBuilder. StringBuffer est effectivement une chaîne mutable.
Derek Mahar,

3
Puis-je suggérer que nous ajoutions la balise "débutant" à cette question afin que les programmeurs débutants en Java puissent la trouver dans la recherche d'autres questions d'introduction?
Derek Mahar

Réponses:


268

Immuable signifie qu'une fois que le constructeur d'un objet a terminé son exécution, cette instance ne peut plus être modifiée.

C'est utile car cela signifie que vous pouvez passer des références à l'objet autour, sans vous soucier que quelqu'un d'autre va changer son contenu. Surtout lorsqu'il s'agit de simultanéité, il n'y a pas de problème de verrouillage avec des objets qui ne changent jamais

par exemple

class Foo
{
     private final String myvar;

     public Foo(final String initialValue)
     {
         this.myvar = initialValue;
     }

     public String getValue()
     {
         return this.myvar;
     }
}

Foon'a pas à craindre que l'appelant getValue()ne change le texte de la chaîne.

Si vous imaginez une classe similaire à Foo, mais avec un StringBuilderplutôt que Stringcomme un membre, vous pouvez voir qu'un appelant à getValue()pourrait modifier l' StringBuilderattribut d'une Fooinstance.

Méfiez-vous également des différents types d'immuabilité que vous pourriez trouver: Eric Lippert a écrit un article de blog à ce sujet. Fondamentalement, vous pouvez avoir des objets dont l'interface est immuable mais dans les coulisses de l'état privé mutable réel (et ne peut donc pas être partagé en toute sécurité entre les threads).


3
Je pense que vous devriez ajouter un constructeur à un argument pour attribuer une valeur au moins une fois. Le point du code actuel n'est pas clair car il n'y a pas de valeur à changer vraiment :).
Georgy Bolyuba

4
Vous devez rendre le champ en lecture seule. Cela rend tout à fait explicite que le champ est immuable. En ce moment, il est immuable par convention
JaredPar

7
Le membre myVar doit être définitif pour que cela soit vraiment immuable.
laz

13
Vous avez raison de dire que myVar n'est pas accessible en dehors de Foo. Cependant, la présence de final indique à quiconque pourrait modifier la classe à l'avenir que sa valeur n'est pas censée changer. J'ai tendance à être aussi explicite que possible dans de telles circonstances.
laz

2
Que diriez-vous "Les types de référence ne peuvent pas être rendus immuables simplement en utilisant le mot clé final. Final empêche uniquement la réaffectation." de en.wikipedia.org/wiki/Immutable_object
Yousha Aleayoub

81

Un objet immuable est un objet dans lequel les champs internes (ou du moins, tous les champs internes qui affectent son comportement externe) ne peuvent pas être modifiés.

Les chaînes immuables présentent de nombreux avantages:

Performances: effectuez l'opération suivante:

String substring = fullstring.substring(x,y);

Le C sous-jacent de la méthode substring () est probablement quelque chose comme ceci:

// Assume string is stored like this:
struct String { char* characters; unsigned int length; };

// Passing pointers because Java is pass-by-reference
struct String* substring(struct String* in, unsigned int begin, unsigned int end)
{
    struct String* out = malloc(sizeof(struct String));
    out->characters = in->characters + begin;
    out->length = end - begin;
    return out;
}

Notez qu'aucun des caractères ne doit être copié! Si l'objet String était modifiable (les caractères pourraient changer plus tard), vous devrez alors copier tous les caractères, sinon les modifications apportées aux caractères de la sous-chaîne seront reflétées ultérieurement dans l'autre chaîne.

Concurrence: si la structure interne d'un objet immuable est valide, elle le sera toujours. Il n'y a aucune chance que différents threads puissent créer un état non valide au sein de cet objet. Par conséquent, les objets immuables sont Thread Safe .

Collecte des ordures: il est beaucoup plus facile pour le garbage collector de prendre des décisions logiques concernant les objets immuables.

Cependant, il y a aussi des inconvénients à l'immuabilité:

Performance: Attendez, je pensais que vous aviez dit que la performance était un avantage de l'immuabilité! Eh bien, c'est parfois, mais pas toujours. Prenez le code suivant:

foo = foo.substring(0,4) + "a" + foo.substring(5);  // foo is a String
bar.replace(4,5,"a"); // bar is a StringBuilder

Les deux lignes remplacent toutes les deux le quatrième caractère par la lettre "a". Non seulement le deuxième morceau de code est plus lisible, mais il est plus rapide. Regardez comment vous devriez faire le code sous-jacent pour foo. Les sous-chaînes sont faciles, mais maintenant parce qu'il y a déjà un caractère à l'espace cinq et que quelque chose d'autre fait référence à foo, vous ne pouvez pas simplement le changer; vous devez copier la chaîne entière (bien sûr, certaines de ces fonctionnalités sont abstraites en fonctions dans le vrai C sous-jacent, mais le point ici est de montrer le code qui est exécuté tout en un seul endroit).

struct String* concatenate(struct String* first, struct String* second)
{
    struct String* new = malloc(sizeof(struct String));
    new->length = first->length + second->length;

    new->characters = malloc(new->length);

    int i;

    for(i = 0; i < first->length; i++)
        new->characters[i] = first->characters[i];

    for(; i - first->length < second->length; i++)
        new->characters[i] = second->characters[i - first->length];

    return new;
}

// The code that executes
struct String* astring;
char a = 'a';
astring->characters = &a;
astring->length = 1;
foo = concatenate(concatenate(slice(foo,0,4),astring),slice(foo,5,foo->length));

Notez que concaténer est appelé deux fois, ce qui signifie que la chaîne entière doit être bouclée! Comparez cela au code C de l' baropération:

bar->characters[4] = 'a';

L'opération de chaîne mutable est évidemment beaucoup plus rapide.

En conclusion: dans la plupart des cas, vous voulez une chaîne immuable. Mais si vous avez besoin de faire beaucoup d'ajout et d'insertion dans une chaîne, vous avez besoin de la mutabilité pour la vitesse. Si vous souhaitez bénéficier des avantages de la sécurité et de la récupération de place de la concurrence, la clé consiste à conserver vos objets mutables en local par rapport à une méthode:

// This will have awful performance if you don't use mutable strings
String join(String[] strings, String separator)
{
    StringBuilder mutable;
    boolean first = true;

    for(int i = 0; i < strings.length; i++)
    {
        if(!first) first = false;
        else mutable.append(separator);

        mutable.append(strings[i]);
    }

    return mutable.toString();
}

Étant donné que l' mutableobjet est une référence locale, vous n'avez pas à vous soucier de la sécurité des accès concurrents (un seul thread le touche). Et comme il n'est référencé nulle part ailleurs, il est uniquement alloué sur la pile, il est donc désalloué dès que l'appel de fonction est terminé (vous n'avez pas à vous soucier du garbage collection). Et vous bénéficiez de tous les avantages de performance de la mutabilité et de l'immuabilité.


4
Bonne lecture! juste une chose que je pense que ça devrait être si (d'abord) et pas si (! d'abord)
Siddhartha

Ce qui est nécessaire n'est pas que les champs soient immuables, mais plutôt que l'état observable défini de l'objet soit immuable; un objet qui contient une référence à un autre objet comme moyen d'encapsuler l'état qu'il contient ne peut être immuable que si tous les aspects encapsulés de l'état qu'il expose au monde extérieur sont également immuables. Notez qu'il n'est ni nécessaire ni suffisant que les champs soient de types immuables. Ce qui compte, c'est l'état visible.
supercat

7
Passing pointers because Java is pass-by-referenceLa java n'est-elle pas «pass-by-value»?
Cristian Gutu

@CristianGutu oui vous avez raison JAVA est "Pass by Value" et non "Pass by REFERENCE"
Arsh Kaushal

La référence est passée comme valeur !!
devv

31

En fait, String n'est pas immuable si vous utilisez la définition wikipedia suggérée ci-dessus.

L'état de la chaîne change la post-construction. Jetez un œil à la méthode hashcode (). La chaîne met en cache la valeur de hashcode dans un champ local mais ne la calcule qu'au premier appel de hashcode (). Cette évaluation paresseuse de hashcode place String dans une position intéressante en tant qu'objet immuable dont l'état change, mais il ne peut pas être observé comme ayant changé sans utiliser la réflexion.

Alors peut-être que la définition d'immuable devrait être un objet qui ne peut pas être observé comme ayant changé.

Si l'état change dans un objet immuable après sa création mais que personne ne peut le voir (sans réflexion), l'objet est-il toujours immuable?


1
Bonne idée - un objet qui ne peut pas être observé comme ayant changé, ainsi que, aucun moyen de le changer de l'extérieur. Le champ privé pour hashCode () est un changement interne qui n'est pas significatif pour l'état visible de l'extérieur de l'objet.
mparaz

2
En fait, on peut observer qu'il a changé si vous utilisez la réflexion. Voir plus à Sedgewick Les cordes sont mutables si vous autorisez la réflexion .
Miguel

24

Les objets immuables sont des objets qui ne peuvent pas être modifiés par programme. Ils sont particulièrement adaptés aux environnements multithreads ou à d'autres environnements où plusieurs processus sont capables de modifier (muter) les valeurs d'un objet.

Juste pour clarifier, cependant, StringBuilder est en fait un objet modifiable, pas immuable. Une chaîne Java régulière est immuable (ce qui signifie qu'une fois qu'elle a été créée, vous ne pouvez pas modifier la chaîne sous-jacente sans changer l'objet).

Par exemple, disons que j'ai une classe appelée ColoredString qui a une valeur String et une couleur String:

public class ColoredString {

    private String color;
    private String string;

    public ColoredString(String color, String string) {
        this.color  = color;
        this.string = string;
    }

    public String getColor()  { return this.color;  }
    public String getString() { return this.string; }

    public void setColor(String newColor) {
        this.color = newColor;
    }

}

Dans cet exemple, ColoredString est censé être mutable car vous pouvez modifier (muter) l'une de ses propriétés clés sans créer de nouvelle classe ColoredString. La raison pour laquelle cela peut être mauvais est, par exemple, supposons que vous avez une application GUI qui a plusieurs threads et que vous utilisez ColoredStrings pour imprimer des données dans la fenêtre. Si vous avez une instance de ColoredString qui a été créée en tant que

new ColoredString("Blue", "This is a blue string!");

Vous vous attendez alors à ce que la chaîne soit toujours "Bleue". Si un autre thread, cependant, s'est emparé de cette instance et a appelé

blueString.setColor("Red");

Vous auriez soudainement, et probablement de manière inattendue, maintenant une chaîne "Rouge" quand vous en vouliez une "Bleue". Pour cette raison, les objets immuables sont presque toujours préférés lors du passage d'instances d'objets autour. Lorsque vous avez un cas où des objets modifiables sont vraiment nécessaires, vous gardez généralement l'objet en ne faisant passer que des copies de votre champ de contrôle spécifique.

Pour récapituler, en Java, java.lang.String est un objet immuable (il ne peut pas être modifié une fois qu'il a été créé) et java.lang.StringBuilder est un objet modifiable car il peut être modifié sans créer de nouvelle instance.


Vous devez rendre les champs en lecture seule. À l'heure actuelle, votre classe est immuable par convention. Rien n'indique aux futurs développeurs que l'immuable est intentionnel. Rendre les champs en lecture seule aidera à clarifier votre intention de devenir un futur développeur
JaredPar

@JaredPar - En fait, la classe n'est pas immuable du tout ... c'est un exemple de classe mutable pour montrer pourquoi cela peut être un problème.
Jason Coco

1
@JaredPar - Oh, c'est tout à fait correct :) J'allais le réécrire un peu pour être plus clair, mais Douglas est déjà bien écrit et semble être le favori, donc je vais laisser le mien comme un autre exemple; mais quelqu'un l'a édité pour rendre les propriétés définitives, ce qui m'a semblé amusant :)
Jason Coco

24
  1. Dans les grandes applications, il est courant que les littéraux de chaîne occupent de gros bits de mémoire. Ainsi, pour gérer efficacement la mémoire, la JVM alloue une zone appelée "String constant pool". ( Notez qu'en mémoire, même une chaîne non référencée porte un char [], un int pour sa longueur et un autre pour son hashCode. Pour un certain nombre , en revanche, un maximum de huit octets immédiats est requis )
  2. Lorsque complier rencontre un littéral String, il vérifie le pool pour voir s'il existe déjà un littéral identique. Et si l'on en trouve, la référence au nouveau littéral est dirigée vers la chaîne existante, et aucun nouvel «objet littéral de chaîne» n'est créé (la chaîne existante obtient simplement une référence supplémentaire).
  3. Par conséquent: la mutabilité des chaînes économise de la mémoire ...
  4. Mais quand l'une des variables change de valeur, en fait - c'est seulement leur référence qui est modifiée, pas la valeur en mémoire (donc cela n'affectera pas les autres variables qui la référencent) comme vu ci-dessous ...

String s1 = "Old string";

//s1 variable, refers to string in memory
        reference                 |     MEMORY       |
        variables                 |                  |

           [s1]   --------------->|   "Old String"   |

Chaîne s2 = s1;

//s2 refers to same string as s1
                                  |                  |
           [s1]   --------------->|   "Old String"   |
           [s2]   ------------------------^

s1 = "Nouvelle chaîne";

//s1 deletes reference to old string and points to the newly created one
           [s1]   -----|--------->|   "New String"   |
                       |          |                  |
                       |~~~~~~~~~X|   "Old String"   |
           [s2]   ------------------------^

La chaîne d'origine «en mémoire» n'a pas changé, mais la variable de référence a été modifiée afin qu'elle fasse référence à la nouvelle chaîne. Et si nous n'avions pas s2, "Old String" serait toujours dans la mémoire mais nous ne pourrons pas y accéder ...


16

"immuable" signifie que vous ne pouvez pas modifier la valeur. Si vous avez une instance de la classe String, toute méthode que vous appelez qui semble modifier la valeur créera en fait une autre String.

String foo = "Hello";
foo.substring(3);
<-- foo here still has the same value "Hello"

Pour conserver les modifications, vous devriez faire quelque chose comme ceci foo = foo.sustring (3);

Immuable vs mutable peut être drôle lorsque vous travaillez avec des collections. Réfléchissez à ce qui se passera si vous utilisez un objet mutable comme clé pour la carte, puis modifiez la valeur (conseil: pensez à equalset hashCode).


13

java.time

Il peut être un peu tard, mais pour comprendre ce qu'est un objet immuable, considérons l'exemple suivant de la nouvelle API de date et d'heure Java 8 ( java.time ). Comme vous le savez probablement, tous les objets de date de Java 8 sont immuables, donc dans l'exemple suivant

LocalDate date = LocalDate.of(2014, 3, 18); 
date.plusYears(2);
System.out.println(date);

Production:

2014-03-18

Cela imprime la même année que la date initiale car le plusYears(2)retourne un nouvel objet, donc l'ancienne date est toujours inchangée car c'est un objet immuable. Une fois créé, vous ne pouvez plus le modifier et la variable de date pointe toujours dessus.

Ainsi, cet exemple de code doit capturer et utiliser le nouvel objet instancié et renvoyé par cet appel à plusYears.

LocalDate date = LocalDate.of(2014, 3, 18); 
LocalDate dateAfterTwoYears = date.plusYears(2);

date.toString ()… 2014-03-18

dateAfterTwoYears.toString ()… 2016-03-18


8

J'aime vraiment l'explication du guide d'étude SCJP Sun Certified Programmer for Java 5 .

Pour rendre Java plus efficace en mémoire, la JVM met de côté une zone spéciale de mémoire appelée "Pool constant de chaînes". Lorsque le compilateur rencontre un littéral String, il vérifie le pool pour voir si une chaîne identique existe déjà. Si une correspondance est trouvée, la référence au nouveau littéral est dirigée vers la chaîne existante et aucun nouvel objet littéral de chaîne n'est créé.


Il devrait pouvoir le faire avec n'importe quel objet immuable identique, mais je suppose que cela prendrait trop de temps d'exécution.
Zan Lynx,

8

Les objets qui sont immuables ne peuvent pas voir leur état changé après avoir été créés.

Il y a trois raisons principales d'utiliser des objets immuables chaque fois que vous le pouvez, ce qui contribuera à réduire le nombre de bogues que vous introduisez dans votre code:

  • Il est beaucoup plus facile de raisonner sur le fonctionnement de votre programme lorsque vous savez que l'état d'un objet ne peut pas être modifié par une autre méthode
  • Les objets immuables sont automatiquement thread-safe (en supposant qu'ils sont publiés en toute sécurité) donc ne seront jamais la cause de ces bugs multithreads difficiles à identifier
  • Les objets immuables auront toujours le même code de hachage, ils peuvent donc être utilisés comme clés dans un HashMap (ou similaire). Si le code de hachage d'un élément dans une table de hachage devait changer, l'entrée de table serait alors effectivement perdue, car les tentatives de le trouver dans la table finiraient par chercher au mauvais endroit. C'est la raison principale pour laquelle les objets String sont immuables - ils sont fréquemment utilisés comme clés HashMap.

Il existe également d'autres optimisations que vous pourriez être en mesure de faire dans le code lorsque vous savez que l'état d'un objet est immuable - la mise en cache du hachage calculé, par exemple - mais ce sont des optimisations et donc pas si intéressantes.


5

Une signification a à voir avec la façon dont la valeur est stockée dans l'ordinateur, pour une chaîne .Net par exemple, cela signifie que la chaîne en mémoire ne peut pas être modifiée, lorsque vous pensez que vous la changez, vous créez en fait une nouvelle chaîne en mémoire et en pointant la variable existante (qui n'est qu'un pointeur vers la collection réelle de caractères ailleurs) vers la nouvelle chaîne.


4
String s1="Hi";
String s2=s1;
s1="Bye";

System.out.println(s2); //Hi  (if String was mutable output would be: Bye)
System.out.println(s1); //Bye

s1="Hi": un objet a s1été créé avec une valeur "Hi".

s2=s1 : un objet s2est créé en référence à l'objet s1.

s1="Bye": la s1valeur de l'objet précédent ne change pas car s1a le type String et le type String est un type immuable, à la place le compilateur crée un nouvel objet String avec la valeur "Bye" et y fait s1référence. ici lorsque nous imprimons la s2valeur, le résultat sera "Hi" et non "Bye" car s2référencé à l' s1objet précédent qui avait une valeur "Hi".


pouvez-vous ajouter une petite explication s'il vous plaît?
minigeek

3

Immuable signifie qu'une fois l'objet créé, aucun de ses membres ne changera. Stringest immuable car vous ne pouvez pas modifier son contenu. Par exemple:

String s1 = "  abc  ";
String s2 = s1.trim();

Dans le code ci-dessus, la chaîne s1 n'a pas changé, un autre objet ( s2) a été créé à l'aide s1.


3

Immuable signifie simplement immuable ou non modifiable. Une fois l'objet chaîne créé, ses données ou son état ne peuvent pas être modifiés

Prenons l'exemple ci-dessous,

class Testimmutablestring{  
  public static void main(String args[]){  
    String s="Future";  
    s.concat(" World");//concat() method appends the string at the end  
    System.out.println(s);//will print Future because strings are immutable objects  
  }  
 }  

Prenons l'idée en considérant le diagramme ci-dessous,

entrez la description de l'image ici

Dans ce diagramme, vous pouvez voir un nouvel objet créé comme "Future World". Mais ne changez pas "Future". Because String is immutable. s, font toujours référence à "Future". Si vous devez appeler "Future World",

String s="Future";  
s=s.concat(" World");  
System.out.println(s);//print Future World

Pourquoi les objets chaîne sont-ils immuables en Java?

Parce que Java utilise le concept de chaîne littérale. Supposons qu'il existe 5 variables de référence, toutes se réfèrent à un objet "Futur". Si une variable de référence modifie la valeur de l'objet, elle sera affectée à toutes les variables de référence. C'est pourquoi les objets chaîne sont immuables en java.


2

Une fois instancié, ne peut être modifié. Considérez une classe dont une instance pourrait être utilisée comme clé pour une table de hachage ou similaire. Découvrez les meilleures pratiques Java.


0

Objets immuables

Un objet est considéré comme immuable si son état ne peut pas changer après sa construction. La confiance maximale dans les objets immuables est largement acceptée comme une stratégie solide pour créer un code simple et fiable.

Les objets immuables sont particulièrement utiles dans les applications simultanées. Puisqu'ils ne peuvent pas changer d'état, ils ne peuvent pas être corrompus par une interférence de thread ou observés dans un état incohérent.

Les programmeurs hésitent souvent à utiliser des objets immuables, car ils s'inquiètent du coût de création d'un nouvel objet par opposition à la mise à jour d'un objet en place. L'impact de la création d'objets est souvent surestimé et peut être compensé par certaines des efficacités associées aux objets immuables. Il s'agit notamment de la réduction des frais généraux en raison de la collecte des ordures et de l'élimination du code nécessaire pour protéger les objets mutables contre la corruption.

Les sous-sections suivantes prennent une classe dont les instances sont mutables et en dérivent une classe avec des instances immuables. Ce faisant, ils donnent des règles générales pour ce type de conversion et démontrent certains des avantages des objets immuables.

La source


0

Comme la réponse acceptée ne répond pas à toutes les questions. Je suis obligé de donner une réponse après 11 ans et 6 mois.

Quelqu'un peut-il clarifier ce que l'on entend par immuable?

J'espère que vous vouliez dire un objet immuable (car nous pourrions penser à une référence immuable ).

Un objet est immuable : si une fois créé, il représente toujours la même valeur (n'a pas de méthode pour changer la valeur).

Pourquoi un Stringimmuable?

Respectez la définition ci-dessus qui pourrait être vérifiée en consultant le code source de Sting.java .

Quels sont les avantages / inconvénients des objets immuables? les types immuables sont:

  • plus sûr contre les bugs.

  • plus facile à comprendre.

  • et plus prêt pour le changement.

Pourquoi un objet mutable tel que StringBuilder devrait-il être préféré à String et vice-versa?

Limiter la question Pourquoi avons-nous besoin du StringBuilder mutable dans la programmation? Une utilisation courante consiste à concaténer un grand nombre de chaînes ensemble, comme ceci:

String s = "";
for (int i = 0; i < n; ++i) {
    s = s + n;
}

En utilisant des chaînes immuables, cela fait beaucoup de copies temporaires - le premier numéro de la chaîne ("0") est en fait copié n fois au cours de la construction de la chaîne finale, le deuxième nombre est copié n-1 fois, et ainsi sur. En fait, il en coûte O (n2) juste pour faire toute cette copie, même si nous n'avons concaténé que n éléments.

StringBuilder est conçu pour minimiser cette copie. Il utilise une structure de données interne simple mais intelligente pour éviter toute copie jusqu'à la fin, lorsque vous demandez la chaîne finale avec un appel toString ():

StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; ++i) {
  sb.append(String.valueOf(n));
}
String s = sb.toString();

Obtenir de bonnes performances est l'une des raisons pour lesquelles nous utilisons des objets mutables. Un autre est le partage pratique: deux parties de votre programme peuvent communiquer plus facilement en partageant une structure de données mutable commune.

Plus d'informations peuvent être trouvées ici: https://web.mit.edu/6.005/www/fa15/classes/09-immutability/#useful_immutable_types


-1

Un objet immuable est celui que vous ne pouvez pas modifier après l'avoir créé. Un exemple typique sont les littéraux de chaîne.

Le langage de programmation AD, qui devient de plus en plus populaire, a une notion d '"immuabilité" à travers un mot-clé "invariant". Consultez cet article de Dr.Dobb à ce sujet - http://dobbscodetalk.com/index.php?option=com_myblog&show=Invariant-Strings.html&Itemid=29 . Cela explique parfaitement le problème.


Je crois qu'à partir de D 2.020 le mot-clé a été changé d'invariant en immuable. Je ne vois pas de point, mais il dit, "immuable maintenant est mis en œuvre." digitalmars.com/d/2.0/changelog.html#new2_020
he_the_great
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.