다시 하자 기초! java.lang의 유용한 클래스
그동안 import문 없이 String클래스나 System클래스를 사용할 수 있었던 것은 바로 java.lang에 속한 클래스들 였기 때문이다. 이 패키지에서 자주 사용되는 몇 가지만 골라서 자세히 보자
1. Object클래스
모든 클래스의 최고 조상이기 때문에 Object클래스의 멤버들은 모든 클래스에서 바로 사용 가능하다.
Object클래스는 멤버변수는 없고 오직 11개의 메서드로만 구성되어 있다. 우선 이 중에서 중요한 것만 보도록 하자
1-1 equals(Object obj)
매개변수로 객체의 참조변수를 받아서 비교하여 그 결과를 boolean값으로 알려주는 역할을 한다.
public static void main(String[] args){
Value v1 = new Value(10);
Value v2 = new Value(10);
if(v1.equals(v2))
System.out.print("v1과 v2는 같습니다.");
else
System.out.print("v1과 v2는 다릅니다.");
v1 = v2;
if(v1.equals(v2))
System.out.print("v1과 v2는 같습니다.");
else
System.out.print("v1과 v2는 다릅니다.");
출력 값
v1과 v2는 다릅니다.
v1과 v2는 같습니다.
equals메서드는 주소값으로 비교하기 때문에, 두 Value인스턴스의 멤버변수 value의 값이 10으로 서로 같을지라도 equals메서드로 비교한 결과는 false일 수 밖에 없는 것이다.
하지만 v1=v2;을 수행한 이후에는 참조변수 v1는 v2이 참조하고 있는 인스턴스의 주소값이 저장되므로 v2도 v1과 같은 주소값이 저장된다. 그래서 true가 되는 것.
equals 메서드로 값 비교하기
인스턴스이 값 자체만 비교하고 싶을 땐 Value 클래스에서 equals메서드를 오버라이딩하여 주소가 아닌 객체에 저장된 내용을 비교하도록 변경하면 된다.
class Person{
long id;
public boolean equals(Object obj){
if(obj!=null && obj instanceof Person)
return id == ((Person)obj).id; //obj가 Object타입이므로 id값을 참조하기 위해서 Person으로 형변환
else
return false; //타입이 Person이 아니라면 값을 비교할 필요가 없다.
}
Person(long id){
this.id = id;
}
}
String클래스 역시 Object클래스의 equals메서드를 그대로 사용하는 것이 아니라 이처럼 오버라이딩을 통해서 String인스턴스가 갖는 문자열 값을 비교하도록 되어 있다.
1-2 hashCode()
이 메서드는 해싱 기법에 사용되는 해시함수를 구현한 것이다. 해싱은 데이터 관리 기법 중의 하나인데 다량의 데이터를 저장하고 검색하는데 유용하다. 해시함수는 찾고자하는 값을 입력하면, 그 값이 저장된 위치를 알려주는 해시코드를 반환한다.
일반적으로 해시코드가 같은 두 객체가 존재하는 것이 가능하지만, Object클래스에 정의된 hashCode메서드는 객체의 주소값을 이용해서 해시코드를 만들어 반환하기 때문에 서로 다른 두 객체는 결코 같은 해시코드를 가질 수 없다. 앞서 본 것과 같이 클래스의 인스턴스변수 값으로 객체의 같고 다름을 판단해야하는 경우라면 equals메서드 뿐 만아니라 hashCode메서드도 적절히 오버라이딩해야 한다. 그렇지 않으면 Object클래스에 정의된 대로 모든 객체가 서로 다른 해시코드값을 가질 것이기 때문이다.
1-3 toString()
이 메서드는 인스턴스에 대한 정보를 문자열로 제공할 목적으로 정의한 것이다. 인스턴스의 정보를 제공한다는 것은 대부분의 경우 인스턴스 변수에 저장된 값들을 문자열로 표현한다는 뜻이다. toString을 오버라이딩 하지 않는다면 16진수의 해시코드를 얻게 된다.
1-4 clone()
이 메서드는 자신을 복제하여 새로운 인스턴스를 생성하는 일을 한다. 어떤 인스턴스에 대해 작업을 할 때, 원래의 인스턴스는 보존하고 clone메서드를 이용해서 새로운 인스턴스를 생성하여 작업을 하면 작업이전의 값이 보존되므로 작업에 실패해서 원래의 상태로 되돌리거나 변경되기 전의 값을 참고하는데 도움이 될 것이다.
Object클래스에 정의된 clone()은 단순히 인스턴스변수의 값만을 복사하기 때문에 참조타입의 인스턴스 변수가 있는 클래스는 완전한 인스턴스 복제가 이루어 지지않는다.
예를 들면 배열을 복사할 경우 복제된 인스턴스도 같은 배열의 주소값을 갖기 때문에 복제 인스턴스 작업이 원래의 인스턴스에 영향을 미치게 된다. 이런 경우 clone메서드를 오버라이딩해서 새로운 배열을 생성하고 배열의 내용을 복사하도록 해야한다.
class Point implements Cloneable { //clone()을 사용하려면 인터페이스를 구현해야함
int x,y;
Point(int x, int y){
this.x = x;
this.y = y;
}
@Override
public Object clone() throws CloneNotSupportedException
{
return super.clone();
}
class CloneEx1 {
public static void main(String[] args){
Point original = new Point(3,5);
Point copy = (Point)original.clone();
}
}
Clonable 인터페이스를 구현한 클래스의 인스턴스만 clone()을 통한 복제가 가능한데 그 이유는 데이터를 보호하기 위해서 이다. Cloneable인터페이스가 구현되어 있다는 것은 클래스 작성자가 복제를 허용한다는 의미이기 때문이다.
배열도 객체이기 때문에 Object클래스를 상속받으며, 동시에 Clonable인터페이스와 Serializable인터페이스가 구현되어있다. 그래서 Object클래스의 멤버들을 모두 상속받는다. 배열뿐만 아니라 List계열, set계열도 복사가능하다.
int[] arr = {1,2,3,4,5};
int[] arrClone = arr.clone();
ArrayList list = new ArrayList();
ArrayList list2 = (ArrayList)list.clone();
얕은복사와 깊은복사
clone()은 단순히 객체에 저장된 값을 그대로 복제할 뿐 객체가 참조하고 있는 객체까지 복제하지는 않는다. 객체배열을 clone()으로 복제하는 경우에는 원본과 복제본이 같은 객체를 공유하므로 완전한 복제라고 보기 어렵다. 이러한 복제를 얕은 복사라고한다. 얕은 복사는 원본을 변경하면 복사본도 영향을 받는다.
반면에 원본이 참조하고 있는 객체까지 복제하는 것을 깊은 복사라고 한다. 그리고 깊은복사에서는 원본과 복사본이 서로 다른 객체를 참조하고 있기 때문에 원본의 변경이 복사본에 영향을 미치지 않는다.
보충 자료 : https://smujihoon.tistory.com/9
1-5 getClass()
이 메서드는 자신이 속한 클래스의 Class객체를 반환하는 메서드인데, Class객체는 이름이 'Class'인 클래스의 객체이다. 즉 클래스 파일을 읽어서 사용하기 편한 형태로 저장해 놓은 것이 클래스 객체이다.
Class객체를 얻는 방법
Class cObj = new Card().getClass(); // 생성된 객체로부터 얻는 방법
Class cObj = Card.class; // 클래스 리터럴로 부터 얻는 방법
Class cObj = Class.forName("Card"); // 클래스 이름으로부터 얻는 방법
데이터베이스 드라이버를 메모리에 올릴 때 주로 사용된다.
2. String 클래스
기존의 다른 언어에서는 문자열을 char형의 배열로 다루었으나 자바에서는 문자열을 위한 클래스를 제공한다. 그것이 바로 String클래스인데 아주 중요하므로 자세히 공부해야한다.
변경불가능한 클래스
String클래스에는 문자열을 저장하기 위해서 문자형 배열변수(char[]) value를 인스턴스 변수로 정의 해놓고 있다. 인스턴스 생성 시 생성자의 매개변수로 입력받는 문자열은 이 인스턴스변수에 문자형 배열로 저장되는 것이다. 한편 생성된 String인스턴스가 갖고 있는 문자열은 읽어 올 수만 있고, 변경할 수는 없다. 예를 들면 +연산자로 문자열을 결합하는 것은 인스턴스내의 문자열이 바뀌는 것이 아니라 새로운 문자열이 생성되는 것이다. 그래서 메모리공간을 낭비하게 될 수도 있다.
문자열간의 결합이나 추출 등이 많은 경우에는 String클래스보단 StringBuffer클래스를 사용하는 것이 좋다. StringBuffer 인스턴스에 저장된 문자열은 변경이 가능하므로 하나의 인스턴스만으로도 문자열을 다루는것이 가능하다.
String str1 = "abc";
String str2 = "abc";
String str3 = new String("abc");
String str4 = new String("abc");
위와 같이 생성할 때 두 번째 보단 첫 번째 방식을 선호한다. 이유는 아래와 같다.
3. StringBuffer클래스와 StringBuilder클래스
String클래스는 인스턴스를 변경할 수 없지만 StringBuffer클래스는 변경이 가능하다. 내부적으로 문자열 편집을 위한 버퍼를 가지고 있으며, StringBuffer인스턴스를 생성할 때 그 크기를 지정할 수 있다. 이 때 버퍼의 길이를 충분히 잡아주는 것이 좋다. 문자열이 버퍼의 길이를 넘어서면 늘려주는 작업이 추가로 수행되어야 하기에 효율이 떨어지기 때문이다.
StringBuffer의 변경
StringBuffer sb = new StringBuffer("abc");
sb.apped("123");
sb.apped("zz").apped("dd");
append의 반환 값이 자기 자신의 주소값을 반환하기 때문에 같은 주소에 데이터 저장이 가능하다.
StringBuffer의 비교
String클래스처럼 오버라이딩이 되어 있지않기 때문에 equals메서드를 사용해도 등가비교연산자(==)로 비교한 것과 같은 결과를 얻는다. 반면에 toString()은 오버라이딩되어 있기에 담고있는 문자열을 출력해준다.
StringBulider란?
StringBuffer는 멀티쓰레드에 안전하도록 동기화되어 있다. 아직은 멀티쓰레드나 동기화에 대해서 배우지 않았지만 동기화가 StringBuffer의 성능을 떨어 뜨린다. 그래서 멀티쓰레드로 작성된 프로그램이 아닐 경우에 사용하도록 만들어진게 StringBuilder다. 두 개가 완전히 똑같으므로 그냥 바꾸면된다.
4. 래퍼(wraapper) 클래스
객체지향 개념에서 모든 것은 객체로 다뤄져야한다. 그러나 자바에서는 8개의 기본형을 객체로 다루지 않는데 이것이 바로 자바가 완전한 객체지향 언어가 아니라는 얘기를 듣는 이유다. 그 대신 보다 높은 성능을 얻을 수 있었다. 하지만 때로는 기본형 변수도 어쩔 수 없이 객체로 다뤄야하는 경우가 있다.
예를 들면 매개변수로 객체를 요구할 때, 기본형 값이 아닌 객체로 저장되어야할 때, 객체 간의 비교가 필요할 때 등등의 경우에는 기본형 값들을 객체로 변환하여 작업을 수행해야한다. 이 때 사용되는 것이 래퍼클래스다.
기본형 값을 래퍼클래스의 객체로 자동 변환해주는 것을 오토박싱이라 하고 그 반대는 언박싱이라고 한다.
5. 정규식(Regular Expression) 클래스
정규식이란 텍스트 데이터 중에서 원하는 조건(패턴, pattertn)과 일치하는 문자열을 찾아내기 위해 사용하는 것으로 미리 정의된 기호와 문자를 이용해서 작성한 문자열을 의미한다. 원래 Unix에서 사용하던 것이고 Perl의 강력한 기능이었는데 요즘은 Java를 비롯해 다양한 언어에서 지원하고 있다.
정규식을 정의하고 데이터를 비교하는 과정
1. 정규식을 매개변수로 Pattern클래스의 static메서드인 Pattern compile(String regex)을 호출하여 Pattern인스턴스를 얻는다. -> Pattern p = Pattern.compile("c[a-z]*");
2. 정규식으로 비교할 대상을 매개변수로 Pattern클래스의 Matcher matcher(CharSequence input)를 호출해서 Matcher인스턴스를 얻는다 -> Matcher m = p.matcher(data[i]);
3. Matcher인스턴스에 boolean matches()를 호출해서 정규식에 부합하는지 확인한다.
-> if(m.matches())
6. Scanner 클래스
scanner는 화면, 파일, 문자열과 같은 입력소스로부터 문자데이터를 읽어오는데 도움을 줄 목적으로 JDK1.5부터 추가되었다. Scanner에는 다음과 같은 생성자를 지원하기 때문에 다양한 입력소스로부터 데이터를 읽을 수 있다.
Scanner(String source)
Scanner(File source)
Scanner(InputStream source)
Scanner(Readable source)
Scanner(ReadableByteChannel source)
Scanner(Path source)