web-dev-qa-db-fra.com

Pourquoi la classe String est-elle déclarée finale en Java?

A partir de quand j'ai appris que la classe Java.lang.String est déclaré final en Java, je me demandais pourquoi. À ce moment-là, je n'ai trouvé aucune réponse, mais cet article: Comment créer une réplique de la classe String en Java? m'a rappelé ma requête.

Bien sûr, String fournit toutes les fonctionnalités dont j'ai besoin, et je n'ai jamais pensé à une opération nécessitant une extension de la classe String, mais vous ne saurez toujours pas de quoi quelqu'un aura besoin!

Alors, est-ce que quelqu'un sait quelle était l'intention des concepteurs lorsqu'ils ont décidé de le rendre final?

133
Alex

Il est très utile d'implémenter les chaînes en tant que objets immuables . Vous devriez lire sur l'immuabilité pour en savoir plus.

Un des avantages des objets immuables est que

Vous pouvez partager des doublons en les dirigeant vers une seule instance.

(de ici ).

Si String n'était pas final, vous pouvez créer une sous-classe et avoir deux chaînes qui se ressemblent lorsqu'elles sont "vues comme des chaînes", mais qui sont en réalité différentes.

87
Bruno Reis

Ceci est un article de Nice qui expose deux raisons déjà mentionnées dans les réponses ci-dessus:

  1. Sécurité : le système peut transmettre des informations sensibles en lecture seule sans craindre qu'elles ne soient modifiées.
  2. Performance : des données immuables sont très utiles pour rendre les choses thread-safe.

Et ceci est probablement le commentaire le plus détaillé de cet article. Cela a à voir avec le pool de chaînes dans Java et les problèmes de sécurité. Il s'agit de savoir comment décider de ce qui va dans le pool de chaînes. En supposant que les deux chaînes sont égales si leur séquence de caractères est la même, alors Nous avons une condition de concurrence sur qui y arrive en premier et sur les problèmes de sécurité, sinon le pool de chaînes contiendra des chaînes redondantes perdant ainsi l'avantage de l'avoir en premier lieu. Lisez-le vous-même, d'accord?


L'extension de la chaîne ferait des ravages avec égaux et intern. JavaDoc dit égal:

Compare cette chaîne à l'objet spécifié. Le résultat est vrai si et seulement si l'argument n'est pas null et est un objet String qui représente la même séquence de caractères que cet objet.

En supposant que Java.lang.String N'était pas final, un SafeString pourrait être égal à un String, et inversement; parce qu'ils représenteraient la même séquence de caractères.

Que se passerait-il si vous appliquiez intern à un SafeString - le SafeString irait-il dans le pool de chaînes de la JVM? Le ClassLoader et tous les objets auxquels le SafeString tenait des références seraient alors verrouillés en place pendant la durée de vie de la machine virtuelle Java. Vous auriez une condition de concurrence sur qui pourrait être le premier à interner une séquence de caractères - peut-être que votre SafeString gagnerait, peut-être un String ou peut-être un SafeString chargé par un autre classloader (donc une autre classe).

Si vous remportiez la course dans la piscine, ce serait un véritable singleton et les gens pourraient accéder à tout votre environnement (bac à sable) par réflexion et secretKey.intern().getClass().getClassLoader().

Ou bien la machine virtuelle Java peut bloquer ce trou en s'assurant que seuls des objets String concrets (et aucune sous-classe) ont été ajoutés au pool.

Si equals était implémenté de telle sorte que SafeString! = String alors SafeString.intern! = String.intern, Et SafeString devrait être ajouté à la bassin. Le pool deviendrait alors un pool de <Class, String> Au lieu de <String> Et tout ce dont vous auriez besoin pour entrer dans le pool serait un nouveau classloader.

59
Anurag

La raison absolument la plus importante pour laquelle String est immuable ou finale est qu'elle est utilisée par le mécanisme de chargement de classe et présente donc des aspects de sécurité profonds et fondamentaux.

Si String avait été mutable ou non final, une demande de chargement de "Java.io.Writer" aurait pu être modifiée pour charger "mil.vogoon.DiskErasingWriter".

reference: Pourquoi String est immuable en Java

25
Jackob

String est une classe très fondamentale en Java, beaucoup de choses dépendent de son fonctionnement, par exemple immuable.

Rendre la classe final empêche les sous-classes qui pourraient casser ces hypothèses.

Notez que, même maintenant, si vous utilisez la réflexion, vous pouvez casser des chaînes (changer leur valeur ou leur code de hachage). La réflexion peut être arrêtée avec un responsable de la sécurité. Si String n'était pas final, tout le monde pourrait le faire.

D'autres classes qui ne sont pas déclarées final vous permettent de définir des sous-classes un peu cassées (vous pourriez avoir un List qui s'ajoute à la mauvaise position, par exemple), mais au moins la JVM ne dépend pas de celles-ci. pour ses opérations de base.

15
Thilo

Comme l'a dit Bruno, il s'agit d'immutabilité. Il ne s'agit pas uniquement de chaînes, mais également de tout emballage, par exemple. Double, Entier, Caractère, etc. Il y a plusieurs raisons à cela:

  • Sécurité du fil
  • Sécurité
  • Tas qui est géré par Java lui-même (différemment du tas ordinaire garbage collecté de manière différente)
  • Gestion de la mémoire

En gros, cela vous permet, en tant que programmeur, d’être sûr que votre chaîne ne sera jamais modifiée. De même, si vous savez comment cela fonctionne, vous pourrez améliorer votre gestion de la mémoire. Essayez de créer deux chaînes identiques l'une après l'autre, par exemple "hello". Vous remarquerez, si vous déboguez, qu'ils ont des identifiants identiques, ce qui signifie qu'ils sont exactement les mêmes objets. Ceci est dû au fait que Java vous le faites. Ce ne serait pas possible si les chaînes étaient mutables. Elles peuvent avoir le même comportement que je ferais, etc., car elles ne le feraient jamais. Ainsi, si vous décidiez de créer 1 000 000 chaînes de caractères "bonjour", vous créeriez en réalité 1 000 000 de pointeurs sur "bonjour". (encore une fois regarder ID d'objet - cela va changer).

En outre, définitif dans Java ne signifie pas nécessairement que l'objet ne peut pas être modifié (il est différent de C++, par exemple). Cela signifie que l'adresse vers laquelle il pointe ne peut pas changer, mais que vous pouvez toujours changer ses propriétés et/ou ses attributs, il peut donc être très important de comprendre la différence entre immuabilité et final.

HTH

Les références:

6
Artur

Avec beaucoup de points positifs déjà mentionnés, j'aimerais en ajouter une autre - l'une des raisons pour lesquelles Why String est immuable en Java est d'autoriser String met en cache son code de hachage) =, étant immuable String in Java met en cache son hashcode, et ne calculez pas à chaque fois que nous appelons la méthode hashcode de String, ce qui le rend très rapide en tant que clé hashmap utilisé dans hashmap en Java.

En bref, parce que String est immuable, personne ne peut en modifier le contenu une fois créé, ce qui garantit que hashCode of String est identique lorsqu’il est appelé plusieurs fois.

Si vous voyez String la classe a est déclarée comme

/** Cache the hash code for the string */
private int hash; // Default to 0

et hashcode() fonction est la suivante -

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

S'il s'agit déjà d'un ordinateur, il suffit de renvoyer la valeur.

2
Aniket Thakur

Outre les raisons mentionnées dans d'autres réponses (sécurité, immuabilité, performance), il convient de noter que String dispose d'un support linguistique particulier. Vous pouvez écrire String littéraux et il y a un support pour le + _ opérateur. Permettre aux programmeurs de sous-classer String encouragerait des piratages tels que:

class MyComplex extends String { ... }

MyComplex a = new MyComplex("5+3i");
MyComplex b = new MyComplex("7+4i");
MyComplex c = new MyComplex(a + b);   // would work since a and b are strings,
                                      // and a string + a string is a string.
2
aioobe

C’était peut-être pour simplifier la mise en œuvre. Si vous concevez une classe qui pourra être héritée par ses utilisateurs, vous devrez alors prendre en compte un nouvel ensemble de cas d'utilisation. Que se passe-t-il s'ils font ceci ou cela avec un champ protégé X? Pour le rendre final, ils peuvent se concentrer sur le bon fonctionnement de l’interface publique et s’assurer de sa solidité.

2
AaronLS

Outre les raisons évidentes suggérées dans d'autres réponses, l'idée de rendre la classe String finale pourrait également être liée à la surcharge de performances des méthodes virtuelles. Remember String est une classe lourde, ce qui signifie que cette finale ne signifie aucune sous-implémentation, ni une surcharge d'appels indirectionnels. Bien sûr, nous avons maintenant des choses comme invoke virtuel et autres, qui optimisent toujours ce type d’optimisation pour vous.

2
Abhishek Singh

Bien, j’ai une pensée différente, mais je ne suis pas sûr d’avoir raison ou pas, mais dans Java La chaîne est le seul objet pouvant être traité comme un type de données primitif. Je veux dire que nous pouvons créer un objet String comme String name = "Java". Maintenant, comme d'autres types de données primitifs qui sont copie par valeur pas copie par référence On s'attend à ce que String Même comportement, c’est pourquoi String est définitif. C’est ce que je pensais. S'il vous plaît, ignorez si c'est totalement illogique.

1
webdev

Pour nous assurer que nous n'obtenons pas une meilleure mise en œuvre. Cela aurait bien sûr dû être une interface.

[modifier] Ah, obtenir plus de votes clairs. La réponse est parfaitement sérieuse. J'ai dû programmer plusieurs fois la stupide implémentation de String, ce qui a entraîné de graves pertes de performances et de productivité.

1

Disons que vous avez une classe Employee qui a une méthode greet. Lorsque la méthode greet est appelée, elle affiche simplement Hello everyone!. C'est donc le comportement attendu de la méthode greet

public class Employee {

    void greet() {
        System.out.println("Hello everyone!");
    }
}

Maintenant, passons GrumpyEmployee sous-classe Employee et substituons la méthode greet comme indiqué ci-dessous.

public class GrumpyEmployee extends Employee {

    @Override
    void greet() {
        System.out.println("Get lost!");
    }
}

Dans le code ci-dessous, jetez un coup d’œil à la méthode sayHello. Il prend comme paramètre Employee et appelle la méthode saluer en espérant qu’il dirait Hello everyone! Mais ce que nous obtenons est Get lost!. Ce changement de comportement est dû à Employee grumpyEmployee = new GrumpyEmployee();

public class TestFinal {
    static Employee grumpyEmployee = new GrumpyEmployee();

    public static void main(String[] args) {
        TestFinal testFinal = new TestFinal();
        testFinal.sayHello(grumpyEmployee);
    }

    private void sayHello(Employee employee) {
        employee.greet(); //Here you would expect a warm greeting, but what you get is "Get lost!"
    }
}

Cette situation peut être évitée si la classe Employee a été créée final. Vous pouvez maintenant imaginer le chaos qu'un programmeur effronté pourrait causer si String Class n'était pas déclaré comme étant final.

1
Andy

La finalité des chaînes les défend également en tant que norme. En C++, vous pouvez créer des sous-classes de chaîne afin que chaque magasin de programmation puisse disposer de sa propre version de chaîne. Cela conduirait à un manque de norme forte.

1
ncmathsadist

Est-ce que JVM sait ce qui est immuable? La réponse est non, le pool constant contient tous les champs immuables mais tous les champs/objets immuables ne sont pas stockés dans le pool constant uniquement. Seulement nous l’implémentons de manière à atteindre l’immuabilité et ses caractéristiques. CustomString pourrait être implémenté sans le rendre final en utilisant MarkerInterface qui fournirait à Java un comportement spécial pour son pooling, la fonctionnalité est toujours attendue!

0
hi.nitish

La plupart des réponses sont liées à l'immutabilité - pourquoi un objet de type String ne peut pas être mis à jour sur place. Il y a beaucoup de bonnes discussions ici, et la communauté Java ferait bien d’adopter l’immuabilité comme principe principal. (Ne pas retenir mon souffle.)

Cependant, la question du PO est de savoir pourquoi c'est définitif - pourquoi ne peut-il pas être prolongé? Certains ici l'ont fait, mais je conviendrais avec le PO qu'il y a un réel fossé. Les autres langues permettent aux développeurs de créer de nouveaux types nominaux pour un type. Par exemple, dans Haskell, je peux créer les nouveaux types suivants, identiques en texte à l'exécution, mais offrant une sécurité de liaison au moment de la compilation.

newtype AccountCode = AccountCode Text
newtype FundCode = FundCode Text

Je propose donc la suggestion suivante comme amélioration du langage Java:

newtype AccountCode of String;
newtype FundCode of String;

AccountCode acctCode = "099876";
FundCode fundCode = "099876";

acctCode.equals(fundCode);  // evaluates to false;
acctCode.toString().equals(fundCode.toString());  // evaluates to true;

acctCode=fundCode;  // compile error
getAccount(fundCode);  // compile error

(Ou peut-être pourrions-nous commencer à nous éloigner de Java)

0
dsmith