JAVA공부 [4. JAVA의 기본 문법(4) ]
1. 🔥 다형성
다형성은 재정의(overriding)을 통해 하나의 함수 모양을 가지고 각기 다른 역할을 하는 것을 의미한다..!
- 오버로딩(overloading)소극적 의미의 다형성에 해당된다.
overriding
오버라이딩은 상속을 통해 메소드 바꿔치기, 덮어쓰기 즉, 재정의를 할 수 있게 되는데 이 부분이 다형성의 확정개념이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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를 가르켜서 해당 메서드를 호출한다면 자동으로 오버라이딩된 자식의 메서드를 호출하게 된다..!
- 바인딩의 용어 처럼 함수의 이름(오버라이딩된)과 실제함수를 연결시켜주는것 가르키고 있는 객체에는 부모의 메서드 이름과 동일한 즉 오버라이딩 된 메서드가 존재하고 그것을 동적으로 판단하여 실행시키는 것..!
조금 더 다형성의 의미를 확장하여 동적바인딩을 살펴보자면
1
2
3
4
5
6
  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를 맞게 호출하게 때문에 동적이라고 할 수 있다.
객체지향의 꽃이며 코드가 간결해지고 읽기 편해짐!
내가 생각한 예제
- 게임을 만든다고 예를 들자면 몬스터라는 클래스를 상속 받은 오크, 고블린, 슬라임 등등이 있다.
- 이 때, 공격이라는 메서드는 몬스터라는 클래스로 부터 상속받아 각각 재정의 되어 있다.(전혀 다르지 않은 경우 재정의하지 않아도 됌 하지만 일반적으로 공격메서드의 경우 전부 각각에 맞게 재정의)
- 그렇다면 코드를 일반화 하는 과정에서 몬스터클래스들(상속받은 클래스 포함 업캐스팅을 말함)의 공격메서드를 실행한다면
- 각각 클래스에 맞게 메서드 실행코드를 만든다면 코드가 길어지고 보기 않 좋아 진다. 하지만 위와 같이 배열로 일반화가 가능하다.
확장 개념(링크드 리스트)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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키워드를 이용하면 기능추가의 개념으로 이해할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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키워드를 통해 부모의 원본 메서드를 호출할 수 있다.
정리
- 수퍼클래스의 참조변수는 서브클래스 객체를 참조할 수 있다.
    - 역은 특별한 경우에 성립하게 되는데 원래 가르키고 있는 객체의 참조변수로 명시적 변환이 가능하다.
 
- instanceof연산자는 실제 그 클래스가 맞는지 확인하는 연산자(실수 없는 코딩을 위해 사용함)
- 다형성은 코드의 간결성, 통일성, 협업을 할 때 아주 유용함
- 어떠한 타입으로도 업캐스팅하고 싶다면 Object타입으로 정의하는 것이 좋다.
1.1. 추상 메서드
추상 메서드는 두가지 목적으로 분류가 가능하다 첫 번째는 shape같이 도형이긴 하지만 어떤 도형인지 모르기 때문에 그리지 못한다.. 즉 추상적이라 구체적인 방법이 마련되지 않았다. 두번째는 이러한 메서드가 있으니 상속받은 클래스들의 구현을 강제할 수 있다.
- 
    정리하면 추상 메서드는 아직 구체화 되지않았지만 꼭 필요할 것 같은 것을 말한다. 
- 
    또한 클래스에 한가지 추상메서드를 만들면 해당 클래스는 추상클래스로 인식되게 된다. 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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개념! 
정리
- 추상메서드란 선언은 되어있지만 구현이 되지 않았고 abstract키워드로 선언한다.
- 추상 메서드는 서브클래스에서 구현을 강제함
- 추상 클래스는 추상 메서드를 하나라도 가진 클래스..! (abstract표기 강제함)
- 추상 메서드가 없지만 abstract러 선언된 클래스
- 추상 클래스는 객체를 만들 수 없다..! 참조변수로는 사용가능
    - 즉 동적 바인딩에 활용할 수 있다.
 
이러한 개념들은 계층적인 상속관계를 가지는 클래스구조를 만들 때 사용된다.
1.2. interface
인터페이스는 클래스와 클래스를 연결해주는 것이 인터페이스
- 자바는 c++과 다르게 다중상속을 제한하고 있기 때문에 무선전화기(전화기로 부터 상속받은)에 바테리를 넣고 싶다면 interface를 상속받아 부가적인 기능을 따로 해결해야한다..!(약속)
만들때 interface키워드 상속받을 때 implements키워드 사용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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는 추상메서드밖에 표현을 못함 즉, 인터페이스 참조변수를 통해 업캐스팅한 과정은 기능적인 측면이라고 볼 수 있음 
- 
    또한 어떠한 메서드가 보장된다, 구현되어 있다를 명확하게 알려줌 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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)
    - 인터페이스의 기능을 추가,확장한다는 의미
 
추성클래스와 인터페이스의 유사점 객체를 생성할 수 없으며 상속을 위한 슈퍼클래스로만 사용, 다형성의 목적을 둔다.
정리
- 인터페이스의 주된 용도는 약속된 기능을 보장한다.(업캐스팅을 통한 인터페이스를 상속받은 클래스들의 기능을 일괄적으로 다룰 수 있다.)
- 하나의 클래스가 두 개의 인터페이스를 구현할 수 있다..!
2. 인터페이스의 활용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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(인터페이스)를 구현하지 않았기 때문..!
가능하게 할려면??
1
2
3
4
5
6
7
8
9
10
11
12
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타입으로 모든 클래스를 받아서 내가 명시적으로 다운캐스팅을 통해 비교메서드를 작성해주면 모든 데이터형을 정렬할 수 있다..!
활용++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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언어의 구조체와 유사하다!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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. 무명클래스
클래스 몸체는 정의되지만 이름이 없는 클래
인터페이스는 이름은 존재하지만 메서드의 몸체가 정의되지 않는다..부족한 부분을 채워주자,,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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("인자값으로 전달된 무명클래스");
            }
        });
    }
}
- 무명클래스란 인터페이스나 추상클래스의 구현이 강제되는 메서드를 무명클래스로 클래스 내부에서 구현하여 따로 클래스를 만드는 번거로움 없이 해당 자리에서 구현할 수 있는 클래스이다..!
정리
- 무명클래스 작성시 new다음에 적어야하는 것은 추상클래스나 인터페이스의 이름이다.
- 특정 인터페이스의 특정 메서드의 값을 넘겨주고 싶을 때 사용하면 따로 클래스를 만들 지 않을 수 있어서 간편하다.(가독성++)
5. 람다식
최근 언어의 추세에 맞게 새로 쓰이는 문법이다. 인터페이스를 더욱 간단하게 사용가능하다.
여러가지 조건이 존재한다.
- 함수가 하나일 경우에 사용가능하다.
- 명확한 조건일 경우 해당 조건들은 생략 가능하다.
    - 조건들을 생략해야지 사용자의 실수가 줄어들기 때문
 
1
2
3
4
5
6
7
8
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; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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인터페이스도 활용가능
 
      
댓글남기기