Java-상속, 다형성, 추상클래스, 내부클래스, 인터페이스
상속
- 상속이란? → 부모클래스의 멤버를 자식클래스에 물려주는 행위 (부모는 단 하나, 2개 안됨)
- 종류
- 부모클래스 (= 슈퍼클래스) : 상위 클래스
- 자식클래스 (= 서브클래스) : 하위 클래스 (파생클래스)
- 장점
- 상속을 통해서 기존 클래스의 필드와 메소드를 재사용
- 기존 클래스의 일부도 변경 가능
- 이미 작성된 검증된 소프트웨어를 재사용
- 코드의 중복을 방지할 수 있음
- 형식
public class SuperCar extends Car { }
- 메소드 오버라이딩 (= 메소드 재정의)
- 자식 클래스가 필요에 따라 상속된 동일한 메소드를 다시 정의하는 것.
- 부모의 메소드와 동일한 리턴타입, 메소드이름, 매개변수 리스트를 가져야함.
- 실행문만 다르게 해야함. (나머진 똑같게)
- 접근제한을 더 강하게 오버라이딩 할 수는 없음.
- super();
- 이건 자식클래스의 생성자에 숨겨져있음. / 부모클래스의 디폴트생성자 호출.
- 자식 생성자가 불리기전에 부모생성자가 먼저 호출됨.
- 상속을 받으면 자식클래스를 만들 때 부모객체를 먼저 만들고 자식객체를 이어붙여서 만드므로 이런 현상 발생.
- 근데 부모 클래스에 디폴트생성자가 없다면 super();가 제 기능을 할 수 없어 오류뜸.
- 이럴 땐 명시적으로 부모생성자의 매개변수 형식에 맞춰서 호출해야한다. (ex : super(500); )
다형성
- 다형성이란?
- 여러가지 형태를 가질 수 있는 능력
- 같은 타입이지만 실행결과가 다양한 객체를 이용할 수 있는 성질
- 하나의 타입의 참조변수에 여러 객체를 대입함으로써 다양한 기능을 이용할수 있도록 해줌.
- 조상클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조할 수 있도록 하는게 다형성! (반대는 안됨.)
Circle c = new Circle();
Rectangle r = new Rectangle(); //이건 당연
Shape s1 = new Circle();
Shape s2 = new Rectangle(); //현재 원과 사각형은 shape클래스의 자식클래스임
- 부모타입의 참조변수 sArray로 자식객체들을 모두 참조가능
- 상속받은 자식객체들을 배열로 관리가능 (서로 다른 객체(Circle, Rectangle)이더라도)
Shape[] sArray = new Shape[2];
sArray[0] = new Circle();
sArray[1] = new Rectangle();
for (int i = 0; i < sArray.length; i++) {
sArray[i].draw();
}
- 강제타입변환 (하향 형변환)
- 부모타입을 자식타입으로 형변환하는 것
- 자식클래스 객체인데 형변환에 의해서 일시적으로 부모클래스 참조 변수에 의하여 참조되고 있는 경우
Shape s1 = new Circle(); Shape s2 = new Rectangle(); s2.draw(); // 이건 rectangle에도 있는 메소드이기 때문에 부모까지 가지도 않음. s2.print(); // rectangle에 print()메소드가 없어서 가장 가까운 부모인 shape클래스의 print()를 가져와 실행함. //s2.setW(10); // 이게 오류뜨는 이유는 부모에 setW라는 메소드와 w라는 필드가 없기 때문 s2.x=10; s2.y=110; // 이건 당연히 가능 //s2.width=40; // 위의 메소드얘기와 같은 맥락
- 이러한 상황을 해결할 수 있는 게 강제타입변환
Rectangle r1 = (Rectangle) s2; // 이러한 강제타입변환 후 아래 코드 전부 가능 r1.setW(10); r1.w=40;
- 강제타입변환할 때 주의해야할 점
Circle c2 = (Rectangle) s2; // 이건 당연히 안된다는 사실 인지
- instanceof
- 강제타입변환을 할 때 참조변수가 어떤 자식객체인지 알아야 제대로 형변환을 할 수 있음.
- 객체의 타입을 확인하는 방법이 instanceof
- 형식 → 객체 instanceof 클래스타입 (왼쪽 객체가 오른쪽의 인스턴스라면 true 아니면 false)
//Shape s2 = new Rectangle(); 이었던 코드가 //Rectangle r1 = (Rectangle) s2; 이렇게 강제타입변환 후에 원래 //Shape s2 = new Rectangle(); 이게 맞았는지를 기억해내 확인하고 싶을 때 boolean result = s2 instanceof Rectangle; // 너 원래 rectangle이었니? 하고 확인한다. System.out.println(result); // 응 맞아! -> true
- 동적바인딩
class Parent { int x = 100; public void method() { System.out.println("부모메소드"); } } class Child extends Parent { int x = 200; public void method() { System.out.println("자식메소드"); } } public class BindingTest { public static void main(String[] args) { Parent p1 = new Child(); Child c1 = new Child(); System.out.println(p1.x); //100 System.out.println(c1.x); //200 p1.method(); //자식메소드 c1.method(); //자식메소드 } }
- 필드값은 자식은 자식꺼로 부모는 부모꺼로 나오지만 메소드는 다르다.
- 자식객체에 오버라이딩된 메소드가 있다면 아무리 부모객체 참조변수라 하더라도 오버라이딩된 자식객체의 메소드를 호출하게 된다.
- 같은 원리로 자식객체에 오버라이딩된 메소드가 없다면 부모메소드 찾아서 실행한다.
추상클래스
- 정의 : 클래스의 공통적인 특성을 추출해서 선언한 클래스
- 선언 형식 : [public] abstract class 클래스명 {필드; 생성자; 메소드;}
- 용도
- 실체 (
자식)클래스들의 공통된 필드와 메소드의 이름을 통일할 목적 - 추상클래스로 통일된 양식이 있으면 실체클래스를 작성할 때 시간절약, 간편
- 실체 (
- 특징 : 추상클래스는 자신 객체생성이 불가능-그저 자식클래스의 공통점만 묶기위한 용도이기 때문
- 추상클래스 vs 실체클래스
- 추상클래스가 부모이고 실체클래스가 자식으로 구현됨
- 실체클래스는 추상클래스의 모든 특성을 물려받고, 추가적인 특성을 가질 수 있음.
public class PhoneTest { public static void main(String[] args) { SmartPhone sp = new SmartPhone("김명관"); // 이게 실체클래스 sp.turnOn(); sp.turnOff(); //Phone p = new Phone("김명관"); -> 이게 오류뜬다. 추상클래스는 객체생성불가! }
- 추상메소드
- 추상클래스는 실체클래스의 멤버를 통일화하는 데에 목적이 있음
- 모든 실체클래스들이 가지고 있는 메소드의 실행내용이 동일하다면 추상클래스에 메소드를 지정하는 것이 좋음.
- 하지만 메소드의 선언만 통일하고 실행내용은 실체클래스마다 달라야하는 경우가 있음.
- 이럴 때 사용하는 게 추상메소드이며 당연히 추상클래스 내부에서 선언된다.
- 그래서 형식은 → 실행문이 없기 때문에 블록{}이 없음 → public abstract void sound();
- 이렇게 추상메소드가 있으면 실체클래스는 반드시 클래스 내부에서 그 추상메소드를 구체화시켜야한다.
abstract class Animal {
public abstract void sound();
public void aadsf () { };
}
class Cat extends Animal{
public void sound() {
System.out.println("야용");
} // 이렇게 무조건 추상클래스의 추상메소드를 구현해야함.
}
class Dog extends Animal {
} // 이렇게 추상메소드를 구현안하면 에러가 뜬다.
내부클래스
- 내부 클래스란? → 하나의 클래스 안에 다른 클래스를 정의할 수 있음.
- 형식
class OuterClass { private String secret = "akak"; class InnerClass { } }
- 특징 : 외부클래스에 필드가 private으로 선언되어도 내부클래스에선 setter/getter메소드없이 자유롭게 접근이 가능함.
- 유용한 상황 : 특정 멤버변수를 외부에서 자주 사용한다고 할 때 사용이 효율적이지 못한 경우 그렇다고 그 멤버변수를 public으로 하는건 캡슐화에 어긋남 → 이럴 때 내부클래스선언해서 자유롭게 사용한다. → 읽기도 쉽고 유지보수도 쉬움.
- 주의사항
- 외부클래스를 생성한다고 자동으로 내부클래스가 생성될까? → NO!!
- 외부클래스 생성 시 내부클래스 객체가 생성되게 하고 싶다면 객체화 코드를 작성해야함.
- 해결방법
import Day08.OuterClass.InnerClass; class OuterClass { private String secret = "akak"; public OuterClass() { // 내부클래스 객체화 InnerClass in = new InnerClass(); in.method(); // outerclass의 private 필드 접근가능 } class InnerClass { public InnerClass() { System.out.println("내부클래스 생성자임!"); } public void method() { System.out.println(secret); } } } public class OuterClassTest { public static void main(String[] args) { OuterClass out = new OuterClass(); InnerClass in = out.new InnerClass(); // import 해줘야함 in.method(); // outerclass의 private 필드 접근가능 } }
인터페이스
- 인터페이스란?
- 추상클래스의 극단적인 경우임. 추상클래스보다 더 엄격한 애임.
- 인터페이스는 무조건 추상메소드로만 이루어짐.
- 이 추상메소드들은 특정 인터페이스를 따르는 클래스들을 위한 요구조건 역할을 함. (규약생성)
- 당연히 객체로 생성안됨
- 상속은 무조건 하나이지만, 인터페이스는 다중 인터페이스가 가능
- 사용 이유
- 시스템을 구성하는 클래스들을 효율적으로 디자인할 수 있음. (틀을 잘 짜놓는 역할이 인터페이스)
- 정형화된 틀 안에서 클래스를 개발할 수 있음.
- 서로 연관이 없는 클래스들끼리 관계를 맺을 수 있음.
- 사전적 의미
- 두 가지 주제 혹은 시스템이 상호작용하기 위한 장치.
- 인터페이스 형식
public interface RemoteControl { public void turnOn(); public void turnff(); public void setVolume(int vol); }
- 구현 클래스 형식
public class Audio implements RemoteControl{ int vol; public void turnOn() { System.out.println("tv가 켜졌습니다."); } public void turnff() { System.out.println("tv가 꺼졌습니다."); } public void setVolume(int vol) { this.vol = vol; System.out.println("tv의 볼륨은 " + vol + "입니다."); } // 이렇게 무조건 추상메소드들을 오버라이딩해야함. }
- test 하기
public class RemoteControlTest { public static void main(String[] args) { RemoteControl rc = new Audio(); // 이런 식으로 객체 생성해야함. (다형성) rc.turnOff(); rc.turnOn(); rc.setVolume(10); } }
댓글남기기