참조자료형
기본생성자
자바는 생성자를 만들지 않아도 자동으로 만들어지는 기본 생성자가 있다.
main() 메소드에서 실습 클래스의 이름으로 객체를 생성한 것이 기본 생성자다.
public class ReferenceDefault{
public static void main(String[] args){
ReferenceDefault reference = new ReferenceDefault();
}
}
main() 메소드를 보면 ReferenceDefault 클래스의 인스턴스인 reference를 만들었다.
ReferenceDefault()
라는 생성자는 다른 생성자가 없을 경우 기본으로 컴파일할 때 만들어진다.
➡️ String을 매개 변수로 받는 ReferenceString 클래스
package main.Chapter08;
public class ReferenceString {
public ReferenceString(String arg){
}
public static void main(String[] args) {
ReferenceString reference = new ReferenceString();
}
생성자 형태
생성자는 메소드와 비슷하게 생겼다.
하지만 …
- 생성자는 리턴 타입이 없다
- 생성자는 클래스 이름과 같다.
위의 코드를 실행해보면 Error가 발생한다.
$ Javac ReferenceString.java
ReferenceString.java:8: error: constructor ReferenceString in class ReferenceString cannot be applied to given types;
ReferenceString reference = new ReferenceString();
^
required: String
found: no arguments
reason: actual and formal argument lists differ in length
actual and formal argument lists differ in length
컴파일 오류 발생
Error 왜 나오는 걸까 ?
기본생성자 → 아무런 매개 변수가 없는 ReferenceString()
이라는 생성자가 다른 생성자가 없을 경우 기본으로 컴파일할 때 만들어진다.
※주의 기본 생성자는 다른 생성자가 있으면 자동으로 만들어지지 않는다.
➡️매개변수가 없는 생성자를 사용하고 싶으면 어떻게 해야할까? 아래와 같은 코드로 하면된다.
public class ReferenceString{
public ReferenceString(){}
public ReferenceString(String arg){}
public static void main(String args[]){
ReferenceString reference = new ReferenceString();
}
}
Java에서 생성자는 왜 필요할까 ?
Java의 생성자는 자바 클래스의 객체(또는 인스턴스)를 생성하기 위해서 존재한다.
생성자에 리턴 타입이 없는 이유 : 생성자의 리턴 타입은 클래스의 객체이기 때문이며, 클래스와 이름이 동일해야 컴파일러가 알 수 있다.
**💡참고
생성자를 작성할 때에는 클래스의 다른 메소드들 보다 위에 가장 윗부분에 선언하는 것이 좋다.**
생성자의 위치와 순서
- 인스턴스 변수들을 선언 후 → 생성자 → 필요한 메소드(메소드 영역)들 순으로 위치 시킨다.
생성자는 몇 개까지 만들 수 있을까?
앞 서 본 생성자는 두 개 였다. 그러면 여러 개를 만들 수 있다는 것 같다. 자세히 알아보자.
결론 : 생성자를 여러개 만들 수 있다.
이유: 클래스의 객체를 보다 간편하게 만들기 위해서 여러 가지 매개 변수를 갖는 여러 생성자를 가질 수 있다.
예제를 통해서 간판하게 만들기 위해서라는 말을 실천해보자.
Java 패턴 중 DTO(Data Transfer Object)라는 것이 있다.
어떤 속성을 갖는 클래스를 만들고, 그 속성들을 쉽게 전달하기 위해서 DTO라는 것을 만든다. (비슷한 것 : VO(Value Object)가 있다. VO는 데이터를 담아 두기 위한 목적으로 사용됨)
- DTO의 목적은 데이터를 다른 서버로 전달하기 위한 것이 주 목적이다.
DTO가 VO를 포함한다고도 볼 수 있기 때문에 대부분 DTO라는 명칭을 선호한다.
- 회원의 개인정보를 담는 DTO클래스 생성
- 이름, 전화번호, 이메일 주소 → 인스턴스 변수
public class MemberDTO{
public String name;
public String phone;
public String email;
}
DTO를 만들면 장점이 무엇일까 ?
자바의 메소드를 선언할 때 리턴 타입은 한 가지만 선언할 수 있다. 복합 적인 데이터를 리턴하려면 String[]과 같이 배열을 리턴해도 되겠지만, int 타입까지 포함되어 있다면 힘들어진다.
따라서 DTO를 만들어 놓으면 메소드의 리턴 타입에 MemberDTO로 선언하고, 그 객체를 리턴해 주면 된다.
public MemberDTO getMemberDTO(){
MemberDTO dto = new MemberDTO();
return dto;
}
이 클래스의 객체를 생성할 때 그 사람의 아무 정보도 모를 때도 있고, 이름만 알 때도 있고, 이름과 전화번호만 알 때도 있고, 모든 정보를 알고 있을 떄도 있다. 이러한 상황에 따른 생성자를 MemberDTO에 추가하면 다음과 같이 구현이 가능하다.
public class MemberDTO {
public String name;
public String phone;
public String email;
public MemberDTO(){
//아무 정보도 모를 때
}
public MemberDTO(String name) {
//이름만 알고 있을 때
this.name = name;
}
public MemberDTO(String name, String phone) {
//이름과 전화번호만 알고 있을 때
this.name = name;
this.phone = phone;
}
public MemberDTO(String name, String phone, String email) {
//모든 정보를 알고 있을 때
this.name = name;
this.phone = phone;
this.email = email;
}
}
각 주석은 생성자가 언제 사용되는 것인지 알수 있다.
자바의 생성자는 매개 변수 개수의 제한은 없다. 하지만 너무 많으면 관리가 힘들어진다. 꼭 필요에 맞는 생성자만 만드는 습관을 들여야만 한다.
public class ReferenceConstructor {
public static void main(String[] args) {
ReferenceConstructor reference = new ReferenceConstructor();
reference.makeMemberObject();
}
public void makeMemberObject(){
MemberDTO dto1 = new MemberDTO();
MemberDTO dto2 = new MemberDTO("Sangmin");
MemberDTO dto3 = new MemberDTO("Sangmin", "010XXXXYYYY");
MemberDTO dto4 = new MemberDTO("Sangmin", "010xxxxxxxx", "eijfe@dkowo.com");
}
}
객체의 변수와 매개 변수를 구분하기 위한 this
- this 예약어는 무엇일까 ?
위치 : this 예약어는 생성자와 메소드 안에서 사용할 수 있다.
public class MemberDTO {
public String name ;
public String phone;
public String email;
public MemberDTO(String name){
this.name = name;
//인스턴스 변수 매개 변수
}
}
- 아래와 같이
매개 변수
와인스턴스 변수
가 같을 경우 컴파일러는 생성자 안에서 사용하는 변수이기 때문에 중괄호 안에 있는 name은 모두 매개 변수로 넘겨준 name이라고 생각한다.
public class MemberDTO{
public String name;
public String phone;
public String email;
public MemberDTO(String name){
name = name;
}
}
- 혼동을 가장 쉽게 피하는 방법 : 매개 변수와 인스턴스 변수의 이름 다르게 선언하는 것.
public class MemberDTO{
public String name;
public String phone;
public String email;
public MemberDTO(String paramName){
name = paramName;
}
}
- 가장 좋은 방법 : this라는 예약어 사용하기
- this는 메소드를 지정할 수 있다.
public class MemberDTO{
public String name;
public String phone;
public String email;
public MemberDTO(String name){
this.name = name;
}
}
메소드 overloading
public class ReferenceOverloading {
public static void main(String[] args) {
ReferenceOverloading reference = new ReferenceOverloading();
}
public void print(int data) {
}
public void print(String data) {
}
public void print(int intData, String stringData) {
}
public void print(String stringData, int intData) {
}
}
- 첫 번째 print( ) 메소드는 int를 매개 변수로하고 있다.
- 두 번째 print( ) 메소드는 String 을 매개 변수로 하고 있다.
- 세 번째 메소드와 네 번째 메소드를 보면 각 매개 변수들의 이름은 같지만, 세 번째 메소드 int, String 순서이고 네 번째 메소드는 String, int 순이다.
➡️ 중요한 것은 매개 변수의 타입이다.
- 타입이 다르면 다른 메소드로 생각하지만 , 타입이 같고 변수 이름이 같으면 같은 메소드로 인식한다.
※오버로딩(Overloading) 메소드의 이름을 같도록하고, 매개 변수만을 다르게 하는 것을 말한다.
왜 ? 오버로딩이라는 기능을 제공할까 ??
System.out.println( )에서 println()
메소드를 확인해보자.
위 그림 코드를 보듯이 오버로딩이 되어 있다. 만약 오버로딩이 없다면?
System.out.printlnInt(),System.out.printlnLong() ..등 이렇게 사용한다면 정말 힘들듯하다. 그래서 그 힘든 점을 극복하기 위해서 오버로딩 기능을 제공하는 것
이다.
메소드에서 값 넘겨주기
메소드의 수행
과 종료
에 대해서
메소드가 종료되는 조건
- 메소드의 모든 문장이 실행되었을 때
- return 문장에 도달했을 때
- 예외가 발생(throw)했을 때
- void타입
void : 이 메소드는 아무것도 돌려주지 않는다. 모든 문장이 수행되면 메소드가 종료된다.
자바에서는 모든 타입을 한 개만 리턴 타입으로 넘겨줄 수 있음.
모든 기본 자료형과 참조 자료형 중 하나를 리턴할 수 있다.
public class ReferenceReturn {
public static void main(String[] args) {
ReferenceReturn reference = new ReferenceReturn();
System.out.println(reference.intReturn());
System.out.println(reference.intArrayReturn());
System.out.println(reference.stringReturn());
}
private int intReturn() {
int returnInt = 0;
return returnInt;
}
private int[] intArrayReturn() {
int returnArray[] = new int[10];
return returnArray;
}
private String stringReturn() {
String returnString = "Return value";
return returnString;
}
}
static 메소드와 일반 메소드 차이
static예약어를 쓰면 객체를 생성하지 않아도 메소드를 호출할 수 있다.
public class ReferenceStatic {
String name = "Dante";
public static void main(String[] args) {
ReferenceStatic.staticMethod();
}
public static void staticMethod(){
System.out.println("This is a staticMethod.");
}
}
static 메소드
는 클래스 변수
만 사용할 수 있다는 단점
public class ReferenceStatic {
String name = "Dante";
public static void main(String[] args) {
ReferenceStatic.staticMethodCallVariable();
}
public static void staticMethodCallVariable(){
System.out.println(name);
}
}
Error : Non-static field ‘name’ cannot be referenced from a static context
static이 아닌 변수 이름은 static context에서 참조할 수 없다. 라는 뜻
이것을 해결하기 위한 두 가지 방법
- staticMethodCallVariable()의 static 을 빼는 방법. 빼놓고 ReferenceStatic의 객체를 생성한 후 호출
- String name 앞에 static을 넣어주는 방법. static을 넣어 주면서 인스턴습 변수에서 클래스 변수로 바뀐다.
public static String name; // 가능
static String name; // 가능
- 2번 째 방법에서 주의해야할 점
public class ReferenceStaticVariable {
static String name; // 클래스 변수
public ReferenceStaticVariable() {
}
public ReferenceStaticVariable(String name) {
this.name = name;
}
public static void main(String[] args) {
ReferenceStaticVariable reference = new ReferenceStaticVariable();
reference.checkName();
}
public void checkName() {
ReferenceStaticVariable reference1 = new ReferenceStaticVariable("a");
System.out.println(reference1.name);
ReferenceStaticVariable reference2 = new ReferenceStaticVariable("b");
System.out.println(reference1.name);
}
}
결과 :
a
b
위에 reference1에 있는 name은 “a”
이다. reference2에 있는 name은 “b”
이다.
그냥 예상하기에는 reference의 name만 두 번 출력했으니깐 “a”
가 두 번 나와야한다.
하지만.. 결과는 a, b ??
이유는 static 예약어에 있다. ReferenceStaticVariable
의 name 변수가 인스턴스 변수
가 아닌 static이 붙었기 때문에 클래스 변수
이다. 그 이유 때문이다.
ReferenceStaticVariable
의 name변수에 static을빼서 실행해보면 아래와 같이 나온다.
결과 :
a
a
Pass by Value, Pass by Reference
출처 : https://blog.penjee.com/passing-by-value-vs-by-reference-java-graphical/
Pass by Value
:값을 전달한다
.정확히 말하면 값만 전달한다.
- 매개변수로 넘길 때에는 원래 값은 놔두고, 전달되는 값이 진짜인 것처럼 보이게 한다.
- 따라서 매개 변수를 받은 메소드에서 그 값을 어떻게 바꾸든 원래의 값은 변화지 않는다.
- 기본 자료형은 무조건 Pass by Value로 데이터를 전달한다.
public class ReferencePass {
public static void main(String[] args) {
ReferencePass reference = new ReferencePass();
reference.callPassByValue();
}
private void callPassByValue() {
int a = 10;
String b = "b"; // 원래 이렇게 표현-> b= new String("z");
System.out.println("before passByValue");
System.out.println("a = " + a); // 10
System.out.println("b = " + b); // b
passByValue(a, b); // a: 20 , b : "z "
System.out.println("after passByValue");
System.out.println("a = " + a); // 10
System.out.println("b = " + b); // "b"
}
public void passByValue(int a, String b) {
a = 20;
b = "z ";
System.out.println("in passByValue");
System.out.println("a = " + a);
System.out.println("b = " + b);
}
}
결과 :
before passByValue
a = 10
b = b
in passByValue
a = 20
b = z
after passByValue
a = 10
b = b
➡️의문점
a는 기본형이기떄문에 값이 변경되지 않았다. 그런데 b는 기본 자료형이 아닌 String(참조형)인데 값이 변경되지 않았다.
b = "z";
String은 이렇게 따옴표로 값을 할당하면 new를 사용하여 객체를 생성한것과 같다
b = new String("z");
String이 아닌 다른 참조 자료형들도 이처럼 호출된 메소드에서 다른 객체로 대체하여 처리하면 기존 값은 바뀌지 않는다.
매개 변수로 받은 참조 자료형 안에 있는 객체를 변경하면 호출한 참조 자료형 안에 있는 객체는 호출된 메소드에서 변경한 대로 데이터가 바뀐다.
Pass by Reference
: 매개변수로 값이 전달되면, 호출한 메소드의데이터에도 영향
이 있다.
public class ReferencePass {
public static void main(String[] args) {
ReferencePass reference = new ReferencePass();
reference.callPassByReference();
}
private void callPassByReference() {
MemberDTO member = new MemberDTO("Dante");
System.out.println("----before passByReference----");
System.out.println("member.name =" + member.name); //Dante
passByReference(member); //Grinch
System.out.println("----after passByReference----");
System.out.println("member.name = " + member.name); // Grinch
}
private void passByReference(MemberDTO member) {
member.name = "Grinch";
System.out.println("----in passByReference----");
System.out.println("member.name = " + member.name); // Grinch
}
}
결과 :
----before passByReference----
member.name =Dante
----in passByReference----
member.name = Grinch
----after passByReference----
member.name = Grinch
PassByReference를 호출하기 전에는 member.name 값이 “Dante”였다. 하지만 passByReference() 메소드 안에서 값이 “Grinch”로 변경되었고 다시 호출했을 때 값이 “Dante”에서 “Grinch”로 변경되었다는 것을 알 수 있다.
매개 변수를 지정하는 특이한 방법
➡️매개 변수가 몇 개가 될지, 호출할 때마다 바뀌는 경우에는 어떻게 해야할까?
방법 1. 가장 쉬운 방법은 배열을 넘겨주는 방법
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에서는 임의 개수의 매개 변수(Arbitrary Number of Arguments)
를 넘겨줄 수 있는 방법을 제공한다.
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) {}
public void calculateNumbers(int...numbers){
//배열이 아닌 int 값들을 받을 수 있는 calculateNumbers()메소드
}
}
“타입…변수명”
선언해주면 된다. 그러면 numbers는 배열로 인식한다.
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) {}
public void calculateNumbers(int...numbers){
int total = 0;
for(int number : numbers){
total+=number;
}
System.out.println("Total =" + total);
}
}
위의 예제를 보고 “배열 보내는 것이랑 무엇이 다를까 ?” 라는 의문이 들 수 있다.
배열 선언 보내는것
vs“타입…변수명”
- 배열을 사용하는 것과는 호출하는 방법이 다르다.
varargs.calculateNumbers(1);
varargs.calculateNumbers(1,2);
varargs.calculateNumbers(1,2,3);
varargs.calculateNumbers(1,2,3,4);
varargs.calculateNumbers(1,2,3,4,5);
이와같이 매개 변수 수를 정하기 애매한 경우 타입과 변수명 사이에 점 세 개를 연달아 입력해주면 된다.
※주의 : 하나의 메소드에서는 한번만 사용가능하다. 또한 여러 매개 변수가 있다면, 가장 마지막에 선언해야만 한다는 것이다.
- 올바른 사용 예)
public void arbitrary(String message, int...numbers){
}
- 틀린 예) 아래와 같이하면 컴파일 에러가 난다.
- 컴파일 에러가 나는 이유 :
int…numbers
선언 뒤에는 메소드의 선언을 닫는소괄호 )
가 와야하기때문에 컴파일 에러가 난다.
- 컴파일 에러가 나는 이유 :
public void arbitray(int...numbers, String message){
}
➡️이런 제약이 있는 메소드 선언은 어디서 사용할까 ?
- 화면에 결과를 출력하는 메소드
System.out.printf( )
라는 메소드에서 사용된다.
printf(String format, Object...args)
처음 출력하는 포맷(format)을 선언하고, 그 뒤에는 Object라는 타입의 args라는 것을 임의의 개수만큼 받는다.
MemberDTO dto = new MemberDTO("Dante", "01034221111", "ekfe@naver.com");
System.out.printf("Name:%s phone:%s E-Mail:%s \n", dto.name, dto.phone, dto.email);
- 출력
Name:Dante Phone:01034221111 E-Mail:ekfe@naver.com
위와같이 활용할 때 쓰인다.
끝…