5주차 : 클래스

Joo·2023년 4월 14일

1. 객체 지향 프로그래밍 (Object-Oriented-Programming)

객체들 간의 상호작용을 통해 프로그래밍하는 방식

1.1 객체 지향 프로그래밍 특징

(1) 캡슐화 (Encapsulation)

객체의 내부 상태와 행동 하나의 클래스로 묶고 외부로부터 숨기는 과정

  • 캡슐화의 목적은 정보 은닉
    • 자바에서 접근 제어자를 통해 외부로부터 클래스, 메소드 등을 숨길 수 있음
    • 객체의 내부 구현을 숨김으로써 객체 간의 결합도를 낮추고, 독립성을 높일 수 있음
      → 모듈화 & 재사용성 향상
    • 객체의 인터페이스를 단순화하고 명확하게 할 수 있음
      • 사용자에게 필요한 메소드만 제공하고, 내부에서 사용되는 메소드는 숨길 수 있음
        → 사용자가 쉽게 객체를 이해하고 사용할 수 있음

(2) 상속 (Inheritance)

공통된 속성과 메소드를 재사용하는 방법

  • 상속을 통해 중복된 코드를 줄여 단순화 시킬 수 있음
  • 클래스의 계층 구조를 만들 수 있음

(3) 다형성 (Polymorphism)

하나의 단어다른 여러 의미를 갖는 것

  • 객체
    • 같은 타입의 객체지만 서로 다른 클래스의 객체가 저장될 수 있음
    • ex
      class Animal {
        public void makeSound() {
          System.out.println("Animal sound");
        }
      }
      
      class Dog extends Animal {
        @Override
        public void makeSound() {
          System.out.println("Woof");
        }
      }
      
      class Cat extends Animal {
        @Override
        public void makeSound() {
          System.out.println("Meow");
        }
      }
      public class Main {
        public static void main(String[] args) {
          Animal animal1 = new Dog();
          Animal animal2 = new Cat();
        }
      }
  • 메소드
    • 오버로딩
      • 같은 이름의 메소드지만 매개변수에 따라 다른 동작을 수행함
      • 컴파일 시 결정되는 정적인 다형성
    • 오버라이딩
      • 같은 이름의 메소드를 자식 클래스에서 재정의함
      • 런타임 시 결정되는 동적인 다형성

(4) 추상화 (Abstraction)

구체적인 것을 덜 구체적인 것으로 만드는 것

  • 일반화
    • 여러 객체로부터 공통 속성, 기능을 추출하는 것
      ex) BBQ 황금올리브 → 후라이드 치킨 → 치킨 → 음식 → 물체
    • 클래스 자체를 정의하는 행위
    • 여러 개의 클래스에서 공통된 것을 뽑아 인터페이스 또는 부모 클래스를 정의하는 행위
  • 장점
    • 공통된 부분을 재사용하여 코드를 줄이고 빠르게 개발할 수 있음
    • 유연한 구조를 가질 수 있음
      • 추상적인 부분(인터페이스, 부모 클래스)은 그대로 유지하고 구체적인 부분(구현체, 자식 클래스)만 변경할 수 있음

1.2 객체 지향 설계 5원칙 - SOLID

유지 관리확장이 가능한 프로그램을 만들기 위한 원칙

(1) SRP (Single Responsibility Principle)

하나의 클래스는 하나의 책임만 있어야 한다는 원칙

  • 클래스 간의 종속성을 낮추기 위한 원칙
    • 사이드 이펙트를 줄여 변경과 유지보수하기 쉬운 코드를 만들 수 있음

(2) OCP (Open/Closed Principle)

클래스가 확장에는 열려 있고, 수정에는 닫혀있어야 한다는 원칙

  • 클래스를 확장하거나 인터페이스 구현체를 추가하는 등의 확장에는 열려있어야 함
  • 확장했을 때 기존 클라이언트의 코드가 수정되면 안됨 → 다형성

(3) LSP (Liskov Substitution Principle)

하위 클래스의 객체가 상위 클래스 객체를 대체할 수 있어야 한다는 원칙

  • 서브 타입은 기반 타입으로 언제든지 교체될 수 있어야 함
    • 특정 자식 타입은 언제든지 다른 자식 타입으로 교체될 수 있어야 함
    • 특정 구현체는 언제든지 다른 구현체로 교체될 수 있어야 함
      → 다형성을 통한 확장을 올바르게 하려면 하위 타입(구현체)을 교체해도 이상이 없어야 함
      → 이를 위해 하위 타입은 인터페이스를 제대로 구현해야 함
  • 인터페이스 구현체를 믿고 사용하려면 구현체(하위 클래스)인터페이스 규약을 다 지켜야 함
    ex) 자동차 인터페이스
    • 엑셀을 밟았는데 뒤로감 → LSP 위반

(4) ISP (Interface Segregation Principle)

클라이언트가 사용하지 않는 메소드에 의존하도록 강요되면 안된다는 원칙

  • 인터페이스가 너무 큰 경우 클라이언트가 사용하지 않는 메소드임에도 포함되어 있을 수 있음
  • 큰 인터페이스를 분리하여 필요한 메소드에만 의존하도록 해야함

(5) DIP (Dependency Inversion Principle)

클라이언트가 특정 구현체가 아닌 추상화(인터페이스)에 의존해야 한다는 원칙

  • 특정 구현체에 의존할 경우 구현체가 변경되면 클라이언트의 코드도 변경되어야 함
  • 인터페이스에 의존할 경우 (구현체를 주입받으면) 클라이언트의 코드를 변경하지 않아도 됨 → 다형성

2. 클래스

2.1 클래스 구조

  1. 필드

    1. 인스턴스 변수

      메소드 밖, 클래스 안에 정의된 변수

      • 스코프 : static 메소드를 제외한 나머지 클래스 내부
      • 라이프타임 : 객체가 생성되고부터 GC에 의해 소멸될 때 까지
    2. 클래스 변수

      인스턴스 변수에 static이 붙은 변수

      • 스코프 : 클래스 내부
      • 라이프타임 : 클래스가 로드된 후 부터 프로그램이 종료될 때 까지
    3. 매개 변수

      메소드에 넘겨주는 변수

      • 스코프 : 호출한 메소드 내부
      • 라이프 타임 : 메소드가 호출될 때 부터 메소드가 끝날 때 까지
    4. 지역 변수

      중괄호 내에 선언된 변수

      • 스코프 : 선언된 중괄호 내부
      • 라이프 타임 : 중괄호가 닫힐 때 까지
      • 기본 자료형을 지역 변수로 사용할 경우 반드시 초기화를 해줘야 함
  2. 메소드

    1. 클래스 메소드
      • static 메소드
      • 보통 클래스 이름.메소드 방식으로 사용
        • 인스턴스.메소드로 사용할 수도 있으나 이렇게 사용하면 클래스에 종속된 메소드를 인스턴스가 사용하는 꼴이기 때문에 경고 문장이 뜸
    2. 인스턴스 메소드
      • static이 없는 메소드
      • 인스턴스를 생성하고 인스턴스를 통해 접근해야 함
  3. 생성자

2.2 modifier

  • 접근 제어자
    • 종류 4가지
      1. public
        • 누구나 접근 가능
      2. protected
        • 같은 패키지or 상속받은 경우 접근 가능
      3. package-private (default)
        • 같은 패키지 내 접근 가능
      4. private
        • 해당 클래스 내 접근 가능
    • 클래스, 메소드, 인스턴스 변수, 클래스 변수 선언 시 사용
    • 하나의 자바(.java) 파일에는 최대 하나의 public class만 존재할 수 있고 그 이름이 같아야 함.
      • 존재하지 않을 수 도 있음
  • 그 외
    • static
    • final
    • abstract
    • transient = 일시적인, 잠깐 머무르는
      • transient 변수 또는 메소드가 포함된 객체를 직렬화 할 때 해당 내용을 무시함
      • ex
        class Person implements Serializable {
            private String name;
            private transient int age;
        
            public Person(String name, int age) {
                this.name = name;
                this.age = age;
            }
        
            public String getName() {
                return name;
            }
        
            public int getAge() {
                return age;
            }
        }
        
        public class Main {
            public static void main(String[] args) throws Exception {
                Person p = new Person("John", 30);
                
                // Serialize the object
                FileOutputStream fileOut = new FileOutputStream("person.ser");
                ObjectOutputStream out = new ObjectOutputStream(fileOut);
                out.writeObject(p);
                out.close();
                fileOut.close();
                
                // Deserialize the object
                FileInputStream fileIn = new FileInputStream("person.ser");
                ObjectInputStream in = new ObjectInputStream(fileIn);
                Person p2 = (Person) in.readObject();
                in.close();
                fileIn.close();
        
                // Output the deserialized object
                System.out.println("Name: " + p2.getName());
                System.out.println("Age: " + p2.getAge());
            }
        }
        Name: John
        Age: 0
        • transient 변수 age는 직렬화 과정에서 제외되므로 역직렬화로 객체를 얻었을 때 값이 기본값으로 초기화되어 있음
    • synchronized
      • 하나의 쓰레드만 접근 가능하게 하는 키워드
        • 원자성을 보장
      • Thread Safe를 위해 사용
        • Thread Safe
          • 여러 쓰레드가 동시에 공유된 자원을 사용해도 안전한 것을 의미함
          • 자원에 대한 접근이 동기화되어 있어 일관성과 정확성이 유지됨
    • volatile
      • 자바 변수를 CPU 캐시가 아니라 메인 메모리로부터 직접 읽거나 쓰기 위해 사용하는 키워드
      • 멀티 쓰레드 환경에서 가시성 문제를 해결하기 위해 사용
        • 가시성 문제
          • 한 쓰레드가 변수의 값을 변경했을 때 다른 쓰레드가 변경된 값을 보지 못하는 현상
      • 가시성은 보장하지만 원자성은 보장하지 못함

2.3 필드 초기화

  1. 변수 선언과 동시에 초기화
  2. 생성자를 통한 초기화
  3. static 블록을 통한 초기화
    • 클래스 변수를 초기화하는데 사용
    • private static 메소드를 사용해도 됨
  4. 인스턴스 블록을 통한 초기화
    • 어떤 생성자를 호출하던 공통으로 초기화시키고 싶은 작업이 있을 시 사용
  • static 블록, 인스턴스 블록사용하는 이유
    • 오버로딩된 생성자가 여러 개 있는 경우
      • 모든 생성자에서 공통으로 처리해야하는 작업이 있으면 코드를 줄일 수 있고 유지보수 하기가 쉬워짐

2.4 Nested Class

클래스 안에 클래스가 들어가 있는 것

  • 별도로 컴파일 할 필요 없이 감싸고 있는 클래스가 컴파일 될 때 자동으로 컴파일됨
    • 내부 클래스가 1개라면 컴파일 시 2개의 .class 파일 생성됨

(1) 종류

  1. Static nested class
  2. Inner class
    1. (local) Inner class - 내부 클래스
      • Local Class - 특정 메소드 내부에 선언
    2. Anonymous inner class - 익명 클래스

(2) 사용 목적

  1. 한 곳에서만 사용되는 클래스를 묶어서 처리하기 위해 (static nested class)
  2. 캡슐화하기 위해 (inner class)
    • A 클래스의 private 변수를 B 클래스가 접근하고 싶은 경우 → B를 inner class로 생성
  3. 가독성과 유지보수성을 높이기 위해

(3) Static nested class

  • 외부 클래스와 느슨하게 관련있거나 외부 인스턴스와 독립적으로 사용할 경우 사용
    • 외부 클래스의 static 변수만 접근 가능함
  • 내부 클래스가 외부 클래스의 인스턴스에 종속되지 않고 독립적으로 사용할 수 있음
    • 메모리 누수를 방지할 수 있음
      • 외부 클래스에 대한 참조를 가지고 있지 않음
      • 외부 클래스가 GC의 수거 대상이 되어도 내부 클래스는 전혀 영향을 받지 않음
  • 객체 생성
    • 외부클래스.static내부클래스
      OuterOfStatic.StaticNested staticObject = new OuterOfStatic.StaticNested();

(4) 내부 클래스

  • 내부 클래스와 외부 클래스가 밀접한 관련이 있거나 외부 인스턴스와 영향을 주고받는 경우 사용
  • 내부 클래스가 외부 클래스를 참조함
    • 외부 클래스 인스턴스가 더 이상 사용되지 않아도 내부 클래스 인스턴스가 살아있으면 GC의 수거 대상이 되지 않음
    • 외부 클래스의 멤버 변수, 생성자 등을 참조하기 때문에 메모리 사용량도 많고 성능이 나쁨
  • 객체 생성
    OuterOfInner outer = new OuterOfInner();      // 외부 클래스 객체 먼저 생성
    OuterOfInner.Inner inner = outer.new Inner(); // 외부 클래스 객체를 사용해 생성자 호출

(5) 익명 클래스

  • 매개변수로 인터페이스를 받는 경우 인터페이스를 구현한 클래스를 따로 만들지 않고 익명의 클래스 객체를 만들어 메소드를 구현하는 것
interface A {
    void methodA();
}

public class Main {

    static void methodB(A a) {  // 매개변수로 인터페이스를 받는 메소드
        a.methodA();            // 인터페이스의 메소드를 사용
    }

    public static void main(String[] args) {
        methodB(new A() {       // 매개변수로 인터페이스를 구현한 익명클래스를 받음
            @Override
            public void methodA() {
                System.out.println("hi");
            }
        });                     // ; 붙여줘야 함
    }
}
public static void main(String[] args) {
    OneMethodInterface implObject = new ImplClass();

    OneMethodInterface implObject2 = new ImplClass2();

    OneMethodInterface implObject3 = new OneMethodInterface() {
        @Override
        public void hi() {
            System.out.println("hihihi");
        }
    };

    OneMethodInterface implObject4 = () -> System.out.println("hihihihi");

    implObject.hi();   // hi
    implObject2.hi();  // hihi
    implObject3.hi();  // hihihi
    implObject4.hi();  // hihihihi
	
}
  • 클래스의 이름도 객체의 이름도 없기 때문에 외부에서 클래스나 메소드를 참조할 수 없음
  • 인터페이스를 구현한 클래스를 만들어서 매개변수로 받는 것 보다 메모리 사용도 줄이고 시간도 줄일 수 있기 때문에 사용

(6) Nested class 특징

참조 가능한 변수가 다름

  • Static nested class
    • 외부 클래스의 static 변수만 접근 가능
  • Inner class & Anonymous class
    • 모든 변수 접근 가능
  • 외부 클래스
    • Nested class의 모든 변수에 접근 가능

2.5 객체 만드는 방법

new

  • 클래스에 선언된 생성자를 호출
  • 하나의 클래스에 다양한 생성자가 오버로딩으로 선언되어 있을 수 있음 → 다양한 매개변수 조합으로 인스턴스를 생성할 수 있음

2.6 메소드 정의하는 방법

4.1 메소드 구조

  1. modifier
  2. 리턴 타입
  3. 메소드 이름
  4. 매개변수
    • 가변 인수(Varargs)
      • 필요에 따라 매개변수의 갯수를 가변적으로 조정할 수 있는 기술
        • 전달받은 매개변수를 배열로 만듦
      • 자바 5부터 지원되는 기능
      • 매개변수 리스트의 맨 마지막에만 사용해야함
      • 한번만 사용 가능함
      • ex
        public static void print(String... data) {
          for (String s : data) {
            System.out.println(s);
          }
        }
        print("Hello"); // Hello
        print("Hello", "World"); // Hello World
        print("Hello", "World", "Java"); // Hello World Java
  5. 메소드 바디

4.2 메소드 시그니처

  • 메소드 이름 + 매개변수의 조합 (매개변수 갯수, 순서, 타입)
    • modifier, 리턴타입은 메소드 시그니처에 포함되지 않음

❗ 오버라이딩

  • 메소드 시그니처 + 리턴타입 동일해야 함
  • modifier는 범위가 더 작아질 수 없음

4.3 메소드 명명법 (자바 컨벤션)

  • lowerCamelCase
  • 동사/전치사로 시작함
    ex) getUserName(), toString()
  • 메소드 이름으로 자주 사용되는 동사
    1. get/set
    2. init
      • 데이터를 초기화하는 메소드 ex) initData(), initMember()
    3. is/has/can
      • 리턴타입으로 boolean을 리턴하는 메소드
      • is
        • 맞는지 틀린지 판단하는 메소드 ex) isEmpty()
      • has
        • 데이터를 가지고 있는지 확인하는 메소드 ex) hasData()
      • can
        • 할 수 있는지 없는지 확인하는 메소드 ex) canOrder()
    4. create
      • 새로운 객체를 만든 후 리턴해주는 메소드 ex) createOrder()
    5. find
      • 데이터를 찾는 메소드 ex) findElement()
    6. to
      • 해당 객체를 다른 형태의 객체로 변환하는 메소드 ex) toString()
    7. A-By-B
      • B를 기준으로 A를 하겠다는 메소드 ex) getUserByName()

2.7 생성자 정의하는 방법

5.1 생성자

  • 객체를 생성하고 heap 영역에 메모리 할당함

5.2 기본 생성자

  • 매개변수로 아무것도 받지 않는 생성자
  • 클래스 내부에 생성자를 하나도 선언하지 않은 경우
    • 컴파일 단계에서 기본 생성자 자동으로 생성
  • 클래스 내부에 선언된 생성자가 있는 경우
    • 자동으로 생성되지 않음
    • 기본 생성자를 사용하려면 추가로 선언해줘야함

5.3 생성자 오버로딩

  • 다양한 조합의 매개변수를 받아 객체를 생성하고자 할 때 사용

Reference

[java-live-study] 5주차-클래스

modifier

study/java/whiteship-study/5week at main · ByungJun25/study

Reference type parameter, nested class

TIL/java/live-study/05. 클래스 at master · hypernova1/TIL

클래스 메소드의 사용

[5주차] 클래스

java 클래스 실행 순서

좋은 코드를 위한 자바 메서드 네이밍

메소드 명명법, getter/setter 주의사항

[Java-25] 클래스 & 내부클래스 & 익명클래스

내부 클래스 사용 → 캡슐화!

+@ 트리 자료구조

[Java] 트리 자료구조의 개념과 구현

0개의 댓글