[JAVA]클래스(2)

yoon·2023년 12월 7일
0

java

목록 보기
7/19
post-thumbnail

✅ 상속

상속이란 기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것이다.

class Car {
   String color;
   int speed;
 }

class SprotsCar extends Car {}

이때 Car를 '조상 클래스', SportsCar를 '자손 클래스'라고 한다.

정리

  • 자손 클래스는 조상 클래스의 모든 멤버를 상속받는다.
    (단, 생성자는 상속되지 않음)
  • 자손 클래스 멤버 개수는 조상 클래스보다 항상 같거나 많다.

✔ 클래스 간의 관계

1. 상속 관계 (a is b)
SportsCar is Car 라는 문장은 성립되므로 상속 관계로 설정한다.

2. 포함 관계 (a has b)
Door is Car 라는 문장보다 Car has Door 이라는 문장이 더 맞으므로
둘은 포함 관계로 설정한다.

✔ 단일 상속

자바에서는 클래스의 다중 상속을 허용하지 않는다.
다중 상속을 사용하면 클래스간의 관계가 매우 복잡해지고, 서로 다른 클래스로부터 상속받은 멤버간의 이름이 같은 경우 구별할 수 있는 방법이 없다는 단점이 있다.

✔ Object 클래스 (모든 클래스의 조상)

모든 클래스 상속계층도의 최상위에 있는 조상 클래스이다.
그래서 자바의 모든 클래스들은 Object클래스의 멤버들을 상속 받기 때문에 Object클래스에 정의된 멤버들을 사용할 수 있다.

✔ 오버라이딩 (overriding)

조상 클래스로부터 상속받은 메서드의 내용을 변경하는 것을 말한다.

class Car {
    String color;
    int speed;

    void riding(){
        System.out.println("달리는 중");
    }
}

class SprotsCar extends Car {
    void riding(){
        System.out.println("스포츠카 달리는 중");
    }
}

오버라이딩 조건
1. 선언부가 조상 클래스의 메서드와 일치해야한다.
2. 접근 제어자는 조상 클래스의 메서드보다 좁은 범위로 변경 할 수 없다.
   → 주로 같은 범위의 접근 제어자를 사용한다.
3. 조상 클래스의 메서드보다 많은 수의 예외를 선언할 수 없다.

🔺 오버로딩 vs 오버라이딩

  • 오버로딩 : 기존에 없던 메서드를 정의하는 것
  • 오버라이딩 : 상속 받은 메서드를 변경하는 것

✔ super

앞에서 다뤘던 this와 비슷한 맥락으로 사용할 수 있는 참조변수이다.
this가 인스턴스 객체 자신을 나타냈다면 super는 조상 클래스라고 생각하면 쉽다.

public class Main {
    public static void main(String[] args) {
        SprotsCar s = new SprotsCar();
        s.checkColor();
    }
}

class Car {
    String color = "white";
    int speed;
}

class SprotsCar extends Car {
    String color = "red";

    void checkColor(){
        System.out.println("color : " + color); // red
        System.out.println("this.color : " + this.color); // red
        System.out.println("super.color : " + super.color); // white
    }
}

✔ super()

this()처럼 super()도 생성자이다. super()는 조상의 생성자를 호출한다.

class Car {
    String color = "white";
    int speed;

    Car(String color, int speed){
        this.color = color;
        this.speed = speed;
    }

}

class SprotsCar extends Car {
    boolean booster;

    SprotsCar(String color, int speed, boolean booster){
        super(color,speed);
        this.booster = booster;
    }
}

클래스 자신에 선언된 변수는 자신의 생성자가 초기화를 책임지도록 작성하는 것이 좋다. 참고로 생성자는 상속되지 않는다.

✅ 접근 제어자(access modifier)

접근 제어자가 사용될 수 있는 곳 > 클래스, 멤버변수. 메서드.생성자

  • private : 같은 클래스 내에서만 접근 가능
  • default : 같은 패키지 내에서만 접근 가능
  • protected : 같은 패키지 내, 다른 패키지의 자손클래스에서 접근 가능
  • public : 접근 제한 없음

✔ 접근 제어자를 사용하는 이유

   → 캡슐화

  • 외부로부터 데이터 보호
  • 외부에는 불필요한, 내부적으로만 사용되는 부분을 감추기 위해

✔ getter, setter

  • get함수 : 멤버 변수의 값을 반환
  • set함수 : 멤버 변수의 값을 변경 (조건에 맞는 값만 변경할 수 있도록 해줌)

캡슐화를 위해 멤버변수를 private나 protected로 제한하고, 멤버 변수의 값을 일고 변경할 수 있는 public메서드를 제공함으로써 멤버변수의 값을 다룰 수 있도록 하는 것이 좋다.
이를 위해 제공하는 것이 get, set 함수이다.

public class Car {
	private int speed;
    private String color;
    
    public int getSpeed(){return speed;}
    public String getColor(){return color;}
    public void setSpeed(int speed){
    	if(speed < 200) this.speed = speed;
        else this.speed = 0;
    }

}

만약 상속을 통해 클래스를 확장할 예정이라면 private대신 protected를 사용하면 된다.

✅ 다형성(polymorphism)

상위 클래스가 동일한 메시지로 하위클래스를 서로 다르게 동작시키는 객체지향 이론(=message polymorphism)

자바에서는 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 구현하였다.

	class Car {
    	String color;
        int speed;
    }
    class SportsCar extends Car {
    	boolean booster;
    }
    
    //참조 변수와 인스턴스의 타입 일치
    SportsCar s = new SportsCar();
    
    //조상 타입 참조변수 & 자손 타입 인스턴스
    Car c = new SportsCar(); //upcasting

보통 인스턴스 타입과 참조 타입을 일치시키지만, 상속 관계에 있다면 위와 같이 작성할 수 있다.
→ 즉, 조상 클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조하도록 할 수 있다. 반대의 경우는 성립되지 않는다. 그 이유는 기존 멤버 개수보다 참조 변수가 사용할 수 있는 멤버 개수가 더 많기 때문이다.

인스턴스 s와 인스턴스 c는 같은 타입의 인스턴스이지만, 참조변수 타입에 따라 사용할 수 있는 멤버의 개수는 다르다.
→ s는 sportscar에 있는 모든 멤버를 사용할 수 있지만, c는 car에 있는 멤버만을 사용할 수 있다.

✔참조변수의 형변환

서로 상속관계에 있는 클래스 사이에서만 참조변수 형변환이 가능하다.
참조변수 형변환을 하는 이유는 멤버 개수를 조절하기 위함이다.
변수에 저장된 값(주소값)이 변환되는 것은 아니다.

SportsCar sc = new SportsCar();

Car c = (Car)sc; //(Car) 생략가능 (자손 → 조상)

//(자손 → 조상) 형변환을 거친 것만 되돌아올 수 있다. 
SportsCar sc2 = (SportsCar)c; // (SportsCar) 생략 불가. (조상 → 자손)

✔ instanceof 연산자

형변환이 가능한지 확인할 수 있는 연산자이다.

// 참조변수 instanceof 타입(클래스명)

void checkPoly(Car c){
	if (c instanceof SportsCar){
    	SportsCar c = (SportsCar)c;
     }
  }

✔ 매개변수의 다형성

class Fruit {
	int count;
}
class Melon extends Fruit {
}
class Peach extends Fruit {
}

class Farmer {
	void pickFruit(Melon m){
    	m.count--;
    }
    void pickFruit(Peach p){
    	p.count--;
    }
}

위와 같이 모든 자손 클래스에 대한 메서드를 선언하는게 아니라 형변환을 통해 중복 코드를 줄일 수 있다.


class Farmer {
    void pickFruit(Fruit f){
    	f.count--;
    }
}

Farmer fm = new Farmer();
Melon m = new Melon();

fm.pickFruit(m); // >> Fruit f = (Fruit)m; 형 변환가능

✔ 여러 종류의 객체 배열

배열에는 한 가지 타입의 객체만 올 수 있었다.
하지만 조상 타입의 참조 변수 배열을 사용하면, 같은 조상을 가진 자손 객체를 배열로 묶어서 다룰 수 있다.

Fruit[] fruits = new Fruit[2];
fruits[0] = new Melon();
fruits[1] = new Peach();

✅ 추상 클래스(abstract class)

추상 클래스는 미완성 메서드(추상 메서드)를 포함하고 있는 클래스이다.
추상 클래스는 상속을 통해 자손클래스에 의해서만 완성될 수 있으며, 인스턴스를 만들 수 없다.

✔ 추상메서드 (abstract method)

메서드는 ( 선언부 ){ 구현부 }로 구성되어 있다.
추상 메서드는 선언부만 작성하고 구현부는 작성하지 않은 채로 남겨 두는 것이다.

abstract class Test{ //추상 클래스
	abstract void t1 (int a); //추상 메서드
}

메서드를 작성할 때 실제 작업 내용인 구현부보다 더 중요한 부분은 선언부이다.
메서드를 사용하는 쪽에서는 구현부가 없더라도, 리턴타입, 매개 변수, 메서드의 이름만 알고 있으면 사용하는 코드를 작성할 수 있기 때문이다.

✅ 인터페이스

일종의 추상 클래스이다. 그러나 추상 클래스보다 추상화 정도가 높아서 일반 메서드 또는 멤버 변수를 구성원으로 가질 수 없다.

인터페이스 제약사항

  • 모든 멤버 변수는 public static final 이어야하며, 이를 생략할 수 있다.
  • 모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 잇다.
    ( 단, static메서드와 디폴트 메서드는 예외)
interface Test {
	public static final int 상수이름 =;
    public abstract void method(매개변수);
}

✔ 인터페이스 상속

인터페이스로부터만 상속받을 수 있으며, 클래스와는 달리 다중 상속이 가능하다.

✔ 인터페이스 구현

class 클래스이름 implements 인터페이스이름 {
	// 인터페이스에 정의된 추상 메서드 모두 구현해야함
}

✔ 인터페이스를 이용한 다형성

class Fighter implements Fightable{
	public void move(int x, int y){...};
}

Fightable f = (Fightable)new Fighter(); //(Fightable)생략 가능

위와 같은 클래스가 존재할 때 인터페이스는 클래스의 조상이라 할 수 있으므로 인터페이스 타입으로 형변환이 가능하다.

void attack(Fightable f){...};

따라서 인터페이스는 다음과 같이 메서드 매개변수의 타입으로도 사용될 수 있다.
인터페이스 타입의 매개변수가 갖는 의미는 메서드 호출 시 해당 인터페이스를 구현한 클래스의 인스턴스를 매개변수로 제공해야 한다는 것이다.

마찬가지로 리턴타입으로 인터페이스를 지정하는 것도 가능하다.

Fightable method(){
	Fighter f = new Fighter();
    return f //인스턴스의 주소 반환
}

리턴타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미한다.

✔ 인터페이스 장점

  1. 개발 시간 단축
  2. 표준화 가능
  3. 서로 관계없는 클래스들에게 관계를 맺어줄 수 있다.
  4. 독립적인 프로그래밍 가능

✔ 디폴트 메서드와 static

원래는 인터페이스에 추상 메서드만 선언할 수 있는데 JDK1.8부터 디폴트 메서드와 static 메서드도 추가할 수 있게 되었다.
디폴트 메서드는 추상 메서드의 기본적인 구현을 제공하는 메서드로, 추상 메서드가 아니기 때문에 디폴트 메서드를 추가하여도 해당 인터페이스를 구현한 클래스는 수정할 필요가 없다.

interface Test {
	void t1 (); // 추상메서드
    default void t2(){...}; //디폴트 메서드
}

디폴트 메서드는 추상 메서드와 달리 구현부가 필요하다.

✅ 내부 클래스(inner class)

class A { //outer class
	class B {//inner class}
}

장점

  • 내부 클래스에서 외부 클래스의 멤버들을 쉽게 접근할 수 있다.
  • 코드의 복잡성을 줄일 수 있다.(캡슐화)
class Outer {
	int val = 10;
    
    class Inner {
    	int val = 20;
        
        void method() {
        	int val = 30;
            
            System.out.println ( val ); //30
            System.out.println ( this.val ); //20
            System.out.println ( Outer.this.val ); //10
		}
    }
 }
 
 class Test {
 	public static void main (String args[]){
    	Outer o = new Outer();
        Outer.Inner i = o.new Inner();
        i.method();
    }
}
profile
하루하루 차근차근🌱

0개의 댓글