OOP - 2

귀찮Lee·2022년 5월 16일
0

Java

목록 보기
4/15

◎ 객체지향 프로그래밍의 특징

  • 추상화 : 서로 다른 여러 객체의 공통점을 묶어 추상 클래스나 인터페이스로 사용한다
    • 인터페이스나 추상 클래스를 구현한 객체를 만들 때, 특정 기능을 강제할 수 있다
  • 캡슐화 : 서로 연관있는 속성과 기능들을 하나의 캡슐(capsule)로 만들어 데이터를 외부로부터 보호한다
    • 외부에 노출할 필요 없는 정보들은 바깥으로 노출할 필요 없다
  • 상속 : 기존 클래스를 확장하여 새로운 클래스를 작성할 수 있다
    • 상위 클래스로부터 확장된 여러 개의 하위 클래스들이 모두 상위 클래스의 속성과 기능들을 간편하게 사용할 수 있다
  • 다형성 : 어떤 객체의 속성이나 기능이 상황에 따라 여러 가지 형태를 가질 수 있는 성질
    • 상속관계에 있는 객체들은 상위 클래스 등으로 형변환이 가능하다

◎ 상속(Inheritance)

  • 상속(Inheritance)

    • 상위 클래스의 멤버(필드, 메서드, 이너 클래스)를 하위 클래스에게 내려주는 것을 의미한다.
    • 한 클래스의 하위 클래스를 여러개 만들 경우, 상위 클래스의 멤버는 모든 하위 클래스에서 공통적으로 적용되는 멤버일 것이다.
    • 용이성 : 코드를 재사용하여 보다 적은 양의 코드로 새로운 클래스를 작성할 수 있어 코드의 재사용이 줄어든다.
    • tip : 자신의 상위 클래스가 없다면 컴파일러에서 Object를 상위 클래스로 갖는다.
      -> Object 클래스는 자바의 클래스 상속계층도에서 최상위에 위치해있다.
  • 주의사항

    • Java에서는 다중상속을 지원하지 않는다.
      • 서로 다른 Class에서 상속받을 때, 이름이 같은 Method를 받으면 충돌이 일어날 수 있기 때문이다.
      • 이는 Interface를 통해 비슷한 효과를 낼 수 있다.
  • 메서드 오버라이딩(Method Overriding)

    • 상위 클래스에서 상속받은 메서드와 동일한 이름의 메서드를 재정의하는 것 (기능을 하위 클래스에 맞추어 다시 메서드를 설정함)
    • 메서드 오버라이딩 조건
      • 메서드의 선언부(메서드 이름, 매개변수, 반환타입)이 상위클래스의 그것과 완전히 일치해야한다.
      • 접근 제어자의 범위가 상위 클래스의 메서드보다 같거나 넓어야 한다.
      • 예외는 상위 클래스의 메서드보다 많이 선언할 수 없다.
      • 인스턴스 메서드를 static 메서드로 또는 그 반대로 변경할 수 없다.
    public class InheritanceTest {
        public static void main(String[] args) {
            ParentsClass parentsClass = new ParentsClass();
            FirstClass firstClass = new FirstClass();
            SecondClass secondClass = new SecondClass();
    
            // 오버라이딩이 되어 각 줄마다 다른 기능을 함
            parentsClass.printClass(); // I'm ParentsClass
            firstClass.printClass();  // I'm FirstClass
            secondClass.printClass(); // I'm SecondClass
    
            // 오버라이딩 되지 않은 부분은 상위 클래스의 값을 가지고 있음
            System.out.println(firstClass.commonPoint); // 100
        }
    }
    
    class ParentsClass {
        int commonPoint = 100;
        void printClass() {
            System.out.println("I'm ParentsClass");
        }
    
    }
    
    class FirstClass extends ParentsClass{
        void printClass(){
            System.out.println("I'm FirstClass");
        }
    }
    class SecondClass extends ParentsClass{
        void printClass(){
            System.out.println("I'm SecondClass");
        }
    }
  • super 키워드

    • 상위 클래스의 객체를 의미함
    • 해당 클래스의 객체와 상위 클래스의 객체를 구분하기 위해 사용
      -> 상위 클래스의 객체값을 가져와서 사용할 수 있다.
    public class Super {
        public static void main(String[] args) {
            Lower lower = new Lower();
            lower.callNum();
            // count = 15 // this.count = 15 // super.count = 20
        }
    }
    
    class Upper {
        int count = 20; // super.count
    }
    
    class Lower extends Upper {
        int count = 15; // this.count
    
        void callNum() {
            // Upper의 count 와 Lower의 count 를 구분하기 위해 this. super.을 사용
            System.out.println("count = " + count);
            System.out.println("this.count = " + this.count);
            System.out.println("super.count = " + super.count);
        }
    }
  • super()

    • 상위 클래스의 생성자를 의미함
    • 사용조건 ( this()와 같음 )
      • 생성자 내부에서 사용가능
      • 반드시 생성자 첫줄에 사용해야함
    • 하위 클래스 객체 생성자를 통해 생성시에 super가 없다면, 컴파일 과정에서 자동으로 super()를 추가해서 함
    public class superTest {
        public static void main(String[] args) {
            Lower lower = new Lower();
            lower.callNum();
            // count = 100
            // this.count = 100
            // super.count = 100 / 상위 클래스의 생성자를 사용해서 상위 클래스의 field값도 설정되어 있음
        }
    }
    
    class Upper {
        int count = 10;
        Upper(int count){this.count = count;}
    }
    
    class Lower extends Upper {
        Lower(){super(100);}
    
        void callNum() {
            // Upper의 count 와 Lower의 count 를 구분하기 위해 this. super.을 사용
            System.out.println("count = " + count);
            System.out.println("this.count = " + this.count);
            System.out.println("super.count = " + super.count);
        }
    }

◎ 캡슐화(Encapsulation)

  • 캡슐화(Encapsulation)

    • 특정 객체 안에 관련된 속성과 기능을 하나의 캡슐로 만들어 데이터를 외부로부터 보호하는 것
    • 목적 : 데이터 보호, 내부적으로만 사용되는 데이터에 대한 불필요한 외부 노출을 방지
    • 장점 : 정보 은닉(Data Hiding), 외부로부터 객체의 속성과 기능이 함부로 변경되지 못하게 막고, 변경되더라도 다른 객체에 영향을 주지 않게함
  • 패키지

    • 특정한 목적을 공유하는 클래스와 인터페이스의 묶음, 클래스들을 그룹 단위로 묶어 효과적으로 관리
    • . 단위로 하위 패키지를 구분한다. (디렉토리의 계층구조를 표시)
    • 같은 패키지 내에서는 같은 이름의 Class를 사용할 수 없다.
  • Import 문

    • 다른 패키지 내의 클래스를 사용하기 위해 사용, 패키지 구문과 클래스 구문 사이에 작성

    • 컴파일시에 import문이 처리되므로 프로그램의 성능에는 영향을 주지 않는다.

      package practicepack.test2;
      
      import practicepack.test.ExampleImp // import문 작성
      
      public class PackageImp {
              public static void main(String[] args) {
                  ExampleImp x = new ExampleImp(); // 이제 패키지명을 생략 가능
              }
      }
  • 접근 제어자(Access Modifier)

    • 외부로의 불필요한 노출을 방지, 데이터의 임의적 변경 방지 가능
    • field나 method 선언하는 부분 앞에 적어서 접근 범위를 제한함
    • 종류
      • private : 동일 클래스에서만 접근 가능
      • default : 동일 패키지 내에서만 접근 가능 (해당 구문 생략 가능)
      • protected : 동일 패키지 + 다른 패키지의 하위 클래스에서 접근 가능
      • public : 접근 제한 없음
  • getter와 setter

    • 데이터 보호를 위해 field 변수를 private를 선언할 시,
      다른 Class에서는 변수값 호출, 변경이 불가
    • 만약 데이터의 호출이 필요하다면, getter 메서드를 구현해서 값을 가져옴
      만약 데이터의 변경이 필요하다면, setter 메서드를 구현해서 값을 변경함 (조건 부여 가능)
    public class EncapsulationTest {
        public static void main(String[] args) {
    
            GetterSetter getterSetter = new GetterSetter(10);
            // getterSetter.data // 에러 발생
            System.out.println(getterSetter.getData()); // 10
    
            getterSetter.setData(100);
            System.out.println(getterSetter.getData()); // 100
        }
    }
    
    class GetterSetter{
        private int data;
        
        // 생성자
        GetterSetter(int data){
            this.data = data;
        }
    
        // Getter,  네이밍 규칙 : get-
        public int getData(){
            return this.data;
        }
    
        // Setter,  네이밍 규칙 : set-
        public void setData(int data){
            this.data = data;
        }
    
    }

◎ 다형성(Polymorphism)

  • 다형성(Polymorphism)

    • 한 타입의 참조변수(상위 클래스)를 통해 여러 타입의 객체(하위 클래스들)를 참조할 수 있도록 만든 것
      • 이렇게 선언한 객체에서는 상위 클레스에 있는 필드와 메서드만 사용할 수 있다.
    • 타입별로 메서드들을 따로 만들지 않고, 상위 클래스 한번으로만 사용할 수 있게 해준다. (코드의 중복을 줄여줌)
    public class Main {
      public static void main(String[] args) {
      
      // 상위 클래스 (PeoPle) 가 하위 클래스 (Man)를 참조 가능
      People ManOfPerson = new Man();  
      // 반대는 불가능 ( 일반적으로 하위 클래스(Man)의 기능이 더 많으므로)
      // Man PersonOfMan = new Person();
      }
    }
    
  • 참조변수의 타입 변환

    • 타입변환 조건
      • 상속관계에 있는 상위 클래스 - 하위 클래스 사이에만 타입 변환이 가능
      • 하위 클래스 타입에서 상위 클래스 타입으로의 타입 변환(업캐스팅)은 형변환 연산자(괄호)를 생략할 수 있다.
      • 상위 클래스에서 하위 클래스 타입으로 변환(다운캐스팅)은 형변환 연산자(괄호)를 반드시 명시
    public class Main {
      public static void main(String[] args) {
          
          FirstClass firstClass = new FirstClass();
          ParentClass parentClass = new ParentClass();
    
          ParentClass example1_1 = firstClass; // OK
          ParentClass example1_2 = (ParentClass) firstClass; // OK
          // FirstClass example2_1 = parentClass; // 형변환 연산자 생략 불가 (Incompatable Type)
          FirstClass example2_2 = (FirstClass) parentClass; // ClassCastException 발생
      }
    }
  • instanceof

    • 캐스팅(형변환)이 가능한 지 여부를 boolean 타입으로 확인할 수 있는 자바의 문법요소
    • 필요성 : 프로젝트의 규모가 커지고, 클래스가 많아지면 매번 이러한 정보를 확인하는 것이 어려움
    • 판단방법 : 클래스 사이에 상속관계가 존재하는가
    public class Main {
      public static void main(String[] args) {
          ParentClass parentClass = new ParentClass();
          FirstClass firstClass = new FirstClass();
    
          // 모든 객체는 Object 를 확장함
          System.out.println(parentClass instanceof Object); // true
          // 자기 자신으로 형변환은 true
          System.out.println(parentClass instanceof ParentClass);  // true
          // 하위 클래스로 형변환은 불가 (형변환 연산자 사용하면 가능)
          System.out.println(parentClass instanceof FirstClass); // false
          // 상위 클래스로 형변환 가능
          System.out.println(firstClass instanceof ParentClass); // true
          // 형제 클래스로 형변환 불가
          System.out.println(firstClass instanceof SecondClass); // false
    
          // 상위 클래스가 하위 클래스를 참조한 경우
          ParentClass firstParentClass = new FirstClass();
    
          System.out.println(firstParentClass instanceof ParentClass); // true
          System.out.println(firstParentClass instanceof FirstClass); // true
          System.out.println(firstParentClass instanceof SecondClass); // false
      }
    }
  • 다형성 활용예제

    class UtilizePolymorphism {
    
        // 매개변수를 상위 클래스로 선언시, 하위 클래스로 넣을 수 있음
        static void checkOrder(ParentClass parentClass){
            System.out.print(parentClass.order + " ");
        }
    }
    public class Main {
      public static void main(String[] args) {
        FirstClass firstClass = new FirstClass();
        SecondClass secondClass  = new SecondClass();
        ThirdClass thirdClass = new ThirdClass();
    
        // 서로 다른 하위 클래스들을 상위 클래스의 배열로 선언 및 초기화 가능
        ParentClass[] classArray = {firstClass, secondClass, thirdClass};
    
        // 서로 다른 클래스의 같은 필드나 매서드를 보여주거나 실행하는데 용이함
        for (ParentClass orderclass : classArray){
              UtilizePolymorphism.checkOrder(orderclass);
        } // 1 2 3
    
      }
    }
    class ParentClass {
      int order;
      void CheckMethod() {System.out.println("I'm ParentClass Method");}
    }
    
    class FirstClass extends ParentClass {
        FirstClass() {this.order = 1;}
        void CheckMethod() {System.out.println("I'm FirstClass Method");}
    }
    class SecondClass extends ParentClass{
        SecondClass(){this.order = 2;}
        void CheckMethod() {System.out.println("I'm SecondClass Method");}
    }
    class ThirdClass extends ParentClass{
        ThirdClass(){this.order = 3;}
        void CheckMethod() {System.out.println("I'm ThirdClass Method");}
    }

◎ 추상화(Abstraction)

  • 추상화(Abstraction)

    • 기존 클래스들의 공통적인 요소들을 뽑아서 상위 클래스를 만들어 내는 것
    • 장점 : 공통적인 속성과 기능을 모아서 정의해주면 코드의 중복을 줄일 수 있고, 보다 효과적으로 클래스 간의 관계를 설정할 수 있으며, 유지/보수가 용이
  • abstract 제어자

    • 추상메서드
      • 메서드의 시그니처만 있고 바디가 없는 메서드
      • 메서드 앞에 abstract를 붙여 표현
    • 추상클래스
      • 추상메서드를 하나 이상 포함한 매서드
      • 추상클래스 앞에 abstract를 붙여 표현
      • 추상 클래스로 객체 선언 불가
    public class AbstractionTest {
        public static void main(String[] args) {
            // AbstractExample abstractExample = new AbstractExample();
        }
    }
    
    abstract class AbstractExample { // 추상 메서드가 최소 하나 이상 포함돼있는 추상 클래스
        abstract void start(); // 메서드 바디가 없는 추상메서드
    }
  • 추상 클래스 필요성

    • 추상 클래스에서 확장하여 하위 클래스를 만들어 사용
    • 하위 클래스에서 상위 메서드의 추상 메서드를 Overriding 하는 것을 강제함 (안 만들면 에러남)
    • 자바 객체지향 프로그래밍의 마지막 기둥인 추상화를 구현하는데 핵심적인 역할을 수행
    abstract class AbstractClass {
        int order;
        abstract void showOrder(); // 하위 클래스에서는 반드시 showOrder Method를 만들어야 함
    } 
    
    class FirstClass extends AbstractClass {
    
        FirstClass() {this.order = 1;}
        void showOrder() {System.out.println("My order: 1");}
    }
    class SecondClass extends AbstractClass {
    
        SecondClass() {this.order = 1;}
        void showOrder() {System.out.println("My order: 2");}
    }
  • Interface

    • 오직 추상 메서드와 상수만을 멤버로 가질 수 있다. (추상도가 추상 클래스보다 높음)
    • 모든 field 가 public static final 로 선언됨
    • static과 default 메서드 이외의 모든 메서드가 public abstract로 정의된다
  • Interface 구현 ( 하위 객체를 만들어 구현함 )

    • 인터페이스 구현시, implements 키워드를 통해 구현함
    • 당연히 Interface의 모든 매서드들은 하위 클래스에서 모두 오버라이딩 해야함
    • 하나의 클래스가 여러 개의 인터페이스를 구현할 수 있다.
    • 장점
      • 오버라이딩을 강제함 (유사한 기능의 특정 메서드를 반드시 만들어야 함)
      • 서로 다른 클래스의 공통점을 쉽게 뽑아낼 수 있음
      • Interface의 추상메서드를 구현한 모든 객체에 같은 이름의 메서드로 사용할 수 있다.
    public class InterfaceTest {
        public static void main(String[] args) {
            // Camera camera = new Camera(); // Interface는 직접 객체를 만들 수 없다.
            Owner owner = new Owner();
            SmartPhone smartPhone = new SmartPhone();
            DigitalCamera digitalCamera = new DigitalCamera();
    
            owner.takePhoto(smartPhone); // 사진 찍는다 찰칵!
            owner.takePhoto(digitalCamera); // 사진 찍는다 고급지게 찰칵!
            owner.callSomeone(smartPhone, "이충안"); // 이충안에게 전화를 건다.
        }
    }
    
    interface Camera {public abstract void takePhoto();}
    interface Phone {
        public static final String mic = "normal mic";
        String speaker = "normal speaker"; // public static final 생략 (일부 생략도 가능)
        void callSomeone(String person);
    }
    
    class SmartPhone implements Camera, Phone {
        // speaker = "upgrade"; // 수정하려 하면 에러 발생
        public void takePhoto(){
            System.out.println("사진 찍는다 찰칵!");
        }
        public void callSomeone(String person){
            System.out.println(person + "에게 전화를 건다.");
        }
    }
    
    class DigitalCamera implements Camera{
        public void takePhoto() {
            System.out.println("사진 찍는다 고급지게 찰칵!");
        }
    }
    
    class Owner {
        static void takePhoto(Camera camera) { // Inerface의 이름으로 매개변수를 선언해 구현한 모든 클래스의 객체를 넣을 수 있다.
            camera.takePhoto(); // Interface를 구현한 모든 객체에 같은 이름의 메서드로 사용할 수 있다.
        }
        static void callSomeone(Phone phone, String person){
            phone.callSomeone(person);
        }
    }

◎ 추가 학습 내용

  • final 키워드

    • 클래스 : 변경 또는 확장 불가능한 클래스, 상속 불가
    • 메서드 : 오버라이딩 불가
    • 변수 : 값 변경이 불가한 상수
  • Immutable Object(불변객체)

    • 어떤 객체를 생성 후, 필드 값들이 변경되지 않는 객체 (재할당은 가능)
    • 모든 field 에 final를 붙여준다고 불변객체가 되지 않는다.
      1. 상위 Class가 있을 경우, 참조 변수는 변할 수 있다.
      2. Array나 List가 사용되었을 경우, 변경할 수 없도록 따로 설정을 해주어야 한다.
profile
배운 것은 기록하자! / 오류 지적은 언제나 환영!

0개의 댓글