(A == 1 && a == 2 && a == 3) có thể đánh giá là đúng trong Java không?


176

Chúng tôi biết nó có thể trong JavaScript .

Nhưng có thể in thông báo "Thành công" với điều kiện được đưa ra dưới đây trong Java không?

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

Có người đề nghị:

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

Nhưng bằng cách này, chúng tôi đang thay đổi các biến thực tế. Còn cách nào khác không?


5
&&andtoán tử logic , có nghĩa là aphải có giá trị 1, 2 và 3 cùng một lúc, điều này là không thể. Câu trả lời là KHÔNG, không thể. Bạn có muốn viết một ifcâu lệnh kiểm tra xem acó một trong các giá trị 1, 2 HOẶC 3 không?
Boris Pavlović

16
Lưu ý cho mọi người: Câu hỏi này có thể đến từ cùng một câu hỏi cho Javascript: stackoverflow.com/questions/48270127/ , trong trường hợp đó câu trả lời làyes
nos

28
@ArthurAttout Không phải là một bản sao, câu hỏi đó là dành cho Javascript, không phải Java.
số

13
Cái sử dụng khoảng trắng trong tên biến cũng hoạt động trong Java. Nhưng tất nhiên điều này về cơ bản giống như sử dụng _, ngoại trừ bạn không thể nhìn thấy nó.
tobias_k

8
@Seelenvirtuose Đúng, nhưng if(a==1 && a==2 && a==3)không nhất thiết phải được đánh giá cùng một lúc. Và điều đó có thể được sử dụng để làm cho công việc này hoạt động mà không cần phải dùng đến các thủ thuật Unicode.
Erwin Bolwidt

Câu trả lời:


324

Có, khá dễ dàng để đạt được điều này với nhiều luồng, nếu bạn khai báo biến alà biến động.

Một luồng liên tục thay đổi biến a từ 1 đến 3 và một luồng khác liên tục kiểm tra điều đó a == 1 && a == 2 && a == 3. Nó thường xảy ra đủ để có một dòng "Thành công" liên tục được in trên bảng điều khiển.

(Lưu ý nếu bạn thêm một else {System.out.println("Failure");}mệnh đề, bạn sẽ thấy thử nghiệm thất bại thường xuyên hơn nhiều so với thành công.)

Trong thực tế, nó cũng hoạt động mà không cần khai báo alà không ổn định, nhưng chỉ 21 lần trên MacBook của tôi. Nếu không volatile, trình biên dịch hoặc HotSpot được phép lưu trữ ahoặc thay thế ifcâu lệnh bằng if (false). Rất có thể, HotSpot sẽ khởi động sau một lúc và biên dịch nó thành các hướng dẫn lắp ráp làm bộ đệm giá trị của a. Với volatile , nó tiếp tục in "Thành công" mãi mãi.

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

83
Đây là một ví dụ tuyệt vời! Tôi nghĩ rằng tôi sẽ ăn cắp nó vào lần tới khi tôi thảo luận về các vấn đề tương tranh nguy hiểm tiềm tàng với một nhà phát triển cơ sở :)
Alex Pruss

6
Nó không có khả năng mà không có volatile, nhưng về nguyên tắc, điều này thậm chí có thể xảy ra nếu không có volatiletừ khóa và nó có thể xảy ra một số lần tùy ý, trong khi, mặt khác, không có gì đảm bảo rằng nó sẽ xảy ra, ngay cả với volatile. Nhưng tất nhiên, có nó xảy ra trong thực tế, thì ấn tượng lặng lẽ
Holger

5
@AlexPruss Tôi vừa mới làm trên tất cả các nhà phát triển, không chỉ là đàn em trong công ty của tôi (tôi biết điều đó có thể xảy ra), 87% thành công của những thất bại trong câu trả lời
Eugene

3
@Holger Đúng, không có gì đảm bảo. Trên một kiến ​​trúc đa lõi hoặc đa lõi điển hình, trong đó cả hai luồng được gán cho một lõi riêng biệt, rất có khả năng điều đó sẽ xảy ra volatile. Các rào cản bộ nhớ được tạo để thực hiện volatilelàm chậm các luồng và làm cho nhiều khả năng chúng hoạt động không đồng bộ trong khoảng thời gian ngắn. Nó xảy ra thường xuyên hơn tôi mong đợi. Nó rất nhạy cảm về thời gian, nhưng tôi thấy khoảng từ 0,2% đến 0,8% đánh giá (a == 1 && a == 2 && a == 3)lợi nhuận true.
Erwin Bolwidt

4
Đây là câu trả lời duy nhất thực sự làm cho mã dự định trở về đúng thay vì chỉ thực hiện các biến thể nhỏ giống nhau. +1
Alejandro

85

Sử dụng các khái niệm (và mã) từ một câu trả lời golf mã xuất sắc , Integercác giá trị có thể bị rối tung.

Trong trường hợp này, nó có thể làm cho intcác cast thành Integers bằng nhau khi chúng thường không:

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

Thật không may, nó không hoàn toàn thanh lịch như câu trả lời đa luồng của Erwin Bolwidt (vì Integercâu hỏi này đòi hỏi phải đúc) , nhưng vẫn có một số shenanigans vui vẻ diễn ra.


Rất thú vị. Tôi đã chơi với lạm dụng Integer. Thật xấu hổ về buổi casting nhưng nó vẫn rất tuyệt.
Michael

@Michael Tôi không thể tìm ra cách thoát khỏi buổi casting. ađược đặt thành 1/2/3 sẽ thỏa mãn a == 1, nhưng nó không đi theo hướng khác
phflack

4
Tôi đã trả lời câu hỏi này vài ngày trước trong JS / Ruby / Python và Java. Phiên bản nhất xấu xí tôi có thể tìm được sử dụng a.equals(1) && a.equals(2) && a.equals(3), trong đó lực lượng 1, 23được autoboxed như Integers.
Eric Duminil

@EricDuminil Điều đó có thể mang lại một số niềm vui, bởi vì nếu bạn trở athành một lớp học boolean equals(int i){return true;}thì sao?
phflack

1
Như tôi đã nói, nó xấu nhất nhưng vẫn không tối ưu. Với Integer a = 1;dòng trước, rõ ràng nó vẫn alà một Integer.
Eric Duminil

52

Trong câu hỏi này @aioobe gợi ý (và khuyên chống lại) việc sử dụng bộ tiền xử lý C cho các lớp Java.

Mặc dù nó cực kỳ gian lận, đó là giải pháp của tôi:

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

Nếu được thực hiện bằng các lệnh sau, nó sẽ xuất ra chính xác một Success:

cpp -P src/Main.java Main.java && javac Main.java && java Main

6
Loại cảm giác này giống như chạy mã thông qua tìm và thay thế trước khi thực sự biên dịch nó, nhưng tôi chắc chắn có thể thấy ai đó làm điều gì đó như một phần của quy trình làm việc của họ
phflack

1
@phflack Tôi thích nó, có thể nói rằng câu hỏi này là về việc thể hiện sự nguy hiểm của việc đưa ra các giả định khi đọc mã. Thật dễ dàng để giả sử alà một biến, nhưng nó có thể là bất kỳ đoạn mã tùy ý nào trong các ngôn ngữ có bộ tiền xử lý.
kevin

2
Đó không phải là Java. Đó là ngôn ngữ "Java sau khi vượt qua bộ tiền xử lý C". Lỗ hổng tương tự được sử dụng bởi câu trả lời này . (lưu ý: underhandedlạc đề đối với Code Golf ngay bây giờ)
user202729

40

Như chúng ta đã biết rằng có thể đánh giá mã này thành đúng nhờ các câu trả lời tuyệt vời của Erwin Bolwidtphflack , tôi muốn chứng minh rằng bạn cần giữ sự chú ý cao khi xử lý một điều kiện giống như điều kiện được trình bày trong câu hỏi, vì đôi khi những gì bạn nhìn thấy có thể không chính xác như những gì bạn nghĩ.

Đây là nỗ lực của tôi để chỉ ra rằng mã này in Success!ra bàn điều khiển. Tôi biết tôi đã lừa dối một chút , nhưng tôi vẫn nghĩ rằng đây là một nơi tốt để trình bày nó ngay tại đây.

Bất kể mục đích của việc viết mã như thế này là gì - tốt hơn để biết cách xử lý tình huống sau đây và cách kiểm tra nếu bạn không sai với những gì bạn nghĩ bạn thấy.

Tôi đã sử dụng Cyrillic 'a', một ký tự riêng biệt với tiếng Latin 'a'. Bạn có thể kiểm tra các ký tự được sử dụng trong câu lệnh if ở đây .

Điều này hoạt động vì tên của các biến được lấy từ các bảng chữ cái khác nhau. Chúng là các định danh riêng biệt, tạo ra hai biến riêng biệt với mỗi giá trị khác nhau.

Lưu ý rằng nếu bạn muốn mã này hoạt động chính xác, mã hóa ký tự cần được thay đổi thành một ký tự hỗ trợ cả hai ký tự, ví dụ: tất cả các mã hóa Unicode (UTF-8, UTF-16 (trong BE hoặc LE), UTF-32, thậm chí UTF-7 ), hoặc Windows-1251, ISO 8859-5, KOI8-R (cảm ơn bạn - Thomas WellerPaŭlo Ebermann - vì đã chỉ ra):

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

(Tôi hy vọng bạn sẽ không bao giờ phải đối phó với vấn đề đó bất cứ lúc nào trong tương lai.)


@Michael Ý bạn là gì nó không xuất hiện tốt? Tôi đã viết nó trên điện thoại di động và ở đây nó có vẻ ổn với tôi, ngay cả khi chuyển sang chế độ xem trên máy tính để bàn. Nếu nó sẽ làm cho câu trả lời của tôi tốt hơn xin vui lòng chỉnh sửa nó tuo làm cho nó rõ ràng vì tôi thấy nó hơi khó khăn bây giờ.
Przemysław Moskal

@Michael Cảm ơn bạn rất nhiều. Tôi nghĩ rằng những gì tôi thừa nhận ở cuối câu trả lời của mình là đủ :)
Przemysław Moskal

@mohsenmadi Vâng, tôi không thể kiểm tra nó ở đây trên điện thoại di động, nhưng tôi khá chắc chắn rằng trước khi chỉnh sửa, câu cuối cùng là về việc sử dụng các ngôn ngữ khác nhau trong ví dụ của tôi: P
Przemysław Moskal

Tại sao == 0 lại giống với a == 1?
Thomas Weller

1
Thêm nitpicking: Bạn không nhất thiết cần UTF-8 (mặc dù đó là khuyến cáo dù sao), bất kỳ mã hóa ký tự nào có cả hai аahoạt động, miễn là bạn nói với biên tập viên của mình mã hóa trong đó (và có thể cả trình biên dịch nữa). Tất cả các mã hóa Unicode đều hoạt động (UTF-8, UTF-16 (bằng BE hoặc LE), UTF-32, thậm chí UTF-7), cũng như Windows-1251, ISO 8859-5, KOI8-R.
Paŭlo Ebermann

27

Có một cách khác để tiếp cận điều này (bổ sung cho phương pháp đua dữ liệu dễ bay hơi mà tôi đã đăng trước đó), sử dụng sức mạnh của PowerMock. PowerMock cho phép các phương thức được thay thế bằng các triển khai khác. Khi điều đó được kết hợp với tự động hủy hộp, biểu thức ban đầu (a == 1 && a == 2 && a == 3), mà không sửa đổi, có thể được thực hiện đúng.

Câu trả lời của @ phflack dựa vào việc sửa đổi quy trình tự động đấm bốc trong Java sử dụng lệnh Integer.valueOf(...)gọi. Cách tiếp cận dưới đây dựa vào sửa đổi tự động hủy hộp bằng cách thay đổi Integer.intValue()cuộc gọi.

Ưu điểm của cách tiếp cận dưới đây là câu lệnh if gốc do OP đưa ra trong câu hỏi được sử dụng không thay đổi, mà tôi nghĩ là thanh lịch nhất.

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

}

Powermock có thể thay thế các phương thức tổng hợp như các access$nnnphương thức được sử dụng để đọc privatecác trường của các lớp bên trong / bên ngoài không? Điều đó sẽ cho phép một số biến thể thú vị khác (thậm chí hoạt động với một intbiến) Số
Holger

@Holger Ý tưởng thú vị, tôi hiểu ý của bạn. Nó vẫn chưa hoạt động - không rõ điều gì ngăn nó hoạt động.
Erwin Bolwidt

Thực sự tốt, đó là những gì tôi đã cố gắng thực hiện, nhưng tôi thấy rằng việc thay đổi phương thức tĩnh từ lớp bất biến sẽ khá khó khăn nếu không thao tác mã byte. Có vẻ như tôi đã sai, chưa bao giờ nghe nói về PowerMock, tôi tự hỏi làm thế nào nó làm điều đó. Là một lưu ý phụ, câu trả lời của phflack không phụ thuộc vào hộp tự động: anh ta thay đổi địa chỉ của Số nguyên được lưu trong bộ nhớ cache (vì vậy == thực sự đang so sánh địa chỉ đối tượng Integer thay vì giá trị).
Asoub

2
@Asoub the casting ( (Integer)2) hộp int. Nhìn vào phản xạ nhiều hơn , có vẻ như không thể thực hiện điều này bằng cách hủy hộp bằng cách sử dụng phản chiếu, nhưng có thể có thể với Thiết bị thay thế (hoặc với PowerMock, như trong câu trả lời này)
phflack

@Holger có lẽ nó không chặn phương thức tổng hợp, mặc dù nó cho phép tôi đăng ký thay thế access$0và sự tồn tại của phương thức được kiểm tra khi đăng ký. Nhưng sự thay thế không bao giờ được viện dẫn.
Erwin Bolwidt

17

Vì đây có vẻ là phần tiếp theo của câu hỏi JavaScript này , điều đáng chú ý là thủ thuật này và các công việc tương tự cũng có trong 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");
        }
    }
}

Trên Ideone


Nhưng lưu ý rằng đây không phải là điều tồi tệ nhất bạn có thể làm với Unicode. Sử dụng các ký tự khoảng trắng hoặc các ký tự điều khiển là các phần định danh hợp lệ hoặc sử dụng các chữ cái khác nhau trông giống nhau vẫn tạo ra các mã định danh khác nhau và có thể được phát hiện, ví dụ như khi thực hiện tìm kiếm văn bản.

Nhưng chương trình này

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

sử dụng hai mã định danh giống nhau, ít nhất là theo quan điểm Unicode. Họ chỉ sử dụng các cách khác nhau để mã hóa cùng một ký tự ä, sử dụng U+00E4U+0061 U+0308.

Trên Ideone

Vì vậy, tùy thuộc vào công cụ bạn đang sử dụng, chúng có thể không chỉ trông giống nhau, các công cụ văn bản hỗ trợ Unicode thậm chí có thể không báo cáo bất kỳ sự khác biệt nào, luôn luôn tìm thấy cả khi tìm kiếm. Bạn thậm chí có thể gặp vấn đề khi các biểu diễn khác nhau bị mất khi sao chép mã nguồn cho người khác, có lẽ đang cố gắng giúp đỡ cho hành vi kỳ lạ của Hồi, làm cho nó không thể tái tạo được cho người trợ giúp.


3
Eh, câu trả lời này không bao gồm lạm dụng unicode đủ tốt?
Michael

2
int ᅠ2 = 3;Là cố ý? Bởi vì, tôi đang nhìn thấy mã rất kỳ lạ xung quanh
Ravi

1
@Michael không chính xác, vì câu trả lời đó là về việc sử dụng hai chữ cái khác nhau (mà IDE sẽ xem xét khác nhau khi tìm kiếm a), trong khi đây là về khoảng trắng, và tốt, nó cung cấp tín dụng cho các câu trả lời cũ hơn cho câu hỏi JavaScript liên quan. Lưu ý rằng nhận xét của tôi thậm chí còn cũ hơn câu hỏi Java (trước vài ngày),
Holger

2
Tôi không thấy sự khác biệt. Cả hai câu trả lời đều cung cấp một giải pháp trong đó ba định danh trong câu lệnh if giống nhau về mặt trực quan nhưng khác biệt về mặt kỹ thuật nhờ vào việc sử dụng các ký tự unicode bất thường. Cho dù đó là khoảng trắng hay Cyrillic đều không liên quan.
Michael

2
@Michael bạn có thể thấy nó theo cách đó, tùy bạn. Như đã nói, tôi muốn nói rằng câu trả lời được đưa ra năm ngày trước cho JavaScript cũng áp dụng cho Java, chỉ xem xét lịch sử của câu hỏi. Có, điều đó hơi thừa đối với một câu trả lời khác không liên quan đến câu hỏi JavaScript được liên kết. Dù sao, trong khi đó, tôi đã cập nhật câu trả lời của mình để thêm một trường hợp không phải về các ký tự tương tự trực quan, nhưng các cách khác nhau để mã hóa cùng một ký tự và nó thậm chí không phải là một ký tự unicode bất thường khác; đó là một nhân vật tôi đang sử dụng hàng ngày Ngày mai
Holger

4

Lấy cảm hứng từ câu trả lời tuyệt vời của @ Erwin , tôi đã viết một ví dụ tương tự, nhưng sử dụng API Java Stream .

Và một điều thú vị là giải pháp của tôi hoạt động, nhưng trong những trường hợp rất hiếm (vì   just-in-timetrình biên dịch tối ưu hóa mã như vậy).

Mẹo nhỏ là vô hiệu hóa mọi JITtối ưu hóa bằng VMtùy chọn sau :

-Djava.compiler=NONE

Trong tình huống này, số trường hợp thành công tăng đáng kể. Đây là mã:

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

Các luồng song song PS sử dụng ForkJoinPooldưới mui xe và biến a được chia sẻ giữa nhiều luồng mà không có bất kỳ sự đồng bộ hóa nào, đó là lý do tại sao kết quả không mang tính quyết định.


1

Dọc theo các dòng tương tự , bằng cách buộc một float (hoặc double) tràn xuống (hoặc tràn) thông qua phép chia (hoặc nhân) với một số lượng lớn:

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

2
@PatrickRoberts - hành vi cụ thể này là dòng chảy dấu phẩy động theo tài liệu Java . Cũng thấy cái này , cái này , cái này & cái này .
Aloke
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.