나무모에 미러 (일반/밝은 화면)
최근 수정 시각 : 2026-06-19 06:55:30

Java/문법


#!if top = 문서명1 != null ? 문서명1 : calleeTitle != null ? (i = calleeTitle.lastIndexOf("/")) != -1 ? calleeTitle.substr(0, i) : '상위 문서' : '상위 문서'
[[파일:상위 문서 아이콘.svg|align=left&width=21.6px]] {{{#!html  }}} 상위 문서: [[]]


#!style
.table > div {
    margin: 5px 0;
}
.table td {
    border-style: solid none;
}
.sub-title {
    margin: 5px;
    padding: 3px;
    border-radius: 5px;
    background: #154c79;
    color: #fff;
    font-weight: bold;
}
프로그래밍 언어 문법
{{{#!folding [ 펼치기 · 접기 ]
{{{#!wiki class="table"
#!wiki class="sub-title"
프로그래밍 언어 문법
C(포인터 · 구조체 · 공용체 · size_t) · C++(이름공간 · 클래스 · 특성 · 상수 표현식 · 람다 표현식 · 템플릿/제약조건/메타 프로그래밍 · 코루틴) · C# · Forth · Java · Python(함수 · 모듈) · Kotlin · MATLAB · SQL · PHP · JavaScript(표준 내장 객체, this) · Haskell(모나드) ·
#!wiki class="sub-title"
마크업 언어 문법
HTML · CSS
#!wiki class="sub-title"
개념과 용어
자료형 · 함수(인라인 함수 · 고차 함수 · 콜백 함수 · 람다식) · 리터럴 · 문자열 · 식별자(예약어) · 네임스페이스 · 조건문 · 반복문 · 비트 연산 · 참조에 의한 호출 · 상속 · 예외 · eval · 호이스팅
#!wiki class="sub-title"
기타
#! · == · === · deprecated · GOTO · NaN · null · undefined · S-표현식 · 배커스-나우르 표기법
}}}}}}
프로그래밍 언어 목록 · 분류 · 문법 · 예제


1. 개요2. 파일 구조3. 패키지4. import5. 타입
5.1. 원시 타입
5.1.1. 정수5.1.2. 실수5.1.3. 문자
5.2. 객체 타입
5.2.1. 래퍼 클래스
5.3. String5.4. void5.5. 배열5.6. 컬렉션
6. 변수 선언
6.1. 전역/지역 변수6.2. 가변/불변 변수와 상수
7. 출력8. 입력9. 연산자
9.1. 산술9.2. 증감9.3. 부호9.4. 비교9.5. 논리9.6. 비트9.7. 시프트9.8. 대입9.9. 조건9.10. 형변환9.11. instanceof9.12. 기타 특수 연산자9.13. 우선순위
10. 조건문
10.1. if10.2. switch
11. 반복문
11.1. while
11.1.1. do-while
11.2. for
11.2.1. for-each
11.3. 분기
12. 라벨13. 메인 메서드14. 제어자
14.1. 접근 제어자14.2. 기타 제어자
15. 메서드
15.1. 오버로딩
16. 클래스
16.1. 유틸리티 클래스16.2. 데이터 클래스
16.2.1. Getter와 Setter
16.3. 싱글톤 패턴16.4. 상속16.5. 추상 클래스16.6. Object
17. 인터페이스
17.1. 함수형 인터페이스
18. Enum19. 예외
19.1. try
20. 제네릭
20.1. 클래스/인터페이스20.2. 메서드20.3. 와일드카드20.4. 바운딩
21. 익명 클래스
21.1. 람다식
21.1.1. 메서드 참조
22. 스레드

1. 개요

프로그래밍 언어 Java의 문법을 정리한 문서이다.

2. 파일 구조

.java의 확장자를 가진다.

[package 이름 명시]
[import할 패키지 명시]
[클래스 구현]

package는 생략할 수 있다. 자세한 건 후술한다.

클래스 중 public 접근 제어자가 붙은 주요 클래스는 파일과 동일한 이름으로 정해야 하며, 2개 이상 선언할 수 없다.[1] 그 외의 클래스는 상관없다.

3. 패키지

#!syntax java
package com.example;

public class Main {
    void main() {
        IO.println(this.getClass()); // class com.example.Main
    }
}
파일 최상단에 표기한다. 해당 파일의 경로를 뜻하며, 이 경우에서 파일의 위치는 com/example/Main.java가 된다. 클래스 중복을 방지하기 위해 존재하며 클래스 이름이 같더라도 패키지가 다르면 구분할 수 있다.
패키지 이름은 절대 겹치지 않도록 하는 것이 좋다. 때문에 도메인 기반 패키지를 사용하는 편이다. 예시로 com.google..., org.apache... 등이 있다. 그 외에도 비슷한 클래스끼리 묶어서 보기 편하게 하기 위해 사용한다.[2]

#!syntax java
public class Main {
    void main() {
        IO.println(this.getClass()); // class Main
    }
}
패키지를 쓰지 않을 경우 default package가 된다. 다른 패키지에서 접근할 수 없으므로 추천하지 않는다.

4. import

다른 패키지의 클래스에 접근하기 위해서는 패키지명. 클래스명인 완전 수식 이름을 사용하거나 import를 해야 한다.

#!syntax java
package com.example;

import java.util.List;
import java.util.ArrayList;
import java.io.*; // java.io 패키지의 모든 클래스를 import 한다. 하위 패키지의 클래스는 import되지 않는다.
//import java.lang.String; // java.lang 패키지는 import 없이 접근할 수 있다.
import static java.lang.Math.IP; // Math.PI가 아닌 PI만올 접근할 수 있다.

public class Main {
    void main() {
        List<String> list = new ArrayList<>();
        IO.println(PI); // 파이값 출력
        java.util.Set set = new java.util.HashSet<>(); // import 없이 사용.
        var file = new File("file.txt"); // com.example.File 객체가 만들어진다.
        var file2 = new java.io.File("file.txt"); // java.io.File 객체가 만들어진다.
    }
}

// File.java
package com.example;

class File {
    public File(String str) {
    }
}
같은 이름의 클래스가 있을 때 우선순위는 동일 패키지 > 명시적 import > java.lang > * import이다.
import static은 자주 사용되지 않는다.
컴파일 시 import는 제거되고 모두 완전 수식 이름으로 교체되므로 import만으로 클래스가 로딩되거나 하지 않는다.

5. 타입

5.1. 원시 타입

primitive type. 자바에선 8개의 원시 타입이 있다.
굵게 표시한 타입은 그 종류 내에서 기본적인 타입이다.[4]

원시 타입의 이름은 소문자로 시작한다. 대문자로 시작한다면 원시 타입이 아닌 객체 타입이다. 대표적인 예가 문자열인 String.
원시 타입은 null을 허용하지 않고, 실제 값을 저장한다.

5.1.1. 정수

4가지 타입이 있으며 기본인 int가 주로 사용된다.
int를 벗어나는 정수는 뒤에 l 또는 L을 붙여야 한다.[5]
#!syntax java
long namu = 1000000000000L;


char도 정수로 간주할 수 있다. 크기는 short와 동일하다.

5.1.2. 실수

2가지 타입이 있으며 기본은 double이다.
float 타입 실수는 뒤에 f 또는 F를 붙여야 한다.[6]
부동 소수점 방식으로 저장해서 정확한 연산이 불가능하다.[7]
절댓값에 최솟값이 있다.

5.1.3. 문자

char은 유니코드로 0 ~ 65535까지 저장이 가능하다. 예를 들어
#!syntax java
char namu = 'A';

면 namu는 65가 된다.A의 유니코드가 65기 때문이다.

5.2. 객체 타입

원시 타입 아닌 타입은 객체 타입이라고 한다. 문자열 자료형인 String이 대표적이다.[8] 객체 타입은 모두 클래스이다.

원시 타입과 달리 객체는 따로 저장되고 변수에는 그 객체의 주소(위치)가 저장된다. 때문에 == 연산자의 경수 변수의 값인 주소를 비교한다.[9]

대부분의 연산자를 쓸 수 없다. 단, String에 한에서 +가 문자열 연결로 동작한다.

5.2.1. 래퍼 클래스

원시 타입을 객체 타입으로 변환한 클래스를 래퍼 클래스(Wrapper Class)라고 한다. 래퍼 클래스는 원시 타입과 관련 내장 함수가 존재한다. Java 5 이상이면 동일한 데이터 형식을 공유하는 원시 타입과 래퍼 클래스 사이에는 자동적으로 박싱과 언박싱을 할 수 있다.
원시 타입 래퍼 클래스 데이터 형식
boolean Boolean 불 대수(true 또는 false)
byte Byte 1바이트 정수
short Short 2바이트 정수
int Integer 4바이트 정수
long Long 8바이트 정수
float Float 4바이트 실수
double Double 8바이트 실수
char Character[10] 문자

Java 4 이하는 수동으로 박싱과 언박싱을 해야 한다.
#!syntax java
int a = 2;
Integer b = new Integer(a);
int c = b.intValue();


원시 타입과 다르게 null을 쓸 수 있고, 제네릭의 타입 매개변수로 쓰일 수 있지만, 연산속도가 느리다.

5.3. String

문자열을 나타낸다. 객체 타입이지만 특별 취급을 받는다.
#!syntax java
String a = new String("abc");
String b = "abc";
원래라면 변수 a처럼 생성자를 이용해 객체를 만들어야 하나, 생성자 없이도 만들 수 있도록 되어 있다.

#!syntax java
String a = "abc";
String b = a;
a += "def"; // a의 값이 "abcdef"가 됨.
IO.println(b) // abc 출력
String 객체는 불변이다. 겉으로는 수정된 것처럼 보여도 실제로는 새로운 String 객체로 재대입한 것이다. 때문에 원시 타입처럼 편하게 수정할 수 있다. 또한 String 간 + 연산자는 문자열 연결로 동작한다.

#!syntax java
String a = "abc";
String b = "abc";
String c = new String("abc");

IO.println(a == b); // true
IO.println(a == c); // false
IO.println(a.equals(c)); // true
문자열 풀이 존재한다. 매번 객체를 만들면 비효율적이기에 이미 만들어진 객체를 저장하는 방식이다.
코드를 보면 a는 "abc"라는 문자열 객체 만들었다. 이 객체는 문자열 풀에 저장된다. 이후 b가 문자열 객체를 만드려하는데 문자열 풀에 이미 동일한 내용의 객체가 존재하므로 해당 객체를 참조하게 된다. c는 생성자를 통해 새 객체를 만드므로 a, b와 내용은 같지만, 다른 객체를 가지게 된다.
a == b의 경우 두 객체가 동일하므로 true이다. a == c는 두 객체가 다르므로 false이다. String에서 a.equals(c)는 두 객체의 내용이 같은지를 검사한다. 객체는 달라도 내용은 같으므로 true이다.
이처럼 문자열 비교에는 항상 equals를 사용해야 한다. 문자열 풀은 성능 때문에 존재하지, 코드 작성할 때 고려하라고 있는 게 아니다.

5.4. void

오로지 메서드가 값을 반환하지 않을 때 사용하는 타입이다. 래퍼 클래스 비슷한 형태인 Void도 존재하는데 제네릭에서 타입 매개변수를 쓰지 않을 때 사용한다.

5.5. 배열

같은 타입의 데이터를 나열하고, 인덱스(index)를 부여한 자료 구조. 객체 타입으로 취급된다.

#!syntax java
int[] intArray;
String strArray[];
타입이나 변수에 '[]'를 추가하여 배열을 선언한다.[11]

#!syntax java
int[] intArray = new int[3];
String[] strArray = {"hello", "world", "example"};
배열을 초기화할 때는 배열의 크기만 지정하거나 실제 배열의 내용을 지정한다. 크기만 지정할 경우, 모든 인덱스는 기본값이 저장된다.[12]

#!syntax java
IO.println(strArray[0]); // 'hello'를 출력
IO.println(strArray[1]); // 'world'를 출력
IO.println(strArray[2]); // 'example'을 출력
변수 앞에 []를 붙이고 대괄호 안에 인덱스를 넣으면, 해당 인덱스에 맞는 값을 가져온다. 인덱스는 0부터 시작한다.

#!syntax java
intArray[0] = 1;
intArray[1] = 3;
intArray[2] = -10;
같은 방법으로 값을 대입할 수 있다.

#!syntax java
IO.println(intArray[3]);
strArray[-1] = "text";
크기를 벗어나거나 음수 인덱스를 이용해 접근할 경우 예외를 발생시킨다.

5.6. 컬렉션

파일:자바 컬렉션.jpg
위 이미지는 추상 클래스를 생략한 모습이다.

자바에서는 컬렉션을 통해 여러 개의 데이터를 저장할 수 있는 자료 구조를 제공한다. 배열과 다르게 크기가 바뀔 수 있다.

컬렉션은 특정 타입의 데이터만 저장하기 위해 제네릭을 지원한다.

Iterable은 for-each문에 사용할 수 있도록 하는 인터페이스다.
컬렉션 자료 구조에 대한 정렬이나 필터링에 필요한 함수는 Collections 클래스 (java.util.Collections)에서 제공하고 있다.

6. 변수 선언

기본적으로 '(타입) (이름)'의 구조를 가지며 이는 메서드 선언과 매개 변수, for-each문에서의 변수, catch문에서의 받을 변수 등에서도 해당한다.

#!syntax java
int namu;
int 타입의 namu라는 변수를 선언한다.

#!syntax java
namu = 3;
namu 변수에 값을 대입할 수 있다. 여기서 namu가 어떤 값도 가지고 있지 않은 경우[14] 초기화한다고 부른다.

#!syntax java
int namu = 3;
선언과 동시에 대입할 수도 있다.

#!syntax java
final int namu = 3;
final을 붙여 불변 변수로 만들 수 있다. 재대입이 불가능하다.[15]

#!syntax java
var namu = 3;
10 버전부터는 타입을 명시하지 않고 var로 대체할 수도 있다. 이 경우 컴파일러가 타입추론을 한다.

#!syntax java
IO.println(namu);
변수의 값을 참조할 수도 있다.

#!syntax java
namu = namuwiki;
변수의 값을 이렇게 복사할 수도 있다.[16]

6.1. 전역/지역 변수

클래스 내에 있는 변수를 전역 변수, 메서드 같이 그 외의 경우는 지역 변수라고 한다.
지역 변수는 제한이 생긴다. final을 제외한 다른 제어자를 붙일 수 없고, 해당 변수가 선언된 곳에서만 쓸 수 있으며 그 이후에는 제거된다.
전역 변수는 값을 대입하지 않아도 자동으로 초기화되지만 지역변수는 수동으로 초기화해야 한다.
자동 초기화 시 객체 타입은 null, 원시 타입은 0 또는 false로 초기화된다.

6.2. 가변/불변 변수와 상수

일반적으로 만들어진 변수는 가변 변수로, 수정 및 재대입이 가능하다.
반면 불변 변수는 final이 붙어 재대입이 불가능하다.
상수는 크게 2개로 나뉘는데 static final이 붙은 변수와 거기에 더해 원시 타입 또는 String이라는 조건이 붙은 경우이다.

불변 변수와 상수의 차이는 컴파일 시점에서 값을 알 수 있는지로 구분할 수 있다.
#!syntax java
public class A {
    public final int someIntValue;
    public static final int SOME_INT_VALUE = 2;

    public A(int i) {
        someIntValue = i;
    }
}
여기서 someIntValue의 값은 객체마다 다를 수 있지만, SOME_INT_VALUE는 2라는 고정된 값을 가진다.

7. 출력

#!syntax java
System.out.println("Hello, World!");
전통적인 출력 방법이다. System.out은 표준 입력 스트림이다.

#!syntax java
System.err.println("Error message");
표준 에러 스트림에 출력하는 방법이다. 콘솔에서는 구분되지 않지만, IDE에서 빨간색을 이용해 구분하는 경우가 있다.

#!syntax java
System.out.print("Hello, World!\n");
줄바꿈을 하지 않고 출력할 수 있다.

#!syntax java
String str = "World!"
System.out.printf("Hello, %s\n", str);
C와 동일한 printf를 사용할 수도 있다. 자주 사용되지 않는다.

#!syntax java
IO.println("Hello, World!");
IO.print("Hello, World!\n");
자바 25 부터는 IO 클래스로 간단히 쓸 수 있다. 표준 출력 스트림에 출력한다.

8. 입력

표준 입력 스트림(System.in)에서 입력 받는 방법이다.

#!syntax java
int i = System.in.read();
가장 기본적인 입력이다. 바이트 하나를 읽는다. 예를 들어 "ABC\n"을 입력하면 A의 아스키코드인 65를 반환한다. 이후 한번 더 read()를 호출하면 B의 아스키코드인 66이 반환되는 식. 한글의 경우 3바이트로 이루어져 있어서 한번으로는 완전히 읽을 수 없다. 매우 불편하기에 특수한 경우가 아니면 아래 방식들을 사용하는 편.

#!syntax java
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str = br.readLine();
Scanner 추가 전에 주로 사용된 방식이다. 속도가 Scanner보다 빨라서 사용된다.

#!syntax java
Scanner sc = new Scanner(System.in);
String str = sc.nextLine();
자바 24까지 간단히 사용되는 입력 방식이다. sc.nextInt(); 같은 메서드로 바로 타입을 바꿀 수 있다.[17]

#!syntax java
String str = IO.readln();
자바 25부터 사용 가능한 입력 방식이다. 내부적으로 BufferedReader를 사용하므로 사용도 쉽고, 속도도 빠르다.

9. 연산자

숫자 타입은 그 크기를 알아야 한다.
byte < short = char < int < long < float < double
byte, short, char는 보통 자동으로 int로 승격한다.

9.1. 산술

#!syntax java
a + b;
a - b;
a * b;
a / b;
a % b;
순서대로 덧셈, 뺄셈, 곱셈, 나눗셈, 나머지 연산이다. 피연산자는 숫자이다.
반환 타입은 피연산자 중 가장 크기가 큰 타입이다.[18]
+는 두 피연산자 중 하나라도 String이면 문자열 연결로 동작한다. 이때 문자열이 아닌 피연산자는 String.valueOf(obj);의 반환값으로 사용한다.[19]

9.2. 증감

#!syntax java
++a;
--a;
a++;
a--;
++은 1 증가. --는 1 감소를 뜻한다. 의미적으로 a = a + 1; 또는 a = a - 1;과 비슷하다.[20]
연산자가 앞에 붙으면 전위, 뒤에 붙으면 후위이다. 전위는 연산을 바로 수행하며, 후위는 변수 사용 후 연산을 수행한다.[21]
후위의 경우 굉장히 헷갈리기 때문에 단독으로 사용하거나 간단히 사용하는 편이다.[22] 단독으로 사용하는 경우 전위와 후위의 구분이 없어지는데 이 경우 보통 후위가 자주 사용된다.[23]

9.3. 부호

#!syntax java
+a;
-a;
피연산자는 숫자이다. +는 부호 유지, -는 반전이다.

9.4. 비교

#!syntax java
a == b;
a != b;
a > b;
a < b;
a >= b;
a <= b;
순서대로 같을 때, 다를 때, a가 클 때, a가 작을 때, a가 같거나 클 때, a가 같거나 작을 때 true를 반환한다. 피연산자는 부등호는 숫자, 등호는 숫자, boolean, 객체 모두 가능하다.
==과 !=은 객체 타입간의 연산에서 사용 가능하며 주소를 비교한다.

9.5. 논리

#!syntax java
a && b;
a || b;
!a;
순서대로 모두 true인 경우, 하나 이상 true인 경우, false인 경우에 true를 반환한다. 피연산자는 boolean이다.
첫 번째 항에서 반환값이 결정된 경우[24] 두 번째 항을 평가하지 않는 단락 평가가 적용된다.[25]

9.6. 비트

#!syntax java
a & b;
a | b;
a ^ b;
~a;
순서대로 각 비트에 대하여 모두 1인 경우, 하나 이상 1인 경우, 하나만 1인 경우, 0인 경우에 그 자리가 1인 비트를 반환한다. 피연산자는 정수이다.
&, |, ^의 경우 boolean 간 연산에서도 사용할 수 있다. &&, ||와 달리 단락 평가를 적용하지 않는다.

9.7. 시프트

#!syntax java
a << b;
a >> b;
a >>> b;
순서대로 a의 비트를 b만큼 왼쪽으로 이동, 부호를 유지하여 오른쪽으로 이동, 부호를 유지하지 않고 오른쪽으로 이동이다.

9.8. 대입

#!syntax java
x = a;
x += a;
x -= a;
x *= a;
x /= a;
x %= a;
x &= a;
x |= a;
x ^= a;
x <<= a;
x >>= a;
x >>>= a;
a의 값을 x에 대입한다. 두번재 줄부터의 연산자는 추가로 붙은 연산자의 결과를 대입한다. 예를 들어 x += ax = x + a와 같다. =의 경우 객체간 연산에서 사용할 수 있으며 이 경우 주소를 대입한다. 연산자 자체도 대입한 값을 반환한다. 예를 들어 (x = a) != null의 경우 x에 a를 대입 후 그 값이 null이면 false를 반환한다.

9.9. 조건

유일하게 3개의 항을 사용하기 때문에 삼항 연산자라고도 부른다.

#!syntax java
a ? b : c;
a는 boolean이며, b와 c는 같은 타입으로 승격될 수 있어야 한다.
a가 true면 b를, false면 c를 반환한다.

9.10. 형변환

#!syntax java
(T) a;
a의 타입을 T로 변환한다. 변환할 수 없는 경우 예외가 발생한다.
숫자 타입을 크기가 작은 타입으로 변환시키거나[26], 부모 클래스 타입의 객체를 자식 클래스로 변환시킬 때 사용된다.[27]

9.11. instanceof

#!syntax java
obj instanceof T
obj가 null이 아니면서 그 타입이 T의 하위 타입이거나, T 자체인 경우 true를 반환한다. 피연산자는 객체 타입이다.
즉, obj가 T로 항변환될 수 있는지를 확인하는 용도다.

#!syntax java
obj instanceof T t
자바 16부터 true인 경우 즉시 항변환하도록 할 수 있다. t는 타입이 T이고, obj가 저장된 변수이다. t는 컴파일러가 사용 가능하다고 추론한 범위 내에서만 사용 가능하다.

9.12. 기타 특수 연산자

#!syntax java
arr[i];
method();
new Class();
obj.field;
(a, b) -> a + b;
obj::method;
순서대로 배열 접근, 메서드 호출, 생성자 호출 및 객체 생성, 멤버 접근, 람다, 메서드 참조이다.
arr는 배열, i는 정수, method는 메서드, Class는 클래스, obj는 객체 또는 클래스, field는 obj의 메서드 또는 변수이다.
반환 타입은 순서대로 원소의 타입, method의 반환 타입, Class 객체, field의 타입 또는 반환 타입, 나머지 2개는 컴파일러가 추론한 함수형 인터페이스를 구현한 객체이다.

9.13. 우선순위

우선순위 연산자 역방향 결합[28]
1 (), [], ., 후위 증감 X
2 전위 증감, 부호, !, ~, (T) O
3 *, /, % X
4 +, -
5 시프트
6 <, <=, >, >=, instanceof
7 ==, !=
8 &
9 ^
10 |
11 &&
12 \
13 대입 O
::, ->, new의 경우 문법으로 취급할 수도 있기 때문에 쓰지 않았다. 굳이 쓰자면, ::, new는 최상위이고 ->는 최하위에 대응된다.

10. 조건문

10.1. if

#!syntax java
if (expression) {
    //statement
}
소괄호 내의 식이 true라면 statement 부분의 중괄호를 실행하고 아니면 넘어간다.

#!syntax java
if (expression) {
    //statement1
else {
    //statement2
}
소괄호 내의 식이 true라면 statement1 부분을, false라면 statement2 부분을 실행한다.

#!syntax java
if (exp1) {
    //stmt1
} else if (exp2) {
    //stmt2
} else if (exp3) {
    //stmt3
}
...
} else {
    //stmtN
}
exp1이 true면 stmt1을, false인데 exp2가 true면 stmt2를, ... 전부 false면 stmtN를 실행한다.

#!syntax java
if (exp1) stmt1;
else if (exp2) stmt2;
else stmt3;
중괄호 내의 문이 한줄이라면 생략 가능하다.

10.2. switch

#!syntax java
switch (exp) {
    case value1:
        stmt1;
        break;
    case value2:
        stmt2;
        break;
...
    default:
        stmtN;
}
exp의 값이 value1이면 stmt1을, value2면 stmt2를, ... 전부 아니면 stmtN을 실행한다. break는 switch문을 벗어나는 역할을 한다.
exp의 반환 타입은 byte, short, char, int와 그 래퍼 클래스나, Enum 상수, String이어야 한다.
case의 value들은 상수 표현식[29]이어야 한다.

#!syntax java
switch (exp) {
    case value1:
        stmt1;
    case value2:
    case value3:
        stmt2;
        break;
    case value4:
        stmt3;
exp의 값이 value1이면 stmt1과 stmt2를, value2또는 value3이면 stmt2를, value4면 stmt3를 실행하고 아무것도 아니면 그냥 넘어간다.

#!syntax java
switch (exp) {
    case value1 -> stmt1;
    case value2 -> {
        stmt2;
        stmt3;
    }
    case value3, value4 -> stmt4
    default -> stmt5;
}
자바 14부터 사용 가능한 개선된 switch이다. exp의 값이 value1이면 stmt1을, value2면 stmt2와 stmt3를, value3 또는 value4면 stmt4를, 아무것도 아니면 stmt5를 실행한다.

#!syntax java
var a = switch (exp1) {
    case value1:
        yield exp2;
    case value2:
        stmt1;
        yield exp3;
    default:
        yield exp4;
}
표현식으로 쓸 수도 있다. 이 경우 switch가 모든 경우의 수를 다뤄야 한다.[30] yield로 반환값을 지정하기 때문에 break를 사용하지 않는다.
개선된 switch로도 사용 가능하며, case에서 중괄호를 생략한 경우 yield 없이 바로 반환값을 쓴다.

#!syntax java
switch (exp) {
    case "hello" -> stmt1;
    case String s -> stmt2;
    case Integer i when i > 0 -> stmt3;
    case Integer i -> stmt4;
    case null -> stmt5;
    default -> stmt5;
}
자바 21부터 패턴 매칭을 사용할 수 있다. 이 여기에서 exp의 값이 "hello"면 stmt1을, String으로 항변환 가능하면 변환한 변수 s를 만들고 stmt2를, Integer로 항변환 가능하고 그 값이 0보다 크면 stmt3, 0보다 같거나 작으면 stmt4를, null이거나 아무것도 아니면 stmt5를 실행한다.
기존 switch에서도 쓸 수 있다.

11. 반복문

11.1. while

#!syntax java
while (exp) {
    stmt;
}
exp가 true인동안 stmt를 반복한다. 처음부터 false라면 반복하지 않고 넘어간다.

#!syntax java
while (true) stmt;
exp 자리에 true를 쓰면 무한반복이 된다. 중괄호 내 문이 한줄이라면 생략할 수 있다.

11.1.1. do-while

#!syntax java
do {
    stmt;
} while (exp)
stmt를 한번 실행 후, exp가 true면 반복을 하며, 아니라면 그대로 넘어간다.

11.2. for

#!syntax java
for (exp1; exp2; exp3) {
    stmt;
}
exp1을 실행 후, exp2가 true인 동안 stmt를 반복하며, 매 반복 마지막에 exp3를 실행한다. 역시 중괄호 내 문이 한줄이면 생략 가능하다.

#!syntax java
exp1;
while (exp2) {
    stmt;
    exp3;
}
for를 while로 바꾼 코드다.[31]

#!syntax java
for (int i = 0; i < n; i++) {
    stmt;
}
보통은 이런방식으로 쓴다. 이 코드는 stmt를 n번 실행한다.

11.2.1. for-each

#!syntax java
Collection<E> collection = ...

for (E e : collection) {
  stmt;
}
배열과 Iterable을 구현간 객체에 대해 각 데이터를 순환하며 반복한다. 컴파일 시 그냥 for로 바뀐다.
여기서 <E>는 E 타입의 원소만 허용한다고 이해하면 된다.

#!syntax java
E[] arr = ...
E[] temp = arr;

for (int i = 0; i < temp.length; i++) {
    E e = temp[i];
    stmt;
}


#!syntax java
Collection<E> collection = ...
Iterator<E> iterator = collection.iterator();

while (iterator.hasNext()) {
    E e = (E) iterator.next();
    stmt;
}

11.3. 분기

#!syntax java
for (var e : arr) {
    if (e == null) continue;
    IO.println(e.toString())
}
해당 코드는 배열 arr의 각 원소에 대하여 null이 아닐경우 문자열로 변환하여 출력하는 코드이다.
continue는 현재 반복문에서 다음 반복으로 넘어간다. 당연히 다음 반복 전에 조건을 검사하며, for (exp1; exp2; exp3) stmt;에서 exp3를 정상적으로 실행한다.

#!syntax java
while (true) {
    var str = IO.readln("비밀번호:");
    if (str.equals("pass")) break;
}
해당 코드는 pass를 입력받을 때까지 계속 입력을 받는 코드이다.
break는 현재 반복문에서 탈출한다.

12. 라벨

문(statement)에 사용할 수 있다. 해당 문에 이름을 붙여 즉시 빠져나가거나(break), 다음 반복으로 넘어간다.(continue. 단, 반복문에 라벨을 붙여야 한다.) 보통은 반복문에서 사용한다.

#!syntax java
outer: for (var arr : arr2d) for (var e : arr) {
    if (...) continue outer;
}
바깥쪽 for문이 outer라는 이름을 가졌으므로 continue를 바깥쪽 for문에 적용되도록 쓸 수 있다.

#!syntax java
loop: do {
    stmt;
} while (exp)

ifLabel: if (exp) {
    stmt;
}

sw: switch (exp) {
    case value1 -> stmt1;
    case value2 -> stmt2;
}

t: try {
    stmt1;
} catch (...) {
    stmt2;
}

syncBlock: synchronized (...) {
    stmt;
}

stmtBlock: {
    stmt;
}
문이기만 하면 어디든지 사용할 수 있다. if, switch, try, synchronized, 심지어 단독으로 사용할 수도 있다.

13. 메인 메서드

자바 프로그램을 실행하려면 진입점인 메인 메서드가 필요하다. top-level에 클래스만 선언할 수 있는 자바의 특성상 메인 메서드도 반드시 어떤 클래스 안에 있어야 하며, 해당 클래스를 메인 클래스라고 한다.

#!syntax java
public class Main {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}
가장 기본적인 메인 메서드이다. 클래스 이름과 매개변수 이름은 상관 없으며, 매개변수 타입을 String...으로 쓸 수 있다. 보통은 타입을 String[], 이름을 args로 쓴다.

#!syntax java
public class Main {
    void main() {
        IO.println("Hello, World!");
    }
}
자바 25부터는 main 메서드가 간소화되었다. 접근 제어자는 private만 아니면 되고, 매개변수를 생략할 수 있으며, static을 쓰지 않아도 된다.[32]

#!syntax java
void main() {
    IO.println("Hello World!");
}
또는 컴팩트 파일을 사용할 수 있다. top-level에 메인 메서드를 두면 된다. 비메인 메서드도 최상위 함수로 쓸 수 있다. 컴팩트 파일은 무조건 메인 메서드를 포함해야 하며, 패키지를 명시할 수 없다. 파일 이름과 동일한 이름의 클래스의 인스턴스가 만들어진다.

14. 제어자

클래스, 메서드, 변수를 선언할 때 특성을 지정하는 키워드로 12개의 제어자가 있다.

14.1. 접근 제어자

생략 포함, 4개의 제어자가 있다. 생략은 default로 부른다.
해당 요소를 어디까지 접근할 수 있는지를 지정한다.

14.2. 기타 제어자

사용 여부에 따라 특성이 지정된다.

15. 메서드

자바에서 모든 함수는 클래스에 속해 있으므로 메서드라고 부른다.

수학에서는 수를 넣으면 그 수에 무언갈 더하거나 빼는 등의 연산을 거친 결과를 밷는 것을 함수라고 한다.
자바에서 함수(메서드)는 코드상에서 메서드를 실행(호출)하면 내부의 코드들이 실행되는 코드 묶음이다. 여기서 수학처럼 호출할 때 데이터를 넘길 수 있고, 메서드가 종료될 때 데이터가 반환될 수 있다.
메서드가 받는 변수를 매개변수라고 하고, 매개변수로 넘기는 값을 인자라고 한다.

#!syntax java
// 반환 타입과 메서드이름을 쓰고 뒤에 소괄호를 붙인다.
void name() {
    // 메서드 내용
}
메서드를 선언하려면 다음과 같이 작성한다.

#!syntax java
public void myMethod(int i) {
    // 메서드 내용
}
이름 앞에 제어자를 쓰거나, 매개변수를 지정할 수 있다.

#!syntax java
// 두 정수를 받고 더한 결과를 반환하는 메서드
public int sum(int a, int b) {
    return a + b;
}
메서드를 종료하고 값을 반환하려면 return을 사용한다. void 메서드는 생략할 수 있으며, 다른 메서드는 반드시 반환해야 한다.

#!syntax java
// 문자열들을 받아 하나의 문자열로 결합 후 출력하는 메서드
public void printJoinedText(String... args) {// 가변 매개변수이다. 배열로 취급된다.
    if (args.length == 0) return; // return은 값 반환 뿐만 아니라 메서드 종료도 하므로 args가 비어있을 경우 아래코드가 실행되기 전에 메서드가 종료된다.
    IO.println(String.join(", ", args)); // 문자열 배열을 하나의 문자열로 합친다. ", "로 원소가 이어진다.
    // return;이 생략되었다.
}
가변 매개변수를 사용한 메서드이다. printJoinedText();, printJoinedText("Hello");, printJoinedText("Hello", "World!");, printJoinedText(arr); 처럼 인자를 넘기지 않거나, 여러 개를 넘기거나, 배열로 넘길 수 있다.
가변 매개변수는 마지막에 써야 하며, 하나만 쓸 수 있다.

15.1. 오버로딩

같은 위치에 같은 이름의 메서드를 만들 수 없지만, 매개변수가 다른 경우 호출할 메서드를 구분할 수 있기에 가능하다.
매개변수 개수, 타입, 순서가 달라야 한다.

#!syntax java
public void println() {
    IO.println();
}
public void println(int i) {
    IO.println(i);
}
public void println(String s) {
    IO.println(s);
}
public void println(int i, String s) {
    IO.println(i + s);
}
public void println(String s, int i) {
    IO.println(s + i);
}
해당 메서드들은 호출 시점에서 호출할 메서드를 구분할 수 있다.

#!syntax java
public void print(Object o, boolean newLine) {
    IO.print(o);
    if (newLine) println();
}
public void print(Object o) {
    print(o, false);
}
매개변수가 기본값을 가진 것처럼 동작하도록 할 수 있다.

#!syntax java
public void print(int i) {
    IO.println(i);
}
public void print(int... args) {
    for (var i : args) IO.print(i);
    println();
}
public void print(String... args) {
    for (var i in args) IO.print(i);
    println();
}
매개변수를 int 하나만 주면 1번째가, 2개 이상 주면 2번째가 호출된다. 하나도 안 줄 경우 2번째와 3번째를 구분할 수 없어서 호출할 수 없다.

16. 클래스

클래스는 용도에 따라 크게 3가지로 볼 수 있다. 유틸리티 클래스와 데이터 클래스는 임의로 정한 명칭이다.

16.1. 유틸리티 클래스

정적 메서드 및 변수를 모아둔 클래스다. 예시로 java.lang.Math가 있다.

이 클래스의 역할은 메서드와 변수를 담아두는 상자에 비유할 수 있다.

(클래스명). (메서드명)으로 메서드를 호출하고, (클래스명).(변수명)으로 변수를 가져올 수 있다.

예시로 도형의 넓이를 계산해 주는 클래스는 다음과 같다.
#!syntax java
// Utils.java
public class Utils
    public static final int PI = 3.14159;

    public static int getAreaOfSquare(int width, int height) {
        return width * height;
    }

    public static double getAreaOfTriangle(int base, int height) {
        return base * height / 2.0;
    }

    public static double getAreaOfCycle(int radius) {
        return Math.pow(radius, 2) * PI;
    }
}

16.2. 데이터 클래스

대부분이 비정적 메서드 및 필드로 이루어져 객체를 만들고 활용하는 클래스다. 예시로 java.lang.String, java.util.ArrayList가 있다.

용어 정리

데이터 클래스를 쉽게 이해하자면 커스텀 데이터 타입이다. 예시로 "사람"이라는 데이터 타입을 만들면 다음과 같다.
#!syntax java
// Person.java
public class Person {
    // 정적 변수를 사용할 수 있다. 모든 객체가 공유하는 필드라고 생각하면 된다.
    private static final String SPECIES = "Homo sapiens";

    // 사람의 이름. 각 객체별로 다른 필드를 가져 공유되지 않는다.
    // 이 경우 final을 붙여도 되며, 생성자에서 한번 초기화하고 읽기 전용이 된다. 객체마다 값이 다를 수 있으므로 상수가 아닌 불변 변수라고 부른다.
    private String name;

    // 생성자. 객체를 생성할때 new 키워드와 함께 사용된다.
    public Person(String name) {
        // this는 객체 자기 자신을 가리킨다.
        this.name = name;
    }

    // 비정적 메서드. name 필드의 값을 리턴한다.
    public String getName() {
        return name; // this.name과 동일
    }

    // 마찬가지로 정적 메서드를 사용할 수 있다. 객체에 대하여 호출하는게 아니기에 비정적 변수 및 메서드를 직접 활용할 수 없다.(어떤 인스턴스의 필드를 가리키는지 모르기 때문.)
    public static String getSpecies() {
        return SPECIES;
    }
}

// Main.java
void main() {
    // Person 타입의 myPerson 변수를 만들고, new Person("John")이 만드는 인스턴스를 값으로 저장한다.
    Person myPerson = new Person("John"); // 새로운 객체를 생성하려면 new 키워드를 사용한다.
    IO.println(myPerson.getName()); // John
    IO.println(Person.getSpecies()); // Homo sapiens
}


이해를 위한 다른 비유도 많다. 클래스를 붕어빵 틀, 인스턴스를 만들어진 붕어빵, 필드를 반죽 및 속으로 비유하고, 객체는 붕어빵을 가리키는 용어로 설정하여 이해할 수 있다.

자바는 객체간의 상호작용으로 프로그램을 구현한다. 단순히 사람이나 자전거 같이 물체를 구현한 객체 외에도 정말 많은 종류가 존재한다.

16.2.1. Getter와 Setter

private 필드를 가져오는 메서드를 Getter, 설정하는 메서드를 Setter라고 한다.
#!syntax java
// 자동차 클래스
public class Car {
    private int speed = 0; // 속도
    private int gear = 0; // 기어. 0부터 차례대로 P, R, N, D를 뜻한다.
    private final List<Object> trunk = new ArrayList<>(); // 트렁크
    private String licensePlate = "12가 3456"; // 번호판

    public int getSpeed() {
        return speed;
    }
    public void setSpeed(int speed) {
        if (speed < 0) throw new IllegalArgumentException("속력은 음수가 될 수 없음"); // 잘못된 대입 방지
        this.speed = speed;
    }

    public String getGear() { // 알아보기 힘든 int를 문자열로 바꿔서 반환
        return switch (gear) {
            case 0 -> "P";
            case 1 -> "R";
            case 2 -> "N";
            case 3 -> "D";
            default -> throw new RuntimeException("도달할 수 없는 코드");
        };
    }
    public void setGear(String gear) {
        this.gear = switch (gear) {
            case "P" -> 0;
            case "R" -> 1;
            case "N" -> 2;
            case "D" -> 3;
            default -> throw new IllegalArgumentException("잘못된 변속");
        };
    }

    public List<Object> getTrunk() {
        return Collections.unmodifiableList(trunk); // List를 수정 불가로 바꿔서 반환
    }
    public void addObjectInTrunk(Object object) {
        if (object instanceof Person) throw new IllegalArgumentException("잘못된 물건"); // 사람을 트렁크에 넣을 수 없다.
        trunk.add(object);
    }
    public void removeObjectInTrunk(Object object) {
        if (!trunk.contains(object)) throw new IllegalArgumentException("존재하지 않는 물건"); // 없는 물건을 꺼낼 수 없다.
        trunk.remove(object);
    }

    public String getLicensePlate() {
        return licensePlate;
    }
    void setLicensePlate(String licensePlate) { // 아무나 번호판을 변경할 수 없다.
        this.licensePlate = licensePlate;
    }
}

16.3. 싱글톤 패턴

하나의 객체만을 다루는 디자인 패턴이다.

#!syntax java
// Config.java
public class Config {
    private static Config instance = new Config();

    public static Config getConfig() {
        return instance;
    }

    private int intOption;
    private boolean boolOption;

    private Config() {
        loadConfig();
    }

    private loadConfig() {
        intOption = ...
        boolOption = ...
    }

    //Getter, Setter 생략
}

// Main.java
void main() {
    var i = Config.getConfig().intValue;
    var b = Config.getConfig().boolValue;
}
단 하나의 객체만 사용하기에 유틸리티 클래스와 비슷해 보이지만, 유틸리티 클래스는 객체가 없고 싱글톤 패턴에는 객체가 있다. 때문에 상속, 구현이 가능하다.

#!syntax java
public class Config {
    private static Config instance; // 자동으로 null로 초기화된다.

    public static Config getConfig() {
        if (instance == null) instance = new Config();
        return instanace;
    }
}
지연 초기화 방식이다. 단, 이 상태에서는 멀티 스레드 환경에서 문제가 발생할 수 있다.


상태가 없이 순수 메서드와 상수들이 모여있다면 유틸리티 클래스, 상태와 동작을 가진 하나의 시스템 객체는 싱글톤이 적합하다.

16.4. 상속

상속을 하려면 extends 키워드를 사용하면 된다. 상속이라는 개념이 다소 이해가 어려울 수 있는데 비슷한 필드, 메서드를 가진 자식 클래스들의 공통적인 부분을 부모 클래스로 만들고 상속하여 한번의 선언으로 여러 클래스에서 활용하는 기능이다.
#!syntax java
// Student.java
public class Student extends Person {
    private double gpa;

    public Student(String name, double gpa) {
        super(name); // 부모 클래스 Person의 생성자를 호출. 반드시 호출해야 하며, 생성자 호출 전에 코드를 쓸 수 있지만, 현재 객체에 접근하는 코드는 쓸 수 없다.
        this.gpa = gpa;
    }
    public double getGPA() {
        return gpa;
    }

    public String getNameWithGPA() {
        return getName() + "_" + gpa; // 부모 클래스의 메서드를 호출할 수 있다.
    }
}
// Main.java
void main() {
    Student joe = new Student("Joe", 4.0);
    // Student는 Person이기도 하므로 Person의 getName() 메서드를 사용할 수 있다.
    IO.println(joe.getName()); // Joe
    IO.println(joe.getGPA()); // 4.0
    IO.println(joe instanceof Person); // true
    Person p = new Student("p", 2.0); // 타입을 부모 클래스로 지정할 수 있다. 단, 부모 클래스에 존재하는 필드와 메서드만 접근 가능하다.
    IO.println(p.getName()); // p
    if (p instanceof Student s) IO.println(s.getNameWithGPA()); // p_2.0
    else IO.println("p is not student"); // 호출되지 않는다.
    printName(joe); // Student 타입은 Person의 하위 타입이므로 자동으로 항변환된다.
}

void printName(Person p) {
    IO.println(p.getName());
}
자식 클래스는 부모 클래스의 메서드와 필드를 이용할 수 있다.[39] 자식 클래스는 부모 클래스 타입으로 항변환될 수 있으며, 이 경우 접근 가능한 메서드와 필드가 부모 클래스기준이 된다.
부모 클래스가 final이면 상속할 수 없다.

#!syntax java
// Parent.java
public class Parent {
    public int i = 1;

    public void printHello() {
        IO.println("Hello");
    }
}

// Child.java
public class Child extends Parent {
    public int i = 3;

    @Override // 생략할 수 있지만 쓰는걸 추천한다.
    public void printHello() {
        IO.println("Hello, World!");
    }

    public void print() {
        IO.println(i);
        IO.println(super.i);
        printHello();
        super.printHello();
    }
}

// Main.java
void main() {
    Child c = new Child();
    c.printHello(); // Hello, World!
    IO.println(c.i); // 3
    Parent p = c;
    p.printHello(); // Hello, World!
    IO.println(p.i); // 1
    c.print(); // 순서대로 1, 3, "Hello, World!", "Hello" 출력
}
자식 클래스에서 부모 클래스의 메서드를 재정의할 수 있다. 재정의할 메서드는 자식 클래스에서 접근 가능해야 하며[40] final이 아니여야 한다. 부모 클래스와 동일한 이름, 매개변수의 메서드를 만들면 재정의로 간주한다. 단, 접근 제어자는 동일하거나 더 넓어야 하며[41] 반환 타입은 동일하거나 그 하위 타입으로 지정해야 한다.[42]
자식 클래스에서 부모 클래스 기준의 메서드나 필드를 사용하려면 super를 사용한다.
상속되지 않은 메서드[43]나, 필드[44]를 재정의하려고 시도하면 선언은 되지만, 재정의는 되지 않는다.[45] 이를 숨김이라고 하며[46] 두 메서드/필드가 동시에 존재하게 된다. 호출 기준은 해당 객체가 어떤 타입으로 다뤄지는지에 따라 달라지며, 자식 클래스에서는 그냥 쓰면 자식 클래스의, super를 사용하면 부모 클래스의 것이 호출된다.

16.5. 추상 클래스

#!syntax Java
// Animal.java
public abstract class Animal {
    public abstract void sound();
    public void eat(String food) {
        IO.println(food + "을/를 먹습니다.");
    }

    public Animal() {
        IO.println("객체 생성")
    }
}

// Dog.java
public class Dog {
    @Override
    public void sound() {
        IO.println("멍멍");
    }
}
메서드에 abstract를 붙이면 추상 메서드로 선언된다. 추상 메서드는 반드시 추상 클래스 내에 있어야 하므로 클래스에 abstract를 붙여 추상 클래스로 선언해야 한다.[47]
추상 메서드는 구현을 부모 클래스가 하지 않고, 자식 클래스가 반드시 재정의해야 한다.
추상 클래스는 생성자를 가질 수 있지만 인스턴스를 만들 수 없고 반드시 상속을 해야 한다. 추상 클래스의 생성자는 자식 클래스에 의해 호출된다.

16.6. Object

최상위 클래스인 Object 클래스는 모든 클래스의 부모로 모든 클래스는 Object 클래스를 상속받으며, 사용자가 직접 만든 클래스 또한 Object 클래스를 상속받는다.
때문에 Object 타입의 변수는 모든 객체를 담을 수 있고, 반환 타입이 Object면 모든 객체를 반환할 수 있다.

Object 클래스의 대표적인 메서드

17. 인터페이스

추상 클래스와 비슷하지만, 상속이 어느 클래스의 속성과 메서드를 '상속'한다면, 인터페이스는 어떤 클래스가 '구현'해야 한다.

#!syntax java
// Incrementable.java
public interface Incrementable {
    String NAME = "Incrementable";
    void increment();
    default void printMessage() {
        IO.print("디폴트 메서드 | ");
        helper();
    }
    private void helper() {
        IO.println("private 메서드");
    }
    static void printMe() {
        IO.println("Incrementable interface");
    }
}

// Person.java
public class Person implements Incrementable {
    public int age;

    public Person(int age) {
        this.age = age;
    }

    @Override
    public void increment() {
        age++;
    }

// Main.java
void main() {
    Person p = new Person(1);
    p.increment();
    IO.println(p.age); // 2
    IO.println(p instanceof Incrementable); // true
    p.printMessage(); // 디폴트 메서드 | private 메서드
    Incrementable.printMe(); // Incrementable interface
    IO.println(Incrementable.NAME); // Incrementable
}
}
interface 키워드를 사용한다. 인터페이스를 만들 때는 주로 추상 메서드만을 사용한다.
인터페이스의 필드는 자동으로 public static final이 붙으며, 메서드는 public, 추상 메서드는 abstract가 기본으로 붙는다.
Java 8부터는 비추상 메서드를 'default' 키워드를 사용해 정의할 수 있다.
private 메서드는 인터페이스의 default 메서드를 위해 존재한다.
구현은 implements를 사용한다. 추상 클래스와 마찬가지로 모든 추상 메서드를 구현해야 한다.

#!syntax java
public class Child extends Parent implements IntOne, IntTwo, IntThree {
}
구현과 상속을 동시에 할 수 있고, 여러 인터페이스를 구현할 수도 있다.

#!syntax java
public interface Collection extends Iterable {
}
인터페이스가 인터페이스를 상속할 수 있다.

#!syntax java
public void printAll(Iterable iterable) {
    for (Object e : iterable) IO.println(e);
}
Iterable을 구현한 객체에 대해 각 원소를 출력한다. ArrayList나, HashSet 등은 모두 Iterable을 구현하므로 인자로 쓸 수 있다.

17.1. 함수형 인터페이스

#!syntax java
@FunctionalInterface // 없어도 된다.
public interface Runnable {
    void run();
}
추상 메서드가 단 하나뿐인 인터페이스를 말한다. default, static 메서드와 Object의 메서드는 고려하지 않는다.
람다식에서 사용된다.

18. Enum

#!syntax java
// Day.java
enum Day {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

// Main.java
void main() {
    Day day = Day.MONDAY;

    if (day == Day.SATURDAY || day == Day.SUNDAY) IO.println("주말 입니다.");


    var text = switch (day) {
        case Day.MONDAY -> "월요일";
        case Day.TUESDAY -> "화요일";
        case Day.WEDNESDAY -> "수요일";
        default -> {
            return;
        }
    }

    IO.println(text);
}
상수로 쓸 수 있는 객체를 모아둔 특수 클래스로 열거형이라고 번역된다. 객체가 더 이상 생성되지 않기 때문에 == 연산자로 비교할 수 있다.

#!syntax java
enum Constant {
    PI(3.14), E(2.71), ROOT2(1.41);

    private final double value;

    Constant(double value) {
        this.value = value;
    }

    public double getValue() {
        return value;
    }
}
클래스인 만큼 필드, 메서드, 생성자를 가질 수 있다. 생성자는 자동으로 private이 붙는다.

19. 예외

예외란 프로그램의 오류라고 생각하면 된다. 보통 0으로 나누거나, 배열에서 범위를 벗어난 인덱스에 접근하거나 할 때 발생한다.
예외를 발생시키는 것을 예외를 던진다고 한다.

파일:자바 throwable.png
checked 예외는 catch 블록으로 처리하거나 throws 키워드로 넘겨야 하며, unchecked 예외는 예외처리를 강제하지 않는다.

#!syntax java
public void method() {
  RuntimeException e = new RuntimeException();
  throw e;
} 
예외를 던지려면 throw 키워드를 사용한다.

#!syntax java
public void method() thorws Exception {
  throw new Exception();
}
throws 키워드를 사용하여 강제 예외 처리를 상위 메서드로 넘길 수 있다.

#!syntax java
void main() {
    method1();
    IO.println("main method"); // 실행되기 전에 메인 메서드가 끝나 프로그램이 종료된다.
}

public void method1() {
    method2();
    IO.println("method1") // 실행되기 전에 윗줄에서 메서드가 끝난다.
}
public void method2() {
    throw new RuntimeException();
    IO.println("method2"); // 실행되기 전에 윗줄에서 메서드가 끝난다.
}
예외를 던지면 그 예외가 잡힐 때까지 호출 스택을 거슬러 올라간다. 진입점(보통 메인 메서드)에서도 예외가 잡히지 않은 경우 프로그램이 종료된다.

#!syntax java
// StopSignal.java
public class StopSignal extends Throwable {
}

// Main.java
void main() {
    try {
        ...
    catch (StopSignal _) {
        IO.println("프로그램 중지됨.");
    }
}
예외가 잡힐 때까지 호출부로 넘어간다는 특징을 이용해 바로 상위 메서드로 빠져나가는 코드를 쓸 수 있다. 단, 일반적으로 추천하지 않는다.[49]

19.1. try

예외 처리를 위한 문법. try 단독으로 사용할 수 없고 catch나 finally 블록과 함께 사용하거나, try-with-resources로 사용해야 한다.[50]

try-finally: 예외처리 목적보다는 finally의 무조건 실행 특징을 활용하는 문법이다.
#!syntax java
try {
    IO.println("Hello, World!");
} finally {
    IO.println("이 문자열은 반드시 출력된다.");
}


try-catch: 가장 일반적인 용법. 여러개의 catch를 쓸 수 있다.
#!syntax java
String str = "1234a";
try {
    int i = Integer.parseInt(str); // 문자열을 정수로 변환한다. 이 경우, a 때문에 NumberFormatException을 발생시킨다.
} catch(NumberFormatException _) {
    System.err.println("잘못된 문자열"); // 에러 메시지를 출력한다.
} catch (IOExceptoin | RuntimeException e) { // IOException 또는 RuntimeException을 잡는다.
    e.printStackTrace(); // printStackTrace()는 현재 예외의 정보(종류, 메시지, 원인 등)와 함수 호출 트리를 보여준다.
} catch (Throwable e) {
    e.printStackTrace(); // 오류 포함 모든 예외를 잡는다. 상술했듯 오류는 처리를 안하기에 잘 사용되지 않는다.
} catch (Error e) {
    e.printStackTrace(); // 이 catch 블록은 모든 오류를 잡지만, 위의 Throwable을 잡는 catch 블록 때문에 실제로는 작동하지 않는다.
}


try-catch-finally: Java 6까지, Closeable을 구현한 객체를 다룰 때 사용되던 용법.
#!syntax java
BufferedReader reader = null;
try {
    reader = new BufferedReader(new FileReader("example.txt")); // example.txt 파일을 가져온다.
    String line;
    while ((line = reader.readLine()) != null) IO.println(line); // 파일의 내용을 한줄 씩 출력한다.
} catch (Exception e) {
    System.err.println("파일 읽기 실패: " + e.getMessage()); // 모든 예외를 잡는 catch 블록이다. 예외를 잡은 후, 예외 메시지를 출력한다.
} finally {
    if (reader != null) {
        try {
            reader.close(); // 무조건 실행되는 finally 블록의 특징을 활용하여 자원을 닫는다.
        } catch (IOException e) {
            System.err.println("파일 닫기 실패: " + e.getMessage());
        }
    }
}


try-with-resources: Java 7 부터, AutoCloseable[53]을 구현한 객체를 다룰 때 사용되는 문법. 마지막에 자동으로 close() 메서드를 호출한다. 위의 try-catch-finally의 코드를 try-with-resources로 바꾼 모습.
#!syntax java
try (BufferedReader reader = new BufferedReader(new FileReader("example.txt"))) { // 여러 객체를 선언하려면, 세미콜론(;)을 사용하여 구분한다.
    String line;
    while ((line = reader.readLine()) != null) IO.println(line);
} catch (Exception e) {
    System.err.println("파일 읽기 실패: " + e.getMessage());
} // 이후 자동으로 reader.close(); 를 실행한다.


아래는 위의 코드가 컴파일될 때 변환되는 모습이다.
#!syntax java
try {
    BufferedReader reader = new BufferedReader(new FileReader("example.txt"));
    Throwable primaryException = null;
    try {
        String line;
        while ((line = reader.readLine()) != null) IO.println(line);
    } catch (Throwable t) {
        primaryException = t;
        throw t;
    } finally {
        if (reader != null) {
            if (primaryException != null) {
                try {
                    reader.close();
                } catch (Throwable closeEx) {
                    primaryException.addSuppressed(closeEx);
                }
            } else reader.close();
        }
    }
} catch (Exception e) {
    System.err.println("파일 읽기 실패: " + e.getMessage());
}

20. 제네릭

타입을 미리 정해두지 않고 사용할 때 정하는 방식. 사용할 때 객체 타입[54]을 매개변수로 받듯이 사용돼서 타입 매개변수라고 부른다. 타입 매개변수는 <>안에 넣어야 하며, 여러 개의 타입 매개변수를 사용하려면 사이에 콤마를 넣어주면 된다.
타입 매개변수의 이름은 암묵적인 규칙이 있다.
제네릭이 있더라도 없이 쓸 수 있다. 이 경우 타입 매개변수는 Object와 비슷하게 동작한다.

컴파일 시 제네릭 정보는 사라진다. 때문에 instanceof를 쓸 수 없다.

20.1. 클래스/인터페이스

#!syntax java
// Wrapper.java
public class Wrapper<T> {
  private final T content;

  public Wrapper(T content) {
    this.content = content;
  }
  
  public T getContent() {
    return this.content;
  }
}

// Main.java
void main() {
    Wrapper<String> myWrapper1 = new Wrapper<>("Namu Wiki"); // new Wrapper뒤에 <>를 붙여야한다.
    Wrapper<Integer> myWrapper2 = new Wrapper<Integer>(44); // java 6 까지는 타입까지 표기해야 한다.
    IO.println(myWrapper1.getContent()); // Namu Wiki
    IO.println(myWrapper2.getContent()); // 44
}
클래스/인터페이스 이름 뒤에 타입 매개변수를 쓰면 된다.

#!syntax java
void main() {
    List list1 = new ArrayList();
    list1.add("abc");
    list1.add(2); // 작동한다.
    String s1 = (String) list1.get(0);

    List<String> list2 = new ArrayList<>();
    list2.add("abc");
    //list2.add(2); // 불가능하다.
    String s2 = list2.get(0);
}

컬렉션에서 제네릭을 사용하면 얻는 이점이다. 제네릭을 사용하지 않을 경우 <Object>로 지정한 것과 비슷하게 동작한다.

20.2. 메서드

#!syntax java
public Object firstObj(Object a, Object b) {
    return a;
}
public <T> T firstT(T a, T b) {
    return a;
}

void main() {
    String a = (String) firstObj("a", "b"); // Object를 반환하므로 항변환이 필요하다.
    String b = firstT("a", "b");
}
메서드이 반환 타입 뒤에 사용할 타입 매개변수를 <>를 이용해 지정해야 한다.

20.3. 와일드카드

#!syntax java
public static void printAll1(Iterable<?> iterable) {
    for (Object e : iterable) IO.println(e);
}
public static <T> void printAll2(Iterable<T> iterable) {
    for (T e : iterable) IO.println(e);
}
와일드 카드는 정해진 타입 없이 아무 타입이나 받을 수 있다. 단, 타입이 정해지지 않기 때문에 쓰기는 불가능하다.
제네릭 안에서만 쓸 수 있기에 void f(? value) 이런 메서드는 불가능하다.

#!syntax java
public static void addList(List<?> list1, List<?> list2) {
    // list1.addAll(list2); // 불가능
}
public static <T> void addList(List<T> list1, List<T> list2) {
    list1.addAll(list2);
}
각각의 와일드카드는 서로 다른 타입이기 때문에 공통된 타입을 쓰려면 제네릭을 써야 한다.

와일드 카드는 제네릭 클래스에서 쓸 수 없다.

20.4. 바운딩

#!syntax java
public static <N extends Number> double sum1(List<N> list) {
    var sum = 0.0;
    for (N n : list) sum += n.doubleValue();
    return sum;
}
public static double sum2(List<? extends Number> list) {
    var sum = 0.0;
    for (Number n : list) sum += n.doubleValue();
    return sum;
}
extends는 지정된 클래스/인터페이스나 그것을 상속/구현하는 타입만 받는다. 그냥 사용한다면 가능한 최상위 클래스는 Object이므로 컴파일 시점에서 T는 Object로 취급된다. 그러나 제한을 걸면 가능한 최상위 클래스가 Number가 되므로 Number의 메서드를 사용할 수 있게 된다.

#!syntax java
public static void addInteger(List<? super Integer> list) {
    list.add(12);
}
super는 지정된 클래스/인터페이스나 그것의 상위 타입만 받는다. 그냥 사용하면 list는 String 같은 전혀 다른 타입을 가질 수 있다고 취급하나, 제한을 걸면 가능한 모든 타입이 Integer를 받을 수 있으므로 쓰기가 가능해진다.
super는 와일드 카드에서만 쓸 수 있다.[55]

상속과 구현은 직접적일 필요 없다. 예를 들어서 Collection -> List -> ArrayList일 때, ArrayList도 T extends Collection의 조건을 만족한다.

#!syntax java
public static <N extends Number & Comparable<T>> void printBiggerNumber(N a, N b) {
    IO.println(a.compareTo(b) > 0 ? a : b); // a가 b보다 크다면 a를 아니면 b를 출력한다.
}
여러 클래스/인터페이스를 동시에 상속/구현하는 타입을 받을 때 구분자로 '&'를 쓸 수 있다. 이때는 클래스가 맨 앞으로 가야 한다.[56]

21. 익명 클래스

#!syntax java
// RunnableImpl.java
public class RunnableImpl implements Runnable {
    @Override
    public void run() {
        IO.pritnln("Delayed");
        IO.println("Hello, World!");
    }
}

// Main.java
void main() {
    Runnable run = new Runnable();

    runWithDelay(run);
}

public void runWithDelay(Runnable run) {
    try {
        Thread.sleep(1000); // 1000ms(1초) 동안 대기한다.
    } catch (InterruptedException _) {} // 체크 예외 처리. 보통은 무시해도 된다.
    run.run();
}
코드를 보면 한번 쓰고 말 객체를 사용하기 위해 RunnableImpl 클래스를 만들어야 하는 번거로움이 있다.

#!syntax java
void main() {
    Runnable run = new Runnable() {
        @Override
        public void run() {
            IO.println("Delayed");
            IO.println("Hello, World!");
        }
    };

    runWithDelay(run);
}

public void runWithDelay(Runnable run) {
    try {
        Thread.sleep(1000); // 1000ms(1초) 동안 대기한다.
    } catch (InterruptedException _) {} // 체크 예외 처리. 보통은 무시해도 된다.
    run.run();
}
익명 클래스는 특정 클래스/인터페이스를 상속/구현하는 이름 없는 클래스를 만들고 즉시 객체를 생성하여 사용한다. 이름이 없기 때문에 다른 객체를 생성할 수 없다. 하나의 클래스/인터페이스만 상속/구현할 수 있다.
익명 클래스의 타입은 상속한 클래스 또는 구현한 인터페이스이므로 외부에서 새로 선언한 메서드를 활용할 수 없다.

21.1. 람다식

만약 익명 클래스가 인터페이스를 구현하는데, 재정의할 메서드가 하나뿐이라면[57] 충분히 컴파일러가 별도의 재정의 문법 없이 알 수 있을 것이다. 다른 이름으로 익명 함수라고도 불린다.

#!syntax java
void main() {
    Runnable run = () -> {
        IO.println("Delayed");
        IO.println("Hello, World!");
    };

    runWithDelay(run);
}

public void runWithDelay(Runnable run) {
    try {
        Thread.sleep(1000); // 1000ms(1초) 동안 대기한다.
    } catch (InterruptedException _) {} // 체크 예외 처리. 보통은 무시해도 된다.
    run.run();
}
'()'는 재정의할 메서드의 매개변수를 뜻하며, '->'는 람디식을 의미하는 연산자, 그 이후 중괄호가 재정의할 메서드의 내용이다. Runnable의 void run() 메서드는 매개변수가 없으므로 소괄호가 비어있다.
람다식이 생성하는 객체의 타입은 사용된 위치를 보고 추론되기 때문에 그냥 var를 사용할 수 없다.[58]

#!syntax java
void main() {
    runWithDelay(() -> IO.println("Hello, World"));
}

public void runWithDelay(Runnable run) {
    try {
        Thread.sleep(1000); // 1000ms(1초) 동안 대기한다.
    } catch (InterruptedException _) {} // 체크 예외 처리. 보통은 무시해도 된다.
    run.run();
}
람다식의 코드가 한줄이라면 중괄호를 생략할 수 있다.

#!syntax java
IntPredicate isEven = (int n) -> {
    return n % 2 == 0;
};

IO.println(isEven.test(4)); // true
매개변수와 반환값이 있는 경우이다. 매개변수가 여러개라면 메서드와 마찬가지로 콤마를 사용한다.

#!syntax java
IntPredicate isEven = n -> n % 2 == 0;

IO.println(isEvent.test(4)); // true
타입 추론으로 매개변수의 타입을 생략할 수 있다. 매개변수가 하나라면 소괄호도 생략할 수 있다.
중괄호를 생략할 때, 반환값이 있는 경우 return을 생략한다.

21.1.1. 메서드 참조

#!syntax java
Supplier<Double> a = () -> Math.random();
IO.println(a.get()); // 0.0 이상 1.0 미만의 실수 출력

ToIntFunction<String> b = str -> str.length();
IO.println(b.applyAsInt("Hello")); // 5

List<String> list = ...
IntFunction<String> c = i -> list.get(i);
IO.println(c.applyAsInt(0)); // list의 0번 인덱스 원소 출력

Supplier<Object> d = () -> new Object();
IO.println(d.get()); // Object 객체 출력
공통적으로 단 하나의 메서드만 사용하며, 받거나 사용하는 매개변수가 일치한다.

#!syntax java
Supplier<Double> a = Math::random;

ToIntFunction<String> b = String::length;

IntFunction<String> c = list::get;

Supplier<Object> d = Object::new;
이러한 람다식을 더 간추려서 메서드 참조를 쓸 수 있다.
정적 메서드를 호출하는 경우 ClassName::methodName, 객체의 메서드를 호출하는 경우 obj::methodName, 매개변수의 메서드를 호출하는 경우 ClassName::methodName, 생성자를 호출하는 경우 ClassName::new를 사용한다.

22. 스레드

스레드는 프로그램 안에서 작업을 실행하는 단위를 말한다. 두 스레드의 실행 위치는 다를 수 있다.

#!syntax java
void main() {
    new Thread(() -> {
        while (true) {
            IO.println("1");
            sleep(500);
        }
    };).start();

    while (true) {
        IO.println("2");
        sleep(500);
    }
}

public void sleep(long ms) {
    try {
        Thread.sleep(ms);
    catch (InterruptedException _) {}
}
1과 2가 각각 별도의 0.5초 간격으로 출력된다. Thread 생성자는 Runnable을 구현한 객체를 받으며, start()메서드는 해당 스레드 실행을 요청한다.[59]

#!syntax java
void main() {
    new Thread(() -> {
        while (true) IO.println(IO.readln());
    };).start();

    while (true) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException _) {}
        IO.println("Hello, World!");
    }
}
1초마다 Hello, World!를 출력하는 동시에 사용자가 입력한 값을 그대로 출력한다.



[1] Main.java 파일에서는 주요 클래스의 이름이 Main이어야 한다.[2] 파일을 하나의 폴더에 저장하지 않고 이미지, 동영상 등으로 나눠서 저장하는 것과 같은 방식.[3] 1구골은 10^100 이다.[4] 정확히는 컴파일러가 타입 추론을 할 때 별도의 선언이 없다면 해당 타입으로 추론한다.[5] 1과 비슷하기 때문에 대문자를 권장한다.[6] 보통 소문자가 많이 쓰이나, 대문자도 자주 보인다.[7] 0.1 + 0.2와 0.3이 아주 살짝 다르다.[8] String은 특별 취급인지라 생성자 없이 만들 수 있고 + 연산자가 따로 정의되어 있는 등 약간의 차이가 있다.[9] 문자열 비교 시 A == B가 아닌 A.equals(B)를 사용하는 이유이기도 하다.[10] String 클래스와 다르다.[11] 대괄호를 타입에 붙이는 걸 권장한다.[12] 객체 타입은 null, boolean은 false, 나머지(int, char, long 등)는 0[13] 실제로 HashSet은 HashMap에서 값을 의미없는 걸로 사용해서 작동한다.[14] null이 아니다.[15] 선언 따로, 초기화 따로 해서 선언도 가능하다.[16] 원시 타입이 아닌 객체 타입의 경우 변수에는 주소가 저장되어 있으므로 주소가 복사된다. 원본 객체와 대입된 객체의 해시값을 출력해 보면 서로 같음을 알 수 있다. Java를 처음 학습하는 초보들이 흔하게 저지를 수 있는 실수이다.[17] 당연히 바꿀 수 없는 경우 예외가 발생한다.[18] 때문에 정수간 나눗셈에서 반환값은 정수이다.[19] obj가 null이면 "null", 객체 타입이면 obj.toString();의 반환값, 원시 타입이면 값에 큰따옴표를 씌운 형태의 값을 반환한다.[20] 실제로 다른 경우도 존재한다. 예를 들어 a가 byte인 경우 a = a + 1;에서 a + 1이 int로 승격되어 그대로 a에 넣을 수 없게 된다. 반면 증감 연산자로 사용하는 경우 알아서 해결한다.[21] a가 1일 때, int x = a++;의 경우 변수 a를 사용 후 증가하기 때문에 x는 1, a는 2가 된다.[22] arr[i++]; 같이 간단하게 사용한다.[23] for문이 그 예시다.[24] 예를 들어 a && b에서 a가 false인 경우 b에 관계없이 false를 반환함이 확정된다.[25] 예를 들어 a != null && a.something()에서 a가 null이라면 a.something()을 호출하지 않아 NPE가 발생하지 않는다.[26] double을 int로 변환, char를 byte로 변환 등[27] List를 ArrayList로 변환 등[28] 왼쪽부터 결합이면 'X', 오른쪽부터 결합이면 'O'이다.[29] 컴파일 시점에서 값을 알 수 있는 식이다. 리터럴, Enum 상수가 대표적. final이 붙은 변수도 가능하지만 리터럴 값으로 초기화해야 하고, 지역변수 이거나, 정적 전역 변수이어야 한다. 이들간의 연산도 가능하다.[30] Enum 상수, 봉인 클래스의 경우 경우의 수가 유한하지만, int, String 같은 나머지는 무한하기에 필연적으로 default를 사용해야 한다.[31] 엄밀하게는 완전히 같지 않다. continue, break 등 분기가 있을 때 차이가 생긴다.[32] 이 경우, 인스턴스가 자동으로 만들어진다.[33] 변수가 객체를 가진 경우, 수정할 수 없는 건 변수가 가진 값인 주소이지, 객체가 아니다.[34] 이하 "유틸리티 클래스'로 설명[35] 이하 "데이터 클래스"로 설명[36] 일반적으로 클래스를 배운다고 하면 이 방법을 배운다.[37] 나중에 getter와 setter를 도입하면 그동안 필드가 사용된 코드를 모두 손봐야 한다.[38] 메서드 이름 규칙에 의해 변수는 대문자로 시작한다.[39] 단, 접근 제어자가 protected 이상이어야 한다. 만약 동일 패키지라면 default도 접근할 수 있다.[40] 동일 패키지라면 default 이상, 다른 패키지면 protected 이상.[41] 부모가 protected면 자식은 protected나 public으로 해야 한다.[42] 부모가 Person라면 자식은 Person이나 그 하위타입인 Student 등을 사용해야 한다.[43] 접근 제어자가 private이거나, 정적 메서드. final은 상속은 되므로 포함되지 않는다.[44] 정적 여부, 접근 제어자 상관 없이[45] 재정의가 아니기에 @Override를 쓸 수 없다.[46] 차이는 없으나 private 메서드의 경우 보통 숨김이라 하지 않는다.[47] 반대로 추상 메서드 없이 추상 클래스로 선언할 수 있다.[48] 처리하는 경우도 있다. 예시로 마인크래프트 자바 에디션은 OutOfMemoryError 발생 시 처음에는 메모리를 비워서 해결하지만, 이후에도 발생하면 메인화면으로 이동된다. 두 경우 모두 예외를 잡아 처리하는 경우다.[49] 이렇게 써야 하는 경우도 있다. 다른 스레드의 sleep을 중단시키면 InterruptedException으로 전달된다.[50] 이 경우 사용되는 객체가 보통은 체크 예외를 던져서 catch 블록이 필요하지만, try-catch 안에서 사용하거나 해당 메서드가 예외를 던지도록 하면 된다.[51] 설정한 예외와 그 예외를 상속하는 예외를 모두 잡는다. 예를 들어 Exception으로 설정하면 모든 예외를 잡는다.[52] Java 7부터는 AutoCloseable이 추가되어 try-with-resources가 대신 사용된다.[53] Closeable도 AutoCloseable의 하위 인터페이스이므로 사용 가능하다.[54] 원시 타입 사용 불가.[55] 때문에 클래스에서 쓸 수 없다.[56] 다중상속은 불가능하기 때문에 당연히 클래스는 최대 1개밖에 못쓴다.[57] 함수형 인터페이스[58] 굳이 쓰려면 항변환으로 타입을 결정시켜야 한다.[59] 실행 자체는 JVM이 하기 때문에 언제실행 되는지는 정확히 알 수 없다.