final
은 변수, 메소드, 클래스에 붙을 수 있는데, 어디에 붙느냐에 따라 다른 의미를 가진다.
변수에 final
을 붙이면 이 변수는 수정할 수 없다는 뜻이다.
값을 수정할 수 없기 때문에 초기화 값을 꼭 정해줘야 한다. 초기화를 하지 않고 코드를 실행하면 컴파일 에러가 나기 때문에, 선언할때 초기화를 굳이 하지 않더라도 해당 final
변수를 사용하기 전에 꼭 초기값을 주어야 한다.
그러나 final
이 붙은 변수가 기본형 변수가 아니라 다른 객체를 참조하는 참조형 변수라면, 그 객체 내부의 값은 변경할 수 있다. 다시 말해 객체 자체를 변경할 수는 없지만, 객체 내부의 값은 변경할 수 있다는 뜻이다.
그 이유를 간단히 설명하자면, 기본형 변수는 내부에 값을 가지고, 참조형 변수는 내부에 객체의 주소를 가진다. 따라서 final
이 값을 변경할 수 없도록 영향을 미치는 부분은 기본형 변수의 값과 참조형 변수의 객체 주소 이기 때문에, 객체 내부의 값은 final
의 영향이 닿지 않아 바꿀 수 있는 것이다.
메소드에 final
을 붙이면 override를 제한한다는 뜻이다. 다시 말해 상속받은 클래스에서 상속해준 클래스의 메소드를 수정해서 사용하지 못하도록 하는 것이다.
final
을 안붙였을때는 다음과 같이 Pet
을 상속받은 Dog
에서 Pet
의 hello 메소드를 Dog
에서 override하는 것이 가능하다.
그런데 Pet
의 hello 메소드에 final
을 붙이면
override를 할 수 없게 된다.
클래스에 final
을 붙이면 상속이 불가능한 클래스가 된다. 다른 클래스에서 이 클래스를 상속받아 재정의할 수 없다는 뜻이다.
static
변수는 메모리에 고정적으로 한번 할당되어, 프로그램이 종료될 때 해제되는 변수다. 여러 객체가 해당 메모리를 공유하는데, 다음과 같은 상황에서 static
을 사용하면 메모리 효율을 높일 수 있다.
static
을 붙이면 생성된 모든 Dog
객체가 하나의 name
메모리를 참조할 것이고, 이는 어차피 변하지 않는 값이므로 final
도 붙여주고, 상수이므로 public
도 붙인다. 또한 static
변수는 static
메소드를 통해 접근하는 것이 권장되기 때문에 메소드 앞에도 static
을 붙인다.
💡대다수의 경우에서 어차피 변하지 않는 상수값을 메모리 한군데만 선언해두고 여러 군데에서 참조하기 때문에,
public static final
이 세트로 잘 사용된다.
static
은 클래스 변수라서 따로 객체 생성을 하지 않고도 static
메소드에 접근이 가능하다. 반대로 static
이 붙지 않은 메소드에는 new 연산을 통한 객체 생성 후에 메모리가 할당되어야 접근할 수 있다.
public class Hello {
public static void main(String[] args) {
Dog.printName1();
// Dog.printName2(); <- 객체 생성 후에 사용 가능
Dog dog = new Dog();
// dog.printName1(); <- 가능은 하지만 권장되지 않음
dog.printName2();
}
}
public class Dog {
public static final String name = "댕댕이";
public String dangdang = "댕댕이";
public static void printName1() {
System.out.println(name);
// System.out.println(dangdang); <- 불가능
}
public void printName2() {
System.out.println("멍멍이");
}
}
그리고 static
메소드 안에서는 static
이 붙지 않은 변수를 사용할 수 없다. 그 이유는 다음과 같은 흐름을 생각하면 당연한 것이다.
객체 생성 없이 static 메소드 사용
-> static이 붙지 않은 변수는 객체 생성이 돼야 메모리 할당됨
-> 할당되지 않은 메모리는 사용할 수 없음
-> 오류⚡