[김영한의 실전 자바 - 중급 1편] 04. 래퍼, Class 클래스

Turtle·2024년 7월 6일
0
post-thumbnail

🙄래퍼 클래스 - 기본형의 한계1

public class MyIntegerMethodMain0 {
	public static void main(String[] args) {
		int value = 10;
		int i1 = compareTo(value, 5);
		int i2 = compareTo(value, 10);
		int i3 = compareTo(value, 15);

		System.out.println("i1 = " + i1);
		System.out.println("i2 = " + i2);
		System.out.println("i3 = " + i3);
	}

	private static int compareTo(int value, int target) {
		if (value < target) {
			return -1;
		} else if (value > target) {
			return 1;
		} else {
			return 0;
		}
	}
}
  • ✔️기본형의 한계 - 객체가 아님
    • ❗객체가 아님 : 기본형 데이터는 객체가 아니기 때문에 메서드를 제공할 수 없다.
public class MyInteger {
	private final int value;

	public MyInteger(int value) {
		this.value = value;
	}

	public int getValue() {
		return value;
	}

	// 자기 자신의 값과 외부 값을 비교
    // MyInteger 객체에서 메서드를 편리하게 호출
    // 참고로 int는 기본형이기 때문에 메서드를 가질 수 없다.
	public int compareTo(int target) {
		if (value > target) {
			return 1;
		} else if (value < target) {
			return -1;
		} else {
			return 0;
		}
	}

	@Override
	public String toString() {
		return "MyInteger{" +
				"value=" + value +
				'}';
	}
}

🙄래퍼 클래스 - 기본형의 한계2

public class MyIntegerNullMain0 {
	public static void main(String[] args) {
		int[] intArr = {-1, 0, 1, 2, 3};

		System.out.println(findValue(intArr, -1));
		System.out.println(findValue(intArr, 0));
		System.out.println(findValue(intArr, 1));
		System.out.println(findValue(intArr, 100));
	}

	private static int findValue(int[] intArr, int target) {
		for (int i : intArr) {
			if (i == target) {
				return i;
			}
		}
		return -1;
	}
}
  • ✔️기본형의 한계 - null값을 가질 수 없음
    • ❗null값을 가질 수 없음 : 기본형 데이터 타입은 null을 가질 수 없다. 때로는 데이터가 없음이라는 상태를 나타내야 할 필요가 있는데 기본형은 항상 값을 가지기 때문에 이런 표현을 할 수 없다.
    • 입력값이 -1일 때를 생각해보면, 배열에 -1값이 있어서 -1을 반환하는 것인지 아니면 -1값이 없어서 -1을 반환한 것인지 명확하지 않다.
public class MyIntegerNullMain1 {
	public static void main(String[] args) {
		MyInteger[] intArr = {new MyInteger(-1),
				new MyInteger(0),
				new MyInteger(1),
				new MyInteger(2),
				new MyInteger(3),
		};

		System.out.println(findValue(intArr, -1));
		System.out.println(findValue(intArr, 0));
		System.out.println(findValue(intArr, 1));
		System.out.println(findValue(intArr, 100));
	}

	private static MyInteger findValue(MyInteger[] intArr, int target) {
		for (MyInteger myInteger : intArr) {
			if (myInteger.getValue() == target) {
				return myInteger;
			}
		}
		return null;
	}
}

🙄래퍼 클래스 - 자바 래퍼 클래스

public class WrapperClassMain {
	public static void main(String[] args) {
		Integer i1 = new Integer(10);
		Integer i2 = Integer.valueOf(10);
		System.out.println("i1 = " + i1);
		System.out.println("i2 = " + i2);

		Long l1 = new Long(10L);
		Long l2 = Long.valueOf(10L);
		System.out.println("l1 = " + l1);
		System.out.println("l2 = " + l2);

		Double d1 = new Double(3.141592);
		Double d2 = Double.valueOf(3.141592);
		System.out.println("d1 = " + d1);
		System.out.println("d2 = " + d2);

		int i = i2.intValue();
		long l = l2.longValue();
		double d = d2.doubleValue();
		System.out.println("i = " + i);
		System.out.println("l = " + l);
		System.out.println("d = " + d);
	}
}
  • ✔️래퍼 클래스 생성 - 박싱(Boxing)
    • 기본형을 래퍼 클래스로 변경하는 것을 마치 박스에 물건을 넣은 것 같다고 해서 박싱(Boxing)이라고 한다.
    • Integer.valueOf()를 사용한다.
    • Integer.valueOf()에는 성능 최적화 기능이 있다. 개발자들이 일반적으로 자주 사용하는 -128 ~ 127 범위의 Integer 클래스를 미리 생성해준다. 해당 범위의 값을 조회하면 미리 생성된 Integer 객체를 반환한다. 해당 범위의 값이 없으면 new Integer()를 호출한다.
    • 문자열 풀과 비슷하게 자주 사용하는 숫자를 미리 생성해두고 재사용한다.
  • ✔️intValue()
    • 래퍼 클래스에 들어있는 기본형 값을 다시 꺼내는 메서드
    • 박스에 들어있는 물건을 꺼내는 것 같다고 해서 언박싱(UnBoxing)이라고 한다.
  • ✔️비교는 equals() 사용
    • 래퍼 클래스는 객체이기 때문에 == 비교를 하면 인스턴스의 참조값을 비교한다.
    • 래퍼 클래스는 내부의 값을 비교하도록 equals()를 재정의 해두었다. 따라서 값을 비교하려면 equals()를 사용해야 한다.
    • 또한 래퍼 클래스는 toString()을 재정의 해두었다.

🙄래퍼 클래스 - 오토 박싱

public class AutoBoxingMain {
	public static void main(String[] args) {
		// 기본형 → 참조형
		int value = 10;
		Integer refValue = Integer.valueOf(value);

		// 참조형 → 기본형
		int priValue = refValue.intValue();

		System.out.println("priValue = " + priValue);
		System.out.println("refValue = " + refValue);

		int a = refValue;		// 오토 언박싱
		Integer b = priValue;	// 오토 박싱

		System.out.println("a = " + a);
		System.out.println("b = " + b);
	}
}
  • ✔️오토 박싱과 오토 언박싱
    • 컴파일러가 개발자 대신 valueOf(), xxxValue()등의 코드를 추가해주는 기능이다.
    • 덕분에 기본형과 래퍼형을 서로 편리하게 변환할 수 있다.

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

  • ✔️유지보수와 최적화
    • 유지보수 vs 최적화를 고려해야 하는 상황이라면 유지보수하기 좋은 코드를 먼저 고민해야 한다.
    • 특히 최신 컴퓨터는 매우 빠르기 때문에 메모리 상에서 발생하는 연산을 몇 번 줄인다고해도 실질적인 도움이 되지 않는 경우가 많다.
    • 코드 변경 없이 성능 최적화를 하면 가장 좋겠지만, 성능 최적화는 대부분 단순함 보다는 복잡함을 요구하고, 더 많은 코드들을 추가로 만들어야 한다. 최적화를 위해 유지보수 해야 하는 코드가 더 늘어나는 것이다.
    • 그런데 진짜 문제는 최적화를 한다고 했지만 전체 애플리케이션의 성능 관점에서 보면 불필요한 최적화를 할 가능성이 있다.
    • 특히 웹 애플리케이션의 경우 메모리 안에서 발생하는 연산 하나보다 네트워크 호출 한 번이 많게는 수십만배 더 오래 걸린다.
    • 자바 메모리 내부에서 발생하는 연산을 수천번에서 한 번으로 줄이는 것 보다, 네트워크 호출 한 번을 더 줄이는 것이 더 효과적인 경우가 많다.
    • 권장하는 방법은 개발 이후에 성능 테스트를 해보고 정말 문제가 되는 부분을 찾아서 최적화 하는 것이다.

🙄Class 클래스

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

  • ✔️Class의 주요 기능
    • 타입 정보 얻기 : 클래스의 이름, 슈퍼 클래스, 인터페이스, 접근 제한자 등과 같은 정보를 조회할 수 있다.
    • 리플렉션 : 클래스에 정의된 메서드, 필드, 생성자 등을 조회하고 이들을 통해 객체 인스턴스를 생성하거나 메서드를 호출하는 등의 작업을 할 수 있다.
    • 동적 로딩과 생성 : Class.forName() 메서드를 사용하여 클래스를 동적으로 로드하고 newInstance() 메서드를 통해 새로운 인스턴스를 생성할 수 있다.
    • 어노테이션 처리 : 클래스에 적용된 어노테이션을 조회하고 처리하는 기능을 제공한다.
public class ClassMain {
	public static void main(String[] args) {
		Class clazz = String.class;

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

		// 모든 메서드 출력
		Method[] declaredMethods = clazz.getDeclaredMethods();
		for (Method declaredMethod : declaredMethods) {
			System.out.println("declaredMethod = " + declaredMethod);
		}

		// 상위 클래스 정보 출력
		Class superclass = clazz.getSuperclass();
		System.out.println("superclass = " + superclass);

		// 인터페이스 정보 출력
		Class[] interfaces = clazz.getInterfaces();
		for (Class i : interfaces) {
			System.out.println("i = " + i);
		}
	}
}
public class ClassCreateMain {
	public static void main(String[] args) throws Exception {
		Class helloClass = Hello.class;
		Hello hello = (Hello) helloClass.getDeclaredConstructor().newInstance();
		String result = hello.hello();
		System.out.println("result = " + result);
	}
}
  • ✔️리플렉션 - reflection
    • Class를 사용하면 클래스의 메타 정보를 기반으로 클래스에 정의된 메서드, 필드, 생성자 등을 조회하고 이들을 통해 객체 인스턴스를 생성하거나 메서드를 호출하는 작업을 할 수 있다.
    • 이런 작업을 리플렉션이라고 한다. 추가로 어노테이션 정보를 읽어서 특별한 기능을 수행할 수 있다. 최신 프레임워크들은 이런 기능을 적극 활용한다.

🙄System 클래스

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

public class SystemMain {
	public static void main(String[] args) {
		// 현재 시간(밀리초)
		long currentTimeMillis = System.currentTimeMillis();
		System.out.println("currentTimeMillis = " + currentTimeMillis);

		// 현재 시간(나노초)
		long currentNano = System.nanoTime();
		System.out.println("currentNano = " + currentNano);

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

		// 시스템 속성 → 22.0.1
		System.out.println("Java version = " + System.getProperty("java.version"));

		// 배열 고속 복사
		char[] originalArray = {'h', 'e', 'l', 'l', 'o'};
		char[] copiedArray = new char[5];

		System.arraycopy(originalArray, 0, copiedArray, 0, copiedArray.length);
		System.out.println("copiedArray = " + Arrays.toString(copiedArray));

		// 프로그램 종료
		System.exit(0);
	}
}
  • ✔️System 클래스
    • 표준 입출력, 오류 스트림 : System.in, System.out, System.err은 각각 표준 입출력, 오류 스트림을 나타낸다.
    • 시간 측정 : System.currentTimeMillis(), System.nanoTime()은 현재 시간을 밀리초, 나노초 단위로 제공한다.
    • 환경 변수 : System.getenv() 메서드를 사용하여 OS에서 설정한 환경 변수의 값을 얻을 수 있다.
    • 시스템 속성 : System.getProperty()로 특정 속성을 얻을 수 있다. 시스템 속성이란 자바에서 사용하는 설정 값이다.
    • 시스템 종료 : System.exit() 메서드는 프로그램을 종료하고 OS에 프로그램 종료 상태 코드를 전달한다. 이 때, 상태 코드 0은 정상 종료, 0이 아닌 값은 오류나 예외적인 종류를 말한다.
    • 배열 고속 복사 : System.arraycopy()는 시스템 레벨에서 최적화된 메모리 복사 연산을 사용한다. 직접 반복문을 사용해 배열을 복사할 때보다 수 배 이상 빠른 성능을 제공한다.

🙄Math, Random 클래스

  • ✔️Math 클래스
    • Math는 수많은 수학 문제를 해결해주는 클래스다. 자바 API 문서를 찾아보면 된다.
  • ✔️Random 클래스
    • 다양한 랜덤 값을 구성하기 위해 사용하는 클래스다.
    • 랜덤은 내부에서 씨드(Seed) 값을 사용해서 랜덤값을 구한다. 그런데 이 씨드 값이 같으면 항상 같은 결과가 출력된다.
    • new Random() : 생성자를 비워두면 내부에서 System.nanoTime() 에 여러가지 복잡한 알고리즘을 섞어서 씨드값을 생성한다. 따라서 반복 실행해도 결과가 항상 달라진다
    • new Random(int seed) : 생성자에 씨드 값을 직접 전달할 수 있다. 씨드 값이 같으면 여러 번 반복 실행해도 실행 결과가 같다. 이렇게 씨드 값을 직접 사용하면 결과 값이 항상 같기 때문에 결과가 달라지는 랜덤 값을 구할 수 없다. 하지만 결과가 고정되기 때문에 테스트 코드 같은 곳에서 같은 결과를 검증할 수 있다.

0개의 댓글