자바 표준 모듈에서 제공하는 라이브러리는 방대하기 때문에 쉽게 사용할 수 있도록 도와주는 API(Application Programming Interface) 도큐먼트가 있다. 라이브러리가 클래스와 인터페이스의 집합이라면, API 도큐먼트는 이를 사용하기 위한 방법을 기술한 것이다.
다음 URL을 방문하면 JDK 버전별로 사용할 수 있는 API 도큐먼트를 볼 수 있다.
https://docs.oracle.com/en/java/javase/index.html
java.base는 모든 모듈이 의존하는 기본 모듈로, 모듈 중 유일하게 requires하지 않아도 사용할 수 있다.
이 모듈에 포함되어 있는 패키지는 대부분의 자바 프로그램에서 많이 사용하는 것들이다.
다음은 java.base 모듈에 포함된 주요 패키지와 용도를 설명한 표이다.
우리가 지금까지 사용한 String, System, Integer, Exception 등의 클래스는 java.lang 패키지에 있고, 키보드 입력을 위해 사용한 Scanner는 java.util 패키지에 있다.
java.lang은 자바 언어의 기본적인 클래스를 담고 있는 패키지로, 이 패키지에 있는 클래스와 인터페이스는 import 없이 사용할 수 있다.
다음은 java.lang 패키지에 포함된 주요 클래스와 용도를 설명한 표이다.
클래스를 선언할 때 extends 키워드로 다른 클래스를 상속하지 않으면 암시적으로 java.lang.Object 클래스를 상속하게 된다. 따라서 자바의 모든 클래스는 Object의 자식이거나 자손 클래스이다.
그렇기 때문에 Object가 가진 메소드는 모든 객체에서 사용할 수 있다.
다음은 Object가 가진 주요 메소드를 설명한 표이다.
메소드 | 용도 |
---|---|
boolean equals(Object obj) | 객체의 주소를 비교하고 결과를 리턴 |
int hashCode() | 객체의 해시코드를 리턴 |
String toString() | 객체 문자 정보를 리턴 |
Object의 euqals() 메소드는 객체의 주소를 비교하고 boolean 값을 리턴한다.
public boolean equals(Object obj)
equals() 메소드의 매개변수 타입이 Object이므로 자동 타입 변환에 의해 모든 객체가 매개값으로 대입될 수 있다. equals() 메소드는 비교 연산자인 ==과 동일한 결과를 리턴한다.
두 객체가 동일한 객체라면 true를 리턴하고, 그렇지 않으면 false를 리턴한다.
일반적으로 Object의 equals() 메소드는 재정의해서 동등 비교용으로 사용된다.
동등 비교란 객체가 비록 달라도 내부의 데이터가 같은지를 비교하는 것을 말한다.
예를 들어 String은 equals() 메소드를 재정의해서 내부 문자열이 같은지를 비교한다.
객체 해시코드란 객체를 식별하는 정수를 말한다.
Object의 hashCode() 메소드는 객체의 메모리 주소를 이용해서 해시코드를 생성하기 때문에 객체마다 다른 정수값을 리턴한다.
hashCode() 메소드의 용도는 equals() 메소드와 비슷한데, 두 객체가 동등한지를 비교할 때 주로 사용한다.
public int hashCode()
equals() 메소드와 마찬가지로 hasCode() 메소드 역시 객체의 데이터를 기준으로 재정의해서 새로운 정수값을 리턴하도록 하는 것이 일반적이다.
객체가 다르다 할지라도 내부 데이터가 동일하다면 같은 정수값을 리턴하기 위해서이다.
자바는 두 객체가 동등함을 비교할 때 hashCode()와 equals() 메소드를 같이 사용하는 경우가 많다.
우선 hashCode()가 리턴하는 정수값이 같은지를 확인하고, 그 다음 equals() 메소드가 true를 리턴하는지를 확인해서 동등 객체임을 판단한다.
Object의 toString() 메소드는 객체의 문자 정보를 리턴한다. 객체의 문자 정보란 객체를 문자열로 표현한 값을 말한다.
기본적으로 Object의 toString() 메소드는 클래스명@16진수해시코드
로 구성된 문자열을 리턴한다.
Object obj = new Object();
System.out.println(obj.toString());
java.lang.Object@de6ced
객체의 문자 정보가 중요한 경우에는 Object의 toString() 메소드를 재정의해서 간결하고 유익한 정보를 리턴하도록 해야 한다.
데이터 전달을 위한 DTO(Data Transfer Object)를 작성할 때 반복적으로 사용되는 코드를 줄이기 위해 Java 14부터 레코드가 도입되었다.
class 키워드 대신에 record 키워드를 사용하고 클래스 이름 뒤에 괄호를 작성해서 저장할 데이터의 종류를 변수로 선언한다.
public record Person(String name, int age) {
}
이렇게 선언된 레코드 소스를 컴파일하면 변수의 타입과 이름을 이용해서 private final 필드가 자동 생성되고, 생성자 및 Getter 메소드가 자동으로 추가된다. 그리고 hashCode(), equals(), toString() 메소드를 재정의한 코드도 자동으로 추가된다.
자바 프로그램은 운영체제상에서 바로 실행되는 것이 아니라 자바 가상 머신(JVM) 위에서 실행된다. 따라서 운영체제의 모든 기능을 자바 코드로 직접 접근하기란 어렵다. 하지만 java.lang 패키지에 속하는 System 클래스를 이용하면 운영체제의 일부 기능을 이용할 수 있다.
System 클래스의 정적 필드와 메소드를 이용하면 프로그램 종료, 키보드 입력, 콘솔 출력, 현재 시간 읽기, 시스템 프로퍼티 읽기 등이 가능하다.
out 필드를 이용하면 콘솔에 원하는 문자열을 출력할 수 있다.
err 필드도 out 필드와 동일한데, 차이점은 콘솔 종류에 따라 에러 내용이 빨간색으로 출력된다는 것이다.
자바는 키보드로부터 입력된 키를 읽기 위해 System 클래스에서 in 필드를 제공한다.
다음과 같이 in 필드를 이용해서 read() 메소드를 호출하면 입력된 키의 코드값을 얻을 수 있다.
int keyCode = System.in.read();
키 코드는 각 키에 부여되어 있는 번호로, 다음과 같다.
read() 메소드는 호출과 동시에 키 코드를 읽는 것이 아니라, Enter 키를 누르기 전까지는 대기 상태이다가 Enter 키를 누르면 입력했던 키들을 하나씩 읽기 시작한다.
단, read() 메소드는 IOException을 발생시킬 수 있는 코드이므로 예외 처리가 필요하다.
운영체제는 실행 중인 프로그램을 프로세스로 관리한다. 자바 프로그램을 시작하면 JVM 프로세스가 생성되고, 이 프로세스가 main() 메소드를 호출한다. 프로세스를 강제 종료하고 싶다면 System.exit() 메소드를 사용한다.
exit() 메소드는 매개값이 필요한데, 이 값을 종료 상태값이라고 한다.
종료 상태값으로 어떤 값을 주더라도 프로세스는 종료되는데 정상 종료일 경우 0, 비 정상 종료는 1 또는 -1로 주는 것이 관례이다.
System 클래스의 currentTimeMillis() 메소드와 nanoTime() 메소드는 1970년 1월 1일 0시부터 시작해서 현재까지 진행된 시간을 리턴한다.
메소드 | 용도 |
---|---|
long currentTimeMilis() | 1/1000 초 단위로 진행된 시간을 리턴 |
long nanoTime() | 1/109 초 단위로 진행된 시간을 리턴 |
이 두 메소드는 프로그램 처리 시간을 측정하는 데 주로 사용된다.
프로그램 처리를 시작할 때 한 번, 끝날 때 한 번 읽어서 그 차이를 구하면 프로그램 처리 시간이 나온다.
시스템 프로퍼티(System Property)란 자바 프로그램이 시작될 때 자동 설정되는 시스템의 속성을 말한다.
예를 들어 운영체제 종류 및 사용자 정보, 자바 버전 등의 기본 사용 정보가 해당한다.
다음은 시스템 프로퍼티의 주요 속성 이름(Key)과 값(Value)에 대해 설명한 것이다.
속성 이름(Key) | 설명 |
---|---|
java.specification.version | 자바 스펙 버전 |
java.home | JDK 디렉토리 경로 |
os.name | 운영체제 |
user.name | 사용자 이름 |
user.home | 사용자 홈 디렉토리 경로 |
user.dir | 현재 디렉토리 경로 |
자바에서 문자열과 관련된 주요 클래스에 대해 알아보자
클래스 | 설명 |
---|---|
String | 문자열을 저장하고 조작할 때 사용 |
StringBuilder | 효율적인 문자열 조작 기능이 필요할 때 사용 |
StringTokenizer | 구분자로 연결된 문자열을 분리할 때 사용 |
String 클래스는 문자열을 저장하고 조작할 떄 사용한다.
문자열 리터럴은 자동으로 String 객체로 생성되지만, String 클래스의 다양한 생성자를 이용해서 직접 객체를 생성할 수도 있다.
String은 내부 문자열을 수정할 수 없다. 다음 코드를 보면 다른 문자열을 결합해서 내부 문자열을 변경하는 것처럼 보이지만, 사실 "ABCEDF"라는 새로운 String 객체를 생성하는 것이다. 그리고 data 객체는 새로 생성된 String 객체를 참조하게 된다.
String data = "ABC";
data += "DEF";
문자열의 + 연산은 새로운 String 객체가 생성되고 이전 객체는 계속 버려지는 것이기 때문에 효율이 좋지 않다.
따라서 잦은 문자열 변경 작업을 해야 한다면 StringBuilder를 사용하는 것이 좋다.
StringBuilder는 내부 버퍼에 문자열을 저장해두고 그 안에서 추가, 수정, 삭제 작업을 하도록 설계되어 있다.
따라서 String처럼 새로운 객체를 만들지 않고도 문자열을 조작할 수 있다.
StringBuilder가 제공하는 조작 메소드는 다음과 같다.
리턴 타입 | 메소드(매개변수) | 설명 |
---|---|---|
StringBuilder | append(기본값 | 문자열) |
StringBuilder | insert(위치, 기본값 | 문자열) |
StringBuilder | delete(시작 위치, 끝 위치) | 문자열 일부를 삭제 |
StringBuilder | replace(시작 위치, 끝 위치, 문자열) | 문자열 일부를 대체 |
String | toString() | 완성된 문자열을 리턴 |
문자열이 구분자(Delimter)로 연결되어 있을 경우, 구분자를 기준으로 문자열을 분리하려면 String의 split() 메소드를 이용하거나 java.util 패키지의 StringTokenizer 클래스를 이용할 수 있다.
split은 정규 표현식으로 구분하고, StringTokenizer는 문자로 구분한다는 차이점이 있다.
StringTokenizer가 제공하는 조작 메소드는 다음과 같다.
리턴 타입 | 메소드(매개변수) | 설명 |
---|---|---|
int | countTokens() | 분리할 수 있는 문자열의 총 수 |
boolean | hasMoreTokens() | 남아 있는 문자열이 있는지 여부 |
String | nextToken() | 문자열을 하나씩 가져옴 |
자바는 기본 타입(byte, short, int 등 총 8개의 타입)의 값을 갖는 객체의 생성할 수 있다. 이런 객체를 포장(Wrapper) 객체라고 한다. 값을 포장하고 있다고 해서 붙여진 이름이다.
포장 객체를 생성하기 위한 클래스는 java.lang 패키지에 포함되어 있는데, char 타입과 int 타입이 각각 Character와 Integer인 것만 제외하고는 기본 타입의 첫 문자를 대문자로 바꾼 이름을 가지고 있다.
e.g. byte -> Byte, float -> Float
포장 객체는 포장하고 있는 기본 타입의 값을 변경할 수 없고, 단지 객체로 생성하는 데 목적이 있다.
이런 객체가 필요한 이유는 컬렉션 객체 때문이다. 컬렉션 객체는 기본 타입의 값은 저장할 수 없고, 객체만 저장할 수 있다.
기본 타입의 값을 포장 객체로 만드는 과정을 박싱(Boxing)이라고 하고, 반대로 포장 객체에서 기본 타입의 값을 얻어내는 과정을 언박싱(Unboxing)이라고 한다.
Interger obj = 100; // 박싱
int value = obj; // 언박싱
언박싱은 다음과 같이 연산 과정에서도 발생한다. obj는 50과 연산되기 전에 언박싱된다.
int value = obj + 50; 언박싱 후 연산
대부분의 포장 클래스에는 parse + 기본타입 명으로 되어 있는 정적 메소드가 있다.
이 메소드는 문자열을 해당 기본 타입 값으로 변환한다.
e.g. Integer.parseInt()
포장 객체는 내부의 값을 비교하기 위해 ==와 != 연산자가 아닌 equals() 메소드를 사용한다.
포장 객체의 주소가 아닌 내부의 값을 비교해야 하기 때문이다.
Math 클래스는 수학 계산에 사용할 수 있는 메소드를 제공한다. Math 클래스가 제공하는 메소드는 모두 정적(Static) 이므로 Math 클래스로 객체 생성 없이 바로 사용 가능하다.
다음은 주요 메소드이다.
자바는 컴퓨터의 날짜 및 시각을 읽을 수 있도록 java.util 패키지에서 Date와 Calendar 클래스를 제공하고 있다. 또한 날짜와 시간을 조작할 수 있도록 java.time 패키지에서 LocalDateTime 등의 클래스를 제공한다.
클래스 | 설명 |
---|---|
Date | 날짜 정보를 전달하기 위해 사용 |
Calendar | 다양한 시간대별로 날짜와 시간을 얻을 때 사용 |
LocalDateTime | 날짜와 시간을 조작할 때 사용 |
Foramt(형식) 클래스는 숫자 또는 날짜를 원하는 형태의 문자열로 변환해주는 기능을 제공한다.
Format 클래스는 java.text 패키지에 포함되어 있는데, 주요 Format 클래스는 다음과 같다.
Format 클래스 | 설명 |
---|---|
DecimalFormat | 숫자를 형식화된 문자열로 변환 |
SimpleDateFormat | 날짜를 형식화된 문자열로 변환 |
문자열리 정해져 있는 형식으로 구성되어 있는지 검증해야 하는 경우가 있다. 예를 들어 이메일이나 전화번호를 사용자가 제대로 입력했는지 검증할 때이다. 자바는 정규 표현식(Regular Expression)을 이용해서 문자열이 올바르게 구성되어 있는지 검증한다.
java.util.regex 패키지의 Pattern 클래스는 정규 표현식으로 문자열을 검증하는 matches() 메소드를 제공한다.
첫 번째 매개값은 정규 표현식이고, 두 번째 매개값은 검증할 문자열이다.
검증한 후의 결과는 boolean 타입으로 리턴된다.
boolean result = Pattern.matches("정규식", "검증할 문자열");
자바는 클래스와 인터페이스의 메타 정보를 Class 객체로 관리한다. 여기서 메타 정보란 패키지 정보, 타입 정도, 멤버 정보 등을 말한다. 이러한 메타 정보를 프로그램에서 읽고 수정하는 행위를 리플렉션(Reflection)이라고 한다.
프로그램에서 Class 객체를 얻으려면 다음 3가지 방법 중 하나를 이용하면 된다.
코드에서 @으로 작성되는 요소를 어노테이션(Annotation)이라고 한다. 어노테이션은 클래스 또는 인터페이스를 컴파일하거나 실행할 때 어떻게 처리해야 할 것인지를 알려주는 설정 정보이다.
어노테이션은 다음 세 가지 용도로 사용된다.
1. 컴파일 시 사용하는 정보 전달
2. 빌드 툴이 코드를 자동으로 생성할 때 사용하는 정보 전달
3. 실행 시 특정 기능을 처리할 때 사용하는 정보 전달
컴파일 시 사용하는 정보 전달의 대표적인 예는 @Override
어노테이션이다.
@Override
는 컴파일러가 메소드 재정의 검사를 하도록 설정한다. 정확히 재정의되지 않았다면 컴파일러는 에러를 발생시킨다.
어노테이션은 자바 프로그램을 개발할 때 필수 요소가 되었다. 웹 개발에 많이 사용되는 Spring Framework 또는 Spring Boot는 다양한 종류의 어노테이션을 사용해서 웹 애플리케이션을 설정한다.
따라서 자바 개발자라면 어노테이션의 사용 방법을 반드시 알아야 한다.
어노테이션도 하나의 타입이므로 어노테이션을 사용하기 위해서는 먼저 정의부터 해야 한다.
어노테이션을 정의하는 방법은 인터페이스를 정의하는 것과 유사하다.
다음과 같이 @interface 뒤에 사용할 어노테이션 이름이 온다.
public @interface AnnotationName {
}
이렇게 정의한 어노테이션은 코드에서 다음과 같이 사용된다
@AnnotationName
어노테이션은 속성을 가질 수 있다. 속성은 타입과 이름으로 구성되며, 이름 뒤에 괄호를 붙인다.
속성의 기본값은 default 키워드로 지정할 수 있다.
public @interface AnnotationName {
String prop1();
int prop2() default 1;
}
이렇게 정의한 어노테이션은 코드에서 다음과 같이 사용할 수 있다.
@AnnotationName(prop1 = "값", prop2 = 3);
@AnnotationName(prop1 = "값"); // default 값이 있는 prop2는 생략 가능
어노테이션은 기본 속성인 value를 다음과 같이 가질 수 있다.
public @interface AnnotationName {
String value();
int prop2() default 1;
}
value 속성을 가진 어노테이션을 코드에서 사용할 때에는 다음과 같이 값만 기술할 수 있다.
이 값은 value 속성에 자동으로 대입된다.
@AnnotationName("값");
하지만 value 속성과 다른 속성의 값을 동시에 주고 싶다면 value 속성 이름은 반드시 언급해야 한다.
@AnnotationName(value = "값", prop2 = 3);