¿Puede (a == 1 && a == 2 && a == 3) evaluar como verdadero en Java?

176

Sabemos que puede en JavaScript .

Pero, ¿es posible imprimir el mensaje "Éxito" en la condición dada a continuación en Java?

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

Alguien sugirió:

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

Pero al hacer esto, estamos cambiando la variable real. ¿Hay alguna otra manera?

Taylor Sen
fuente
55
&&es un andoperador lógico , lo que significa que adebe tener los valores 1, 2 y 3 al mismo tiempo, lo que es lógicamente imposible. La respuesta es NO, no es posible. ¿Desea escribir una ifdeclaración que verifique si atiene uno de los valores 1, 2 O 3?
Boris Pavlović
16
Nota a todos: Esta pregunta podría venir de la misma pregunta para Javascript: stackoverflow.com/questions/48270127/... , en cuyo caso la respuesta esyes
28
@ArthurAttout No es un duplicado, esa pregunta es para Javascript, no para Java.
nos
13
El que usa espacios en blanco en nombres de variables también funciona en Java. Pero, por supuesto, esto es básicamente lo mismo que usar _, excepto que no puede verlo.
tobias_k
8
@Seelenvirtuose Verdadero, pero if(a==1 && a==2 && a==3)no necesariamente se evalúa al mismo tiempo. Y eso se puede usar para hacer que esto funcione sin tener que recurrir a trucos Unicode.
Erwin Bolwidt

Respuestas:

324

Sí, es bastante fácil lograr esto con múltiples hilos, si declaras que la variable aes volátil.

Un hilo cambia constantemente la variable a de 1 a 3, y otro hilo lo prueba constantemente a == 1 && a == 2 && a == 3. Ocurre con la frecuencia suficiente para tener un flujo continuo de "Éxito" impreso en la consola.

(Tenga en cuenta que si agrega una else {System.out.println("Failure");}cláusula, verá que la prueba falla con mucha más frecuencia de la que tiene éxito).

En la práctica, también funciona sin declararlo acomo volátil, pero solo 21 veces en mi MacBook. Sin volatile, el compilador o HotSpot puede almacenar en caché ao reemplazar la ifdeclaración con if (false). Lo más probable es que HotSpot se active después de un tiempo y lo compile en instrucciones de ensamblaje que guarden en caché el valor de a. Con volatile , sigue imprimiendo "Éxito" para siempre.

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
fuente
83
Este es un ejemplo brillante! Creo que lo robaré la próxima vez que esté discutiendo problemas de concurrencia potencialmente peligrosos con un desarrollador junior :)
Alex Pruss
66
No es muy probable sin esto volatile, pero en principio, esto puede suceder incluso sin la volatilepalabra clave y podría suceder un número arbitrario de veces, mientras que, por otro lado, no hay garantía de que suceda, incluso con volatile. Pero, por supuesto, tener que ocurra en la práctica, es tranquilo impresionante ...
Holger
55
@AlexPruss Lo acabo de hacer en todos los desarrolladores, no solo en juniors en mi empresa ( sabía que podría ser posible), 87% de éxito de fallas en las respuestas
Eugene
3
@Holger True, no hay garantías de ninguno de los dos. En una arquitectura típica de múltiples núcleos completos o múltiples CPU, donde ambos subprocesos se asignan a un núcleo separado, es bastante probable que suceda volatile. Las barreras de memoria creadas para implementar volatiledisminuyen la velocidad de los subprocesos y hacen que sea más probable que funcionen sincronizados durante cortos períodos de tiempo. Sucede mucho más a menudo de lo que esperaba. Es muy sensible al tiempo, pero veo aproximadamente que entre 0.2% y 0.8% de las evaluaciones de (a == 1 && a == 2 && a == 3)retorno true.
Erwin Bolwidt
44
Esta es la única respuesta que realmente hace que el código deseado regrese verdadero en lugar de solo hacer pequeñas variaciones del mismo. +1
Alejandro
85

Usando conceptos (y código) de una respuesta de golf de código brillante , los Integervalores pueden ser alterados.

En este caso, puede hacer que ints se convierta en Integers igual cuando normalmente no serían:

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");
    }
}

Desafortunadamente, no es tan elegante como la respuesta multiproceso de Erwin Bolwidt (ya que esta requiere un Integercasting) , pero aún tienen lugar algunas travesuras divertidas.

phflack
fuente
Muy interesante. Estaba jugando con el abuso Integer. Es una pena lo del reparto, pero sigue siendo genial.
Michael
@Michael No puedo entender cómo deshacerme del casting. aestar configurado en 1/2/3 satisfará a == 1, pero no va para otro lado
phflack
44
Respondí esta pregunta hace unos días en JS / Ruby / Python y Java. La versión menos fea que pude encontrar fue usar a.equals(1) && a.equals(2) && a.equals(3), lo que obliga 1, 2y 3ser autoboxed como Integers.
Eric Duminil
@EricDuminil Eso podría quitarle algo de diversión, porque ¿qué pasaría si hicieras auna clase boolean equals(int i){return true;}?
phflack
1
Como dije, es el menos feo pero aún no es óptimo. Con Integer a = 1;en la línea anterior, todavía está claro que arealmente es un número entero.
Eric Duminil
52

En esta pregunta, @aioobe sugiere (y desaconseja) el uso del preprocesador C para las clases Java.

Aunque es extremadamente tramposo, esa es mi solución:

#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");
    }
}

Si se ejecuta utilizando los siguientes comandos, generará exactamente uno Success:

cpp -P src/Main.java Main.java && javac Main.java && java Main
Pado
fuente
66
Esto se siente como ejecutar código a través de buscar y reemplazar antes de compilarlo, pero definitivamente podría ver a alguien haciendo algo así como parte de su flujo de trabajo
phflack
1
@phflack Me gusta, podría decirse que esta pregunta trata sobre mostrar los peligros de hacer suposiciones al leer el código. Es fácil suponer que aes una variable, pero puede ser cualquier fragmento de código arbitrario en idiomas con un preprocesador.
Kevin
2
No es Java Es el lenguaje "Java después de pasar por el preprocesador C". Laguna similar utilizada por esta respuesta. (nota: solapada está fuera de tema para el código de golf ahora)
user202729
40

Como ya sabemos que es posible hacer que este código se evalúe como verdadero gracias a las excelentes respuestas de Erwin Bolwidt y phflack , quería mostrarle que debe mantener un alto nivel de atención cuando se trata de una condición similar a la presentada en la pregunta, ya que a veces lo que ves puede no ser exactamente lo que crees que es.

Este es mi intento de mostrar que este código se imprime Success!en la consola. Sé que hice trampa un poco , pero sigo pensando que este es un buen lugar para presentarlo aquí mismo.

No importa cuáles sean los propósitos de escribir código como este: es mejor saber cómo lidiar con la siguiente situación y cómo verificar si no está equivocado con lo que cree que ve.

Usé el cirílico 'a', que es un carácter distinto del latín 'a'. Puede inspeccionar los caracteres utilizados en la declaración if aquí .

Esto funciona porque los nombres de las variables se toman de diferentes alfabetos. Son identificadores distintos, que crean dos variables distintas con un valor diferente en cada uno.

Tenga en cuenta que si desea que este código funcione correctamente, la codificación de caracteres debe cambiarse a una que admita ambos caracteres, por ejemplo, todas las codificaciones Unicode (UTF-8, UTF-16 (en BE o LE), UTF-32, incluso UTF-7 ), o Windows-1251, ISO 8859-5, KOI8-R (gracias - Thomas Weller y Paŭlo Ebermann - por señalarlo):

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

(Espero que nunca tenga que lidiar con ese tipo de problema en el futuro).

Przemysław Moskal
fuente
@ Michael ¿Qué quieres decir con que no se ve bien? Lo escribí en el teléfono móvil y aquí me parece bien, incluso cuando cambio a la vista de escritorio. Si mejoraría mi respuesta, por favor siéntase libre de editarlo para dejarlo claro ya que ahora me resulta un poco difícil.
Przemysław Moskal
@Michael Muchas gracias. Pensé que lo que admití al final de mi respuesta es suficiente :)
Przemysław Moskal
@mohsenmadi Sí, no puedo verificarlo aquí en el teléfono móvil, pero estoy bastante seguro de que antes de editar, la última oración fue sobre el uso de diferentes idiomas en mi ejemplo: P
Przemysław Moskal
¿Por qué █ == 0 debe ser similar a a == 1?
Thomas Weller
1
Más nitpicking: no necesariamente necesita UTF-8 (aunque eso se recomienda de todos modos), cualquier codificación de caracteres que tenga ambos аy afuncione, siempre que le diga a su editor en qué codificación está (y posiblemente también el compilador). Todas las codificaciones Unicode funcionan (UTF-8, UTF-16 (en BE o LE), UTF-32, incluso UTF-7), así como, por ejemplo, Windows-1251, ISO 8859-5, KOI8-R.
Paŭlo Ebermann el
27

Hay otra forma de abordar esto (además del enfoque volátil de carreras de datos que publiqué anteriormente), utilizando el poder de PowerMock. PowerMock permite que los métodos sean reemplazados por otras implementaciones. Cuando eso se combina con el desempaquetado automático, la expresión original (a == 1 && a == 2 && a == 3), sin modificación, puede hacerse realidad.

La respuesta de @ phflack se basa en modificar el proceso de auto-boxeo en Java que usa la Integer.valueOf(...)llamada. El siguiente enfoque se basa en modificar el desempaquetado automático al cambiar la Integer.intValue()llamada.

La ventaja del siguiente enfoque es que la declaración if original dada por el OP en la pregunta se usa sin cambios, lo que creo que es el más elegante.

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
fuente
¿Puede Powermock reemplazar métodos sintéticos como los access$nnnmétodos utilizados para leer privatecampos de clases internas / externas? Eso permitiría algunas otras variantes interesantes (que incluso funcionan con una intvariable) ...
Holger
@Holger Idea interesante, veo a qué te refieres. Todavía no funciona, no está claro qué impide que funcione.
Erwin Bolwidt el
Realmente agradable, eso es lo que estaba tratando de hacer, pero descubrí que cambiar el método estático de una clase inmutable sería bastante difícil sin manipular el código de bytes. Parece que estaba equivocado, aunque nunca escuché hablar de PowerMock, me pregunto cómo lo hace. Como nota al margen, la respuesta de phflack no se basa en el autoboxing: cambia las direcciones del Integer en caché (por lo que los == en realidad comparan las direcciones de los objetos Integer en lugar de los valores).
Asoub
2
@Asoub the casting ( (Integer)2) encajona el int. Mirando más en la reflexión , parece que no es posible hacer esto con unboxing usando la reflexión, pero puede ser posible con Instrumentation en su lugar (o con PowerMock, como en esta respuesta)
phflack
@Holger probablemente no intercepte el método sintético, aunque me permite registrar un reemplazo access$0y la existencia de un método se verifica en el registro. Pero el reemplazo nunca se invoca.
Erwin Bolwidt
17

Dado que esto parece ser un seguimiento de esta pregunta de JavaScript , vale la pena señalar que este truco y otros similares también funcionan 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");
        }
    }
}

En Ideone


Pero tenga en cuenta que esto no es lo peor que podría hacer con Unicode. El uso de espacios en blanco o caracteres de control que son partes de identificadores válidos o el uso de letras diferentes que se ven iguales todavía crea identificadores que son diferentes y se pueden detectar, por ejemplo, cuando se realiza una búsqueda de texto.

Pero este programa

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

utiliza dos identificadores que son iguales, al menos desde el punto de vista Unicode. Simplemente usan diferentes formas de codificar el mismo carácter ä, usando U+00E4y U+0061 U+0308.

En Ideone

Por lo tanto, dependiendo de la herramienta que esté utilizando, es posible que no solo se vean iguales, sino que las herramientas de texto habilitadas para Unicode ni siquiera informan ninguna diferencia, siempre encuentran ambas al buscar. Incluso puede tener el problema de que las diferentes representaciones se pierden al copiar el código fuente a otra persona, tal vez tratando de obtener ayuda para el "comportamiento extraño", haciéndolo no reproducible para el ayudante.

Holger
fuente
3
Eh, ¿ esta respuesta no cubre el abuso unicode lo suficientemente bien?
Michael
2
int ᅠ2 = 3;es intencional? Porque, estoy viendo un código muy extraño por todas partes
Ravi
1
@Michael no exactamente, ya que esa respuesta se trata de usar dos letras diferentes (que los IDEs considerarían diferentes al buscar a), mientras que se trata de espacios en blanco, y bueno, está dando crédito a las respuestas aún más antiguas en la pregunta de JavaScript relacionada. Tenga en cuenta que mi comentario allí es incluso más antiguo que la pregunta de Java (por varios días) ...
Holger
2
No veo la distinción. Ambas respuestas proporcionan una solución donde los tres identificadores en la declaración if son visualmente similares pero técnicamente distintos en virtud del uso de caracteres unicode inusuales. Ya sea espacio en blanco o cirílico es irrelevante.
Michael
2
@ Michael puedes verlo de esa manera, eso depende de ti. Como dije, quería decir que la respuesta dada hace cinco días para JavaScript también se aplica a Java, solo considerando el historial de la pregunta. Sí, eso es algo redundante a otra respuesta que no se refiere a la pregunta de JavaScript vinculada. De todos modos, mientras tanto, actualicé mi respuesta para agregar un caso que no se trata de caracteres visualmente similares, sino de diferentes formas de codificar el mismo carácter y que ni siquiera es un "carácter unicode inusual"; es un personaje que uso todos los días ...
Holger
4

Inspirado por la excelente respuesta de @ Erwin , escribí un ejemplo similar, pero usando Java Stream API .

Y una cosa interesante es que mi solución funciona, pero en casos muy raros (porque el   just-in-timecompilador optimiza dicho código).

El truco consiste en deshabilitar cualquier JIToptimización utilizando la siguiente VMopción:

-Djava.compiler=NONE

En esta situación, el número de casos de éxito aumenta significativamente. Aquí está el código:

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");
        }
    }
}

Las transmisiones paralelas de PS se usan ForkJoinPooldebajo del capó, y la variable a se comparte entre múltiples subprocesos sin ninguna sincronización, es por eso que el resultado no es determinista.

Oleksandr Pyrohov
fuente
1

En líneas similares , forzando un flotador (o doble) a un flujo inferior (o desbordamiento) a través de la división (o multiplicación) por un gran número:

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
fuente
2
@PatrickRoberts: este comportamiento específico es un flujo inferior de coma flotante según los documentos de Java . También vea esto , esto , esto y esto .
Aloke