🚀 꼭 매개변수가 아니더라도 변수가 참조하는 객체의 타입을 확인하고자 할 때instance of 연산자를 사용할 수 있다
좌항에는 객체가 오고 우항에는 타입이 위치
좌항의 객체가 우항의 타입이 일치하면 true를 산출하고 그렇지 않으면 false를 산출
if(parent instance of Child child) {
// child 변수 사용
}
사용 예제
public class Person {
//필드 선언
public String name;
//생성자 선언
public Person(String name) {
this.name = name;
}
//메소드 선언
public void walk() {
System.out.println("걷습니다.");
}
}
public class Student extends Person {
//필드 선언
public int studentNo;
//생성자 선언
public Student(String name, int studentNo) {
super(name);
this.studentNo = studentNo;
}
//메소드 선언
public void study() {
System.out.println("공부를 합니다.");
}
}
public class InstanceofExample {
//main() 메소드에서 바로 호출하기 위해 정적 메소드 선언
public static void personInfo(Person person) {
System.out.println("name: " + person.name);
person.walk();
//person이 참조하는 객체가 Student 타입인지 확인
/*if (person instanceof Student) {
//Student 객체일 경우 강제 타입 변환
Student student = (Student) person;
//Student 객체만 가지고 있는 필드 및 메소드 사용
System.out.println("studentNo: " + student.studentNo);
student.study();
}*/
// person이 참조하는 객체가 Student 타입일 경우
// student 변수에 대입(타입 변환 발생)- java 12~
if(person instanceof Student student) {
System.out.println("studentNo: " + student.studentNo);
student.study();
}
}
public static void main(String[] args) {
//Person 객체를 매개값으로 제공하고 personInfo() 메소드 호출
Person p1 = new Person("홍길동");
personInfo(p1);
System.out.println();
//Student 객체를 매개값으로 제공하고 personInfo() 메소드 호출
Person p2 = new Student("김길동", 10);
personInfo(p2);
}
}
🚀 객체는 부품과 같아서 프로그램을 구성하는 객체를 바꾸면 프로그램의 실행 성능이 다르게 나올 수 있다.
객체 사용 방법이 동일하다는 것은 동일한 메소드를 가지고 있다는 뜻
한국 타이어와 금호 타이어는 모두 타이어를 상속하고 있으므로 부모의 메소드를 동일하게 가지고 있다고 말할 수 있다.
타이어 메소드 호출 시 오버라이딩 된 메소드가 호출되는데, 오버라이딩 된 내용은 두 타이어가 다르기 때문에 실행 결과가 다르게 나옴
이것을 '다형성' 이라고 하는데 다형성을 구현하기 위해서는 자동 타입 변환과 메소드 재정의가 필요하다.
1) 필드 다형성
👾 필드 타입은 동일하지만(사용 방법은 동일하지만) 대입되는 객체가 달라져서 실행 결과가 다양하게 나올 수 있는 것을 말한다.
public class Car {
//필드 선언
public Tire tire;
//메소드 선언
public void run() {
//tire 필드에 대입된 객체의 roll() 메소드 호출
tire.roll();
}
}
public class Tire {
//메소드 선언
public void roll() {
System.out.println("회전합니다.");
}
}
public class HankookTire extends Tire {
//메소드 재정의(오버라이딩)
@Override
public void roll() {
System.out.println("한국 타이어가 회전합니다.");
}
}
public class KumhoTire extends Tire {
//메소드 재정의(오버라이딩)
@Override
public void roll() {
System.out.println("금호 타이어가 회전합니다.");
}
}
public class CarExample {
public static void main(String[] args) {
//Car 객체 생성
Car myCar = new Car();
//Tire 객체 장착
myCar.tire = new Tire();
myCar.run();
//HankookTire 객체 장착
myCar.tire = new HankookTire();
myCar.run();
//KumhoTire 객체 장착
myCar.tire = new KumhoTire();
myCar.run();
}
}
☑️ 어떤 자식 객체가 제공하느냐에 따라 drive() 실행 결과는 달라진다. 이것이 '매개변수의 다형성'
public class Vehicle {
//메소드 선언
public void run() {
System.out.println("차량이 달립니다.");
}
}
public class Bus extends Vehicle {
//메소드 재정의(오버라이딩)
@Override
public void run() {
System.out.println("버스가 달립니다.");
}
}
public class Taxi extends Vehicle {
//메소드 재정의(오버라이딩)
@Override
public void run() {
System.out.println("택시가 달립니다.");
}
}
public class Driver {
//메소드 선언(클래스 타입의 매개변수를 가지고 있음)
public void drive(Vehicle vehicle) {
vehicle.run();
}
}
public class DriverExample {
public static void main(String[] args) {
//Driver 객체 생성
Driver driver = new Driver();
//매개값으로 Bus 객체를 제공하고 driver() 메소드 호출
Bus bus = new Bus();
driver.drive(bus);
//매개값으로 Taxi 객체를 제공하고 driver() 메소드 호출
Taxi taxi = new Taxi();
driver.drive(taxi);
}
}
응용 예제
public class Product {
int price; // 제품의 가격
int bonusPoint; // 제품구매 시 제공하는 보너스 점수
Product(int price){
this.price = price;
this.bonusPoint = (int)(price / 10.0); // 보너스점수는 제품가격의 10%
}
}
public class Tv extends Product{
Tv () {
// 부모클래스의 생성자 Product(int price)를 호출한다.
super(100); // Tv의 가격을 100만원으로 한다.
}
@Override
public String toString() {
return "Tv";
}
}
public class Audio extends Product{
Audio() {
super(50);
}
@Override
public String toString() {
return "Audio";
}
}
public class Computer extends Product{
Computer () {
super(200);
}
@Override
public String toString() {
return "Computer";
}
}
1. Object 클래스 상속: - 모든 Java 클래스는 자동으로 Object 클래스를 상속받는다. Object 클래스는 모든 클래스의 최상위 부모 클래스이고, 이 클래스에서 제공하는 여러 메서드들이 모든 클래스에서 사용할 수 있게 된다. 그 중 하나가 toString() 메서드
2. toString() 메서드: - Object 클래스에는 이미 toString() 메서드가 정의되어 있기 때문에, 어떤 클래스든 toString() 메서드를 오버라이딩할 수 있다. 따라서, Product 클래스에 따로 toString() 메서드를 정의하지 않더라도, Tv 클래스에서 이 메서드를 오버라이딩하는 것이 가능
public class Buyer { // 고객, 물건을 사는 사람
int money = 1000; // 소유금액
int bonusPoint = 0; // 보너스점수
/* Product[] products = new Product[10]; // 구입한 제품을 저장하기 위한 배열
int i = 0; // Product 배열에 사용될 카운터 */
ArrayList<Product> products = new ArrayList<>();
void buy (Product product){ // 부모클래스 타입으로 매개변수 받음.
// 부모 클래스의 필드 사용. price, bonusPoint
if (money < product.price) {
System.out.println("잔액이 부족하여 물건을 살 수 없습니다.");
return;
}
money -= product.price; // 가진 돈에서 구입한 제품의 가격을 뺀다.
bonusPoint += product.bonusPoint; // 제품의 보너스 점수를 추가한다.
products.add(product); // 구입한 제품을 ArrayList에 저장한다.
System.out.println(product + " 을/를 구입하셨습니다.");
}
void summary () { // 구매한 물품에 대한 정보를 요약해서 보여 준다.
int sum = 0; // 구입한 물품의 가격합계
String itemList = ""; // 구입한 물품목록
if(products.isEmpty()) { // ArrayList가 비어있는지 확인한다.
System.out.println("구입하신 제품이 없습니다.");
return;
}
// ArrayList의 i번째에 있는 객체를 얻어 온다.
for(int i =0; i < products.size(); i++) {
Product product = products.get(i);
sum += product.price;
itemList += (i==0) ? "" + product : ", " + product;
}
/* // 반복문을 이용해서 구입한 물품의 총 가격과 목록을 만든다.
// 1) for 이용
for (int i = 0; i < products.length; i++) {
if (products[i] == null)
break;
sum += products[i].price;
itemList += products[i] + ", ";
}
// 2) foreach 사용
// for(각 요소의 타입과 요소를 담을 변수 : 배열 또는 컬렉션)
for (Product product : products) {
if (product == null)
break;
sum += product.price;
itemList += product + ", ";
}
// 3) 반복을 줄이기 위해 인스턴스 변수 사용
for (int i = 0; i < this.i; i++) {
sum += products[i].price;
itemList += products[i] + ", ";
} */
System.out.println("구입하신 물품의 총금액은 " + sum + "만원입니다.");
System.out.println("구입하신 제품은 " + itemList + "입니다.");
}
void refund(Product product) { // 구입한 제품을 환불한다.
if (products.remove(product)) { // 제품을 ArrayList에서 제거한다.
money += product.price;
bonusPoint -= product.bonusPoint;
System.out.println(product + "을/를 반품하셨습니다.");
} else { // 제거에 실패한 경우
System.out.println("구입하신 제품 중 해당 제품이 없습니다.");
}
}
}
public class Test {
/*
코딩 순서 : Product -> Tv -> Computer -> Buyer -> Test
*/
public static void main(String[] args) {
Buyer b = new Buyer();
Tv tv = new Tv();
Computer com = new Computer();
Audio audio = new Audio();
b.buy(tv); // Tv 을/를 구입하셨습니다.
b.buy(com); // Computer 을/를 구입하셨습니다.
b.buy(audio); // Audio 을/를 구입하셨습니다.
b.summary(); // 구입하신 물품의 총금액은 350만원입니다. 구입하신 제품은 Tv, Computer, Audio입니다.
System.out.println();
b.refund(com); // Computer을/를 반품하셨습니다.
b.summary(); // 구입하신 물품의 총금액은 150만원입니다. 구입하신 제품은 Tv, Audio입니다.
}
}
class A {
}
class B extends A {
}
class C extends A {
}
class D extends B {
}
class E extends C {
}
public class PromotionExample {
public static void main(String[] args) {
B b = new B();
C c = new C();
D d = new D();
E e = new E();
A a1 = b;
A a2 = c;
A a3 = d;
A a4 = e;
B b1 = d;
C c1 = e;
// B b3 = e;
// C c2 = d;
}
}
부모 타입으로 자동 타입 변환된 이후에는 부모 클래스에 선언된 필드와 메소드만 접근이 가능
변수는 자식 객체를 참조하지만 변수로 접근 가능한 멤버는 부모 클래스 멤버로 한정됨
자식 클래스에서 오버라이딩된 메소드가 있다면 부모 메소드 대신 오버라이딩된 메소드가 호출됨
2) 강제 타입 변환 Casting
자식타입 변수 = (자식타입) 부모타입객체;
Parent parent = new Child(); // 자동 타입 변환
Child child = (Child) parent; // 강제 타입 변환
⚒️ 부모 타입은 자식 타입으로 자동 변환되지 않는다. 대신캐스팅 연산자로 강제 타입 변환을 할 수 있다.
⚒️ 자식 객체가 부모 타입으로 자동 변환하면 부모 타입에 선언된 필드와 메소드만 사용 가능
➡️ 만약 자식 타입에 선언된 필드와 메소드를 꼭 사용해야 한다면 강제 타입 변환을 해서 다시 자식타입으로 변환해야 함
🚀 클래스를 선언할 때 final 키워드를 class 앞에 붙이면 최종적인 클래스이므로 더 이상 상속할 수 없는 클래스가 됨
2) final 메소드
public final 리턴타입 메소드( 매개변수, ...) { ... }
🚀 메소드를 선언할 때 final 키워드를 붙이면 이 메소드는 최종적인 메소드이므로 오버라이딩할 수 없는 메소드가 됨
2. protected 접근 제한자
🚀 같은 패키지에서는 default처럼 접근이 가능하나, 다른 패키지에서는 자식 클래스만 접근을 허용
package package1;
public class A {
//필드 선언
protected String field;
//생성자 선언
protected A() {
}
//메소드 선언
protected void method() {
}
}
같은 패키지
package package1;
public class B {
//메소드 선언
public void method() {
A a = new A(); //o
a.field = "value"; //o
a.method(); //o
}
}
다른 패키지
package package2;
import package1.A;
public class C {
//메소드 선언
public void method() {
//A a = new A(); //x
//a.field = "value"; //x
//a.method(); //x
}
}
다른 패키지 & 자식 클래스
package package2;
import package1.A;
public class D extends A {
//생성자 선언
public D() {
//A() 생성자 호출
super(); //o
}
//메소드 선언
public void method1() {
//A 필드값 변경
this.field = "value"; //o
//A 메소드 호출
this.method(); //o
}
//메소드 선언
public void method2() {
//A a = new A(); //x
//a.field = "value"; //x
//a.method(); //x
}
}
⚡️ new 연산자를 사용해서 생성자를 직접 호출할 수는 없고, 자식 생성자에서 super()로 A 생성자를 호출할 수 있다.
➡️ 이러한 메소드는 자식 클래스에서 재정의해서 사용해야한다. 이것을 메소드 오버라이딩이라고 함
1) 메소드 오버라이딩
👾 상속된 메소드를 자식 클래스에서 재정의하는 것을 말함
👾 메소드가 오버라이딩되었다면 해당 부모 메소드는 숨겨지고, 자식 메소드가 우선적으로 사용됨
💡 오버라이딩 규칙 1. 부모 메소드의 선언부(리턴 타입, 메소드 이름, 매개 변수)와 동일해야 한다 2. 접근 제한을 더 강하게 오버라이딩 할 수 없다. ( public → private 변경 불가 ) 3. 새로운 예외를 throws 할 수 없다.
public class Calculator {
//메소드 선언
public double areaCircle(double r) {
System.out.println("Calculator 객체의 areaCircle() 실행");
return 3.14159 * r * r;
}
}
public class Computer extends Calculator {
//메소드 오버라이딩
@Override
public double areaCircle(double r) {
System.out.println("Computer 객체의 areaCircle() 실행");
return Math.PI * r * r;
}
}
public class ComputerExample {
public static void main(String[] args) {
int r = 10;
Calculator calculator = new Calculator();
System.out.println("원 면적: " + calculator.areaCircle(r));
System.out.println();
Computer computer = new Computer();
System.out.println("원 면적: " + computer.areaCircle(r));
}
}
// 실행 결과
Calculator 객체의 areaCircle() 실행
원 면적: 314.159
Computer 객체의 areaCircle() 실행
원 면적: 314.1592653589793
📍 자바는 개발자의 실수를 줄여주기 위해 정확히 오버라이딩 되었는지 체크해주는 @Override 어노테이션을 제공한다.
➡️ 문제가 있다면 컴파일 에러를 출력함
2) 부모 메소드 호출
👾 메소드를 재정의하면 부모 메소드는 숨겨지고 자식 메소드만 사용되기 때문에 비록 부모 메소드의 일부만 변경된다 하더라도 중복된 내용을 자식 메소드도 가지고 있어야 한다.
👾 이 문제는 자식 메소드와 부모 메소드의 공동 작업 처리 기법을 이용하면 해결됨
super.method()의 위치는 작업 처리2 전후에 어디든지 올 수 있다. 우선 처리가 되어야 할 내용을 먼저 작성하면 됨.
이 방법은 부모 메소드를 재사용함으로써 자식 메소드의 중복 작업 내용을 없애는 효과를 가져옴
public class Airplane {
//메소드 선언
public void land() {
System.out.println("착륙합니다.");
}
public void fly() {
System.out.println("일반 비행합니다.");
}
public void takeOff() {
System.out.println("이륙합니다.");
}
}
public class SupersonicAirplane extends Airplane {
//상수 선언
public static final int NORMAL = 1;
public static final int SUPERSONIC = 2;
//상태 필드 선언
public int flyMode = NORMAL;
//메소드 재정의
@Override
public void fly() {
if(flyMode == SUPERSONIC) {
System.out.println("초음속 비행합니다.");
} else {
//Airplane 객체의 fly() 메소드 호출
super.fly();
}
}
}
public class SupersonicAirplaneExample {
public static void main(String[] args) {
SupersonicAirplane sa = new SupersonicAirplane();
sa.takeOff();
sa.fly();
sa.flyMode = SupersonicAirplane.SUPERSONIC;
sa.fly();
sa.flyMode = SupersonicAirplane.NORMAL;
sa.fly();
sa.land();
}
}
// 실행결과
이륙합니다.
일반 비행합니다.
초음속 비행합니다.
일반 비행합니다.
착륙합니다.
👾 상속은 이미 잘 개발된 클래스를 재사용해서 새로운 클래스를 만들기 때문에 중복되는 코드를 줄여 개발 시간을 단축시킨다.
👾 자식 클래스에 새로운 코드가 추가되어도 부모 클래스는 아무런 영향을 받지 않는다.
but, 조상클래스가 변경되면 자손클래스는 자동적으로 영향을 받게 됨. ( = 조상 클래스의 모든 멤버를 상속 받음 )
👾 부모 클래스를 확장(extend) 한다는 의미로 상속에 사용되는 키워드가 'extends'이다.
👾 자식 클래스의 인스턴스를 생성하면 부모 클래스의 멤버와 자손 클래스의 멤버가 합쳐진 하나의 인스턴스로 생성됨
class Person { // 사람 클래스
void breath() {
System.out.println("숨쉬기");
}
void eat() {
System.out.println("밥먹기");
}
void say() {
System.out.println("말하기");
}
}
class Student extends Person{ // 사람 클래스를 상속한 학생 클래스
void learn(){
System.out.println("배우기");
}
}
class Teacher extends Person{ // 사람 클래스를 상속한 선생 클래스
void teach(){
System.out.println("가르치기");
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student(); // 학생 인스턴스 student 생성
student.breath(); // 사람 클래스의 breath 메서드를 상속 받았음.
student.learn();
student.say();
Teacher teacher = new Teacher(); // 선생 인스턴스 teacher 생성
teacher.eat(); // 사람 클래스의 eat 메서드를 상속 받았음
teacher.teach();
teacher.say();
Person person = new Person();
person.breath();
// person.learn(); // 자식 클래스의 메서드나 멤버 변수는 사용하지 못함.
}
}
1) 클래스 상속
⚒️ 자식 클래스를 선언할 때 어떤 부모로부터 상속받을 것인지를 결정하고, 부모 클래스를 extends 뒤에 기술
⚒️ 다른 언어와 달리 자바는 다중 상속 허용 X. 단 하나의 부모 클래스만 상속 받을 수 있다!
public class Phone {
//필드 선언
public String model;
public String color;
//메소드 선언
public void bell() {
System.out.println("벨이 울립니다.");
}
public void sendVoice(String message) {
System.out.println("자기: " + message);
}
public void receiveVoice(String message) {
System.out.println("상대방: " + message);
}
public void hangUp() {
System.out.println("전화를 끊습니다.");
}
}
public class SmartPhone extends Phone {
//필드 선언
public boolean wifi;
//생성자 선언
public SmartPhone(String model, String color) {
this.model = model;
this.color = color;
}
//메소드 선언
public void setWifi(boolean wifi) {
this.wifi = wifi;
System.out.println("와이파이 상태를 변경했습니다.");
}
public void internet() {
System.out.println("인터넷에 연결합니다.");
}
}