Question

Je travaille sur une application et une approche de conception implique une utilisation extrêmement intensive de l'opérateur instanceof. Bien que je sache que la conception orientée objet essaie généralement d'éviter d'utiliser ==, il en va tout autrement et cette question est purement liée à la performance. Je me demandais s'il y avait un impact sur les performances? Est-ce juste aussi vite que <=>?

Par exemple, j'ai une classe de base avec 10 sous-classes. Dans une fonction unique prenant la classe de base, je vérifie si la classe est une instance de la sous-classe et exécute une routine.

L’un des autres moyens que j’ai pensé résoudre était d’utiliser un & "type id &"; primitive entière à la place, et utilisez un masque de bits pour représenter les catégories des sous-classes, puis effectuez une comparaison avec masque de bits des sous-classes & "type id &"; à un masque constant représentant la catégorie.

Est-ce que <=> la JVM a optimisé pour être plus rapide que cela? Je veux m'en tenir à Java, mais les performances de l'application sont essentielles. Ce serait cool si quelqu'un qui avait déjà emprunté cette voie pouvait donner quelques conseils. Est-ce que je croque trop ou que je me concentre sur la mauvaise chose à optimiser?

Était-ce utile?

La solution

Les compilateurs JVM / JIC modernes ont supprimé le succès de la plupart des & "; ralentis &"; opérations, y compris instanceof, traitement des exceptions, réflexion, etc.

Comme l’a écrit Donald Knuth, & "Nous devrions oublier les petites efficacités, disons environ 97% du temps: l’optimisation prématurée est à la base de tout mal. &"; Les performances d’instanceof ne seront probablement pas un problème, alors ne perdez pas votre temps à proposer des solutions de rechange exotiques tant que vous n’êtes pas sûr que c’est le problème.

Autres conseils

Approche

J'ai écrit un programme d'évaluation pour évaluer différentes implémentations:

  1. instanceof implémentation (comme référence)
  2. objet orienté via une classe abstraite et @Override une méthode de test
  3. utilisant une implémentation de type propre
  4. getClass() == _.class implémentation

J'ai utilisé jmh pour exécuter le test avec 100 appels de préchauffage, 1 000 itérations sous mesure et avec 10 fourchettes. Ainsi, chaque option a été mesurée 10 000 fois, ce qui prend 12h18:57 pour exécuter l’ensemble des critères de référence sur mon MacBook Pro avec macOS 10.12.4 et Java 1.8. Le repère mesure le temps moyen de chaque option. Pour plus de détails, voir ma mise en œuvre sur GitHub .

Par souci d'exhaustivité: il existe une version précédente de cette réponse et de mon point de repère .

Résultats

| Operation  | Runtime in nanoseconds per operation | Relative to instanceof |
|------------|--------------------------------------|------------------------|
| INSTANCEOF | 39,598 ± 0,022 ns/op                 | 100,00 %               |
| GETCLASS   | 39,687 ± 0,021 ns/op                 | 100,22 %               |
| TYPE       | 46,295 ± 0,026 ns/op                 | 116,91 %               |
| OO         | 48,078 ± 0,026 ns/op                 | 121,42 %               |

tl; dr

En Java 1.8, getClass() est l'approche la plus rapide, bien que <=> soit très proche.

Je viens de faire un test simple pour voir comment instanceOf performance se compare à un simple appel s.equals () à un objet chaîne avec une seule lettre.

dans une boucle 10.000.000 l'instanceOf m'a donné 63-96ms, et la chaîne égale m'a donné 106-230ms

J'ai utilisé java jvm 6.

Donc, dans mon test simple, il est plus rapide de faire une instanceOf au lieu d'une comparaison d'une chaîne de caractères.

utiliser .equals () au lieu de string m'a donné le même résultat, mais seulement lorsque j'ai utilisé le == i était plus rapide que instanceOf de 20 ms (dans une boucle 10.000.000)

Les éléments qui détermineront l’impact sur les performances sont les suivants:

  1. Le nombre de classes possibles pour lesquelles l'opérateur d'instanceof pourrait renvoyer true
  2. La distribution de vos données - la plupart des opérations d'instance d'opérations sont-elles résolues lors de la première ou de la deuxième tentative? Vous aurez envie de mettre votre plus susceptible de retourner les véritables opérations en premier.
  3. L'environnement de déploiement. L'exécution sur une VM Sun Solaris est très différente de la JVM Windows de Sun. Solaris fonctionnera par défaut en mode 'serveur', tandis que Windows s'exécutera en mode client. Les optimisations JIT sur Solaris rendront toutes les méthodes accessibles identiques.

J'ai créé un microbenchmark pour quatre méthodes d'envoi différentes . Les résultats de Solaris sont les suivants, le plus petit nombre étant plus rapide:

InstanceOf 3156
class== 2925 
OO 3083 
Id 3067 

Répondant à votre toute dernière question: à moins que votre profileur ne vous dise que vous passez une quantité ridicule de temps dans une instance de: Oui, vous êtes en train de faire la grimace.

Avant de vous interroger sur l'optimisation de quelque chose qui n'a jamais été optimisé: écrivez votre algorithme de la manière la plus lisible et exécutez-le. Exécutez-le jusqu'à ce que le compilateur jit ait la possibilité de l'optimiser lui-même. Si vous rencontrez des problèmes avec ce morceau de code, utilisez un profileur pour vous dire où trouver le meilleur résultat et l'optimiser.

À une époque où les compilateurs sont très optimistes, vos suppositions sur les goulots d'étranglement seront probablement totalement fausses.

Et dans l’esprit de cette réponse (que je crois sincèrement): Je ne sais absolument pas comment instance de et == se rapportent une fois que le compilateur Jit a eu l’occasion de l’optimiser.

J'ai oublié: ne mesurez jamais le premier passage.

J'ai la même question, mais comme je n'ai pas trouvé de "métrique de performance" similaire à la mienne, j'ai utilisé quelques exemples de code supplémentaires. Sur mon matériel et Java 6 & Amp; 7, la différence entre instanceof et activer les itérations de 10 mln est

for 10 child classes - instanceof: 1200ms vs switch: 470ms
for 5 child classes  - instanceof:  375ms vs switch: 204ms

Ainsi, instanceof est vraiment plus lent, en particulier pour un grand nombre d'instructions if-else-if, mais la différence sera négligeable dans le cadre d'une application réelle.

import java.util.Date;

public class InstanceOfVsEnum {

    public static int c1, c2, c3, c4, c5, c6, c7, c8, c9, cA;

    public static class Handler {
        public enum Type { Type1, Type2, Type3, Type4, Type5, Type6, Type7, Type8, Type9, TypeA }
        protected Handler(Type type) { this.type = type; }
        public final Type type;

        public static void addHandlerInstanceOf(Handler h) {
            if( h instanceof H1) { c1++; }
            else if( h instanceof H2) { c2++; }
            else if( h instanceof H3) { c3++; }
            else if( h instanceof H4) { c4++; }
            else if( h instanceof H5) { c5++; }
            else if( h instanceof H6) { c6++; }
            else if( h instanceof H7) { c7++; }
            else if( h instanceof H8) { c8++; }
            else if( h instanceof H9) { c9++; }
            else if( h instanceof HA) { cA++; }
        }

        public static void addHandlerSwitch(Handler h) {
            switch( h.type ) {
                case Type1: c1++; break;
                case Type2: c2++; break;
                case Type3: c3++; break;
                case Type4: c4++; break;
                case Type5: c5++; break;
                case Type6: c6++; break;
                case Type7: c7++; break;
                case Type8: c8++; break;
                case Type9: c9++; break;
                case TypeA: cA++; break;
            }
        }
    }

    public static class H1 extends Handler { public H1() { super(Type.Type1); } }
    public static class H2 extends Handler { public H2() { super(Type.Type2); } }
    public static class H3 extends Handler { public H3() { super(Type.Type3); } }
    public static class H4 extends Handler { public H4() { super(Type.Type4); } }
    public static class H5 extends Handler { public H5() { super(Type.Type5); } }
    public static class H6 extends Handler { public H6() { super(Type.Type6); } }
    public static class H7 extends Handler { public H7() { super(Type.Type7); } }
    public static class H8 extends Handler { public H8() { super(Type.Type8); } }
    public static class H9 extends Handler { public H9() { super(Type.Type9); } }
    public static class HA extends Handler { public HA() { super(Type.TypeA); } }

    final static int cCycles = 10000000;

    public static void main(String[] args) {
        H1 h1 = new H1();
        H2 h2 = new H2();
        H3 h3 = new H3();
        H4 h4 = new H4();
        H5 h5 = new H5();
        H6 h6 = new H6();
        H7 h7 = new H7();
        H8 h8 = new H8();
        H9 h9 = new H9();
        HA hA = new HA();

        Date dtStart = new Date();
        for( int i = 0; i < cCycles; i++ ) {
            Handler.addHandlerInstanceOf(h1);
            Handler.addHandlerInstanceOf(h2);
            Handler.addHandlerInstanceOf(h3);
            Handler.addHandlerInstanceOf(h4);
            Handler.addHandlerInstanceOf(h5);
            Handler.addHandlerInstanceOf(h6);
            Handler.addHandlerInstanceOf(h7);
            Handler.addHandlerInstanceOf(h8);
            Handler.addHandlerInstanceOf(h9);
            Handler.addHandlerInstanceOf(hA);
        }
        System.out.println("Instance of - " + (new Date().getTime() - dtStart.getTime()));

        dtStart = new Date();
        for( int i = 0; i < cCycles; i++ ) {
            Handler.addHandlerSwitch(h1);
            Handler.addHandlerSwitch(h2);
            Handler.addHandlerSwitch(h3);
            Handler.addHandlerSwitch(h4);
            Handler.addHandlerSwitch(h5);
            Handler.addHandlerSwitch(h6);
            Handler.addHandlerSwitch(h7);
            Handler.addHandlerSwitch(h8);
            Handler.addHandlerSwitch(h9);
            Handler.addHandlerSwitch(hA);
        }
        System.out.println("Switch of - " + (new Date().getTime() - dtStart.getTime()));
    }
}

instanceof est très rapide et ne prend que quelques instructions du processeur.

Apparemment, si une classe X n'a aucune sous-classe chargée (la machine virtuelle sait), <=> peut être optimisé comme suit:

     x instanceof X    
==>  x.getClass()==X.class  
==>  x.classID == constant_X_ID

Le coût principal n’est qu’une lecture!

Si <=> des sous-classes sont chargées, quelques lectures supplémentaires sont nécessaires. ils sont probablement co-localisés de sorte que le coût supplémentaire est également très faible.

Bonne nouvelle à tous!

instanceof va probablement coûter plus cher qu'un simple équivalent dans la plupart des implémentations du monde réel (c'est-à-dire celles où instanceof est vraiment nécessaire, et vous ne pouvez pas simplement le résoudre en surchargeant une méthode commune, comme dans tout manuel pour débutant. ainsi que Demian ci-dessus suggèrent).

Pourquoi ça? Parce que ce qui va probablement arriver, c’est que vous avez plusieurs interfaces, qui fournissent des fonctionnalités (disons, des interfaces x, y et z), et des objets à manipuler pouvant implémenter (ou non) l’une de ces interfaces ... mais Pas directement. Dis, par exemple, j'ai:

w étend x

A implémente w

B étend A

C étend B, implémente y

D étend C, implémente z

Supposons que je traite une instance de D, l'objet d. L'informatique (d instance de x) nécessite de prendre d.getClass (), de parcourir les interfaces implémentées pour savoir si on est == à x, et si ce n'est pas le cas de manière récursive pour tous leurs ancêtres ... Dans notre cas, si vous effectuez une première exploration en profondeur de cet arbre, vous obtiendrez au moins 8 comparaisons, en supposant que y et z ne prolongent rien ...

La complexité d'un arbre de dérivation du monde réel sera probablement plus élevée. Dans certains cas, le JIT peut optimiser l'essentiel, s'il est capable de résoudre à l'avance d comme étant, dans tous les cas possibles, une instance de quelque chose qui étend x. De manière réaliste, cependant, vous passerez la plupart du temps dans cet arbre traversant.

Si cela devient un problème, je suggérerais plutôt d'utiliser une carte de gestionnaire, liant la classe concrète de l'objet à une fermeture assurant le traitement. Il supprime la phase de traversée des arbres en faveur d'une cartographie directe. Cependant, sachez que si vous avez défini un gestionnaire pour C.class, mon objet d ci-dessus ne sera pas reconnu.

voici mes 2 centimes, j'espère qu'ils vous aideront ...

'instanceof' est en fait un opérateur, comme + ou -, et je crois qu'il a sa propre instruction de code intermédiaire JVM. Cela devrait être rapide.

Je ne devrais pas dire que si vous avez un commutateur dans lequel vous testez si un objet est une instance d'une sous-classe, votre conception devra peut-être être retravaillée. Envisagez de placer le comportement spécifique à une sous-classe dans les sous-classes elles-mêmes.

Instanceof est très rapide. Cela se résume à un bytecode utilisé pour la comparaison de référence de classe. Essayez quelques millions d'instances dans une boucle et voyez par vous-même.

Demian et Paul mentionnent un bon point; Cependant , l'emplacement du code à exécuter dépend vraiment de la manière dont vous souhaitez utiliser les données ...

Je suis un grand fan de petits objets de données pouvant être utilisés de nombreuses manières. Si vous suivez l'approche de substitution (polymorphe), vos objets ne peuvent être utilisés que & "À sens unique". & ";.

C’est là que les motifs entrent en jeu ...

Vous pouvez utiliser la double dépêche (comme dans le modèle de visiteur) pour demander à chaque objet de & "vous appeler &"; passer lui-même - cela résoudra le type de l'objet. Cependant (encore), vous aurez besoin d’une classe qui puisse & "faire des choses &"; avec tous les sous-types possibles.

Je préfère utiliser un modèle de stratégie dans lequel vous pouvez enregistrer des stratégies pour chaque sous-type que vous souhaitez gérer. Quelque chose comme ce qui suit. Notez que cela ne sert que pour les correspondances de types exactes, mais présente l'avantage d'être extensible - les contributeurs tiers peuvent ajouter leurs propres types et gestionnaires. (Ceci est utile pour les frameworks dynamiques comme OSGi, où de nouveaux bundles peuvent être ajoutés)

J'espère que cela inspirera d'autres idées ...

package com.javadude.sample;

import java.util.HashMap;
import java.util.Map;

public class StrategyExample {
    static class SomeCommonSuperType {}
    static class SubType1 extends SomeCommonSuperType {}
    static class SubType2 extends SomeCommonSuperType {}
    static class SubType3 extends SomeCommonSuperType {}

    static interface Handler<T extends SomeCommonSuperType> {
        Object handle(T object);
    }

    static class HandlerMap {
        private Map<Class<? extends SomeCommonSuperType>, Handler<? extends SomeCommonSuperType>> handlers_ =
            new HashMap<Class<? extends SomeCommonSuperType>, Handler<? extends SomeCommonSuperType>>();
        public <T extends SomeCommonSuperType> void add(Class<T> c, Handler<T> handler) {
            handlers_.put(c, handler);
        }
        @SuppressWarnings("unchecked")
        public <T extends SomeCommonSuperType> Object handle(T o) {
            return ((Handler<T>) handlers_.get(o.getClass())).handle(o);
        }
    }

    public static void main(String[] args) {
        HandlerMap handlerMap = new HandlerMap();

        handlerMap.add(SubType1.class, new Handler<SubType1>() {
            @Override public Object handle(SubType1 object) {
                System.out.println("Handling SubType1");
                return null;
            } });
        handlerMap.add(SubType2.class, new Handler<SubType2>() {
            @Override public Object handle(SubType2 object) {
                System.out.println("Handling SubType2");
                return null;
            } });
        handlerMap.add(SubType3.class, new Handler<SubType3>() {
            @Override public Object handle(SubType3 object) {
                System.out.println("Handling SubType3");
                return null;
            } });

        SubType1 subType1 = new SubType1();
        handlerMap.handle(subType1);
        SubType2 subType2 = new SubType2();
        handlerMap.handle(subType2);
        SubType3 subType3 = new SubType3();
        handlerMap.handle(subType3);
    }
}

instanceof est très efficace, votre performance ne devrait donc pas en souffrir. Cependant, l'utilisation de nombreuses instances de suggère un problème de conception.

Si vous pouvez utiliser xClass == String.class, c'est plus rapide. Remarque: vous n'avez pas besoin d'instanceof pour les classes finales.

Il est difficile de dire comment une machine virtuelle Java implémente une instance de, mais dans la plupart des cas, les objets sont comparables aux structures et les classes le sont également et chaque structure d'objet a un pointeur sur la structure de classe dont il est une instance. Donc, en fait, instanceof pour

if (o instanceof java.lang.String)

pourrait être aussi rapide que le code C suivant

if (objectStruct->iAmInstanceOf == &java_lang_String_class)

en supposant qu'un compilateur JIT est en place et fait un travail décent.

Considérant qu’il s’agit uniquement d’accéder à un pointeur, d’obtenir un pointeur à un certain décalage et de le comparer à un autre pointeur (ce qui revient fondamentalement à tester avec un nombre égal de 32 bits), je dirais que l'opération peut en réalité être très rapide.

Cela ne doit pas nécessairement, cela dépend beaucoup de la machine virtuelle Java. Toutefois, si cela devait s'avérer être l'opération goulet d'étranglement dans votre code, la mise en œuvre de la machine virtuelle Java serait plutôt médiocre. Même celui qui n'a pas de compilateur JIT et interprète uniquement du code devrait pouvoir créer une instance de test en un rien de temps.

InstanceOf est un avertissement concernant une mauvaise conception orientée objet.

Les

JVM actuelles signifient que instanceOf ne constitue pas un problème de performances en soi. Si vous vous trouvez souvent en train de l'utiliser, en particulier pour les fonctionnalités de base, il est probablement temps de regarder le design. Les gains de performance (et de simplicité / facilité de maintenance) du refactoring pour une meilleure conception l'emporteront largement sur les cycles de processeur réellement utilisés pour l'appel instanceOf en cours.

Pour donner un très petit exemple de programmation simpliste.

if (SomeObject instanceOf Integer) {
  [do something]
}
if (SomeObject instanceOf Double) {
  [do something different]
}

Une architecture médiocre aurait-elle été préférable si SomeObject était la classe parente de deux classes enfant, chaque classe enfant remplaçant une méthode (doSomething) afin que le code ait l'apparence suivante:

Someobject.doSomething();

Je vous recontacterai au sujet des performances. Mais un moyen d'éviter le problème (ou son absence) serait de créer une interface parent pour toutes les sous-classes sur lesquelles vous devez faire instanceof. L’interface sera un super ensemble de toutes les méthodes des sous-classes pour lesquelles vous devez faire instanceof. Lorsqu'une méthode ne s'applique pas à une sous-classe spécifique, fournissez simplement une implémentation fictive de cette méthode. Si je ne comprenais pas bien le problème, c’est ainsi que j’ai abordé le problème par le passé.

Généralement, la raison pour laquelle & l'instance de & "; L'opérateur est mal vu dans un cas comme celui-là (où instanceof vérifie les sous-classes de cette classe de base), car vous devriez déplacer les opérations dans une méthode et la remplacer pour les sous-classes appropriées. Par exemple, si vous avez:

if (o instanceof Class1)
   doThis();
else if (o instanceof Class2)
   doThat();
//...

Vous pouvez remplacer cela par

o.doEverything();

et ensuite avoir la mise en oeuvre de & "DoEverything () &"; dans Class1, appelez & "; DoThis () &"; et en Class2, appelez & "DoThat () &"; et ainsi de suite.

Dans la version Java moderne, l'opérateur instanceof est plus rapide qu'un simple appel de méthode. Cela signifie:

if(a instanceof AnyObject){
}

est plus rapide que:

if(a.getType() == XYZ){
}

Une autre chose est si vous avez besoin de mettre en cascade plusieurs instances de. Ensuite, un commutateur qui appelle seulement une fois getType () est plus rapide.

Si votre seul objectif est la vitesse, utiliser des constantes int pour identifier les sous-classes semble réduire les millisecondes du temps

static final int ID_A = 0;
static final int ID_B = 1;
abstract class Base {
  final int id;
  Base(int i) { id = i; }
}
class A extends Base {
 A() { super(ID_A); }
}
class B extends Base {
 B() { super(ID_B); }
}
...
Base obj = ...
switch(obj.id) {
case  ID_A: .... break;
case  ID_B: .... break;
}

conception OO terrible, mais si votre analyse de performance indique que c'est là que vous êtes goulot d’étranglement, alors peut-être. Dans mon code, le code d’envoi occupe 10% du temps total d’exécution, ce qui a peut-être contribué à une amélioration de la vitesse totale de 1%.

J'écris un test de performance basé sur jmh-java-benchmark-archetype: 2.21. JDK est openjdk et la version est 1.8.0_212. La machine de test est mac pro. Le résultat du test est:

Benchmark                Mode  Cnt    Score   Error   Units
MyBenchmark.getClasses  thrpt   30  510.818 ± 4.190  ops/us
MyBenchmark.instanceOf  thrpt   30  503.826 ± 5.546  ops/us

Le résultat montre que: getClass est meilleur que instanceOf, ce qui est contraire à d’autres tests. Cependant, je ne sais pas pourquoi.

Le code de test est ci-dessous:

public class MyBenchmark {

public static final Object a = new LinkedHashMap<String, String>();

@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean instanceOf() {
    return a instanceof Map;
}

@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean getClasses() {
    return a.getClass() == HashMap.class;
}

public static void main(String[] args) throws RunnerException {
    Options opt =
        new OptionsBuilder().include(MyBenchmark.class.getSimpleName()).warmupIterations(20).measurementIterations(30).forks(1).build();
    new Runner(opt).run();
}
}

Vous devriez mesurer / profiler s’il s’agit vraiment d’un problème de performance dans votre projet. Si c'est le cas, je recommanderais une refonte, si possible. Je suis sûr que vous ne pouvez pas battre l'implémentation native de la plate-forme (écrite en C). Vous devez également prendre en compte l'héritage multiple dans ce cas.

Vous devriez en dire plus sur le problème. Vous pourriez peut-être utiliser un magasin associatif, par exemple. une carte < Classe, objet > si vous êtes uniquement intéressé par les types concrets.

En ce qui concerne la note de Peter Lawrey, vous n'avez pas besoin d'instanceof pour les classes finales et vous pouvez simplement utiliser une égalité de référence, soyez prudent! Même si les classes finales ne peuvent pas être étendues, leur chargement par le même chargeur de classes n'est pas garanti. Utilisez uniquement x.getClass () == SomeFinal.class ou son équivalent si vous êtes absolument certain qu’un seul chargeur de classe est en jeu pour cette section de code.

Je préfère également une approche enum, mais j'utiliserais une classe de base abstraite pour forcer les sous-classes à implémenter la méthode getType().

public abstract class Base
{
  protected enum TYPE
  {
    DERIVED_A, DERIVED_B
  }

  public abstract TYPE getType();

  class DerivedA extends Base
  {
    @Override
    public TYPE getType()
    {
      return TYPE.DERIVED_A;
    }
  }

  class DerivedB extends Base
  {
    @Override
    public TYPE getType()
    {
      return TYPE.DERIVED_B;
    }
  }
}

J'ai pensé qu'il serait peut-être intéressant de soumettre un contre-exemple au consensus général sur cette page selon lequel & "instance of &"; n'est pas assez cher pour s'inquiéter. J'ai trouvé que j'avais du code dans une boucle interne qui (dans une tentative historique d'optimisation) l'a fait

if (!(seq instanceof SingleItem)) {
  seq = seq.head();
}

où l'appel de head () sur un SingleItem retourne la valeur inchangée. Remplacement du code par

seq = seq.head();

me donne une accélération de 269 ms à 169 ms, malgré le fait que des choses assez lourdes se produisent dans la boucle, comme une conversion chaîne à double. Il est possible que l'accélération soit plus due à la suppression de la branche conditionnelle qu'à la suppression de l'instance de l'opérateur lui-même; mais j'ai pensé qu'il valait la peine de le mentionner.

Vous vous concentrez sur la mauvaise chose. La différence entre instanceof et toute autre méthode de vérification de la même chose ne serait probablement même pas mesurable. Si les performances sont critiques, Java est probablement le mauvais langage. La principale raison est que vous ne pouvez pas contrôler le moment où la machine virtuelle décide de collecter des déchets, ce qui peut amener le processeur à 100% pendant plusieurs secondes dans un programme volumineux (MagicDraw 10 était idéal pour cela). À moins que vous ne maîtrisiez tous les ordinateurs sur lesquels ce programme sera exécuté, vous ne pouvez pas garantir la version de la machine virtuelle Java sur laquelle il sera installé, et de nombreux ordinateurs plus anciens présentaient des problèmes de vitesse importants. Si c'est une petite application, vous pouvez utiliser Java, mais si vous lisez et supprimez constamment des données, vous remarquerez que le GC entre en action.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top