본문 바로가기
  • 기업가 장준영
Java_1

Java_1_7 ( 참조 자료형 )

by 장준영 2020. 1. 8.

Java_1_6 에서는 배열에 대해 알아 보았다

이번 시간에는 Java_1_3 에서 설명했던 자료형 중에서 참조 자료형 에 대해서 정리했다

자바의 타입은 기본 자료형참조 자료형이 있다고 했다

지금까지 계속 알아본 기본 자료형 8개를 제외한 나머지 타입은 모두 참조 자료형이다

쉽게 구분하려면, 기본 자료형과 참조 자료형의 가장 큰 차이는 new를 사용해서 객체를 생성하는지 여부의 차이 이다

new 없이도 객체를 생성할 수 있는 참조 자료형은 오직 String 뿐이라고 알고 있으면 된다

다시말해서 byte, short, int, long, float, double, char, boolean을 제외하고는 모두 참조 자료형이다

Java_1_4에서 배운 각종 연산자들은 대부분 기본 자료형을 위해서 존재하는 것이다

그 중에서 +는 참조 자료형 중에서 String클래스만 사용 가능하고, 나머지 클래스에서는 사용할 수 없다

그 이외에 다른 참조 자료형이 사용할 수 있는 연산자는 오직 하나다

바로 값을 할당하기 위한 등호(=) 뿐이다

참조 자료형은 new를 사용하여 객체를 사용한다고 했는데, new 뒤에 나오는 것이 바로 생성자다

여러분들이 클래스를 만들면 보통 인스턴스 변수와 클래스 변수를 만들고, 생성자와 메소드를 만들어야 한다

그러면 본격적으로 생성자와 메소드에 대해서 알아보자

기본 생성자

자바는 생성자를 만들지 않아도 자동으로 만들어지는 기본 생성자가 있다

main메소드에서 클래스의 이름으로 객체를 생성한 것이 바로 기본 생성자다

//Java_1_7_src1

public class ReferenceDefault{

public static void main(String[]args){

ReferenceDefault reference = new ReferenceDefault(); //여기에서 기본생성자는 ReferenceDefault() 이다

}

}

Java_1_7_src1 의 main() 메소드를 보면 ReferenceDefault 클래스의 인스턴스인 reference를 만든 것을 볼 수 있다

여기서 등호 우측에 new옆에 있는 ReferenceDefault()라는 것이 바로 생성자다

아무런 매개 변수가 없는 ReferenceDefault() 라는 생성자는 다른 생성자가 없을 경우 기본으로 컴파일할 때 만들어진다

"생성자는 다른 생성자가 없을 경우 기본으로 컴파일할 때 만들어진다"

라고 했다 그런데, 기본 생성자는 다른 생성자가 있으면 자동으로 만들어지지 않는다

그럼 매개변수가 없는 생성자를 사용하고 싶으면 어떻게 해야 할까? 만들어 주면 된다

//Java_1_7_src2

public class ReferenceDefault{

public ReferenceDefault(){ } // 기본 생성자

public ReferenceDefault(String arg){ }// 매개변수를 String으로 받는 생성자

public static void main(String[]args){

  ReferenceDefault reference = new ReferenceDefault(); //만들어 준 기본 생성자를 사용

}

}

자바에서 생성자는 왜 필요할까?

자바의 생성자는 자바 클래스의 객체(인스턴스)를 생성하기 위해 존재한다

방금 살펴본 main() 메소드에서 reference라는 것이 바로 객체

생성자에 리턴 타입이 없는 이유는 생성자의 리턴 타입은 클래스의 객체이기 때문이며

클래스와 이름이 동일해야 컴파일러가 "아~ 얘가 생성자구나~~"하고 알아 차릴 수 있기 때문이다

생성자를 작성할 떄에는 클래스의 다른 메소드들 보다 위에, 가장 윗부분에 선언하는 것이 좋다

즉, 인스턴스 변수들을 선언한 후에 생성자를 위치시키고, 그 다음에 여러분들이 필요한 메소드들을 위치시켜야

누가 해당 소스 코드를 보더라도 생성자를 찾기 위한 시간을 허비하지 않을 것이다

일반적인 대부분의 코드들은 이러한 암묵적인 약속하에 작성하기 떄문에 여러분들도 이러한 약속을 지켜주는 것이 좋다

생성자는 몇 개까지 만들 수 있을까?

자바는 클래스의 객체르 보다 간편하게 만들기 위해서 여러가지 매개 변수를 갖는 여러 생성자를 가질 수 있다

생성자의 개수는 1개여도 되고 100개가 되도 상관이 없다

자바 패턴 중에서 DTO(Data Transfer Object)라는 것이 있다

어떤 속성을 갖는 클래스를 만들고, 그 속성들을 쉽게 전달하기 위해서 DTO라는 것을 만든다

한 사람의 개인정보를 담는 DTO클래스가 있다고 생각해보자

보통 사람의 개인정보라고 하면 이름, 전화번호, 이메일 주소 등이 있다

이를 DTO클래스로 만들어 본다면

//Java_1_7_src3

public class MemberDTO{

public String name;

public String phone;

public String email;

}

이렇게 DTO를 만들어 두면 무슨 장점이 있을까?

자바의 메소드를 선언할 떄 리턴 타입은 한 가지만 선언할 수 있다

이와 같이 복합적인 데이터를 리턴하려면 String[]과 같이 배열을 리턴해도 되겠지만

int 타입까지 포함되어 있다면 매우 애매해진다, 그래서 이처럼 DTO를 만들어 놓으면 매소드의 리턴 타입을

MemberDTO클래스로 선언하고, 그 객체를 리턴해 주면 된다. 다음과 같이 말이다

//Java_1_7_src4

public MemberDTO getMemberDTO(){

MemberDTO dto = new MemberDTO();

//중간 생략

return dto;

}

자바의 생성자는 매개 변수 개수의 제한은 없고, 몇 개를 만들어도 생관 없다

하지만, 혹시나 해서 이야기하면 너무 많으면 관리가 힘들어지므로, 너무 많이 만드는 것은 좋지 않다

꼭 필요에 맞는 생성자만 만드는 습관을 들여야만 한다

생성자를 만들 때에는 어떤 생성자들이 꼭 필요한지를 생각해서 만들어야 하고, 생성자를 사용할 때에는 어떤 것이 해당 클래스에 가장 적합한지를 선택하는 것이 중요하다

이 객체의 변수와 매개 변수를 구분하기 위한 this

this라는 예약어는 영어 단어의 의미 그대로 "이 객체"의 의미이다

this예약어는 생성자와 메소드 안에서 사용할 수 있다

왜 this가 필요할까? 아래 소스를 보자

//Java_1_7_src5

public class MemberDTO{

public String name;

public String phone;

public String email;

public MemberDTO(String name){

name = name;

}

}

여러분들이 자바에서 컴파일을 하는 컴파일러라고 생각해보자

인스턴스 변수인 name도 있고 매개 변수로 넘어온name도 있다

여러분들의 마음은 앞에 있는 name은 인스턴스 변수인 name으로 생각하게 하고

뒤에 있는 name은 매개변수인 name으로 생각하고 싶을 것이다

하지만 컴파일러는 여러분들의 마음처럼 쉽게 움직여 주질 않는다

생성자 안에서 사용하는 변수이기 떄문에 중괄호 안에있는 name은 모두 매개 변수로 넘겨준 name이라고 생각한다

이러한 혼동을 가장 쉽게 피하는 방법은 매개 변수와 인스턴스 변수의 이름을 다음과 같이 다르게 하는 것이다

//Java_1_7_src6

public class MemberDTO{

public String name;

public String phone;

public String email;

public MemberDTO(String inputName){

name = inputName;

}

}

이렇게 이름을 다르게 하면 쉽게 구분이 되기 떄문에 여러분들도 컴파일러도 혼동되지 않을 것이다

하지만 이름을 다르게하는 것보다 간단한 방법이 this라는 예약어를 사용하는 것이다

//Java_1_7_src7

public class MemberDTO{

public String name;

public String phone;

public String email;

public MemberDTO(String name){

this.name = name;

}

}

이렇게 this.name이라고 지정해 주면, 매개 변수 안에 있는 name이 아닌

"이 객체의 name"이라고 명시적으로 지정해 주는 것이다

클래스의 생성자는 매개 변수들을 서로 다르게 하여 선언 할 수 있다

그렇다면 메소드도 이렇게 이름은 같게 하고 매개 변수들은 다르게 하여 만들 수 있을까? 당연히 가능하다

//Java_1_7_src8

public void print(int data){

}

public void print(String data){

}

public void print(int intData, String stinrgData){

}

public void print(String stringData, int intData){

}

여기에 있는 메소드들은 모두 이름이 동일하다

하지만 각 메소드의 매개 변수의 종류와 개수, 순서다르다

첫 번쨰 print() 메소드는 int를 매개 변수로 하고있고

두 번쨰 print() 메소드는 String를 매개 변수로 하고있고

세 번쨰 와 네 번째 print() 메소드를 보면 각 매개 변수들의 이름은 같지만, 세 번째 메소드는 int, String 순서이며

네 번째 메소드는 String, int순이다

이와 같이 개수가 같아도 타입의 순서가 다르면 다른 메소드처럼 인식된다

여기서 중요한 것은 매개 변수의 이름이 아니라 매개 변수의 타입이다

타입이 다르면 다른 메소드로 생각하지만, 타입이 같고 변수 이름이 같으면 같은 메소드로 인식한다

이와 같이 메소드의 이름을 같도록 하고, 매개 변수만을 다르게 하는 것을 바로 Overloading(오버로딩) 이라고 한다

그렇다면 왜 이러한 오버로딩이라는 기능을 제공하는 것일까?

여러분들이 많이 사용했던 메소드 중에서 System.out.println()이라는 메소드가 있다

이 메소드의 매개 변수로, int만 넘겨줘도 되고 long만 넘겨줘도 되고, Stirng만 넘겨줘도 된다 이게 오버로딩의 잇점이다

만약 int만 넘겨줄 때에는 System.out.printlnInt() 라는 메소드를 사용하고

long만 넘겨줄 때에는 System.out.printlnLong()이라는 메소드를 사용한다고 생각해보자, 얼마나 귀찮겠는가?

세상에 외워야 할 것도 많은데, 이렇게 매개 변수에 따라서 메소드 이름이 바뀌어야만 한다면, 개발자들도 힘들 것이다

메소드 오버로딩은

"같은 역할을 하는 메소드는 같은 메소드 이름을 가져야 한다" 라는 모토로 사용하는 것이라고 기억하면 된다

생성자도 매개 변수에 따라서 다르게 인식된다, 이것도 오버로드의 일종이라고 생각하면 더 기억하기 쉬울 것이다

이번 절에서는 메소드의 수행과 종료에 대해서 알아보자

자바에서 메소드가 종료되는 조건은 다음과 같다

  • 메소드의 모든 문장이 실행되었을 때
  • return 문장에 도달했을 떄
  • 예외가 발생(throw)했을 떄

자바에서는 모든 타입을 한 개만 리턴 타입으로 넘겨줄 수 있다, 모든 기본 자료형과 참조 자료형 중 하나를 리턴할 수 있다

리턴 타입이 있다고 해놓고, 리턴을 하지 않을 때에는 컴파일러에서 에러를 내뿜어준다

만약 리턴 문장 뒤에 다른 계산 문장이 있으면 어떻게 될까?

여러분들이 return이라는 예약어를 사용하여 메소드를 리턴하면

그 메소드는 값을 넘겨주고 그 위치에서 종료한다

다시 말해서 다른 계산 문장이 선언되어 있는 부분은 절대 실행될 일이 없다

즉, 리턴 문장 이후에는 어떤 문장도 있으면 안 된다

그런데, 만약 if문 안에 리턴 문장이 있을 경우

그 이외의 경우가 존재하기 때문에 리턴 문장이 하나의 메소드 내에 두개 이상 있어야만 한다

만약 여러분들이 else 문장에서 리턴을 하지않는다면 이 메소드는 다시 컴파일 오류를 발생시킨다

그런데 이렇게 한 개의 타입만 리턴할 수 있다면

아니 그럼 여러 개를 넘겨 줄 수 없으면, 어떻게 해?

라고 궁금해 하는 사람이 많을 것이다 그래서 사용하는 것이 DTO라는 것이다

자바에서는 한 개만 리턴타입으로 넘겨줄 수 있기 떄문에 이 DTO를 리턴 타입으로 선언해주면

여러 개의 데이터를 하나의 타입에 넣어서 넘겨줄 수 있다

static 메소드와 일반 메소드의 차이

지금까지 main() 메소드에서 클래스나 다른 클래스에 있는 메소드를 호출하려면 반드시 객체를 생성해 왔는데

System.out.println() 메소드는 왜 객체를 생성하지 않아도 될까??

그 이유는 바로 static이라는 예약어 떄문이다

static은 객체를 생성하지 않아도 메소드를 호출할 수 있는 마법의 메소드다

//Java_1_7_src9

public class ReferenceStatic{

public static void main(String[]args){

ReferenceStatic.staticMethod();

}

public static void staticMethod(){

System.out.println("This is a staticMethod");

}

}

이렇게 메소드의 리턴 타입 앞에 static이라고 써 주면 static메소드가 된다.

main()메소드를 보면 지금까지와는 다르게 객체를 생성하지 않고 바로 staticMethod()를 호출한다

당연히 staticMethod()라는 메소드는 static 메소드이기 때문에 객체를 생성하지 않고서도 메소드를 호출할 수 있다

결과만 놓고 보면 몇몇 독자는

"이렇게 모든 메소드에 static을 붙이면 될 텐데 왜 지금까지 알려주지 않았을까? 앞으로 이렇게 쓰면 되겠그만......"

이라고 생각할 수도 있다

하지만 static 메소드는 클래스 변수만 사용할 수 있다는 단점이 있다

static 블록

static에 대해서 설명한 김에 static을 특이하게 사용하는 방법을 한 가지 더 살펴보자

어떤 클래스의 객체가 생성되면서 딱 한번만 불러야 하는 코드가 있다고 생각해보자

그러한 코드를 만들려면 어떻게 해야 할까?

객체는 여러 개를 생성하지만, 한 번만 호출되어야 하는 코드가 있다면 static 블록을 사용하면 된다

//Java_1_7_src10

static{

//딱 한번만 수행되는 코드

}

이 static 블록은 객체가 생성되기 전에 한 번만 호출되고 그 이후에는 호출하려고 해도 호출 할 수가 없다

클래스 내에 선언되어 있어야하며 메소드 내에서는 선언할 수가 없다

인스턴스 변수나 클래스 변수와 같이 어떤 메소드나 생성자에 속해 있으면 안된다

static 블록은 클래스를 초기화할 떄 꼭 수행되어야 하는 작업이 있을 경우 유용하게 사용될 수 있다

static 블록은 생성자가 불리지 않더라도 클래스에 대한 참조가 발생하자마자 호출된다

static 블록에서는 static변수와 staitc메소드만 사용가능하다

pass by value, pass by reference

pass by value 라는 말은 "값을 전달한다"는 말이다 더 정확히 말하자면

"값만 전달한다"는 말이다

"pass by value"는 복제술처럼 메소드의 매개 변수로 넘길 때에는 원래 값은 놔두고

전달되는 값이 진짜인 것처럼 보이게 한다

그래서 매개 변수를 받은 메소드에서 그 값을 어떻게 지지고 볶던 간에 원래의 값은 변하지 않는다

기본 자료형은 "무조건 pass by value로 데이터를 전달한다"

그리고 참조 자료형은 "pass by reference"로 데이터를 전달한다

pass by value는 값을 전달하는 작업이고, 호출되기 전과 후에 데이터가 변경되지 않는다

pass by reference로 값이 전달되면, 호출한 메소드의 데이터에도 영향이 있다

만약 매개 변수로 받은 참조 자료형 안에 있는 객체를 여러분들이 변경하면 호출한 참조 자료형 안에 있는 객체는 호출된 메소드에서 변경한 대로 데이터가 바뀐다

이와 같이 value(값)이 아니라 객체에 대한 reference(참조)가 넘어가는 것을 "pass by reference"라고 한다

메소드의 매개 변수로 참조 자료형을 넘길 경우에는 메소드 안에서 객체의 상태가 변경한 결과에 영향을 받게 된다

메소드의 매개 변수가 객체가 되면 매소드의 로직대로 객체가 직접적인 영향을 받는다

다시 한번 정리해 보자

  • 모든 기본 자료형은 pass by value이다
  • 참조 자료형은 값이 아닌 참조가 전달되는 pass by reference이다

pass by value와 pass by reference에 대한 중요성은 아무리 강조해도 지나치지 않다

그러므로 이 내용이 이해되지 않으면 이해하도록 노력해 주기 바란다 그래도 이해가 안 되면 외워야 한다

마지막으로

매개 변수를 지정하는 특이한 방법에 대해서 알아보자

지금까지는 매개 변수의 개수가 정확히 정해져 있는 경우만 알아보았다

하지만, 매개 변수가 몇 개가 될지, 호출할 때마다 바뀌는 경우에는 어떻게 해야 할까?

가장 쉬운 방법은 다음과 같이 배열을 넘겨주는 방법일 것이다

//Java_1_7_src11

public class MethodVarargs{

public static void main(String[]args){

MethodVarargs varargs = new MethodVarargs();

varargs.calculateNumbersWithArray(new int[]{1, 2, 3, 4, 5});

}

public void calculateNumbersWithArray(int []numbers){

}

}

하지만 이렇게 배열을 사용해도 되지만, 매개 변수로 넘겨 줄 때 계산할 숫자들을 모두 배열로 만든 후 넘겨주어야 한다는 단점이 존재한다

그래서 자바에서는 임의 개수의 매개 변수를 넘겨줄 수 있는 방법을 제공한다

//Java_1_7_src12

public class MethodVarargs{

public static void main(String[]args){

  MethodVarargs varargs = new MethodVarargs();

  varargs.calculateNumbers(1, 2, 3, 4, 5);

}

public void calculateNumbers(int...numbers){

}

}

이와 같이 "타입...변수명"으로 선언해 주면, numbers는 배열로 인식하고 그 값은 배열을위한for문으로 처리할 수 있다

ex)

for(int number : numbers){

total += number;

}

여기서 중요한 것은 ... 을 적어줄 때에 점을 연속해서 적어줘야지, 점 사이에 공백이 있으면 안된다

이렇게 사용하면 numbers[]라는 배열을 받은 것과 다름없이 사용할 수 있다

그럼 뭐 배열 보내는 거랑 뭐가 달라?라고 생각할 수도 있을 것이다

하지만 배열을 사용하는 것과는 호출하는 방법이 다른 것을 볼 수 있다

그런데 한가지 명심해야 할 것이 있다, 이 방법을 사용할 떄에는

하나의 메소드에서는 한번만 사용 가능하고 여러 매개 변수가 있다면 가장 마지막에 선언해야만 한다

오늘은 참조 자료형의 중심을 이루는 생성자와 메소드에 대해서 알아봤다

이정도만 알았어도 오늘 하루는 유익한 하루이다

 

'Java_1' 카테고리의 다른 글

Java_1_9 ( 상속 )  (0) 2020.01.10
Java_1_8 ( 패키지와 접근 제어자 )  (0) 2020.01.09
Java_1_6 ( 배열 )  (0) 2019.12.09
Java_1_5 ( 조건처리문 )  (0) 2019.12.04
Java_1_4 ( 연산자 )  (0) 2019.12.04

댓글