this 는 참조변수, this()는 메소드
🔽 example 🔽
class Car {
private String modelName;
private int modelYear;
private String color;
private int maxSpeed;
private int currentSpeed;
Car(String modelName, int modelYear, String color, int maxSpeed) {
this.modelName = modelName;
this.modelYear = modelYear;
this.color = color;
this.maxSpeed = maxSpeed;
this.currentSpeed = 0;
}
Car() {
this("G90", 2021, "검정색", 200); // 생성자 호출
}
}
Q. this 참조변수는 아무 때나 사용할 수 있나요?
A. NOPE. 인스턴스 메소드 내에서만 사용할 수 있습니다. 클래스 메소드에서는 사용할 수 없습니다.
인스턴스 메소드에서 사용할 수 있는 이유는 모든 인스턴스 메소드에 this 참조변수가 숨겨진 지역변수로 존재하고 있기 때문.
- 제네릭(Generic)은 class, method에서 사용할 자료형을 나중에 확정하는 기법
- 제네릭을 사용할 때, 객체 생성 시 결정 되는 자료형 정보를 'T'로 대체
- 다이아몬드 연산자 < >를 통해 자료형을 전달
class Camp<T> { private T unit; public void set(T unit) { this.unit = unit; } public T get(){ return unit; } }
데이터 형식에 의존하지 않고, 하나의 값이 여러 다른 데이터 타입들을 가질 수 있도록 하는 방법
클래스나 메서드를 선언할 때가 아니라 ⭐️ 사용할 때 ⭐️(== 객체를 생성할 때 or 메서드를 호출할 때) 자료형을 정함
아래의 두 코드를 비교해보면, 제네릭을 이해하는 데 도움이 된다.
제네릭을 사용하지 않는 코드
class Camp {
private Object unit; // ⭐️
public void set(Object unit) {
this.unit = unit;
}
public Object get() {
return unit;
}
}
+) ⭐️: Object로 설정한 이유는?
-> 어떤 자식 클래스라도 받아들일 수 있도록
-> 근데 이렇게 하면 꺼내올 때 형변환이 필요함.
예를 들어, Npc2 hUnit = (Npc2)human.get();
Q. 잘 작동하기만 하면 되는 거 아닌가?
A. NOPE. 위와 같이 코드를 작성하면 컴파일러가 오류를 발견할 수가 없겠지?
예를 들어서 개발자의 실수로 잘못된 타입으로 강제 변환하거나(위에서 말했다시피 Object로 지정할 경우 형변환이 필수임!)
타입에 맞지 않는 즉 다른 타입의 객체를 저장하게 되었을 때, 컴파일러는 문제가 있다는 점을 알아챌 수 없어!
왜? Object는 Java에서 모든 클래스의 최상위 클래스이기 때문에 문법적 오류가 없거든.
제네릭을 사용하는 코드
class Camp<T> {
private T unit;
public void set(T unit) {
this.unit = unit;
}
public T get() {
return unit;
}
}
용어 | 대상 |
---|---|
타입 매개변수(type parameter) | Camp에서 T |
타입 인수(type argument) | Camp에서 Npc |
매개변수화 타입(parameterized type) | Camp |
⬇️ 타입 매개변수의 이름 규칙 ⬇️
ㄴ 네.
class Camp<T1, T2> // ⭐️
{
private T1 param1;
private T2 param2;
public void set(T1 o1, T2 o2)
{
param1 = o1;
param2 = 02;
}
public String toString() // 📌
{
return param1 + " & " + param2;
}
}
public class Multiparameter
{
public static void main(String[] args)
{
Camp<String, Integer> camp = new Camp<>(); // ⭐⭐️
camp.set("Apple", 25);
System.out.println(camp); // ⭐️⭐️⭐️
}
}
⭐️ : < > 내부에 타입 매개변수는 하나일 필요 없음
⭐️⭐️ : 앞쪽(좌변) < >에 데이터형 지정 후, 뒤쪽(우변)에는 생략
⭐️⭐️⭐️ : 클래스에 📌 toString() method가 overriding으로 재정의 되어 있음. 따라서 method 내용이 출력된다.
class MyData
{
public static <T> T showData(T data)
{
...
}
}
1) 제너릭 메서드의 'T'는 메서드 호출 시점에 결정된다.
MyData.<String>showData("Hello World");
2) 타입 인수 생략이 가능하다. (생략된 인수는 매개변수로 들어온 데이터의 자료형으로 추론)
MyData.showData(1);
final : 한 번 값이 정해지고 나면 값을 바꿀 수 없는 필드
static : 객체마다 가질 필요가 없는 공용으로 사용하는 필드 혹은 인스턴스 필드를 포함하지 않는 메소드
static final : 모든 영역에서 고정된 값으로 사용하는 상수
"지금 저장하는 값이 final이야" 즉, 앞으로 수정이 불가능해. 라고 기억하면 된다.
public class Shop {
final int closeTime = 21; // 1) 선언과 동시에 값을 주는 방법
final int openTime; // 2-1) 먼저 생성하고
public Shop(int openTime) { // 2-2) 객체를 생성할 때 값을 주는 방법
this.openTime = openTime;
}
}
-> 지금 위의 코드는 가게를 닫는 시간을 고정하는 것!
객체를 생성하지 않고 사용할 수 있는 필드나 메서드를 생성할 때 활용
어떻게 사용할까?
public class PlusClass {
static int field1 = 15;
static int plusMethod(int x, int y) {
this.field2 = 10; // <-- x
this.method1(); // <-- x
}
}
int ans1 = PlusClass.plusMethod(15,2); // 클래스이름.필드
int ans2 = PlusClass.field1 + 2;
상수를 선언할 때 사용
static fianal double PI = 3.141592;
super는 참조변수, super()는 메소드
부모 클래스의 생성자를 호출할 때 사용
super();
를 추가하여부모 클래스로부터 상속받은 필드나 메소드를 자식 클래스에서 참조할 때 사용
SOLID 원칙을 지키면 유지보수 및 확장에 용이한 SW 개발에 도움이 된다.
객체지향 설계의 핵심 : 높은 응집도와 낮은 결합도
- SRP(Single Responsibility Principle) : 단일 책임 원칙
- OCP(Open-Closed Principle) : 개방-폐쇄 원칙
- LSP(Liskov Substitution Principle) : 리스코프 치환 원칙
- ISP(Interface Segregation Principle) : 인터페이스 분리 원칙
- DIP(Dependency Inversion Principle) : 의존 역전 원칙
📌Responsibility
: SRP에서 이야기하는 Responsibility(책임)는 곧 기능에 해당한다.
하나의 클래스가 수행할 수 있는 기능이 여러 개라면, 클래스 내부의 함수끼리 강한 결합을 가질 가능성이 높아지고, 이는 코드의 효율성을 떨어뜨린다.
어떤 모듈의 기능을 수정할 때, 수정하려는 모듈을 이용하는 다른 모든 모듈을 수정한다면 유지 보수가 복잡해진다.
따라서 OCP(개방-폐쇄 원칙)을 적용해서 기존 코드를 변경하지 않아도 기능을 수정 및 추가할 수 있게 해야한다.
-> OCP를 지키지 않으면 객체지향 프로그래밍의 장점인 유연성, 재사용성, 유지보수성 등을 활용하지 못하게 된다.
Q. 그러면 기존의 코드를 변경하지 않고 어떻게 기능을 수정하거나 추가하나요?
A. 상속(다형성)과 추상화(인터페이스)를 활용하면 된다.
자주 변경하는 부분을 추상화해서 기존 코드를 수정하지 않고 기능을 확장할 수 있도록 하여 유연성을 살린다.
스프링은 모든 기능의 기반을 제어의 역전(IoC)과 의존성 주입(DI)에 두고 있다.
외부에서 관리하는 객체를 가져와 사용하는 것
지금까지의 객체 생성 방식 | 제어의역전(IoC) |
---|---|
객체가 필요한 곳에서 직접 생성 | 외부에서 관리하는 객체를 가져와 사용 |
ex) public class A { b = new B(); } class A에서 new 키워드로 class B의 객체 생성 | ex) public class A { private B b; } 코드에서 객체를 생성하지 않음, 어디선가 받아온 객체를 b에 할당 |
! 지금까지 객체를 생성했던 방식처럼 직접 생성하거나 제어하는 것이 아니라는 점이 특징 !
Q. 이게 spirng에서는 어떻게 나타나나요?
A. 실제로 spirng은 spring container가 객체를 관리, 제공하는 역할을 해요.
다시 말하자면, 스프링에서는 객체들을 관리하기 위해 IoC, 제어의 역전을 사용합니다.
그 역할을 해주는 게 스프링 컨테이너입니다.
개발자가 직접 객체를 생성하고 의존성을 관리하는 대신, Spring 컨테이너가 이 작업을 대신 처리하는 것이죠.
어떤 클래스가 다른 클래스에 의존한다는 것
🔽 example 🔽
public class A {
// A에서 B를 주입 받음
@Autowired
B b;
}
1️⃣ 생성자 주입
2️⃣ 수정자 주입
3️⃣ 필드 주입
4️⃣ 일반 메서드 주입
(스프링 공식 문서에서는 생성자 주입을 권장하고 있다고 함.
의존 관계가 변경되지 않는 경우에는 생성자 주입을,
의존 관계가 선택적이거나 변경 가능한 경우에는 수정자 주입을)
생성자를 통해 의존 관계를 주입받는 방법
생성자에 @Autowired를 하면 스프링 컨테이너에 @Component로 등록된 빈에서 생성자에 필요한 빈들을 주입한다.
@Autowired
가 붙은 생성자를 찾아서생성자 주입의 특징
NPE(NullPointerException)를 방지할 수 있음
주입받을 필드를 final로 선언 가능
🔽 example 🔽
@Component
public class CoffeeService {
private final MemberRepository memberRepository;
private final CoffeeRepository coffeeRepository;
@Autowired
public CoffeeServiceImpl(MemberRepository memberRepository, CoffeeRepository coffeeRepository) {
this.memberRepository = memberRepository;
this.coffeeRepository = coffeeRepository;
}
}
MemberRepository
와 CoffeeRepository
두 개의 의존성을 가지고 있음MemberRepository
와 CoffeeRepository
인터페이스 구현체를 스프링 컨테이너가 자동으로 찾아서 주입해줌Setter(필드 값을 변경하는 수정자 메서드)를 통해 의존 관계를 주입
@Component를 통해 실행하는 클래스를 스프링 빈으로 등록 -> 의존관계를 주입
@Autowired가 있는 수정자들을 자동으로 의존관계를 주입
수정자 주입(Setter 주입)의 특징
🔽 example 🔽
@Component
public class CoffeeService {
private final MemberRepository memberRepository;
private final CoffeeRepository coffeeRepository;
@Autowired
public setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Autowired
public void setCoffeeRepository(CoffeeRepository coffeeRepository) {
this.coffeeRepository = coffeeRepository;
}
}
MemberRepository
와 CoffeeRepository
두 개의 의존성을 가지고 있음setMemberRepository
, setCoffeeRepository
)를 통해 주입됨필드에 @Autowired를 붙여서 바로 주입하는 방버
필드 주입의 특징
코드가 간결해짐
-> 왜? 생성자나 setter 메서드 없이 의존성을 주입할 수 있기 때문
단, 외부에서 변경이 불가능하며, 테스트하기 어려움
DI 프레임워크가 없으면 아무것도 할 수 없음
Application의 실제 코드와 상관없는 특정 테스트를 하고 싶을 때 사용
정상적으로 작동되게 하려면 setter가 필요함
의존관계를 필수적으로 넣지 않으려면 @Autowired(required=false) 옵션 처리를 통해 필수가 아님을 명시할 수 있음
🔽 example 🔽
@Component
public class CoffeeService {
@Autowired
private MemberRepository memberRepository;
@Autowired
private CoffeeRepository coffeeRepository;
}
필드 주입을 사용하지 않는 이유
일반 메서드를 통해서 의존 관계를 주입하는 방법
의존성 주입을 위한 특별한 메서드를 자유롭게 정의 가능
@Autowired 어노테이션은 모든 메서드에서 사용할 수 있기 때문에 일반 메서드 주입이 가능
일반 메서드 주입의 특징
🔽 example 🔽
@Component
public class CoffeeService {
private MemberRepository memberRepository;
private CoffeeRepository coffeeRepository;
@Autowired
public void method(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
일반 메서드 주입을 사용하지 않는 이유
JDK 14부터 제공(preview), 16부터 정식 제공
불변 객체를 쉽게 생성할 수 있도록 하는 클래스
불변 객체(immutable) 생성 시 다음과 같이 작성해야한다.
- 모든 필드에 final을 선언한다.
- 필드 값을 모두 포함한 생성자를 사용한다.
- 접근자 메서드 getter를 사용한다.
- 클래스의 상속을 제한하려면 클래스 레벨에도 final을 선언한다.
🔽 example 🔽
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
직접 구현하지 않아도 아래의 내용을 자동 생성할 수 있다.
- 필드 캡슐화
- 생성자 메서드
- getters 메서드
- equals 메서드
- hashcode 메서드
- toString 메서드
이유 : 컴파일 타임에 컴파일러가 코드를 추가해주기 때문
위에서의 예시로 살펴본 Student 객체를 구현하려면 다음과 같이 간단한 코드를 작성하면 된다.
🔽 example 🔽
public record Student(String name, int age) {
}
🔽 example 🔽(생성자 재정의)
public record Student(String name, int age) {
public Student {
if(age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
}
}
1️⃣ this와 this() 키워드
🔗 #1-1 도서 - 자바의 정석 기초 / 🔗 #1-2 사이트 - tcp school
2️⃣ Java의 Generic 타입
🔗 #2 도서 - 이재환의 자바 프로그래밍 입문 / 🔗 #3 블로그 - Stranger's LAB
3️⃣ final, static, static final
🔗#4 블로그 - Go devlog
4️⃣super, super()
🔗#5 사이트 - tcp school
5️⃣ SOLID 원칙
🔗#6 강의 - (김영한) 스프링 핵심 원리 - 기본편
6️⃣ 스프링의 의존성 주입 방식
🔗#7 도서 - 스프링부트 3 백엔드 개발자되기 - 자바편 / 🔗#8 블로그 - IT is True
7️⃣ Java record