'김영한의 실전 자바 - 중급 1편' 강의를 들으면서 복습할만한 내용을 정리하였다.

4. 래퍼, Class 클래스

4.1 래퍼 클래스 - 기본형의 한계

기본형의 한계

자바는 객체 지향 언어이다. 그런데 자바 안에 객체가 아닌 것이 있다. 바로 int, double 같은 기본형(Primitive Type)이다.

기본형은 객체가 아니기 때문에 다음과 같은 한계가 있다.

  • 객체가 아님 : 기본형 데이터는 객체가 아니기 때문에, 객체 지향 프로그래밍의 장점을 살릴 수 없다. 예를 들어 객체는 유용한 메서드를 제공할 수 있는데, 기본형은 객체가 아니므로 메서드를 제공할 수 없다.

    • 추가로 객체 참조가 필요한 컬렉션 프레임워크를 사용할 수 없다. 그리고 제너릭도 사용할 수 없다.
  • null 값을 가질 수 없음 : 기본형 데이터 타입은 null 값을 가질 수 없다. 때로는 데이터가 없음 이라는 상태를 나타내야 할 필요가 있는데, 기본형은 항상 값을 가지기 때문에 이런 표현을 할 수 없다.

기본형은 항상 값이 존재해야 한다. 숫자의 경우 0, 1 같은 값이라도 항상 존재해야 한다. 반면에 객체인 참조형은 값이 없다는 null 을 사용할 수 있다. 물론 null 값을 반환하는 경우 잘못하면 NullPointerException 이 발생할 수 있기 때문에 주의해서 사용해야 한다.

4.2 래퍼 클래스 - 자바 래퍼 클래스

래퍼 클래스는 기본형의 객체 버전이다.

자바는 기본형에 대응하는 래퍼 클래스를 기본으로 제공한다.

  • byte -> Byte
  • short -> Short
  • int -> Integer
  • long -> Long
  • float -> Float
  • double -> Double
  • char -> Character
  • boolean -> Boolean

자바가 제공하는 기본 래퍼 클래스의 특징

  • 불변이다.

  • equals 로 비교해야 한다.

래퍼 클래스 사용법

Integer newInteger = new Integer(10); //미래에 삭제 예정, 대신에 valueOf() 사용
Integer integerObj = Integer.valueOf(10); //-128 ~ 127 자주 사용하는 숫자 값 재사용, 불변
Long longObj = Long.valueOf(100);
Double doubleObj = Double.valueOf(10.5);

래퍼 클래스 생성 - 박싱(Boxing)

  • 기본형을 래퍼 클래스로 변경하는 것을 마치 박스에 물건을 넣은 것 같다고 해서 박싱(Boxing)이라 한다.

  • new Integer(10) 은 직접 사용하면 안된다. 작동은 하지만, 향후 자바에서 제거될 예정이다.

  • 대신에 Integer.valuOf(10) 를 사용하면 된다.

    • 내부에서 new Integer(10)을 사용해서 객체를 생성하고 돌려준다.
  • 추가로 Integer.valueOf() 에는 성능 최적화 기능이 있다. 개발자들이 일반적으로 자주 사용하는 -128 ~ 127 범위의 Integer 클래스를 미리 생성해준다. 해당 범위의 값을 조회하면 미리 생성된 Integer 객체를 반환한다. 해당 범위의 값이 없으면 new Integer() 를 호출한다.

    • 마치 문자열 풀과 비슷하게 자주 사용하는 숫자를 미리 생성해두고 재사용한다.

    • 참고로 이런 최적화 방식은 미래에 더 나은 방식으로 변경될 수 있다.

intVlaue() - 언박싱(Unboxing)

  • 래퍼 클래스에 들어있는 기본형 값을 따시 꺼내는 메서드이다.
  • 박스에 들어있는 물건을 꺼내는 것 같다고 해서 언박싱(Unboxing)이라 한다.

비교는 equals() 사용

  • 래퍼 클래스는 객체이기 때문에 == 비교를 하면 인스턴스의 참조값을 비교한다.
  • 래퍼 클래스는 내부의 값을 비교하도록 equals() 를 재정의 해두었다. 따라서 값을 비교하려면 equals() 를 사용해야 한다.

4.3 래퍼 클래스 - 오토 박싱

오토 박싱 - Autoboxing

개발자들이 오랜기간 개발을 하다 보니 기본형을 래퍼 클래스로 변환하거나 또는 래퍼 클래스를 기본형으로 변환하는 일이 자주 발생했다. 그래서 많은 개발자들이 불편함을 호소했다.

자바는 이런 문제를 해결하기 위해 자바 1.5부터 오토 박싱(Auto-boxing), 오토 언박싱(Auto-Unboxing)을 지원한다.

Integer boxedValue = value; //오토 박싱(Auto-boxing)
Integer boxedValue = Integer.valueOf(value); //컴파일 단계에서 추가
int unboxedValue = boxedValue; //오토 언박싱(Auto-Unboxing)
int unboxedValue = boxedValue.intValue(); //컴파일 단계에서 추가

4.4 래퍼 클래스 - 주요 메서드와 성능

Integer i1 = Integer.valueOf(10);//숫자, 래퍼 객체 반환
Integer i2 = Integer.valueOf("10");//문자열, 래퍼 객체 반환
int intValue = Integer.parseInt("10");//문자열 전용, 기본형 반환

//비교
int compareResult = i1.compareTo(20);
System.out.println("compareResult = " + compareResult);

//산술 연산
System.out.println("sum: " + Integer.sum(10, 20));
System.out.println("min: " + Integer.min(10, 20));
System.out.println("max: " + Integer.max(10, 20));
  • valueOf() : 래퍼 타입을 반환한다. 숫자, 문자열을 모두 지원한다.

  • parseInt() : 문자열을 기본형으로 변환한다.

  • compareTo() : 내 값과 인수로 넘어온 값을 비교한다. 내 값이 크면 1, 같으면 0, 작으면 -1을 반환한다.

  • Integer.sum(), Integer.min(), Integer.max() : static 메서드이다. 간단한 덧셈, 작은 값, 큰 값 연산을 수행한다.

parseInt() vs valueOf()

원하는 타입에 맞는 메서드를 사용하면 된다.

  • valueOf("10") 는 래퍼 타입을 반환한다.
  • parseInt("10") 는 기본형을 반환한다.

래퍼 클래스와 성능

그렇다면 더 좋은 래퍼 클래스만 제공하면 되지 기본형을 제공하는 이유는 무엇일까?

  • 기본형은 메모리에서 단순히 그 크기만큼의 공간을 차지한다.

  • 래퍼 클래스의 인스턴스는 내부에 필드로 가지고 있는 기본형의 값 뿐만 아니라 자바에서 객체 자체를 다루는데 필요한 객체 메타데이터를 포함하므로 더 많은 메모리를 사용한다.

기본형, 래퍼 클래스 어떤 것을 사용? - 유지 보수 vs 최적화

유지보수 vs 최적화를 고려해야 하는 상황이라면 유지보수하기 좋은 코드를 먼저 고민해야 한다. 특히 최신 컴퓨터는 매우 빠르기 때문에 메모리 상에서 발생하는 연산을 몇 번 줄인다고해도 실질적인 도움이 되지 않는 경우가 많다.

  • 코드 변경 없이 성능 최적화를 하면 가장 좋겠지만, 성능 최적화는 대부분 단순함 보다는 복잡함을 요구하고, 더 많은 코드들을 추가로 만들어야 한다. 최적화를 위해 유지보수 해야 하는 코드가 더 늘어나는 것이다. 그런데 진짜 문제는 최적화를 한다고 했지만 전체 애플리케이션의 성능 관점에서 보면 불필요한 최적화를 할 가능성이 있다.

  • 특히 웹 애플리케이션의 경우 메모리 안에서 발생하는 연산 하나보다 네트워크 호출 한 번이 많게는 수십만배 더 오래 걸린다. 자바 메모리 내부에서 발생하는 연산을 수천번에서 한 번으로 줄이는 것 보다, 네트워크 호출 한 번을 더 줄이는 것이 더 효과적인 경우가 많다.

  • 권장하는 방법은 개발 이후에 성능 테스트를 해보고 정말 문제가 되는 부분을 찾아서 최적화 하는 것이다.

4.5 Class 클래스

자바에서 Class 클래스는 클래스의 정보(메타데이터)를 다루는데 사용된다. Class 클래스를 통해 개발자는 실행중인 자바 어플리케이션 내에서 필요한 클래스의 속성과 메서드에 대한 정보를 조회하고 조작할 수 있다.

Class 클래스의 주요 기능

  • 타입 정보 얻기 : 클래스의 이름, 슈퍼클래스, 인터페이스, 접근 제한자 등과 같은 정보를 조회할 수 있다.

  • 리플렉션 : 클래스에 정의된 메서드, 필드, 생성자 등을 조회하고, 이들을 통해 객체 인스턴스를 생성하거나 메서드를 호출하는 등의 작업을 할 수 있다.

  • 동적 로딩과 생성: Class.forName() 메서드를 사용하여 클래스를 동적으로 로드하고, newInstance() 메서드를 통해 새로운 인스턴스를 생성할 수 있다.

  • 애노테이션 처리: 클래스에 적용된 애노테이션(annotation)을 조회하고 처리하는 기능을 제공한다.

String.class

//Class 조회
Class clazz = String.class; // 1.클래스에서 조회
//Class clazz = new String().getClass();// 2.인스턴스에서 조회
//Class clazz = Class.forName("java.lang.String"); // 3.문자열로 조회

// 모든 필드 출력
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
	System.out.println("Field: " + field.getType() + " " + field.getName());
}

// 모든 메서드 출력
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
	System.out.println("Method: " + method);
}
 
// 상위 클래스 정보 출력
System.out.println("Superclass: " + clazz.getSuperclass().getName());
 
// 인터페이스 정보 출력
Class[] interfaces = clazz.getInterfaces();
for (Class i : interfaces) {
	System.out.println("Interface: " + i.getName());
}

실행 결과

Field: class [B value
...
Method: public boolean java.lang.String.equals(java.lang.Object)
Method: public int java.lang.String.length()
...
Superclass: java.lang.Object
Interface: java.io.Serializable
Interface: java.lang.Comparable
...

Class 클래스의 주요 기능

  • getDeclaredFields() : 클래스의 모든 필드를 조회한다.
  • getDeclaredMethod() : 클래스의 모든 메서드를 조회한다.
  • getSuperClass() : 클래스의 부모 클래스를 조회한다.
  • getInterfaces() : 클래스의 인터페이스들을 조회한다.

리플렉션 - reflection

Class를 사용하면 클래스의 메타 정보를 기반으로 클래스에 정의된 메서드, 필드, 생성자 등을 조회하고, 이들을 통해 객체 인스턴스를 생성하거나 메서드를 호출하는 작업을 할 수 있다. 이러한 작업을 리플렉션이라 한다. 추가로 애노테이션 정보를 읽어서 특별한 기능을 수행할 수도 있다. 최신 프레임워크들은 이런 기능을 적극 활용한다.

4.6 System 클래스

System 클래스는 시스템과 관련된 기본 기능들을 제공한다.

// 현재 시간(밀리초)를 가져온다.
long currentTimeMillis = System.currentTimeMillis();
System.out.println("currentTimeMillis: " + currentTimeMillis);
 
// 현재 시간(나노초)를 가져온다.
long currentTimeNano = System.nanoTime();
System.out.println("currentTimeNano: " + currentTimeNano);

// 환경 변수를 읽는다.
System.out.println("getenv = " + System.getenv());

// 시스템 속성을 읽는다.
System.out.println("properties = " + System.getProperties());
System.out.println("Java version: " + 
System.getProperty("java.version"));
 
// 배열을 고속으로 복사한다.
char[] originalArray = new char[]{'h', 'e', 'l', 'l', 'o'};
char[] copiedArray = new char[5];
System.arraycopy(originalArray, 0, copiedArray, 0, originalArray.length);
 
// 배열 출력
System.out.println("copiedArray = " + copiedArray);
System.out.println("Arrays.toString = " + Arrays.toString(copiedArray));
 
//프로그램 종료
System.exit(0);

실행 결과

currentTimeMillis: 1703570732276
currentTimeNano: 286372106104583

getenv = {IDEA_INITIAL_DIRECTORY=/, COMMAND_MODE=unix2003, LC_CTYPE=ko_KR.UTF-8, 
SHELL=/bin/zsh, HOME=/Users/yh, PATH=/opt/homebrew/bin:/usr/local/bin: ...}

properties = {java.specification.version=21, java.version=21.0.1, sun.jnu.encoding=UTF-8, os.name=Mac OS X, file.encoding=UTF-8 ...}

Java version: 21.0.1

copiedArray = [C@77459877
Arrays.toString = [h, e, l, l, o]
  • 표준 입력, 출력, 오류 스트림 : System.in, System.out, System.err 은 각각 표준 입력, 표준 출력, 표준 오류 스트림을 나타낸다.

  • 시간 측정 : System.currentTimeMillis()System.nanoTime() 은 현재 시간을 밀리초 또는 나노초 단위로 제공한다.

  • 환경 변수 : System.getenv() 메서드를 사용하여 OS에서 설정한 환경 변수의 값을 얻을 수 있다.

  • 시스템 속성 : System.getProperties() 를 사용해 현재 시스템 속성을 얻거나 System.getProperty(String key) 로 특정 속성을 얻을 수 있다. 시스템 속성은 자바에서 사용하는 설정 값이다.

  • 시스템 종료 : System.exit(int status) 메서드는 프로그램을 종료하고, OS에 프로그램 종료의 상태 코드를 전달한다.

    • 상태 코드 0 : 정상 종료
    • 상태 코드 0 이 아님 : 오류나 예외적인 종료
  • 배열 고속 복사 : System.arraycopy 는 시스템 레벨에서 최적화된 메모리 복사 연산을 사용한다. 직접 반복문을 사용해서 배열을 복사할 때 보다 수 배 이상 빠른 성능을 제공한다.


profile
가오리의 개발 이야기

0개의 댓글