객체지향 프로그래밍 (OOP), Polymorphism

Wonkyun Jung·2023년 2월 15일
0

자바기초

목록 보기
4/4
post-thumbnail

1. 다형성 (polymorphism)

  • 다형성의 정의 : 상속 관계에 있을 때 조상 클래스의 타입으로 자식 클래스 객체를 레퍼런스 할 수 있다
  1. 다형성의 활용 예 1 - 다른 타입의 객체를 다루는 배열
  • 배열의 특징 - 같은 타입의 데이터를 묶음으로 다룬다
void beforePoly(){
	Person[] persons = new Person[10];
	persons[0] = new Person();
	SpiderMan [] spiderMans = new SpdierMan[10];
	spiderMans[0] = new SpiderMan();
}

void afterPoly(){
	Person [] persons = new Person[10];
	Person[0] = new Person();
	Person[1] = new SpiderMan();
}

다형성으로 다른 타입의 데이터(Person, SpiderMan을 하나의 배열로 관리)
  • Object는 모든 클래스의 조상 → Object의 배열은 어떤 타입의 객체라도 다 저장가능
  • 자바의 자료구조를 간단하게 처리할 수 있음

  1. 다형성의 활용 예 2- 매개변수 다형성
  • 무언가를 출력할 때

→ 메서드가 호출되기 위해서는 메서드 이름과 파라미터가 맞아야 한다

ex) public void println(Phone p), public void println(SmartPhone sp)

public void println(Object x){
	String S = String.valueOf(x);
	synchronized(this){
		print(s);
		newLine();
	}
}

Object가 모든 클래스의 조상이므로 어떤 타입의 객체라도 저장해서 출력 가능 

  1. 다형성의 활용 예3 - 다형성과 참조형 객체의 형 변환

    → 메모리에 있는 것과 사용할 수 있는 것의 차이

메모리에 있더라도 참조하는 변수의 타입에 따라 접근할 수 있는 내용이 제한됨

  • 참조형 객체의 형 변환
  1. 작은 집(child) 에서 큰 집(super)으로 → 묵시적 캐스팅
  2. 큰 집(super)에서 작은 집(child)으로 → 명시적 캐스팅

1byte b = 10;
int i = b;

Phone phone = new Phone();
Object obj = phone;

자손 타입의 객체를 조상 타입으로 참조: 형변환 생략 가능 (조상의 모든 내용이 자식에 있음)

2int i = 10;
byte b = (byte)i;

Phone phone = new SmartPhone();
SmartPhone sPhone = (SmartPhone)phone; 

조상 타입을 자손 타입으로 참조: 형변환 생략 불가 
  • 참조형 객체의 형 변환 (ex 무늬만 SpiderMan인 Person)
Person person = new Person();
SpiderMan sman = (SpiderMan) person;
sman.fireweb();

메모리의 객체는 fireWeb이 없다

  • 조상을 무작정 자손으로 바꿀 수는 없다

→ instanceof 연산자: 실제 메모리에 있는 객체가 특정 클래스 타입인지 bool로 리턴

다른 형태의 하위객체 들이 한 부모를 상속 받았을 경우에 어떤 클래스인지 알아낼 수 있다.

Person person = new Person(); 

if(person instanceof SpiderMan){
	SpiderMan sman = (SpiderMan) person;
}

-> person 이라는 객체가 SpiderMan과 같은 객체 (메모리상에서)일때만 if문을 실행 

class AA{}
class BB extends AA {}
class CC extends AA {}
public class InstanceofEx1 {
	public static void main(String[] args) {
		AA a=new AA();
		BB b=new BB();
		
		System.out.println(b instanceof AA);  // true
		System.out.println(a instanceof CC);  // false
//		System.out.println(b instanceof CC);  // error
		
	}
}
  • 정적 바인딩과 동적 바인딩
class SuperClass{
	String x = "super";	
	public void method(){
		system.out.println("super class method");
	}
}

class SubClass extends SuperClass{
	String x = "sub";
		
	@Override
	public void method(){
		system.out.println("sub class method");
	}
}

public class MemberBindingTest{
	public static void main(String[]args){
		SubClass subClass = new SubClass();
		system.out.println(subClass.x);       // sub
		subClass.method();                    // sub class method
		
		SuperClass superClass = subClass;     
		system.out.println(superClass.x);     //super
		superClass.method();                  //sub class method
	}
}

정적 바인딩(static binding)

  • 컴파일 단계에서 참조 변수의 타입에 따라 연결이 달라짐
  • 상속 관계에서 객체의 멤버 변수가 중복될 때 또는 static method

동적 바인딩(dynamic binding)

  • 다형성을 이용해서 메서드 호출이 발생할 때 runtime에 메모리의 실제 객체의 타입으로 결정
  • 상속 관계에서 객체의 instance method가 재정의 되었을 때 마지막에 재정의 된 자식 클래스의 메서드가 호출됨 → 최대한 메모리에 생성된 실제 객체에 최적화 된 메서드가 동작한다.

  • static 메서드는 클래스가 Compile 되는 시점에 결정되지만, Override의 경우에는 Runtime 시점에 사용될 메서드가 결정되기 때문

  • 부모에 static 메서드를 선언하고 자식에서 똑같은 static함수로 선언하면 해당함수는 상속받는게 아니라 자식 객체로 해당 메서드를 실행하면 부모의 static 메서드가 hiding 되는 것 부모 객체의 해당 메서드를 실행하면 부모 클래스에서 선언한 static 메서드가 호출된다




객체가 출력되는 과정

  • System.out.print(Object obj)를 이용한 객체 출력
SuperClass superClass = new subClass();
System.out.print(superClass)
  • PrintStream의 print 메서드
public void print(Object obj){
	write(String.valueOf(obj));
}
  • String의 valueOf
public statoc String valueOf(Object obj){
	return (obj == null) ? "null" : obj.toString();
}
  • Objectd의 toString
public String toString(){
	return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

2. 추상 클래스

  • abstract 클래스는 상속 전용의 클래스
    • 클래스에 구현부가 없는 메서드가 있으므로 객체를 생성할 수 없음(자식 객체로 선언해야함)
    • 하지만 상위 클래스 타입으로써 자식을 참조할 수는 있다
      • 다형성을 사용할 수 있다는 의미 Superclass sp = new Subclass(); 로 사용가능

  • 조상 클래스에서 상속받은 abstract 메서드를 재정의 하지 않은 경우
    • 클래스 내부에 abstract 메서드가 있는 상황이므로 자식 클래스는 abstract 클래스로 선언 되어야 함

  • 자손 클래스에서 반드시 재정의해서 사용되기 때문에 조상의 구현이 무의미한 메서드
    • 메서드의 선언부만 남기고 구현부는 세미콜론으로 대체
    • 구현부가 없다는 의미로 abstract 키워드를 메서드 선언부에 추가
    • 객체를 생성할 수 없는 클래스라는 의미로 클래스 선언부에 abstract를 추가한다

public class AbstractEx1 {
	public static void main(String[] args) {
//		AA ob=new AA();   //AA로 객체 생성 불가능
//		ob.view1();       //1번 호출
		
		AA ob2=new BB();
		ob2.view1();      //3번 호출
		ob2.view2();      //2번 호출
	}
}

//추상메서드가 있는 추상클래스----------------------------------

abstract class AA{
	abstract public void view1();  //--1
	public void view2() {}         //--2
}
class BB extends AA{
	@Override
	public void view1() {}         //--3,강제성이 있는 재정의
}
public class AbstractEx1 {
	public static void main(String[] args) {
//		AA ob=new AA();   //AA로 객체 생성 불가능
//		ob.view1();       //1번 호출
		
		AA ob2=new BB();
		ob2.view1();      //3번 호출
		ob2.view2();      //2번 호출
	}
}

//일반클래스-----------------------------------------------

class AA{
	public void view1() {}  //--1
	public void view2() {}  //--2
}
class BB extends AA{
	@Override
	public void view1() {}  //--3, 강제성이 없는 재정의
}
public class AbstractEx1 {
	public static void main(String[] args) {
		AA ob=new AA();   //AA로 객체 생성 가능
		ob.view1();       //1번 호출
		
		AA ob2=new BB();
		ob2.view1();      //3번 호출
		ob2.view2();      //2번 호출
	}
}

예를 들어 pair programming 중에 각자 다양한 메서드 이름을 쓰는 경우가 존재, 이럴 때 메서드 이름을 통일하되, 각자의 클래스에서 부모클래스를 강제로 오버라이딩 해서 사용하도록 하기 위해서 사용

  1. 추상클래스(abstract class)란?
  • 내용이 구현되어 있지 않는 메소드가 1개라도 존재하면 추상클래스라고 함
    ex)
    public void view() { }             -- 구현O
    abstract public void view();       -- 구현X
  • 추상메서드가 없어도 추상 클래스를 만들 수 있다
  • 자기자신으로 객체 생성이 불가능
  • 하위클래스들이 특정메소드(추상메소드)를 반드시 강제 overriding 하기 위함
  • 추상클래스를 사용하는 이유 : 메서드의 일관성을 유지하기 위해서이다
  1. 추상클래스 사용방법 : abstract 키워드를 사용한다.

→ abstract class 클래스 이름{
abstract 리턴타입 추상메소드이름( );
}

3. 인터페이스

  • 최고 수준의 추상화 단계 : 일반 메서드는 모두 abstract 형태
  • 클래스와 유사하게 interface선언
    • 모든 멤버변수는 public static final 이며 생략 가능
    • 모든 메서드는 public abstract 이며 생략 가능
public interface MyInterface{
	public static final MEMBER1 = 10;
	int MEMBER2 = 10;
	
	public abstract void method1(int param);
	void method2(int param)
}

추상클래스 vs 인터페이스

//인터페이스 ---------------------------------------------

interface Flyable{
	abstract public void fly();
}

interface Cryable{
	abstract public void cry();
}

class Eagle implements Flyable,Cryable{

	@Override
	public void fly() {
		System.out.println("독수리는 날아 다닙니다");
	}
	
	@Override
	public void cry() {
		System.out.println("까악 까악");
	}
}

public class InterfaceEx2 {
	public static void main(String[] args) {
		new Eagle().fly();
		new Eagle().cry();
	}
}

//추상클래스 ---------------------------------------------
abstract class Flyable{
	abstract public void fly();
}

abstract class Cryable{
	abstract public void cry();
}

//클래스 상속은 하나 밖에 받을 수가 없다
class Eagle extends Flyable{

	@Override
	public void fly() {
		System.out.println("독수리는 날아 다닙니다");
		new CryableEx().cry();
	}
	
	//중첩클래스(inner class):클래스 안에서 또 다른 클래스를 사용
	class CryableEx extends Cryable{
		@Override
		public void cry() {
			System.out.println("까악 까악");
		}
	}
}

public class InterfaceEx2 {
	public static void main(String[] args) {
		new Eagle().fly();
	}
}



인터페이스 활용 2

class User {
	
	private String name;
	public  User() {}
	public  User(String name) {
		this.name = name;
	}
	
	@Override 
	public String toString() {
		return "이름 : "+ this.name;
	}
}
//----------------------------------------------------
interface Score {
	public int sol = 20;
	public int getScore();
}
//-----------------------------------------------------
interface Print {
	public String toPaint();
}

//-----------------------------------------------------
public class InterfaceEx3 extends User implements Score,Print{  // User,Score,Print 상속받기
	int s; // 맞은수
	
	public InterfaceEx3(String name, int s) {
		super(name);
		this.s = s;

	}
	
	public static void main(String[] args) {
		InterfaceEx3 ob = new InterfaceEx3("홍길동", 3);
		System.out.println(ob.toPaint());
	}

	@Override
	public String toPaint() {
		return super.toString() +"\n점수 : "+getScore()+"점";
	}

	@Override
	public int getScore() {
		return this.s*sol;
	}
}

[출력화면]
이름 : 홍길동
점수 : 60
  • 인터페이스 상속

    • 클래스와 마찬가지로 인터페이스도 extends를 이용해 상속가능
    • 클래스와 다른 점은 인터페이스는 다중 상속이 가능하다
      • 헷갈릴 메서드 구현 자체가 없으니까
  • 인터페이스 구현과 객체 참조

    • 클래스에서 implements 키워드를 사용해서 interface 구현
    • implements 한 클래스는
      • 모든 abstract 메서드를 override해서 구현하거나
      • 구현하지 않을 경우 abstract 클래스로 표시해야 함
    • 여러 개의 interface implements 가능
  • 인터페이스의 필요성

    • 구현의 강제로 표준화 처리 → abstract 메서드 사용
    • 인터페이스를 통한 간접적인 클래스 사용으로 손쉬운 모듈 교체 지원
    • 서로 상속의 관계가 없는 클래스들에게 인터페이스를 통한 관계 부여로 다형성 확장
    • 모듈 간 독립적 프로그래밍 가능 → 개발 기간 단축

if MySQL에서 Oracle로 데이터베이스를 바꾼다고 할 때 다 뜯어고칠 필요가 없다. JDBC 인터페이스를 각각의 DB가 구현하고 있으니 인터페이스 단에서

고쳐줘야 하는 코드는 없다. 그냥 MySQL에서 Oracle로만 바꾸면 그만 → 다형성 이용

자바는 Single Inheritance 만 지원한다 → 상속 받을 수 있는 클래스는 1개 밖에 없음 HandPhone은 Phone을 상속받고 Chargeable 인터페이스를 구현

DigitalCamera는 Camera를 상속받고 Chargeable 인터페이스를 구현 ⇒ chargeable 객체로 묶어서 한 번에 처리가 가능하다.

  • 독립적인 프로그래밍으로 개발 기간 단축
    • 계산기를 구현하는 두 팀의 작업

default method

  • 인터페이스에 선언 된 구현부가 있는 일반 메서드
    • 메서드 선언부에 default modifier 추가 후 메서드 구현부 작성
      • 접근 제한자는 public으로 한정됨(생략가능)

        interface DefaultMethodInterface{
        	void abstracrMethod();
        
        	default void defaultMethod(){
        		System.out.println("기본 메서드 입니다");
        	}
        }
    • 필요성
      • 기존에 interface 기반으로 동작하는 라이브러리의 interface에 추가해야 하는 기능이 생김
      • 기존 방식으로라면 모든 구현체들이 추가되는 메서드를 override해야함
      • default 메서드는 abstract가 아니므로 반드시 구현해야 할 필요는 없어짐

새로운 메서드를 인터페이스에 넣을 때 인터페이스를 구현하는 하위 클래스들이 일일히 다 override하지 않아도 되게한다.

원래 interface 메서드는 구현부가 없기 때문에 다중상속이 가능했다, but default 메서드를 만들면 이 메서드는 구현되기 때문에 충돌이 발생할 수 있다.

  • 메서드 우선 순위
    • super class의 method 우선: super class가 구체적인 메서드를 갖는 경우 default method는 무시됨

    • interface 간의 충돌: 하나의 interface에서 default method를 제공하고 다른 interface에서도 같은 이름의 메서드(default 유무와 무관) 가 있을 때 sub class는 반드시 override 해서 충돌 해결

  • static method → interface에 선언된 static method
    • 일반 static 메서드와 마찬가지로 별도의 객체가 필요 없음
    • 구현체 클래스 없이 바로 인터페이스 이름으로 메서드에 접근해서 사용 가능
interface StaticMethodInterface{
	static void staticMethod(){
		System.out.println("static 메서드");
	}
}

public class StaticMethodTest{
	public static void main(String[] args){
		StaticMethodInterface.staticMethod();
	}
}

Interface & Inheritance Code 예제


요구사항
/*  
[출력 결과]
이름 : 홍길동              <--- Family의 toString()에서 작성
아빠는 나가서 일을 한다       <--  Job인터페이스를 상속받아서 Father의  work()에서 작성 
														 
이름 : 김순희
엄마는 집안일을 한다

이름 : 홍돌이
아들은 공부를 한다 
*/

class Family{
	private String name;
	public Family() {}
	public Family(String name) {
		this.name = name;
	}
	
	@Override
	public String toString(){
		return "이름 : "+name +"\n";
	}
}

interface Job{
	public String work();
}

class Father extends Family implements Job{
	public Father() {}
	public Father(String name) {
		super(name);
	}
	
	@Override
	public String work() {
		return "아빠는 나가서 일을한다";
	}
	
	@Override
	public String toString() {	
		return super.toString()+this.work()+"\n";
	}
}

class Mother extends Family implements Job{
	
	public Mother() {}
	public Mother(String name) {
		super(name);
	}
	
	@Override
	public String work() {
		return "엄마는 집안일을 한다";
	}
	
	@Override
	public String toString() {	
		return super.toString()+this.work()+"\n";
	}
	
}

class Son extends Family implements Job{
	
	public Son() {}
	public Son(String name) {
		super(name);
	}
	
	@Override
	public String work() {
		return "아들은 공부를 한다";
	}
	
	@Override
	public String toString() {	
		return super.toString()+this.work()+"\n";
	}
}

public class MainEx {
	public static void main(String[] args) {
		//-------------------------------------------------
		Father ob1=new Father("홍길동");
		Mother ob2=new Mother("김순희");
		Son ob3=new Son("홍돌이");
		
		System.out.println(ob1);    // or  ob1.toString()
		System.out.println(ob2);
		System.out.println(ob3);
	}
}

이름 : 홍길동                    
아빠는 나가서 일을 한다       
					    
이름 : 김순희
엄마는 집안일을 한다

이름 : 홍돌이
아들은 공부를 한다 

똑같이 객체 출력을 했는데 실행되는 정보가 다르다 

참고 Java String 주의사항

    String s1="Hello";
		String s2="Hello";
		String s3=s2;
		
		String s4=new String("Hello");
		String s5=new String("Hello");
		
		System.out.println(s1==s2); //true
		System.out.println(s2==s3); //true
		System.out.println(s1.equals(s2)); //true
		System.out.println(s2.equals(s3)); //true
		
		System.out.println(s4==s5);    //false
		System.out.println(s4.equals(s5));  //true

문자를 비교할 땐 str1.equals(str2)로 하자 str1 == str2로 쓰는 거 보단

0개의 댓글