1. 🔥 다형성

다형성은 재정의(overriding)을 통해 하나의 함수 모양을 가지고 각기 다른 역할을 하는 것을 의미한다..!

  • 오버로딩(overloading)소극적 의미의 다형성에 해당된다.

overriding

오버라이딩은 상속을 통해 메소드 바꿔치기, 덮어쓰기 즉, 재정의를 할 수 있게 되는데 이 부분이 다형성의 확정개념이다.

class Shape{
    int x, y;
    String color;

    Shape(int _x, int _y, String c){
        x = _x;
        y = _y;
        color = c;
    }
    
    void draw(){
        System.out.println("x = "+x+", y = "+y+" color = "+color);
    }
}

class Rect extends Shape{
    int w,h;
    Rect(int _x, int _y, int _w,int _h, String c){
        super(_x,_y,c);
        w = _w;
        h = _h;
    }
    // void drawRect(){ 
    //     System.out.println("x = "+x+", y = "+y+" color = "+color+" | ("+w+","+h+") ");
    // }
    void draw(){ // 재정의(overriding) 부모의 메서드를 덮어씀
        System.out.println("x = "+x+", y = "+y+" color = "+color+" | ("+w+","+h+") ");
    }
}

class Circle extends Shape{
    int r;
    Circle(int _x, int _y, int _r, String c){
        super(_x, _y, c);
        r = _r;
    }
    void draw(){ // overriding
        System.out.println("x = "+x+", y = "+y+" color = "+color+" | "+r);
    }
}

public class App {
    public static void main(String[] args) throws Exception {
        Rect a = new Rect(10, 20, 30, 40, "White");
        Circle b = new Circle(10, 10, 5, "black");
        a.draw();
        b.draw();

        Shape sh;
        sh = a; //업캐스팅
        sh.draw(); //Rect draw호출 dynamic binding(동적 바인딩)
        sh = b; //업캐스팅
        sh.draw(); //Circle draw호출
    }
}
  • 왜 shape레퍼런스 변수를 만들었는데 shape의 draw가 호출되지 않고 해당 객체의 draw가 호출 될까? (여전히 필드참조 불가능)

dynamic binding(동적 바인딩)

A로부터 상속 받은 B가 존재할 때 B는 A의 메서드를 오버라이딩하였다. 이 때 업캐스팅을 통해 부모의 레퍼런스 변수가 B를 가르켜서 해당 메서드를 호출한다면 자동으로 오버라이딩된 자식의 메서드를 호출하게 된다..!

  • 바인딩의 용어 처럼 함수의 이름(오버라이딩된)과 실제함수를 연결시켜주는것 가르키고 있는 객체에는 부모의 메서드 이름과 동일한 즉 오버라이딩 된 메서드가 존재하고 그것을 동적으로 판단하여 실행시키는 것..!

조금 더 다형성의 의미를 확장하여 동적바인딩을 살펴보자면

  Shape [] shapes = new Shape[3]; //레퍼런스 변수 3개 배열로 생성
  shapes [0] = new Shape(10, 20, "blue"); 
  shapes [1] = new Rect(100, 100, 5, 5,"white");//업캐스팅..
  shapes [2] = new Circle(1,2,3, "red"); //업캐스팅..
  for(Shape elem : shapes)
      elem.draw(); //동적바인딩
  • elem.draw()로 하나의 메서드를 여러번 호출하지만 각각 다른 메서드를 호출하고 있다. 즉, 다형성이다.

그때 그때(실행시간 중/ 런타임 중) 해당 객체가 어떤 형태인지 보고 draw를 맞게 호출하게 때문에 동적이라고 할 수 있다.

객체지향의 꽃이며 코드가 간결해지고 읽기 편해짐!


내가 생각한 예제

  • 게임을 만든다고 예를 들자면 몬스터라는 클래스를 상속 받은 오크, 고블린, 슬라임 등등이 있다.
  • 이 때, 공격이라는 메서드는 몬스터라는 클래스로 부터 상속받아 각각 재정의 되어 있다.(전혀 다르지 않은 경우 재정의하지 않아도 됌 하지만 일반적으로 공격메서드의 경우 전부 각각에 맞게 재정의)
  • 그렇다면 코드를 일반화 하는 과정에서 몬스터클래스들(상속받은 클래스 포함 업캐스팅을 말함)의 공격메서드를 실행한다면
  • 각각 클래스에 맞게 메서드 실행코드를 만든다면 코드가 길어지고 보기 않 좋아 진다. 하지만 위와 같이 배열로 일반화가 가능하다.

확장 개념(링크드 리스트)

class Shape{
    Shape next; //레퍼런스 변수(포인터 같이 동작함)
    // 부모에서 next변수를 만드는 순간 자식들은 기본적으로 가지고 있게 된다.
    생략
}

public class App {
    public static void main(String[] args) throws Exception {

        Shape start = new Shape(1, 2, "blue"); //해당 객체에는 next 즉, shape를 가리키는 레퍼런스 변수가 존재
        start.next = new Rect(10,10,3,3,"red"); // 해당 레퍼런스 변수를 객체를 가리키게함
        start.next.next = new Circle(3, 3, 3, "yellow"); //반복 현재는 null을 가리킴

        Shape cur = start;
        while(cur != null){
            cur.draw();
            cur = cur.next; //이동
        }
    }
}
  • 이렇게만 봐도 c언어보다 보기 좋고 이해가 퐉! 된다..!

오버라이딩 기능추가 개념

지금까지는 오버라이딩이 재정의 덮어쓰기의 기능이였다면 super키워드를 이용하면 기능추가의 개념으로 이해할 수 있다.

class Shape{
    int x, y;
    String color;

    Shape(int _x, int _y, String c){
        x = _x;
        y = _y;
        color = c;
    }
    
    void draw(){
        System.out.println("x = "+x+", y = "+y+" color = "+color);
    }
}

class Rect extends Shape{
    int w,h;
    Rect(int _x, int _y, int _w,int _h, String c){
        super(_x,_y,c);
        w = _w;
        h = _h;
    }
   
    void draw(){ 
        super.draw(); //부모의 draw를 호출 한뒤
        System.out.println("| ("+w+","+h+") "); //내가 원하는 기능 추가
    }
}
  • 이렇게 super키워드를 사용하면 코드 재사용을 줄이고 충돌을 예방할 수 있다.

상속받은 클래스는 super키워드를 통해 부모의 원본 메서드를 호출할 수 있다.

정리

  1. 수퍼클래스의 참조변수는 서브클래스 객체를 참조할 수 있다.
    • 역은 특별한 경우에 성립하게 되는데 원래 가르키고 있는 객체의 참조변수로 명시적 변환이 가능하다.
  2. instanceof연산자는 실제 그 클래스가 맞는지 확인하는 연산자(실수 없는 코딩을 위해 사용함)
  3. 다형성은 코드의 간결성, 통일성, 협업을 할 때 아주 유용함
  4. 어떠한 타입으로도 업캐스팅하고 싶다면 Object타입으로 정의하는 것이 좋다.

1.1. 추상 메서드

추상 메서드는 두가지 목적으로 분류가 가능하다 첫 번째는 shape같이 도형이긴 하지만 어떤 도형인지 모르기 때문에 그리지 못한다.. 즉 추상적이라 구체적인 방법이 마련되지 않았다. 두번째는 이러한 메서드가 있으니 상속받은 클래스들의 구현을 강제할 수 있다.

  • 정리하면 추상 메서드는 아직 구체화 되지않았지만 꼭 필요할 것 같은 것을 말한다.

  • 또한 클래스에 한가지 추상메서드를 만들면 해당 클래스는 추상클래스로 인식되게 된다.

abstract class Shape{ //추상 메서드가 있어 추상 클래스로 선언되어야 함
    int x, y;
    String color;

    Shape(int _x, int _y, String c){
        x = _x;
        y = _y;
        color = c;
    }
    
    abstract void draw(); // 추상 메서드 선언, 메서드 바디 없음
}

class Line extends Shape{ //빨간줄 에러..! 반드시 정의해야하는 추상 메서드가 없음
    String dir;
    Line(int _x,int _y, String c, String d){
        super(_x, _y, c);
        dir = d;
    }
}

그렇다면 왜 사용하는 거지?

추상 클래스(즉 추상메서드)는 객체화가 되지 않기 때문에(추상) 자기 자신을 이용해서 서브클래스를 만들어라 라는 의미를 가지고 있다..!

  • 생물 -> 사람, 개구리, 장수풍뎅이 등등… 생물의 행동을 정의할 수 없고 추상적으로 표현 가능 즉, 메서드가 존재한다면 숨을 쉰다정도?

  • 하지만 레퍼런스 변수로는 사용가능하다!(업캐스팅) Object개념!

정리

  1. 추상메서드란 선언은 되어있지만 구현이 되지 않았고 abstract키워드로 선언한다.
  2. 추상 메서드는 서브클래스에서 구현을 강제함
  3. 추상 클래스는 추상 메서드를 하나라도 가진 클래스..! (abstract표기 강제함)
  4. 추상 메서드가 없지만 abstract러 선언된 클래스
  5. 추상 클래스는 객체를 만들 수 없다..! 참조변수로는 사용가능
    • 즉 동적 바인딩에 활용할 수 있다.

이러한 개념들은 계층적인 상속관계를 가지는 클래스구조를 만들 때 사용된다.

1.2. interface

인터페이스는 클래스와 클래스를 연결해주는 것이 인터페이스

  • 자바는 c++과 다르게 다중상속을 제한하고 있기 때문에 무선전화기(전화기로 부터 상속받은)에 바테리를 넣고 싶다면 interface를 상속받아 부가적인 기능을 따로 해결해야한다..!(약속)

만들때 interface키워드 상속받을 때 implements키워드 사용

interface NetworkControl{
    void turnOn(); //abstract가 붙어있음(생략가능) interface자체가 추상적이기 때문..!
    void turnOff();//public또한 생략되어 있음
    //public abstarct void turnOff(); 풀네임
}

class OldTV{

}
//클래스 상속과 인터페이스 상속도 같이 일어날 수 있음
class TV extends OldTV implements NetworkControl{
    public void turnOn() { //interface에서는 생략했지만 접근지정자는 통일해야함
        System.out.println("TV ON");
    }
    public void turnOff() { // interface를 implements하는 순간 매서드 구현을 강제함
        System.out.println("TV OFF");
    }
}

class Aircon implements NetworkControl{
    public void turnOn() { 
        System.out.println("Aircon ON");
    }
    public void turnOff() { 
        System.out.println("Aircon OFF");
    }
}

public class InterfaceText {

    static void On(NetworkControl c){ // 인터페이스 참조 변수로 받음 즉, 업캐스팅
        c.turnOn();
    }
    public static void main(String[] args){
        TV tv = new TV();
        Aircon ac = new Aircon();
        On(ac);
        On(tv);
    }
}
  • interface는 추상메서드밖에 표현을 못함 즉, 인터페이스 참조변수를 통해 업캐스팅한 과정은 기능적인 측면이라고 볼 수 있음

  • 또한 어떠한 메서드가 보장된다, 구현되어 있다를 명확하게 알려줌

interface NetworkControl{ //원격연결 기능
    void turnOn();
    void turnOff();
}

interface Portable{ //충전기능
    public abstract void charge(); //public abstarct생략가능..!
}

class TV implements NetworkControl, Portable{
    public void turnOn() {
        System.out.println("TV ON");
    }
    public void turnOff() { 
        System.out.println("TV OFF");
    }
    public void charge() {
        System.out.println("Charge..!");
    }
}
  • 이렇게 인터페이스는 여러개를 추가할 수 있다..!

정리하자면 본질은 TV이지만 NetworkControl, Portable인터페이스의 기능을 지닌 클래스라고 할 수 있다..!

특정기능의 구현을 보장함

추가적인 부분 && 정리

  • final 상수는 추가 가능함..! public static final int MAX = 100;
    • 기능 내부에서 사용할 수 있는 상수값을 넣어줘야 하기 때문..!
  • 디폴트 메서드 또한 구현 가능..!
    • 구현하지 않으면 디폴트메서드가 실행된다.
  • private메서드는 해당 인터페이스 내에서 호출하여 사용
  • 인터페이스 객체 생성 불가능
    • 인터페이스 타입의 레퍼런스 변수는 생성가능
  • 인터페이스 다중 상속 가능..!
  • 인터페이스 끼리의 상속도 가능(extends)
    • 인터페이스의 기능을 추가,확장한다는 의미

추성클래스와 인터페이스의 유사점 객체를 생성할 수 없으며 상속을 위한 슈퍼클래스로만 사용, 다형성의 목적을 둔다.

정리

  1. 인터페이스의 주된 용도는 약속된 기능을 보장한다.(업캐스팅을 통한 인터페이스를 상속받은 클래스들의 기능을 일괄적으로 다룰 수 있다.)
  2. 하나의 클래스가 두 개의 인터페이스를 구현할 수 있다..!

2. 인터페이스의 활용

import java.util.Arrays; 
//정렬을 위한 유틸패키지의 Arrays클래스 가져옴
class MyData{ //int형 데이터를 저장하는 클래스
    MyData(int in){
        data = in;
    }
    int data;
}

public class InterfaceText2 {

    public static void main(String[] args){
        int [] arr = {1,2,54,9,58,6,6,4,2}; //무순정렬 배열
        for(int a : arr)
            System.out.println(a); 

        System.out.println("-----------------------");
        Arrays.sort(arr); //Arrays클래스의 static메서드, 정렬을 실행함
        for(int a : arr)
        System.out.println(a);


        System.out.println("-----------------------");
        MyData [] myArr = new MyData[10];
        for(int i = 0;i < myArr.length; i++)
            myArr[i] = new MyData((int)(Math.random()*100)); //난수발생(int)형변환
        for(var a : myArr){
            System.out.println(a.data);
        }
        System.out.println("-----------------------");
        Arrays.sort(myArr); //sort에 Object도 정렬이 가능하기 때문에 내가 만든 클래스의 객체를 정렬
        //error출력! java.lang.Comparable(인터페이스)를 구현하지 않았기 때문에 오류 발생
        for(var a : myArr){ //var키워드 사용
            System.out.println(a.data); 
        }
    }
}
  • MyData객체 myArr이가 정렬이 실행되지 않은 이유는 Comparable(인터페이스)를 구현하지 않았기 때문..!

가능하게 할려면??

class MyData implements Comparable{ //Comoarable인터페이스 상속
    MyData(int in){
        data = in;
    }
    int data;
    @Override //오버라이딩키워드..! 오버라이딩메서드인지 확인할 수 있다..!
    public int compareTo(Object o) { //해당 인터페이스 정렬에 사용하는 메서드 구현
        if(!(o instanceof MyData)) return 0;
        MyData in = (MyData)o; //다운캐스팅
        return data - in.data; //같으면 0반환, 크면 양수, 작으면 음수
    }
}
  • 기본형이 Object타입으로 모든 클래스를 받아서 내가 명시적으로 다운캐스팅을 통해 비교메서드를 작성해주면 모든 데이터형을 정렬할 수 있다..!

활용++

interface Printable{ //인터페이스 구현
    void print(); //public abstract 생략
}

class MyData_2 implements Printable{ //Printable상속
    MyData_2(int in){
        data = in;
    }
    int data;
    @Override //문법적 오류 검증
    public void print(){
        System.out.println("data : "+data);
    }
}

public class InterfaceText2 {

    public static void printAll(Printable []arr){ //Printable 참조변수 배열로 인자값을 받음 즉. 업캐스팅
        for(Printable e : arr)
            e.print(); //동적바인딩(오버라이딩에 의한)
    } 

    public static void main(String[] args){

        MyData_2 [] myArr = new MyData_2[10];
        for(int i = 0;i < myArr.length; i++)
            myArr[i] = new MyData_2((int)(Math.random()*100));
        printAll(myArr);
    }
}

3. 내부클래스

내부클래스는 클래스안에 선언된 클래스를 의미한다..!

  • 내부적으로 잠깐 쓰이는 클래스를 정의해서 사용하는 것인데 이는 c언어의 구조체와 유사하다!
class MyTriangle{
    private class Point{ //private으로 가려놓았기 때문에 밖에서 보이지 않음
        int x, y;
    }

    Point[] pts;
    MyTriangle(){
        pts = new Point[3];
    }
}

public class InterfaceText2 {

    public static void main(String[] args){

        MyTriangle a = new MyTriangle();
    }
}
  • 사용목적은 어디선가 사용했던것 같은 클래스이거나 내부적으로만 사용되는 클래스를 만들 때 사용한다. -> 클래스 이름의 충돌을 막아줌

이러한 내부클래스를 인스턴스 클래스라고 부름

중요

내부 클래스에서는 내부클래스가 속한 클래스 즉, 내부클래스의 외부클래스의 필드에 접근이 가능하다.

  • 내부클래스 관점에서는 전역변수처럼 사용 가능..!

4. 무명클래스

클래스 몸체는 정의되지만 이름이 없는 클래

인터페이스는 이름은 존재하지만 메서드의 몸체가 정의되지 않는다..부족한 부분을 채워주자,,

interface Printable{ //Printable을 상속받는 클래스들은 print기능을 강제함
    void print(); 
}

public class InterfaceText2 {

    static Printable head;

    static void print(Printable p){ //Printable을 가지고 있는 모든 객체를 Printable레퍼런스 변수로 받음
        p.print(); // 해당 객체의 print기능 실행 다형성 유도
    }

    public static void main(String[] args){
        head = new Printable(){ //무명클래스 
            @Override
            public void print() {
                System.out.println("무명클래스"); //
            }
        };
        head.print();
        Printable o = new Printable(){
            @Override
            public void print() {
                System.out.println("객체에 넣어진 무명클래스");
            }
        };
        print(o);

        print(new Printable(){
            @Override
            public void print() {
                System.out.println("인자값으로 전달된 무명클래스");
            }
        });
    }
}
  • 무명클래스란 인터페이스나 추상클래스의 구현이 강제되는 메서드를 무명클래스로 클래스 내부에서 구현하여 따로 클래스를 만드는 번거로움 없이 해당 자리에서 구현할 수 있는 클래스이다..!

정리

  1. 무명클래스 작성시 new다음에 적어야하는 것은 추상클래스나 인터페이스의 이름이다.
  2. 특정 인터페이스의 특정 메서드의 값을 넘겨주고 싶을 때 사용하면 따로 클래스를 만들 지 않을 수 있어서 간편하다.(가독성++)

5. 람다식

최근 언어의 추세에 맞게 새로 쓰이는 문법이다. 인터페이스를 더욱 간단하게 사용가능하다.

여러가지 조건이 존재한다.

  1. 함수가 하나일 경우에 사용가능하다.
  2. 명확한 조건일 경우 해당 조건들은 생략 가능하다.
    • 조건들을 생략해야지 사용자의 실수가 줄어들기 때문
print(new Printable(){ //무명클래스
    @Override
    public void print() {
        System.out.println("인자값으로 전달된 무명클래스");
    }
});
//람다식
print(()->System.out.println("람다식으로 표현한 무명클래스.."));
  • 람다식은 명확한 값을 전부 지우고 정말 필요한 부분 함수의 정의부분(이름부분 다 날림)만 작성하는 것!

왜냐하면 print라는 메서드는 위에서도 그렇고 Printable이라는 인터페이스를 강제하기 때문에 생략가능.. 마찬가지로 내부 메서드가 한가지 print만 존재하기 때문에 생략가능…하다!

() -> 123.45라는 람다식은 이름은 명확하니 적지않고 return값이 123.45라는 무명클래스를 넘겨 주는 것

double myMath() { return 123.45; }

interface MyNum{
    int getNum();
}

interface MyNum_2{
    int getNumR(int min,int max);
}

public class InterfaceText2 {

    public static void main(String[] args){

        MyNum a = new MyNum(){ //무명클래스를 사용
            @Override
            public int getNum(){
                return (int)(Math.random()*100);
            }
        };
        //람다식 사용
        MyNum b = () -> (int)(Math.random()*100);

        MyNum_2 c = (int min,int max) -> (int)(Math.random()*((max - min) + 1) + min);
        
        System.out.println(a.getNum());
        System.out.println(b.getNum());
        System.out.println(c.getNumR(2, 7));
    }
}
  • 이 처럼 인자값을 넘겨줄 수 도 있다.

앞서 사용한 compareto인터페이스도 활용가능

태그: ,

카테고리:

업데이트:

댓글남기기