본문 바로가기
Java/이것이 자바다

기본 API 클래스 (1)

by k-mozzi 2023. 4. 13.
반응형
Preface

 

이번 장은 기본 API 클래스에 관련된 부분인데, 내용이 너무 방대해 2, 3번에 나누어 글을 업로드할 생각이다.

 

대부분 특정 클래스에 포함된 메소드의 사용 방법에 관한 내용이라 특별히 어려운 부분은 없었지만, Class 클래스에서 동적인 객체를 생성하는 부분의 개념이 조금 헷갈렸다.

 

아직 복잡한 프로그램을 다뤄본 경험이 없어서 그런지, 굳이 동적으로 객체를 생성해야 할 필요성을 잘 모르겠다.

 

gpt에게 몇가지 예시를 물어봐야겠다.

1. 런타임에 클래스 정보를 동적으로 가져와야 하는 경우: 클래스 이름이 런타임에 동적으로 결정되는 경우 Class.forName() 메소드를 사용하여 클래스 정보를 가져와 newInstance() 메소드를 사용하여 객체를 생성할 수 있습니다.

2. 플러그인 시스템에서 사용되는 경우: 동적으로 로드되는 플러그인은 컴파일 시간에 정적으로 객체를 생성할 수 없습니다. 이러한 경우, Class.forName() 메소드를 사용하여 플러그인 클래스를 동적으로 로드하고, newInstance() 메소드를 사용하여 플러그인 객체를 생성할 수 있습니다.

3. 객체를 생성할 때 생성자 매개 변수를 동적으로 결정해야 하는 경우: 생성자 매개 변수가 런타임에 결정되는 경우, newInstance() 메소드를 사용하여 동적으로 객체를 생성할 수 있습니다.

4. 제네릭 타입으로 작성된 클래스의 객체를 생성해야 하는 경우: 제네릭 타입의 객체를 생성하려면 클래스 정보를 사용해야합니다. 이 경우, Class.forName() 메소드를 사용하여 클래스 정보를 가져와 newInstance() 메소드를 사용하여 객체를 생성할 수 있습니다.

5. 리플렉션(Reflection)을 사용하는 경우: 리플렉션은 런타임에 클래스 및 메소드 정보를 동적으로 가져오는 기술입니다. 이를 통해 클래스의 인스턴스를 동적으로 생성하고 메소드를 호출할 수 있습니다.

 

1. java.lang과 java.util 패키지

 

 

- java.lang 패키지: 자바 프로그램의 기본적인 클래스를 담고 있는 패키지

→ 패키지 안에 있는 클래스와 인터페이스는 import 없이 사용할 수 있다.

 

 

- java.lang 패키지의 주요 클래스와 용도

클래스 용도
Object - 자바 클래스의 최상위 클래스로 사용
System - 표준 입력 장치로부터 데이터를 입력 받을 때 사용
- 표준 출력 장치로 출력하기 위해 사용
- JVM을 종료시킬 때 사용
- 쓰레기 수집기를 실행 요청할 때 사용
Class - 클래스를 메모리로 로딩할 때 사용
String - 문자열을 저장하고 여러 정보를 얻을 때 사용
StringBuffer, StringBuilder - 문자열을 저장하고 내부 문자열을 조작할 때 사용
Math - 수학 함수를 이용할 때 사용
Wrapper - 기본 타입의 데이터를 갖는 객체를 만들 때 사용
- 문자열을 기본 타입으로 변환할 때 사용
- 입력값 검사에 사용

 

 

- java.util 패키지: 자바 프로그램 개발을 도울 수 있는 클래스를 담고 있는 패키지

 

 

- java.util 패키지의 주요 클래스와 용도

클래스 용도
Arrays 배열을 조작(비교, 복사, 정렬, 찾기)할 때 사용
Calendar OS의 날짜와 시간을 얻을 때 사용
Date 날짜와 시간 정보를 저장하는 클래스
Objects 객체 비교, 널 여부 등을 조사할 때 사용
StringTokenizer 특정 문자로 구분된 문자열을 뽑아낼 때 사용
Random 난수를 얻을 때 사용

 


 

2. Object 클래스

 

 

- 자바의 모든 클래스는 Object 클래스의 자식이거나 자손 클래스이다.

→ Object는 자바의 최상위 부모 클래스이다.

 

 

- equals( ) 메소드: 두 객체가 동일한 객체라면 true를, 그렇지 않으면 false를 리턴한다.

1) Object를 매개 타입으로 가지므로 모든 객체가 매개값으로 대입될 수 있다.

2) 두 객체를 논리적으로 비교하는데, 여기서 논리적으로 동등하다는 것은 객체의 동일성에 상관 없이 객체가 저장하고 있는 데이터가 동일함을 의미한다.

3) 직접 사용되지 않고 하위 클래스에서 재정의(@Override)하여 논리적으로 동등 비교할 때 이용된다.

4) 오버라이딩할 때에는 매개값(비교 객체)이 기준 객체와 동일한 타입의 객체인지 먼저 확인해야 한다.

package ch11;

class Member {
	public String id;

	public Member(String id) {
		this.id = id;
	}

	@Override
	public boolean equals(Object obj) {
		if (obj instanceof Member) {
			Member member = (Member) obj;
			if (id.equals(member.id)) {
				return true;
			}
		}
		return false;
	}

}

public class MemberExample {

	public static void main(String[] args) {

		Member obj1 = new Member("blue");
		Member obj2 = new Member("blue");
		Member obj3 = new Member("red");

		if (obj1.equals(obj2)) {
			System.out.println("obj1 is same as obj2");
		} else {
			System.out.println("obj1 is not same as obj2");
		}

		if (obj1.equals(obj3)) {
			System.out.println("obj1 is same as obj3");
		} else {
			System.out.println("obj1 is not same as obj3");
		}
	}

}

 

 

- hashCode( ) 메소드: 객체의 메모리 번지를 이용해서 해시코드를 만들어 리턴한다.

1) 객체 해시코드: 객체를 식별할 하나의 정수값

2) 논리적 동등비교 시 해시코드 값이 다르면 다른 객체로 판단하고, 같으면 equals( ) 메소드로 다시 비교해야하므로, 메소드의 값이 true가 나와도 equals( )의 리턴값이 다르면 다른 객체가 된다.

→ equals( ) 메소드 뿐만이 아니라 hashCode( ) 메소드도 재정의해서 논리적 동등 객체일 경우 동일한 해시코드가 리턴되도록 해야 한다.

 

 

- toString( ) 메소드: 객체의 문자 정보를 리턴한다.

1) 객체의 문자 정보: 객체를 문자열로 표현한 값

2) 기존의 리턴값 자체는 별 의미가 없는 정보이므로 Object 하위 클래스는 toString( ) 메소드를 재정의하여 필요한 정보를 리턴한다.

→ Date 클래스: 현재 시스템의 날짜와 시간 정보 리턴 / String 클래스: 저장하고 있는 문자열 리턴

package ch11;

class SmartPhone {
	public String company;
	public String os;

	public SmartPhone(String company, String os) {
		this.company = company;
		this.os = os;
	}

	@Override
	public String toString() {
		return company + ", " + os;
	}
}

public class SmartPhoneExample {

	public static void main(String[] args) {

		SmartPhone myPhone = new SmartPhone("google", "android");

		String strObj = myPhone.toString();
		System.out.println(strObj);
		System.out.println(myPhone);
	}

}

 

 

- 객체 복제: 원본 객체의 필드값과 동일한 값을 가지는 새로운 객체를 셍성하는 것

→ 원본 객체를 안전하게 보호하기 위함이다.

 

 

- 얕은 복제(thin clone): 단순히 필드값을 복사해서 객체를 복제하는 것

1) 필드의 경우 복제된 객체에서 값을 수정해도 원본 객체의 값은 변하지 않는다.

2) 참조 타입 필드는 번지만 복제되므로 원본 객체의 필드와 복제 객체의 필드는 같은 객체를 참조하게 된다.

→ 복제 객체에서 참조 객체를 변경하면 원본 객체도 변경된 객체를 가지게 된다.

 

 

- clone( ) 메소드: 자신과 동일한 필드값을 지닌 얕은 복제된 객체를 리턴한다.

1) 원본 객체는 반드시 java.lang.Cloneable 인터페이스를 구현하고 있어야 한다.

→ 직접 구현하는 것이 아닌 Cloneable을 implements 하는 것이다.

2) Cloneable 인터페이스를 구현하지 않으면 clone( ) 메소드를 호출할 때 CloneNotSupportedException 예외가 발생하여 복제가 실패된다.

→ try-catch 구문으로 예외처리를 해야 한다.

package ch11;

public class ThinClone implements Cloneable {

	public String id;
	public String name;
	public String age;

	public ThinClone(String id, String name, String age) {
		this.id = id;
		this.name = name;
		this.age = age;
	}

	public ThinClone getMember() {
		ThinClone cloned = null;

		try {
			cloned = (ThinClone) clone();	// clone() 메소드의 리턴 타입은 Object이므로 ThinClone 타입으로 캐스팅한 것
		} catch (CloneNotSupportedException e) {

		}
		return cloned;
	}
}

 

 

- 깊은 복제(deep clone): 참조하고 있는 객체도 복제하는 것

→ Object의 clone( ) 메소드를 재정의해서 참조 객체를 복제하는 코드를 직접 작성해야 한다.

package ch11;

import java.util.Arrays;

class Car {
	public String model;

	public Car(String model) {
		this.model = model;
	}
}

public class DeepClone {

	public String name;
	public int age;
	public int[] scores;
	public Car car;

	public DeepClone(String name, int age, int[] scores, Car car) {
		this.name = name;
		this.age = age;
		this.scores = scores;
		this.car = car;
	}

	@Override
	protected Object clone() throws CloneNotSupportedException {
		DeepClone cloned = (DeepClone) super.clone(); // Object의 clone() 호출
		cloned.scores = Arrays.copyOf(this.scores, this.scores.length);
		cloned.car = new Car(this.car.model);
		return cloned;
	}

	public DeepClone getMember() {
		DeepClone cloned = null;
		try {
			cloned = (DeepClone) clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return cloned;
	}

}

 

 

- 참조하지 않는 배열이나 객체는 쓰레기 수집기(Garbage Collector)가 힙 영역에서 자동적으로 소멸시킨다.

→ gc는 객체를 소멸하기 직전에 객체의 소멸자(finalize( ))를 실행시킨다.

 

 

- finalize( ) 메소드: 객체를 소멸하는 메소드

1) 기본적으로 실행 내용이 없다.

2) 원하는 객체를 소멸시키거나 중요한 데이터를 저장하고 싶을 땐 메소드를 재정의해서 사용할 수 있다.

→ 재정의할 땐 throws Throwable로 예외를 던져야 한다.

3) gc는 쓰레기가 생겼을 때 바로 실행되는 것이 아니고 cpu가 한가하거나 메모리 용량이 부족할 때 자체적으로 실행되는 것이다.

→ finalize( ) 메소드가 호출되는 시점은 명확하지 않다.

4) System.gc: JVM에게 gc를 가능한 한 빨리 실행해달라고 요청하는 명령어

5) 소멸자는 객체를 무작위 순서대로 소멸시킨다.

6) 소멸자는 객체를 전부 소멸시키지 않고, 메모리의 상태를 본 후 일부만 소멸시킨다.

 


 

3. Objects 클래스

 

 

- Objects 클래스: 정적 메소드들로 구성된 Object의 유틸리티 클래스

 

 

- Objects.compare(T a, T b, Comparator<T>c) 메소드: 두 객체를 비교자(Comparator)로 비교해서 int 값을 리턴한다.

1) 일단 T는 비교할 객체 타입이라고 생각하기.

2) a가 b보다 작으면 -1, 같으면 0, 크면 1을 리턴한다.

→ Integer.compare(값1, 값2) 형태로 간단히 사용할 수 있다.

package ch11;

import java.util.Comparator;
import java.util.Objects;

public class CompareExample {

	public static void main(String[] args) {

		Student s1 = new Student(1);
		Student s2 = new Student(1);
		Student s3 = new Student(2);
		
		int result = Objects.compare(s1, s2, new StudentComparator());
		System.out.println(result);
		result = Objects.compare(s1, s3, new StudentComparator());
		System.out.println(result);
	}
	
	static class Student {
		int sno;
		Student(int sno) {
			this.sno = sno;
		}
	}
	
	static class StudentComparator implements Comparator<Student> {
		@Override
		public int compare(Student o1, Student o2) {
			return Integer.compare(o1.sno, o2.sno);
		}
	}

}

 

- 코드의 성능 및 효율성 측면을 gpt에게 물어본 결과 아래와 같은 답변이 출력됐다.

Q: 자바에서 Integer.compare() 메소드를 사용하는 것과 직접 제어문을 통해 두 수를 비교하는 것 중 어느 방법이 더 효율적이야?

A: 일반적으로, 직접 제어문을 사용하여 두 수를 비교하는 것보다 Integer.compare() 메소드를 사용하는 것이 더 효율적입니다. Integer.compare() 메소드는 두 개의 int 값을 비교하여 첫 번째 값이 작으면 음수, 같으면 0, 크면 양수를 반환합니다. 이 메소드는 내부적으로 삼항 연산자를 사용하여 값을 반환하기 때문에 코드가 간단하고 가독성이 좋습니다. 반면, 직접 제어문을 사용하여 두 수를 비교하는 경우 코드가 더 복잡하고 가독성이 낮아질 수 있습니다. 또한, 직접 제어문을 사용하는 경우 프로그래머가 작성한 코드가 최적화되어 있지 않으면 성능에 영향을 미칠 수 있습니다. 따라서, 가능하면 Integer.compare() 메소드를 사용하는 것이 좋습니다. 그러나 매우 성능에 민감한 코드의 경우, 해당 코드의 성능을 측정하여 최적화하는 것이 좋습니다.

 

 

- Objects.equals(Object a, Object b) 메소드: 두 객체의 동등을 비교한다.

1) a와 b가 모두 not null인 경우 a.equals(b)의 값을 리턴하지만, 한 쪽이 null일 경우엔 false를 리턴한다.

2) a와 b가 모두 null인 경우 true를 리턴한다.

 

 

- Objects.deepEquals(Object a, Object b) 메소드: 두 객체의 동등을 비교한다.

1) a와 b가 서로 다른 배열일 경우, 항목 값이 모두 같다면 true를 리턴한다.

→ Arrays.deepEquals(Object[ ] a, Object[ ] b)와 동일하다.

2) a와 b가 모두 not null인 경우 a.equals(b)의 값을 리턴하지만, 한 쪽이 null일 경우엔 false를 리턴한다.

3) a와 b가 모두 null인 경우 true를 리턴한다.

 

 

- Objects.hash(Objects ... values) 메소드: 매개값으로 주어진 값을을 이용해서 해시 코드를 생성한다.

1) 주어진 매개값들로 배열을 생성하고 Arrays.hashCode(Object[ ])를 호출해서 해시코드를 얻고 이 값을 리턴한다.

2) 클래스가 hashCode( )를 재정의할 때 리턴값을 생성하기 위해 사용하면 좋다.

3) Object.hashCode(Object o)는 o.hachCode( )의 리턴값과 동일하다.

→ 매개값이 null이면 0을 리턴한다는 차이점이 있다.

 

 

- 널 여부 조사 메소드

1) Objects.isNull(Object obj): 매개값이 null일 경우 true를 리턴한다.

2) Objects.nonNull(Object obj): 매개값이 not null일 경우 true를 리턴한다.

3) Objects.requireNonNull( ): 첫 번째 매개값이 not null이면 첫 번째 매개값을, null이면 모두 NullPointerException을 발생시킨다.

→ 두 번째 매개값은 NullPointerException의 예외 메시지를 제공한다. (optional)

 

 

- Objects.toString( ) 메소드: 객체의 문자 정보를 리턴한다.

1) 첫 번째 매개값이 not null이면 toString( )으로 얻은 값을 리턴한다.

2) 첫 번째 매개값이 null이면 "null" 또는 두 번째 매개값인(optional) nullDefault를 리턴한다.

 


 

4. System 클래스

 

 

- System 클래스의 모든 필드와 메소드는 정적(static) 필드와 정적 메소드로 구성되어 있다.

 

 

- exit( ) 메소드: 현재 실행하고 있는 프로세스를 강제 종료시킨다.

1) int 매개값을 지정하도록 되어 있는데, 일반적으로 정상 종료일 경우엔 0, 비정상 종료일 경우엔 0 이외의 값을 준다.

→ 어떤 값을 주더라도 종료되지만, 특정 값이 입력되었을 경우에만 종료하고 싶다면 자바의 보안 관리자를 직접 설정해서 종료 상태값을 확인하면 된다.

2) System.exit(0): JVM을 강제 종료하는 명령어

3) System.exit( )이 실행되면 보안 관리자의 checkExit( ) 메소드가 자동 호출된다.

→ 특정 값이 입력되지 않으면 SecurityException을 발생시켜 System.exit( )을 호출한 곳에서 예외 처리를 할 수 있게 해준다.

package ch11;

public class ExitExample {

	public static void main(String[] args) {

//		System.setSecurityManager(new SecurityManeger() {
//			@Override
//			public void checkExit(int status) {
//				if (status != 5) {
//					throw new SecurityException();
//				}
//			}
//		});

		for (int i = 0; i < 10; i++) {
			System.out.println(i);
			try {
				System.exit(i);
			} catch (SecurityException e) {}
		}
	}

}

 

 

- 시스템 프로퍼티(System Property): JVM이 시작할 때 자동 설정되는 시스템의 속성값

1) 키(key)와 값(value)으로 구성되어 있다.

2) System.getProperty( ) 메소드를 통해 시스템 프로퍼티를 읽어올 수 있다.

→ 시스템 프로퍼티의 키 이름을 매개값으로 받고, 해당 키에 대한 값을 문자열로 리턴한다.

3) System.getProperties( ): 모든(키, 값) 쌍을 저장하고 있는 Properties 객체를 리턴한다.

→ 해당 객체의 keySet( ) 메소드를 호출하면 키만으로 구성된 Set 객체를 얻을 수 있다.

키(key) 설명
java.version 자바의 버전
java.home 사용하는 JRE의 파일 경로
os.name 운영체제의 이름
file.separator File separator ("/" on UNIX)
user.name 사용자의 이름
user.home 사용자의 홈 디렉터리
user.dir 사용자가 현재 작업 중인 디렉터리 경로

 

 

- 환경 변수: 운영체제에서 이름과 값으로 관리되는 문자열 정보

1) System.getenv( ) 메소드를 통해 환경 변수의 값을 읽어올 수 있다.

→ 매개값으로 환경 변수 이름을 주면 값을 리턴한다.

 


 

5. Class 클래스

 

 

- 자바는 클래스와 인터페이스의 메타 데이터를 java.lang 패키지에 소속된 Class 클래스로 관리한다.

→ Class는 생성자를 감추고 있으므로 new 연산자로 객체를 생성할 수 없다.

 

 

- getClass( ) 메소드: Class 객체를 리턴한다.

1) 해당 클래스로 객체를 생성했을 때만 사용할 수 있다.

 

 

- forName( ) 메소드: 클래스 전체 이름(패키지가 포함된 이름)을 매개값으로 받고 Class 객체를 리턴한다.

1) 객체를 생성하기 전에 직접 Class 객체를 얻을 수 있는 방법이다.

2) ClassNotFoundException 예외 처리가 필요하다.

 

 

- 리플렉션(Reflection): Class 객체를 이용해 클래스의 생성자, 필드, 메소드 정보를 알아내는 것

1) getDeclaredConstructors( ): Constructor 배열을 리턴한다.

2) getDeclaredFields( ): Field 배열을 리턴한다.

3) getDeclaredMethods( ): Method 배열을 리턴한다.

4) getDeclaredConstructors( ), getDeclaredFields( )는 클래스에 선언된 멤버만 가져오고 상속된 멤버는 가져오지 않는다.

→ getFields( ), getMethods( )를 이용하면 상속된 멤버 중 public 멤버에 한해서 얻을 수 있다.

 

 

- Class 객체를 이용한 동적 객체 생성은 코드 작성 시에 클래스 이름을 결정할 수 없거나, 런타임 시에 클래스 이름이 결정되는 경우에 사용한다.

1) Class.forName( ) 메소드로 Class 객체를 얻은 다음 newInstance( ) 메소드를 호출하면 Object 타입의 객체를 얻을 수 있다.

 

 

- newInstance( ) 메소드: 동적 객체를 생성한다.

1) 기본 생성자를 호출해서 객체를 생성하기 때문에 반드시 클래스에 기본 생성자가 존재해야 한다.

2) 매개 변수가 있는 생성자를 호출하고 싶다면 리플렉션으로 Constructor 객체를 얻어 newInstance( ) 메소드를 호출하면 된다.

3) InstantiationException, IllegalAccessException 예외를 처리하는 코드가 필요하다.

4) 리턴 타입이 Object이므로 원래 클래스 타입으로 캐스팅해야 메소드를 사용할 수 있다.

→ 클래스 타입을 모르는 상태이므로 인터페이스를 사용해야 한다.

package ch11;

interface Action {
	public void execute();
}

public class SendReceiveAction implements Action {

	@Override
	public void execute() {
		System.out.println("send data.");
	}

}
package ch11;

public class NewInstanceExample {

	public static void main(String[] args) {

		try {
			Class clazz = Class.forName("ch11.SendReceiveAction");
			Action action = (Action) clazz.newInstance();	// 인터페이스 타입으로 캐스팅
			action.execute();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
	}

}

 

728x90
반응형

댓글