[Java] 17. 상속과 final

Java / / 2019. 4. 8. 16:29

1. 상속과 관련된 예약어 'final'

final은 클래스, 메소드, 변수에 선언할 수 있습니다. final이라는 단어의 뜻은 "마지막" 이라는 뜻입니다. 이 예약어를 클래스, 메소드, 변수가 어떻게 달라지는지 각각의 상황에 대해서 알아봅시다.

2. 클래스에 final을 선언할 때

클래스 선언시 final을 접근 제어자와 class예약어 사이에 추가할 수 있습니다. 

package c.impl;

public final class FinalClass {

}

이 FinalClass의 객체를 생성하고, 선언된 메소드를 사용하는 것은 전혀 상관 없습니다. 하지만, 클래스가 final로 선언되어 있으면 상속을 해 줄 수 없습니다. extends 키워드로 이 클래스를 상속 받고 컴파일을 해보면 상속을 받을 수 없다는 에러메시지를 볼 수 있습니다. 

 

이와 같이 클래스에 final을 선언하는 이유는 더 이상 확장해서는 안되는 클래스 일 때 사용합니다. 누군가 이 클래스를 상속 받아서 내용을 변경해서는 안돼는 클래스를 선언할 때 final로 선언하면 됩니다.

 

지금까지 많이 본 String 클래스는 자바에서는 아주 중요한 클래스이며, 이 클래스를 조금이라도 변경해서 무슨 작업을 하면 안됩니다. 만약 String 클래스를 상속 받아서 toString() 메소드에서 무조건 1을 리턴하게 한다면 String이라는 클래스에 대한 기본 속성을 변경하는 것입니다. 

 

3. 메소드를 final로 선언할 때

메소드의 경우도 클래스에 final로 선언하는 경우와 비슷합니다. 메소드를 final로 선언하면 더 이상 Overriding 할 수 없습니다. 앞서 사용했던 MemberManager Abstract 클래스의 printLog() 메소드를 다음과 같이 final로 선언합시다.

package c.impl;

import c.inheritance.MemberDTO;

public abstract class MemberManagerAbstract {

    public abstract boolean addMember(MemberDTO member);

    public abstract boolean removeMember(String name, String phone);

    public abstract boolean updateMember(MemberDTO member);

    public final void printLog(String data) {

        System.out.println("Data="+data);

    }

}

그 다음에 MemberManager2 클래스에서 이 printLog() 메소드를 Overriding하고 컴파일 해봅시다. 에러 메시지를 보면 printLog() 메소드는 final이기 때문에 overriding 할 수 없다라고 되어 있습니다. 여러분들이 만든 메소드를 누가 변경하지 못하게 하려면 final로 만들어 주면 됩니다 하지만 final 클래스는 많이 사용하지만, final 메소드는 그리 많이 사용하지 않습니다.

 

4. 변수에서 final을 쓰는 것과 메소드나 클래스에서 쓰는 것과 비교

변수에 final을 사용하는 것은 개념이 조금 다릅니다. 변수에 final을 사용하면 그 변수는 더이상 바꿀 수 없습니다. 그래서, 인스턴스 변수나 static으로 선언된 클래스 변수는 선언과 함께 값을 지정해야 합니다. 다음과 같이 FinalVariable이라는 클래스를 만들어봅시다.

package c.impl;

public class FinalVariable {

    final int instanceVariable;

}

이렇게 인스턴스 변수에 final을 선언 한 후 컴파일을 하면 에러메시지를 볼 수 있습니다. final로 선언되어 있기 때문에 변수 생성과 동시에 초기화를 해야만 컴파일 시 에러가 발생하지 않습니다. 생성자나 메소드에서 초기화하면 되지 않나라고 생각할 수 있지만, 그러면 중복되어 변수값이 선언될 수 있기 때문에 final의 기본 의도를 벗어납니다.

package c.impl;

public class FinalVariable {

    final int instanceVariable = 1;

}

그렇다면, 매개 변수나 지역변수는 어떨까요? 다음의 메소드를 클래스에 추가합시다.

package c.impl;

public class FinalVariable {

    final int instanceVariable = 1;

    public void method(final int parameter) {

        final int localVariable;

    }

}

컴파일을 해보면 문제가 없음을 확인할 수 있습니다. 매개 변수나 지역변수를 final로 선언하는 경우에는 반드시 선언할 때 초기화할 필요는 없습니다. 왜냐하면, 매개 변수는 이미 초기화가 되어서 넘어왔고, 지역변수는 메소드를 선언하는 중괄호 내에서만 참조되므로 다른 곳에서 변경할 일이 없습니다. 하지만 다음과 같이 사용해서는 안됩니다.

package c.impl;

public class FinalVariable {

    final int instanceVariable = 1;

    public void method(final int parameter) {

        final int localVariable;

        localVariable=2;

        localVariable=3;

        parameter=4;

    }

}

localVariable을 이미 2로 선언했는데 3으로 변경하거나, 매개 변수로 넘어온 parameter의 값을 4로 변경하는 행위는 하면 안됩니다. 

 

5. final과 참조 자료형

지금까지는 기본 자료형에 대한 final 선언만 알아보았습니다. 그렇다면 참조 자료형도 동일하게 적용될까요? 다음과 같이 이 절의 앞부분에서 만든 FinalClass에 내용을 추가합시다.

package c.impl;

import c.inheritance.MemberDTO;

public final class FinalClass {

    final MemberDTO dto = new MemberDTO();

    public static void main(String[] args) {

        FinalClass cls = new FinalClass();

    }

    public void checkDTO() {

        System.out.println(dto);

        dto = new MemberDTO();

    }

}

이 FinalClass를 저장 후 컴파일해 봅시다. dto가 final이기 때문에 값을 할당할 수 없다는 메시지와 함께 에러가 발생합니다. 기본 자료형과 마찬가지로 참조 자료형도 두번 이상 값을 할당하거나 새로 생성자를 사용하여 초기화할 수 없습니다. 

package c.impl;

import c.inheritance.MemberDTO;

public final class FinalClass {

    final MemberDTO dto = new MemberDTO();

    public static void main(String[] args) {

        FinalClass cls = new FinalClass();

        cls.checkDTO();

    }

    public void checkDTO() {

        System.out.println(dto);

        //dto = new MemberDTO();

        dto.name="SangMin";

        System.out.println(dto);

    }

}

dto의 name 값을 변경하고 그 결과를 출력하도록 했습니다. 문제가 전혀 없고 결과도 다음과 같이 name값이 할당되었음을 볼 수 있습니다. 

 

실행 결과

도대체 final로 선언한 객체인데도 왜 이렇게 사용할 수 있을까요? dto 객체, 즉 MemberDTO 클래스의 객체는 FinalClass에서 두번 이상 생성할 수 없습니다. 하지만, 그 객체의 안에 있는 객체들은 그러한 제약이 없습니다. 인스턴스 변수들이 final로 선언되지 않았기 때문입니다. 해당 클래스가 final이라고 해서, 그 안에 있는 인스턴스 변수나 클래스 변수가 final은 아니라는 부분은 꼭 기억해 둡시다.

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기