📍 this와 this() 키워드
🍀 한 줄 요약 : this는 인스턴스 자신을 가르키는 참조 변수이고 this()는 생성자를 뜻한다.
세미나에서 다뤘던 아래의 예시를 살펴보면, Person 생성자에서 인스턴스 변수 값을 세팅하기위해 this.name = name가 같이 사용한 것을 볼 수 있다.
public class Person {
private String name;
private int age;
private String sex;
public void walk() {
System.out.println("사람이 걷습니다.");
}
public Person(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
}
🤔 this를 사용하는 이유는?
: 인스턴스 변수와 지역변수를 구분하기 위해서 사용한다. 위에서 매개변수로 선언된 변수와 인스턴스 변수를 구분하기 위해서!
❗️static 메서드 내에서는 this 사용 불가 ❗️
: static 메서드는 객체 생성 없이 호출가능하므로, 당연히 인스턴스를 가리키는 참조 변수도 쓸 수 없음 🙅♀️
다음으로, this() 키워드에 대해 알아보자.
🍀 this()는 같은 클래스의 다른 생성자를 호출할 때 사용된다.
위의 코드에서 this()를 적용해보면 다음과 같다. 여기서 Person(String name, int age) 생성자는 this()키워드를 통해 Person(String name, int age, String sex)의 다른 Person 생성자를 호출하고 있다. 여기서, 중요한 오버로딩 개념이 등장한다!
🤔 오버로딩이란?
: 동일한 이름의 메소드가 다른 반환 타입, 다른 매개변수의 개수나 타입을 가질 수 있는 것
public class Person {
private String name;
private int age;
private String sex;
public void walk() {
System.out.println("사람이 걷습니다.");
}
public Person(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public Person(String name, int age) {
this(name, age, "unknown");
}
}
📍 Java의 Generic 타입
🍀 한 줄 요약 : 객체(Object)에 타입을 지정해주는 것 ( 컬렉션 외에도 클래스, 인터페이스 등 다양하게 사용된다. )
아래에서, <> 가 제네릭이고, 이 꺽쇠 괄호 안에 타입을 명시 해주면 된다.
ArrayList<String> list = new ArrayList<>();
✔️ Generics의 다형성
아래 코드에서 보면, arrList를 생성할 때 타입을 Sports로 명시해주었다. Soccer, Baseball 클래스가 Sports 클래스를 상속받고 있으며, 제네릭의 다형성을 통해 arrList에 Soccer, Baserball 타입의 클래스도 저장할 수 있는 걸 확인할 수가 있다.
public class Generics { public static void main(String[] args) { ArrayList<Sports> arrList = new ArrayList<Sports>(); arrList.add(new Sports()); arrList.add(new Soccer()); arrList.add(new Baseball()); Sports sports = arrList.get(0); Soccer soccer = (Soccer) arrList.get(1); } } class Sports{} class Soccer extends Sports{} class Baseball extends Sports{}
👀 클래스에서의 Generic 사용
: 타입 T는 객체를 생성할 때 해당 타입으로 변경된다.package Generic1; public class man <T> { private T name; //이름 필드 private T bloodtype; //혈액형 필드 public T getName() { return name; } public void setName(T name) { this.name = name; } public T getBloodtype() { return bloodtype; } public void setBloodtype(T bloodtype) { this.bloodtype = bloodtype; } 출처: https://seeminglyjs.tistory.com/184 [Seemingly Online:티스토리]
📍final, static, static final
🍀 한 줄 요약
1️⃣ static : 객체 생성 없이 필드와 메소드를 생성하고자 할 때
2️⃣ final : 수정 x
3️⃣ static final : 고정된 최종값 -> 상수 선언 시 사용
1️⃣ static
정적 팩토리 메소드 패턴 개념을 공부할 겸 예시를 가져왔다. 여기서 잠깐, 정적 팩토리 메소드가 무엇인지 알고 넘어가자!
👀 정적 팩토리 메소드란?
: 적절한 메소드 네이밍을 통해, 반환될 객체의 특성을 좀 더 파악하기 쉽게 도와준다. new 키워드로 객체를 생성할 시에는 어떤 매개변수로 어떤 객체가 반환될지 파악하기에 어려움이 있다. (특히 내가 짠 코드가 아니라면 더더욱)
이펙티브 자바에도 생성자 대신 정적 팩토리 메소드를 써라! 라고 언급이 되어 있기도 하다.
하지만, 정적 팩토리 메서드에도 아래와 같은 단점이 있다.(이 부분은 따로 추가적인 공부가 필요할 듯...)
1. 하위 클래스로 상속하기 위해서는 public 또는 protected 생성자가 필요하다.
2. 프로그래머가 찾기 어렵다. --> 생성자처럼 명확하지 않으니 from, of 등과 같은 규약을 따라야 한다.
다시 static의 개념으로 돌아와서, 아래와 같이 정적 팩토리 메소드를 생성하여, Car 객체 생성 없이 바로 Car.brandColorOf("BMW", "Red");를 호출한 것을 볼 수 있다.
class Car {
private String brand;
private String color;
// private 생성자
private Car(String brand, String color) {
this.brand = brand;
this.color = color;
}
// 정적 팩토리 메서드 (매개변수 하나는 from 네이밍)
public static Car brandBlackFrom(String brand) {
return new Car(brand, "black");
}
// 정적 팩토리 메서드 (매개변수 여러개는 of 네이밍)
public static Car brandColorOf(String brand, String color) {
return new Car(brand, color);
}
}
출처: https://inpa.tistory.com/entry/GOF-💠-정적-팩토리-메서드-생성자-대신-사용하자 [Inpa Dev 👨💻:티스토리]
public static void main(String[] args) {
// 검정색 테슬라 자동차
Car teslaCar = Car.brandBlackFrom("Tesla");
// 빨간색 BMW 자동차
Car bmwRedCar = Car.brandColorOf("BMW", "Red");
}
출처: https://inpa.tistory.com/entry/GOF-💠-정적-팩토리-메서드-생성자-대신-사용하자 [Inpa Dev 👨💻:티스토리]
2️⃣ final
: 수정이 불가능
아래와 같이 선언과 동시에 closeTime처럼 값을 줄 수도 있고, 객체 생성시에 값을 부여할 수도 있다.
이렇게 하면 오픈 시간은 객체마다 다르게 설정이 가능하고, 마감시간은 객체마다 무조건 21시로 고정이 되게 된다.
public class Shop{
final int closeTime = 21;
final int openTime;
public Shop(int openTime){
this.openTime = openTime;
}
}
3️⃣ static final
: static + final, 값 수정도 불가능하고 객체 생성없이도 사용가능(모든 객체가 같은 값을 공유) -> 보통 상수 선언시 사용
static final double PI = 3.141592;
📍super, super()
🍀 한 줄 요약 :
1) super는 자신이 상속받은 부모 클래스에 대한 레퍼런스 변수로, 부모 클래스의 멤버에 접근할 때 사용
2) super( )는 자식 클래스의 생성자에서 부모 클래스의 생성자를 호출하기 위해서 사용
출처: https://kadosholy.tistory.com/93
아래는 세미나 시간에 다뤘던 코드이다. Member 클래스가 Person 클래스를 상속받고 있는 구조이다.
여기서 super(name, age, sex)는 Member의 부모 클래스인 Person의 생성자를 호출하고 있는 것이다.
public class Member extends Person {
private Part part;
public Member(
String name,
int age,
String sex,
Part part
) {
super(name, age, sex);
this.part = part;
}
}
📍객체지향 프로그래밍의 5가지 설계 원칙, SOLID
🍀 한 줄 요약
✔️ SRP(Single Responsibility Principle): 단일 책임 원칙
✔️ OCP(Open Closed Priciple): 개방 폐쇄 원칙
✔️ LSP(Listov Substitution Priciple): 리스코프 치환 원칙
✔️ ISP(Interface Segregation Principle): 인터페이스 분리 원칙
✔️ DIP(Dependency Inversion Principle): 의존 역전 원칙
출처: https://inpa.tistory.com/entry/OOP-💠-객체-지향-설계의-5가지-원칙-SOLID
1️⃣ SRP(Single Responsibility Principle): 단일 책임 원칙
하나의 클래스는 하나의 기능 담당하여 하나의 책임을 수행하는데 집중되도록 클래스를 따로따로 여러개 설계하라는 원칙이다.
목적 : 프로그램의 유지보수성을 높이기 위함
2️⃣ OCP(Open Closed Priciple): 개방 폐쇄 원칙
확장에는 열려있어야 하고, 변경에는 닫혀 있어야 함
즉, 기존의 코드를 변경하지 않고 기능을 수정하거나 추가할 수 있도록 설계해야 함
OCP 는 추상화 (인터페이스) 와 상속 (다형성) 등을 통해 구현해낼 수 있다. 자주 변화하는 부분을 추상화함으로써 기존 코드를 수정하지 않고도 기능을 확장할 수 있도록 함으로써 유연함을 높이는 것이 핵심이다.
아래는 OCP를 잘 적용한 예시이다. 부모 클래스인 HelloAnimal 클래스를 만듦으로써, 다른 rabbit 등등의 클래스를 새로 생성한다 하면, HelloAnimal 클래스를 더 이상 수정할 필요가 없다.
(예시 출처: https://inpa.tistory.com/entry/OOP-💠-아주-쉽게-이해하는-OCP-개방-폐쇄-원칙 [Inpa Dev 👨💻:티스토리])
3️⃣ LSP(Listov Substitution Priciple): 리스코프 치환 원칙
🍀 핵심 : 상속 시 부모에서 구현한 원칙을 따라야 한다
서브 타입은 언제나 기반(부모) 타입으로 교체할 수 있어야 한다는 원칙
아래는 공부하다 발견한건데,,, 아직 이해가 안돼서 좀 더 공부가 필요함...! (추후 정리하겠음👀)
다형성을 이용하고 싶다면 extends 대신 인터페이스로 implements하여 인터페이스 타입으로 사용하기를 권하며, 상위 클래스의 기능을 이용하거나 재사용을 하고 싶다면 상속(inheritnace) 보단 합성(composition)으로 구성하기를 권장한다.
출처: https://inpa.tistory.com/entry/OOP-💠-아주-쉽게-이해하는-LSP-리스코프-치환-원칙 [Inpa Dev 👨💻:티스토리]
4️⃣ ISP(Interface Segregation Principle): 인터페이스 분리 원칙
프로그램 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다
ex) 자식클래스가 오버라이딩 할 때 부모클래스의 의도와 다르게 맘대로 구현하면 안됨
5️⃣ DIP(Dependency Inversion Principle): 의존 역전 원칙
특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다
구현: 인터페이스를 잘게 분리함으로써, 클라이언트의 목적과 용도에 적합한 인터페이스 만을 제공
📍스프링의 의존성 주입 방식
🍀 스프링 컨테이너에 Bean을 먼저 생성해두고 지정된 객체에 주입하는 방식이다. 의존성 주입을 이용해서 객체의 의존성을 낮추어 유연하고 확장성있는 코드 개발이 가능하다.
✔️ 의존성 주입 방식
1. 생성자 주입
2. 수정자(setter) 주입
3. 필드 주입
4. 일반 메서드 주입
👀 스프링 공식 문서에서는 생성자 주입을 권장하고 있다고 한다.
-> 의존 관계가 변경되지 않을 경우 : 생성자 주입
-> 의존 관계가 선택적이거나 변경 가능한 경우 : 수정자 주입(setter 주입)
출처: https://ittrue.tistory.com/227 [IT is True:티스토리]
대표적인 예시로, 생성자 주입을 알아보자.
1️⃣ 생성자 주입(Constructor Injection)
|
@Controller |
|
public class CocoController { |
|
//final을 붙일 수 있음 |
|
private final CocoService cocoService; |
|
//--------------------------------------------------------- |
|
//@Autowired |
|
public CocoController(CocoService cocoService) { |
|
this.cocoService = cocoService; |
|
} |
|
} |
특징)
클래스의 생성자가 하나이고, 그 생성자로 주입받을 객체가 빈으로 등록되어 있다면 @Autowired를 생략 할 수 있다.
출처: https://dev-coco.tistory.com/70 [슬기로운 개발생활:티스토리]
장점)
- 테스트 용이 : 프로엠워크 의존적이지 않아 순수 자바 등 외부 테스트 코드 작성 가능
- 객체 불변성 확보: final 선언 가능, 유지보수 용이성
- 순환 참조 에러가 발생할 경우 컴파일 시 판단 가능
📍Java Record
🍀 필드 유형과 이름만 필요한 불변 데이터 클래스
- equals, hashcode, tostring 메서드와 private, final field, public constructor는 Java 컴파일러에 의해 자동 생성된다.
public record SampleRecord(
String name,
Integer age,
Address address
) {}
특징)
- 해당 record 클래스는 final 클래스이라 상속할 수 없다.
- 각 필드는 private final 필드로 정의된다.
- 모든 필드를 초기화하는 RequiredAllArgument 생성자가 생성된다.
- 각 필드의 getter는 getXXX()가 아닌, 필드명을 딴 getter가 생성된다.(name(), age(), address())
'연합동아리 > SOPT' 카테고리의 다른 글
엔티티의 생명주기와 Java의 Checked Exception과 Unchecked Exception (0) | 2024.05.01 |
---|---|
[NOW SOPT] SERVER - 2차 세미나 정리 (0) | 2024.04.17 |