오늘의하루

[JAVA] 객체지향 추상클래스 인터페이스 내부클래스 본문

JAVA

[JAVA] 객체지향 추상클래스 인터페이스 내부클래스

오늘의하루_master 2022. 8. 3. 11:19
반응형

1. 추상 클래스(abstract class)

  • 아직 사용할 수 없는 미완성된 클래스
    • 인스턴스 생성 불가능하다.
  • 추상 메서드를 포함하고 있는 클래스
    • 선언 부만 작성된 메서드
    • 꼭 필요하지만 자식마다 다르게 구현될 것으로 예상되는 경우에 사용
  • 정상적인 메서드는 추상 메서드를 호출할 수 있다.
    • 호출할 때 필요한 건 선언 부이기 때문이다.
  • 다른 클래스가 상속을 통해 추상 메서드를 완성해서 사용할 수 있게 도움을 주는 목적으로 작성한다.
  • 사용 목적은 여러 클래스에 공통적으로 사용될 수 있는 부분을 뽑아서 추상 클래스로 만든다.
abstract class player{ // 추상 클래스
    int x;
    
    player(){ // 추상 클래스도 생성자는 있어야 한다.
        x = 0;
    }
    
    abstract void play(int a); // 추상메서드
    abstract void stop(); // 추상메서드
    
    void play(){
        play(this.x); // 일반 메서드가 추상 메서드 사용
    }
}

// 추상클래스를 상속받은 클래스는 추상 메서드를 완성해서 사용할 수 있다.
class Audio extends player {
    void play(int a){
        // 구현부 완성!
    }
    void stop(){
        // 구현부 완성!
    }
}

// Error! 추상메서드를 상속받고 사용할때는 모든 추상메서드를 완성해야한다.
// 모든 추상메서드를 완성하지 않으면 그 class도 추상메서드가 된다.
abstract class Error extends player{ 
    void play(int a){
        // 구현부 완성
    }
}

1-1. 추상 클래스를 사용하는 실전 예제

// ⭐추상 클래스 사용 전
class Marine{
    int x, y;
    void move(int x, int y){}
    void stop(){}
    void stimpack(){}
}
class Tank{
    int x, y;
    void move(int x, int y){}
    void stop(){}
    void changeMode(){}
}
class Dropship{
    int x, y;
    void move(int x, int y){}
    void stop(){}
    void load(){}
    void unload(){}
}

// ⭐추상 클래스 사용
abstract class Unit{
    int x, y;
    abstract void move(int x, int y);
    void stop(){}
}

class Marine extends Unit{
    void move(int x, int y){}
    void stimpack(){}
}
class Tacnk extends Unit{
    void move(int x, int y){}
    void changeMode(){}
}
class Dropship extends Unit{
    void move(int x, int y){}
    void load(){}
    void unload(){}
}

2. 인터페이스(interface) - 껍데기

  • 추상 메서드와 상수만을 멤버로 가질 수 있다.
  • 추상 클래스보다 추상화 정도가 높다.
  • 인스턴스를 생성할 수 없고 클래스 작성에 도움을 줄 목적으로 사용된다.
interface Interface_name{
    public static final int x = 1; // 상수
    public abstract String method(); // 추상 메서드
}
  • 인터페이스의 모든 멤버 변수는 public static final 이기 때문에 생략이 가능하다.
  • 인터페이스의 모든 메서드는 public abstract 이기 때문에 생략이 가능하다.
interface Interface_name{
    public static final int x = 1; // 상수
    static final int y = 2;
    final int z = 3;
    int r = 4;
    
    public abstract String method(); // 추상 메서드
    abstract int method2();
    String method3(int[] T);
}

2-1. 인터페이스의 상속

  • 인터페이스도 클래스처럼 상속이 가능하며, 다중 상속도 허용된다.
  • 인터페이스는 Object 클래스와 같은 최고 조상이 없다.

클래스에서는 다중 상속이 불가능한 건 메서드의 구현부가 충돌하기 때문인데 인터페이스의 경우 선언 부만 존재하기 때문에 가능하다.

interface I{
    void move(int x, int y);
}
interface II{
    void attack(Unit u);
}
interface III extends I, II{ }

2-2. 인터페이스 구현

  • 인터페이스를 구현하기 위해서는 implements로 클래스에서 상속을 받아야 한다.
  • 인터페이스에 정의된 모든 추상 메서드를 구현해야 한다.
    • 모두 구현하지 않으면 추상 클래스가 된다.
  • 다른 클래스의 상속과 인터페이스의 구현이 동시에 가능하다.
    • 클래스 상속 먼저 그 이후 인터페이스 구현
interface Movable {
    void move(int x, int y);
}
interface Attackable {
    void attack(Unit u);
}
interface Fightable extends Movable, Attackable{ }

class Unit{
    int hp;
    int mp;
}

class Fighter extends Unit implements Fightable{
    public void move(int x, int y){/* 구현부 작성 */}
    public void attack(Unit u){/* 구현부 작성 */}
}

// 추상 메서드를 모두 구현못했기 때문에 추상 클래스가 된다.
abstract class Fighter2 implements Fightable{ 
    public void move(int x, int y){/* 구현부 작성 */}
}

2-3. 인터페이스를 이용한 다형성

  • 인터페이스 타입의 변수로 인터페이스를 구현한 클래스의 인스턴스를 참조할 수 있다.
  • 인터페이스를 메서드의 매개변수 타입으로 지정할 수 있다.
  • 인터페이스를 메서드의 리턴 타입으로 지정할 수 있다.
public static void main(String args[]) {
    Fighter f = new Fighter(1,1);
    Fightable ff = new Fighter(2,2);
    f.attack(ff);
    Fightable Test = f.method();
    // System.out.println(Test.hp); Fightable 타입에서는 hp 멤버 변수가 없다.
     
    // hp에 접근하기 위해 형변환을 시켜준다.
    Fighter Test2 = null; // Fighter 타입의 참조변수 Test2에 null을 저장해 둔다.
    Test2 = (Fighter) Test; // 형변환
    System.out.println(Test2.hp); // 100        
}

interface Movable {
    void move(int x, int y);
}
interface Attackable {
    void attack(Unit u);
}
interface Fightable extends Movable, Attackable{ } // 인터페이스 다중 상속

class Unit{
    int hp;
    int mp;
    
    Unit(int x, int y){ // 생성자
        hp = x;
        mp = y;
    }
}

class Fighter extends Unit implements Fightable{ // 클래스 상속과 인터페이스 구현 (동시)
    public void move(int x, int y){/* 구현부 작성 */}
    public void attack(Unit u){/* 구현부 작성 */}
    
    Fighter(int x, int y){ // 생성자
        super(x,y);
    }
    
    void attack(Fightable f){ // 매개변수로 인터페이스 타입으로 지정했다.
        System.out.println("T class attack method");
    }
    
    Fightable method(){ // 리턴 타입을 인터페이스로 지정했다.
        System.out.println("method class Fightable Type");
        // Fighter 클래스는 인터페이스를 모두 구현했기때문에 다형성을 사용할 수 있다.
        return new Fighter(100,100); // 생성된 객체의 주소를 반환
    }
}
  • 인터페이스의 장점
    • 개발 시간을 단축시킬 수 있다.
    • 표준화가 가능하다.
    • 서로 관계없는 클래스들에게 관계를 맺어 줄 수 있다.
      • 하나의 인터페이스를 공통적으로 구현함으로써 관계를 맺는다.
    • 독립적인 프로그래밍이 가능하다.

인터페이스의 장점을 살려 중간 점검 코드를 작성하였습니다.

    public static void main(String args[]) {
        Tank t = new Tank(100); // 부모타입 : GroundUnit
        Dropship d = new Dropship(80); // 부모타입 : AirUnit
        Marine m = new Marine(40); // 부모타입 : GroundUnit
        SCV s = new SCV(40); // 부모타입 : GroundUnit
        
        System.out.println(t.getname() + "의 MAX_HP = " + t.MAX_HP + " 공격력 = " + t.hitpoint);
        System.out.println(d.getname() + "의 MAX_HP = " + d.MAX_HP + " 공격력 = " + d.hitpoint);
        System.out.println(m.getname() + "의 MAX_HP = " + m.MAX_HP + " 공격력 = " + m.hitpoint);
        System.out.println(s.getname() + "의 MAX_HP = " + s.MAX_HP + " 공격력 = " + s.hitpoint);
        
        s.powerful_evolution(t); // 가능 
        s.powerful_evolution(d); // 가능
        // powerful_evolution(m); Error : Repariable 인터페이스를 구현하지 않았다.
        s.powerful_evolution(s); // 가능
        
        System.out.println("진화 후 "+t.getname() + "의 MAX_HP = " + t.MAX_HP + " 공격력 = " + t.hitpoint);
        System.out.println("진화 후 "+d.getname() + "의 MAX_HP = " + d.MAX_HP + " 공격력 = " + d.hitpoint);
        System.out.println("진화 후 "+s.getname() + "의 MAX_HP = " + s.MAX_HP + " 공격력 = " + s.hitpoint);
    }
}

//서로 다른 클래스의 관계를 맺어주지 위한 인터페이스
interface evolution {} 

class Unit{
    int hitpoint;
    final int MAX_HP;
    Unit(int x){
        MAX_HP = x;
    }
}

class GroundUnit extends Unit{
    GroundUnit(int x){
        super(x);
    }
}

class AirUnit extends Unit{
    AirUnit(int x){
        super(x);
    }
}

class Tank extends GroundUnit implements evolution{
    Tank(int x){
        super(x);
        hitpoint = MAX_HP/2;
    }
    public String getname(){
        return "Tank";
    }
}

class Dropship extends AirUnit implements evolution{
    Dropship(int x){
        super(x);
    }
    public String getname(){
        return "Drop ship";
    }
}

class Marine extends GroundUnit{
    Marine(int x){
        super(x);
        hitpoint = MAX_HP/10;
    }
    public String getname(){
        return "Marine";
    }
}

class SCV extends GroundUnit implements evolution{
    SCV(int x){
        super(x);
        hitpoint = MAX_HP/20;
    }
    
    void powerful_evolution(evolution r){ // 매개변수로 인터페이스 타입을 지정해줬다.
        if(r instanceof Unit){ // Unit이 참조변수 r의 부모가 맞는지 확인
            Unit u = (Unit)r; // Unit의 멤버를 사용할 수 있도록 형변환
            while(u.hitpoint < 100){ // 100은 조건 성립이 되지 않아 while문을 빠져나오지만 hitpont는 100이 된다.
                u.hitpoint++; // 공격력을 100까지 증가시켜준다.
            }
        }
    }
    public String getname(){
        return "SCV";
    }
}

2-4. 인터페이스의 이해

  • 인터페이스는 두 객체 간의 연결을 돕는 중간 역할을 한다.
    • 선언과 구현을 분리시키는 것이 가능하다.
class B{public void method(){System.out.println("methodInB");}}
// 위에 있는 클래스의 메서드를 선언부와 구현부로 나눠서 아래 처럼작성이 가능하다.
interface I{public void metdhod();}
class B implements I{public void method(){System.out.println("methodInB");}}
  • 클래스를 사용하는 쪽(User)과 클래스를 제공하는 쪽(Provider)이 있다.
  • 메서드를 호출하는 쪽(User)에서는 사용하려면 메서드의 선언 부만 알면 된다.
  • 직접적인 관계를 인터페이스를 사용하여 간접적인 관계로 만들어 줄 수 있다.
// 예제 1번

// 직접적인 관계 ( A -> B )
// A에서 B를 직접 접근한다.
class A {
    public void methodA(B b){
        b.methodB();
    }
}

class B {
    public void methodB(){
        System.out.println("methodB()");
    }
}

// 간접적인 관계 ( A -> interface -> B )
// A에서 interface를 통해 B에 접근한다.
class A {
    public void methodA(I i){
        i.methodB();
    }
}

interface I {
    void methodB();
}

class B implements I {
    public void methodB(){
        System.out.println("methodB()");
    }
}
public static void main(String args[]) {
    Time t = new Time();
    t.setHour(4);
    t.setMinute(75);
    t.setSecond(35);
    System.out.println(t.getHour() + " : " + t.getMinute() + " : " + t.getSecond());
}

interface TimeIntf{
    int getHour();
    void setHour(int h);
    
    int getMinute();
    void setMinute(int m);
    
    int getSecond();
    void setSecond(int s);
}

class Time implements TimeIntf{
    private int hour;
    private int minute;
    private int second;
    
    public void setHour(int h){
        if (h < 0 || h > 23){
            System.out.println("You entered the wrong hour.");
            return;
        }
        hour = h;
    }
    public int getHour(){
        return hour;
    }
    
    public void setMinute(int m){
        if (m < 0 || m > 59){
            System.out.println("You entered the wrong minute.");
            return;
        }
        minute = m;
    }
    public int getMinute(){
        return minute;
    }
    
    public void setSecond(int s){
        if (s < 0 || s > 59){
            System.out.println("You entered the wrong seconds.");
            return;
        }
        second = s;
    }
    public int getSecond(){
        return second;
    }
}

2-5. 디폴트 메서드

  • JDK 1.8 이후 인터페이스에 디폴트 메서드, static 메서드를 추가할 수 있는 변경되었다.
  • 디폴트 메서드는 인터페이스에 추가된 일반 메서드를 말한다.
interface a{
    int a = 1; // 상수
    void method(); // 추상 메서드
    default void method2(){ // 디폴트 메서드
        System.out.println("응애 나 디폴트메서드~");
    }
}
  • 디폴트 메서드가 기존의 메서드와 충돌하는 경우 아래와 같이 해결할 수 있다.
    • 여러 인터페이스의 디폴트 메서드 간의 충돌
      • 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버 라이딩해야 한다.
    • 디폴트 메서드와 부모 클래스의 메서드 간의 충돌
      • 부모 클래스의 메서드가 상속되고 디폴트 메서드는 무시된다.

3. 내부 클래스

  • 클래스 안에 선언된 클래스를 말한다.
  • 특정 클래스 내에서만 사용되는 클래스를 내부 클래스로 선언한다.
  • 내부 클래스를 쓰는 이유
    • 내부 클래스에서 외부 클래스의 멤버들을 쉽게 접근할 수 있다.
    • 코드의 복잡성을 줄일 수 있다.( 캡슐화 )
  • 내부 클래스의 종류는 변수의 선언 위치에 따른 종류와 유효 범위가 동일하다.
    • 인스턴스 내부 클래스
      • 외부 클래스의 멤버 변수 선언 위치에 선언하며 외부 클래스의 인스턴스 멤버처럼 사용된다.
      • 주로 외부 클래스의 인스턴스 멤버들과 관련된 작업에 사용된다.
    • 스태틱 내부 클래스
      • 외부 클래스의 멤버 변수 선언 위치에 선언하며 외부 클래스의 static 멤버처럼 사용된다.
      • 특히 static 메서드에서 사용될 목적으로 선언된다.
    • 지역 내부 클래스
      • 외부 클래스의 메서드나 초기화 블록 안에 선언되며 선언된 블록이 유효 범위이다.
    • 익명 내부 클래스
      • 클래스의 선언과 객체의 생서울 동시에 하는 이름 없는 1회용 클래스이다.
  • 내부 클래스의 접근제어자는 변수에 사용할 수 있는 접근제어자와 동일하다.
class Outer{
    class InstanceInner{}
    static class StaticInner{}
    private class PrivateInstanceInner{}
    protected static class ProtectedStaticInner{}
    
    void Mymethod(){
        class LocalInner{} // 지역변수는 접근제어자가 불가능하다.
    }
    protected void Mymethod2(){
        class LocalInner2{} // 지역변수는 접근제어자가 불가능하다.
    }
}
반응형
Comments