(A == 1 && a == 2 && a == 3) peut-il être évalué à vrai en Java?

176

Nous savons que c'est possible en JavaScript .

Mais est-il possible d'imprimer le message "Success" à la condition donnée ci-dessous en Java?

if (a==1 && a==2 && a==3) {
    System.out.println("Success");
}

Quelqu'un a suggéré:

int _a = 1;
int a  = 2;
int a_ = 3;
if (_a == 1 && a == 2 && a_ == 3) {
    System.out.println("Success");
}

Mais en faisant cela, nous modifions la variable réelle. Est-ce qu'il y a un autre moyen?

Taylor Sen
la source
5
&&est un andopérateur logique , ce qui signifie qu'il adoit avoir les valeurs 1, 2 et 3 en même temps, ce qui est logiquement impossible. La réponse est NON, pas possible. Voulez-vous écrire une ifinstruction qui vérifie si aa l'une des valeurs 1, 2 OU 3?
Boris Pavlović
16
Note à tous: Cette question peut provenir de la même question pour Javascript: stackoverflow.com/questions/48270127/… , auquel cas la réponse estyes
nos
28
@ArthurAttout Pas un doublon, cette question est pour Javascript, pas pour Java.
nos
13
Celui qui utilise des espaces dans les noms de variables fonctionne également en Java. Mais bien sûr, c'est essentiellement la même chose que l'utilisation _, sauf que vous ne pouvez pas le voir.
tobias_k
8
@Seelenvirtuose Vrai, mais if(a==1 && a==2 && a==3)n'est pas nécessairement évalué en même temps. Et cela peut être utilisé pour que cela fonctionne sans avoir à recourir à des astuces Unicode.
Erwin Bolwidt

Réponses:

325

Oui, il est assez facile d'y parvenir avec plusieurs threads, si vous déclarez la variable acomme volatile.

Un thread change constamment la variable a de 1 à 3, et un autre thread le teste constamment a == 1 && a == 2 && a == 3. Il arrive assez souvent d'avoir un flux continu de "Succès" imprimé sur la console.

(Notez que si vous ajoutez une else {System.out.println("Failure");}clause, vous verrez que le test échoue beaucoup plus souvent qu'il ne réussit.)

En pratique, cela fonctionne également sans se déclarer avolatile, mais seulement 21 fois sur mon MacBook. Sans volatile, le compilateur ou HotSpot est autorisé à mettre en cache aou à remplacer l' ifinstruction par if (false). Très probablement, HotSpot démarre après un certain temps et le compile en instructions d'assemblage qui mettent en cache la valeur de a. Avec volatile , il continue à imprimer "Success" pour toujours.

public class VolatileRace {
    private volatile int a;

    public void start() {
        new Thread(this::test).start();
        new Thread(this::change).start();
    }

    public void test() {
        while (true) {
            if (a == 1 && a == 2 && a == 3) {
                System.out.println("Success");
            }
        }
    }

    public void change() {
        while (true) {
            for (int i = 1; i < 4; i++) {
                a = i;
            }
        }
    }

    public static void main(String[] args) {
        new VolatileRace().start();
    }
}
Erwin Bolwidt
la source
83
C'est un exemple brillant! Je pense que je vais le voler la prochaine fois que je discuterai de problèmes de concurrence potentiellement dangereux avec un développeur junior :)
Alex Pruss
6
Ce n'est pas très probable sans volatile, mais en principe, cela peut même se produire sans le volatilemot - clé et cela pourrait arriver un nombre arbitraire de fois, alors que, d'une part, il n'y a aucune garantie que cela se produira jamais, même avec volatile. Mais bien sûr, l'avoir en pratique, c'est assez impressionnant…
Holger
5
@AlexPruss Je viens de le faire sur tous les développeurs, pas seulement sur les juniors de mon entreprise (je savais que cela pourrait être possible), 87% de réussite des échecs dans les réponses
Eugene
3
@Holger True, aucune garantie de l'un ou de l'autre. Sur une architecture typique à plusieurs cœurs ou multi-processeurs, où les deux threads sont affectés à un cœur séparé, il est fort probable que cela se produise avec volatile. Les barrières de mémoire créées pour implémenter volatileralentissent les threads et rendent plus probable leur fonctionnement synchronisé pendant de courtes périodes. Cela arrive beaucoup plus souvent que prévu. C'est très sensible au timing, mais je vois à peu près qu'entre 0,2% et 0,8% des évaluations de (a == 1 && a == 2 && a == 3)rendement true.
Erwin Bolwidt
4
C'est la seule réponse qui fait que le code prévu renvoie vrai au lieu de simplement faire de petites variations du même. +1
Alejandro
85

En utilisant les concepts (et le code) d'une réponse brillante au code de golf , les Integervaleurs peuvent être perturbées.

Dans ce cas, cela peut rendre ints castés Integerégaux alors qu'ils ne le seraient pas normalement:

import java.lang.reflect.Field;

public class Test
{
    public static void main(String[] args) throws Exception
    {
        Class cache = Integer.class.getDeclaredClasses()[0];
        Field c = cache.getDeclaredField("cache");
        c.setAccessible(true);
        Integer[] array = (Integer[]) c.get(cache);
        // array[129] is 1
        array[130] = array[129]; // Set 2 to be 1
        array[131] = array[129]; // Set 3 to be 1

        Integer a = 1;
        if(a == (Integer)1 && a == (Integer)2 && a == (Integer)3)
            System.out.println("Success");
    }
}

Malheureusement, ce n'est pas aussi élégant que la réponse multithread d'Erwin Bolwidt (car celle-ci nécessite un Integercasting) , mais des manigances amusantes ont toujours lieu.

phflack
la source
Très intéressant. Je jouais avec les abus Integer. C'est dommage pour le casting mais c'est quand même cool.
Michael
@Michael Je n'arrive pas à comprendre comment me débarrasser du casting. aêtre réglé sur 1/2/3 satisfera a == 1, mais cela ne va pas dans l'autre sens
phflack
4
J'ai répondu à cette question il y a quelques jours dans JS / Ruby / Python et Java. La version la moins moche que j'ai pu trouver était d'utiliser a.equals(1) && a.equals(2) && a.equals(3), qui force 1, 2et 3d'être autobox comme Integers.
Eric Duminil
@EricDuminil Cela pourrait prendre un peu de plaisir, car et si vous faisiez aun cours avec boolean equals(int i){return true;}?
phflack
1
Comme je l'ai dit, c'est le moins moche mais ce n'est toujours pas optimal. Avec Integer a = 1;sur la ligne avant, il est toujours clair que c'est avraiment un entier.
Eric Duminil
52

Dans cette question, @aioobe suggère (et déconseille) l'utilisation du préprocesseur C pour les classes Java.

Bien que ce soit extrêmement triche, c'est ma solution:

#define a evil++

public class Main {
    public static void main(String[] args) {
        int evil = 1;
        if (a==1 && a==2 && a==3)
            System.out.println("Success");
    }
}

S'il est exécuté à l'aide des commandes suivantes, il en produira exactement une Success:

cpp -P src/Main.java Main.java && javac Main.java && java Main
Pado
la source
6
Cela ressemble à exécuter du code via une recherche et un remplacement avant de le compiler, mais je pourrais certainement voir quelqu'un faire quelque chose comme ça dans le cadre de son flux de travail
phflack
1
@phflack J'aime ça, sans doute cette question est de montrer les dangers de faire des hypothèses lors de la lecture de code. Il est facile de supposer que ac'est une variable, mais cela peut être n'importe quel morceau de code arbitraire dans des langages avec un préprocesseur.
kevin le
2
Ce n'est pas Java. C'est le langage "Java après être passé sur le préprocesseur C". Faille similaire utilisée par cette réponse. (note: sournois est hors sujet pour Code Golf maintenant)
user202729
40

Comme nous le savons déjà, il est possible de rendre ce code évalué à vrai grâce aux excellentes réponses d' Erwin Bolwidt et phflack , je voulais montrer que vous devez garder un haut niveau d'attention lorsque vous traitez avec une condition qui ressemble à celle présentée dans la question, car parfois ce que vous voyez n'est peut-être pas exactement ce que vous pensez.

C'est ma tentative pour montrer que ce code s'imprime Success!sur la console. Je sais que j'ai un peu triché , mais je pense toujours que c'est un bon endroit pour le présenter ici.

Quels que soient les objectifs d'écrire un code comme celui-ci, il vaut mieux savoir comment gérer la situation suivante et comment vérifier si vous ne vous trompez pas avec ce que vous pensez voir.

J'ai utilisé le cyrillique «a» qui est un caractère distinct du latin «a». Vous pouvez inspecter les caractères utilisés dans l'instruction if ici .

Cela fonctionne car les noms des variables proviennent de différents alphabets. Ce sont des identifiants distincts, créant deux variables distinctes avec une valeur différente dans chacune.

Notez que si vous voulez que ce code fonctionne correctement, le codage des caractères doit être remplacé par un codage prenant en charge les deux caractères, par exemple tous les codages Unicode (UTF-8, UTF-16 (en BE ou LE), UTF-32, même UTF-7 ), ou Windows-1251, ISO 8859-5, KOI8-R (merci - Thomas Weller et Paŭlo Ebermann - de l'avoir signalé):

public class A {
    public static void main(String[] args) {
        int а = 0;
        int a = 1;
        if == 0 && a == 1) {
            System.out.println("Success!");
        }
    }
}

(J'espère que vous n'aurez plus jamais à faire face à ce genre de problème à l'avenir.)

Przemysław Moskal
la source
@Michael Qu'est-ce que tu veux dire, ça ne se voit pas bien? Je l'ai écrit sur le téléphone portable et ici, cela me semble correct, même lorsque vous passez en vue de bureau. Si cela peut améliorer ma réponse, n'hésitez pas à la modifier pour que ce soit clair car je trouve cela un peu difficile maintenant.
Przemysław Moskal
@Michael Merci beaucoup. Je pensais que ce que j'avais admis à la fin de ma réponse était suffisant :)
Przemysław Moskal
@mohsenmadi Oui, je ne peux pas le vérifier ici sur téléphone portable, mais je suis presque sûr qu'avant la modification, la dernière phrase concernait l'utilisation de différentes langues dans mon exemple: P
Przemysław Moskal
Pourquoi █ == 0 devrait-il être similaire à a == 1?
Thomas Weller
1
Plus de pinaillage: vous n'avez pas nécessairement besoin d'UTF-8 (bien que cela soit recommandé de toute façon), de tout encodage de caractères qui a les deux аet afonctionne, tant que vous dites à votre éditeur dans quel encodage il se trouve (et peut-être aussi le compilateur). Tous les encodages Unicode fonctionnent (UTF-8, UTF-16 (en BE ou LE), UTF-32, même UTF-7), ainsi que par exemple Windows-1251, ISO 8859-5, KOI8-R.
Paŭlo Ebermann
27

Il existe une autre façon d'aborder cela (en plus de l'approche de course de données volatile que j'ai publiée plus tôt), en utilisant la puissance de PowerMock. PowerMock permet de remplacer les méthodes par d'autres implémentations. Lorsque cela est combiné avec le déballage automatique, l'expression d'origine (a == 1 && a == 2 && a == 3), sans modification, peut être rendue vraie.

La réponse de @ phflack repose sur la modification du processus d'auto-boxing en Java qui utilise l' Integer.valueOf(...)appel. L'approche ci-dessous repose sur la modification du déballage automatique en modifiant l' Integer.intValue()appel.

L'avantage de l'approche ci-dessous est que l'instruction if originale donnée par l'OP dans la question est utilisée sans changement, ce qui est à mon avis le plus élégant.

import static org.powermock.api.support.membermodification.MemberMatcher.method;
import static org.powermock.api.support.membermodification.MemberModifier.replace;

import java.util.concurrent.atomic.AtomicInteger;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@PrepareForTest(Integer.class)
@RunWith(PowerMockRunner.class)
public class Ais123 {
    @Before
    public void before() {
        // "value" is just a place to store an incrementing integer
        AtomicInteger value = new AtomicInteger(1);
        replace(method(Integer.class, "intValue"))
            .with((proxy, method, args) -> value.getAndIncrement());
    }

    @Test
    public void test() {
        Integer a = 1;

        if (a == 1 && a == 2 && a == 3) {
            System.out.println("Success");
        } else {
            Assert.fail("(a == 1 && a == 2 && a == 3) != true, a = " + a.intValue());
        }
    }

}
Erwin Bolwidt
la source
Powermock peut-il remplacer les méthodes synthétiques comme les access$nnnméthodes utilisées pour lire les privatechamps des classes internes / externes? Cela permettrait d'autres variantes intéressantes (qui fonctionnent même avec une intvariable)…
Holger
@Holger Idée intéressante, je vois ce que tu veux dire. Cela ne fonctionne pas encore - on ne sait pas ce qui l'empêche de fonctionner.
Erwin Bolwidt
Vraiment bien, c'est ce que j'essayais de faire, mais j'ai trouvé que changer la méthode statique d'une classe immuable serait assez difficile sans manipuler le bytecode. Il semble que j'avais tort, je n'ai jamais entendu parler de PowerMock, je me demande comment il fait cela. En remarque, la réponse de phflack ne repose pas sur l'autoboxing: il change les adresses de l'entier mis en cache (donc les == comparent en fait des adresses d'objet Integer plutôt que des valeurs).
Asoub
2
@Asoub le casting ( (Integer)2) encadre l'int. En regardant plus dans la réflexion , il semble qu'il n'est pas possible de le faire avec unboxing utilisant la réflexion, mais cela peut être possible avec Instrumentation à la place (ou avec PowerMock, comme dans cette réponse)
phflack
@Holger il n'intercepte probablement pas la méthode synthétique, bien que cela me permette d'enregistrer un remplaçant access$0et que l'existence d'une méthode soit vérifiée lors de l'enregistrement. Mais le remplacement n'est jamais invoqué.
Erwin Bolwidt
17

Puisque cela semble être un suivi de cette question JavaScript , il convient de noter que cette astuce et d'autres fonctionnent également en Java:

public class Q48383521 {
    public static void main(String[] args) {
        int a = 1;
        int 2 = 3;
        int a = 3;
        if(aᅠ==1 && a==ᅠ2 && a==3) {
            System.out.println("success");
        }
    }
}

Sur Ideone


Mais notez que ce n'est pas la pire chose que vous puissiez faire avec Unicode. L'utilisation d'espaces blancs ou de caractères de contrôle qui sont des parties d'identifiants valides ou l' utilisation de lettres différentes qui se ressemblent crée toujours des identifiants différents et qui peuvent être repérés, par exemple lors d'une recherche de texte.

Mais ce programme

public class Q48383521 {
    public static void main(String[] args) {
        int ä = 1;
        int ä = 2;
        if(ä == 1 && ä == 2) {
            System.out.println("success");
        }
    }
}

utilise deux identificateurs identiques, au moins du point de vue Unicode. Ils utilisent simplement différentes manières pour encoder le même caractère ä, en utilisant U+00E4et U+0061 U+0308.

Sur Ideone

Ainsi, en fonction de l'outil que vous utilisez, ils peuvent non seulement se ressembler, mais les outils de texte compatibles Unicode peuvent même ne pas signaler de différence, trouvant toujours les deux lors de la recherche. Vous pouvez même avoir le problème que les différentes représentations se perdent lors de la copie du code source à quelqu'un d'autre, essayant peut-être d'obtenir de l'aide pour le «comportement étrange», le rendant non reproductible pour l'assistant.

Holger
la source
3
Eh, cette réponse ne couvre- t-elle pas assez bien les abus Unicode?
Michael
2
int ᅠ2 = 3;est-ce intentionnel? Parce que je vois du code très étrange tout autour
Ravi
1
@Michael pas exactement, car cette réponse concerne l'utilisation de deux lettres différentes (que les IDE considéreraient comme différentes lors de la recherche a), alors qu'il s'agit d'espaces blancs, et bien, cela donne du crédit aux réponses encore plus anciennes sur la question JavaScript associée. Notez que mon commentaire est encore plus ancien que la question Java (de plusieurs jours)…
Holger
2
Je ne vois pas la distinction. Les deux réponses fournissent une solution où les trois identifiants dans l'instruction if sont visuellement similaires mais techniquement distincts en raison de l'utilisation de caractères Unicode inhabituels. Qu'il s'agisse d'espaces blancs ou cyrillique, cela n'a pas d'importance.
Michael
2
@Michael, vous pouvez le voir de cette façon, c'est à vous de décider. Comme dit, je tenais à préciser que la réponse donnée il y a cinq jours pour JavaScript s'applique également à Java, juste en considérant l'historique de la question. Oui, c'est quelque peu redondant avec une autre réponse qui ne fait pas référence à la question JavaScript liée. Quoi qu'il en soit, en attendant, j'ai mis à jour ma réponse pour ajouter un cas qui ne concerne pas des caractères visuellement similaires, mais différentes façons d'encoder le même caractère et ce n'est même pas un «caractère unicode inhabituel»; c'est un personnage que j'utilise tous les jours…
Holger
4

Inspiré par l'excellente réponse de @ Erwin , j'ai écrit un exemple similaire, mais en utilisant l' API Java Stream .

Et une chose intéressante est que ma solution fonctionne, mais dans de très rares cas (car le   just-in-timecompilateur optimise un tel code).

L'astuce consiste à désactiver toutes les JIToptimisations à l'aide de l' VMoption suivante :

-Djava.compiler=NONE

Dans cette situation, le nombre de cas de réussite augmente considérablement. Voici le code:

class Race {
    private static int a;

    public static void main(String[] args) {
        IntStream.range(0, 100_000).parallel().forEach(i -> {
            a = 1;
            a = 2;
            a = 3;
            testValue();
        });
    }

    private static void testValue() {
        if (a == 1 && a == 2 && a == 3) {
            System.out.println("Success");
        }
    }
}

PS Les flux parallèles utilisent ForkJoinPoolsous le capot, et la variable a est partagée entre plusieurs threads sans aucune synchronisation, c'est pourquoi le résultat est non déterministe.

Oleksandr Pyrohov
la source
1

Dans le même ordre d'idées , en forçant un flottant (ou double) à déborder (ou à déborder) par division (ou multiplication) par un grand nombre:

int a = 1;
if (a / Float.POSITIVE_INFINITY == 1 / Float.POSITIVE_INFINITY
        && a / Float.POSITIVE_INFINITY == 2 / Float.POSITIVE_INFINITY
        && a / Float.POSITIVE_INFINITY == 3 / Float.POSITIVE_INFINITY) {
    System.out.println("Success");
}
Aloke
la source
2
@PatrickRoberts - ce comportement spécifique est un sous-débordement en virgule flottante selon la documentation Java . Voir aussi ceci , ceci , ceci et cela .
Aloke le