Java Language
Java 함정 - 언어 구문
수색…
소개
몇 가지 Java 프로그래밍 언어 오용은 올바르게 컴파일되었지만 부정확 한 결과를 생성하는 프로그램을 수행 할 수 있습니다. 이 주제의 주요 목적은 일반적인 함정을 원인과 함께 나열하고 그러한 문제에 빠지지 않도록 올바른 방법을 제안하는 것입니다.
비고
이 주제는 오류가 발생하기 쉬운 Java 언어 구.의 특정 측면 또는 특정 f 식으로 사용해서는 안되는 것입니다.
함몰 - 방법의 가시성 무시
숙련 된 Java 개발자조차도 Java에는 단 3 개의 보호 수정 자만 있다고 생각하는 경향이 있습니다. 언어에는 실제로 4가있다! 패키지 비공개 (일명 기본값) 표시 수준은 종종 잊어 버립니다.
어떤 방법으로 공개하는지주의해야합니다. 애플리케이션의 공개 메소드는 애플리케이션의 가시적 인 API입니다. 특히 재사용 가능한 라이브러리를 작성하는 경우에는 작고 작아야합니다 ( SOLID 원칙 참조). 모든 메소드의 가시성을 유사하게 고려하고 적절한 경우 보호되거나 패키지 된 개인 액세스 만 사용하는 것이 중요합니다.
비공개 로 선언해야하는 메서드를 public으로 선언하면 해당 클래스의 내부 구현 정보가 노출됩니다.
이것에 대한 결과는 클래스의 public 메소드를 단위 테스트 만한다는 것입니다. 사실 public 메소드 만 테스트 할 수 있습니다. 이러한 메소드에 대해 단위 테스트를 실행할 수 있도록 개인 메소드의 가시성을 높이는 것은 나쁜 습관입니다. 보다 제한적인 가시성을 가진 메소드를 호출하는 public 메소드를 테스트하면 전체 API를 테스트하는 데 충분해야합니다. 단위 테스트를 허용하기 위해 더 많은 공용 메소드로 API를 확장해서는 안됩니다 .
Pitfall - '스위치'케이스에서 '휴식'이 없습니다.
이러한 Java 문제는 매우 당혹 스러울 수 있으며 때로는 프로덕션 환경에서 실행될 때까지 알려지지 않은 채로 남아 있습니다. switch 문에서의 fallthrough 동작은 종종 유용합니다. 그러나 이러한 동작이 바람직하지 않을 때 "중단"키워드가 누락되면 심각한 결과를 초래할 수 있습니다. 아래의 코드 예제에서 "case 0"에 "break"를 넣는 것을 잊어 버린 경우, 프로그램은 "Zero"다음에 "One"을 씁니다. 여기의 제어 흐름은 전체 "switch"문을 통과 할 때까지 그것은 "휴식"에 도달합니다. 예 :
public static void switchCasePrimer() {
int caseIndex = 0;
switch (caseIndex) {
case 0:
System.out.println("Zero");
case 1:
System.out.println("One");
break;
case 2:
System.out.println("Two");
break;
default:
System.out.println("Default");
}
}
대부분의 경우 클리너 솔루션은 인터페이스를 사용하고 특정 동작이있는 코드를 별도의 구현 ( 상속을 통한 구성 )으로 이동하는 것입니다.
switch-statement가 불가피한 경우 "예상"fallthrough가 발생할 경우 문서화하는 것이 좋습니다. 그렇게하면 동료 개발자에게 누락 된 부분을 알리고 이것이 예상되는 동작임을 알 수 있습니다.
switch(caseIndex) {
[...]
case 2:
System.out.println("Two");
// fallthrough
default:
System.out.println("Default");
함몰 - 잘못 배치 된 세미콜론과 누락 된 중괄호
이것은 Java 초보자가 적어도 처음에는 혼란을 야기하는 실수입니다. 이것을 쓰는 대신에 :
if (feeling == HAPPY)
System.out.println("Smile");
else
System.out.println("Frown");
그들은 실수로 다음과 같이 씁니다 :
if (feeling == HAPPY);
System.out.println("Smile");
else
System.out.println("Frown");
Java 컴파일러가 else
가 잘못 배치되었다고 말할 때 당황합니다. 자바 컴파일러는 다음과 같이 해석합니다.
if (feeling == HAPPY)
/*empty statement*/ ;
System.out.println("Smile"); // This is unconditional
else // This is misplaced. A statement cannot
// start with 'else'
System.out.println("Frown");
다른 경우에는 컴파일 오류가 없지만 코드는 프로그래머가 의도 한대로 수행하지 않습니다. 예 :
for (int i = 0; i < 5; i++);
System.out.println("Hello");
한 번만 "Hello"를 인쇄합니다. 다시 한번, 가짜 세미콜론은 for
루프의 본문이 빈 문장임을 의미합니다. 즉, 뒤 따르는 println
호출은 무조건 부 M됩니다.
다른 변형 :
for (int i = 0; i < 5; i++);
System.out.println("The number is " + i);
이렇게하면 i
대해 "기호를 찾을 수 없음"오류가 발생합니다. 가짜 세미콜론이 존재한다는 것은 println
호출이 범위 밖에서 i
를 사용하려고한다는 것을 의미합니다.
이 예에서는 간단한 해결책이 있습니다. 가짜 세미콜론을 삭제하면됩니다. 그러나 다음 예제에서 더 깊이 이해할 수있는 교훈이 있습니다.
Java의 세미콜론은 "통어론 적 소음"이 아닙니다. 세미콜론의 유무는 프로그램의 의미를 바꿀 수 있습니다. 모든 행의 끝에 추가하지 마십시오.
코드의 들여 쓰기를 신뢰하지 마십시오. Java 언어에서는 컴파일러가 행의 처음에있는 여분의 공백을 무시합니다.
자동 압자를 사용하십시오. 모든 IDE 및 많은 간단한 텍스트 편집기는 Java 코드를 올바르게 들여 쓰기하는 방법을 알고 있습니다.
이것이 가장 중요한 교훈입니다. 최신 Java 스타일 지침을 따르고 "then"및 "else"문과 루프 본문에 중괄호를 사용하십시오. 대괄호 (
{
)는 새 줄에 있어서는 안됩니다.
프로그래머가 스타일 규칙을 따르면, 잘못 배치 된 세미콜론이있는 if
예제는 다음과 같습니다.
if (feeling == HAPPY); {
System.out.println("Smile");
} else {
System.out.println("Frown");
}
그것은 경험 많은 눈에 이상하게 보입니다. 해당 코드를 자동 들여 쓰기하면 다음과 같이 보일 것입니다.
if (feeling == HAPPY); {
System.out.println("Smile");
} else {
System.out.println("Frown");
}
초보자조차도 두드러지지 않아야합니다.
핏방울 - 괄호를 벗어나 : "매달려 있다면"과 "매달려있는"문제
Oracle Java Style Guide의 최신 버전에서는 if
문에있는 "then"및 "else"문을 항상 "중괄호"또는 "중괄호"로 묶어야한다고 규정하고 있습니다. 비슷한 규칙이 다양한 루프 문 본문에 적용됩니다.
if (a) { // <- open brace
doSomething();
doSomeMore();
} // <- close brace
이것은 실제로 Java 언어 구문에서는 필요하지 않습니다. (가) "다음"는 부분이 있다면 사실, if
문이 하나의 문, 괄호를 생략 할 법적
if (a)
doSomething();
또는
if (a) doSomething();
그러나 Java 스타일 규칙을 무시하고 중괄호를 생략하면 위험 할 수 있습니다. 특히 잘못된 들여 쓰기가있는 코드가 잘못 읽힐 위험이 크게 높아집니다.
"dangling if"문제 :
위에서 괄호없이 다시 작성된 예제 코드를 살펴보십시오.
if (a)
doSomething();
doSomeMore();
이 코드 는 doSomething
과 doSomeMore
대한 호출은 a
가 true
경우에만 발생 true
. 사실, 코드는 잘못 들여 쓰기되어 있습니다. doSomeMore()
호출이 if
문 다음에 오는 별도의 명령문 인 Java 언어 사양. 올바른 들여 쓰기는 다음과 같습니다.
if (a)
doSomething();
doSomeMore();
"dangling else"문제
믹스에 else
를 추가 할 때 두 번째 문제가 나타납니다. 중괄호가 누락 된 다음 예제를 고려하십시오.
if (a)
if (b)
doX();
else if (c)
doY();
else
doZ();
위의 코드는 말할 것 doZ
때 호출 될 a
이다 false
. 사실, 들여 쓰기는 다시 한번 부정확합니다. 코드의 올바른 들여 쓰기는 다음과 같습니다.
if (a)
if (b)
doX();
else if (c)
doY();
else
doZ();
코드가 Java 스타일 규칙에 따라 작성된 경우 실제로는 다음과 같습니다.
if (a) {
if (b) {
doX();
} else if (c) {
doY();
} else {
doZ();
}
}
왜 더 나은지 설명하기 위해 우연히 실수로 코드를 들여 쓰기했다고 가정 해보십시오. 다음과 같이 끝낼 수도 있습니다.
if (a) { if (a) {
if (b) { if (b) {
doX(); doX();
} else if (c) { } else if (c) {
doY(); doY();
} else { } else {
doZ(); doZ();
} }
} }
그러나 두 경우 모두, 들여 쓰기가 잘못된 코드는 숙련 된 Java 프로그래머의 눈에 "틀린 것처럼 보입니다".
함정 - 오버라이드 대신 오버로딩
다음 예제를 고려하십시오.
public final class Person {
private final String firstName;
private final String lastName;
public Person(String firstName, String lastName) {
this.firstName = (firstName == null) ? "" : firstName;
this.lastName = (lastName == null) ? "" : lastName;
}
public boolean equals(String other) {
if (!(other instanceof Person)) {
return false;
}
Person p = (Person) other;
return firstName.equals(p.firstName) &&
lastName.equals(p.lastName);
}
public int hashcode() {
return firstName.hashCode() + 31 * lastName.hashCode();
}
}
이 코드는 예상대로 작동하지 않습니다. 문제는 Person
의 equals
및 hashcode
메소드가 Object
의해 정의 된 표준 메소드를 대체하지 않는다는 것입니다.
-
equals
메소드의 서명이 잘못되었습니다.equals(Object)
아닌equals(String)
로 선언해야합니다. -
hashcode
메소드의 이름이 잘못되었습니다.hashCode()
(대문자 C에 주의hashCode()
이어야합니다.
이러한 실수는 우발적 인 과부하를 선언했음을 의미하며 Person
이 다형성 환경에서 사용되는 경우에는 사용되지 않습니다.
그러나이 문제를 처리 할 수있는 간단한 방법이 있습니다 (Java 5부터). 메소드를 재정의 하려는 경우 @Override
주석을 사용하십시오.
public final class Person {
...
@Override
public boolean equals(String other) {
....
}
@Override
public hashcode() {
....
}
}
@Override
어노테이션을 메소드 선언에 추가하면 컴파일러는 메소드 가 수퍼 클래스 또는 인터페이스에 선언 된 메소드를 대체 (또는 구현)하는지 확인합니다. 따라서 위의 예에서 컴파일러는 컴파일 오류를 두 가지로 제공합니다.이 오류는 우리에게 실수를 알리는 데 충분합니다.
핏방울 - 8 진 리터럴
다음 코드 스 니펫을 고려하십시오.
// Print the sum of the numbers 1 to 10
int count = 0;
for (int i = 1; i < 010; i++) { // Mistake here ....
count = count + i;
}
System.out.println("The sum of 1 to 10 is " + count);
Java 초보자는 위의 프로그램이 잘못된 대답을 인쇄한다는 것을 알고 놀랄 수 있습니다. 실제로 숫자 1 - 8의 합계를 인쇄합니다.
그 이유는 영숫자 ( '0')로 시작하는 정수 리터럴은 자바 컴파일러에 의해 예상되는 십진수 리터럴이 아닌 8 진수 리터럴로 해석되기 때문입니다. 따라서 010
은 8 진수 10이며 십진수는 8입니다.
Pitfall - 표준 클래스와 같은 이름을 가진 클래스 선언
때로 Java를 처음 사용하는 프로그래머는 널리 사용되는 클래스와 같은 이름의 클래스를 정의하는 실수를 저지르는 경우가 있습니다. 예 :
package com.example;
/**
* My string utilities
*/
public class String {
....
}
그런 다음 그들은 예상치 못한 오류가 왜 발생하는지 궁금해합니다. 예 :
package com.example;
public class Test {
public static void main(String[] args) {
System.out.println("Hello world!");
}
}
위에서 컴파일 한 클래스를 실행하려고하면 오류가 발생합니다.
$ javac com/example/*.java
$ java com.example.Test
Error: Main method not found in class test.Test, please define the main method as:
public static void main(String[] args)
or a JavaFX application class must extend javafx.application.Application
Test
클래스의 코드를 본 누군가는 main
이라는 선언을보고 서명을보고 java
명령이 불평하는 것이 무엇인지 궁금해 할 것이다. 그러나 실제로, java
명령은 진실을 말하고 있습니다.
Test
와 같은 패키지에 String
버전을 선언하면이 버전이 java.lang.String
의 자동 가져 오기보다 우선합니다. 따라서 Test.main
메서드의 서명은 실제로
void main(com.example.String[] args)
대신에
void main(java.lang.String[] args)
및 java
명령은 엔트리 포인트 방식으로 그것을 인식하지 않을 것이다.
레슨 : java.lang
기존 클래스와 같은 이름을 가진 클래스 나 Java SE 라이브러리의 다른 일반적으로 사용되는 클래스를 정의하지 마십시오. 그렇게한다면, 모든 종류의 모호한 오류에 대해 스스로를 열어 놓을 것입니다.
Pitfall - '=='를 사용하여 부울 테스트하기
때때로 새로운 자바 프로그래머는 다음과 같은 코드를 작성합니다.
public void check(boolean ok) {
if (ok == true) { // Note 'ok == true'
System.out.println("It is OK");
}
}
숙련 된 프로그래머는 그것을 서투른 것으로 여기고 그것을 다음과 같이 다시 쓰려고합니다.
public void check(boolean ok) {
if (ok) {
System.out.println("It is OK");
}
}
그러나 단순한 서투름보다 ok == true
가 더 잘못되었습니다. 다음 변형을 고려하십시오.
public void check(boolean ok) {
if (ok = true) { // Oooops!
System.out.println("It is OK");
}
}
여기서 프로그래머는 ==
as =
...을 잘못 입력했습니다. 코드에는 미묘한 버그가 있습니다. 식 x = true
무조건 할당 true
에 x
다음으로 평가 true
. 즉, 매개 변수가 무엇이든 관계없이 check
메소드가 "It is OK"를 인쇄합니다.
여기서 교훈은 == false
및 == true
를 사용하는 습관을 벗어나는 것입니다. 장황함에 더하여, 그들은 당신의 코딩을 에러가 발생하기 쉽게 만든다.
참고 : 함정을 피하는 ok == true
의 가능한 대안은 Yoda 조건 을 사용하는 것입니다 . 즉, 관계 연산자의 왼쪽에 리터럴을 넣습니다 ( true == ok
처럼). 이것은 작동하지만 대부분의 프로그래머는 Yoda 조건이 이상하게 보일 수도 있음에 동의 할 것입니다. 확실히 ok
(또는 !ok
)는 더 간결하고 자연 스럽습니다.
핏방울 - 와일드 카드 가져 오기로 인해 코드가 깨지기 쉽습니다.
다음 부분적인 예를 고려하십시오.
import com.example.somelib.*;
import com.acme.otherlib.*;
public class Test {
private Context x = new Context(); // from com.example.somelib
...
}
그것이 somelib
버전 1.0과 somelib
버전 1.0에 대한 코드를 처음 개발했을 때를 가정 otherlib
. 나중에 어떤 시점에서 종속성을 이후 버전으로 업그레이드해야하며 otherlib
버전 2.0을 사용하기로 결정했습니다. 또한 1.0과 2.0 사이에서 otherlib
에 한 변경 사항 중 하나가 Context
클래스를 추가하는 것이 otherlib
가정하십시오.
이제 Test
를 다시 컴파일하면 Context
가 모호한 가져 오기라는 컴파일 오류가 발생합니다.
당신이 코드베이스에 익숙하다면, 이것은 아마도 사소한 불편 일 것이다. 그렇지 않다면, 당신은이 문제를 해결하기 위해 할 일이 있습니다. 여기서 그리고 잠재적으로 다른 곳에서 할 수 있습니다.
여기서 문제는 와일드 카드 가져 오기입니다. 반면에 와일드 카드를 사용하면 클래스를 몇 줄 더 짧게 만들 수 있습니다. 반면에 :
코드베이스의 다른 부분, Java 표준 라이브러리 또는 타사 라이브러리에 대한 위쪽 호환 가능 변경은 컴파일 오류를 유발할 수 있습니다.
가독성이 떨어집니다. IDE를 사용하지 않는 한 와일드 카드 가져 오기 중 이름이 지정된 클래스를 가져 오는 것이 무엇인지 파악하는 것은 어려울 수 있습니다.
교훈은 오래 살 필요가있는 코드에서 와일드 카드 가져 오기를 사용하는 것은 좋지 않다는 것입니다. 특정 (와일드 카드가 아닌) 가져 오기는 IDE를 사용하는 경우 유지하기 위해 많은 노력을 기울이지 않으며 그 노력은 가치가 있습니다.
Pitfall : 인수 또는 사용자 입력 유효성 검사에 'assert'사용
StackOverflow에서 종종 assert
를 사용하여 메서드에 제공된 인수를 유효하게하는지 또는 심지어 사용자가 제공 한 입력을 사용하는지 여부가 문제입니다.
간단한 대답은 적절하지 않다는 것입니다.
더 나은 대안은 다음과 같습니다.
- 커스텀 코드를 사용해 IllegalArgumentException를 Throw하는
- Google Guava 라이브러리에서 사용할 수있는
Preconditions
방법 사용 - Apache Commons Lang3 라이브러리에서 사용할 수있는
Validate
메소드 사용.
이것이 Java 언어 사양 (JLS 14.10, Java 8) 에서이 문제에 관해 조언하는 내용입니다.
일반적으로 어설 션 검사는 프로그램 개발 및 테스트 중에 활성화되고 배포를 위해 비활성화되어 성능을 향상시킵니다.
어설 션은 사용할 수 없으므로 프로그램은 어설 션에 포함 된 표현식을 평가한다고 가정해서는 안됩니다. 따라서 이러한 부울 식은 일반적으로 부작용이 없어야합니다. 이러한 불리언 표현식을 평가해도 평가가 완료된 후 표시되는 상태에는 영향을주지 않습니다. 어설 션에 포함 된 부울식이 부작용을 갖는 것은 불법이 아니지만 어설 션을 사용할지 여부에 따라 프로그램 동작이 달라질 수 있으므로 일반적으로 부적절합니다.
이 점에 비추어, 공용 메소드의 인수 검사에 어설 션을 사용해서는 안됩니다. 인수 검사는 일반적으로 메소드 계약의 일부이며, 어설 션을 사용할지 여부에 관계없이이 계약을 유지해야합니다.
인수 검사에 어설 션을 사용하는 경우의 두 번째 문제점은 잘못된 인수가 적절한 런타임 예외 (예 :
IllegalArgumentException
,ArrayIndexOutOfBoundsException
또는NullPointerException
)를 야기해야한다는 것입니다. 어설 션 오류는 적절한 예외를 throw하지 않습니다. 다시 말하지만 공개 메소드에 대한 인수 검사에 어설 션을 사용하는 것은 불법이 아니지만 일반적으로 부적절합니다.AssertionError
절대로 잡힐 수 없지만 그렇게 할 수 있습니다. 따라서 try 문에 대한 규칙은 throw 문에 대한 현재 처리와 비슷하게 try 블록에 나타나는 어설 션을 처리해야합니다.
자동 언 박싱 Null 객체의 프리미티브로의 찔림
public class Foobar {
public static void main(String[] args) {
// example:
Boolean ignore = null;
if (ignore == false) {
System.out.println("Do not ignore!");
}
}
}
여기서 함정은 null
이 false
와 비교된다는 것입니다. 우리는 원시적 비교하고 있기 때문에 boolean
에 대해 Boolean
, 자바하여 언 박싱을 시도 Boolean
Object
비교를 위해 준비 원시 상당으로. 그러나 그 값은 null
이기 때문에 NullPointerException
이 발생합니다.
Java는 런타임시 NullPointerException
을 발생시키는 null
값과 원시 유형을 비교할 수 없습니다. 조건의 원시적 인 경우를 생각해 보자. false == null
; 이는 incomparable types: int and <null>
의 컴파일 타임 오류를 생성합니다.