..
2024.01.07 - [java] Java 의

참고로, 인텔리제이에서는 final 클래스의 경우 "압정표시"로 상속할 수 없다는 것을 표시해준다.
## 메서드(method)
final 이 붙은 메서드는 오버라이딩 할 수 없다.
부모 클래스에 해당하는 Cat 클래스는 `public`, `private`, `final` 메서드로 각기 다른 "야옹~"을 출력하도록 했다.
자식 클래스에 해당하는 WhiteCat 가 오버라이딩 할 수 있는 메서드는 `public` 메서드 뿐이다.
이렇게 `public` 메서드의 경우 Cat 을 상속하여 오버라이딩을 통해 해당 `meow()` 메서드를 사용할 수 있다.
```java
// 부모 클래스
public class Cat {
private int weight;
public void meow() {
System.out.println("누구나 야옹~");
}
final public void finalMeow() {
System.out.println("나만 야옹~");
}
private void privateMeow() {
System.out.println("내부 야옹~");
}
}
// 자식 클래스
public class WhiteCat extends Cat{
@Override
public void meow() {
System.out.println("흰 고양이 야옹");
}
}
```
만약 자식클래스에서 부모의 final 메서드를 재정의 하려고 시도하면 어떻게 될까?
`finalMeow()' cannot override 'finalMeow()' in 'Cat'; overridden method is final` 
오버라이드한 메서드가 final 이기 때문에 오버라이드를 할 수 없다고 에러 메시지를 띄우고 있다.
## 변수(varibles)
### 1. 원시변수(Primitive Varibles)
final 로 선언한 원시변수에 값을 할당한 후에는, 다른 값을 할당할 수 없다.
```java
final int i = 1;
int i = 2;
```
i 변수에 1을 할당한 후, 2를 재할당하려고 하면 아래와 같은 에러를 뿜어낸다.
이미 위에서 정의되었기 때문에 다시 재할당할 수 없다.!
---
### 2. 참조변수(Reference Varibles)
```java
final User user = new User("jimin");
user = new User("junguk");
```

원시변수의 경우와 마찬가지로 final 로 선언한 참조변수의 경우 역시 다른 참조변수의 할당이 불가능하다.
실수로 다른 변수 값으로 바꿔치기 되는 대참사를 막을 수 있다.
참고로 user 객체는 불변은 아니다. 즉, 재할당이 불가능한 거지 객체의 내부 변수 값은 바꿀 수 있다.
만약 final class 를 이용하여 객체를 생성할 때 해당 변수에 final을 선언하면 어떻게 될까?
위의 내용을 다시 정리하자면,
1. class 의 final 은 상속이 불가하다라는 것을 의미한다.
2. 참조변수의 final은 초기화 후 재할당 할 수 없는 것을 의미한다.
다시 한번 말하지만 해당 클래스 자체를 완전한 불변(immutable)으로 만드는 것은 아니다.
따라서 아래의 생성된 객체 내부의 변수(`a`)를 수정하는 건 가능하다.
```
final class XXXclass{
private int a = 5;
}
final XXXClass xxxClass = new XXXClass();
xxxClass.a = 10; // 수정이 가능하다!!
```
---
### 3. 필드(Field)
constant 상수 필드에 사용하거나, 생성자 맴버 변수에 final 을 사용할 수 있다.
이 경우 생성자가 완료되기 전에 모든 final 필드를 초기화해야 한다.(즉, 값을 할당해야한다)
```java
class Point{
private static final GLOBAL_POINT = "10.0;
public changePointToTenDotOne(){
this.GLOBAL_POINT = 10.1; // 이미 위에서 할당했기 떄문에 에러 발생!!
}
}
```
위의 `GLOBAL_POINT` constant 변수는 final 키워드로 인해 재할당이 불가능한 변수가 되었다.
`Point.GLOBAL_POINT = "10.1"` 로 값을 바꾸려고 시도한다면 컴파일 에러가 난다.

아래의 코드는 OrderService 가 ProductRepository 를 의존하고 있으며, 생성 시점에 ProductRepository 를 생성자 파라미터로 받아 초기화한다.
그리고 `void order(int id)` 의 경우 제품의 번호를 받아 productRepository 로 부터 상품을 조회하도록 한다.
```java
public class OrderService {
private ProductRepository productRepository;
public OrderService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
public void order(int id) {
Product product = productRepository.findId(id);
// etc
}
}
```
따라서 productRepository 는 null 이 되어선 안되고 반드시 초기화
`this.productRepostiroy = productRepostirot` 가 되어야한다.
만약 실수로 OrderService 생성 시점에 맴버변수 ProductRepository 를 초기화하는 코드를 깜빡 잊었다고 가정해보자. 아래와 같이 작성한다고 해서 컴파일 에러는 나지 않는다.
```java
public class OrderService {
private ProductRepository productRepository;
public OrderService() {
}
public void order(int id) {
Product product = productRepository.findId(id);
// etc
}
}
```
하지만 아래의 코드를 실행하면
```java
OrderService orderService = new OrderService();
orderService().order(1);
```
초기화 되지 않은 ProductRepository 를 호출하려고 했기 때문에 `NullPointException` 예외가 터진다.
```
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "variablesfinal.field.ProductRepository.findId(int)" because "this.productRepository" is null
at variablesfinal.field.OrderService.order(OrderService.java:10)
at variablesfinal.field.OrderMain.main(OrderMain.java:7)
```
이때 final 키워드를 맴버 변수에 사용한다면 클래스 생성 시점에 강제로 final 키워드가 붙은 맴버변수를 초기화 할 수 있도록 강제할 수 있다. 따라서 최소한 맴버 변수 초기화 하는 것을 깜빡해 `NullPointException` 에러가 나는 것은 막을 수 있다.

변수 'productRepository'가 초기화되지 않았을 수 있다고 경고를 보낸다. 따라서 강제로 초기화하여 사용할 수 밖에 없도록 만든다.
```java
private final ProductRepostiroy productRepository;
public OrderService(ProductRepository productRepository){
this.productRepository = productRepository;
}
```
---
### 4. 메서드 인자(Argument Varibles)
메서드의 인자에 final 키워드를 사용할 수 있다.
이 경우 받은 인자를 메서드 내부에서 재할당 하여 사용할 수 없다.
```java
public int plus(final int a, final int b){
int a += b;
return a;
}
```
메서드 인자 중 `a` 는 이미 final로 선언되었기 때문에 `a`에 다른 값을 재할당할 수 없다.
final 메서드 인자에 새로운 값을 할당할 수 없기 때문에 새로운 변수를 선언하여 사용해야한다.
```java
public int plus(final int a, final int b){
int c = a + b;
return c;
}
```
## 요약
java 의 final 키워드는 클래스, 메서드, 메서드의 아규먼트, 필드(consant, member varibles) 에 사용가능하다. final 은 말그대로 최종이라는 뜻으로서 재할당을 막는 데 목표를 두고있다.그렇기 때문에 적절한 final 키워드를 사용해서 재할당을 하지 말도록 하는 의미를 들어내도록 사용할 수 있다.
특히, 생성자 맴버변수에 final을 사용하게 될 경우, 재할당 뿐만 아니라 생성자 초기화 시 무조건 할당해야 하도록 강제하는 역할을 하기도 한다.
### 참조
---
https://www.baeldung.com/java-final
Java 의 final 은 어디에 사용할 수 있을까?
2024.01.07 - [java] Java 의 final
예제에서 사용한 모든 코드는 Github Repository 에 있습니다.
나는 변하지 않는 전역변수를 만들 때나 생성자의 파라미터를 받을 때 final 키워드를 사용했었다. 문득 final 에 대해 이것 말고 더 쓰이는 곳이 없을까? final에 대해 더 찾아보고 공부한 결과를 기록했다.
class A {
// 변하지 않는 전역변수
private static final INIT_VALUE = 0.1;
}
email 과 password 를 생성자 파라미터로 받는 Member 클래스의 모습이다.
// 생성자의 파라미터
public Member(final String email, final String password) {
validateNonNull(email, password);
this.email = email;
this.password = password;
}
우선 final 은 클래스, 메서드, 변수에 사용할 수 있다.
클래스(class)
final 이 있는 클래스는 상속하여 사용할 수 없다.
public final class Cat {
private int weight;
// standard getter and setter
}
public class BlackCat extends Cat {
}
확장을 원하지 않는 경우에 해당 클래스에 final 키워드를 통해 다른 클래스가 상속받아 사용하는 것을 막을 수 있다.
단, 클래스에 final이 있다고 해서 final 클래스로 만든 객체가 불변하다는 것을 뜻하는 것은 아니다. 즉, 내부의 맴버변수는 얼마든지 바꿀 수 있다.
class ClassFinalMainTest {
@Test
@DisplayName("final 클래스의 맴버변수는 바꿀 수 있다.")
void mainTest() {
Moeny moeny = new Moeny();
moeny.setValue(100);
assertEquals(100, moeny.getValue());
assertDoesNotThrow(() -> moeny.setValue(200)); // 예외발생하지 않음.
assertEquals(200, moeny.getValue());
}
final class Moeny {
private int value;
public void setValue(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
}


