[Java] 객체지향프로그래밍 추가 강의

근이의 개발일기·2024년 8월 19일
0
post-thumbnail

Exception Hadling (예외 처리)

  • Type of Exception Classes
    • Exception 클래스들은 상속관계로 이뤄진다
      • 최상위에는 Trowable 예외 클래스를 상속하고 그 후에 Error와 Exception으로 나누어진다.

      • 모든 Runtime에러가 Exception으로 구서오딘 것은 아니다. 바로 종료하게 되는 Error도 있음

      • Error는 Handling 할 수 없음

        Untitled

    • Unchecked exception과 Checked exception으로 나누어짐
      • Check를 하는 주체는 컴파일러이다
      • Runtime Exception이 중요하다. Runtime Exception은 Unchecked Exception이다.
  • Unchecked exception을 발생하는 프로그램
    • Unchecked exception은 컴파일러가 확인할 수 없어 런타임 에러를 발생시킨다 → 예외 발생 Untitled
  • Checked exception을 발생하는 프로그램
    • 중요 Checked exception은 컴파일러가 확인할 수 있어 예외처리를 하지 않으면 컴파일 에러를 발생시킨다 Untitled
      • 의도적으로 예외 객체를 발생시킨다!
      • 예외 처리 방법은 2가지가 있음
    • try&catch문을 통한 처리 Untitled
      • 주의! catch문 안에서 오류 메시지를 출력할 때는 System.err을 사용한다!
    • 메소드의 throws 선언 Untitled
      • 함수의 리턴형 또는 예외를 발생시킨다는 것을 의미한다
      • 주의! throws (예외 객체)를 메소드 인자 뒤에 정의하게 되면 컴파일은 되지만 예외의 실질적인 처리는 되지 않는다
  • Unchecked exception을 던지는 메소드
    • Unchecked exception을 던지는 메소드는 예외처리 해주지 않아도 컴파일이 가능하고, 주의! throws Exception 표시를 해주어도 컴파일이 가능하다! Untitled
    • 아래의 예시에서는 메소드에서 Unchecked Exception 예외가 발생해서 try & catch 구문으로 처리해주는 모습이다 Untitled
  • Checked exception을 던지는 메소드
    • Checked exeption을 caught하거나 throws를 정의하지 않아서 컴파일 에러가 난 모습이다 Untitled
    • throws를 정의하여서 컴파일은 되지만 caught하지 않아서 런타임 에러가 난 모습이다. Untitled
      • 예외 발생하는 위치에 throws Exception을 정의하여야함
      • 여러 예외를 ,로 구분하여 나열 가능함.
      • 최상위 타입인 Exception만 써주어도 되지만 정보공유를 위해 다 써주어도
    • 예외가 발생하는 메소드에 throws Exception을 해주고, main에서 예외를 caught하여서 에러가 발생하지 않고 컴파일이 된 모습이다. Untitled
    • 중요 Checked exception을 던지는 메소드 다루기
      • Checked exception을 던지는 메소드에 throws 예외를 정의해야 컴파일이 됨
      • 메소드를 호출한 곳에서 try & catch로 예외를 처리해야 런타임 에러가 나지 않음
  • getMessage 메소드의 사용
    • getMessage(): 예외 객체가 발생시킨 메시지 문자열을 직접적으로 리턴한다. Throwable 클래스를 상속 받은 모든 예외 클래스에서 제공되는 메소드이다. Untitled
      • 화면에 뜨는 오류 Exception 뒤에 오게 되는 내용임

        Untitled

      • 중요 예외 객체 생성자에 문자열을 넣음으로써 그 문자열이 getMessage() 메소드의 리턴값이 되게 할 수 있다.

  • printStackTrace 메소드의 사용
    • printStackTrace(): 예외처리 하지 않았을 때의 화면을 예외처리로써 보여줄 수 있다. 구조적으로 예외처리 한 것이고 디버깅하기 위해 일부러 사용해준다. Untitled
      • 여기서도 마찬가지로 Exception 생성자를 통해 전달한 문자열이 오류 메시지로써 출력되게 된다!
  • 중요 Exception의 다형성
    • 중요 catch의 파라미터에 오는 예외 타입은 상속관계를 생각하여 상위 타입 예외 타입으로 받아줄 수 있다! Untitled
      • FileReader 생성자에서는 FileNotFoundException (IOException의 하위 클래스)
      • close() 메소드에서는 IOException 발생 가능
      • 두가지 종류 모두 발생하면 받아줄 수 있다!
    • 중요 상속 관계에 있는 두 종류의 예외처리 예시
      • 올바른 예시 → 서브 타입 예외를 먼저 catch문에서 처리해준다!! Untitled
        • 상위 타입이 나왔을 때, catch문에서 받아주지 못한다
      • catch 절의 순서가 잘못된 경우 → 상위 타입 예외를 먼저 catch문에서 처리한다면.. Untitled
        • 주의! 상위타입 예외가 모두 다 catch하므로 하위타입 예외는 실행이 될 수 없음 → 컴파일 에러가 난다!
  • Exception 클래스의 선언 예시
    • Exception 클래스의 선언 Untitled
      • Exception 예외 타입을 상속해준다
      • 중요 생성자에서 super(String)의 형태로 정의하여 getMessage()에 들어갈 오류 문자열을 상위타입의 생성자로 보내준다
    • 사용 예시 Untitled
      • 디폴트 생성자 밖에 없기에 디폴트 생성자로 생성해준다!
  • 중요 try-with-resources 구문
    • resource를 가지는 상황에 예외처리를 해줄 때, close와 같은 함수를 finally에서 실행시켜 안정성을 더해줄 수 있음 Untitled
      • 이를 try의 파라미터에서 resource를 넣어줌으로써 close() 메소드를 호출할 필요 없이, 자원을 자동으로 해제해줌 (try 블록이 끝날 때 자동으로 자원이 해제됨) , close 보장!

      • 주의! 이 자원은 AutoCloseable 인터페이스를 구현해야만함

      • 이 자원은 try문 내부에서만 사용됨

        Untitled

      • 정상적인 상황: try문 들어가기 전 resource 할당, try문 실행, try 끝나면 바로 close, 그 후 catch, finally절이 실행됨

      • 비정상적인 상황: try문 들어가기 전 resource 할당, try문 실행중 예외발생, catch가기 전 바로 close, 그 후 catch, finally절이 실행됨

        Untitled

Initialization (초기화)

  • 정적 초기화 블록
    • 정적 초기화 블록: static 멤버를 초기화하는 블록이다. 초기화는 처음 클래스 로드 바로전에 한 번만 실행된다. 정적 초기화 블록은 static 키워드를 사용하여 정의함

      • 클래스가 처음 로드될 때 한 번 실행된다.
      • 정적 필드만 초기화할 수 있다.
      • 클래스 로더에 의해 클래스가 메모리에 로드될 때 실행된다.
        • 중요 클래스의 로딩 시점 → 클래스가 처음으로 사용될 때 (예: 객체 생성, 클래스의 정적 메소드 호출, 정적 필드에 접근 등).
          • 주의! 레퍼런스 생성 시에는 로딩되지 않는다!
    • 정적 초기화 블록 초기화 과정

      Untitled

      • 필드 공간의 할당을 먼저함
      • 정적 초기화 블록은 위에서 아래로 순서대로 초기화함
        • 중요 맨아래 블럭이 마지막에 실행!
      • 처음 접근하기 바로 전에 초기화함
      • 나중에 초기화한 값을 가지게 됨
        • 중요 arr1은 처음에 {1,2,3}으로 초기화 되었지만, 나중의 정적 초기화 블록 내에서 arr1을 한번 더 초기화한 값을 가지게 됨
    • 주의! 선언과 함께 초기화하는 경우

      Untitled

      • 공간 할당 및 기본 값 할당을 먼저 해준다
        • arr, filed1, field2의 공간을 할당해준다.
        • null, null, null을 할당한
        • 주의! 선언과 함께 초기화하는 경우도 초기화는 나중에 순서대로 위에서 부터 한다는 것이다!
      • 위에서부터 정적 초기화를 시작한다
        • 정적 초기화 블록에 들어가 arr, filed1, filed2를 초기화해준다
        • field1은 선언부에서 초기화하게 된다.
          • filed1은 나중에 초기화된 값인 “field1 in the declaration”의 값을 가지게 된다!!!
    • 정적 초기화 블록 내에서 선언된 위치보다 위에서 초기화(쓰는 것)는 가능하지만, 읽어가는것은 안된다!

      Untitled

      • 아직 초기화 되지 않았을 수도 있기 때문이다! 변수를 위로 올려서 해결한다
    • 선언부를 정적 초기화 블록 위에서 해주어서 읽고 쓰기를 가능하게 함

      ![Untitled](https://prod-files-secure.s3.us-west-2.amazonaws.com/d48a8b3a-0902-461a-8e65-1e3bd3ed9699/f2ffed7b-1897-4ab7-8769-031a84f5424e/Untitled.png)

      정적 초기화 블록 예시

      java코드 복사
      public class Example {
          static int staticVar;
      
          static {
              // 정적 초기화 블록
              staticVar = 10;
              System.out.println("Static initialization block executed.");
          }
      
          public static void main(String[] args) {
              System.out.println("StaticVar: " + staticVar);
          }
      }
      

      위 예제에서 static 블록은 staticVar를 초기화하고, 클래스가 처음 로드될 때 "Static initialization block executed." 메시지를 출력한다. 주의! public class에서 정적 초기화 블록은 main함수 실행 전에 바로 클래스가 로딩되어 가장 먼저 실행된다!

  • 인스턴스 초기화 블록
    • 인스턴스 초기화 블록: 생성자와 비교하자면, 생성자는 외부값을 받아서 초기화하는 것이지만, 내부적으로 초기화할때는 인스턴스 초기화 블록을 사용한다.

    • 클래스의 객체가 생성될 때마다 실행된다. 인스턴스 필드를 초기화하거나 객체 생성 시에 필요한 설정을 수행하는 데 사용된다.

      • 클래스의 인스턴스가 생성될 때마다 실행된다.
      • 생성자 호출 전에 실행된다.
      • 인스턴스 필드와 정적 필드 모두 초기화할 수 있다.
    • 인스턴스 초기화 블록 초기화 과정

      Untitled

      • 주의! 클래스는 하나지만 객체는 여러번 생성함 스태틱 멤버는 객체 초기화시 마다 만들어 지는 것이 아님
      • 그러나 객체 만들기 위해서는 클래스 로딩 전 정적 초기화 먼저 해야한다 → static 필드 먼저 초기화 해주어야함 <정적 초기화>
        • static 초기화 먼저 진행한다 → static 필드 공간 및 기본값 할당, 정적 초기화 블록 및 초기화 위에서부터 아래로 실행

          <클래스 로딩>

          <객체 생성>

        • new를 통해 heap에 객체가 생겨서 nonstatic 필드의 공간과 기본값을 할당한다.

        • 그 후 nonstatic 초기화 블록 및 초기화 위에서부터 아래로 실행

        • 그 다음 생성자 호출

      • 주의! 맨처음 초기화 시에는 static끼리만 생성 후, nonstatic끼리만 생성한다!
    • 인스턴스 초기화 블록에서도 선언의 위치 주의!

      ![Untitled](https://prod-files-secure.s3.us-west-2.amazonaws.com/d48a8b3a-0902-461a-8e65-1e3bd3ed9699/06893c24-8fbf-4b3a-9ed8-5a7193ecdf21/Untitled.png)

      인스턴스 초기화 블록 예시

      java코드 복사
      public class Example {
          int instanceVar;
      
          {
              // 인스턴스 초기화 블록
              instanceVar = 20;
              System.out.println("Instance initialization block executed.");
          }
      
          public Example() {
              System.out.println("Constructor executed.");
          }
      
          public static void main(String[] args) {
              Example example = new Example();
              System.out.println("InstanceVar: " + example.instanceVar);
          }
      }
      

      위 예제에서 인스턴스 초기화 블록은 instanceVar를 초기화하고, 인스턴스가 생성될 때마다 "Instance initialization block executed." 메시지를 출력합니다. 이후 생성자가 호출되어 "Constructor executed." 메시지를 출력합니다.

Object Cloning (객체 복제)

  • 중요 Object 클래스의 clone() 함수를 사용함
    • protected Object clone() throws CloneNotSupportedException
    • Object 클래스의 clone() 메서드는 객체의 얕은 복사를 수행함.
    • 이 메서드는 protected로 선언되어 있으므로, 클래스에서 직접 호출하려면 clone() 메서드를 재정의하고 접근 제어자를 public으로 변경해야함
    • checked Exception이기 때문에 오버라이딩 시 필수적으로 예외 처리해주어야함 자바에서 객체 복제(Object Cloning)는 한 객체의 데이터를 다른 객체에 복사하는 방법을 말함. 자바에서는 Cloneable 인터페이스와 Object 클래스의 clone() 메서드를 사용하여 객체를 복제할 수 있습니다. 객체 복제는 얕은 복사(Shallow Copy)와 깊은 복사(Deep Copy) 두 가지 방식으로 나뉩니다.
  • Cloneable 인터페이스

Cloneable 인터페이스는 마커 인터페이스(Marker Interface)로, 아무 메서드도 선언하지 않습니다. 이 인터페이스는 해당 클래스가 clone() 메서드를 사용할 수 있다는 신호로 사용됩니다.

  • 중요 clone()의 오버라이딩 방법

    Untitled

    • public Object clone() throws CloneNotSupportedException {

      return super.clone();}
      
      형식으로 clone()함수를 오버라이드 해준다! 구현이 간단
      
      - throws를 해주어야함 → 컴파일 에러
      - clone()메소드를 사용하는 부분에서 try catch를 사용해야함
          
          → 컴파일 에러
          
      - Object형식으로 리턴하기 때문에 clone()메소드 사용 후에 타입캐스팅을 해주어야함
      - Closeable을 구현하지 않으면 무조건 예외를 발생시킴

      Untitled

    • clone() 메소드를 오버라이딩한 후에 클래스에 implements Cloneable을 해주어야 정상적으로 작동함

    • 주의! Cloneable을 구현해주지 않으면 clone을 만들 수 없는 클래스라고 인식하여서 예외를 발생시킴!

      Untitled

    • 클론 메소드에서 여러 메소드 사용가능

    • 중요 p1과 p2는 같은 필드값을 가지지만 같은 레퍼런스를 가리키는 것이 아님을 알 수 있음

  • 얕은 복사(Shallow Copy)와 깊은 복사(Deep Copy)

    • 얕은 복사(Shallow Copy): 원본 객체의 모든 필드 값을 그대로 복사한다. 객체 필드의 경우, 해당 필드 값(객체의 참조)만 복사되므로 원본 객체와 복사된 객체는 같은 객체를 참조함.
      • 주의! String은 깊은 복사가 이뤄지니 상관하지 말 것!

        Untitled

    • 깊은 복사(Deep Copy): 객체가 참조하는 모든 객체를 포함하여 전체 객체 그래프를 복사합니다. 즉, 원본 객체와 복사된 객체는 완전히 독립적인 객체가 됩니다. Untitled
      • clone() 메소드에서 객체 레퍼런스에 clone() 메소드로 복사해주고, 그 객체 레퍼런스로 객체 필드는 따로 clone()을 호출하여 복사해준다. 그 후 객체 레퍼런스를 return한다
  • clone() 함수를 사용자 클래스 타입으로 return

    Untitled

    • clone메소드 내에서는 캐스팅해주고 리턴형은 사용자 클래스 타입으로 하여서 main에서 사용할 때는 캐스팅 하지 않고 사용한다

얕은 복사 예제

java코드 복사
class Person implements Cloneable {
    String name;
    int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }

    public static void main(String[] args) {
        try {
            Person person1 = new Person("John", 30);
            Person person2 = (Person) person1.clone();

            System.out.println("person1: " + person1);
            System.out.println("person2: " + person2);

            System.out.println("Are they the same object? " + (person1 == person2));
            System.out.println("Do they have the same name? " + (person1.name == person2.name));
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

위의 코드에서 person1person2는 다른 객체이지만, 그 안의 필드 name은 동일한 문자열 객체를 참조합니다.

깊은 복사 예제

깊은 복사를 위해서는 모든 참조된 객체들도 복제해야함

java코드 복사
class Address implements Cloneable {
    String city;
    String country;

    Address(String city, String country) {
        this.city = city;
        this.country = country;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

class Person implements Cloneable {
    String name;
    int age;
    Address address;

    Person(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person cloned = (Person) super.clone();
        cloned.address = (Address) address.clone();
        return cloned;
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + ", address=" + address.city + ", " + address.country + "}";
    }

    public static void main(String[] args) {
        try {
            Address address = new Address("New York", "USA");
            Person person1 = new Person("John", 30, address);
            Person person2 = (Person) person1.clone();

            System.out.println("person1: " + person1);
            System.out.println("person2: " + person2);

            System.out.println("Are they the same object? " + (person1 == person2));
            System.out.println("Do they have the same address object? " + (person1.address == person2.address));
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

위의 코드에서 person1person2는 서로 다른 객체이며, address 필드도 각각 독립적인 객체를 가집니다.

결론

  • Cloneable 인터페이스: 객체 복제를 허용하기 위한 마커 인터페이스.
  • Object의 clone() 메서드: 객체의 얕은 복사를 수행하는 메서드로, 클래스에서 이를 오버라이드하여 접근 제어자를 public으로 변경해야 함.
  • 얕은 복사: 객체의 모든 필드를 그대로 복사하지만 참조된 객체는 동일한 객체를 참조함.
  • 깊은 복사: 객체와 그 객체가 참조하는 모든 객체를 독립적으로 복사함.

Nested Classes (중첩 클래스)

  • Nested Classes
    • Nested 클래스: 자바에서 클래스는 다른 클래스 내부에 정의될 수 있으며, 이러한 클래스는 Nested 클래스라고 한다.
    • Nested 클래스는 크게 두 가지로 분류:
      • 정적 Nested 클래스(Static Nested Class)
      • 비정적 Nested 클래스(Non-static Nested Class). 비정적 Nested 클래스는 내부 클래스(Inner Class)로도 불림
    • 비정적 Nested 클래스는 다시 세 가지로 나뉨:
      • 인스턴스 내부 클래스(Instance Inner Class)
      • 지역 클래스(Local Class)
      • 익명 클래스(Anonymous Class)
    • static nested 클래스와 Instance Inner 클래스 Untitled
      • 멤버로 사용하기 때문에 static으로 정의가 가능하다

        Untitled

      • 내부 클래스에서도 static이든 아니든 똑같이 필드, 메소드, 생성자를 만들어 줄 수 있음

    • 중요 별도의 클래스에서 Nested 클래스 생성 및 접근 Untitled Untitled
      • OuterClass타입이름.InnerClass타입이름=
        • (OuterClass객체이름).new InnerClass(); ← Instance
          • 외부 클래스 객체의 내부 클래스 객체인 것을 짝지어 주기 위해 이 방식 사용
        • new (OuterClass타입이름).StaticNestedClass(); ← static
          • 클래스 타입의 static 멤버를 생성하는 것
    • 중요 외부 클래스 내부에서 Nested 클래스를 생성 Untitled
      • 외부 클래스 내부에서 생성하는 경우 (OuterClass타입이름).과 this. {= (OuterClass객체이름).}이 생략!
      • 주의! this.을 썼거나 생략된 경우(Instane Inner Class) static에서 사용할 수 없다
    • Nested 클래스에서 외부 클래스 내부에 접근 Untitled
      • Instance Inner 클래스에서는 전부 접근 가능
      • static nested 클래스에서는 static 멤버만 접근 가능
      • 중요 Nested 클래스와 외부 클래스의 멤버 명이 겹치는 경우
        • Instance Inner 클래스에서 Untitled
          • OuterClass타입이름.this.으로 접근
          • 주의! 그냥 this.으로 접근하면 InnerClass의 객체를 의미한다!
        • static nested 클래스에서 Untitled
          • OuterClass타입이름.으로 접근
          • static은 클래스 타입 명으로 접근하면 됨.
      • 주의! private 멤버여도 OterClass와 InnerClass 서로 접근 가능함 Untitled
  • Local Classes
    • Local Class: 메서드, 생성자, 또는 초기화 블록 내에 정의되며, 해당 영역에서만 접근 가능 Untitled
      • Nested 클래스와 마찬가지로 사용됨. 메소드를 외부 클래스라고 생각하면 된다
    • 외부 클래스 내부의 메소드의 Local 클래스
      • Local 클래스에서 외부클래스 접근하는 경우: 내부 클래스에서 외부클래스 접근하는 것과 접근 방법에 차이 없음!! 이름 겹쳐도 클래스타입이름.this로 접근하면 된다

        Untitled

      • 중요 Local 클래스에서 메소드에 접근하는 경우:

        Untitled

      • 메소드안에서 Local 클래스보다 위에서 선언 및 값이 할당된 것을 클래스 내부에서 사용가능하다!

        Untitled

        • 메소드의 지역변수는 객체를 만들 때 capture해서 객체 내에 필드로 생성해준다.
          • 만약 클래스를 리턴하는 함수라면 지역변수는 날아가버리기 때문에 Local 클래스에서는 지역변수를 capture하는 것이다! (클래스 자체 리턴 가능)
        • 중요 이를 사용할 수 있는 조건은 다음과 같다
          • 값이 할당되어 있어야 접근 가능하다.
            1. 해당 변수는 값이 할당된 final이다.
            2. 해당 변수는 값이 할당된 effectively final이다.
              • 값 할당이 1번만 일어나는 것만 접근한다.
              • 결국 final처럼 동작하기때문에 effectively final이다
          • 만족하지 못하면 컴파일 에러가 난다.
    • 주의! Local 클래스를 리턴하는 방법 Untitled
      • Local 클래스는 말 그대로 지역변수이기 때문에 그 타입 자체로 리턴할 수 없음
      • 인터페이스를 구현하여서 상위타입으로 return 하게 함
  • Anonymous Classes
    • Anonymous Class: 이름이 없는 일회용 클래스, 주로 인터페이스나 추상 클래스를 구현할 때 사용 Untitled
      • class{…} new(); 이름이 없기 때문에 클래스 정의와 생성을 구분지을 수 없음. → 같은 위치에서 일어나야 이름이 없을 수 있음
      • 이름이 없으니 본체를 데려온다! 중요 상위 타입을 이용해서 서브타입을 가져온다고 생각하면 됨 → 오버라이딩 가능
      • { 가 나오는 순간 상위 타입의 자식클래스 여기에 작성한다는 정보를 전달한다! 그대로 클래스 작성하듯이 작성해주면 됨
    • 중요 상위 타입의 생성자 사용 Untitled
      • 이름이 없어서 생성자를 만들 수 없고, super 생성자를 사용하지 못하는데, ()안을 super와 동일하게 생각하고 사용해주면 된다!

        Untitled

      • 인터페이스는 생성자 없지만, Anonymous Class 생성 시에는 괄호를 꼭 넣어줘야함

원본 노션 링크

https://believed-poinsettia-f0f.notion.site/f05bfd3103ee4c1f88d079fda21dc5f1?pvs=4

0개의 댓글