Post

자바 중급 개념 정리 1-1: 컬렉션 프레임워크와 제네릭

김영한의 실전 자바 중급 1편에서 학습한 내용을 정리한 글입니다.

Object 클래스

java.lang

java.lang 패키지는 모든 자바 애플리케이션에서 자동으로 import 된다.

Object 클래스

자바에서 모든 클래스의 최상위 부모 클래스는 항상 Object 클래스이다.

자바에서 Object 클래스가 최상위 부모 클래스인 이유는 공통 기능을 제공하고, 다형성의 기본 구현을 위해서이다.

공통 기능 제공

객체의 정보 제공(toString), 다른 객체와의 비교(equals), 객체의 클래스 확인(getClass) 등의 기능은 모든 객체에 필요한 기능이다. 따라서 이런 기능을 일관성있게 제공하기 위해 모든 클래스는 Object 클래스를 상속받게하여 공통 기능을 편리하게 사용할 수 있게 하였다.

다형성의 기본 구현

모든 자바 객체는 Object 타입으로 처리 될 수 있고 이는 다양한 타입의 객체를 통합적으로 처리할 수 있게 한다. 하지만 다른 객체의 메서드가 정의되어 있지 않아 메서드 오버라이딩을 활용할 수 없다. 즉, 각 객체의 기능을 사용하려면 다운 캐스팅이 필요하다.

toString 메소드

패키지를 포함한 객체의 이름과 객체의 참조값(해시코드)를 16진수로 반환한다. System.out.println 메서드는 내부에서 toString 메서드를 호출하기 때문에 Object 타입이 println에 인수로 전달되면 내부에서 obj.toString() 메서드를 호출하여 결과를 출력한다.

클래스 정보와 참조값만으로는 객체의 상태를 자세히 알지 못하기 때문에 보통 toString 메소드를 오버라이딩하여 사용한다. (IDE에서 toString 오버라이딩 자동완성 제공)

참고 : toString이나 hashCode를 재정의하면 객체의 참조 값을 출력할 수 없다. 이 경우 아래 코드를 사용하여 객체의 참조값을 출력한다

1
String refValue = Integer.toHexString(System.identityHashCode(dog1));

Object 와 OCP

만약 Object가 제공하는 toString이 없으면 구체적인 클래스에 의존해야하기 때문에 클래스의 수 마다 메소드의 수가 늘어날 것이다. 또한 Object의 toString을 사용하면 새로운 클래스를 추가할 때 확장에 용이 하고 (open) 기존 코드를 변경하지 않아도 된다(close)

Equals 동일성과 동등성

  • 동일성 : == 연산자를 사용해서 두 객체의 참조가 동일한 객체를 가리키고 있는지 확인
  • 동등성 : equals() 메서드를 사용하여 두 객체가 논리적으로 동등한지 확인
1
2
String s1 = "hello";
String s2 = "hello";

물리적인 객체의 참조값은 다르지만 논리적으로 같은 문자열을 가지고 있다. 즉 동일성은 다르지만 동등성은 같다.

(사실 같은 문자열은 자바가 같은 메모리를 바라보도록 하고 있다)

Object가 기본으로 제공하는 equals는 == 을 사용한다. 따라서 동등성 비교를 사용하고 싶으면 클래스에 맞게 equals 메서드를 재정의해야한다.

IDE가 정확한 equals 조건을 자동완성으로 제공해주고 있다.

  • 반사성 : 객체는 자기 자신과 동등해야한다
  • 대칭성 : 두 객체가 서로에 대해 동일하다면, 양방향으로 동일해야한다
  • 추이성 : A=B, B=C, A=C
  • 일관성 : 두 객체의 상태가 변경되지 않으면, equals 메소드는 항상 동일한 값을 반환한다.
  • null : 모든 객체는 null과 비교했을 때 false를 반환한다

불변 객체

기본형과 참조형의 공유

  • 기본형 : 하나의 값을 여러 변수에서 절대로 공유하지 않는다
  • 하나의 객체를 참조값을 통해 여러 변수에서 공유할 수 있다

공유 참조와 사이드 이펙트

여러 개 의 변수가 하나의 객체를 공유하는 것을 막은 방법은 없다. 공유 참조 사용으로 인한 사이드 이펙트를 발생시키지 않은 가장 쉬운 해결책은 서로 다른 객체를 생성해서 사용하는 것이다.

불변 객체 도입

공유 참조를 사용 할 때 사이드 이펙트가 발생하는 근본적인 원인은 객체의 공유보다 공유된 객체의 값을 변경하는 것에 있다. 사실 공유참조로 사용하는 것은 서로 다른 인스턴스를 사용하는 것보다 메모리와 성능상 더 효율적이다. 따라서 공유 참조에서 사이드 이펙트를 발생시키지 않기 위해 불변 객체 도입이 필요하다.

불변 객체

객체의 상태(객체 내부의 값, 필드, 멤버 변수)가 변하지 않는 객체를 불변 객체라 한다.

다음 과정으로 불변 객체를 만들 수 있다.

  • 멤버 변수에 final 키워드를 붙여준다.
  • 상태가 변경되지 않기 때문에 setter를 제거한다

이제 생성자를 통해서만 값을 설정할 수 있고 이후에 값을 변경하는 것은 불가능해진다.

불변 객체 값 변경

불변 객체에서 값이 필요하면 기존 값을 변경하지 않고 필요한 값으로 새로운 객체를 만들어 반환한다. 주의할 점은 반환 값을 꼭 받아서 사용해야한다.

String 클래스

문자열은 자주 사용되기 때문에 편의상 쌍따옴표로 문자열을 감싸면 자바에서 아래와 같이 변화해준다.

1
2
String str1 = "hello";
String str1 = new String("hello");

String 클래스 내부에서는 char[] 배열 멤버 필드를 가지고, 문자열 관련 메서드를 제공한다.

(자바 9부터는 char[] 대신 byte[]을 사용한다. 단순 영어 숫자는 1byte를 사용하고 나머지의 경우 2byte UTF-16 인코딩을 사용하여 메모리를 더 효율적으로 사용한다)

String 클래스와 참조형

String은 클래스이기 때문에 기본형이 아니라 참조형이다. 참조형 변수에는 참조값이 들어가 있기 때문에 원칙적으로 + 와 같은 연산은 사용할 수 없다. 하지만 문자열을 너무 자주 두라기 때문에 편의상 concat과 같은 동작을 하는 + 을 제공한다.

String 클래스 비교

String 클래스를 비교할 때는 == 비교가 아닌 equals 비교를 해야한다. String에서 equals는 오버라이딩이 되어 두 객체의 문자열을 비교 한다.

만약 new 생성자 키워드 없이 문자열로만 생성하면(리터럴) 자바는 메모리 효율성과 성능 최적화를 위해 문자열 풀을 사용한다. 문자열 풀에는 String 인스턴스를 미리 만들어 사용하는데, 문자열 풀은 같은 문자열이 있으면 기존에 있는 인스턴스를 사용한다. 즉 문자열 리터럴 두 객체가 동일한 문자열이라면 이때는 == 가 true가 된다.

(문자열 풀은 힙 영역을 사용하고, 문자열 풀에서 문자를 찾을 때는 해시 알고리즘을 사용하기 때문에 매우 빠르다)

문자열 비교를 할 때는 new 키워드로 생성된 String 객체일지, 문자열 리터럴로 생성된 String 객체일지 모르기 때문에 문자열 비교는 항상 equals로 비교해야한다

String 클래스 불변 객체

String은 불변 객체이기 때문에 생성 후 할당된 문자열을 바꿀 수 없다

StringBuilder - 가변 String

불변 String의 단점은 문자를 변경할 때 마다 계속 새로운 객체를 만들어야 한다. 즉 GC의 대상이 많아지고, 시스템 자원을 더 많이 사용하게 된다. 이럴 때 가변 String을 사용한다. 단, 사이드 이펙트에 주의를 하며 사용을 해야 한다.

StringBuilder 내부에는 fianl이 아닌 byte[]을 가지고 있다. 제공하는 메소드 사용 시 불변이 아니기 때문에 반환값을 받지 않아도 된다.

String 최적화

자바 컴파일러는 문자열 리터럴을 더하는 부분을 자동으로 합쳐주기 때문에 런타임에 별도의 문자열 결합 연산을 수행하지 않는다.

문자열 변수의 경우 변수안에 어떤 값이 있는지 컴파일 시점에 알 수 없기 때문에 스트링 빌더를 사용하여 최적화를 수행한다. 따라서 간단한 경우엔 더하기 연산으로 처리하는 것으로 충분하다.

하지만 반복문 안에서문자열을 더하는 경우엔 반복 횟수만큼 객체를 생성하기 때문에 최적화가 이루어지지 않는다. 이런 케이스는 String Builder를 사용하면 효과적이다.

또 아래 케이스에도 StringBuilder로 최적화를 하는 것이 좋다.

  • 반복조건문을 통해 동적으로 문자여을 조합 할 때
  • 복잡한 문자열의 특정 부분을 변경할 때
  • 매우 긴 대용량 문자열을 다룰 때

StringBuilder와 동일한 기능을 수행하는 StringBuffer가 있다. StringBuilder와 비교하였을 때 StringBuffer는 내부 동기화가 있어서 멀티 스레드 상황에 안전하지만 동기화 오버헤드로 느리다.

래퍼 클래스

기본형의 한계

자바에서 기본형은 객체가 아니다. 객체가 아니면 아래와 같은 한계가 있다.

  • 기본형은 객체가 아니므로 메스드를 제공할 수 없다.
  • null을 가질 수 없다. 데이터가 없는 경우에도 항상 어떠한 값으로 표현이 되어야 한다

이런 기본형의 한계를 보완하기 위해 기본형을 감싸서 만드는 클래스를 래퍼 클래스라고 부른다.

기본형을 래퍼 클래스로 변경하는 것은 마치 박스로 물건을 넣은 것 같다고 하여 박싱이라고 한다.

  • valueOf를 사용하면 자주 사용하는 범위의 미리 생성한 클래스를 사용한다(like 문자열 리터럴)

래퍼 클래스를 기본형으로 다시 꺼내는 것은 언박싱이라고 부른다.

오토 박싱

자바에서 개발자의 편의성을 위해 int → Intger 또는 Integer → int형으로 자동으로 변환해준다.

  • Integer.valueOf(10) 래퍼 객체 반환
  • Integer.valueOf(”10”) 래퍼 객체 변환
  • Integer.parseInt(”10”); 결과가 기본형
  • Integer.compareTo 내 값과 인자값 비교
  • Integer.sum(), Integer.min() 등 존재

성능

기본형은 메모리에서 단순히 그 크기만큼 공간을 차지하지만, 래퍼 클래스는 자바 객체 자체를 다루는데 필요한 메타 데이터를 포함하므로 더 많은 메모리를 사용한다. 연산 수행 성능도 약 3~5배 차이가 난다. 하지만 이런 부분을 최적화 해도 성능 향상은 미미하기 때문에 일반적인 경우라면 코드를 유지보수하기 좋은 편을 선택하여 사용하자.

서버 메모리 최적화보다 외부 호출을 최적화 하는 것이 훨씬 효율적이다.

TIP 일단 유지보수 하기 좋은 코드를 작성하고 부하 테스트를 진행하면서 병목 구간을 확인하여 최적화 하는 방향으로 개발

Class 클래스

클래스의 정보(메타 데이터)를 다루는 클래스이다.

  • 타입 정보 얻기 : 클래스의 이름, 슈퍼클래스 ,인터페이스, 접근 제한자 등 조회
  • 리플렉션 : 클래싀에 정의된 메소드, 필드, 생성자 등을 조회하고 이들을 통해 객체 인스턴스를 생성하거나 메소드를 호출 한다.
  • 동적 로딩과 생성 : Class.forName() 메서드를 사용하여 클래스를 동적 로드하고, newInstance() 메서드를 통해 새로운 인스턴스를 생성할 수 있다.
  • 어노테이션 처리 : 클래스에 적용된 어노테이션을 조회하고 처리하는 기능을 제공한다.

  • getDeclaredFields() : 클래스의 모든 필드 조회
  • getDeclaredMethods() : 클래스의 모든 메서드를 조회
  • getSuperclass() : 클래스의 부모 클래스를 조회
  • getInterfaces() : 클래스의 인터페이스들을 조회

System 클래스

  • 표준 입력, 출력, 오류 스트림 : System.in, System.out, System.err
  • 시간 측정 : System.currentTimeMillis(), System.nanoTime()
  • 환경 변수 : System.getenv()
  • 시스템 속성 : System.getProperteis()
  • 시스템 종료 : System.exit(int status)
    • 가급적 사용 X
  • 배열 고속 복사 : System.arraycopy
    • 시스템 레벨에서 최적화된 메모리 복사 연산 사용
    • 반복문 복사보다 2배 이상 빠름

Enum

String으로 상태나 카테고리를 표현하면 잘못된 문자열을 실수로 입력할 가능성이 있고, 컴파일 시 오류 감지가 불가하다. 이런 문제를 해결하려면 특정 범위로 값을 제한할 필요성이 있다.

타입 안전 열거형 패턴

클래스를 하나 생성하고, 상수마다 별도의 인스턴스를 생성하고 생성한 인스턴스를 대입한다. 각각을 상수로 선언하기 위해 static, final을 사용한다.

  • static 을 사용해서 상수를 메서드 영역에 선언한다
  • fianl을 사용해서 인스턴스를 변경할 수 없게 한다.

생성자는 prviate으로 선언하여 외부에서 생성자를 만들지 못하도록 해주어야 클래스 내 주어진 상수만 사용하도록 제한할 수 있다.

장점

  • 정해진 객체만 사용하도록 제한할 수 있기 때문에 잘못된 값을 입력하지 못하도록 차단한다
  • 정해진 객체만 사용하므로 데이터의 일관성이 보장된다

단점

이 패턴을 구현하려면 많은 코드 작성이 필요하고 private 생성자를 추가해야한다.

열거형 Enum Type

자바는 타입 안전 열거형 패턴을 편리하게 사용할 수 있도록 열거형을 제공한다

  • 열거형도 클래스이다.
  • 열거형은 자동으로 java.lang.Enum을 상속받는다
  • 외부에서 임의로 생성하지 못한다.

주요 메소드

  • values : 모든 ENUM 상수를 포함하는 배열을 반환한다
  • valueOf(String name) : 주어진 이름과 일치하는 ENUM 상수를 반환한다
  • name() : ENUM 상수의 이름을 문자열로 반화한다
  • ordinal() : ENUM 상수의 선언 순서(0부터 시작)을 반환한다
    • 이 값을 사용하다가 중간에 상수를 선언하는 위치가 변경되면 전체 상수의 위치가 모두 변경되기 때문에 가급적 사용하지 않는 것이 좋다.
  • toString() : ENUM 상수의 일므을 문자열로 반환한다. name 과 유사하지만 toString을 오버라이드 할 수 있다

리팩토링

생성 시 특정 등급에 따라 할인율을 가지도록 변경할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ClassGrade {

	public static final ClassGrade BASIC = new ClassGrade(10);
	public static final ClassGrade BASIC = new ClassGrade(20);
	public static final ClassGrade BASIC = new ClassGrade(30);
			
	private final int discountPercent;
			
	private classGrade(int discountPercent) {
		this.discountPercent = discountPercent;
	}
			
	public int getDisCountPercent() {
		return discountPercent;
	}
}

열거형도 클래스이기 때문에 위 리팩토링을 Grade에 동일하게 적용이 가능하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public enum Grade {
	BASIC(10), GOLD(20), DIAMON(30);
	
	private final int discountPercent;
	
	Grade(int discountPercent) {
		this.discountPercent = discountPercent;
	}
	
	public int getDiscountPercent() {
		return discountPercent;
	}

}

생성자에 private을 달아주지 않아도 된다.

객체지향 관점에서 Grade의 값을 외부로 노출시켜 계산하는 것보다 계산하여 스스로 관리하는 것이 캡슐화 원칙에 더 맞다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public enum Grade {
	BASIC(10), GOLD(20), DIAMON(30);
	
	private final int discountPercent;
	
	Grade(int discountPercent) {
		this.discountPercent = discountPercent;
	}
	
	public int getDiscountPercent() {
		return discountPercent;
	}
	
	public int discount(int price) {
		return price * discountPercent / 10;
	}

}
This post is licensed under CC BY 4.0 by the author.