강의

멘토링

로드맵

Inflearn brand logo image

인프런 커뮤니티 질문&답변

박준형님의 프로필 이미지
박준형

작성한 질문수

김영한의 실전 자바 - 중급 2편

직접 구현하는 연결 리스트4 - 제네릭 도입

직접 구현하는 연결 리스트4 - 제네릭 도입 코드 질문

작성

·

188

·

수정됨

0

 
=========================================
[질문 템플릿]
1. 강의 내용과 관련된 질문인가요? (예/아니오)


2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)

예3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)

[질문 내용]



package collection.link;

public class MyLinkedListV3<E> {
    private Node<E> first;
    private int size;
    public void add(E e){
        Node<E> newNode = new Node<>(e);

        if (first == null){
            first = newNode;
        } else {
            Node<E> lastNode = getLastNode();
            lastNode.next = newNode;
        }
        size++;

    }

    //추가 코드
    public void add(int index, E e){
        Node<E> newNode = new Node<>(e);
        if (index == 0){
            newNode.next = first;
            first = newNode;
        } else{
            Node<E> prev = getNode(index - 1);
            newNode.next = prev.next;
            prev.next = newNode;
        }
        size++;
    }

    @Override
    public String toString() {
        return "MyLinkedListV1{" +
                "first=" + first +
                ", size=" + size +
                '}';
    }
    private static class Node<E>{
        E item;
        Node<E> next;

        public Node(E item) {
            this.item = item;
        }

        //A->B->C이런모양으로 출력하고 싶어!
        @Override
        public String toString() {
            StringBuilder stringBuilder = new StringBuilder();
            Node<E> x = this;
            stringBuilder.append("[");
            while (x != null) {
                stringBuilder.append(x.item);
                if (x.next != null) {
                    stringBuilder.append("->");
                }
                x = x.next;
            }
            stringBuilder.append("]");
            return stringBuilder.toString();
        }
    }
}

위 코드에서 Node는 정적 중첩 클래스로서 MyLinkedListV3에 선언돼 있습니다. static은 바깥 클래스와 독립적이고 클래스레벨에 소속돼 있어 인스턴스에 따라서 static class가 바뀌는 일은 없다고 알고 있었습니다.

 

그런데 여기서는 바깥 클래스의 타입 매개변수 E가 정적 중첩클래스인 Node에 영향을 주어 Node의 item의 타입을 E로 만듭니다. 그러면 E가 Integer인 MyLinkedListV3, E가 String인 MyLinkedListV3가 있을 수 있다는 것인데 타입 매개변수가 도입된 정적 중첩 클래스는 정적 변수와 다르게 인스턴스에 영향을 받을 가능성이 있다고 생각돼서 다음과 같이 실험해봤습니다.

아래 코드를 실행한 결과

package collection.link;


public class MyLinkedListV3Main {
    public static void main(String[] args) {
        MyLinkedListV3<String> stringList = new MyLinkedListV3<>();
        MyLinkedListV3<Integer> intList = new MyLinkedListV3<>();
        stringList.add("a");
        stringList.add("b");
        stringList.add("c");
        String string = stringList.get(0);
        System.out.println("string = " + string);
        System.out.println(stringList);

        intList.add(1);
        intList.add(2);
        intList.add(3);
        Integer integer = intList.get(0);
        System.out.println("integer = " + integer);
        System.out.println(intList + " = " + stringList);

    }
}
string = a
MyLinkedListV1{first=[a->b->c], size=3}
integer = 1
MyLinkedListV1{first=[1->2->3], size=3} = MyLinkedListV1{first=[a->b->c], size=3}
이 부분을 유의 깊게 봐주세요
System.out.println(intList + " = " + stringList);

위 코드를 실행하면 intList, strList를 동시에 출력해도 잘 출력이 되고 서로 독립적인 클래스가 타입만 다른 상태로 선언된 것처럼 보입니다.

의문점이 또 있습니다.

MyLinkedListV3<Integer> intList2 = new MyLinkedListV3<>();
//을 main에 추가하고 

System.out.println(intList + " = " + stringList + intList2);
//위와 같이 intList2도 출력하면 

MyLinkedListV1{first=[1->2->3], size=3} = MyLinkedListV1{first=[a->b->c], size=3}MyLinkedListV1{first=null, size=0}

위와 같이 타입 매개변수를 inList와 intList2가 같은 Integer인 상황임에도 링크드 리스트안의 요소들을 공유하진 않습니다. 같은 타입 매개변수를 사용하는 인스턴스들 끼리는 같은 정적 중첩 클래스를 공유하는 줄 알았는데 이것도 아닌 것 같습니다.

 

타입 매개변수가 적용된 정적 중첩 클래스는 정적 변수와 다르게 인스턴스마다 서로 다르게 존재하는지 여쭙고 싶습니다.

 

마지막 제네릭 타입 안에서 Node<E> node = new Node<>();는 되고 new E();는 안되는 이유를 생각해봤는데 맞게 생각한건지 봐주셨으면 좋겠습니다.

  1. E는 단순히 컴파일 되기 전 타입을 선언하여 해당 타입의 안정성을 보장하기 위해 E가 String으로 선언된 상황이면 String전용 Node로 만들기 위해 들어간거고 이 영향은 컴파일단계에서만 미치며 컴파일 이후에는 전부 상한의 클래스로 대체된다. 하지만 상한 클래스로 예를들어 Object라고 대체되어 어떤 메서드의 return 값이 Object로 변하는 경우라도 컴파일러가 알맞게 이전의 E타입으로 다운캐스팅 해주니 문제 없는 것이다.

  2. 하지만 인스턴스 E를 "생성"하는 것은 문제가 된다. 단순히 타입을 선언하는게 아니고 인스턴스 E를 생성해서 뭔갈 한다면 예를 들어 상한이 Object이고 Object의 이름모를 하위타입의 인스턴스를 생성하면 해당 타입의 메서드, 멤버변수를 전부 활용할 수도 있다는 것인데, E가 정확히 무엇일지 몰라서 컴파일러가 상한 타입으로 퉁친 상태인데, 상한 타입보다 하위의 타입의 인스턴스 메서드를 쓸 가능성은 당연히 없애는 것이 맞기 때문에 new E();도 못쓰는 것이고 E인스턴스의 메서드, 멤버 변수 정보도 당연히 모르니까 instanceOf도 못 쓰는 것이다.

라고 결론을 내렸는데 괜찮을까요?

답변 2

2

박준형님의 프로필 이미지
박준형
질문자

정적 중첩 클래스는 정적변수처럼 모든 인스턴스가 같은 참조 값을 공유하는 형태는 아니라서 그런 것인가요?? 아예 다른 기능이라고 생각하는게 맞을까요?

예. 주소 값 을 공유하지 않아서, 독립적인 객체입니다. new로 만들어진 객체는 새로운 주소 값을 가지고 있는것을 생각해보시면됩니다.

 

같은 기능이죠.

String aa = "a1"; String bb = "b1";

aa.concat("- a2");

bb.concat("- b2");

이렇게 concat() 를 쓰면 같은 기능을 하듯이 말입니다.
저가 틀렸다면 아마도 다른 분들이 답변을 해주실거에요.

박준형님의 프로필 이미지
박준형
질문자

아 그렇네요 new라는 생성자가 어찌됐든 새로운 객체를 생성하는 키워드니까 내부에 new로 새로운 정적 중첩 인스턴스를 만드는 방식이군요. 같이 고민해주셔서 감사합니다!

2

값이 공유 되지 않습니다. System.out.println(intList + " = " + stringList); 의 코드를 보시면 중간에 문자열 "=" 이 있습니다.

즉 '내용 + 문자열 : 문자열' 로 바뀐다는 사실을 생각해보시면 이해가 되실 거 에요.

 

2번째 내용은 타입 이레이저 를 복습하시면 알게 되실 거에요.

박준형님의 프로필 이미지
박준형

작성한 질문수

질문하기