7
votes

Devrais-je lancer une exception sur la tentative de mutation de la classe immuable? Si oui, quelle exception?

Je souhaite alerter le développeur lorsqu'il tente de muter un objet immuable. L'objet immuable est en réalité une extension d'un objet mutable et remplace les setters sur ledit objet pour la rendre immuable.

Classe de base mutable: vecteur3 fortre> p> xxx pré >

Version immuable: immutatitablevector3 strong> p> xxx pré>

mon cas d'utilisation est la suivante: p>

if (position == Vector3.Zero)
    position = new Vector3(x, y, z);


7 commentaires

J'irais avec UnsppoertedoperationException . Le non modifiable * décorateurs de Collection est un bon précédent.


@crush résolvez-vous une question de performance connue en faisant de vecteur3d mutable? Sinon, le rendre immuable. Laissez les gens construire une nouvelle instance quand ils veulent de nouvelles valeurs. Problème de performance connue = Vous avez effectué des tests / analyses de performance, et c'est le goulot d'étranglement.


@ERICSTEIN Les valeurs du vecteur3 changeront chaque seconde ou plus fréquemment pour des centaines de milliers d'objets dans mon champ d'application. Je suis sous la présomption, éventuellement fausse, qui crée un nouvel objet Vector3 à chaque fois que je dois modifier la position d'un objet susceptible de provoquer des problèmes avec des pauses de GC et / ou d'être plus lente que de définir simplement la nouvelle position sur un objet existant.


@crush Je ne peux pas promettre que ce ne sera pas un problème, mais Java est plutôt bon à traiter d'objets de courte durée (je blâme la dimension). Vous voudrez peut-être l'essayer et voir s'il souffle d'abord de votre code.


@Ericstein en fait, je viens de rappeler ce fait, qu'une autre limitation ici avec la fabrication de vecteur3 immuable est que la JPA n'autorise pas les objets immutables à être sérialisé et nous utilisons JPA pour notre couche d'abstraction DB / sérialisation.


@crush c'est la fin de cette fête. Tout ce hubaloo est pour une instance, non? Le vecteur zéro? Jetez non-prise en chargeOperationException, comme tout le monde a dit (à juste titre).


J'ai une poignée d'une même poignée d'instances statiques, sharables et volantes, comme cela, mais essentiellement oui.


4 Réponses :


10
votes

Lancer une exception est généralement la voie à suivre et vous devriez utiliser non pris en chargeOrexceptionException .

L'API Java elle-même recommande cela dans le Cadre de collections :

Les méthodes "destructives" contenues dans cette interface, c'est-à-dire les méthodes qui modifient la collection sur laquelle elles fonctionnent sont spécifiées pour lancer une introduction non pratiquéeOrexception si cette collection ne prend pas en charge l'opération.

une alternative Si vous créez la hiérarchie de la classe complète consiste à créer une super classe vecteur qui n'est pas modifiable et n'a pas de méthodes de modification comme Définir () et deux sous-classes immutacultvector (qui garantit une immuabilité) et mutabiltaturvector (qui est modifiable). Ce serait mieux parce que vous obtenez la sécurité du temps complets (vous ne pouvez même pas essayer de modifier un immutacultvector ), mais en fonction de la situation peut être trop conçu (et vous pourriez vous retrouver avec de nombreuses classes ).

Cette approche était par exemple choisi pour le Collections Scala , ils existent tous dans trois variations.

Dans tous les cas où vous souhaitez modifier le vecteur, vous utilisez le type mutatablevector . Dans tous les cas où vous ne vous souciez pas de modifier et de vouloir simplement en lire, vous utilisez simplement vecteur . Dans tous les cas où vous souhaitez garantir une immuabilité (par exemple, une instance est passée dans un constructeur et que vous souhaitez vous assurer que personne ne la modifiera plus tard), vous utilisez immutatingvector ( qui fournirait généralement une méthode de copie, vous venez d'appeler immutacultvector.copyof (vecteur) ). Down-Casting de Vecteur ne doit jamais être nécessaire, spécifiez le sous-type correct dont vous avez besoin en tant que type. Tout le point de cette solution (qui a besoin de plus de code) consiste à être sûr de type sûr et de chèques de compilation, de sorte que la coulée de descente détruisit l'avantage. Il existe des exceptions, cependant, par exemple le immutitablevector.copyof () la méthode serait, comme une optimisation, ressemblait généralement à ceci: xxx

ceci est toujours en sécurité et vous donne un autre bénéfice des types immuables, à savoir l'évitement de la copie inutile.

le Bibliothèque de Guava possède une grande collection de Collection immuable < / a> Mises en œuvre qui suivent ce modèle (bien qu'ils étendent toujours les interfaces de collecte Java standard mutable et ne fournissent donc aucune sécurité du temps de compilation). Vous devriez lire la description d'eux pour en savoir plus sur les types immuables.

La troisième alternative serait de disposer d'une classe de vecteur immuable et de classes mutables du tout. Les classes très souvent mutables utilisées pour des raisons de performance sont une optimisation prématurée, car Java Est très bon dans la manipulation de nombreux objets temporaires de petite taille (en raison d'un collecteur de déchets générationnel, une allocation d'objet est extrêmement bon marché et le nettoyage d'objets peut même être sans frais dans le meilleur cas). Bien sûr, ce n'est pas une solution pour des objets très gros tels que des listes, mais pour un vecteur tridimensionnel, c'est sûrement une option (et probablement la meilleure combinaison, car sa facilité, sa sécurité et sa nécessité de moins de code que les autres). Je ne connais aucun exemple dans l'API de Java de cette alternative, car de retour dans les jours où la plupart des parties de l'API ont été créées, le VM n'était pas si optimisé et si trop d'objets étaient peut-être un problème de performance. Mais cela ne devrait pas vous empêcher de le faire aujourd'hui.


8 commentaires

La super classe aurait-elle toujours une méthode () sur elle? Sans la méthode Set () sur la base ou une interface, je vais devoir jeter jusqu'à Mutablevector afin d'accéder au Setter, ce qui signifie également vérifier si Position est une instance de mutabiltaturvector en premier.


Dans ce cas, cependant, je veux muter l'instance uniquement s'il n'est pas défini sur une instance immuable en premier. Si c'est une instance immuable, je souhaite créer une nouvelle instance mutable et affecter cela à position . Ainsi, je devrais toujours me lancer vers Mutabiltaturvector à partir de ce moment-là afin de SET Le vecteur.


Problèmes de performance de la barrage, vous devriez s'efforcer de simplement avoir une classe immuable. Cela aura l'avantage supplémentaire d'éviter une hiérarchie de base / immuable / mutable chaque fois que vous entrez dans cette situation.


Dans ce cas, vous pouvez (et devez-vous avoir besoin), bien que je n'aime vraiment pas ce genre de conception.


Le problème de les rendre tous immuables est que mes mains sont liées à la JPA ne permettant pas la sérialisation des champs finaux (qui me fait bogler, mais quoi que ce soit).


Je ne suis pas sûr de savoir si cela vous aide, mais il y a une question de JPA et des types immuables: Stackoverflow.com/questions/7222366/...


Je voulais vous remercier pour toutes les informations et la recherche à ce sujet. J'ai décidé d'aller avec un peu de ce que vous avez suggéré avec une suggestion de Thiket ci-dessus. Je vais utiliser une interface pour vector3 , mais place SET () dessus. Il retournera une instance vector3 . Cette instance sera ceci sur mutatablevector3 et neuf mutablesvector3 (x, y, z) sur immutatevector3 . Cela aidera à favoriser le modèle de poids volé que je souhaite recevoir. Je ne peux pas décider quelle réponse à accepter, car, depuis que vous avez tous deux contribué de manière égale à ma solution à mon estimation.


Je vous ai donné l'acceptation parce que votre réponse est la plus verbeuse et couvre de nombreuses approches.



3
votes

Vous devez lancer une erreur de compilateur si vous essayez de muter une classe immuable.

Les classes immuables ne doivent pas s'étendre mutables.


3 commentaires

Dites-vous: extraire une interface de la classe mutable et avoir la classe immuable implémente cette interface à la place?


Je n'étais pas trop sûr d'étendre une classe mutable et d'essayer de le rendre immuable. Avez-vous d'autres suggestions sur la manière dont je pourrais gérer les situations ci-dessus?


Je préférerais que Java prit en charge les méthodes const à la place, ce qui contribuerait grandement à résoudre ce non-sens, mais il est trop tard pour cela. :-)



3
votes

Compte tenu de votre situation, je pense que vous devriez jeter un non pris en chargeOrexception code>. Voici pourquoi:

package com.sandbox;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Sandbox {

    public static void main(String[] args) throws IOException {
        List<Object> objects = Collections.unmodifiableList(new ArrayList<Object>());
        objects.add(new Object());
    }

}


7 commentaires

J'ai envisagé la suggestion de Sotiriosdelimanolis. Je ne serais-je pas obligé de jeter jusqu'à Mutablevector3 afin de la muter? Cela nécessiterait également un contrôle supplémentaire: si (instance de position de mutabiltaturvector3) je crois.


@crush vous pourrait . Cela dépend de votre code hérité. Si vous avez du code qui dépend de la mise en œuvre des méthodes mutables du vecteur, vous auriez à votre disposition. Vous pourriez être en mesure de refactoriser la méthode Set de Vector mutable afin qu'il renvoie son propre type. Ensuite, tous les codes hérités utilisent le résultat du setter. Ensuite, votre code hérité dépendra de votre nouvelle interface plutôt que de l'ancienne mise en œuvre.


Quelque chose comme mutabiltaturvector3 , Public Vector3 Set (x, y, z) {/*...*/ retourne ceci; } et sur immutacultvector3 , Publique vector3 Set (x, y, z) {/*...*/ retour Nouveau vecteur3 (x, y, z); ?


@crush Oui. Si vous faites tout dans le code hérité qui appelle mutatablevector3.set Toujours faire ceci: x.mymutablevector = x.mymutablevector.set (x, y, z); alors vous devriez Soyez capable de remplacer toutes les implémentations avec votre immutacultvector3 et supprimez votre mutabiltaturvector3 . Malheureusement, le compilateur ne vous aidera pas si vous oubliez de dire x.mymutablevector =


J'aime vraiment votre suggestion de retourner une instance à partir de la méthode (code>. Cela simplifie mon code même et facilite le modèle de poids volé! Lorsque quelqu'un tente de figurer sur le cas spécial immuable, ils obtiendront un vecteur mutable3 avec les valeurs qu'ils définies peuvent ensuite être utilisées à partir de ce moment. Ceci, avec une interface / des objets mutables / immuables, est là que j'ai décidé de prendre! Je rencontre des difficultés à décider de la réponse à accepter, cependant, car vous avez tous deux aidé de manière égale à ma solution.


@crush ce n'est pas l'itinéraire que je suggérais, mais si c'est ce qui fonctionne le mieux pour vous, cela pourrait être viable. Je pense que définir sur immutacultvector doit renvoyer un autre immutatevector .


Dans la plupart des cas, peut-être, mais la façon dont je l'utilise, il est préférable qu'il retourne un vecteur mutable. Je serai sûr de le documenter de manière appropriée. Merci.



1
votes

Les classes immuables ne doivent pas dériver de classes mutables concrètes, ni inversement. Au lieu de cela, vous devez avoir un lishablefoo Classe abstraite avec concrets Mutablefoo et immutatefoo dérivés. De cette façon, une méthode qui veut un objet, elle peut muté peut exiger mutablablefoo , une méthode qui souhaite un objet dont l'état actuel peut être persisté simplement en persistant une référence à l'objet peut exiger immutatefoo et une méthode qui ne veut pas changer d'état d'objet et ne se souciera pas de ce que l'état après son retour peut accepter librement lisiblefoo sans avoir à craindre d'être mutable ou immuable.

L'aspect le plus difficile est de décider s'il est préférable d'avoir la classe de base inclure une méthode permettant de vérifier si elle est mutable avec une méthode "définie" qui jettera si elle ne l'est pas, ou si elle omet simplement omettre la méthode "SET" . Je préférerais omettre la méthode "Set", sauf si le type inclut des fonctionnalités pour faciliter un motif de copie-écriture.

Un tel motif peut être facilité par le fait que la classe de base inclut des méthodes abstraites asimmutable , asmutable , avec du béton asNewMutable . Procédé qui reçoit un lisiblefoo nommé george et veut capturer son état actuel peut stocker sur un champ georgefield george.asimmutable () ou george.asNewMutable () selon que cela pense qu'il souhaite la modifier. S'il doit le modifier, il peut définir georgefield = georgefield.asmutable () . Si cela doit le partager, il peut renvoyer georgefield.asimmauttable () ; S'il s'attend à la partager plusieurs fois avant de la modifier à nouveau, il peut définir georgefield = georgefield.asimmauttable () . Si de nombreux objets doivent partager les États ... FOO S, et la plupart d'entre eux n'ont pas besoin de les modifier du tout, mais ont besoin de les modifier beaucoup, une telle approche peut s'entraîner plus efficacement que d'utiliser uniquement des objets mutables ou immuables.


2 commentaires

Considérez mon exemple ci-dessus. J'ai MyObject avec le membre position dessus. Position est un Vector3 . Disons que vector3 est une classe de base / interface, et j'ai mutabiltavector3 et immutacultvector3 qui en dérive. Position est initialement défini sur une instance de immutacultaturvector3 . Il reste un immutacultvector3 jusqu'à ce que la position doit changer. À ce stade, un nouveau mutabiltaturvector3 sera créé à la position modifiée et utilisé à partir de l'avant vers l'avant. Toutefois, si SET () est omis de la base / de l'interface, je voulais SET () J'aurais besoin de vérifier l'immuabilité.


@crush: si définir les méthodes a été omis de l'interface de base, il serait probable que la santé mentale doit probablement inclure un Assameutable () [retour auto exception mutable ou lancer]; Si on voulait appeler setxx et setyy sur georgefield , il aurait besoin de georgefield = georgefield.asmutable (); Georgefield.assamemutable.setxx (); Georgefield.assamemutable.Setyyy (); . Si l'on utilise asmutable plutôt que assamempeutable et omis le premier asmutable en pensant à tort qu'il était déjà arrivé, les tentatives de définition de XX et YY seraient silencieusement échouer.