Polymorphism(다형성)

1c2·2024년 1월 18일
0

JAVA

목록 보기
6/13

데이터 은닉과 보호

class UnbelievableUserInfo {
    // 이름은 null이 될 수 없음.
    public String name = "홍길동";
    // 계좌는 0보다 커야 함.

    public int account = 10000;

    // TODO: name 과 account에 부적절한 값이 할당되지 못하도록 처리하시오.
    //  name과 account 는 private으로 변경되어야 한다.

    // END
}

public class UnbelievableTest {
    public static void main(String[] args) {
        UnbelievableUserInfo info = new UnbelievableUserInfo();
        System.out.printf("사용자 정보:%s, %d%n", info.name, info.account);
        info.name = null;
        info.account = -1000;
        System.out.printf("사용자 정보:%s, %d%n", info.name, info.account);
    }
}

위 코드를 아래 처럼 변경하여 정보를 은닉 및 보호할 수 있다.

class UnbelievableUserInfo {
    // 이름은 null이 될 수 없음.
    private String name = "홍길동";
    // 계좌는 0보다 커야 함.

    private int account = 10000;

    // TODO: name 과 account에 부적절한 값이 할당되지 못하도록 처리하시오.
    //  name과 account 는 private으로 변경되어야 한다.
	public String getName() {
		return name;
	}

	public void setName(String name) {
		if(name == null) {
			System.out.println("name is not null"); // 보호
			return;
		}
		this.name = name;
	}

	public int getAccount() {
		return account;
	}

	public void setAccount(int account) {
		if(account < 0) {
			System.out.println("account must bigger than zero");
			return;
		}
		this.account = account;
	}
    // END
}

public class UnbelievableTest {
    public static void main(String[] args) {
        UnbelievableUserInfo info = new UnbelievableUserInfo();
        System.out.printf("사용자 정보:%s, %d%n", info.getName(), info.getAccount());
        info.setName(null);
        info.setAccount(-1000);
        System.out.printf("사용자 정보:%s, %d%n", info.getName(), info.getAccount());
    }
}

객체의 생성 제어와 Singleton 디자인 패턴

  • 객체의 생성을 제한해야 한다면?

    • 여러 개의 객체가 필요 없는 경우
      • 객체를 구별할 필요가 없는 경우 = 수정 가능한 멤버 변수가 없고 기능만 있는 겨웅
      • 이런 객체를 stateless한 객체라고 한다.
    • 객체를 계속 생성/삭제 하는데 많은 비용이 들어서 재사용이 유리한 경우
  • Singleton 디자인 패턴

    • 외부에서 생성자에 접근 금지 -> 생성자의 접근 제한자를 private으로 설정

    • 내부에서는 private에 접근 가능하므로 직접 객체 생성 -> 멤버 변수이므로 private 설정

    • 외부에서 private member에 접근 가능한 getter 생성 -> setter는 불필요

    • 객체 없이 외부에서 접근할 수 있도록 getter와 변수에 static 추가

    • 외부에서는 언제나 getter를 통해서 객체를 참조하므로 하나의 객체 재사용

  class SingletonClass {
  // TODO:SingletonClass에 Singleton Design Pattern을 적용하시오.
  private SingletonClass() {
	  super();
  }
  
  private static SingletonClass instance = new SingletonClass();
  
  public static SingletonClass getInstance() {
	  return instance;
  }
  // END
  public void sayHello() {
    System.out.println("Hello");
  }

}
  public class SingletonTest {
    public static void main(String[] args) {
      // TODO:SingletonClass를 사용해보세요.
      SingletonClass sc1 = SingletonClass.getInstance();
      SingletonClass sc2 = SingletonClass.getInstance(); 
      //sc1 == sc2 이다.
      sc1.sayHello();
      
      // END
    }
  }

다형성

  • 다형성 - 하나의 객체가 많은 형을 가질 수 있는 성질

  • 다형성이 존재하지 않는다면 붕어빵 판매에는 어떤 변화가 있을까?

다형성 활용 예 1 - 다른 타입의 객체를 다루는 배열

  • 배열이 특징 = 같은 타입의 데이터를 묶음으로 다룬다.
void beforePoly(){
	Person [] persons = new Person[10];
    persons[0] = new Person();
    SpiderMan[] spiderMans = new Spider[10];
    spiderMans[0] = new SpiderMan();
}

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

위와 같이 Person을 상속하는 SpiderMan 클래스 또한 Class와 같이 배열로 묶을 수 있다.

  • Object는 모든 클래스의 조상

    • Object의 배열은 어떤 타입의 객체라도 모두 저장 가능
  • 자바의 자료 구조를 간단하게 처리할 수 있음

    • 이와 같은 특성을 이용하여 Collection API가 등장하게 됨
    public ArrayList(int initialCapacity){
    	if(initalCapacity > 0){
      	this.elemantData = new Object[initalCapacity];
      }else if(initialCapacity == 0){
      	this.elementData = EMPTY_ELEMENTDATA;
      } else{
      	throw new IllegalArgumentException("Illegal Capacity");
      }
    }

다형성의 활용 예 2 - 매개변수의 다형성

다형성과 참조형 객체의 형 변환

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

    Person person = new SpiderMan();

    person은 SpiderMan의 기능을 모른다.

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

참조형 객체의형 변환

  • 하위 타입을 상위 타입으로 형 변환 -> 묵시적 캐스팅
Phone phone = new Phone();
Object obj = phone;
  • 상위 타입을 하위 타입으로 형 변환 -> 명시적 캐스팅
Phone phone = new SmartPhone();
SmartPhone sPhone = (SmartPhone)phone;
  • 무늬만 SpiderMan인 Person
person person = new Person();
SpiderMan sman = (SpiderMan) person;
sman.fireWeb();
  • 메모리 객체는 fireWeb이 없음

    컴파일은 됨 -> 하지만 런타임 에러 발생

  • instanceof 연산자

    • 실제 메모리에 있는 객체가 특정 클래스 타입인지 boolean으로 리턴
    Person person = new Person();
    if(person instanceof SpideMan){
    	SpiderMan sman = (SpiderMan) person;
    }

참조 변수의 레벨에 따른 객체의 멤버 연결

  • 정적 바인딩

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

    • 다형성을 이용해서 메서드 호출이 발생할 때 runtime에 메모리의 실제 객체의 타입으로 결정

    • 상속 단계에서 객체의 instance method가 재정의 되었을 때 마지막에 재정의 된 자식 클래스의 메서드가 호출됨
      * 최대한 메모리에 생성된 실제 객체의 최적화 된 메서드가 동작한다.

      public class Person{
      	...
         @Override
         public String toString() {
         	return "사람이름 : " + this.name;
         }
      }
      
       public class PolyTest {
           public static void main(String[] args) {
               SpiderMan sman = new SpiderMan();
               System.out.println(sman); // obj의 toString()이 출력됨 -> Person의 toString()이 출력됨
           }
       }
       
       public class SpiderMan extends Person{
         ...
      	@Override
      	public String toString() {
      		return "SpiderMan [isSpider=" + isSpider + ", toString()=" + super.toString() + "]";
           }
       }
       
       public class PolyTest {
           public static void main(String[] args) {
               SpiderMan sman = new SpiderMan();
               System.out.println(sman); // obj의 toString()이 출력됨 -> Person의 toString()이 출력됨 -> SpiderMan의 toString()이 출력됨
           }
       }

참조 변수의 레벨에 따른 객체의 멤버 연결

  • 용도에 따른 가장 적합한 메서드 구성은?

    public class AppropriateParameter {
       public void useJump1(Object obj) {
           if (obj instanceof Person) {
               Person casted = (Person) obj;
               casted.jump();
           }
       }
    
       public void useJump2(Person person) {
           person.jump();
       }
    
       public void useJump3(SpiderMan spiderMan) {
           spiderMan.jump();
       }
    
       public static void main(String[] args) {
           Object obj = new Object();
           Person person = new com.ssafy.day3.a_inheritance.Person();
           SpiderMan sman = new com.ssafy.day3.a_inheritance.SpiderMan();
    
           AppropriateParameter ap = new AppropriateParameter();
           // TODO:ap의 useJumpX를 obj, person, sman으로 호출해보세요.
           
           // 호출 가능하지만 실제로 jump할 수 없다.
           ap.useJump1(obj);
           ap.useJump1(person);
           ap.useJump1(sman);
           
           //ap.useJump1(obj);
           ap.useJump1(person);
           ap.useJump1(sman);
           
           //ap.useJump1(obj);
           //ap.useJump1(person);
           ap.useJump1(sman);
           
          
           // END
    
       		} // end of main
    
    	}//end of class

    Object는 jump를 못하니까 Person까지 매서드 정의하는게 가장 적절하다.

Object 클래스

  • 가장 최상위 클래스로 모든 클래스의 조상
    • Object의 멤버는 모든 클래스의 멤버

toString 메서드

  • 객체를 문자열로 변경하는 메서드

equals 메서드

  • 두 객체가 같은지를 비교하는 메서드

hashCode

  • 객체의 해시 코드
  • equals 메서드를 재정의 할 때는 반드시 hashCode도 재정의할 것
  • HashSet, HashMap 등에서 객체의 동일성을 확인하기 위해 사용
public class Product extends Object{
    private String sn;
    private int age;

    public Product(String sn) {
        this.sn = sn;
    }

	@Override
	public String toString() {
		return "Product [sn=" + sn + "]";
	}

    // TODO: toString, equals, hashCode를 적절히 재정의해보자.
    @Override
    public boolean equals(Object obj) {
    	if(obj instanceof Product) {
    		Product p = (Product) obj;
    		return this.sn.equals(p.sn);
    	}
    	return false;
    }
    @Override
    public int hashCode() {
    	//return Integer.valueOf(sn);
    	//Wrapper class, String class, Object class의 HashCode()를 활용해서 구현한다.
    	return (sn+age).hashCode(); //이런식으로 구현 가능
    }
    // END
}

0개의 댓글