변수에 final붙여주는 것은 상수이다. 클래스에도 fianl을 붙여줄 수 있는데 이러면 다른 클래스가 자신을 상속 못 받게 할 수있다.
final을 붙인 Member클래스를 만들었고, 다른 클래스가 상속을 받게했다.

이때 Member클래스에 빨간 줄이 생기며 오류가 뜨는 것을 볼 수 있다.
이렇게 final을 붙임으로 다른 클래스가 상속 받는 것을 제한 할 수 있다.
메서드에도 final을 붙일 수 있다. 메서드에 final을 붙이면 상속받은 클래스에서 final함수는 오버라이딩을 못하게 만들 수 있다.
Car라는 하
speedUp함수와 stop함수를 만들었는데 그 중 stop에만 final을 만들어 주었다.

sportsCar라는 자식클래스를 만들고 Car클래스를 상속 받게 하고 함수들을 오버라이딩을 시도했다.
하지만 final이 붙은 함수는 오버라이딩 선택창에 나오지 않는 것을 볼 수 있다.
하나의 타입으로 여러가지 클래스로 다양한 결과(자식의 오버라이딩 함수)를 만들 수 있게 해주는 기능이다.
부모 클래스
public class Parent {
public void method1(){
System.out.println("부모함수 - method1");
}
public void method2(){
System.out.println("부모함수 - method2");
}
}
자식 클래스
public class Child extends Parent{
@Override
public void method2() {
System.out.println("자식함수 - method2");
}
public void method3(){
System.out.println("자식함수 - method3");
}
}
main 클래스

자식의 인스턴스에서 부모나 자식의 함수를 호출하면 컴파일 오류없이 다 잘 호출이된다. 이건 이미 전 시간에 배웠다.
그런데 밑에 다형성으로 만든 인스턴스(부모타입 자식 클래스)는 부모클래스의 함수에서 만든 함수는 호출이 가능하지만 자식 클래스에서 만든 함수는 호출이 안되고 컴파일 오류가 나는 것을 확인 할 수 있다.
오류가 나는 이유는 메모리 구조를 보면 확인 할수 있다. 잠시 뒤 알아보자
결과

결과에서는 부모타입에서 부른 method2가 자식이 오버라이딩 한 함수가 호출되는 것을 확인 할 수 있다.
부모타입에 자식을 담으면 부모타입의 인스턴스는 부모와 자식의 클래스를 다 가지게 된다. 여기서 함수를 호출할때 부모 타입이므로 부모의 클래스에서 함수를 찾게된다.
만약 함수가 부모에게 존재한다면 그함수를 호출한다.
함수가 부모에게 존재하지만 자식이 오버라이딩한 것이면 오버라이딩 함수를 호출한다.
함수가 부모에게 존재하지 않고 자식에겐 존재한다고 해도, 탐색은 아래방향으로 진행 할 수 없기 때문에 함수를 호출하지 못하고 컴파일 오류가 발생한다.
오버라이딩 함수가 있으면 자식함수가 실행된다. 이렇게 되면 우리는 자식함수를 사용하면서도 부모타입으로 자식들을 통일 시킬 수 있어서 배열이나 반복을 사용하기에 편리해진다.
또한 서비스가 서버에 올라간 상황에서 수정이 어려운데 이때 다형성으로 타입은 같게하고 다른 클래스에서 다른 코드를 짜서 새로운 실험을 할 수도 있다. 즉 서버는 운용하면서 새로운 코드로 수정을 할 수 있게 된다.
위에서 인스턴스를 만들어 거기에 자식인스턴스를 집어넣었는데 이번엔 함수와 매개변수로 똑같은 것을 해보자.
부모 클래스
public class Vehicle {
public void run(){
System.out.println("차량이 달립니다.");
}
}
자식 클래스
public class Bus extends Vehicle{
@Override
public void run() {
System.out.println("버스가 달립니다.");
}
}
main 클래스
public class DriverApplication {
public static void main(String[] args) {
Driver driver = new Driver();
Bus bus = new Bus();
Taxi taxi = new Taxi();
driver.drive(bus);
}
}
결과

탈것이 달립니다가 아닌 버스가 달립니다가 된다. drive함수에 매개변수 타입은 부모타입인데 여기에 자식의 이름을 넣으면 부모타입의 자식 인스턴스가 들어가게 되어서 자동으로 다형성이 형성되고 오버라이딩된 자식의 함수가 실행되는 것이다.
부모타입의 변수를 받는 함수를 만들고 거기에 부모타입에서 함수를 호출하면 된다.
위에서 다형성을 사용할 시에는 오버라이딩 되지않은 자식의 함수는 사용 할 수 없었던 것을 확인했다. 근데 다운캐스팅이 자식 함수를 사용할 수 있게 한다.
부모 클래스
public class Parent {
public String field1;
public void method1(){
System.out.println("부모 1");
}
public void method2(){
System.out.println("부모 2");
}
}
자식 클래스
public class Child extends Parent{
public String field2;
public void method3(){
System.out.println("자식 3");
}
}
main 클래스
public class ChildApplication {
public static void main(String[] args) {
int a = (int)0.0;
Parent parent = new Child();
parent.method1();
parent.method2();
Child child = (Child) parent;
child.method3();
}
}
부모에 자식을 담아서 사용하다가 자식의 오버라이딩되지 않은 함수를 사용하고 싶을때 다음과 같이 부모 타입을 자식타입에 담아서 잠시 사용할 수 있다.
결과

자식 함수가 잘 실행되는 것을 확인 할 수 있다.
다운캐스팅을 사용할때 부모 타입에 자식 인스턴스가 들어가 있는상태에서만 가능하다 아니면 예외가 발생된다.
근데 이렇게 사용할 바에 그냥 자식 인스턴스를 만들면 되는데 왜 굳이 사용하는지가 궁금해서 챗지피티에게 물어봤다.

다음과 같은 이유가 있는데 내가 아직 아는게 없어서 이해를 못하는 것 같다. 다음에 실무에서 사용해야하는 일이 생기면 그때 이해를 하자.
이 예약어의 결정적인 목적은 강제형변환시 오류를 예방하기 위함이다.
다형성에서 강제 형변환(다운캐스팅)을 할 경우 부모타입에 자식 인스턴스가 들어가있어아한다. 그렇지 않은 경우 다운캐스팅을 하면 예외가 발생한다.
이것을 예방하기 위해 생긴 예약어가 instanceOf이다.
public class ChildApplication {
public static void main(String[] args) {
Parent parent = new Parent();
System.out.println("부모가 자식을 담지 않은 경우: Parent parent = new Parent();");
method1(parent); // 부모가 자식을 담지 않은 경우
Parent parent2 = new Child();
System.out.println("부모가 자식을 담은 경우: Parent parent2 = new Child();");
method1(parent2); // 부모가 자식을 담은 경우
}
// 전역함수 : static 자료형 함수명(){}
public static void method1(Parent parent) {
if( parent instanceof Child) {
// 강제형변환
Child child = (Child)parent;
System.out.println("강제형변환 성공");
} else {
System.out.println("실패");
}
}
}
이 코드는 if문을 이용하여 Child가 Parent타입의 인스턴스일 경우에만 강제 형변환이 일어나게 하는 코드이다. 이때 if의 조건문에 Child가 Parent타입의 인스턴스인지를 false,true로 내보내는게 instanceOf예약어의 역할이다.
결과

부모가 자식을 담은 경우에만 형 변환이 잘 일어난 것을 확인 할 수 있다.
추상메서드를 가질 수 있는 클래스로, 꼭 추상메서드를 가져야하는 것은 아니지만 추상메서드가 있다며 꼭 추상클래스로 지정해주어야한다.
또한 추상 클래스는 인스턴스를 생성할 수 없다.
public abstract class Animal {
public String kind;
public void breathe(){
System.out.println("숨을 쉰다.");
}
public abstract void sound();
}
추상 클래스를 지정하는 방법은 위 코드와같이 클래스에 abstract를 붙여주면된다. 위 코드에서 sound함수가 추상 메서드인데 일반 메서드와 다르다. 자세하게 알아보자
추상 메서드란 부모 클래스에서 틀만 생성하고 내용을 입력하지 않은 메서드를 말한다. 비어 있는 이유는 자식 클래스에서 구현을 하게 만들기 위함이다.
public class Cat extends Animal{
public Cat() {
}
@Override
public void sound() {
}
}
이렇게 자식 클래스에서 오버라이딩을 해서 내용을 채워야한다. 그리고 추상클래스를 상속받았을 경우 추상메서드는 반드시 구현해야하며, 하지 않을 시 컴파일 오류가 발생한다. 왜 컴파일 오류가 나는지는 기능을 보면 알 수 있다.
결과
public class AnimalApplication {
public static void main(String[] args) {
Dog dog = new Dog();
Cat cat = new Cat();
dog.sound();
cat.sound();
}
}

오버라이딩 함수가 나오는 법칙에 의해 부모클래스가 아닌 자식의 메서드가 호출된다.