목차
1. 클래스 정의하는 방법
2. 객체 만드는 방법 (new 키워드 이해하기)
3. 메서드 정의하는 방법
4. 생성자 정의하는 방법
5. this 키워드 이해하기
OOP 언어인 JAVA 에서는 모든 프로그래밍이 클래스를 기반으로 이루어진다. 클래스를 통해 객체 인스턴스를 생성할 수 있고, 클래스를 통해 객체들이 가질수 있는 필드와 수행할 수 있는 메서드들을 정의해 놓을 수 있다.
public class Car { // 클래스 이름
private String color; // 필드
private int price; // 필드
public static String classVar = "클래스 변수"; //필드
public Car(){ } // 기본 생성자
public Car(String color, int price) { // 생성자
this.color = color;
this.price = price;
}
public void getCarInfo(){ // 이하 메서드
System.out.println(this.color);
System.out.println(this.price);
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public static String getClassVar() {
return classVar;
}
public static void setClassVar(String classVar) {
Car.classVar = classVar;
}
public static void aboutCarClass(){
System.out.println("차와 관련된 class");
}
}
필드란 클래스에서 선언된 모든 종류의 변수들을 의미한다. 클래스 안에는 다양한 scope 를 가지는 변수들이 존재한다.
@Test
@DisplayName("인스턴스 변수")
void instanceVar(){
Car car = new Car();
car.color = "red"; // 인스턴스 변수에 직접 값을 할당
car.price = 10000;
Car car2 = new Car("blue", 9999); // 생성자를 통해 값을 할당
Car car3 = new Car();
car3.setColor("green"); // 메서드(setter) 를 통해 값을 할당
car3.setPrice(1);
assertThat(car.color).isEqualTo("red");
assertThat(car.price).isEqualTo(10000);
assertThat(car2.color).isEqualTo("blue");
assertThat(car2.price).isEqualTo(9999);
assertThat(car3.getColor()).isEqualTo("green");
assertThat(car3.getPrice()).isEqualTo(1);
}
@Test
@DisplayName("클래스 변수")
void staticVar(){
assertThat(Car.classVar).isEqualTo("클래스 변수");
Car.classVar = "클래스 변수에 직접 값을 할당";
assertThat(Car.classVar).isEqualTo("클래스 변수에 직접 값을 할당");
Car.setClassVar("클래스 변수에 static 메서드를 통해 값을 할당");
assertThat(Car.classVar).isEqualTo("클래스 변수에 메서드를 통해 값을 할당");
}
당연하게도, 클래스 변수는 생성자를 통해 값을 초기화할 수 없다. 인스턴스가 가지는 변수가 아니라, 클래스가 가지는 변수로 클래스와 라이프사이클을 같이하기 때문이다.
메서드란, 클래스 안에서 특정 작업을 수행하기 위한 명령문의 집합을 의미한다.
메서드의 구조는 다음과 같다.
접근제어자 반환타입 메소드이름(매개변수목록) { // 선언부
// 구현부
}
접근 제어자 : 해당 메소드에 접근할 수 있는 범위를 명시합니다.
반환 타입(return type) : 메소드가 모든 작업을 마치고 반환하는 데이터의 타입을 명시합니다.
메소드 이름 : 메소드를 호출하기 위한 이름을 명시합니다.
매개변수 목록(parameters) : 메소드 호출 시에 전달되는 인수의 값을 저장할 변수들을 명시합니다.
구현부 : 메소드의 고유 기능을 수행하는 명령문의 집합입니다.
(출처 - http://www.tcpschool.com/java/java_methodConstructor_method)
@Test
@DisplayName("인스턴스 메서드")
void instanceMethod(){
Car car = new Car("Red", 100);
car.getCarInfo(); // 호출하기 위해선 인스턴스가 필요하다.
}
-->> console
Red
100
------------------------------------------------------------
@Test
@DisplayName("클래스 메서드")
void staticMethod(){
Car.aboutCarClass();
}
-->> console
차와 관련된 class
테스트코드를 보면 알 수 있듯, 인스턴스 메서드를 호출하기 위해선 인스턴스가 필요하고 클래스 메서드는 직접 호출하여 사용할 수 있다. 인스턴스 변수 클래스 변수에서 설명한 이유와 동일한 이유가 젹용된다.
객체는 어떻게 생성할 수 있을까? 위에서 살짝 보였는데, 다음과 같이 new 연산자와 생성자를 통해 객체를 만들 수 있다.
Car car = new Car();
new 연산자를 통해 JVM 메모리의 Heap 영역에 데이터를 저장할 공간을 할당하고, 참조값을 반환해 준다. 그럼 해당 참조값을 통해 객체에 접근이 가능해 지는것이다.
그리고 생성자가 호출되는데, 생성자를 통해 초기값을 세팅할 수 있게 된다.
위 코드에서는 기본 생성자를 사용하였는데, 기본 생성자를 사용할 경우 인스턴스변수들이 기본값으로 초기화가 진행되고, 이외에도 생성자를 통해 인스턴스 변수들의 값을 커스텀하게 초기화할 수 있다. 테스트 코드를 통해 무슨 말인지 살펴보도록 하자.
public class Person {
String name;
int age;
float height;
Person friend;
public Person() {
}
public Person(String name, int age, float height) {
this.name = name;
this.age = age;
this.height = height;
}
public Person(String name, int age, float height, Person friend) {
this.name = name;
this.age = age;
this.height = height;
this.friend = friend;
}
}
-------------- 테스트
@Test
@DisplayName("기본 생성자를 사용한 초기화")
void defaultConstruct(){
Person person = new Person();
assertThat(person.name).isEqualTo(null); // String 타입의 기본값은 null
assertThat(person.age).isEqualTo(0); // int 타입의 기본값은 0
assertThat(person.height).isEqualTo(0.0f); // float 타입의 기본값은 0.0f
assertThat(person.friend).isEqualTo(null); // reference 타입의 기본값은 null (String 도 reference 타입이다)
}
@Test
@DisplayName("생성자를 사용한 초기화")
void construct(){
Person son = new Person("Son", 28, 183f);
Person messi = new Person("Messi", 33, 168.8f, son);
assertThat(messi.name).isEqualTo("Messi");
assertThat(messi.age).isEqualTo(33);
assertThat(messi.height).isEqualTo(168.8f);
assertThat(messi.friend).isEqualTo(son);
}
매서드를 정의하는 방법은 위에서 살펴본 것 처럼
접근제어자 반환타입 메소드이름(매개변수목록) { // 선언부
// 구현부
}
다음과 같다.
다양한 메서드를 작성해보며 익숙해지도록 해보자.
위의 Person 클래스에 다음과 같은 메서드들을 추가해보았다.
// 이름을 변경하고 변경된 이름을 return 하는 메서드
public String changeName(String name){
this.name = name;
return name;
}
// 현재 Person 의 각각의 변수에 저장된 값들을 콘솔에 출력해주는 메서드
public void showPersonInfo(){
System.out.println("name : " + name);
System.out.println("age : " + age);
System.out.println("height : " + height);
if(friend != null ){
System.out.println("friend : " + friend.name);
}
}
// 태어난 년도를 return 하는 메서드
public int birthYaer(){
return Calendar.getInstance().get(Calendar.YEAR)-age;
}
------------------------------- 테스트
@Test
@DisplayName("changeName 메서드 테스트")
void chageName(){
Person messi = createPerson_messi();
String changedName = messi.changeName("ronaldo");
assertThat(changedName).isEqualTo("ronaldo");
assertThat(messi.name).isEqualTo("ronaldo");
}
@Test
@DisplayName("showPersonInfo 메서드 테스트")
void showPersonInfo(){
Person messi = createPerson_messi();
messi.showPersonInfo();
}
@Test
@DisplayName("birthYaer 메서드 테스트")
void birthYaer(){
Person messi = createPerson_messi();
assertThat(messi.birthYaer()).isEqualTo(1987);
}
private Person createPerson_messi() {
return new Person("messi", 33 , 168.8f);
}
생성자는 객체를 생성할 때 호출되는 특수한 형태의 메서드(반환값 X, 클래스명과 동일한 메서드명)로, 객체를 초기화 하는데 사용된다. 위에서 살펴보앗듯이, 기본생성자는 객체의 인스턴스 변수들을 변수의 기본값으로 세팅해준다. 그리고 아무런 생성자를 작성하지 않으면 default 로 기본생성자가 들어가 기본생성자를 사용할 수 있게 한다. 단, 생성자를 하나라도 정의하면 기본생성자가 default로 들어가진 않기때문에 기본생성자를 사용하고 싶다면 오버라이딩을 활용해야한다. 파라미터로 받은 값들로 인스턴스 변수의 값을 초기화 하는데, 오버라이딩을 활용하여 유연성 있게 객체를 생성할 수 있다.
기본생성자 와 다양한 파라미터를 받는 생성자
public Person() {} // 기본생성자
public Person(String name){
this.name = name;
}
public Person(String name, int age, float height) {
this.name = name;
this.age = age;
this.height = height;
}
public Person(String name, int age, float height, Person friend) {
this.name = name;
this.age = age;
this.height = height;
this.friend = friend;
}
------------------테스트
@Test
@DisplayName("기본생성자 및 다양한 생성자")
void consTest(){
Person person = new Person();
assertThat(person.name).isEqualTo(null);
assertThat(person.age).isEqualTo(0);
assertThat(person.height).isEqualTo(0.0f);
assertThat(person.friend).isEqualTo(null);
Person person1 = new Person("jaden");
assertThat(person1.name).isEqualTo("jaden");
assertThat(person1.age).isEqualTo(0);
assertThat(person1.height).isEqualTo(0.0f);
assertThat(person1.friend).isEqualTo(null);
Person person2 = new Person("kim", 10, 180.0f, person1);
assertThat(person2.name).isEqualTo("kim");
assertThat(person2.age).isEqualTo(10);
assertThat(person2.height).isEqualTo(180.0f);
assertThat(person2.friend).isEqualTo(person1);
}
this 는 해당 키워드를 호출한 인스턴스를 가리키는 키워드이다.
위에서 사용했던 Car 클래스에 다음과 같은 메서드를 추가하였다.
public Car returnThis(){
return this;
}
@Test
@DisplayName("This 테스트")
void returnThis(){
Car car = new Car("Blue", 100);
Car car1 = car.returnThis();
assertThat(car).isEqualTo(car1);
}
이러한 키워드를 통해서 동일한 변수명을 가지는 지역변수와, 인스턴스변수를 컨트롤할 수 있는데, 코드를 통해 알아보자
public class Car {
String color;
int price;
public Car(String color, int price) {
this.color = color;
this.price = price;
}
위의 코드에서 생성자 블럭 안의 color 는 지역변수의 특성상 파라미터로 들어온 color를 의미한다. 하지만 this 키워드를 붙여서 사용한 this.color는 인스턴스 변수를 의미함으로 이렇게 동일한 변수명을 사용할 수 있게끔 문법적으로 설정해 놓은 것이다 .
this 가 인스턴스 자신을 참조하는 키워드라면, this()는 같은 클래스의 생성자내에서 오버라이딩된 생성자를 호출할때 사용하는 연산자이다.
public class Car {
String color;
int price;
public Car(){ }
public Car(String color) {
this.color = color;
}
public Car(String color, int price){
this(color);
this.price = price;
}
}
스터디에 사용된 코드 깃헙 레포: https://github.com/JadenKim940105/whiteship-study