객체 생성 시, 초기화 담당
new 연산자로 호출됨
모든 클래스에 반드시 1개 이상 존재
인스턴스가 생성될 때마다 호출되는 '인스턴스 초기화 메서드'
메서드와 비교
오버로딩이 가능함
단, 생성자가 인스턴스를 생성하는 것이 아니다.
생서자 이름 == 클래스 이름
return 값 없다.
클래스 이름(타입변수명, 타입변수명, ...) {
인스턴스 생성 시 수행될 코드. 주로 인스턴스 변수의 초기화 코드
}
매개변수가 없는 생성자를 의미
public Car() {
}
클래스 내부에 생성자 선언을 생략 or 하나도 선언하지 않았다면?
: 컴파일러가 { } 중괄호 블록이 비어있는 기본 생성자를 바이트 코드에 자동 추가함
클래스가 public class로 선언되면, 기본 생성자에도 public이 붙음
클래스가 public 없이 class로만 선언되면, 기본 생성자에도 public이 붙지 않음
상속의 경우
부모 클래스에서 기본 생성자 없으면
자식 클래스에서 생성자를 만들어주면 됨
pulic class Car {
//car(): 기본 생성자
public Car() { } // 자동 추가
}
Data1클래스
class Data1 {
int value;
}
Data2클래스
class Data2 {
int value;
Data2(int x) { // 매개변수가 있는 생성자
value = x;
}
}
EX클래스
class EX {
public static void main(String[] args) {
Data1 d1 = new Data1();
// Data2 d1 = new Data2(); 컴파일 에러 발생
Data2 d1 = new Data2(10); // OK
}
}
class Tv9_1 {
// 속성 : 변수 선언
boolean power; // 전원상태
int channel; // 채널
String color; // 색깔
long price; // 가격
// 위 속성에서 필수로 초기값이 필요한 값들을 초기화 해주는 기본 생성자
public Tv9_1() {
power = false;
channel = 1;
}
// 오버로딩 한 생성자 - 매장 진열 용 일 경우에는 가격과 색깔의 초기화가 필요합니다. this는 매개변수와 인스턴스변수를 구분하기 위해 사용
public Tv9_1(String color, long price) {
power = false; // this.power, power 둘다 지금 상황에서는 인스턴스 변수를 명확하게 판단 할 수 있기 때문에 어떤걸 사용해도 상관 없습니다.
channel = 1;
this.color = color;
this.price = price;
}
// 기능 : 메서드 선언
void power() { // 전원 기능
power = !power;
if (power) {
System.out.println("전원 ON");
} else {
System.out.println("전원 OFF");
}
}
void channelUp() { // 채널 증가
channel++;
System.out.println("채널 증가");
}
void channelDown() { // 채널 감소
channel--;
System.out.println("채널 감소");
}
}
class Tv9_1Main {
public static void main(String[] args) {
// 기본 초기화된 Tv9_1 생성
Tv9_1 tv = new Tv9_1();
System.out.print("기본 생성자 TV: ");
tv.power();
// 진열 용 Tv9_1 생성
Tv9_1 exTv = new Tv9_1("보라색", 3456789);
System.out.print("오버로딩 생성자 TV: ");
exTv.power();
System.out.println("exTv.color = " + exTv.color);
System.out.println("exTv.price = " + exTv.price);
// 근데 이때 주의할 점!
// 기본 생성자는 없고 오버로딩한 생성자만 있을 경우!
// 컴파일러는 기본 생성자를 만들어주지 않기 때문에 기본 생성자를 사용하려고 하면 Error 발생!
// 위 기본 생성자를 주석 하세요!!!!
Tv9_1 tv2 = new Tv9_1(); // Error 발생, 기본 생성자가 없기 때문에 매개 변수를 넣으라고 intellij 가 요구합니다.
}
}
class Product {
int price;
int point;
Product() { // 기본 생성자
}
Product(int price) { // 생성자
this.price = price;
point = (int) (price / 10.0);
}
}
class ... {
...
}
...
기본 생성자 대신, '명시적으로 생성자를 선언'하는 경우
매개변수를 선언
public class Car {
// 생성자 (명시적 생성자 선언)
Car(String model, String color, int maxSpeed) {
...
}
}
매개변수에 각각 매개값을 넣어준다.
// String 타입 매개값 2개, int 타입 매개값 1개
car myCar = new Car("그랜저", "검정", 300);
매개 변수
주의점
클래스에 생성자가 명시적으로 선언되어 있는 경우(기본 생성자가 아닌 생성자 선언인 경우),
반드시 선언된 생성자를 호출해서 객체를 생성해야 한다.
명시적 생성자 선언을 한 경우, 기본 생성자는 호출 불가능
Car myCar = new Car(); → (X)
// 기존 코드
Car c = new Car(); // 인스턴스 생성
c.color = "white"; // 인스턴스의 값 초기화
c.gearType = "auto";
c.door = 4;
// 매개변수를 갖는 생성자를 사용한다면
Car c = new Car("white", "auto", 4) // 인스턴스 생성 & 초기화
public class Korean {
// 필드
String nation = "대한민국";
String name; // 이름
String ssn; // 주민번호
// 생성자
public Korean(String n, String s) {
name = n;
ssn = s;
}
}
public class KoreanExample {
public static void main(String[] args) {
// 객체 생성
Korean1 K1 = new Korean("박자바", "011225-1234567"); // 이 값들이 name필드와 ssn필드의 초기값으로 사용됨
System.out.println("K1.name : " + K1.name);
System.out.println("K1.ssn : " + K1.ssn);
}
}
필드
생성자
매개 변수를 달리하는 생성자를 여러 개 선언하는 것
public class Car {
Car() {...}
Car(String model,String color) {...}
Car(String model,String color, int maxSpeed) {...}
}
public class Car {
Car() {...}
Car(String model,String color) {...}
Car(String color, String model) {...} // 오버로딩 (X) : 매개변수의 선언 순서가 다르다
}
public class Car {
// 필드
String company = "현대자동차";
String model;
String color;
int maxSpeed;
// 생성자 1
Car() {
}
// 생성자 2
Car(String model) {
this.model = model;
}
// 생성자 3
Car(String model, String color) {
this.model = model;
this.color = color;
}
// 생성자 4
Car(String model, String color, int maxSpeed) {
this.model = model;
this.color = color;
this.maxSpeed = maxSpeed;
}
}
public class CarExample {
public static void main(String[] args) {
// 생성자 1 선택
Car car1 = new car();
System.out.println("car1.company :" + car1.company);
System.out.println();
// 생성자 2 선택
Car car2 = new car("자가용");
System.out.println("car2.company :" + car2.company);
System.out.println("car2.model :" + car2.model);
System.out.println();
// 생성자 3 선택
Car car3 = new car("자가용", "빨강");
System.out.println("car3.company :" + car3.company);
System.out.println("car3.model :" + car3.model);
System.out.println("car3.color :" + car3.color);
System.out.println();
// 생성자 4 선택
Car car4 = new car("자가용", "빨강", 200);
System.out.println("car4.company :" + car4.company);
System.out.println("car4.model :" + car4.model);
System.out.println("car4.color :" + car4.color);
System.out.println("car4.maxSpeed :" + car4.maxSpeed);
System.out.println();
}
}
public class Car {
// 필드
String company = "현대자동차";
String model;
String color;
int maxSpeed;
// 생성자 1
Car() {
}
// 생성자 2 : 마지막 생성자인 생성자 4에서 Car(String model, String color, int maxSpeed)를 호출
Car(String model) {
this(model, "은색", 250);
}
// 생성자 3 : 마지막 생성자인 생성자 4에서 Car(String model, String color, int maxSpeed)를 호출
Car(String model, String color) {
this(model, color, 250);
}
// 생성자 4 : 공통 실행 코드들
Car(String model, String color, int maxSpeed) {
this.model = model;
this.color = color;
this.maxSpeed = maxSpeed;
}
}
생성자 오버로딩이 多 경우, 생성자 간의 중복된 코드 발생 위험
해결책
나머지 생성자는 초기화 내용을 가지고 있고, 한 생성자에게만 this( ) 를 이용하여 집중적으로 작성
// 기존 코드
Car 2() {
color = "white";
gearType = "auto";
door = 4;
}
// 간략히 하면
Car 2() {
this("white", "auto", 4);
}
생성자 에서 다른 생성자를 호출할 때 사용
this() 를 사용하지 않고 클래스명(); 이렇게 생성자를 호출하려고 하면, Error 가 발생
다른 생성자 호출시 '첫 줄'에서만 사용 가능
Car (String color) {
door = 5;
Car(color, "auto", 4); // 에러
}
// 올바르게 수정한다면
Car (String color) {
tihs(color, "auto", 4);
door = 5;
}
인스턴스 자신을 가리키는 참조변수
this 는 인스턴스 멤버만 사용 가능
지역 변수 와 인스턴스 변수를 구별할 때 사용
class Tv10_1 {
// 속성 : 변수 선언
boolean power; // 전원상태
int channel; // 채널
String color; // 색깔
long price; // 가격
// 위 속성에서 필수로 초기값이 필요한 값들을 초기화 해주는 기본 생성자
public Tv10_1() {
this.power = false;
this.channel = 1;
}
// 오버로딩 한 생성자 - 매장 진열 용 일 경우에는 가격과 색깔의 초기화가 필요합니다.
public Tv10_1(String color, long price) {
this.power = false;
this.channel = 1;
this.color = color;
this.price = price;
}
// 기능 : 메서드 선언
void power() { // 전원 기능
this.power = !power;
if (this.power) {
System.out.println("전원 ON");
} else {
System.out.println("전원 OFF");
}
}
void channelUp() { // 채널 증가
this.channel++;
System.out.println("채널 증가");
}
void channelDown() { // 채널 감소
this.channel--;
System.out.println("채널 감소");
}
// 색깔을 수정하고 자기 자신을 반환하는 메서드
Tv10_1 changeColor(String color) { // 반환 타입으로 자기자신인 Tv 선언
this.color = color;
System.out.println("색깔 변경 완료!");
return this; // this 는 자기 자신을 가리키는 참조변수!
}
}
class Tv10_1Main {
public static void main(String[] args) {
// 기본 초기화된 Tv10_1 생성
Tv10_1 tv = new Tv10_1();
System.out.print("기본 생성자 Tv10_1: ");
tv.power();
// 진열 용 Tv10_1 생성
Tv10_1 exTv = new Tv10_1("보라색", 3456789);
System.out.print("오버로딩 생성자 Tv10_1: ");
exTv.power();
System.out.println("exTv.color = " + exTv.color);
System.out.println("exTv.price = " + exTv.price);
System.out.println();
// 진열 용 Tv10_1 의 색깔을 수정하고 수정된 객체를 참조변수에 저장하겠습니다.
Tv10_1 exTvThis = exTv.changeColor("파란색");
// 색깔이 변경된 Tv의 주소가 저장된 참조변수 exTvThis 를 사용하여 변경된 색깔 확인
System.out.println("색깔이 변경되었는지 확인 exTvThis.color : " + exTvThis.color);
// 당연히 exTv 이걸로 확인해도 색깔이 변경되어 있습니다.
System.out.println("exTv.color = " + exTv.color);
}
}
파라미터(인수)가 없는 기본 생성자를 만들어준다.
@NoArgsConstructor
public class Order {
private String food;
private int price;
private String makers;
/*@NoArgsConstructor 사용하면 아래와 같은 생성자를 자동 생성할 수 있다.
public Order() {
}
*/
}
@NoArgsConstructor
public class MenuRequest {
// @NotNull : NotNull을 지정한 id에 대해 null 값인지 체크
@NotNull
private int id;
private String menuName;
private String partName;
}
만약, @NoArgsConstructor 를 아래처럼 바꾼다면
@NoArgsConstructor(force=true)
객체 내의 모든 변수가 초기값으로 설정되지 않아 컴파일 에러가 발생
→ force의 기본 값은 false 이므로, @NoArgsConstructor 만 사용하면 변수가 초기값으로 자동 설정된다.
모든 필드값을 파라미터(인수)로 받는 생성자를 만들어준다.
주의!
이 어노테이션을 사용하기보다는
build 패턴 or 정적 팩토리 메소드를 사용하는 것이 코드 가동성에 더 좋다.
@AllArgsConstructor
public class Order {
private String food;
private int price;
private String makers;
/*@AllArgsConstructor를 사용하면 아래와 같은 생성자를 자동 생성할 수 있다.
public Order(String food, int price, String makers) {
this.food = food;
this.price = price;
this.makers = makers;
}
*/
}
@AllArgsConstructor
@Getter
public class MenuRequest {
@NotNull
private int id;
private String menuName;
private String partName;
}
// @AllArgsConstructor 로 인해, 모든 필드가 있는 생성자가 생성됨
MenuRequest menu = new MenuRequest(id, menuName, partName);
@RequireArgsConstructor
public class Order {
@NotNull
private String food;
private final int price;
private String makers;
/*@RequireArgsConstructor 사용하면 아래와 같은 생성자를 자동 생성할 수 있다.
public Order(String food, int price) {
this.food = food;
this.price = price;
}
*/
}
참고: 디자인 패턴 - (2) 생성 패턴 - 2) 빌더 (Builder) - (3) 빌더 패턴
Order 클래스에는 자동으로 cancelPrice, orderPrice 순서로 인자를 받는 생성자가 만들어진다.
@AllArgsConstructor
public static class Order {
private long cancelPrice;
private long orderPrice;
}
// 취소금액 5,000원, 주문금액 10,000원
Order order = new Order(5000L, 10000L);
이 때 만약, 이렇게 두 인자의 위치를 바꾼다면?
@AllArgsConstructor
public static class Order {
private long orderPrice;
private long cancelPrice;
}
두 필드는 동일한 Type 이라서, 기존 생성자 호출 코드에서는 인자 순서를 변경하지 않았음에도 어떠한 오류도 발생하지 않는다.
그러나 실제로는 입력된 값이 바뀌어 들어가게 된다.
// 주문금액 5,000원, 취소금액 10,000원. 취소금액이 주문금액보다 많아짐!
Order order = new Order(5000L, 10000L); // 인자값의 순서 변경 없음
해결책
@Builder 사용을 권장한다.public static class Order { private long cancelPrice; private long orderPrice; @Builder private Order(long cancelPrice, long orderPrice) { this.cancelPrice = cancelPrice; this.orderPrice = orderPrice; } } // 필드 순서를 변경해도 문제 없음. Order order = Order.builder().cancelPrice(5000L).orderPrice(10000L).build(); System.out.println(order);
@Builder 는
파라미터 순서가 아닌이름으로 값을 설정하므로, 리팩토링에 유연하게 대응할 수 있다.
@Builder 는 기본적으로 @AllArgsConstructor 를 내포하고 있다.
생성자를 package private 으로 만들기 때문에, 외부에서 생성자를 호출하는 일은 쉽게 안 생긴다.
하지만, 해당 클래스의 다른 메소드에서 자동으로 생성된 생성자를 사용할 경우에는 문제 소지가 있다.
해결책
1. @Builder 는 가급적클래스보다는직접 만든 생성자 혹은 static 객체 생성 메소드에 붙이는 것을 권장한다.
- @Builder 에 명확하게 어떤 역할을 하는 빌더인지를 메소드 이름으로 표현(클래스와 메소드 이름을 지정하는 기능으로) 해주는 것이 좋다.
@EqualsAndHashCode 은 equals(), hashCode() 를 자동 생성한다.
동등성
을 비교동일성
을 비교두 메소드를 변경가능한(Mutable) 객체에 파라미터 없이 사용할 경우, 문제가 발생할 수 있다.
@EqualAndHashCode
@AllArgsConstructor
@Setter
public static class Sample {
private Long sampleId;
private String sampleName;
}
Sample sample = new Sample(lL, "sample_one");
Set<Sample> samples = new HashSet<>();
samples.add(sample); // set 에 객체 sample 를 추가
// 실험 1 : set 에 저장 후, 단순 출력
samples.contains(order); // 출력 결과 : true
// 실험 2 : set 에 저장 + 필드값 sample1 로 변경 후, 출력
sample.setSampleName("sample1");
samples.contains(sample); // 출력 결과 : false
실험 1 과 실험 2 는 동일한 객체 sample 임에도
필드값을 변경해버리면(실험 2), hashCode 가 변경되면서 찾을 수가 없게 된다.
@Data 는 @getter + @setter + @RequiredArgsConstructor + @toString + @EqualsAndHashCode 를 한번에 설정해준다.
따라서 @RequiredArgsConstructor, @EqualsAndHashCode 의 사용을 지양하기 때문에(윗글 참고), 마찬가지로 @Data 의 사용도 권장되지 않는다.
참고: @AllArgsConstructor , @NoArgsConstructor의 의미
참고: [ Lombok ] 생성자 어노테이션
참고: [Spring]생성자와 의존성 주입
참고: Lombok 사용상 주의점(Pitfall)
참고: 자주 사용되는 lombok, 주의 사항