자바의 정석(남궁성 저) 학습내용 정리
1. java.lang 패키지
- java.lang 패키지의 클래스들은 import문 없이도 사용 가능하다.
2. Object 클래스
- Object 클래스는 모든 클래스의 최고 조상
- Object 클래스의 멤버들은 모든 클래스에서 바로 사용 가능
- Object 클래스는 멤버변수는 없고 11개의 메소드만 있다.
- protected Object clone() : 객체 자신의 복사본 반환
- public boolean equals(Object obj) : 객체 자신과 객체 obj가 같은 객체인지 판단
- protected void finalize() : 객체가 소멸될 때 가비지콜렉터에 의해 자동으로 호출(거의 사용안함)
- public Class getClasss() : 객체 자신의 클래스 정보를 담고 있는 Class 인스턴스를 반환
- public int hashCode() : 객체 자신의 해시코드를 반환
- public String toString() : 객체 자신의 정보를 문자열로 반환
- public void notify() : 객체 자신을 사용하려고 기다리는 쓰레드를 하나만 깨운다.
- public void notifyAll() : 객체 자신을 사용하려고 기다리는 모든 쓰레드를 깨운다.
- public void wait(long timeout, int nanos) : 다른 쓰레드가 notify()나 notifyAll()을 호출할 때 까지 현재 쓰레드를 무한히 또는 지정된 시간동안 기다리게 한다.
3. equals(Object obj)
- Object 클래스로부터 상속받은 equals 메소드는 두 개의 참조변수가 같은 객체를 참조하는지(두 참조변수에 저장된 주소값이 같은지) 판단
- equals 메소드로 Value 인스턴스가 가지고 있는 value값을 비교하려면,
 Value 클래스에서 equals 메소드를 오버라이딩하여 주소가 아닌 객체에 저장된 내용을 비교하도록 변경해야 한다.
4. hashCode()
- 해시함수는 찾는 값을 입력하면, 그 값이 저장된 위치를 알려주는 해시코드(hash code)를 반환한다.
- 일반적으로 해시코드가 같은 두 객체가 존재하는 것이 가능하지만,
 Object 클래스에 정의된 hashCode 메소드는 객체의 주소값을 이용해서 해시코드를 만들어 반환하기 때문에
 서로 다른 두 객체는 같은 해시코드를 가질 수 없다.
- String 클래스는 문자열의 내용이 같으면 같은 해시코드를 반환하도록 hashCode메소드가 오버라이딩 되어 있기 때문에
 문자열의 내용이 같은 str1과 str2에 대해 hashCode()를 호출하면 항상 동일한 해시코드 값을 얻는다.
5. toString()
- Object 클래스에 정의된 toString()
public String toString() { return getClass().getName()+"@"+Integer.toHexString(hashCode()); }
- 클래스를 작성할 때 toString()을 오버라이딩 하지 않고, toString()을 호출하면 클래스이름+16진수의 해시코드를 반환한다.
- String 클래스의 toString()은 String 인스턴스가 갖고 있는 문자열을 반환하도록 오버라이딩 되어 있다.
- Date 클래스의 경우, Date 인스턴스가 갖고 있는 날짜와 시간을 문자열로 반환하도록 오버라이딩 되어 있다.
6. clone()
- 자신을 복제하여 새로운 인스턴스를 생성한다.
- Object 클래스에 정의된 clone()은 단순히 인스턴스 변수의 값만 복사하기 때문에
 참조타입의 인스턴스 변수가 있는 클래스는 완전한 인스턴스 복제가 이루어지지 않는다.
- clone()은 반드시 예외처리를 해줘야 한다.
7. 공변 반환타입 (covariant return type)
- JDK1.5부터 오버라이딩할 때 부모 메소드의 반환타입을 자식 클래스의 타입으로 변경 가능하다.
public Point clone() { // 반환타입을 Object에서 Point로 변경 Object obj = null; try { obj = super.clone(); } catch(CloneNotSupportedException e) {} return (Point)obj; //Point 타입으로 형변환
- 공변 반환타입을 사용하면 조상의 타입이 아닌, 실제로 반환되는 자손 객체의 타입으로 반환할 수 있어 번거로운 형변환이 줄었다.
- Point copy = (Point)original.clone();->- Point copy = original.clone();
 
8. 배열 복사
- 배열도 객체이기 때문에 Object 클래스를 상속받으며, 동시에 Cloneable 인터페이스와 Serializable 인터페이스가 구현되어 있다.
- 일반적으로 배열을 복사할 때는 같은 길이의 새로운 배열을 생성한 다음 System.arraycopy()를 이용해서 내용을 복사하지만clone()을 이용해서 간단하게 복사할 수 있다.- int[] arr = {1,2,3,4,5}; int[] arrClone = arr.clone();
- int[] arr = {1,2,3,4,5}; int[] arrClone = new int[arr.length]; System.arraycopy(arr,0,arrClone,0,arr.length);
 
- 배열 뿐만 아니라 java.util패키지의
Vector
ArrayList
LinkedList
HashSet
TreeSet
HashMap
TreeMap
Calendar
Date와 같은 클래스들도 복제가 가능하다.
9. 얕은 복사와 깊은 복사 (shallow copy & deep copy)
- clone()은 객체에 저장된 값을 그대로 복제할 뿐, 객체가 참조하고 있는 객체까지 복제하지 않음
- 원본과 복제본이 같은 객체를 참조
- 이것이 얕은 복사(shallow copy)
- 얕은 복사에서는 원본을 변경하면 복사본도 영향을 받는다.
 
- 원본이 참조하고 있는 객체까지 복제하는 것이 깊은 복사(deep copy)
- 원본과 복사본이 서로 다른 객체를 참조하기 때문에 원본의 변경이 복사본에 영향 없음
 
- 원본과 복사본이 서로 다른 객체를 참조하기 때문에 원본의 변경이 복사본에 영향 없음
10. getClass()
- 자신이 속한 클래스의 Class 객체를 반환하는 메소드
- Class 객체는 이름이 ‘Class’인 클래스의 객체이다.
public final class Class implements ... { ... }
- Class 객체는 클래스의 모든 정보를 담고 있으며, 클래스 당 1개만 존재한다.
- 클래스 파일이 ‘ClassLoader’에 의해 메모리에 올라갈 때 자동으로 생성
- 파일 형태로 저장되어 있는 클래스를 읽어서 Class클래스에 정의된 형식으로 변환하는 것
- 즉, 클래스 파일을 읽어서 사용하기 편한 형태로 저장한 것이 클래스 객체
 
- Class 객체 얻는 방법
- Class cObj = new Card().getClass(); // 생성된 객체로 부터 얻기
- Class cObj = Card.class; // 클래스 리터럴(*.class)로 부터 얻기
- Class cObj = Class.forName("Card"); //클래스 이름으로 부터 얻기
 
- 객체 생성
- Card c = new Card(); // new연산자로 객체 생성
- Card c = Card.class.newInstance(); // Class객체로 객체 생성
 
11. String 클래스
- String 클래스는 문자열을 저장하기 위해 문자형 배열 변수(char[]) value를 인스턴스 변수로 정의되어있다.
public final class String implements java.io.Serializable, Comparable { private char[] value; ... }
- 인스턴스 생성 시 생성자의 매개변수로 입력받는 문자열은 인스턴스 변수 value에 문자형 배열char[]로 저장되는 것이다.
- String 클래스는 앞에 final이 붙어 있으므로 다른 클래스의 부모가 될 수 없다.
- 변경 불가능한(Immutable) 클래스다.
- 예를 들어 ‘+’ 연산자를 이용해서 문자열을 결합하는 경우, 인스턴스 내의 문자열이 바뀌는 것이 아니라 새로운 문자열이 담긴 String 인스턴스가 생성되는 것이다.
- 즉, 매 연산 마다 새로운 문자열을 가진 String 인스턴스가 생성되어 메모리 공간을 차지한다.
 
11.1. 문자열을 만드는 방법 2가지
- String str1 = "abc";// 문자열 리터럴 “abc”의 주소를 str1에 저장
- String str2 = new String("abc");// 새로운 String 인스턴스 생성
- 문자열 리터럴은 이미 존재하는 것을 재사용 하는 것이다. (문자열 리터럴은 클래스가 메모리에 로드될 때 자동으로 미리 생성된다.)
- String str1 = "abc"; String str2 = "abc"; str1 == str2 ? true str1.equals(str2) ? true String str3 = new String("abc"); String str4 = new String("abc"); str3 == str4 ? false str3.equals(str4) ? true
11.2. 문자열 리터럴
- 클래스 파일에는 소스파일에 포함된 모든 리터럴의 목록이 있다.
- 해당 클래스 파일이 클래스 로더에 의해 메모리에 올라갈 때, 리터럴 목록에 있는 리터럴들이 JVM내에 있는 상수 저장소(constant pool)에 저장된다.
11.3. 빈 문자열
- 길이가 0인 배열은 존재한다.(C언어는 불가능)
- char형 배열도 길이가 0인 배열 생성할 수 있고, 이 배열을 내부적으로 가지고 있는 문자열이 빈 문자열이다.
- String s = "";에서 참조변수- s가 참조하고 있는 String 인스턴스는 내부에- new char[0]같이 길이가 0인 char형 배열을 저장하고 있는 것이다.
- char[] chArr = new char[0];
- int[] iArr = {};
11.4. join()과 StringJoiner (jdk1.8부터 추가)
- join()은 문자열 사이에 구분자를 넣어서 결합- String[] arr = { "dog", "cat", "bear"}; String str = String.join("-", arr);
- java.util.StringJoiner클래스 사용하여 문자열 결합- StringJoiner sj = new StringJoiner("," , "[" , "]"); String[] strArr = { "aaa", "bbb", "ccc" }; for(String s : strArr) sj.add(s.toUpperCase()); System.out.println(sj.toString()); // [AAA,BBB,CCC]
11.5. 기본형-String 변환
- 숫자에 빈문자열(“”) 더하기
- valueOf()사용
- String str1 = 100 + ""; // 100 > "100" String str2 = String.valueOf(100); // 100 > "100" int i = Integer.parseInt("100") // "100" > 100 int i2 = Integer.valueOf("100") // "100" > 100
- valueOf()의 반환타입은 int가 아니라 Integer지만 오토박싱에 의해 자동변환
- valueOf(String s)는 메소드 내부에- parseInt(String s)를 호출할 뿐이므로, 두 메소드는 반환 타입만 다르고 같은 메소드다.
12. StringBuffer & StringBuilder 클래스
public final class StringBuffer implements java.io.Serializable {
    private char[] value;
    ...
}- String클래스는 Immutable이므로 인스턴스를 생성할 때 지정된 문자열을 변경할 수 없지만, StringBuffer클래스는 가능하다.
- 내부적으로 문자열 편집을 위한 buffer를 가지고 있으며, StringBuffer 인스턴스를 생성할 때 그 크기를 지정할 수 있다.
- buffer의 크기를 지정하지 않으면 16개의 문자를 저장할 수 있는 크기의 버퍼 생성
12.1 append()
- append()는 반환타입이 StringBuffer이며 자신의 주소를 반환한다.- StringBuffer sb = new StringBuffer("abc"); sb.append("123").append("ZZ");
- 위의 코드에서 sb.append("123")이sb를 반환하므로 연속적으로 append()를 호출할 수 있다.
- StringBuffer 클래스는 equals() 메소드를 오버라이딩 하지 않아서 ==로 비교한 것과 같은 결과를 얻는다.
- StringBuffer 인스턴스에 담긴 문자열을 비교하기 위해서는 인스턴스에 toString()을 호출해서 String인스턴스를 얻은 후에equals()사용해야 한다.
12.2. StringBuilder 클래스
- StringBuffer는 멀티쓰레드에 안전(thread safe)하도록 동기화 되어있다.
- 동기화가 StringBuffer의 성능을 떨어트리므로, StringBuffer에서 쓰레드의 동기화만 뺀 것이 StringBuilder.
13. java.util.Random 클래스
- Math.random()은 내부적으로 Random클래스의 인스턴스를 생성해서 사용한다.
- Math.random()과- Random의 가장 큰 차이는 종자값(seed)을 설정할 수 있다는 것이다.
- 종자값이 같은 Random인스턴스들은 항상 같은 난수를 같은 순서대로 반환
- 생성자 Random()은 종자값을System.currentTimeMillis()로 하기 때문에 실행할 때마다 얻는 난수가 달라진다.
14. java.util.StringTokenizer 클래스
- StringTokenizer는 긴 문자열을 지정된 구분자(delimeter)를 기준으로 토큰(token)이라는 여러 개의 문자열로 잘라내는 데 사용
- StringTokenizer는 구분자로 단 하나의 문자만 사용 가능하기 때문에 복잡한 형태의 구분자는 정규식을 사용해야 한다.
- StringTokenizer (String str, String delim, boolean returnDelims)- returnDelims 값을 true로 하면 구분자도 토큰으로 간주한다.
- split()은 빈 문자열도 토큰으로 인식, StringTokenizer는 빈 문자열을 토큰으로 인식 안함
- split()은 데이터를 토큰으로 잘라낸 결과를 배열에 담아서 반환
- StringTokenizer는 데이터를 토큰으로 바로바로 잘라서 반환하므로 split()보다 성능 좋음
14.1. 한글로 된 숫자를 아라비아 숫자로 변환
import java.util.*;
class StringTokenizer {
  public static void main(String args[])  {
    String input = "삼십만삼천백십오";
    System.out.println(input); // 삼십만삼천백십오
    System.out.println(hangulToNum(input)); // 303115
  }
  
  public static long hangulToNum(String input)  {
    long result = 0;  // 최종 변환결과를 저장하기 위한 변수
    long tmpResult = 0; // 십백천 단위의 값을 저장하기 위한 임시변수
    long num = 0;
    
    final String NUMBER = "영일리삼사오육칠팔구";
    final String UNIT = "십백천만억조";
    final long[] UNIT_NUM = {10,100,1000,10000,(long)1e8,(long)1e12};
    
    StringTokenizer st = new StringTokenizer(input, UNIT, true);
    
    while(st.hasMoreTokens()) {
      String token = st.nextToken();
      int check = NUMBER.indexOf(token);  // 숫자인지, 단위(UNIT)인지 확인
      
      if(check == -1) {
        if("만억조".indexOf(token) == -1) {
          tmpResult += ( num!=0 ? num : 1) * UNIT_NUM[UNIT.indexOf(token)];
        }else {
          tmpResult += num;
          result += (tmpResult!=0 ? tmpResult : 1)
           * UNIT_NUM[UNIT.indexOf(token)];
          tmpResult = 0;
        }
        num = 0;
      }else {
        num = check;
      }
    }
    return result + tmpResult + num;
  }
} 
         
        