티스토리 뷰

객체지향적으로 개발해야하는 이유
클래스와 상속
추상 클래스와 인터페이스
Enum
예외
Object 클래스
Optional

 


객체지향적으로 개발해야하는 이유

절차지향 프로그래밍 → 객체지향 프로그래밍
왜 우리는 객체지향적으로 개발해야 할까❓

 

이는 결국 개발 과정에서 자주 발생하는 문제를 해결하기 위함이다!
먼저 객체지향 프로그래밍이 등장하기 전을 살펴보자👻

 

절차지향 프로그래밍의 경우

  • 데이터와 그 데이터에 접근할 수 이는 함수 사이에 서로 연관 관계가 낮다.
  • 실제 서비스에서는 메시지를 발송하고, 테스트 시에는 발송하지 않기 위해서는 코드를 변경해야 하는데, 이 과정에서 실수가 유발될 수 있다..!(테스트 용이성)

 

객체지향 프로그래밍을 통해 인터페이스 이용할 수 있다.

여기서 의문이 들 수 있다! 그래도 코드 변경이 이루어지지 않나❓

 

집중할 점은 Client 코드가 바뀌지 않는다는 것이다!(코드 참고)

  • 메시지 센더 안에 어떤 구현체가 들어있는지 신경쓰지 않는다.

 

Spring Framework를 사용한다고 해서 객지프의 장점을 누리는 것이 아니라 코드를 객체지향적으로 작성한 상태에서 스프링프레임워크같은 OOP 프레임워크를 활용했을 때 객지프 같은 OOP 장점을 누릴 수 있다.

  • 코드의 실행에 필요한 메시지센더라는 인터페이스의 구현체를 외부에서 주입을 받는 것이다.
  • 이렇게 해서 이 클라이언트는 외부에서 어떤 의존성을 선택할지 선택받음으로써 코드 자체에는 어떤 구현체에도 의존하지 않도록 만들 수 있는 것이다.

 

예제 코드에서 사용한 것은 의존주입패턴이다.

  • 클라이언트라는 클래스가 페이크 메시지 센더와 리얼 메시지 센더 중 어떤 메시지 센더를 사용하든지 간에 코드가 변경되지 않았다는 것이 중요한 포인트이다🌟

 

👻정리하면!

객체지향 프로그래밍이란
객체지향 절차지향적인 개발방법에서 발생하던 여러가지 문제점들을 해결하기 위해 등장한 패러다임!

문제점들을 개선한 단적인 예시로는 캡슐화 테스트에 대한 용이성이 있다!


클래스와 상속🌳

상속과 다형성

  • 상속 가능 여부는 접근자에 의해 결정된다.
  • 상속 관계에 있으면 다형성도 함께 생긴다.부모에 정의된 메소드 호출 가능⭕
  • 하지만 부모 타입으로 정의되었으므로 자식 타입에 선언된 메서드는 호출 불가❌
  • 다형성이 생기면 부모 타입으로 선언된 레퍼런스 변수에 자식 타입 집어넣기 가능⭕

 

오버라이딩(재정의)

  • 부모 클래스에서 상속받은 메서드를 자식 클래스에서 다시 정의하는 것이다.
  • 어노테이션은 필수는 아니지만 나름의 의미가 있으니 사용하자!
  • 부모클래스의 해당 함수가 없어지면, @Override의 빨간줄을 통해 알 수 있다! 보통 오타가 나는 경우가 많다ㅏ

 

오버로딩(중복정의)

  • 동일한 메서드 이름으로 파라미터, 리턴 타입이 다른 메서드를 여러 개 정의한다.
  • 메서드를 호출하는 입장에서 **반환 타입만 다르면 오류 발생(**구분 불가하므로!!! → return 타입만 다르게 오버로딩은 불가능❌

 

상속 관련 주의사항

  • 올바른 상속이란 메서드 재사용 X, 필드에 대한 재사용 O
  • → 메서드를 재사용하기 위해서는 전략 패턴 구성(Composite) 한다!
  • 부모 타입이 할 수 있는 일은 자식 타입도 할 수 있어야 함
  • → 리스코프 치환 원칙(이걸 지키면 진정한 다형성이 있는 상속이 된다!)

 

👻정리하면!

다형성은 부모 클래스 타입으로 자식 클래스의 인스턴스를 넣어서 사용 가능
상속은 부모 클래스의 필드와 메서드를 자식 클래스로 확장
오버라이딩(재정의)
부모 클래스에 있던 메서드를 자식 클래스에서 다시 정의
오버로딩(중복정의)
같은 이름의 메서드로 여러 개의 파라미터나 리턴 타입이 다른 메서드를 정의
상속 관련 주의사항 위에 참고

 


추상 클래스와 인터페이스🌳

추상 클래스

  • 인스턴스를 생성할 수 없으며, 일반적으로 하나 이상의 추상 메서드를 포함한다!
  • 또한 추상클래스에서는 아직 구현되지 않은 메서드를 호출한다.
  • 각각의 구현 클래스에서 달라져야 하는 부분만 추상 메서드를 오버라이딩 하면 된다.
  • 추상 클래스는 인스턴스를 생성할 수 없다고 했는데, 할 수는 있다. 이 경우 추상 메서드를 구현해야 한다! 일반적으로 하나 이상의 추상 메서드를 포함한다.

 

인터페이스

  • 클래스는 하나만 상속받을 수 있지만, 인터페이스는 여러 개 구현할 수 있다.
  • 인터페이스를 구현하는 클래스는 해당 인터페이스가 가지고 있던 메서드들을 반드시 구현해야한다.
  • defaultMethod
    • 인터페이스에서 메서드를 정의할 수 있는 기능
    • 인터페이스와 추상클래스는 다른 기능을 한다.
    • 자바8부터 인터페이스도 디폴트메서드를 통해 메서드 정의가 가능해짐

 

그럼 추상클래스와 인터페이스 둘 중에 뭐를 사용해야 할까❓

 

일반적으로 추상클래스를 사용해야만 하는 상황이 아니라면 인터페이스에 디폴트 메서드를 사용하는 것을 권장!

  • 추상클래스를 사용해야만 하는 상황
    • 인스턴스 변수(필드)가 필요한 경우
    • 생성자가 필요한 경우
    • Object 클래스의 메서드를 오버라이딩 하고 싶은 경우
  • 이런 경우를 제외하고 대부분의 경우 인터페이스를 사용하는게 더 적절하다!
    • 추상 클래스보다는 인터페이스가 더 추상적인 존재이기 때문이다.
    • 추상적인 것과 덜 추상적인 것 사이에 선택할 수 있다면 더 추상적인 것을 사용하는게 좋다. 아주 애매모호한 놈을 선호하구만


Enum🌳

하나하나가 클래스의 인스턴스로 필드를 가진다. 생성자도 있고 필드도 있다.

BiFunction

  • 함수형 인터페이스로 함수를 마치 하나의 타입처럼 다룰 수 있도록 해준다.
  • 첫번째 두번째는 파라미터 타입, 세번째는 리턴 타입
  • 람다 표현식이 생성자의 파라미터로 들어간다.
  • 클라이언트가 caculatetype의 구체적인 내용을 몰라도 된다.
    • 응집력이 높아졌다.
    • if문 사용시에는 계산하는 타입이 늘어날때마다 계속 추가해줘야 했던 귀찮음이 있다!

 



예외🌳

Checked Exception과 Unchecked Exception은 무슨 차이일까? 면접 단골질문이라고 한다!

 

자주 이야기하는 오답

  • 컴파일할때 발생하고, 런타임에 발생하는 예외 → 땡❌
  • 예외는 모두 런타임 시점에만 발생한다. 컴파일 시점에는 발생하지 않는다. 컴파일 시점에 발생하는건 예외가 아니라 문법적으로 발생하는 컴파일 에러이다.

정답

  • Checked Exception은 컴파일 할 때 예외에 대한 처리를 강제하고, Unchecked Exception은 예외에 대한 처리를 강제하지 않는다. → 딩동댕⭕

 

+++ 종료코드(그냥 상식(?)

  • 정상적으로 종료 0
  • 그이외의 값들은 비정상적인 종료

 

Checked Exception✅

  • try catch 필요
  • throw 필요
  • Exception 클래스를 상속한다.

 

UnChecked Exception

  • try catch와 throw 같은 예외처리문법을 강조하지 않는다.
  • RuntimeException을 상속 받음
  • Checked Exception을 상속받고 있긴 한데 RuntimeException으로는 문법적으로 Unchecked Exception으로 정의가 되어있는 것이다. 이건 이해의 영역이 아니라 암기! 그냥 그렇게 만들어졌대ㅐ

 

그럼 둘 중 어떤걸 써야할까❓

 

새로운 예외를 정의하는 경우에는 예외가 발생하면 처리를 해야지 바깥으로 던지기만 하면 찝찝해서 Checked Exception을 사용해야 한다고 생각할 수 있지만 실제로는 반대이다.

그 이유는 대부분의 예외는 로직에서 해결할 수 없기 때문이다.

  • 파일 이름을 입력 받아 파일을 읽어와 되돌려주는 서비스가 있다고 해보자.서버에서 해줄 수 있는 일은 사용자에게 에러메시지를 띄워주는 것이다!
  • 만약 사용자가 파일 이름을 잘못 입력했다고 하자.
  • readFile이 Filenotfoundexception을 던지는 예외라는 것이 외부에 알려지면 캡슐화가 깨진다.

예외를 도중에 처리하고 싶다면 Unchecked Exception도 try-catch를 할 수 있다.

 

👻결론은

Checked Exception 대신 UnChecked Exception을 쓰자!
캡슐화를 위반하지 않으면서 필요할때만 예외를 처리할 수 있는 UnChecked Exception을 사용하는 것이 더 적절하다.

 


Object 클래스🌳

자바에서 아주 특별한 클래스로 모든 클래스는 Object 클래스의 자손이다.

 

모든 클래스는 오브젝트 클래스가 가지고 있는 메소드들을 상속받고 있으며,

자바 문법 전반에서 특별한 기능을 수행하고 있다.

 
자주 사용되는 Object의 메서드 Top3🌟로, 오버라이딩하여 사용하는 경우가 많다!

  1. equals()
  2. hachCode()
  3. toString()

 

equals(), hashCode()

동일성 vs 동등성

 

  동일성 동등성
의미 비교 대상이 실제로 '똑같은' 대상이어야 하는 것으로, 둘은 실제론 하나이다. 비교 대상이 같은 값이라고 우리가 정의한 것이다.
자바에서는? 두 비교 대상을 '==' 연산자로 비교 equals 메서드를 오버라이딩하여 비교

+++ instanceof -> null 체크와 캐스팅 과정 포함되어 있다!
 
equals 미정의 후 인스턴스 비교를 위해 사용한다면❓

  • 둘이 어떤 상태일때 동일한지 알 수 없으므로 equals를 사용해도 false가 나온다.

 
일반적으로는 두 인스턴스가 동등함을 확인하기 위해 equals 메서드를 사용한다.
그러나 해시 맵 같은 해시 관련 컬렉션 사용 시에는 해시코드 메서드가 중요하게 사용된다고 한다!
 
+++ Map: 특정 키를 기준으로 데이터를 저장하고 값을 조회할때도 키를 기준으로 값을 찾는 자료구조로, 저장 시에는 Key, Value가, 조회 시에는 Key 필요하다.
 
hashCode()

  • 해시코드는 equals 메소드와 마찬가지로 두 인스턴스가 동등하다는 것을 정의하기 위해 사용된다.
  • 데이터를 담는 클래스를 정의했다면 equals 메서드와 함께 해시코드 메서드까지 오버라이딩 해줘야 한다.
  • HashMap이 Key를 찾는 방법
    • 해시맵은 키를 그냥 들고 있는게 아니라 키를 해시코드한 연산 결과가 키가 된다.
    • 조회 시에는 키로 들어온 값을 해시코드하여 나온 값을 통해 저장된 키에서 같은 값을 찾게 된다.

 
만약 이 해시코드 결과에 매칭되는 키가 하나만 있다면 그 키에 해당하는 값을 하나만 반환한다.
😯 그러나 문제는 이 해시코드로 나온 키가 중복될 수 있다는 점이다!

  • 해시코드를 16 같은 작은 수로 나눈 나머지를 사용한다. 0~15 값이 나오므로 충분히 키 값이 겹칠 수 있다.
  • 해시코드 결과가 같다고 해보자(해시 코드한 결과를 나머지 연산한 결과가 같다는 것) 
    • 한번에 2개의 데이터가 조회가 된다.

 
그래서 equals로 이 둘을 비교하는 것이다. equals 까지 비교했을 때, 동등한 데이터를 찾을 수 있다!
 
그렇다면 왜 처음부터 해시맵 같은 자료구조에서는 equals로 비교하지 않고 hashcode를 통해서 비교하는 걸까❓

  •  그 이유는 상대적으로 가벼운 해시코드 값을 통해 찾으려는 값의 후보군을 대폭 줄일 수 있기 때문이다.

 

equals 오버라이딩 ⭕ hashcode를 오버라이딩 ❌

 
equals가 정의되어 있다고 하더라도
해시코드한 값이 다르므로 해시코드에서 부터 다른 값이라고 인식되어,
이퀄스 연산까지 가보지도 못하고 다른 것이라고 인식된다.
 

equals 오버라이딩 ❌ hashcode를 오버라이딩 ⭕

 
해시코드 값을 같지만 equals 비교시 다른 것으로 인식된다.
 
🐝Tip. 인텔리제이에서 equals, hashCode 메서드 만들기

  • 마우스 오른쪽 -> Generate -> equals and hashCode() 클릭  -> equals 비교할 필드 선택

 
toString()

인스턴스를 문자열로 만드는 것

 
대부분의 경우 "로그"를 남기기 위해!!!
👻 (서비스 운영 과정에서 발생하는 문제 해결 위해, 문제 발생을 알아차리기 위해, 법적으로 로그를 남겨야 하는 경우)
 
로그 남길때 핵심이 되는 것은 시스템에서 어떤 데이터가 처리되었는가이다.
특정 데이터 처리 시 오류가 발생하는 경우
오류 해결 위해 동일한 데이터로 재현해봐야 문제해결이 쉬운 경우가 많다.
데이터를 로그로 남긴다는 것은 인스턴스를 문자열로 바꿔서 로그로 바꾸는 것을 의미한다.
 
🐝Tip. 인텔리제이에서 toString() 메서드 만들기

  • 우클릭 -> Generate -> toString() -> 필드 선택

 

Objects 클래스는 NPE를 피할 수 있게 도와준다.

객체에 널이 들어갔을때 null에 대한 체크 없이 NPE를 피할 수 있는 코드를 작성할 수 있어, if문을 줄여 가독성을 높게 만들어준다!!!
 


Optional🌳

비어있는 값을 의미하는 null을 처리하기 위해 사용되는 클래스이다.

 
자바로 코드를 작성하면서, 한번쯤 경험한 NPE(Null Pointer Exception) 🤯
NPE 피하기 위해 null에 대한 처리가 필요하다!
null에 대한 처리를 위해 if문이 아닌, optional을 활용하면 좀 더 가독성 높은 코드를 만들 수 있다.
 
NPE 발생

  • null이 들어있는 레퍼런스 변수를 대상으로 필드 참조나 메서드 호출 시에 발생한다.

 
NPE가 발생하지 않도록 하기 위해서는❓

  • null이 들어올 수 있는 string 변수에 대한 널체크..(if문 이용하거나 Optional 사용!)

 
Optional에서는 어떻게 처리하며 왜 이게 더 가독성이 높을까❓

  • API 호출을 유창하게? 세련되게? 할 수 있다🌻
  • 또한 if 문으로 null 체크 시 그 사이에 다른 코드가 들어갈 수 있으나, Optional을 사용하면 한줄로 처리가 가능하다 👍

 

Optional 생성방법

1. Optional.of()

  • 파라미터로 들어오는 값이 null인 경우 NPE를 던진다.
  • NPE 발생을 방지하기 위한 것인데 왜 던지지❓
    • 여기서 던져지는 NPE는 레퍼런스 변수를 참조하는 시점이 아니라
      Optional 객체를 생성하는 시점에 발생하기 때문에 개발자가 의도적으로 예외를 발생시키는 것에 가깝다.
      아무튼 null에 대한 체크를 할 필요는 없어지는 것이다.

 
2. Optional.ofNullable()

  • 파라미터로 들어온 값이 null인 경우 비어있는 Optional을 널이 아닌 경우에는 해당 값이 들어있는 Optional을 생성해준다.
  • 보통 Optional을 사용할때 이 메서드를 통해 Optional 객체를 생성한다.

 
3. Optional.empty()

  • 널을 반환하는 것과 같은 효과를 주고싶을때 이 empty를 반환할 수 있다.

 

Optional 관련 메서드

1. ifPresentOrElse()

  • 첫번째 파라미터: 옵셔널이 비어있는 값이 아닌 경우에 이 값을 넣은 람다 표현식을 실행시킨다.
  • 두번째 파라미터: 비어있는 경우에 람다 표현식을 하나 파라미터로 받는데, 이때는 예외가 발생하도록 작성 가능하다.

 
2. getOptionalValue()

  • .orElseThrow() : 비어있는 값이 들어올때만 수행되며,
  • 비어있지 않을때는 값을 반환 Optional로 감싸진게 아니라 그냥 반환된다.

 
3. ofNullable()

  • 널이 들어올지 아닐지 모르는 값에 대해서 감싸주면, 이 값을 가지고 Optional 객체를 만들 수 있다.

 

Optional의 실제 활용

1. 내가 쓰는 라이브러리의 메서드 반환 타입이 Optional인 경우

  • 그대로 .orElseThrow 같은 메서드를 사용

 
2. 내가 쓰는 라이브러리의 반환 타입이 Optional이 아닌 경우

  • Optional.ofNullable로 Optional 객체 생성 후 orElseThrow 같은 메서드로 사용

 

Optional 관련 널리 알려진 안티패턴

if(string.isPresent()) ->  if문이 사용이랑 다를 바 없음 ..😕
 

Optional과 Stream

filter를 걸어 남긴 값들이 있을수도 있고 없을수도 있다면, optional을 활용하는게 좋다.
옵셔널은 stream api랑 굉장히 잘 어울리므로 자주 사용될 것이다!