JavaRush /Java Blog /Random-KO /당신이 자바에 대해 몰랐던 10가지
minuteman
레벨 32

당신이 자바에 대해 몰랐던 10가지

Random-KO 그룹에 게시되었습니다
그렇다면 최근에 Java 작업을 시작하셨나요? "Oak"이라고 불렸던 시절, 객체 지향이 여전히 뜨거운 주제였던 시절, C++ 사람들이 Java에는 가능성이 없다고 생각했던 시절, 애플릿에 대해 들어본 사람조차 아무도 없던 시절을 기억하십니까? 나는 당신이 다음 사항 중 절반도 모른다고 가정할 수 있습니다. Java의 내부 작동에 대한 몇 가지 멋진 놀라움으로 한 주를 시작해 보겠습니다. 당신이 자바에 대해 몰랐던 10가지 - 11. 확인된 예외란 없습니다. 좋아요! JVM은 그런 일에 대해 전혀 모르고 오직 Java 언어만 알고 있습니다. 오늘날 모든 사람들은 확인된 예외가 실수였다는 데 동의합니다. Bruce Eckel이 프라하의 GeeCON에서의 마지막 강연에서 말했듯이 Java는 검사 예외를 사용하기 때문에 다른 언어는 없으며 Java 8조차도 더 이상 새로운 Streams API에서 이를 다루지 않습니다(람다가 IO 또는 JDBC를 사용할 때 약간 성가실 수 있음). ). JVM이 그런 것을 모른다는 증거를 원하십니까? 다음 코드를 시도해 보십시오. 이 코드는 컴파일될 뿐만 아니라 SQLException도 발생시킵니다. 이를 위해 Lombok의 @SneakyThrows를 사용할 필요조차 없습니다. 2. 반환 유형만 다른 ​​오버로드된 메서드를 가질 수 있습니다.public class Test { // No throws clause here public static void main(String[] args) { doThrow(new SQLException()); } static void doThrow(Exception e) { Test. doThrow0(e); } @SuppressWarnings("unchecked") static void doThrow0(Exception e) throws E { throw (E) e; } } 이건 컴파일되지 않을 거예요, 그렇죠? class Test { Object x() { return "abc"; } String x() { return "123"; } } 오른쪽. Java 언어에서는 throw 또는 반환 유형의 차이에 관계없이 동일한 클래스 내에서 동시에 두 메서드를 동등하게 재정의하는 것을 허용하지 않습니다. 하지만 잠깐만요. Class.getMethod(String, Class…)에 대한 설명서를 다시 확인하세요. 다음과 같이 말합니다. 클래스에 해당 메소드가 두 개 이상 있을 수 있습니다. Java 언어는 서명은 동일하지만 반환 유형이 다른 여러 메소드를 금지하는 반면 Java Virtual Machine은 그렇지 않기 때문입니다. 가상 머신의 이러한 유연성을 사용하여 다양한 언어 기능을 구현할 수 있습니다. 예를 들어 공변 반환은 브리지 메서드를 사용하여 수행할 수 있습니다. 브리지 메서드와 재정의된 메서드는 동일한 시그니처를 가지지만 반환 유형은 다릅니다. 와, 말이 되네요. 다음을 작성할 때 실제로 많은 일이 일어나고 있습니다. 생성된 바이트코드를 보십시오. 따라서 t는 실제로 바이트코드의 객체입니다. 이것은 잘 이해됩니다. 호출의 특정 부분에서 Parent.x()의 반환 유형을 예상할 수 있기 때문에 합성 브리지 메서드는 실제로 컴파일러에 의해 생성됩니다. 이러한 브리지 메서드 없이 제네릭을 추가하는 것은 더 이상 이진 표현에서 불가능합니다. 따라서 이러한 기능을 허용하도록 JVM을 변경하면 고통이 줄어듭니다(부작용으로 공변 방법 재정의도 허용됩니다...). 현명합니까? 3. 다음은 모두 2차원 배열이다. 이것은 실제로 사실입니다. 멘탈 분석기가 위에서 설명한 메서드의 반환 유형을 즉시 이해하지 못하더라도 모두 동일합니다! 다음 코드와 같습니다. 이게 미친 짓이라고 생각하세요? 글을 쓸 수 있는 기회의 수도 그야말로 상상력을 폭발시킵니다! 주석을 입력합니다. 그 힘에 이어 신비로움이 두 번째인 장치입니다. 즉, 4주간의 휴가 직전에 마지막 커밋을 할 때입니다. 원하는 방식으로 사용하도록 허가합니다. 4. 조건문을 얻을 수 없습니다 . 조건문을 사용하기 시작했을 때 이미 조건문에 대한 모든 것을 알고 있다고 생각하셨나요? 당신을 실망시키겠습니다. 당신은 틀렸습니다. 여러분 대부분은 다음 두 가지 예가 동일하다고 생각할 것입니다. 이것과 동일합니까? 아니요. 간단한 테스트를 사용해 보겠습니다. 프로그램은 다음을 출력합니다. 예! 조건 연산자는 필요한 경우 유형 캐스트를 수행합니다. 그렇지 않으면 프로그램이 NullPointerException을 던질 것이라고 예상할 수 있기 때문입니까? 5. 복합 할당 연산자도 얻을 수 없습니다. 수완이 충분합니까? 다음 두 가지 코드 조각을 살펴보겠습니다. abstract class Parent { abstract T x(); } class Child extends Parent { @Override String x() { return "abc"; } } // Method descriptor #15 ()Ljava/lang/String; // Stack: 1, Locals: 1 java.lang.String x(); 0 ldc [16] 2 areturn Line numbers: [pc: 0, line: 7] Local variable table: [pc: 0, pc: 3] local: this index: 0 type: Child // Method descriptor #18 ()Ljava/lang/Object; // Stack: 1, Locals: 1 bridge synthetic java.lang.Object x(); 0 aload_0 [this] 1 invokevirtual Child.x() : java.lang.String [19] 4 areturn Line numbers: [pc: 0, line: 1] class Test { int[][] a() { return new int[0][]; } int[] b() [] { return new int[0][]; } int c() [][] { return new int[0][]; } } class Test { int[][] a = {{}}; int[] b[] = {{}}; int c[][] = {{}}; } @Target(ElementType.TYPE_USE) @interface Crazy {} class Test { @Crazy int[][] a1 = {{}}; int @Crazy [][] a2 = {{}}; int[] @Crazy [] a3 = {{}}; @Crazy int[] b1[] = {{}}; int @Crazy [] b2[] = {{}}; int[] b3 @Crazy [] = {{}}; @Crazy int c1[][] = {{}}; int c2 @Crazy [][] = {{}}; int c3[] @Crazy [] = {{}}; } 당신이 자바에 대해 몰랐던 10가지 - 2 Object o1 = true ? new Integer(1) : new Double(2.0); Object o2; if (true) o2 = new Integer(1); else o2 = new Double(2.0); System.out.println(o1); System.out.println(o2); 1.0 1 Integer i = new Integer(1); if (i.equals(1)) i = null; Double d = new Double(2.0); Object o = true ? i : d; // NullPointerException! System.out.println(o); i += j; i = i + j; 직관적으로 그들은 동일해야 합니까? 그러나 당신은 알고 있습니다 - 그들은 다릅니다. JLS 사양에 따르면 E1 op = E2 유형의 복합 표현식은 E1 = (T) ((E1) op (E2))와 동일합니다. 여기서 T는 E1이 한 번만 평가된다는 점을 제외하면 E1의 유형입니다. 좋은 예는 *= 또는 /= : byte b = 10; b *= 5.7; System.out.println(b); // prints 57 또는: byte b = 100; b /= 2.5; System.out.println(b); // prints 40 또는: char ch = '0'; ch *= 1.1; System.out.println(ch); // prints '4' 또는: 을 사용하는 것입니다 char ch = 'A'; ch *= 1.5; System.out.println(ch); // prints 'a' . 그렇다면 이것이 여전히 유용한 도구입니까? 6. 난수 정수 이제 더 어려운 작업을 수행합니다. 해결책을 읽지 마십시오. 스스로 답을 찾을 수 있는지 확인해 보세요. 다음 프로그램을 실행하면 for (int i = 0; i < 10; i++) { System.out.println((Integer) i); } 다음과 같은 결과가 나올 때가 있습니다. 92 221 45 48 236 183 39 193 33 84 그런데 이것이 어떻게 가능할까요? 좋습니다. 대답은 리플렉션을 통해 JDK의 정수 캐시를 재정의한 다음 자동 박싱 및 자동 언박싱을 사용하는 것입니다. 어른들의 허락 없이는 이런 짓을 하지 마세요! 즉, 당신이 자바에 대해 몰랐던 10가지 - 3 7. GOTO 제가 가장 좋아하는 것 중 하나입니다. 자바에는 GOTO가 있습니다! 이렇게 쓰세요: int goto = 1; 그러면 다음과 같은 결과가 나옵니다: 만약을 대비해 goto는 사용되지 않는 예약어이기 때문입니다... 하지만 그것은 흥미로운 부분이 아닙니다. 멋진 점은 break, continue 및 표시된 블록과 함께 goto를 포함할 수 있다는 것입니다. 앞으로 점프 바이트 코드에서 : 뒤로 점프 바이트 코드에서: 8. Java에는 유형 별칭이 있습니다 . 다른 언어(예: Ceylon)에서는 유형을 정의할 수 있습니다. 별칭은 매우 쉽습니다. 여기의 People 클래스는 Set과 상호 교환될 수 있는 방식으로 구축되었습니다. Test.java:44: error: expected int goto = 1; ^ label: { // do stuff if (check) break label; // do more stuff } 2 iload_1 [check] 3 ifeq 6 // Jumping forward 6 .. label: do { // do stuff if (check) continue label; // do more stuff break label; } while(true); 2 iload_1 [check] 3 ifeq 9 6 goto 2 // Jumping backward 9 .. interface People => Set ; : Java에서는 단순히 최상위 수준에서 별칭을 정의할 수 없습니다. 하지만 클래스나 메서드의 필요에 따라 이 작업을 수행할 수 있습니다. Integer, Long 등과 같은 이름이 마음에 들지 않는다고 가정해 보겠습니다. 그리고 우리는 더 짧은 이름을 원합니다: I와 L. Easy: 위의 예에서 Integer는 Test 클래스의 가시성을 위해 I로 변환되는 반면 Long은 x() 메소드의 요구에 따라 L로 변환됩니다. 이제 우리는 이 방법을 다음과 같이 부를 수 있습니다. 물론 이 기술을 심각하게 받아들여서는 안 됩니다. 이 경우 Integer와 Long은 최종 유형입니다. 즉, I와 L은 효율적인 변환입니다(거의 변환은 단방향으로만 진행됩니다). 최종 유형이 아닌(예: Object)을 사용하기로 결정한 경우 일반 제네릭을 사용하면 됩니다. 우리는 조금 놀았고 그것으로 충분했습니다. 정말 흥미로운 것으로 넘어가겠습니다. 9. 일부 유형 관계는 결정할 수 없습니다! 자, 이제 정말 흥미로워질 것입니다. 농축된 커피를 한잔 마시고 다음 두 가지 유형을 살펴보겠습니다. People? p1 = null; Set ? p2 = p1; People? p3 = p2; class Test { void x(I i, L l) { System.out.println( i.intValue() + ", " + l.longValue() ); } } new Test().x(1, 2L);// A helper type. You could also just use List interface Type {} class C implements Type > {} class D

implements Type >>> {} 그렇다면 C와 D는 무엇을 의미할까요? 어떤 의미에서는 java.lang.Enum의 재귀와 유사하게 재귀적입니다. 고려 사항: 위의 사양을 고려하면 enum의 실제 구현은 단지 구문적 설탕일 뿐입니다. 이를 염두에 두고 두 가지 유형으로 돌아가겠습니다. 다음 코드가 컴파일됩니까? 어려운 질문인데... 실제로 해결되지 않고 있나요? C는 Type의 하위 유형인가요 ? Eclipse나 Idea에서 이것을 컴파일해 보면 그들이 어떻게 생각하는지 말해 줄 것입니다. 하수구로 씻어내세요... Java의 일부 유형 관계는 결정할 수 없습니다! 10. 유형 교차 Java에는 유형 교차라는 매우 흥미로운 기능이 있습니다. 실제로 두 유형의 교차점인 (일반) 유형을 선언할 수 있습니다. 예를 들면 다음과 같습니다. Test 클래스의 인스턴스와 연결하는 사용자 정의 유형 매개 변수 T에는 Serialized 및 Cloneable 인터페이스가 모두 포함되어야 합니다. 예를 들어 문자열은 제한할 수 없지만 날짜는 제한할 수 있습니다. 이 기능은 유형을 캐스팅할 수 있는 Java8에서 여러 용도로 사용됩니다. 이것이 어떻게 도움이 되나요? 거의 아무것도 아니지만 람다 식을 필요한 유형으로 변환하려면 다른 방법이 없습니다. 메소드에 엄청난 제한이 있다고 가정해 보겠습니다. 다른 곳에서 실행하고 결과를 네트워크를 통해 전송하려는 경우에만 직렬화 가능한 Runnable을 원합니다. 람다와 직렬화는 약간의 아이러니를 더합니다. 대상 유형과 인수가 직렬화 가능한 경우 람다 식을 직렬화할 수 있습니다. 그러나 이것이 사실이더라도 직렬화 가능 인터페이스를 자동으로 활성화하지는 않습니다. 이 유형은 직접 가져와야 합니다. 그러나 직렬화 가능에만 캐스팅하면 람다는 더 이상 실행 가능하지 않으므로 두 유형 모두에 캐스팅합니다. 결론적으로 다음과 같습니다. public abstract class Enum > { ... } // This enum MyEnum {} // Is really just sugar for this class MyEnum extends Enum { ... } class Test { Type c = new C(); Type> d = new D (); } Step 0) C Step 1) Type > >? Step 0) D > Step 1) Type >>> > Step 2) D >> Step 3) List >> > Step 4) D > >> Step . . . (expand forever) class Test { } // Doesn't compile Test s = null; // Compiles Test d = null; void execute(T t) {} execute((Serializable) (() -> {}));execute((Runnable & Serializable) (() -> {}));

Java는 신비한 만큼 강력합니다.

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION