현재 제작중인 sns 프로젝트에서 내가 범한 아주 기본적인 JAVA 사용법 실수로 인해 발생한 문제에 대한 트러블 슈팅및 다시는 이런 실수를 안하기 위한 정리글입니다.
목차
클래스?
객체?
인스턴스?
생성자?
트러블슈팅
깨달은 점
JAVA 17
자바를 다룬다면 클래스(class), 객체, 인스턴스랑 키워드를 정말 지겹도록 듣습니다. 클래스는 보통 객체를 생성하기 위한 '틀' 또는 '설계도' 같은 의미이며 클래스(class)는 객체가 가져야할 속성(멤버변수 또는 필드)와 기능(메서드)로 구성되어있습니다.
public class Person { // 접근제어자ㅣ + class + 클래스명
//필드( 맴버변수) : 특정 클래스에 소속된
String name;
int age;
// 기본 생성자
public Person() {
}
// 사용자 정의 생성자
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Person의 이름을 가져오는 메서드
public String getName() {
return name;
}
}
여기서 좀 더 들어가기전에 앞으로 나올 용어를 한번 정리하고 가겠습니다.(가끔씩 헷갈릴때가 많아요 ㄷㄷ)
클래스 : 객체를 생성하기 위한 '틀' 또는 '설계도' (실존하는것이 아닌 개념으로만 존재한다.)
객체 : 객체는 클래스에서 정의한 속성과 기능을 가진 실체(실체 데이터)이다.
인스턴스 : 인스터스는 특정 클래스로부터 생성된 객체를 의미한다.
public static void main(String[] args) {
// 변수 person1은 객체
// 뱐수 person1은 Person 클래스의 인스턴스
Person person1 = new Person(); // new 키워드를 통해 클래스라는 설계도로 객체 데이터를 생성
}
둘다 클래스에서 나온 실체라는 의미에서 비슷하게 사용도지만, 용어상 인스턴스는 객체보다 좀 더 관계에 초점을 맞춘 단어입니다.
person1은 Person의 객체이다. 대신에 person1은 Person의 인스터스이다. 라고 특정 클랠스와의 관계를 명확히 할 때 인스터스라는 용어를 주로 사용합니다.
쉽게 생각하면 모든 인스턴스가 객체이지만, 우리가 인스턴스라고 부르는 순간은 Person이라는 클래스로부터 person1이라는 객체가 생성되었다는 점을 명확히 하기 위해 person1을 Person의 인스턴스라고 부릅니다.
프로그래밍을 하다보면 객체를 생성하고 그 이후에 바로 초기값을 할당해야하는 경우가 많습니다. 그래서 대부분의 객체 지향 언어는 객체를 생성하자마자 즉시 필요한 기능을 더 편리하게 수행할 수 있도록 생성자 라는 기능(메서드)을 제공합니다.
생성자는 객체가 생성될때 동적으로 인스턴스 변수를 초기화 하기 위해 실행되는 특수한 메서드로 우리가 변수를 선언할때 int a = 1; 처럼,
Person person = new Person(값1,값2) 처럼 클래스에 입력값을 보내는것 입니다. 생성자를 사용하면 객체를 생성하는 시점에 즉시 필요한 기능을 수행이 가능합니다.
생성자에는 몇가지의 규칙이 존재합니다.
생성자의 목적은 객체 초기화 입니다.
생성자의 이름은 클래스 이름가 동일해야 합니다.
생성자는 new 키워드를 통해 객체 생성시, 객체당 한번 호출
객체가 생성될때 반드시 한번은 호출됩니다.
생성자는 리턴 타입을 지정할수 없습니다.
개발자가 생성자를 작성하지 않으면 컴파일러가 자동으로 기본 생성자를 넣어줍니다.(직접 작성한 생성자가 있다면 기본 생성자는 넣어주지 않으니 기본 생성자를 사용할시에는 직접 작성해야합니다.)
생성자는 여러 개 작성 가능합니다. (오버로딩)
class Phone {
String modelName;
String color;
int price;
/// 생성자 (인스턴스 뱐스 깂 초기화)
public Phone(String modelName, String color) {
this.modelName = modelName; // 메서드의 입력값으로 인스턴스 변수를 초기화
this.color = color;
this.price = 10000; // 메서드의 입력값으로 인스턴스 변수를 초기화
}
// public Phone() {
// }
public String getPhone() {
return modelName + " " + color + " " + price;
}
}
public class Const {
public static void main(String[] args) {
Phone phone1 = new Phone("갤럭시", "블랙"); // 생성자 호출
Phone phone2 = new Phone("아이폰", "화이트"); // 생성자 호출
Phone phone3 = new Phone(); // 컴파일 오류 발생
System.out.println(phone1.getPhone());
System.out.println(phone2.getPhone());
}
}
위 코드에서 보면 this라는 키워드가 눈에 띄입니다. this라는 키워드가 있는 생성자를 보면 생성자의 매개변수(파라미터)와 Phone의 맴버변수의 변수 이름이 String modelName으로 동일합니다.
그럼 이처럼 변수들의 이름이 동일할때 구분은 어떻게 하는걸까요?
이 경우 맴버 변수보다 매개변수가 코드 블럭의 더 안쪽에 있기 때문에 매개변수가 우선순위를 가집니다. 그래서 해당 생성자 안에서 modlName이라 적으면 매개변수와 매핑이 되는것이 보일겁니다.
변수의 이름이 같을시 우선순위
맴버 변수에 접근하려면 앞에 this. 이라는 키워드를 붙여주면 this는 자기자신의 인스턴스 참조값을 가리킵니다. 만약 this. 라는 키워드를 제거한다면 modelName = modelName으로 모두 매개변수를 가르키기에 맴버변수의 값은 변경되지 않습니다!
맴버 변수와 매개 변수의 이름이 서로 다르다면 this는 생략이 가능합니다.
this의 생략
위 사진의 코드 처럼 modelName앞에 this가 없어도 맴버 변수에 접근합니다. modelName은 먼저 매개변수에서 같은 이름이 있는지 찾습니다. 이 경우에는 같은 이름이 없으므로 맴버 변수에서 찾습니다.
model은 먼저 매개변수에서 같은 이름이 있는지 찾습니다. 위 코드에는 매개변수에 같은 이름이 있으므로 매개변수를 사용합니다.
this() 메서드를 사용하면 생성자 내부에서 다른 생성자를 호출 할 수 있습니다.(같은 클래스에 있는) this() 메서드에 인수만 전달해주면 됩니다.
public Phone(String model, String color) {
modelName = model; // 메서드의 입력값으로 인스턴스 변수를 초기화
this.color = color;
this.price = 10000; // 메서드의 입력값으로 인스턴스 변수를 초기화
}
public Phone() {
this("갤럭시", "블랙"); // this()를 통해 다른 생성자를 호출
}
public String getPhone() {
return modelName + " " + color + " " + price;
}
}
public class Const {
public static void main(String[] args) {
// 초기화 인수를 보내주지 않아 Person() 생성자가 호출되나, 안에서 this()를 통해 다른 생성자를 호출
// 결과적으로 Phone(String name, int age) 생성자가 호출되어 name과 age가 초기화
Phone phone1 = new Phone();
System.out.printf("phone1: %s\n", phone1.getPhone());
}
}
*this() 메서드는 생성자 코드의 첫줄에만 작성이 가능합니다.
또한 위 사진을 보시면 this() 메서드가 Phone(String model, String color) 생성자를 호출하는것이 확인 됩니다.
@Getter
public class Post {
private String content;
public Post() {
}
public Post (String content) {
Post post = new Post();
post.content = content;
}
}
@Getter
@Setter
public class PostDto {
private String content;
public Post toEntity() {
return new Post(content);
}
}
public static void main(String[] args) {
PostDto postDto = new PostDto(content);
postDto.setContent("test");
Post post = postDto.toEntity();
System.out.printf("post content = %s", postDto.getContent()); // test 출력
System.out.printf("post content = %s", post.getContent()); // null 출력
}
}
위 코드는 당시의 문제코드를 아주 간략하지만 매우 정확하게 구현해두었습니다. 여기서 제가 겪었던 문제는 분명히 dto에는 값이 들어갔는데, 실제 출력되는 값은 null이 출려되는 문제였습니다.
Post post = postDto.toEntity(); 를 통해서 dto에 있는 toEntiy 메서드를 통하여 Post객체를 생성합니다.
인수에는 content가 들어가며 값은 setContent로 설정한 test가 들어갑니다. 그러나 Post 클래스에 있는 생성자에서 문제가 발생합니다.
생성자를 보시면 내부에서 새로운 Post 객체를 생성하여 생성된 객체에 content를 설정만하고 종료가 됩니다.
즉 객체가 총 2개가 생성이 되는데 저희가 지정한 값은 완전히 새로운 객체에 값이 저장되고 그대로 끝나버리며 저희가 원했던 객체에는 아무런 값이 할당되지 않는 문제였습니다.
@Getter
public class Post {
private String content;
public Post() {
}
public Post (String content) {
this.content = content;
}
}
@Getter
@Setter
public class PostDto {
private String content;
public Post toEntity() {
return new Post(content);
}
}
public static void main(String[] args) {
PostDto postDto = new PostDto(content);
postDto.setContent("test");
Post post = postDto.toEntity();
System.out.printf("post content = %s", postDto.getContent()); // test 출력
System.out.printf("post content = %s", post.getContent()); // test 출력
}
}
이렇게 Post.class에 있는 생성자에서 내부에 있는 Post post = new Post()를 지우고 this.키워드를 사용하여 자신의 인스턴스 참조값을 설정하면 객체도 1개만 만들어지고 원하던 값이 출력되는 것을 확인할수 있습니다.
이 문제를 겪으면서 뭔가 공부를 헛으로 했구나라는 허탈감도 들고 그래도 덕분에 제대로 배웠다는 생각도 들었습니다.
사실 조금만 생각하면서 코드를 작성했다면 일어나지 않을 문제였습니다.(무슨 생각으로 저때 저렇게 작성했는지 아직도 기억이 나지 않네요)
생각하면서 코드를 짜자!
코파일럿과 같은 ai가 자동으로 생성해주지만 너무 쉽게 쓰지 말자!
디버깅을 항상 생각하고 실천하자!
감사합니다!