#!if top1 != null && 문서명1 == null
[DEPRECATED] top1 파라미터는 더 이상 사용되지 않습니다! 대신 문서명1 파라미터를 사용해 주세요.
#!if top1 == null && 문서명1 != null
[[Java|Java]]
프로그래밍 언어 문법 | |
{{{#!folding [ 펼치기 · 접기 ] {{{#!wiki style="margin: 0 -10px -5px; word-break: keep-all" | 프로그래밍 언어 문법 C(포인터 · 구조체 · size_t) · C++(클래스 · 이름공간 · 상수 표현식 · 특성) · C# · Java · Python(함수 · 모듈) · Kotlin · MATLAB · SQL · PHP · JavaScript(표준 내장 객체) · Haskell(모나드) · 숨 |
마크업 언어 문법 HTML · CSS | |
개념과 용어 함수(인라인 함수 · 고차 함수 · 콜백 함수 · 람다식) · 리터럴 · 문자열 · 식별자(예약어) · 상속 · 예외 · 조건문 · 반복문 · 비트 연산 · 참조에 의한 호출 · eval · 네임스페이스 · 호이스팅 | |
기타 #! · == · === · deprecated · GOTO · NaN · null · undefined · 배커스-나우르 표기법 | }}}}}} |
프로그래밍 언어 목록 · 분류 · 문법 · 예제 |
1. 개요
프로그래밍 언어 Java의 문법을 정리한 문서이다.==# 편집 지침 #==
소스 코드로 예시를 들 때 아래와 같이 문법을 활용하여 소스 코드를 써 주시기 바랍니다.
\#!syntax java (소스 코드) |
예시 코드는 아래와 같습니다.
#!syntax java
public class Main {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
2. 기본
기본적으로 자바의 코드의 구조는 다음과 같다.[package 이름 명시 (필수는 아님)]
[import할 패키지 명시]
[클래스 구현]
클래스 중 public 접근 제어자가 붙은 주요 클래스는 *.java 파일과 동일한 이름으로 정해야 하며, 2개 이상 선언할 수 없다.[1] 그 외의 클래스는 상관없다.
2.1. C와 차이점
기본적인 문법들은 C와 거의 동일하므로 C(프로그래밍 언어)/문법을 참고. 여기서는 차이점을 서술한다.- for-each: 배열과 Iterable을 구현한 객체[2]에 대해 각 데이터를 순환하며 반복한다. 아래 두 for문은 같은 동작을 한다.
#!syntax java
String[] strArray = { "hello", "world", "namuwiki" }
for (int i = 0; i < strArray.length; i++) {
System.out.println(strArray[i]);
}
for (String s : strArray) {
System.out.println(s);
}
- boolean 타입이 따로 있다. 따라서 if문에 int를 사용할 수 없다.
3. 메인 메서드
자바 프로그램을 실행하려면 메인 메서드가 필요하다. top-level에 클래스만 선언할 수 있는 자바 특성 상 메인 메서드도 어떤 클래스에 속에 있어야 하며, 그 클래스를 메인 클래스라 한다.#!syntax java
public class Main {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
자바로 콘솔에 뭔가를 띄우려면
System.out.println()
메서드를 사용한다.4. 제어자
클래스, 메서드, 변수를 선언할 때 특성을 지정하는 키워드로 12개의 제어자가 있다.4.1. 접근 제어자
생략 포함, 4개의 제어자가 있다. 생략은 default로 부른다.해당 요소를 어디까지 접근할 수 있는지를 지정한다.
- public: 공개. 어디에서나 접근 가능하다.
- protected: 보호. 동일 패키지에서만 접근 가능하지만, 상속관계라면 다른 패키지에서도 접근 가능하다. 클래스는 사용 불가.
- default: package-private, 패키지 비공개. 동일 패키지에서만 접근 가능하다.
- private: 비공개. 해당 클래스에서만 접근 가능하다. 클래스는 사용 불가.
4.2. 기타 제어자
사용 여부에 따라 특성이 지정된다.- static: 이 제어자가 붙은 요소는 정적 요소가 되어 인스턴스 없이 접근 가능하다. 자세한 차이는 클래스 문단에 후술. 클래스는 사용할 수 없지만 중첩 클래스는 사용할 있다.
- final: 수정 불가. 클래스는 상속 불가, 메서드는 재정의 불가, 변수는 상수가 된다.[3]
- abstract: 추상. 메서드에 사용 가능하며, 추상 메서드가 존재하는 클래스는 이 제어자를 붙여 추상 클래스로 선언해야 한다. 추상 메서드의 내부를 구현하지 않고, 자식 클래스가 재정의하여 사용하도록 한다.
- synchronized: 동기화. 메서드에 사용 가능하며, 여러 스레드가 동시에 접근할 수 없도록 한다.
- native: 메서드에서 사용 가능하며, 해당 메서드를 다른 언어(C, C++ 등)로 작성된 함수로 선언한다.
- strictfp: 클래스와 메서드에서 사용 가능하며, 부동소수점 연산이 항상 일정하게 된다. java 17에서는 기본값이 되어 제거되었다.
- transient: 변수에 사용 가능하며, 직렬화에서 제외된다.
- volatile: 변수에 사용 가능하며, 멀티 스레드 환경에서 변수의 가시성을 보장한다.
5. 메서드
자바에서 모든 함수는 클래스에 속해 있으므로 메서드라고 부른다.수학에서는 수를 넣으면 그 수에 무언갈 더하거나 빼는 등의 연산을 거친 결과를 밷는 것을 함수라고 한다.
자바에서 함수(메서드)는 코드상에서 메서드를 실행(호출)하면 내부의 코드들이 실행되는 코드 묶음이다. 여기서 수학처럼 호출할 때 데이터를 넘길 수 있고, 메서드가 종료될 때 데이터가 반환될 수 있다.
메서드 받은 데이터를 매개변수라고 하고, 매개변수로 넘기는 값을 인자라고 한다.
메서드를 선언하려면 다음과 같이 작성한다.
#!syntax java
// 반환 타입과 메서드이름을 쓰고 뒤에 소괄호를 붙인다.
void name() { // void는 반환 값이 없음을 의미한다.
// 메서드 내용
}
이름 앞에 제어자를 쓰거나, 매개변수를 지정할 수 있다.
#!syntax java
public void myMethod(int i) {
// 메서드 내용
}
메서드를 종료하고 값을 반환하려면 return키워드를 사용한다. void 메서드는 생략할 수 있으며, 다른 메서드는 반드시 반환해야 한다.
#!syntax java
// 두 정수를 받고 더한 결과를 반환하는 메서드
public int sum(int a, int b) {
return a + b;
}
받은 문자열이 비어있지 않을 때 출력하는 메서드
public void println(String text) {
if (text == null || text.equals("")) return; // return은 값 반환 뿐만 아니라 메서드 종료도 하므로 text가 비어있을 경우 아래코드가 실행되기 전에 메서드가 종료된다.
System.out.println(text);
// return; 이 생략되었다.
}
메서드 안에서 변수를 선언할 경우, final을 제외한 다른 제어자를 쓸 수 없고 함수가 종료되면 변수는 소멸한다.
#!syntax java
public double getPI() {
double pi = 3.14
return pi; // 실제로는 return 3.14; 만으로 쓸 수 있다.
}
6. 클래스
클래스는 용도에 따라 크게 2가지로 나뉠 수 있다.6.1. 유틸리티 클래스
정적 메서드 및 변수를 모아둔 클래스다. 예시로 java.lang.Math가 있다.이 때 클래스의 역할은 메서드와 변수를 담아두는 상자에 비유할 수 있다.
(클래스명).(메서드명)으로 메서드를 호출하고, (클래스명).(변수명)으로 변수를 가져올 수 있다.
예시로 도형의 넓이를 계산해주는 클래스는 다음과 같다.
#!syntax java
// Utils.java
public class Utils {
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)*Math.PI;
}
}
6.2. 데이터 클래스
대부분이 비정적 메서드 및 변수로 이루어져 객체를 만들고 활용하는 클래스다. 예시로 java.lang.String, java.util.ArrayList가 있다.String, ArrayList 클래스를 활용한 용어 정리[7]
- 인스턴스: String 타입의 변수/상수. "String 인스턴스가 필요하다."라고 하면 특정한 String 타입의 변수가 필요하다는 의미가.
- 객체: String 타입의 변수/상수들. 인스턴스를 통틀어서 부르는 말로 "String 객체가 필요하다."는 값에 상관없이 String 타입의 변수가 필요하다는 의미다.
- 생성자: 객체가 만들어 질 때 최초 실행되는 특수한 메서드. 보통 입력 받은 값이나 기본값으로 필드를 초기화 하는 등의 작업을 한다.
- 비정적 변수(필드): 객체 내부의 값. String에서는 문자열[8], ArrayList에서는 배열이다. 보통은 private로 선언하며, 그 이유는 아래 문단에서 후술.
- 비정적 메서드(메서드): 객체에 대하여 동작을 하는 함수. String에서는 필드인 문자열을 전부 대문자로 바꾸는 toUpperCase(), 문자열의 길이을 가져오는 length() 등이 있으며, ArrayList에서는 배열에 값을 추가하는 add(), 값이 포함되어 있는지 확인하는 contains() 등이 있다.
데이터 클래스를 쉽게 이해하자면 커스텀 데이터 타입이다. 예시로 "사람"이라는 데이터 타입을 만들면 다음과 같다.
#!syntax java
// Person.java
public class Person {
// 상술된 유틸리티 클래스처럼 정적 변수를 사용할 수 있다. 모든 객체가 공유하는 필드라고 생각하면 된다.
// 추가로 final 키워드를 붙여 읽기 전용, 즉 상수로 선언했다. 보통 상수는 대문자로 표기하며 띄어쓰기는 _로 표현한다.
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
public class Main {
public static void main(String[] args) {
// Person 타입의 myPerson 변수를 만들고, new Person("John")이 만드는 인스턴스를 값으로 저장한다.
Person myPerson = new Person("John"); // 새로운 객체를 생성하려면 new 키워드를 사용한다.
System.out.println(myPerson.getName()); // John
System.out.println(Person.getSpecies()); // Homo sapiens
}
}
이해를 위한 다른 비유도 있다. 클래스를 붕어빵 틀, 인스턴스를 만들어진 붕어빵, 필드를 반죽 및 속으로 비유하고, 객체는 붕어빵을 가리키는 용어로 설정하여 이해할 수 있다.
단순히 데이터 타입 목적 외에도 정말 많은 목적이 있다. 무언갈 관리하는 클래스, 다른 앱과 통신하는 클래스 등이 있다.
6.2.1. Getter와 Setter
private 필드를 가져오는 메서드를 Getter, 설정하는 메서드를 Setter라고 한다.- 왜 사용하는가?
캡슐화라던가 정보 은닉 같은 개념이 있지만 그래서 결론은, 개발자가 의도하지 않은 값이 들어가는 것을 방지하는 용도다.
예를 들어 자동차 클래스에서 속력을 저장하는 int 필드는 음수가 될 수 없을 것이다. 하지만 필드가 public이라면 음수가 대입될 수 있으므로 필드를 private으로 설정하고 Setter메서드에서 대입할 값이 음수인지를 확인하여 예외를 던지거나 0을 대입하는 방식을 사용할 수 있다.
Getter의 경우, 컬렉션 같은 가변 객체의 경우, 그대로 넘기면 add(), clear() 메서드 등으로 수정될 수 있으므로 객체를 복사해서 넘기거나 불변으로 넘기는 등으로 바꾸거나, 여러개로 나뉜 필드를 종합하여 값을 넘길 수 있다.
그 외에도 읽기 전용이나, 접근 범위를 다르게 하는 용도로 쓸 수 있다.
- 사용하지 않아도 되는 경우
개인이 사용할 프로그램의 경우, 주석으로 충분하기에 편리한 접근과 코드를 줄이기 위해 필드를 public으로 쓸 수 있다. 하지만, 나중을 위해 getter와 setter를 처음부터 사용하는 것을 추천한다.[9]
- 암묵적인 규칙
- Getter
반환 타입이 void가 아니고, 매개 변수가 없으며, 메서드 이름을 'get변수이름'[10]으로 설정한다. boolean을 반환하는 메서드는 'is변수이름'으로 쓸 수 있다. - Setter
반환 타입이 void고, 매개 변수를 받으며, 메서드 이름을 'set변수이름'으로 설정한다.
- 예시
#!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;
}
protected void setLicensePlate(String licensePlate) { // 아무나 번호판을 변경할 수 없다.
this.licensePlate = licensePlate;
}
}
6.3. 상속
상속을 하려면 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;
}
}
// Main.java
public class Main {
public static void main(String[] args) {
Student joe = new Student("Joe", 4.0);
// Student는 Person이기도 하므로 Person의 getName() 메서드를 사용할 수 있다.
System.out.println(joe.getName()); // Joe.
System.out.println(joe.getGPA()); // 4.0
}
}
자녀 클래스 내에서 상위 클래스의 메서드를 사용하고 싶다면 super 키워드를 사용하자. 참고로 이 예제에서 name을 사용하려고 하면 에러가 뜰 것이다. Person 클래스에서 name은 private 접근자를 가지는데, private 접근자를 가진 변수는 본 클래스 내에서만 직접 가져올 수 있다. Person 클래스에서 private String name;이 아니라 protected String name;을 쓰면 Student 클래스에서도 name을 자유롭게 사용할 수 있다.
6.4. Object
최상위 클래스인 Object 클래스는 모든 클래스의 부모로 모든 클래스는 Object 클래스를 상속받으며, 사용자가 직접 만든 클래스 또한 Object 클래스를 상속받는다.Object 클래스의 대표적인 메서드
- boolean equals(Object obj): 자기 자신과 매개변수를 비교한다. '두 객체가 같다.'는 이 메서드가 true를 반환한다는 뜻이다. 주소를 비교하는 참조 자료형의 특징 때문에 이 메서드를 재정의하여 사용자가 원하는 비교를 가지도록 할 수 있다.
- int hashCode(): 자기 자신의 해시 코드를 반환한다. 동일한 객체는 동일한 해시 코드를 반환하도록 재정의해야 한다.
- String toString(): 자기 자신을 문자열 형태로 반환한다. System.out.println();가 인자로 Object 객체를 받을 경우, 이 메서드로 얻은 문자열을 출력한다. 기본적으로 "클래스이름@해시코드"를 반환하므로 재정의하게 된다. 예시로 Person 클래스의 toString()은 "[species = Homo sapiens, name = (필드 name의 값)]"를 반환하도록 재정의할 수 있다.
7. 변수 및 자료형
7.1. 변수 선언
#!syntax java
int namu;
namu라는 변수를 선언한다.#!syntax java
namu = 3;
같이 namu의 값을 대입 수 있다.#!syntax java
int namu = 3;
선언과 동시에 대입할 수도 있다.#!syntax java
var namu = 3;
10 버전부터는 특정 자료형을 명시하지 않고 var로 대체할 수도 있다. 이 경우 컴파일러가 자동으로 자료형을 추론한다.#!syntax java
System.out.println(namu);
변수의 값을 참조할 수도 있다.#!syntax java
namu = namuwiki;
변수의 값을 이렇게 복사할 수도 있다.[11]다음은 Java에서 기본적으로 제공하는 자료형이다.
7.2. 원시 자료형
primitive type. 자바에선 8개의 원시 자료형(혹은 기본 자료형)이 있다.- 정수
- byte: -128~127
- short: -32768~32767
- int: -2147483648~2147483647
- long: -9223372036854775808~9223372036854775807
- 실수
- float: 약 -340간~340간 (절댓값이 1/14재 보다 작을 수 없다.)
- double: 약 -(1구골)³~(1구골)³ (절댓값이 약 1/(1구골)³ 보다 작을 수 없다.)[12]
- 논리
- boolean: true 또는 false
- 문자
- char: 0~65535
원시 자료형 이름은 소문자로 시작한다. 대문자로 시작한다면 원시 자료형이 아닌 참조 자료형이다. 대표적인 예가 문자열인 String.
원시 자료형은 null을 허용하지 않고, 실제 값을 저장한다.
다음은 두 정수의 합을 반환하는 메서드이다.
#!syntax java
public static int sum(int a, int b) {
return a + b;
}
public static void main(String[] args) {
int n1 = 10;
int n2 = 12;
System.out.println(sum(n1, n2)); // 22
}
7.2.1. 정수
4가지 타입이 있으며 기본인 int가 주로 사용된다.var 키워드를 이용한 타입추론 시 int가 우선으로 적용된다.
long 타입 정수는 뒤에 l 또는 L을 붙여야 한다.
#!syntax java
long namu = 1000000000000L;
7.2.2. 실수
2가지 타입이 있으며 기본은 double이다.float 타입 실수는 뒤에 f 또는 F를 붙여야 한다.
부동 소수점 방식으로 저장해서 정확한 연산이 불가능하다.[13]
절댓값이 최솟값이 있다.
7.2.3. 문자
char은 유니코드로 0 ~ 65535까지 저장이 가능하다. 예를 들어#!syntax java
char namu = 'A';
면 namu는 65가 된다.A의 유니코드가 65기 때문이다.
7.3. 참조 자료형
원시 자료형 아닌 자료형은 참조 자료형이라고 하는데, 쉽게 말하면 데이터 클래스다. 문자열 자료형인 String이 대표적이다.원시 자료형과는 다르게 비교 연산자의 작동 방식이 다른데, 값이 아닌 주소로 비교하는 방식을 사용한다.[14]
7.3.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[15] | 문자 |
Java 4 이하는 수동으로 박싱과 언박싱을 해야 한다.
#!syntax java
int a = 2;
Integer b = new Integer(a);
int c = b.intValue();
원시 자료형과 다르게 null을 쓸 수 있고, 제네릭의 타입 매개변수로 쓰일 수 있지만, 연산속도가 느리다.
7.4. 배열
같은 타입의 데이터를 나열하고, 인덱스(index)를 부여한 자료 구조.#!syntax java
int[] intArray;
String strArray[];
타입이나 변수에 '[]'를 추가하여 배열을 선언한다.#!syntax java
int[] intArray = new int[3];
String[] strArray = {"hello", "world", "example"};
배열을 초기화할 때는 배열의 크기만 지정하거나 실제 배열의 내용을 지정한다. 크기만 지정할 경우, 모든 인덱스는 기본값이 저장된다.[16]#!syntax java
System.out.println(strArray[0]); // 'hello'를 출력
System.out.println(strArray[1]); // 'world'를 출력
System.out.println(strArray[2]); // 'example'을 출력
변수 앞에 []를 붙이고 대괄호 안에 인덱스를 넣으면, 해당 인덱스에 맞는 값을 가져온다. 인덱스는 0부터 시작한다.#!syntax java
intArray[0] = 1;
intArray[1] = 3;
intArray[2] = -10;
같은 방법으로 값을 대입할 수 있다.#!syntax java
System.out.println(intArray[3]);
strArray[-1] = "text";
크기를 벗어나거나 음수 인덱스를 이용해 접근할 경우 예외를 발생시킨다.7.4.1. 컬렉션
위 이미지는 추상 클래스를 생략한 모습이다.
자바에서는 컬렉션을 통해 여러 개의 데이터를 저장할 수 있는 자료 구조를 제공한다. 배열과 다르게 크기가 바뀔 수 있다.
컬렉션은 특정 타입의 데이터만 저장하기 위해 제네릭을 지원한다.
Iterable은 for-each문에 사용할 수 있도록 하는 인터페이스다.
- Collection: 컬렉션의 최상위 타입. 추가, 제거, 포함 여부 등 공통적인 메서드를 가지고 있다.
- List: 순서를 유지하며 값의 중복 허용하는 컬렉션.
- ArrayList: 배열의 형태로 값을 저장하는 List. 배열의 특징상 임의 접근이 빠르지만, 중간에 값을 추가/제거하는 경우 그 뒤의 모든 인덱스를 바꿔야 해서 느리다.
- LinkedList: 각 값이 이전 값과 다음 값을 지정하는 방식. 중간에 값 삽입/제거가 빠르지만 임의 접근은 처음 값 또는 마지막 값에서 순차적으로 탐색해야 해서 느리다.
- Vector: 하위호환을 위해 남겨진 컬렉션으로 ArrayList와 동일하나 멀티 스레드에서 안전하도록 동기화되어 있다.
- Stack: 하위호환을 위해 남겨진 컬렉션으로 먼저 추가된 데이터가 가장 마지막에 나온다.
- Queue
- PriorityQueue
- Deque
- ArrayDeque
- LinkedList: List와 Queue를 모두 구현하므로 위의 것과 동일하다.
- Set: 순서를 유지하지 않고, 값의 중복을 허용하지 않는다. 즉, 집합.
- HashSet: 해시 기반으로 데이터를 저장하는 Set.
- LinkedHashSet: HashSet과 동일하나, 순서를 유지한다.
- SortedSet
- TreeSet: 순서를 정렬하여 저장하는 Set.
- Map: Iterable을 구현하지 않는다. 키-값이 쌍으로 저장되는 Set이라고 보면 된다.[17] 키는 중복될 수 없지만, 값은 중복될 수 있다.
- HashTable
- HashMap: 해시 기반으로 데이터를 저장하는 Map.
- LinkedHashMap: HashMap과 동일하나, 순서를 유지한다.
- SortedMap
- TreeMap: 순서를 정렬하여 저장하는 Map.
컬렉션 자료 구조에 대한 정렬이나 필터링에 필요한 함수는 Collections 클래스 (java.util.Collections)에서 제공하고 있다.
8. 제네릭
타입을 미리 정해두지 않고 사용할 때 정하는 방식. 사용할 때 참조 자료형[18]을 매개변수로 받듯이 사용돼서 타입 매개변수라고 부른다. 타입 매개변수는 <>안에 넣어야 하며, 여러 개의 타입 매개변수를 사용하려면 사이에 콤마를 넣어주면 된다.타입 매개변수의 이름은 암묵적인 규칙이 있다.
- T: Type. 가장 많이 쓰이며, 두번째는 U로 표현한다.
- E: Element. 컬렉션에서 주로 쓰인다.
- K: Key. 아래 V와 함께 주로 Map에서 쓰인다.
- V: Value.
- N: Number.
8.1. 바운딩
부등호 괄호[19] 안에 'extends' 또는 'super' 키워드를 사용하는 것. 이럼으로써 자료형에 약간 제약을 걸 수 있다. 예를 들어서 <T extends Person>는 Person 클래스 또는 Person을 상속하는 클래스를 뜻한다. 이때 상속은 직접적인 상속이 아니어도 된다. 예를 들어서 Person ← Student ← HighSchooler 이런 식으로 상속 관계가 있다면 HighSchooler도 T extends Person의 조건을 만족한다. 하지만 String 같은 자료형은 만족하지 못한다.'super'는 그와 반대다. <? super Student>라 하면 Student 클래스 또는 그의 부모/조상 클래스들을 의미한다. super는 오직 와일드카드만이 쓸 수 있다.
0개 이상의 클래스와 한 개 이상의 인터페이스를 동시에 구현·상속하는 자료형을 받을 때 구분자로 '&'를 쓸 수 있다. 이때는 클래스가 맨 앞으로 가야 한다.
예로 리스트에 담긴 숫자의(부동 소수점) 합을 구하는 메서드를 보자.
#!syntax java
// 일반 자료형
public static <T extends Number> double sum1(List<T> list) {
double sum = 0;
for (T num: list) {
sum += num.doubleValue();
}
return sum;
}
// 와일드카드
public static double sum2(List<? extends Number> list) {
double sum = 0;
for (Number num: list) {
sum += num.doubleValue();
}
return sum;
}
바운딩 없이 메소드를 작성하였다면 컴파일 오류가 발생한다. 바운딩을 안 하게 될 시 T는 Object 클래스를 상속하는 클래스로 취급되어 Object 클래스에 정의된 메소드밖에 호출을 하지 못하는데, doubleValue 메소드는 Number 래퍼 클래스를 상속하는 유도 클래스의 메소드이므로, Object 클래스의 인스턴스에서 doubleValue 메소드를 호출하는 꼴이 되어서 컴파일 오류가 발생하는 것이다.
8.2. 클래스
클래스 이름 뒤에 타입 매개변수를 쓰면 된다.#!syntax java
// Wrapper.java
public class Wrapper<T> {
private T content;
public Wrapper(T content) {
this.content = content;
}
public T getContent() {
return this.content;
}
}
// ... 메인 메서드 안
Wrapper<String> myWrapper1 = new Wrapper<>("Namu Wiki"); // new Wrapper뒤에 <>를 붙여야한다.
Wrapper<Integer> myWrapper2 = new Wrapper<Integer>(44); // java 6 까지는 타입까지 표기해야 한다.
System.out.println(myWrapper1.getContent()); // Namu Wiki
System.out.println(myWrapper2.getContent()); // 44
8.3. 메서드
일반적인 클래스에서도 제네릭을 이용한 메서드를 사용할 수 있다. 제네릭을 제한하는게 아니라면 Object의 존재 때문에 자주 사용되지 않으며, 이 제한 역시 하술된 와일드카드가 더 많이 쓰인다.#!syntax java
public static <T extends Exception> void throwRuntimeException(T e) {
throw RuntimeException(e);
}
public static void main(String[] args) {
throwRuntimeException(new IOException()); // IOException은 Exception의 자식 클래스이므로 인자로 사용 가능하다.
}
8.3.1. 와일드카드
아래의 두 메서드는 똑같은 일을 하는데, 전자는 위에서 소개했던 방식이고, 후자는 와일드카드를 사용한 방식이다.#!syntax java
public static <T> int getLength1(List<T> myList) {
return myList.size();
}
public static int getLength2(List<?> myList) {
return myList.size();
}
보다시피 와일드카드가 읽기 약간 더 편하다. 따라서 가능하면 와일드카드를 사용하는 게 좋다. 물론 그냥 일반 자료형을 사용해야 할 때도 많다.
9. 예외
예외란 프로그램의 오류라고 생각하면 된다. 보통 0으로 나누거나, 배열을 벗어난 인덱스에 접근하거나 할 때 발생한다.예외를 발생시키는 것을 예외를 던진다고 한다.
- Throwable: 예외의 최상위 클래스. 이 클래스를 상속하는 클래스는 던질 수 있고, catch 블록에서 받을 수 있다.
- Error: 치명적 오류. JVM에 문제가 발생하거나 메모리가 부족하는 등의 상황에서 발생한다. catch 블록에서 처리할 수 있지만 처리한다고 해결되는 수준이 아니므로 그냥 냅둔다.
- Exception: 사실상 예외의 최상위 클래스.
- RuntimeException: checked 예외와 unchecked 예외를 구분하는 클래스. 이 클래스와 그 하위 클래스는 unchecked 예외이고, 나머지는 checked 예외다. Error는 그 특징 때문에 unchecked 예외로 구분된다.
checked 예외는 catch 블록으로 처리하거나 throws 키워드로 넘겨야하며, unchecked 예외는 예외처리를 강제하지 않는다.
예외를 던지려면 throw 키워드를 사용한다.
#!syntax java
public void method() {
RuntimeException e = new RuntimeException();
throw e;
}
throws 키워드를 사용하여 강제 예외 처리를 상위 메서드로 넘길 수 있다.
#!syntax java
public void method() thorws Exception {
throw new Exception();
}
9.1. try
예외 처리를 위한 문법. try 단독으로 사용할 수 없고 catch나 finally 블록과 함께 사용해야 한다.- try: 이 블록의 코드에서 예외가 발생하면 다음 블록으로 넘어간다.
- catch: try에서 던져진 예외를 받는 블록. 받을 예외[20]를 정해야 하며, 대상이 아닌 예외는 다음 catch 블록으로 이동한다.
- finally: 이 문법에서 마지막에 무조건 실행되는 블록. 이 블록은 가장 밑에 있어야 한다. 해당 메서드가 반환되더라도 실행되며, 심지어 System.exit()[21]를 사용해도 실행된다. Closeable을 구현한 객체의 close() 메서드를 호출할 때 사용한다.[22]
try-finally: 예외처리 목적보다는 finally의 무조건 실행 특징을 활용하는 문법이다.
#!syntax java
try {
System.out.println("Hello, World!");
} finally {
System.out.println("이 문자열은 반드시 출력된다.");
}
try-catch: 가장 일반적인 용법. 여러개의 catch를 쓸 수 있다.
#!syntax java
String str = "1234a";
try {
int i = Integer.parseInt(str); // 문자열을 정수로 변환한다. 이 경우, a 때문에 NumberFormatException을 발생시킨다.
} catch(NumberFormatException _) {
System.err.println("잘못된 문자열"); // 에러 메시지를 출력한다.
} 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) {
System.out.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[23]을 구현한 객체를 다룰 때 사용되는 문법. 마지막에 자동으로 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) {
System.out.println(line);
}
} catch (Exception e) {
System.err.println("파일 읽기 실패: " + e.getMessage());
} // 이후 자동으로 reader.close(); 를 실행한다.
10. 인터페이스
인터페이스(Interface)는 일부 다른 언어에서는 프로토콜(Protocol)이라고도 한다. 상속과 다르면서도 미묘하게 비슷한 개념이다. 상속이 어느 클래스의 속성과 메서드를 '상속'한다면, 인터페이스는 어떤 클래스가 '구현'해야 한다.10.1. 인터페이스 정의하기
'interface' 키워드를 사용한다. 인터페이스를 만들 때는 주로 추상 메서드만을 사용한다. 추상 메서드란 이 인터페이스/클래스에서 정의하지 않고, 구현/상속한 클래스에서 정의하는 것을 말한다. 클래스에서는 추상 메서드를 만들려면 abstract 키워드를 붙여야 하지만, 인터페이스에서는 abstract가 디폴트다. Java 8부터는 비추상 메서드를 'default' 키워드를 사용해 정의할 수 있다.#!syntax java
// Incrementable.java
public interface Incrementable {
String interfaceName = "Incrementable";
public void increment();
public default void printMessage() {
System.out.println("디폴트 메서드");
}
}
인터페이스 내의 변수 (예제의 "interfaceName")는 디폴트로 static final이다. 어느 한 객체에 붙어있지도 않고 다른 변수를 대입할 수도 없다는 뜻이다.
10.2. 인터페이스 구현하기
'implements' 키워드를 사용한다.#!syntax java
public class Person implements Incrementable {
/* 생략 */
// Incrementable의 모든 추상 메서드를 정의
public void increment() {
age++;
}
}
이제 Person은 Incrementable 인터페이스를 구현했다. 이제 다음과 같은 메서드에 Person 객체를 보낼 수 있다.
#!syntax java
public static void example(Incrementable implementation) {
implementation.increment();
implementation.printMessage();
}
단 한 개의 클래스를 상속할 수 있는 것과는 달리 하나의 클래스가 여러 인터페이스를 구현할 수 있다. 구현과 상속을 동시에 할 수도 있다.
#!syntax java
public class SubClass extends SuperClass implements IntOne, IntTwo, IntThree {
// ...
}
10.3. 함수형 인터페이스
Java 8에 추가된 개념. 단 하나의 추상 메서드를 가지는 인터페이스를 말한다. 람다식을 지원하는 Java 8부터는 매우 특별한 취급을 받는다.[1] Main.java 파일에서는 주요 클래스의 이름이 Main이어야 한다.[2] 대표적으로 컬렉션[3] 객체의 경우, 객체의 필드는 수정할 수 있다.[4] 이하 "유틸리티 클래스'로 설명[5] 이하 "데이터 클래스"로 설명[6] 일반적으로 클래스를 배운다고 하면 이 방법을 배운다.[7] 다만, String은 조금 특수한 경우라서 자세히 따지면 다르니 주의한다.[8] 엄밀히 말하면 조금 다르다.[9] 나중에 getter와 setter를 도입하면 그동안 필드가 사용된 코드를 모두 손봐야 한다.[10] 메서드 이름 규칙에 의해 변수는 대문자로 시작한다.[11] 원시 자료형이 아닌 참조 자료형의 경우 객체의 참조가 복사된다. 원본 객체와 대입된 객체의 해시값을 출력해 보면 서로 같음을 알 수 있다. Java를 처음 학습하는 초보들이 흔하게 저지를 수 있는 실수이다.[12] 1구골은 10^100 이다.[13] 0.1 + 0.2 == 0.3이 false를 나타낸다.[14] 문자열 비교 시 A == B가 아닌 A.equals(B)를 사용하는 이유이기도 하다.[15] String 클래스와 다르다.[16] 참조 자료형은 null, boolean은 false, 나머지(int, char, long 등)는 0[17] 실제로 HashSet은 HashMap에서 값을 의미없는걸로 사용해서 작동한다.[18] 원시 자료형 사용 불가.[19] < >[20] 설정한 예외와 그 예외를 상속하는 예외를 모두 잡는다. 예를 들어 Exception으로 설정하면 모든 예외를 잡는다.[21] 프로그램을 종료시키는 메서드.[22] Java 7부터는 AutoCloseable이 추가되어 try-with-resources가 대신 사용된다.[23] Closeable도 AutoCloseable의 자식 클래스이므로 사용 가능하다.