🚣활동/NHN Academy

참조 타입

말동말동현 2025. 1. 14. 17:06

배열 자체를 넘기면 pass by reference

배열에 있는 요소를 넘기면 pass by value

public class Sample {

    public static void changeSomething(int i) {
        i++;
    } 
    public static void main(String[] args) {
       
        int[] i = {0};
        changeSomething(i[0]);
        System.out.println(i[0]);
    }
}
>> 0

 

 

Enum

 

Enum 사용 예시

public enum Day {
    Sunday,
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday
}

class Date {
    int year, month, day;
    Day dayofday;

    public void setDate(int year, int month, int day, Day dayofday) {
        this.year = year;
        this.month = month;
        this.day = day;
        this.dayofday = dayofday;
    }

}

class Test {
    public static void main(String[] args) {
        Date date1 = new Date();
        date1.setDate(2025, 1, 14, Day.Tuesday);

        Date date2 = new Date();
        date2.setDate(2025, 1, 21, Day.Tuesday);

        System.out.println(date1.dayofday == date2.dayofday); //true
    }
}

 

 

enum_method

name 메소드

- name() 메소드는 enum 객체가 가지고 있는 문자열을 return 합니다. 문자열은 enum 타입을 정의할 때 사용한 상수 이름과 동일합니다.

Suit suit = Suit.Spade;
suit.name();

 

ordinal 메소드

- ordinal() 메소드는 해당 enum 객체가 전체 enum 중 몇 번째 열거 객체인지 알려줍니다. 객체의 순번은 열거 상수의 정의 순서이며, 0에서 시작합니다.

enum Suit {
    Spade,
    Diamond,
    Heart,
    Club
}

Suit suit1 = Suit.Spade;
Suit suit2 = Suit.Diamond;
Suit suit3 = Suit.Heart;
Suit suit4 = Suit.Club;

System.out.println(suit1.ordinal());	// 0
System.out.println(suit2.ordinal());	// 1
System.out.println(suit3.ordinal());	// 2
System.out.println(suit4.ordinal());	// 3

 

compareTo 메소드

- compareTo() 메소드는 메소드 인자로 전달된 enum 타입을 기준으로 전 후로 몇 번째에 위치하는지 비교합니다.

Suit suit1 = Suit.Spade;
Suit suit2 = Suit.Club;

System.out.println(suit1.compareTo(suit2));	// -3

 

 

valueOf 메소드

- valueOf() 메소드는 인자로 전달된 문자열과 동일한 문자열을 가지는 enum 객체를 반환합니다.

Suit result = Suit.valueOf("Spade");
result.name();	// Spade

 

values 메소드

- values() 메소드는 해당 enum에 포함된 모든 열거 객체들을 배열로 만들어 return 합니다.

Suit suit = Suit.Spade;
Suit[] suits = suit.values();
for (Suit suit: suits) {
    System.out.println(suit);
}

 

 

Enum 생성자

  • enum의 각 열거형 상수에 추가 속성 부여
    • 이름을 나타내는 상수와 함꼐 추가적인 속성을 사용할 수 있음
    • 1생성자 파라미터 순서대로 속성을 부여
  • 생성자는 private
    • enum 타입은 고정된 상수의 집합으로, 컴파일시에 타입 안정성이 보장되어야 함
    • 외부에서 접근 가능한 생성자가 없으므로, 실제로 final과 동일하게 동작
    • Singleton을 일반화 함
public enum Day {
    Sunday("일요일"),
    Monday("월요일"),
    Tuesday("화요일"),
    Wednesday("수요일"),
    Thursday("목요일"),
    Friday("금요일"),
    Saturday("토요일");

    private String korDayName;

    private Day(String korDayName) {
        this.korDayName = korDayName;
    }

    public String getKorDayName() {
        return this.korDayName;
    }
}

class Date {
    int year, month, day;
    Day dayofday;

    public void setDate(int year, int month, int day, Day dayofday) {
        this.year = year;
        this.month = month;
        this.day = day;
        this.dayofday = dayofday;
    }

}

class Test {
    public static void main(String[] args) {
        Date date1 = new Date();
        date1.setDate(2025, 1, 14, Day.Tuesday);

        Date date2 = new Date();
        date2.setDate(2025, 1, 21, Day.Tuesday);

        System.out.println(date1.dayofday == date2.dayofday); // true

        System.out.println(date2.dayofday.getKorDayName()); // 화요일
    }
}

 

 

 

Singleton

public class NonSingleton {
    private int accountNo = 0;
    
    public NonSingleton() {}
    
    public int getNextNumber() {
        return accountNo++;
    }
}

class Test {
    public static void main(String[] args) {
        NonSingleton counter1 = new NonSingleton();
        NonSingleton counter2 = new NonSingleton();
        
        System.out.println(counter1.getNextNumber()); // 0
        System.out.println(counter1.getNextNumber()); // 1
        System.out.println(counter2.getNextNumber()); // 0  <- 문제: 새로운 객체가 생성되어 번호가 초기화됨
        System.out.println(counter2.getNextNumber()); // 1
    }
}

 

 

  • 새로운 객체를 생성할 때마다 accountNo가 0으로 초기화됩니다
  • 은행 계좌번호와 같이 고유한 순차 번호가 필요한 경우 문제가 발생합니다

 

위는 싱글톤을 보장하지 못하는 코드다.

싱글톤을 보장하기 위해선 다음과 같이 해야한다.

public class Singleton {
    private static Singleton instance;
    private int accountNo = 0;
    
    private Singleton() {} // private 생성자로 외부 생성 차단
    
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
    
    public synchronized int getNextNumber() {
        return accountNo++;
    }
}

class Test {
    public static void main(String[] args) {
        Singleton counter1 = Singleton.getInstance();
        Singleton counter2 = Singleton.getInstance(); // 새로운 객체가 아닌 기존 객체를 반환
        
        System.out.println(counter1.getNextNumber()); // 0
        System.out.println(counter1.getNextNumber()); // 1
        System.out.println(counter2.getNextNumber()); // 2  <- 정상: 번호가 연속해서 증가
        System.out.println(counter2.getNextNumber()); // 3
    }
}
  1. private 생성자: 외부에서 new 키워드로 객체 생성을 막습니다
  2. private static 인스턴스: 클래스 내부에서만 단일 인스턴스를 관리합니다
  3. public static getInstance() 메소드: 항상 동일한 인스턴스를 반환합니다

이렇게 구현하면 어디서 호출하더라도 항상 같은 인스턴스를 사용하게 되므로, 계좌번호와 같은 순차적인 값을 안전하게 관리할 수 있습니다.

 

 

 

Auto Boxing, Auto Unboxing

public class Sample {
    public static void main(String[] args) {
        int i = 5;
        Integer wrapper = i; // Auto Boxing

        int j = wrapper; // Auto Unboxing
    }
}

 

 

 

 

메소드 오버라이딩

public class Sample {
    public static void main(String[] args) {
        
        D d = new D();
        C c = d;
        B b = c;
        A a = b;

        a.a();
        b.a();
        c.a();
        d.a();
    }
}

class A {
    public void a() {
        System.out.println("A");
    }
}

class B extends A {
    public void a() {
        System.out.println("B");
    }
}

class C extends B {
    public void a() {
        System.out.println("C");
    }
}

class D extends C {
    public void a() {
        System.out.println("D");
    }
}

 

 

객체 생성과 참조:

D d = new D();  // 실제 객체는 D 타입으로 생성됨
C c = d;        // D 객체를 C 타입으로 참조
B b = c;        // D 객체를 B 타입으로 참조
A a = b;        // D 객체를 A 타입으로 참조

여기서 중요한 점은 실제 생성된 객체는 D 타입이라는 것입니다.

 

동적 바인딩(Dynamic Binding):

  • Java는 런타임에 실제 객체의 타입을 확인하고 그 객체의 메소드를 호출합니다.
  • 참조 변수의 타입이 아닌, 실제 객체의 타입에 따라 메소드가 결정됩니다.

메소드 호출 과정:

a.a();  // 참조는 A 타입이지만 실제 객체는 D -> D의 a() 호출
b.a();  // 참조는 B 타입이지만 실제 객체는 D -> D의 a() 호출
c.a();  // 참조는 C 타입이지만 실제 객체는 D -> D의 a() 호출
d.a();  // 참조는 D 타입이고 실제 객체도 D -> D의 a() 호출

 

따라서 모든 호출에서 D 클래스의 a() 메소드가 실행되어 "D"가 출력됩니다.

만약 각각 다른 클래스의 메소드를 호출하고 싶다면, super 키워드를 사용하여 상위 클래스의 메소드를 명시적으로 호출해야 합니다:

class D extends C {
    public void a() {
        super.a();  // C의 메소드 호출
        System.out.println("D");
    }
}

 

Quiz

1.참조 타입의 메모리 할당 및 할당 해제:

  • 메모리 할당: new 연산자를 사용하여 힙(heap) 영역에 객체를 생성합니다. 스택(stack)에는 이 객체의 주소를 가리키는 참조가 저장됩니다.
  • 할당 해제: 가비지 컬렉터(Garbage Collector)가 자동으로 처리합니다. 객체를 참조하는 변수가 없게 되면(참조 카운트가 0이 되면) GC가 해당 객체를 메모리에서 제거합니다.

 

2. 참조 타입 변수의 null 상태:

  • 객체를 가리키지 않는 참조 변수의 값은 null입니다.
  • null인 참조 변수에 접근하면 NullPointerException이 발생합니다.
  • 이를 방지하기 위해 객체 사용 전 null 체크를 하는 것이 좋습니다.

 

3. String 클래스의 특징:

  • 불변(Immutable) 객체입니다: 한번 생성된 문자열은 변경할 수 없습니다.
  • String Pool을 통한 메모리 최적화: 동일한 문자열 리터럴은 String Pool에서 재사용됩니다.
  • 문자열 연산시 새로운 객체가 생성됩니다.
  • 문자열 비교는 equals() 메소드를 사용해야 합니다.
  • Thread-safe 합니다.

 

 

4. 모든 클래스의 기본 클래스:

  • java.lang.Object가 모든 클래스의 최상위 클래스입니다.
  • 명시적으로 상속을 선언하지 않아도 자동으로 Object 클래스를 상속받습니다.

 

 

5. Object 클래스의 주요 메소드:

  • toString(): 객체의 문자열 표현을 반환
  • equals(): 객체의 동등성 비교
  • hashCode(): 객체의 해시코드 값을 반환
  • clone(): 객체의 복사본을 생성
  • getClass(): 객체의 런타임 클래스를 반환
  • finalize(): 객체가 가비지 컬렉션되기 전에 호출
  • wait(), notify(), notifyAll(): 스레드 동기화에 사용