예를 들어 사원 클래스를 설계한다고 할 경우 평사원, 임원, 비서, 임시직 모두 유사한 속성을 가지게 되므로 중복적인 요소가 생길 수 밖에 없다.
=> 수직적인 구조로 변경 시 부모 클래스로 사원 클래스를 만들어 공통속성을 만들어 준 후 평사원, 임원, 비서, 임시직클래스를 자식클래스로 만들어 상속받으면 수평적 구조의단점을 해결할 수 있다.
super class(상위 클래스, 부모 클래스) : 일반화, 추상화, 개념화, 포괄적
↑
extends(상속, 확장)
↓
sub class(하위 클래스, 자식 클래스) : 구체화, 세분화
상속 관계에서 하위 클래스가 상위 클래스로의 접근을 허용하는 접근권한.
또한 같은 패키지 내에서만 접근을 허용한다.
상위 클래스의 멤버변수를 private
로 선언하면 하위 클래스의 접근까지 막아버려 상속의 의미가 없어진다.
반대로 public
으로 선언하면 모든 접근을 허용하므로 상속 시 하위 클래스의 접근만을 허용하는 protected
를 사용한다.
상위 클래스의 생성자를 호출하는 메서드.
직접 작성하지 않아도 기본메서드처럼 기본적으로 들어있다.
상속에서는 상위 클래스가 먼저 메모리에 생성된 후 하위 클래스를 생성해야 한다.
예제
일반사원 한 명의 데이터를 저장하고 출력하는 예제
세 클래스 모두 같은 패키지 내에 두고 실습.
- Employee -
// 전 사원 공통사항
public class Employee {
protected String name;
protected int age;
protected String phone;
protected String empDate;
...
public Employee(){
super();
}
// toString() 생성
}
- RempVO -
// 일반사원
public class RempVO extends Employee{
public RempVO(){
super();
}
}
- EmployeeTest -
public class EmployeeTest {
public static void main(String[] args) {
RempVO vo = new RempVO();
vo.name = "김사원";
vo.age = 25;
vo.phone = "010-1111-1111";
vo.empDate = "2020-10-01";
vo.dept = "홍보부";
System.out.println(vo.toString());
// 결과: Employee{name='김사원', age=25, phone='010-1111-1111', empDate='2020-10-01', dept='홍보부', marriage=false}
}
}
-> RempVO클래스의 super()
는 Employee클래스의 생성자를 의미한다.
고로 RempVO클래스를 생성하면 메모리에 Employee클래스가 먼저 생성된 후 RempVO클래스가 생성된다.
(상위 클래스 생성 → 하위 클래스 생성 의 순서가 중요)
메모리상으로는 상위 클래스와 하위 클래스가 각각의 영역을 가지지만 실제로는 하위 클래스가 상위 클래스의 영역까지 포함한 접근범위를 가지게 된다.
상속을 사용하면 하위 클래스가 상위 클래스에 접근할 수 있는 장점이 있으나 정보은닉 차원에서는 문제가 될 수 있다.
=> 상위 클래스에 멤버변수는 private
로 선언하며, 초기화는 자신의 클래스에서만 가능하도록 한다.
즉, 상위 클래스의 생성자 메서드에서 초기화를 하고 하위 클래스에서는 상위 클래스의 생성자를 호출하여 초기화를 하도록 한다.
예제
- Employee -
// 전 사원 공통사항
public class Employee {
private String name;
private int age;
private String phone;
public Employee() {
}
public Employee(String name, int age, String phone) {
this.name = name;
this.age = age;
this.phone = phone;
}
// toString() 자동생성
}
- RempVO -
public class RemVO extends Employee{
public RemVO(){} // 상위 클래스의 생성자 호출
public RemVO(String name, int age, String phone){
// 하위 클래스에서 상위 클래스의 메모리에 초기화를 진행
super(name, age, phone); // 상위 클래스의 생성자 호출
}
}
- EmployeeTest -
public class EmployeeTest {
public static void main(String[] args) {
RemVO vo = new RemVO("홍길동", 30, "010-1111-2222");
System.out.println(vo.toString());
// 결과: Employee{name='홍길동', age=30, phone='010-1111-2222'}
}
}
-학습정리 & quiz-
1. 상속
2. extends
3. super()
4. protected
5. 중복코드 최소화, 사용자의 요구사항 반영에 유리(유지보수에 유리), 확장성이 좋다
method의 동작을 생각해보자.
보통은 보안을 위해 .java
파일 그러니까 소스코드 원본을 제공하지 않는다. 실행코드인 .class
파일만 받아 접근하게 되는데 이때 데이터를 주고받는 클래스 사이에 interface를 두어 interface를 상위 클래스로, 접근을 실행할 클래스를 하위 클래스로 두어 상속구조를 이루면 객체지향 측면에서 유리하다.
=> 보안상 클래스 내부에 직접 접근하는것이 불가능하게 만들면 하위 클래스에 무슨 코드가 작성되어있는지 알 수 없다.
이때 상위 클래스를 하위 클래스처럼 업캐스팅(Upcasting)
해준다.
업캐스팅에 대한 설명은 다형성 파트에서 계속.
재정의
하는 것.상위 클래스가 가진 데이터가 하위 클래스에 완전히 정합하지 않을때 하위 클래스에 맞춰 재정의하여 사용한다.
=> 상위 클래스의 객체를 생성 후 하위 클래스에서 재정의(오버라이드)
한 메서드를 사용할 경우 동적바인딩
을 통해 실행시점에서 사용될 메서드를 찾아 재정의된 메서드가 실행된다.
패캠Java/Spring 3주차 - Overloading 오버로딩
예제
- Animal -
public class Animal {
public void eat(){
System.out.println("동물이 먹다");
}
}
- Dog -
public class Dog extends Animal{
// Override 재정의
public void eat(){
System.out.println("개처럼 먹다");
}
}
- Cat -
public class Cat extends Animal{
// Override 재정의
public void eat(){
System.out.println("고양이처럼 먹다");
}
public void night(){
System.out.println("고양이는 밤에 눈이 빛난다");
}
}
- OverrideTest -
public class OverrideTest {
public static void main(String[] args) {
// Upcasting : Dog.java(x), Animal <---> Dog.class(o)
Animal dog = new Dog();
dog.eat(); // Animal --(동적바인딩)--> Dog
// Upcasting : Cat.java(x), Animal <---> Cat.class(o)
Animal cat = new Cat();
cat.eat();
}
}
객체의 생성(Upcasting)과 재정의(Override)를 주목하여 볼 것.
상위 클래스의 생성자를 호출한다.
메서드에서 가잣 첫 문장에서 사용.
super()
는 상위 클래스의 기본생성자를 호출함.
-학습정리 & quiz-
1. 생성자?? → super()
2. 업캐스팅 Upcasting
3. 상속체이닝
4. 동적바인딩
5. 재정의 Override
Java의 객체지향 프로그래밍에서 가장 중요한 개념.
상속 관계를 전제로 상위 클래스가 동일한 메시지로 하위 클래스를 서로 다르게 동작 시키는 객체지향 이론.
다형성의 전제조건
1. 상속관계
2. Override(재정의)
3. Upcasting(업캐스팅)
4. 동적바인딩
형 변환의 일종.
상속 관계에서 부모와 자식 간의 형 변환이 가능함.
부모는 여러 명의 자식을 가리킬 수 있다.
부모를 알면 자식을 관리하기 용이함.
* 업캐스팅과 다운캐스팅의 예제는 아래의 오버라이드 예제를 바탕으로 함
Animal클래스를 Dog클래스가 상속방을 때
Dog x = new Dog()
=> x 하위 클래스에서 객체를 생성하는것은 권장하지 않음.
Animal x = new Dog()
=> o
예제
- ObjectCasting -
public class ObjectCasting {
public static void main(String[] args) {
// Animal --> Dog, Cat
Animal ani = new Dog();
ani.eat();
ani = new Cat(); // 업캐스팅 Upcasting
ani.eat();
// ani.night(); // 접근불가
}
}
eat()메서드 : 부모가 자식들에게 동일한 메시지를 보냈으나 두 객체의 반응이 다름. → 다형성
개념으로 이어짐
ani.night()
는 부모에게 없는 Cat클래스만의 고유 메서드이므로 다운캐스팅이 필요함.
상속관계에서는 부모 클래스는 자식 클래스의 메모리 영역에 접근권한이 없으므로 직접 접근 할 수 없다.
Cat c = (Cat) ani;
c.night();
// 혹은
((Cat)ani).night();
두 행에 걸친 방법과 한 행에 걸친 방법 모두 알아둘 것.
- ObjectCasting -
public class ObjectCasting {
public static void main(String[] args) {
// Animal --> Dog, Cat
Animal ani = new Dog();
ani.eat();
ani = new Cat();
ani.eat();
// ani.night(); // 접근불가
// Downcasting 다운캐스팅
((Cat)ani).night();
}
}
다형성 개념을 제대로 알아야 Java의 api를 활용할 수 있으므로 제대로 익혀두어야 함.
다형성을 인수에 활용하면 코드의 중복을 줄일 수 있다.
예제처럼 인수로 넘어갈 클래스의 자료형(각자 Dog, Cat으로 다름)이 다를 때 공통의 부모 클래스인 Animal클래스로 인수를 받으면 다형성으로 인해 메서드가 정상동작하게 된다.
용어알기 : 인수와 매개변수의 차이 참고링크
매개변수는 메서드 선언부에 정의되어 있는 것, 인수는 매서드를 호출할 때 넘겨주는 값.
아래 예제에서display(dog);
의dog
가 인수,public static void display(Animal ani){...}
의ani
가 매개변수가 된다.
예제
public class PolyMethodTest {
public static void main(String[] args) {
Dog dog = new Dog();
display(dog);
Cat cat = new Cat();
display(cat);
public static void display(Animal ani){
ani.eat();
// ani.night(); // 에러발생
}
}
아래는 다형성 인수를 적용하지 않을 경우
private static void display(Dog d){ d.eat(); }
private static void display(Cat c){ c.eat(); }
각 클래스마다 display메서드를 만들 경우 위와 같이 만들어야 하나, 두 클래스의 부모 클래스가 같으므로 Animal클래스를 이용하여 인수를 보낸다.
문제점
eat()메서드는 Dog, Cat 클래스 모두에게 있는 메서드이지만 night()메서드는 Cat클래스만이 가지는 고유한 메서드이다.
이럴때 업캐스팅(Upcasting)을 이용하면 Cat클래스만 가지는 고유한 메서드를 자료형에 관계없이 모든 클래스가 사용할 수 있게 되어 문제가 발생한다.
=> if문을 이용해 instanceof
메서드를 이용해 인수로 보내준 자료형의 타입을 확인하여 Cat클래스인 경우에만 다운캐스팅 후 night()메서드를 실행하도록 해준다.
public static void display(Animal ani){
ani.eat();
// Cat타입으로 받아온 경우에만 다운캐스팅 후 night메서드 실행
if(ani instanceof Cat){
((Cat) ani).night();
}
}
배열은 동일한 자료형만 저장할 수 있지만 부모 타입의 배열은 자식 타입을 저장할 수 있다.
public class PolyArrayTest {
public static void main(String[] args) {
Dog d = new Dog();
Cat c = new Cat();
// Dog, Cat 타입을 저장할 배열을 생성 => 다형성 배열
Animal[] ani1 = new Animal[2];
ani1[0] = d;
ani1[1] = c;
Animal[] ani2 = {new Dog(), new Cat()};
display(ani1);
}
public static void display(Animal[] ani) {
for (int i = 0; i < ani.length; i++) {
ani[i].eat();
// Cat타입으로 받아온 경우에만 다운캐스팅 후 night메서드 실행
if (ani[i] instanceof Cat) {
((Cat) ani[i]).night();
}
}
}
}
night()메서드의 실행은 다형성 인수의 예제처럼 instanceof
로 타입확인 후 다운캐스팅하여 사용.
-학습정리 & quiz-
1. 다운캐스팅 Downcasting
2. 다형성 Polymorphism
3. 상속관계, Override 재정의, Upcasting 업캐스팅, 동적 바인딩
4. instanceof
5. 다형성 배열, 상위타입 배열
다형성을 보장하기 위해 등장한 개념들.
다형성을 보장한다 = 하나의 객체나 메서드가 다양한 형태를가질 수 있다.
- Animal -
public class Animal {
public void eat(){
System.out.println("?");
}
}
- Dog -
public class Dog extends Animal {
}
- Cat -
public class Cat extends Animal {
public void night(){
System.out.println("고양이는 밤에 눈에서 빛이 난다");
}
}
- IsNotOverride -
public class IsNotOverride {
public static void main(String[] args) {
// 재정의(Override)를 하지 않았으므로 부모의 명령에 오동작함
Animal ani = new Dog();
ani.eat();
ani = new Cat();
ani.eat();
}
}
다형성이 보장되지 않은 예시.
부모의 명령에 따라 클래스마다 다른 동작을 해야 하지만 똑같은 동작을 하고 있다.
→ 강제로 재정의를 하도록 만들어야 함.
여기서 추상 클래스와 인터페이스의 개념이 나오게 된다.
abstract
Animal ani = new Animal();
처럼 객체생성 불가.추상 메서드
, 생성자
, 필드(멤버변수)
, 구현 메서드(일반 메서드)
를 가질 수 있다.구현부가 없는 불완전한 메서드.
구현부는 하위 클래스에서 만들어줘야 한다.
public abstract class Animal {
public abstract void eat(); // 구현부가 없음 => 추상 메서드
}
=> 추상 클래스를 상속받은 클래스는 마찬가지로 불완전한 클래스가 된다.
재정의(Override)를 통해 구현이 필요. (미구현 시 에러발생)
추상 클래스는 추상 메서드뿐만 아니라 일반적인 구현 클래스도 가질 수 있기때문에 하위 클래스에서 오동작을 할 위험이 있다.
추상 메서드
만 가질 수 있다. = 구현 메서드(일반 메서드)는 가질 수 없다.interface
, implements
abstract
키워드는 생략한다.예제1
- Remocon -
public interface Remocon {
// chUp(), chDown(), volUp(), volDown()
public void chUp();
public void chDown();
public void volUp();
public void volDown();
// interface는 추상 메서드만 가질 수 있다
// public void internet(){
// System.out.println("인터넷이 구동됨");
// }
}
- Radio -
public class Radio implements Remocon{
@Override
public void chUp() {
System.out.println("Radio의 채널이 올라감");
}
@Override
public void chDown() {
System.out.println("Radio의 채널이 내려감");
}
@Override
public void volUp() {
System.out.println("Radio의 음량이 올라감");
}
@Override
public void volDown() {
System.out.println("Radio의 음량이 내려감");
}
}
- Tv -
public class Tv implements Remocon{
@Override
public void chUp() {
System.out.println("Tv의 채널이 올라감");
}
@Override
public void chDown() {
System.out.println("Tv의 채널이 내려감");
}
@Override
public void volUp() {
System.out.println("Tv의 음량이 올라감");
}
@Override
public void volDown() {
System.out.println("Tv의 음량이 내려감");
}
}
- InterfaceTest -
public class InterfaceTest {
public static void main(String[] args) {
Remocon remo = new Radio();
remo.chUp();
remo.chDown();
remo.volUp();
remo.volDown();
// remo.internet();
remo = new Tv();
remo.chUp();
remo.chDown();
remo.volUp();
remo.volDown();
}
}
인터페이스(Interface)를 이용하면 다형성을 보장된다.
인터페이스는 부모의 역할은 가능.
예제2
다중구현 예시
- Dog -
public class Dog extemds Animal implements Pet, Robots{
Animal r = new Dog();
Pet r = new Dog();
Robots r = new Dog();
}
예제3
인터페이스가 인터페이스를 상속
- A -
public interface A{
...
}
- B -
public class B extemds A{
...
}
- C -
public class C implements B{
...
}
final static
이 기본으로 적용된다 = final static
상수로 선언됨 = final static
은 생략가능final
: 더 이상 해당 변수의 값을 수정할 수 없음 = 상수=> final static
인 이유: 인터페이스는 객체를 생성할 수 없다. 고로 객체를 생성하지 않고도 접근할 수 있는 static
키워드를 사용, 값을 수정을 막기 위해 final
키워드 사용.
- Remocon -
public interface Remocon {
public final static int MAXCH = 99;
public final static int MINCH = 1;
public void chUp();
public void chDown();
public void volUp();
public void volDown();
}
- Tv -
public class Tv implements Remocon {
private int currch = 10;
@Override
public void chUp() {
currch++;
if (currch > MAXCH) {
currch = 1;
}
System.out.println("Tv의 채널이 올라감");
}
@Override
public void chDown() {
currch--;
if (currch < MINCH) {
currch = 99;
}
System.out.println("Tv의 채널이 내려감");
}
// 이하 생략
}
final과 static의 순서는 무관. 생략해도 무관.
-학습정리 & quiz-
1. 추상 클래스 abstract class
2. 인터페이스 interface
3. final static상수, 추상 메서드 abstract method
4. 추상 클래스 abstract class, 인터페이스 interface
5. public class Dog extends Animal implements Pet{}
Object클래스는 모든 클래스의 최상위 클래스로, 모든클래스가 상속받고 있지만 보통은 생략된다.
패키지 위치는 java.lang.Object
예제1
- A -
public class A {
public void printGO(){
System.out.println("나는 A");
}
}
- B -
public class B extends Object{
public void printGO(){
System.out.println("나는 B");
}
}
- ObjectTest -
public class ObjectPolyTest {
public static void main(String[] args) {
A a = new A();
display(a);
B b = new B();
display(b);
}
public static void display(Object obj){
if (obj instanceof A){
((A)obj).printGO();
}
if (obj instanceof B){
((B)obj).printGO();
}
}
}
Object타입으로 Upcasting이 되면 사용하기 전에 반드시 자신의 타입으로 Downcasting을 해주어야 한다.
예제2
- ObjectPolyArray -
public class ObjectPolyArray {
public static void main(String[] args) {
Object[] obj = new Object[2];
obj[0] = new A(); // Upcasting
obj[1] = new B(); // Upcasting
display(obj);
}
public static void display(Object[] obj){
for (int i=0; i< obj.length; i++){
if (obj[i] instanceof A){
((A)obj[i]).printGO();
}
if (obj[i] instanceof B){
((B)obj[i]).printGO();
}
}
}
}
방식은 앞서 했던 예제들과 동일함.
toString()
: 해당 객체의 번지를 문자열로 출력하는 메서드
Object클래스에는 toString()메서드가 미리 구현되어 있음.
=> 재정의(Override)를 해주지 않고 Object클래스의 toString()메서드 사용 시 메모리 내의 객체의 번지를 출력하게 됨.
보통은 객체가 가진 데이터를 출력하기 위해 toString()을 사용하므로 재정의를 해주어야 함.
제네릭, List, 예외처리, 스레드, 람다 등 생각보다 과정에 없는 개념이 많아 이 부분은 따로 공부가 필요할듯.
빠진 부분의 개념이 부족했는데 아쉽다.