JAVA_추상 클래스, 인터페이스, 제네릭

이혜윤·2024년 1월 24일

JAVA

목록 보기
6/9
post-thumbnail

1. 추상 클래스

1.1 개요

설계도가 있지만, 설계도 자체를 가지고 객체를 생성하지 않는다.

ex. Animal - Cat, Dog, Person 에서 Animal에 대한 이야기. “동물”이라는 객체를 만들지는 않으니까

// 두 클래스 사이에 유사점(공통부분)은 있다.
// 그러나 상속 관계는 아니다.
// => 공통부분을 따로 클래스를 만들어서 관리.
//   - 나중에 변경사항이 생기거나, 업그레이드할 때 해당 클래스만 고치면 
//   연관된 모든 클래스에 적용할 수 있고, 그게 더 효율적이다.
//   - 상속에 따른 계층적 관리 => 코드의 조직화에 도움.
//   - 상속관계에 있다면 => 다형성 이용 가능.
//   - 전혀 다른 설계도 사이에, 연관성이 생긴다.
public class KFoodChef extends Chef {

    // @Override 더이상 오버라이드가 아니게 됨.
    public void cook() {
        System.out.println("한식을 조리한다.");
    }
}
public class JFoodChef extends Chef {
    
    // @Override
    public void cook() {
        System.out.println("일식을 조리한다.");
    }
}
public class Chef {
    String name;
    int age;
    String speciality;
    
    public void eat() {
        System.out.println("음식을 먹는다.");
    }
    
	  // cook() 주석처리 해보자. 
    public void cook() {
        System.out.println("전공에 맞는 조리한다.");
    }
}
import test02_inheritance.Chef;

public class ChefTest {
    public static void main(String[] args) {
        Chef[] chefs = new Chef[2];
        
        chefs[0] = new KFoodChef();
        chefs[1] = new JFoodChef();
        
        // chef 설계도에서 쓰이지 않는 cook() 메서드를 삭제했더니
        // 자식 객체가 cook() 메서드를 갖고 있다 하더라도 접근 불가. 
        
        for(Chef chef:chefs) {
        	chef.eat(); // 부모 클래스 타입으로 참조
        	//chef.cook(); // 자식 객체의 행위가 실행됨  
				// -> 에러 발생 다형성 활용 불가. 동적 바인딩 불가. 
        }
        
    }
}

1.2 정의

cook() 메서드는 자손 클래스에서 반드시 재정의해서 사용되기 때문에 조상의 구현이 무의미.

메서드의 선언부만 남기고 구현부는 ; 으로 대체.

  • 구현부가 없으므로 abstract 키워드를 메서드 선언부에 추가
  • 객체를 생성할 수 없는 클래스라는 의미로 클래스 선언부에 abstract를 추가 (abstract 없으면 에러 발생)

추상 메서드를 하나라도 가지고 있으면 추상 클래스

public abstract class Chef {
    String name;
    int age;
    String speciality;
    
    public void eat() {
        System.out.println("음식을 먹는다.");
    }
    
    public abstract void cook();
    
}
public class ChefTest {
    public static void main(String[] args) {
        Chef[] chefs = new Chef[2];
        
        chefs[0] = new KFoodChef();
        chefs[1] = new JFoodChef();
        
        for(Chef chef : chefs) {
        	chef.eat();
        	chef.cook(); // 접근 가능 => 다형성 활용 가능 
        }
    }
}

1.3 장점

  • 다형성 활용 가능
  • 자식 클래스에서 반드시 오버라이드해서 구현 (구현의 강제)

💡 이때, 구현의 강제성이란?

1 ) 해당 추상 메서드를 반드시 오버라이드하거나, ⇒ 자기 자신이 완전한 클래스가 되거나

2 ) 오버라이드 하지 않고 자기 자신도 추상 클래스가 된다. ⇒ 이걸로는 객체 생성 불가 : 추상 메서드가 없어도 abstract는 붙일 수 있고, 객체 생성은 안된다는 뜻. 즉, 이건 상속해서 써라~

// (2) 의 예시
public class abstract Chef {
    String name;
    int age;
    String speciality;
    
    public void eat() {
        System.out.println("음식을 먹는다.");
    }
    
  
    public void cook() {
        System.out.println("전공에 맞는 조리한다.");
    }
}

1.4 특징

abstract 클래스는 상속 전용 클래스

클래스에 구현부가 없는 메서드가 있으므로 객체를 생성할 수 없음

상위 클래스 타입으로 자식을 참조할 수는 있음

조상 클래스에서 상속 받은 abstract 메서드를 재정의하지 않은 경우, 클래스 내부에 abstract 메서드가 있으므로 자식 클래스는 abstract 클래스가 되어야 함

💡 추상 클래스 사용하는 이유

구현의 강제를 통해 프로그램의 안정성 향상

2. 인터페이스

2.1 정의

서로 다른 장치들을 연결시켜 주는 규격

  • 완벽히 추상화된 설계도
  • 클래스와 유사하게 작성 가능 (class 대신 interface 키워드 사용)
  • 모든 메서드가 추상 메서드 (JDK8부터 default method, static method 추가. 이 경우에는 구현부가 존재) = 이 추상 메서드를 구현하겠다! 라는 약속
public interface  인터페이스 이름 {

	public static final 타입 상수 이름1= 10;
	
	(생략 가능) 타입 상수이름2 =10;

	
	public abstract 반환형 메서드이름1(타입 매개변수…);
	
	(생략 가능) 반환형 메서드이름2(타입 매개변수…); 

}

인터페이스 이름으로 바로 접근하는게 static

  • 모든 메서드는 public abstract 생략되어 있다. 모든 메서드는 추상 메서드
  • public static final 이 생략되어 있다. 모든 변수는 상수
public interface MyInterface {
	public static final int MEMBER1 =10;
	int MEMBER2 = 20;
	
	public abstract void method1();
	void method2();
}

2.2 인터페이스의 구현 - implements

  • 인터페이스로 객체 생성 불가능
  • 클래스가 이 인터페이스를 구현하도록 하고, 그 클래스로 객체 생성하면 OK. 인터페이스는 항상 클래스에 의해 구현된다.

public class MyClass implements MyInterface{

	@Override
	public void method1() {
		// TODO Auto-generated method stub
		System.out.println("method1");
		
	}

	@Override
	public void method2() {
		// TODO Auto-generated method stub
		System.out.println("method2");
		
	}

}
public class Test {
	public static void main(String[] args) {
		// 인터페이스의 모든 멤버변수는 static final
		System.out.println(MyInterface.MEMBER1);
		System.out.println(MyInterface.MEMBER2);
		
		// 객체 생성
		MyClass mc = new MyClass();
		mc.method1();
		mc.method2();
		
		// 해당 객체가 그 인터페이스를 구현했다면, 
		// 인터페이스를 참조형 타입으로 활용 가능
		
		MyInterface mi = mc;
		MyInterface mi2 = new MyClass();
		
		
	}
}

2.2.1 인터페이스는 그 자체로 인스턴스를 생성할 수 없음

MyInterface m = new Myinterface(); // 에러 발생. 불가능

2.2.2 인터페이스 내에 있는 메서드를 구현할 클래스가 필요함.

  • extends가 아닌 implements 키워드를 사용해 구현 클래스 작성
  • extends와는 다르게 implements는 다중 구현 허용 (class-class 다중 상속은 불가능)
    • 클래스 → 클래스 : 단일 상속만 허용
    • 클래스 → 인터페이스 : 다중 구현 가능
    • 인터페이스 → 인터페이스 : 다중 상속 가능
interface Shape{} 
class Circle extends Shape{} // X
class Circle implements Shape{} // O

2.3 인터페이스 상속

  • extends를 이용해 상속이 가능
  • 다중 상속이 가능 ( 클래스의 다중 상속에서의 문제점 없음)

2.4 default method

인터페이스에 구현부가 있는 메서드를 작성할 수 있음

메서드 앞에 default라는 키워드를 작성해야 함

public 접근제한자를 사용해야하며 생략 가능 ( 생략되어있으면 public)

public interface AbleToFly {
    void fly();
    
    public default void print() {
        System.out.println("날아요.");
    }
}

2.5 static method

클래스의 static 메서드와 사용 방법 동일

인터페이스 이름으로 메서드에 접근하여 사용

public interface AbleToSwim {
    void swim();
    
    default void print() {
        System.out.println("수영해요.");
    }
    
    public static void infor() {
    	System.out.println("static입니다.");
    }
}
public class Test {
    public static void main(String[] args) {
        Duck d = new Duck();
        Butterfly b = new Butterfly();
        Penguin p = new Penguin();
        Swan sw = new Swan();
        
        
        b.fly();
        b.print(); // default 메서드
        
        AbleToSwim.info(); // static 메서드

2.6 충돌 발생 가능

  • interface A default m(){} vs interface B default m(){} → 반드시 오버라이드 해야함
  • interface하고 조상 클래스의 메서드 충돌 → 조상 클래스 메서드 사용

3. generic

3.1 정의

타입에 대해 일반화된 클래스를 작성하겠다. 타입을 파라미터처럼 사용

  • 다양한 종류의 객체들을 다루는 메서드나 컬렉션 클래스에서 컴파일 시 타입을 체크해주는 기능
  • 객체의 타입 안정성을 제공한다.
  • 형 변환의 번거로움이 없어지므로 코드가 간결해진다.

클래스 안에서 사용되는 자료형(타입)을 구체적으로 명시하지 않고, T와 같이 타입 매개변수를 이용하는 클래스

3.2 제네릭 클래스 선언

클래스 또는 인터페이스 선언 시 <> 에 타입 파라미터 표시

public class CalssName<T>{}
public interface InterfaceName<T>{}

타입 파라미터 : 특별한 의미의 알파벳이라기보단 단순히 임의의 참조형 타입을 말함

  • T: reference Type
  • E: element
  • K: key
  • V: value

generic이 없다면?

class Box {
    private Object obj; // 모든 클래스의 조상, 모든 값이 들어갈 수 있음
    
    public Object getObj() {
        return obj;
    }
    
    public void setObj(Object obj) {
        this.obj = obj;
    }
}

public class BoxTest {
    public static void main(String[] args) {
        Box box = new Box();
        box.setObj("Hi");
        box.setObj(11);
        box.setObj(22.1);
        // 타입 별로 설계도를 만들든지
        // Object로 설계도를 만든다.
        // => 값을 넣을 때는 편하지만, 뺄 때는 번거롭다 !!
        
        // 항상 object를 반환하므로 => 형변환을 해줘야한다. 
        
        Object obj = box.getObj();
        double d = obj; // 형 변환 과정에서 에러 발생할수도
        
        // Double은 double 자료형의 wrapper 클래스
        if(obj instanceof Double) {
        	System.out.println("실수입니다");
        } else if(obj instanceof String) {
        	System.out.println("문자열입니다.");
        }
        
        
    }
}

generic 적용 후 효과

// 제네릭 클래스: 타입을 매개변수화 했다. 
// <> 타입 파라미터

class Box<T>{
    private T t;
    
    public T getT() {
        return t;
    }
    
    public void setT(T t) {
        this.t = t;
    }
}

public class BoxTest {
    public static void main(String[] args) {
        Box<Integer> intBox = new Box<Integer>(); // integer만 들어갈 수 있는 박스
//        intBox.setT("Hi"); // 타입 체크 후 오류
        intBox.setT(11);
        int num = intBox.getT(); // 형변환 할 필요X
     
        Box<String> strBox = new Box<String>(); // string만 들어갈 수 있는 박스
        strBox.setT("Hi");
        String str = strBox.getT();
    }
}

3.3 제네릭 클래스 객체 생성

  • 변수와 생성 쪽의 타입은 반드시 일치해야함 (상속관계에 있어도 마찬가지)
Box<Student> box = new Box<Student>(); //(O)
Box<Person> box = new Box<Student>(); //(X)
  • 추정이 가능한 경우 타입 생략 가능 (생성자 쪽 생략 가능 JDK 1.7 부터)
Box<Student> box = new Box<>();
  • 제네릭 타입을 지정하지 않고 생성이 가능하지만 권장X (자동으로 T는 object)

3.4 제한된 제네릭 클래스

  • 타입 문자로 사용할 타입을 명시했지만, 역시 모든 타입을 사용할 수 있는 문제 발생
  • 구체적인 타입 제한이 필요할 때, extends 키워드를 사용할 수 있음 (Person의 자손만 타입 지정 가능)
class Box<T extends Person>{
	private T obj;

	public T getObj(){
	return obj;}
	}

	public void setObj(T obj){
	this.obj = obj;
	}
}
  • 클래스가 아닌 인터페이스로 제한할 경우도 extends 키워드 사용
  • 클래스와 함께 인터페이스 제약 조건을 이용할 경우 & 로 연결

3.5 와일드 카드 이용 ( 클래스 정의할 때 말고 타입으로 활용할 때)

회고

UserManagerImpl에서 싱글톤 객체 생성 시

//public class UserManagerImpl implements IUserManager { 안에서 ...

private static UserManagerImpl um = new UserManagerImpl();
// 싱글톤 패턴의 기본 생성자, 객체 생성을 외부에서 하지 못하게 막음
private UserManagerImpl() {};
    
// 외부에서 사용할 수 있도록 UserManagerImpl 인스턴스 반환
public static UserManagerImpl getInstance() {
        return um;
}

이걸 인터페이스를 활용해 다른 버전으로도 가능!

private static IUserManager um2 = new UserManagerImpl();
private static IuserManager um3 = new UserManagerImpl_ver2();
public static IUserManager getInstance() {
    return um2;
}
profile
구르미 누나

0개의 댓글