[5/8] Java 기본 Summary (w. 인프런 김영한)

차재현·2025년 1월 3일
0

[ Backend Study - Java ]

목록 보기
6/11
post-thumbnail

Section 8. final

Chapter 1. final 변수와 상수 1

  • final은 “한번 초기화 하면 더이상 변경이 불가능 하다”라는 뜻이다.
  • final은 자료형 앞에 작성한다. (static이 있다해도 자료형 앞에 작성한다)
  • 지역 변수에서의 final 변수는 선언+초기화 or 선언 후, 초기화 모두 가능하다
    public static void main(String[] args){
    	final int a;
    	a = 10; // 가능
     	
    	final int b = 10; // 가능
    }
  • 멤버 변수에서의 final 변수는 생성자로만 초기화 가능하다.
    package final1;
    
    //final 필드 - 생성자 초기화
    public class ConstructInit {
    	
    	final int value;
    	final int CONST_VALUE = 10;
    	
    	public ConstructInit(int value) {
    		this.value = value;
    		// this.CONST_VALUE = value;	// 컴파일 에러
    	}
    }
    • 만약 멤버 변수에서의 final 변수를 선언과 동시에 초기화했다면, 생성자로 초기화 할 수 없다.

💡 여기서 한번 생각해보자

  • 초기화 되지 않은 final 변수의 경우, 매개변수에 들어오는 값에 따라 다른 값이 저장될 수 있다.
    변수로서 아직은 가치가 있다는 것이다.
  • 하지만 위 코드의 CONST_VALUE 처럼 선언과 동시에 초기화했다면?
    CONST_VALUE 가 있는 클래스로 객체를 만든다면 몇 개를 만들던 CONST_VALUE 는 같은 값이 된다.
  • 즉, 객체를 만들때마다 항상 같은 값인 변수로 메모리가 할당되고 이것은 메모리 낭비로 이어진다.
  • 이때, 우리는 이전에 배운 static을 떠올릴 수 있다.
    [모든 객체에서 같은 값으로써 사용되는 변수] == [모든 객체에서 “공유”하는 변수] 로 볼 수 있다.
    단지 final로 인해 두번 다시 값이 변경되지 않는 다는 특성을 가진 공유변수인 셈이다.
  • 그래서 static으로 선언한다면 메서드 영역의 static 영역에서 딱 한개로 생성되어 메모리 낭비를 막으며 기존의 선언과 동시에 초기화한 final 변수와 같은 역할을 할 수 있다.

Java에서 fianl인데 선언과 동시에 초기화를 해야 한다면 다음을 따른다.
1. static final로 사용
2. 변수명을 모두 대문자로 작성

Chapter 2. final 변수와 상수 2

  • 상수 : 변하지 않고 항상 일정한 값을 갖는 수.
    • 그래서 앞서 배운 static final 을 상수를 선언할 때 사용한다.
  • 상수의 변수명은 항상 대문자로, 단어구분(띄워쓰기)는 언더바(_)를 사용한다.
  • static 변수이기에 클래스를 통해 접근하며, import static ~ 을 사용할 수 도 있다.
  • 상수의 특징
    • 런타임 도중에는 값을 변경할 수 없기에, 상수를 변경하기 위해선 프로그램을 종료하고 상수 수정 후,
      프로그램으르 다시 시작해야 한다.
    • 보통 여러곳에서 사용되기에 public을 사용하며, 중앙에서 값을 관리할 수 있다.
    • 매직 넘버 문제 해결
      • 만약 상수가 아닌 일반 정수를 사용했다면 다른 개발자는 이 정수가 의미하는 바를 알기 어렵다.
      • 하지만 변수명으로 의미를 나타낸다면 다른 개발자들도 코드를 이해하는데 큰 도움이 된다.

Chapter 3. final 변수와 참조

  • 참조형 변수에도 final을 사용할 수 있다.
  • 이때는 변수가 참조하는 참조값을 변경하지 못하는 것이지,
    참조되는 대상의 멤버변수는 수정할 수 있다.(물론 멤버변수도 final이라면 변경할 수 없다)
  • 예시 코드
    package final1;
    
    public class Data {
        public int value;
    }
    package final1;
    
    public class FinalRefMain {
        public static void main(String[] args) {
            final Data data = new Data();
            //data = new Data(); //참조값 변경 불가능
    
            //참조 대상의 값은 변경 가능
            data.value = 10;
            System.out.println(data.value);
            data.value = 20;
            System.out.println(data.value);
        }
    }

Section 9. 상속

Chapter 1. 상속 관계

  • 용어 정리
    • 부모 클래스(슈퍼 클래스)
    • 자식 클래스(서브 클래스)
  • Java에서는 다중 상속 불가능
    • 자식 클래스는 하나의 부모만 선택할 수 있다.
    • 다이아몬드 문제 : 다수의 부모와 상속관계인 경우, 메서드명이 같다면 어떤 부모의 메서드를 사용할 것인가? 에 대한 혼동 발생.
  • 예시 코드
    package extends1.ex2;
    
    public class Car {
    
        public void move() {
            System.out.println("차를 이동합니다.");
        }
    }
    package extends1.ex2;
    
    public class ElectricCar extends Car {
    
        public void charge() {
            System.out.println("충전합니다.");
        }
    }
    package extends1.ex2;
    
    public class GasCar extends Car {
    
        public void fillUp() {
            System.out.println("기름을 주유합니다.");
        }
    }
    package extends1.ex2;
    
    public class CarMain {
    
        public static void main(String[] args) {
            ElectricCar electricCar = new ElectricCar();
            electricCar.move();
            electricCar.charge();
    
            GasCar gasCar = new GasCar();
            gasCar.move();
            gasCar.fillUp();
        }
    }
    
    /* 실행 결과
    차를 이동합니다.
    충전합니다.
    차를 이동합니다.
    기름을 주유합니다.
    */
    • 상속을 통해, ElectricCarGasCar는 가지고 있지 않는 move()를 사용할 수 있다.

⭐️ Chater 2. 상속과 메모리 ⭐️

  • 상속 관계의 클래스로 인스턴스 생성 및 호출 과정을 메모리 구조로 확인해보자

  • electricCar 객체에서 electricCar.move()를 호출하는 흐름을 확인해보자
    • ElectricCar electricCar = new ElectricCar();
      1. 힙영역에서 x001 참조값으로 인스턴스 생성
      2. 이때 ElectricCar 클래스와 상속 관계인 Car 클래스가 모두 생성된다.
    • electricCar.move();
      1. 먼저 상속관계인 클래스의 인스턴스의 경우, “호출자”의 자료형 클래스를 먼저 확인한다.
      2. 따라서 우선 힙 영역에서 x001을 참조하여 ElectricCar 클래스를 확인한다.

❓ 이때, 메서드의 정보는 메서드 영역에 저장되어있을 텐데 힙 영역에서 어떻게 확인할 수 있을까? 궁금해서 찾아보았다!
1. 클래스 로딩
- Java 프로그램이 실행될 때, JVM은 필요한 클래스를 메모리에 로드합니다.
이 과정에서 클래스의 정보가 메서드 영역에 저장됩니다.
- 메서드 영역엔 클래스의 이름, 필드, 메서드, 상수 풀, 부모 클래스 정보 등이 포함됩니다.
2. 인스턴스 생성
- 각 객체 인스턴스는 자신의 클래스에 대한 정보를 참조하기 위해 내부적으로 클래스의 메타데이터에 대한 포인터를 가지고 있습니다.
- 이 포인터는 메서드 영역에 있는 클래스 정보를 가리킵니다.
3. 따라서 b단계를 정정하면, electricCar 인스턴스가 가지고 있는 ElectricCar 클래스 정보에 대한 포인터를 통해 메서드 영역에 있는 ElectricCar 클래스의 메서드를 확인한다.
4. 메서드 영역의 ElectricCar 클래스 정보에는 move() 메서드 정보를 확인하지 못했다.
5. “호출자”의 자료형 클래스에 해당 메서드가 없다면, 상속 관계인 부모 클래스의 메서드를 확인한다.
6. 이제 다시 힙 영역에서 x001을 참조하여 인스턴스 생성 시 저장되었던 부모 클래스의 정보에 대한 포인터를 통해 메서드 영역에 있는 Car 클래스의 메서드를 확인한다.
7. 드디어 move() 메서드를 발견했다. move()를 실행한다.
8. 위 과정은 메서드가 아닌 변수여도 동일하다!

Chapter 3. 상속과 기능 추가

  • 상속 관계 덕분에 코드 중복은 줄어들고, 부모 클래스를 기준으로 새로운 기능이 추가된 추가 클래스를 쉽게 만들 수 있다. == 클래스 확장(extend)에 용이

Chapter 4. 상속과 오버라이딩

  • 오버라이딩이란?
    • 부모 클래스의 인스턴스 메서드를 자식 클래스에서 재정의하는 것
  • 오버라이딩과 클래스

  • 사실상 ElectricCar 클래스에도 move() 메서드가 생긴 것이다.
    • 위에서 다룬 클래스 내부 메서드 호출 과정에서
      • “호출자”의 자료형 클래스인 ElectricCar 클래스에서 move()메서드를 먼저 발견한다
      • 이제 부모 클래스인 Car 클래스까지 찾으러 가지않고 발견한 메서드를 바로 실행한다.
  • @Override
    • 자식 클래스에서 오버라이딩할 때, Java에서 해당 오버라이딩에 문제가 없는지 점검해준다.
    • 사용방법
      package extends1.overriding;
      
      public class ElectricCar extends Car {
      
          @Override
          public void move() {
              System.out.println("전기차를 빠르게 이동합니다.");
          }
      
          public void charge() {
              System.out.println("충전합니다.");
          }
      }
      • 오버라이딩 되는 메서드 바로 위에 @Override를 작성해준다.
    • 어노테이션
      • @이 앞에 붙어있는 키워드
      • 컴퓨터가 읽을 수 있는 주석
  • 오버라이딩 조건
    • 메서드 이름이 같아야 한다.
    • 메서드 매개변수가 같아야 한다. (순서, 개수도!)
    • 반환 타입이 같아야 한다.
    • 접근 제어자
      • 오버라이딩 메서드의 접근 제어자는 부모 클래스의 메서드보다 더 제한적이어서는 안된다
    • 예외
      • 오버라이딩 메서드는 부모 클래스의 메서드보다 더 많은 체크 예외를 throws 로 선언할 수 없다.
    • 생성자는 오버라이딩 불가능!
    • static, final, private 키워드 붙은 메서드는 오버라이딩 불가능!
      • static
        • 오버라이딩은 각 자식 클래스마다 재정의한 메서드를 호출하여 다른 기능을 사용하기 위한 목적이지만, static 메서드는 오버라이딩해도 클래스를 통해 호출하기에 인스턴스별로 다른 기능으로서 사용할 수 없다.
        • static 메서드를 클래스에서 재정의했다면, 이것은 오버라이딩이 아니라 메서드 숨김이라고 한다.(그냥 새로운 static 메서드를 만든 것일 뿐이다!)
      • final
        • final 메서드는 재정의를 금지한다
      • private
        • private 메서드는 해당 클래스에서만 접근 가능하기에 자식 클래스에서는 확인할 수 조차 없다.

Chapter 5. 상속과 접근 제어

  • 상속 관계에서 자식 클래스가 부모 클래스의 멤버 변수나 메서드를 사용할 때,
    클래스의 패키지 위치에 따라 접근제어자 영향을 그대로 받는다

  • 실제로 부모 클래스와 자식 클래스의 패키지를 다르게 하여 확인해보자!

  • 예시 코드

    Parent와 Child 클래스의 패키지 위치가 다름을 유의하자

     package extends1.access.parent; // Child 클래스와 패키지가 다름을 유의하자
     
     public class Parent {
     
         public int publicValue;
         protected int protectedValue;
         int defaultValue;
         private int privateValue;
     
         public void publicMethod() {
             System.out.println("Parent.publicMethod");
         }
         protected void protectedMethod() {
             System.out.println("Parent.protectedMethod");
         }
         void defaultMethod() {
             System.out.println("Parent.defaultMethod");
         }
         private void privateMethod() {
             System.out.println("Parent.privateMethod");
         }
     
         public void printParent() {
             System.out.println("==Parent 메서드 안==");
             System.out.println("publicValue = " + publicValue);
             System.out.println("protectedValue = " + protectedValue);
             System.out.println("defaultValue = " + defaultValue);
             System.out.println("privateValue = " + privateValue);
     
             //부모 메서드 안에서 모두 접근 가능
             defaultMethod();
             privateMethod();
         }
     
     }
    package extends1.access.child;
    
    import extends1.access.parent.Parent;
    
    public class Child extends Parent {
    
        public void call() {
            publicValue = 1;
            protectedValue = 1; //상속 관계 or 같은 패키지
            //defaultValue = 1; //다른 패키지 접근 불가, 컴파일 오류
            //privateValue = 1; //접근 불가, 컴파일 오류
    
            publicMethod();
            protectedMethod(); //상속 관계 or 같은 패키지
            //defaultMethod(); //다른 패키지 접근 불가, 컴파일 오류
            //privateMethod(); //접근 불가, 컴파일 오류
    
            printParent();
        }
    }
  • Parent 클래스와 Child 클래스를 다른 패키지에서 정의한다.
  • Child 클래스는 Parent 클래스로부터 상속받는다.
  • Parent 클래스의 멤버 변수/메서드는 접근제어자의 영향을 받는다.
    (기존에 알고 있던 개념이 그대로 적용된다)
  • 이떄, protected 접근제어자의 경우 특징이 있다.
    • 원래 protected는 패키지가 다르면 접근할 수 없게한다
    • 하지만 상속관계의 경우, 무조건 접근이 가능하다!

💡 상속관계에서 부모 클래스의 멤버 변수로 접근하는 경우에도 위의 메서드에 접근하는 과정과 같다!
“호출자”의 자료형 클래스에 찾는 변수가 없다면 다음으로 부모 클래스에서 찾는다.

Chapter 6. super - 부모 참조

  • 자식 클래스에서 변수/메서드를 super를 통해서 접근하게되면, 바로 부모 클래스의 변수/메서드로 접근한다.
  • 기존에는 자식클래스에서 부모 클래스와 같은 이름의 변수나 메서드를 선언하면 부모 클래스의 변수/메서드를
    사용할 수 없었다
  • 자식 클래스에서 먼저 찾고 바로 실행하기 때문이다.
  • 예시 코드
    package extends1.super1;
    
    public class Parent {
    
        public String value = "parent";
    
        public void hello() {
            System.out.println("Parent.hello");
        }
    }
    package extends1.super1;
    
    public class Child extends Parent {
    
        public String value = "child";
    
        @Override
        public void hello() {
            System.out.println("Child.hello");
        }
    
        public void call() {
            System.out.println("this value = " + this.value); //this 생략 가능
            System.out.println("super value = " + super.value);
    
            this.hello(); //this 생략 가능
            super.hello();
        }
    }
    
    /* call() 메서드 실행 결과
    this value = child
    super value = parent
    Child.hello
    Parent.hello
    */
    • 위 처럼 super.value 또는 super.hello() 처럼 super 키워드를 통해 변수/메서드를 접근하면
      기존과 같이 ”호출자”의 자료형 클래스로 먼저 접근하는것이 아니라 부모 클래스인 Parent 클래스의 value 변수, hello() 메서드로 바로 접근한다.
    • 즉, [super == 부모 클래스]인 셈이다.
      • super.hello() == Parent.hello()
      • super.value == Parent.value

Chapter 7. super - 생성자

  • 상속 관계의 경우, 자식 클래스의 인스턴스를 생성하면 힙 영역에서 자식 클래스 인스턴스 내부에 부모 클래스에 대한 인스턴스도 생성됨을 알게 되었다.
  • 그렇다면 부모 클래스에 대한 초기화도 필요하다는 말이다. 그래서 자식 클래스의 생성자는 맨 첫줄에서 무조건 부모 클래스 생성자를 호출해야 한다.
  • 부모 클래스의 생성자는 super()로 사용한다.
    • 만약 부모 클래스의 생성자가 기본 생성자라면 super()를 생략할 수 있다.
    • 만약 부모 클래스 생성자에 매개변수가 있다면 super(매개변수)를 넣어주면 된다.

💡 우리는 생성자 Section에서 this()를 배웠다. 자신의 다른 생성자를 호출하는 메서드이다.
this() 메서드는 생성자의 첫번째 줄에만 위치할 수 있다. 그렇다면 super()와 겹치지 않겠는가?
아래 예시 코드로 알아보자

package extends1.super2;
public class ClassA {
    public ClassA() { //기본 생성자
        System.out.println("ClassA 생성자");
    }
}
package extends1.super2;
public class ClassB extends ClassA {
    public ClassB(int a) {
        this(a, 0);
        System.out.println("ClassB 생성자 a=" + a);
    }
    public ClassB(int a, int b) {
        super(); //ClassA의 생성자가 기본 생성자이기에 생략 가능
        System.out.println("ClassB 생성자 a=" + a + " b=" + b);
    }
}
package extends1.super2;
public class ClassC extends ClassB {
    public ClassC() {
        super(10);
        System.out.println("ClassC 생성자");
    }
}
package extends1.super2;
public class Super2Main {
    public static void main(String[] args) {
        ClassC classC = new ClassC();
    }
}
/* 실행 결과
ClassA 생성자
ClassB 생성자 a=10 b=0
ClassB 생성자 a=10
ClassC 생성자
*/
  • 위 코드를 보면, 자식 클래스인 ClassB에서 매개변수가 하나인 생성자에서는 super()가 아닌 this()를 사용하고 있다. 그리고 this()를 통해 매개변수가 2개인 생성자를 호출하게되면 그 안에 super()가 있음을 알 수 있다.
  • 정리하면, this()를 통해 다른 생성자를 호출하는것은 가능하다! 다만, 마지막에 호출되는 생성자에서는 맨 첫줄에 super()를 무조건 사용하여 부모 클래스의 생성자를 호출해야한다!
  • 추가로, 위의 실행결과를 보면 자식 클래스 인스턴스를 생성하게되면, 부모 생성자부터 실행됨이 보인다.
    모든 자식 클래스 생성자의 맨 첫줄은 결과적으로 super()를 호출하므로
    무조건 최상단 부모 클래스부터 생성하게 되는 것이다.
    - 이러한 것은 부모의 것을 상속받는 자식이라는 관점에서, 부모가 초기화된 후에 그 정보를 물려받는다고 생각하면 얼추 올바른 모습임을 알 수 있다.

➕ 상속과 final ➕

  • Class 앞에 final을 붙여주면 해당 클래스는 상속할 수 없다!
    public final Class Parent{
    ...
    }
    
    // 위 클래스를 상속받을 수 없다! 컴파일 에러 발생
  • 부모 클래스 메서드의 자료형 앞에 final을 붙여주면 해당 메서드는 오버라이드 될 수 없다!
    public Class Parent{
    
      public final void print(){
    	  System.out.println("I am Parent Class");
      }
    }
    
    // 위의 print 메서드는 오버라이디될 수 없다! 컴파일 에러 발생
profile
Develop what? and why?

0개의 댓글