TIL #16 - Final, instanceof, 추상
#final
final은 상수를 지정할 때 사용하는 키워드이다.
지구의 둘레나 원주율 파이와 같이 불변의 값을 상수라고 하며 자바에서는 Constant라고 한다.
final은 한번 지정되면 초기화 할 수 없다.
class AA{
public void sub(int a){}
}
class BB extends AA{
public void sub (int a){}
}
위의 코드는 BB클래스가 AA 클래스에게 상속을 받아 오버라이드가 일어난 상태이다.
만약 AA클래스의 메소드를 상수화를 하고 싶어 final 키워드를 붙이면 오류가 난다.
final 키워드가 붙은 상수화된 메서드는 오버라이드가 불가하다.
또 class AA 자체를 상수화하기 위해 final을 붙이면 클래스 AA는 상속이 불가한 클래스가 된다.
아래의 코드는 상수가 초기화 할 때와 필드에서 초기화가 되지 않았을 경우를 보여준다.
class Final{
public final String FRUIT = "사과";
public final String FRUIT2;
public static final String ANIMAL = "사자";
public static final String ANIMAL2;
static {
ANIMAL2 = "기린";
}
public Final(){
FRUIT2 = "딸기";
}
}
일반적으로 필드에서 상수가 초기화되는데 만약, 필드에서 초기화하지 못했을 경우
static이 있는 경우와 없는 경우로 나눌 수 있다.
먼저 static이 없는 경우이다.
그런 경우엔 생성자에 초기화를 한다.
원래 생성자는 초기화를 위한 것이다.
public Final(){
FRUIT2 = "딸기";
}
생성자는 new를 했을 때 만들어지는 것인데
static은 시작함과 동시에 메모리에 올라간다.
그런 경우엔 생성자에서 초기화를 할 수 없다.
그렇기 때문에 static이 붙은 경우에는 static 초기화 구역을 따로 만들어줘야한다.
static {
ANIMAL2 = "기린";
}
아래는 FinalMain 클래스이다.
class FinalMain {
public static void main(String[] args){
final int AGE = 10;
System.out.println("상수 AGE = " + AGE);
AGE의 값 10을 상수화하였다.
만약 여기서 AGE를 30으로 바꾸고자 하면 어떻게 될까?
AGE = 30;
에러가 발생한다.
이미 상수 AGE는 10으로 초기화 되었기 때문에 다시 값을 변경할 수 없다.
그럼 아래와 같이 초기화가 되지 않은 경우는 코드 아래에 값을 지정해 줘도 괜찮을까?
final int AGE2;
초기값이 없는 상태이기 때문에 괜찮다.
AGE2 = 20;
이제 Final 클래스의 필드에 있는 FRUIT, FRUIT2, ANIMAL, ANIMAL2 들을 불러 출력해본다.
먼저 FRUIT의 경우에는 static이 없는 상태이기 때문에 위치를 알려주어야한다.
Final 클래스의 객체를 생성한다.
Final f = new Final();
아래와 같이 출력한다.
System.out.println("상수 FRUIT = " + f.FRUIT);
System.out.println("상수 FRUIT2 = " + f.FRUIT2);
System.out.println("상수 ANIMAL = " + Final.ANIMAL); // f.ANIMAL도 먹힌다
System.out.println("상수 ANIMAL2 = " + Final.ANIMAL2);
+) final이 걸린 메소드는 override를 절대 해선 안된다.
#instanceof
instanceof는 casting(형변환)이 되는지 안되는지 판별해준다.
또, 객체에 원하는 클래스타입이 메모리에 할당이 되었는지 안되었는지를 확인해준다.
class AA {}
class BB extends AA{}
위와 같은 클래스를 두개 만든다.
클래스 BB는 클래스 AA에게 상속을 받았다.
그런 후 InstanceOf 클래스에서 각각 형변환이 되는지 확인을 해본다.
class InstanceOf {
public static void main(String[] args){
AA aa = new AA();
BB bb = new BB();
AA aa2 = new BB();
클래스 BB가 클래스 AA에게 상속받는 사실을 기억하며 아래의 코드를 보자.
AA aa3 = aa;
if(aa instanceof AA){
System.out.println("1. TRUE");
}else {
System.out.println("1. FALSE");
}
이 코드는 True 일까 False일까.
if(aa instanceof AA)는 aa가 가리키는 곳에 AA타입의 클래스가 있다.
aa는 casting이 된다 라는 의미이다.
현재 aa의 클래스타입은 aa3와 같은 AA이므로 캐스팅이 가능하다.
결과는 TRUE이다.
AA aa4 = bb;
if(bb instanceof AA){
System.out.println("2. TRUE");
}else {
System.out.println("2. FALSE");
}
이 코드는 부모 = 자식의 형태이다.
BB가 AA에게 상속을 받았고 bb안에는 AA가 있기 때문에 캐스팅이 가능하다.
결과는 TRUE이다.
BB bb2 = aa2;
if(aa2 instanceof BB){
System.out.println("3. TRUE");
}else {
System.out.println("3. FALSE");
}
이 코드에서 BB는 자식이다.
자식 = 부모의 형태로 부모의 값 aa2가 자식 값에 들어오려고 하고 있다.
따라서 불가능하다.
그래서 자식 = (자식)부모 로 다운캐스팅을 해야한다.
아래와 같이 고쳐주면 된다.
BB bb2 = (BB)aa2;
그러면 결과가 TRUE가 된다.
BB bb3 = (BB)aa;
if(aa instanceof BB){
System.out.println("4. TRUE");
}else {
System.out.println("4. FALSE");
}
이 코드도 위와 같이 부모의 값이 자식의 값으로 들어오려는 형태이다.
그래서 다운캐스팅까지 해주었다.
하지만 이 코드는 캐스팅이 불가능하다.
aa는 AA 주소값만 이미 가지고 있는 상황이다.
그렇기 때문에 BB가 없는 상태이다.
이럴 경우에는 캐스팅이 불가능하다.
aa가 순수하게 AA만 가리키고 있기 때문이다.
# 추상클래스
추상클래스는 자식 클래스를 제어하는데에 사용한다.
메소드에 {} (body)가 없고 세미콜론이 끝에 붙으며 abstract 라는 키워드를 사용하는 형태이다.
추상메소드가 있다면 그 메소드를 가진 클래스도 역시 추상 클래스가 되어야한다.
하지만 추상클래스는 반드시 추상메소드를 가지진 않는다.
abstract class AbstractTest{
protected String name;
public abstract void setName(String name);
public String getName(){
return name;
}
}
이 코드는 추상메소드를 가진 추상클래스이다.
추상메소드의 형태를 보면 {}가 없는 (바디가 없는) 꼴이고 abstract가 붙어 있다.
이 클래스에게 상속을 받는 AbstractMain 클래스이다.
class AbstractMain extends AbstractTest{
@Override
public void setName(String name){
}
public static void main(String[] args) {
//AbstractTest at = new AbstractTest();
AbstractTest at = new AbstractMain();
}
}
추상 클래스는 자체적으로 new 할 수 없다.
즉 자체적 메모리 생성이 안된다.
그렇기 때문에 AbstractTest am = new AbstractMain(); 와 같이 생성은 자기자신 것으로 접근은 부모것으로 해야한다.
부모클래스는 추상클래스라 안된다고 하더라도,
왜 본인클래스의 객체 생성 AbstractMain am= new AbstractMain(); 이 안되는 것일까?
만약 AbstractMain am= new AbstractMain(); 이면 am이 AbstractMain을 가리켜 AbstractTest 클래스에 있는 getName()을 부를 수 없게 된다.
at.setName("양아무개"); 로 이름 데이터를 set 한 것 까지는 AbstractMain 클래스에서 이루어진다.
(메소드 발동의 우선권은 자식에게 있기 때문)
하지만 getName의 경우엔 부모클래스인 AbstractTest 클래스에 위치하고 있다.
그렇기 때문에 부모클래스에도 가기 위해서는 자기자신으로 생성을 하되 접근은 부모것으로 해야한다.
AbstractTest am = new AbstractMain();
추가로 AbstractMain이 AbstractTest에게 상속을 받으면서 오버라이드를 해주어야한다.
만약 AbstractMain이 추상을 걸면 밑의 자식들도 줄줄이 추상을 걸어야하기 때문에
오버라이드를 걸어줘야한다.
여기서 주의할 것은
모든 클래스가 new를 통하는 것이 아니라는 점이다.
자식이나 메소드로도 클래스에 접근할 수 있다는 것을 기억해야한다.
그런데 여기까지 하다보니, 추상화에 대한 감은 오는데
그래서 왜 추상화를 하는지 이해가 되지 않았다.
class 부모 {
public abstract void disp();
}
class AA {
public void add(~~){
}
}
class BB {
public void sum(~~){
}
}
.....
클래스가 여러개일 경우, 그리고 그 클래스를 만드는 사람들이 다 다르다면
실체 클래스의 필드와 메소드가 같은 용도임에도 각기 다른 이름을 가지는 경우가 생길 수 있다.
이름이 달라지만 사용방법이 달라진다.
그렇기 때문에 어떤 추상클래스를 만들어 공통적으로 사용하는 필드와 메소드를 선언해
다른 클래스에 상속을 함으로서 각 이름을 통일 시킬 수 있다.
(더 다른 이유가 있는 듯 하지만 일단 배운 것은 위와 같다)
+) 부모클래스가 추상클래스이면 자식 클래스는 반드시 오버라이드 해주어야한다.
하지만 final이 걸린 메소드는 override 하면 안된다.
override와 final은 정반대의 개념이다.
#모든 클래스가 new로만 불리는 것은 아니다
모든 클래스가 new로만 생성되는 것은 아니다.
그 대신 메소드를 통하는 방법이 있다.
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Locale;
class NumberTest {
public static void main(String[] args) {
//NumberFormat nf = new NumberFormat();
// 에러 - NumberFormat은 추상이기때문에 new로 생성 불가
NumberFormat은 클래스로 수에 대한 다양한 포멧기능을 제공한다.
NumberFormat은 클래스이기 때문에 new로 생성해서 사용해야지 라고 하면 오류가 난다.
NumberFormat는 추상클래스이기 때문이다.
따라서 NumberFormat의 자식 클래스인 DecimalFormat을 이용해서 NumberFormat을
만들어 준다. ( 자바 API에 들어가서 찾아본다 https://docs.oracle.com/javase/8/docs/api/index.html )
NumberFormat nf = new DecimalFormat();
이제 format메소드로 10진수의 형태로 3자리마다 쉼표를 찍고, 소수 이하 3째자리까지 표시되게 만든다.
만약 소수점이 없으면 표시하지 않는다.
System.out.println(nf.format(12345678.456789));
System.out.println(nf.format(12345678));
다음은 DecimalFormat으로 표시된 형태로 숫자를 출력한다.
NumberFormat nf2 = new DecimalFormat("#,###.#원");
System.out.println(nf2.format(12345678.456789));
System.out.println(nf2.format(12345678));
여기까지가 자식클래스를 이용해서 추상메소드를 부르는 방법이었다.
또 다른 방법은 getInstance() 메소드를 이용해 내부에서 NumberFormat을 만들어주는 것이다.
NumberFormat nf4 = NumberFormat.getInstance();
getCurrencyInstance() 도 있다.
이는 숫자 끝에 원가를 표시하는 메소드이다.
NumberFormat nf4 = NumberFormat.getCurrencyInstance();
nf4.setMaximumFractionDigits(2);
// 최대 소수 둘째까지 표시
nf4.setMinimumFractionDigits(2);
// 소수 이하값이 없어도 0이 강제 표시
위에서 언급했듯이 모든 클래스가 new로만 생성되는 것은 아니며 다양한 방법이 있다는 것을 기억하면 된다.