Study/JAVA

[JAVA] 11장 - 자바 제어자 2

오구민 2022. 10. 5. 19:17

이 스터디는 책 [Do it! 자바 완전 정복]을 공부하고 있습니다.

책 한 권을 꾸준하게 노력해서 끝내는 것을 목표로 매일매일 공부한 내용을 요약 중입니다...

왜인지 조회수가 조금씩 생기고 있는데, 만약 자바 공부를 하고 싶다면 제 글보단 책 자체를 사서 읽는 걸 추천 드립니다.

노파심에 글 업로드 전에 덧붙입니다...

 

 

1. final 제어자

final 제어자는 필드, 지역 변수, 메서드, 클래스 앞에 위치할 수 있으며, 어디에 위치하느냐에 따라 의미가 다르다.

 

1.1 final 변수

final 제어자는 변수를 선언할 때만 지정할 수 있으며, final 변수는 한 번 대입된 값을 수정할 수 없다.

즉, 한 번 대입된 값이 최종(final) 값이 되는 것이다.

 

  1.1.1 주의할 점

  1. 선언과 값의 대입을 분리했을 떄는 적어도 생성자에서는 값을 대입해야 한다. final 필드는 일반 필드와 달리 강제 초기화가 되지 않기 때문이다.
  2. final 제어자를 사용한 필드나 지역 변수에 값이 대입된 후 값을 입력하는 행위 자체를 할 수 없다. 동일한 값이어도 오류가 발생하는 것이다.

 

  1.1.2 메모리 구조

  • 객체를 생성했을 때, 필드는 멤버이므로 final 필드든 아니든 객체 속에 포함된다.
  • 객체가 만들어질 때 final로 선언된 필드값은 상수(final) 영역에 1개가 복사된다. 즉, final로 선언된 모든 필드값이 상수 영역에 복사되므로, 첫 번째 메모리 영역을 상수 영역으로 부르는 것이다.
  • 값의 복사는 값을 선언한 후 최초로 값이 초기화될 때 딱 한 번 일어난다. 즉, 원본의 복사는 값을 대입할 때 딱 한 번 일어나는 것이다.

 

  1.1.3 final은 이때 많이 사용한다.

  • 이벤트를 처리할 때 지역 변수를 final로 선언해야 하는 경우가 자주 있다고 한다. 스택 메모리의 변숫값은 자신이 만들어진 메서드가 종료되면 메모리에서 사라진다.
  • 하지만 이벤트를 처리할 때 메모리에서 사라진 그 변수를 나중에 사용해야 할 때가 있는데, 이를 위해 한 번 생성하면 사라지지 않는 영역인 상수 영역에 복사해두는 것이다.
  • 이벤트 처리는 몰라도 '어떤 필요에 따라 복사본을 하나 만들어 놓음으로써 원본이 삭제된 이후에도 그 값을 활용할 수 있도록 하는 것이 final 변수(필드, 지역 변수)의 기능이다.'로 생각하면 된다.

 

  1.1.4 예제

package java_study_001;

class A1 {
	int a = 3;
	final int b = 5;
	A1() {}
}
class A2 {
	int a;
	final int b;
	A2() {
		a = 3;
		b = 5;
	}
}
class A3 {
	int a = 3;
	final int b = 5;
	A3() {
		a = 3;
		// b = 5; -> final 필드는 최초 선언된 이후로 값을 대입할 수 없음
	}
}
class B {
	void bcd() {
		int a = 3;
		final int b = 5;
		a = 7;
		//b = 9; -> final 지역 변수도 최초 선언된 이후로 값을 대입할 수 없음
	}
}
public class Study_001 {
	public static void main(String[] args) {
		//객체 생성
		A1 a1 = new A1();
		A2 a2 = new A2();
		
		//필드값 변경
		a1.a = 7;
		//a1.b = 9; -> final 필드는 한 번 정해진 값을 변경할 수 없음
		a2.a = 7;
		//a2.b = 9; -> final 필드는 한 번 정해진 값을 변경할 수 없음
	}
}

 

1.2 final 메서드와 final 클래스

final 변수는 변수가 저장한 값이 최종 값을 의미하는 것처럼, final 메서드와 final 클래스도 각각 최종 메서드, 최종 클래스의 의미를 지닌다.

 

  1.2.1 최종 메서드와 최종 클래스

  • 상속할 때 부모의 메서드를 오버라이딩하면 자식 클래스에서는 메서드의 기능이 변경된다.
  • final 메서드를 사용하면 자식 클래스에서 해당 메서드를 오버라이딩(메서드의 기능을 변경)할 수 없다.
  • 이러한 개념으로 final 클래스 역시 최종 클래스의 의미로 더 이상 자식 클래스가 없다는 의미가 되어 상속 자체가 불가능하다.

 

  1.2.2 예제

package java_study_001;

class A {
	void abc() {}
	final void bcd() {}
}
class B extends A {
	void abc() {}
	// void bdc() {} -> final 메서드는 오버라이딩 불가능
}
final class C {}
// class D extends C {} -> final 클래스는 상속 자체가 불가능
public class Study_001 {
	public static void main(String[] args) {
		
	}
}

 

1.3 정리

final 변수 final 메서드 final 클래스
값 변경 불가능 오버라이딩 불가능 상속 불가능

 

 

 

2. abstract 제어자

abstract의 사전적 의미는 '추상적인'이다.

'추상적'은 다른 의미로 '구체적이지 않다.'는 뜻이며, abstract가 붙은 클래스는 추상 클래스, 메서드는 추상 메서드라고 한다.

 

2.1 추상 메서드

추상 메서드는 아직 무슨 기능을 정의할지 정해지지 않은 미완성 메서드라고 생각하자.

이는 중괄호가 없는 메서드로, 메서드의 기능 자체가 정의되지 않았으며, 세미콜론으로 끝난다.

abstract 리턴 타입 메서드명 ();

 

  2.1.1 예제 : 일반 클래스를 상속해 오버라이딩 수행

  • 아래의 예제에서 Animals 클래스의 cry() 메서드가 아무런 기능을 수행하지 않는다면, 즉 중괄호 안을 비워둘 것이라면, 중괄호 자체가 없는 미완성 메서드인 추상 메서드로 정의하는 것이 효율적일 것이다.
package java_study_001;

class Animal {
	void cry() {}
}
class Cat extends Animal {
	void cry() {
		System.out.println("야옹");
	}
}
class Dog extends Animal {
	void cry() {
		System.out.println("멍멍");
	}
}
final class C {}
// class D extends C {} -> final 클래스는 상속 자체가 불가능
public class Study_001 {
	public static void main(String[] args) {
		//객체 생성
		Animal animals1 = new Cat();
		Animal animals2 = new Dog();
		
		//메서드 호출
		animals1.cry();
		animals2.cry();
	}
}

 

2.2 추상 클래스

추상 클래스도 클래스이므로 당연히 상속을 할 수 있다.

하나 주의할 점은 추상 메서드를 1개 이상 포함하고 있는 클래스는 반드시 추상 클래스로 정의해야 한다는 점이다.

 

  2.2.1 예제 : 추상 클래스를 상속해 오버라이딩 수행

package java_study_001;

abstract class Animal {
	//추상 메서드를 한 개라도 품고 있기 때문에 추상 클래스로 정의한다.
	abstract void cry();
}
class Cat extends Animal {
	void cry() {
		System.out.println("야옹");
	}
}
class Dog extends Animal {
	void cry() {
		System.out.println("멍멍");
	}
}
final class C {}
// class D extends C {} -> final 클래스는 상속 자체가 불가능
public class Study_001 {
	public static void main(String[] args) {
		//객체 생성
		Animal animals1 = new Cat();
		Animal animals2 = new Dog();
		
		//메서드 호출
		animals1.cry();
		animals2.cry();
	}
}

 

2.3 abstract 제어자의 장점

일반 클래스나 메서드와 달리, 클래스 내부에 추상 메서드가 1개라도 있다면, 해당 클래스는 추상 메서드를 일반 메서드로 오버라이딩하거나 자신을 추상 클래스로 정의해야 한다.

만약 상속된 메서드에 오타가 났다고 가정하자. 일반 클래스를 상속하는 일반 클래스에서는 오버라이딩이 아닌 새로운 메서드를 정의한 것이 된다. 추상 클래스는 다르다. 추상 클래스를 상속하는 일반 클래스가 되기 때문에, 추상 메서드를 오버라이딩하지도 않고 자신을 추상 클래스로 정의한 것도 아니어서 문법 오류가 발생한다.

정리하자면, 추상 메서드를 포함하고 있는 추상 클래스가 있을 때 '이를 상속한 모든 자식 클래스 내부에는 항상 해당 메서드가 정의돼 있다.'가 보장되는 것이다.

이는 추상 메서드의 여러 가지 장점 중 하나로, 12장에서 더 자세하게 알아본다고 한다.