OOP 특징

박윤택·2022년 5월 12일
3

JAVA

목록 보기
2/14
post-thumbnail

시작하면서

앞서 OOP를 포스팅하면서 언급했던 특징들에 대해 좀 더 세부적으로 정리하려고 한다.

상속

📖 정의

"기존의 클래스를 재사용하여 새로운 클래스를 작성하는 자바의 문법 요소"
두 클래스를 상위 클래스와 하위 클래스로 나누어 상위 클래스의 멤버(필드, 메서드, 이너 클래스)를 하위 클래스에게 내려주는 것을 의미, IS-A 관계(A는 B이다.)

public class Person {
	private String name;
    private String address;
    
    public void sleep() {
    	System.out.println("잠을 잡니다.");
    }
    
    // setter, getter, constructor
}

public class Developer extends Person {
	public void work() {
    	System.out.println("개발을 합니다.");
    }
}

public class Main {
	public static void main(String[] args) {
        Person developer = new Developer();
        
        developer.setName("김선생");
        developer.setAddress("서울");
        
        developer.sleep();
        developer.work();
    }
}
[상속 예제]

Pesrson class를 상속받은 Developer 객체는 상위 클래스(Person)에서 정의한 setName(), setAddress(), sleep() 함수를 사용할 수도 있고 하위 클래스(Developer)에서 정의한 work()도 사용할 수 있다.


👖 메서드 오버라이딩

"상위 클래스로부터 상속받은 메서드와 동일한 이름의 메서드를 재정의하는 것"

Public class Person {
	public void sleep(){
    	System.out.println("잠을 잡니다");
    }
}

public class Developer extends Person {
	@Override
    public void sleep() {
    	Sytsem.out.println("개발자가 잠을 잡니다");
    }
}

public class Main {
	public static void main(String[] args) {
    	Person develper = new Developer();
        
        developer.sleep(); // 개발자가 잠을 잡니다.
    }
}
[메서드 오버라이딩 예]

상위 클래스에서 정의한 sleep 함수를 하위 클래스에서 Overriding 하여 메서드를 호출해보면 "잠을 잡니다"가 아닌 "개발자가 잠을 잡니다"가 출력하는 것을 알 수 있다. 이를 동적 바인딩이라 하며 실행 단계에서 객체의 타입을 보고 적절한 메서드를 호출한다.
여기서 메서드 오버라이딩을 할때 주의사항이 있다.

  • 함수명 동일
  • 매개변수, 타입 동일
  • 리턴 타입 동일
Person persons = new Person[] {new Develper(), new Student() };

for(Person person : persons)
	person.sleep();
[하위 클래스를 상위 클래스 타입으로 관리하는 예]

📚 super & super()

클래스 포스팅에서 this와 this() 키워드에 대해 살펴보았다. this는 현재 객체에 대한 접근과 관련된 것이고 super는 하위 클래스에서 상위 클래스의 접근에 관련된 것이다.

public class Person {
    public Person(){
        System.out.println("Person default 생성자 출력");
    }

    public Person(int a) {
        System.out.println("Person 매개변수있는 생성자 출력");
    }
}

public class Developer extends Person {
    public Developer() {
        System.out.println("Developer default 생성자 출력");
    }

    public Developer(int a) {
//        super(a);
        System.out.println("Developer 매개변수있는 생성자 출력");
    }
}

public class Main {
    public static void main(String[] args) {
        Person developer1 = new Developer();
        // Person default 생성자 출력
		// Developer default 생성자 출력
        
        Person developer2 = new Developer(1);
        // Person default 생성자 출력 또는 Person 매개변수있는 생성자 출력
		// Developer 매개변수있는 생성자 출력
    }
}
[하위 클래스 인스턴스 생성에 따른 상위 클래스의 인스턴스 생성]

하위 클래스인 Developer를 인스턴스화 했을때 먼저 부모 클래스인 Person 클래스의 생성자가 호출된다. 하위 클래스의 생성자에 따로 super() 키워드를 적어주지 않아도 상위 클래스를 먼저 생성한다. 이는 하위 클래스 생성자에서 상위 클래스의 default 생성자가 생략되어 있기 때문이다.

만약 하위 클래스에서 매개변수가 있는 생성자를 호출했을 때 super() 키워드를 이용하여 상위 클래스의 매개변수가 있는 생성자를 호출하지 않으면 default 생성자가 호출되는 것을 알 수 있다.

[super()의 중요성]

또 만약에 상위 클래스에서 default 생성자가 아닌 매개변수가 있는 생성자만 선언을 했을 경우 하위 클래스에서 생성자를 선언할 때 매개변수가 있는 생성자를 호출(super(매개 변수))를 선언하지 않으면 에러가 발생한다.


☝ 제일 상위 클래스

"모든 클래스는 Object class를 상속받는다."

class Upper { // extends Object 자동 추가 

}

class Lower extends Upper {

}

Object 클래스에서 기본적으로 제공하는 메서드가 있다.

메서드내용
String Object.toString()객체 정보를 문자열로 출력
boolean Object.equals(Object obj)값을 비교
int Object.hashCode()객체의 메모리 번지를 이용해서 해시코드를 만들어 리턴, 두개의 객체를 비교할 때 hashCode() 값이 같다면 동일 객체
void Object.wait()현재 쓰레드 일시정지
void Object.notify()일시정지 중인 쓰레드 재동작

캡슐화

📖 정의

"특정 객체 안에 관련된 속성과 기능을 하나의 캡슐로 만들어 데이터를 외부로부터 보호하는 것"

  • 데이터 보호
  • 내부적으로만 사용되는 데이터에 대한 불필요한 외부 노출을 방지

=> 정보 은닉


👍 장점

  1. 객체의 속성과 기능이 함부로 변경되지 못하게 막고, 데이터가 변경되더라도 다른 객체에 영향을 주지 않기에 독립성을 확보
  2. 유지보수와 코드 확장 시에도 오류의 범위를 최소화

🖐 접근 제어자

"클래스 외부로의 불필요한 데이터 노출을 방지(data hiding)할 수 있고, 외부로부터 데이터가 임의로 변경되지 않도록 막을 수 있다."


접근 제어자범위
private동일 클래스에서만 사용 가능
default, 생략 가능동일 패키지 내에서만 사용 가능
protected동일 패키지, 다른 패키지의 하위 클래스에서 사용 가능
public어디서든 사용 가능

다형성

📖 정의

"하나의 객체가 여러 가지 형태를 가질 수 있는 것"
자바에서는 한 타입의 참조변수를 통해 여러 타입의 객체를 참조할 수 있도록 만든 것을 의미


🌓 참조변수의 타입변환

public class SuperClass {
	private int superNumber;
    
    public void info() {
    	System.out.println("super class");
    }
    
    // getter, setter, constructor
}

public class SubClass extends SuperClass {
	private int subNumber;
    
    @Override
    public void info() {
    	System.out.println("sub class");
    }
    
    // getter, setter, constructor
}

public class Main {
	public static void main(String[] args) {
    	SuperClass subClass1 = new SubClass(); // 상위 클래스에 하위 클래스 참조
         
        // subClass1의 타입은 SuperClass이기 때문에 하위클래스에 정의한 setSubNumber() 사용 불가능 
        // subClasss1.setSubNumber(5); // 에러 발생
        subClass1.info(); // "sub class" 출력, 메서드 오버라이딩

		SuperClass superClass = (SuperClass) subClass1; // 하위 클래스에서 상위 클래스로 타입변환
        superClass.info();
        // superClass.setSubNumber(6); // 타입 변환이 되었기 때문에 사용 불가    
        
        
        
        SubClass subClass2 = new SubClass(); 
        subClass2.info(); // "sub Class" 출력

        SubClass subClass4 = subClass2; // subClass4는 subClass2의 주소를 참조하게 됨
        subClass4.setSubNumber(6);
		// subClass2.getSubNumber() : 6
        // subClass4.getSubNumber() : 6
    
    }
}
[다형성 예제]
  • 상위클래스에서 하위클래스로 타입 변환할 때는 형변환 연산자 생략 가능
  • 하위클래스에서 상위클래스로 타입 변환할때는 업캐스팅 필요
  • 형제 관계에서는 서로 타입 변환 불가능
  • 같은 클래스 타입의 다른 변수에 객체를 대입하면 같은 주소를 참조하게 됨(참조 변수 = 참조 변수; => 주소 참조)

그렇다면 하위 클래스 값 설정 -> 부모 클래스로 형 변환 -> 하위 클래스로 형변환하고 나서 하위 클래스의 값을 확인한다면 계속해서 유지가 되고 있을까?

public class Main {
    public static void main(String[] args) {
        SubClass subClass = new SubClass();
        subClass.setSubNumber(8); // 1. 값 설정
        System.out.println("subClass 주소 : " + subClass); // 주소 동일

        SuperClass superClass = subClass; // 2. 하위 클래스 -> 상위 클래스
        System.out.println("superClass 주소 : " + superClass); // 주소 동일

        SubClass subClass1 = (SubClass) superClass; // 3. 상위 클래스 -> 하위 클래스

        System.out.println("subClass1 주소 : " + subClass); // 주소 동일
        System.out.println(subClass1.getSubNumber()); // 값 확인, 8유지
    }
}
[타입 변환을 통한 값 확인]

최초 생성한 클래스의 인스턴스(객체)에 할당된 메모리에 대해서 어느정도의 메모리 범위까지 제어가 가능한 형태라 subNumber의 값은 메모리에 계속 유지가 되어 있어 인스턴스의 범위를 바꿔준다면 다시 접근했을 때 계속해서 값이 유지되는 것을 확인할 수 있다.


🧺 instanceOf

객체를 어떤 생성자로 만들었는가 확인하는 연산자

public class SuperClass {
}

public class SubClass extends SuperClass {
}

public class Main {
	public static void main(String[] args) {
    	SubClass subClass = new SubClass();
        System.out.println(subClass instanceof SubClass); // true
        System.out.println(subClass instanceof SuperClass); // true
        System.out.println(subClass instanceof Object); // true


        SuperClass superClass = new SuperClass(); 
        System.out.println(superClass instanceof SubClass); // false
        System.out.println(superClass instanceof SuperClass); // true
        System.out.println(superClass instanceof Object); // true
    }
}
[instanceOf 예]

하위 클래스는 상위 클래스를 상속받았기 때문에 상위 클래스의 객체인가를 확인하면 true 값을 얻을 수 있다. 이는 하위 클래스를 생성할때 상위 클래스를 상속받았기 때문에 상위 클래스의 생성자를 호출하기 때문이다.


추상화

📖 정의

"사물이나 표상을 어떤 성질, 공통성, 본질에 착안하여 그것을 추출하여 파악하는 것"
자바에서는 기존 클래스들의 공통적인 요소들을 뽑아서 상위 클래스를 만들어 내는 것이라고 할 수 있다.

🟠 abstract

  • 추상 클래스 : 클래스에 abstract 키워드가 붙은 키워드, 추상 메서드가 하나라도 있다면 추상 클래스
  • 추상 메서드 : 메서드에 abstract 키워드가 붙은 메서드
public abstract class Animal {
    public String name;
    public static final int count = 0;

    public void sleep() {
        System.out.println("잠잔다.");
    }
    public abstract void eat();
}

public class Cat extends Animal{
    @Override
    public void eat() {
        System.out.println("고양이가 밥 먹는다.");
    }
}
[absctrat 예]

abstract 클래스에 abstract 메서드가 있으면 상속받는 하위 클래스에서 메서드 오버라이딩을 통해 메서드 바디를 정의해줘야한다. abstract 클래스 내에 일반 메서드, 변수, 상수를 정의할 수 있다.

그렇다면 abstract 키워드가 붙은 클래스를 계속해서 상속받게 한다면 어떻게 될까

public abstract class Animal {
    public abstract void sleep();
}

// 포유류
public abstract class Mammalia extends Animal {
    public abstract void suckle();
}

public class Cat extends Mammalia {

    @Override
    public void suckle() { // from Mammalia class
        System.out.println("고양이가 젖을 먹는다.");
    }

    @Override
    public void sleep() { // from Animal class
        System.out.println("고양이가 잠잔다.");
    }
}
[abstract 상속 예]

추상 클래스를 추상 클래스가 상속 받는다면 따로 메서드를 정의하지 않아도 되고 제일 하위 클래스가 여태 상속받은 추상 클래스들의 추상 메서드를 정의해주면 된다.

⚪ interface

“서로 다른 두 시스템, 장치, 소프트웨어 따위를 서로 이어 주는 부분 또는 그런 접속 장치를 의미” 즉, 어떤 대상들을 연결 또는 소통을 돕는 역할을 수행한다. 앞서 언급한 abstract와는 비교하면 interface가 더 높은 추상성을 가지고 있다. interface 내부에서는 오직 추상 메서드와 상수만을 멤버로 가질 수 있다.

public interface Animal {
    void sleep(); // public 생략 가능, 구현시에는 public으로!
}

public interface Pet {
	void actCharming();
}

public class Cat implements Animal, Cat {
    @Override
    public void sleep() {  // from Animal
        System.out.println("고양이가 잠잔다.");
    }
    
    @Override
    public void actCharming() {  // from Pet
    	System.out.println("고양이가 애교부린다.);
    }
}
[interface 예]

interface는 하위 클래스는 여러 개를 implements 받을 수 있다. 클래스는 다중 상속이 불가능한데 왜 interface는 여러 개를 구현해도 되는지 의문이 들 것이다. 이는 클래스의 경우 상위 클래스에 동일한 이름의 필드 또는 메서드가 존재하는 경우 충돌이 일어날 수 있지만 interface의 경우 미완성된 멤버를 가지고 있기 때문에 다중 구현이 가능하다.

0개의 댓글