🟨 목 차 🟨
1. 참조 자료형이란?
2. 생성자란 ?
2-1. 생성자는 몇 개를 만들어 된다.
2-2. 객체의 변수와 매개 변수를 구분하기 위한 this
2-3. 메소드 overloading
2-4. 메소드에서 값 넘겨주기
3. static 메소드와 일반 메소드와 차이
4 . pass by value, pass by reference
5 . 매개 변수를 지정하는 특이한 방법
◼️ 1. 참조 자료형이란? ◼️
자바의 타입은 기본 자료형과 참조 자료형이 있다.
기본 자료형은 int, byte , char, short, boolean, long, float, double을 제외한 나머지 타입은 모두 참조 자료형이다.
기본자료형과 참조자료형의 가장 큰 차이는 new를 사용해서 객체를 생성하는지 여부의 차이이다.
[ new 없이도 객체를 생성할 수 있는 참조 자료형은 오직 String뿐이다. ]
- 각종 연산자들은 대부분 기본 자료형을 위해서 존재하는 것이다. 그 중에서 +는 참조 자료형 중에서 참조 자료형 중에서 String 클래스만 사용.
- 참조 자료형이 사용할 수 있는 연산자는 오직 하나.
◼️ 2. 생성자란 ? ◼️
참조 자료형은 new를 사용하여 객체를 사용한다고 했는데, new뒤에 나오는 것이 바로 생성자다.
==> 생성자란 인스턴스가 생성될 때 호출되는 인스턴스 초기화 메서드 이다.
따라서 인스턴스 변수의 초기화 작업에 주로 사용, 인스턴스 생성 시에 실행되어야 할 작업을 위해서 생성된다.
생성자는 리턴 타입이 없고, 메소드 이름 대신 클래스 이름과 동일하게 이름을 지정한다.
==> 생성자에 리턴 타입이 없는 이유는 생성자의 리턴 타입은 클래스의 객체이기 때문이며, 클래스와 이름이 동일해야 컴파일러가 알아차릴 수 있기 때문
생성자를 넣는 위치는 암묵적으로 인스턴스 변수들을 선언한 후에 생성자를 위치 시키는게 좋다.(암묵적인 약속)
==> 생성자를 찾기 위한 시간을 허비하지 않기 위해
◼️ 2-1. 생성자는 몇 개를 만들어 된다.
생성자는 몇 개를 만들어도 상관이 없다. 아래의 예시를 참고하자
[EX]
package Part8;
public class MemberDTO {
public String name;
public String phone;
public String email;
public MemberDTO() {
// TODO Auto-generated constructor stub
}
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;
}
}
위에서 만든 생성자로 객체를 생성해보자
[EX]
package Part8;
public class ReferenceConstructor {
public static void main(String[] args) {
ReferenceConstructor exam = new ReferenceConstructor();
exam.makeMemberObject();
}
public void makeMemberObject() {
MemberDTO dto1 = new MemberDTO();
MemberDTO dto2 = new MemberDTO("Sangmin");
MemberDTO dto3 = new MemberDTO("Sangmin", "010xxxxxxxx");
MemberDTO dto4 = new MemberDTO("Sangmin", "010xxxxxxxx","god@naver.com");
}
}
◼️ 2-2. 객체의 변수와 매개 변수를 구분하기 위한 this
public MemberDTO(String name, String phone, String email) {
//이름과 전화번호, 이메일 알 때
this.name = name;
this.phone = phone;
this.email = email;
}
위의 코드를 보게되면 생성자에서 받은 매개변수와 그 객체의 변수와 부분 할 수 있는 부분이 없다.
==> 그래서 객체의 변수 부분을 지정하기 위해선 this 라는 예약어를 사용하여 객체의 변수를 지정한다.
◼️ 2-3. 메소드 overloading
앞에서 본 클래스의 생성자는 매개 변수들을 서로 다른게 여러개 선언 할 수 있었다.
그렇다면 메소드도 이렇게 이름은 같게하고 매개 변수들을 다르게 하여 만들 수 있을까?
==> 당연히 가능하다. 즉, 매개변수 개수가 같아도 타입의 순서가 다르면 다른 메소드처럼 인식된다.
중요한 것은 매개변수의 이름이 아니라 매개 변수의 타입이다. 타입이 다르면 다른 메소드로 생각하지만, 타입이 같고 변수의 이름이 같고 매개변수의 갯수도 같으면 같은 메소드로 인식한다.
위의 지식을 먼저 알고 overload 를 알아보자.
overload란 사전적 의미를 통해 Java로 해석하면 하나의 메소드 이름을 사용하면서 여러 기능을 제공한다는 의미이다.
즉, 쉽게 예시로 말해보겠다.
System.out.println()이라는 메소드는 int long string 등등 다양한 매개변수를 받을 수 있다.
이 방법이 아니라 일일이 하나하나 타입을 지정해주는 메소드를 이용한다면
System.out.printlnInt() / System.out.printlnLong() 메소드 같이 일일히 지정해줘야 하는 번거로움이 있기 때문에 overload를 사용한다.
즉, 다시 말해서 오버로딩은 "같은 역할을 하는 메소드는 같은 메소드 이름을 가져야 한다."는 모토로 사용하는 것이라고 기억하면 된다.
생성자도 매개 변수에 따라서 다르게 익식되므로 이것도 오버로드의 일종이라고 생각하면 더 기억하기 쉽다.
◼️ 2-4. 메소드에서 값 넘겨주기
메소드의 수행과 종료에 대해 알아보자.
자바에서 메소드가 종료되는 조건은 다음과 같다.
- 메소드의 모든 문장이 실행되었을 때
- return 문장에 도달 했을 때
- 예외가 발생(throw)했을 때(추후에 설명)
우리가 대부분의 메소드를 선언할 때 메소드 이름 앞에 "void"라고 많이 쓴다.
이 "void"는 자바에서 "이 메소드는 아무것도 돌려주지 않습니다."라는 의미로, 메소드의 모든 문장이 수행되면 메소드가 종료된다.
그렇다면 void 외에 다른 것은 어떤 것을 넘겨줄 수 있을까?
==> 자바에서는 모든 타입을 한 개만 retrun 타입으로 넘겨 줄 수 있다. 즉, 기본 자료형과 참조 자료형 중 하나를 리턴할 수 있다.
[EX]
package Part8;
public class ReferenceReturn {
public static void main(String[] args) {
}
public int intReturn() { //기본 자료형
int returnInt=0;
return returnInt;
}
public int[] intArrayReturn() { // 참조 자료형
int returnArray[] = new int[10];
return returnArray;
}
}
특정 타입을 지정해서 void 자리에 특정 타입을 넣고 return 타입을 주지 않으면 컴파일 오류가 나오니 주의하자
return 문장 뒤에 다른 계산 문장이 있으면 컴파일 오류가 난다.
if else를 통해 retrun값을 줄 수 있다.
[EX]
package Part8;
public class ifConditionIntReturn {
public int ifConditionIntRentunmethod(){
int returnInt=0;
if(returnInt==0) {
return returnInt;
}
else {
return returnInt;
}
}
}
만약 메소드에서 값을 넘겨 줄 때에는 return을 사용해주면 되지만, 리턴 타입이 void인 메소드에서 더 이상 실행하고 싶지 않을때 어떻게 할까?
==> break 같은 명령어를 쓰면 되지만, 사용하지 못한다. 아래의 코드는 리턴 타입이 void인 메소드에서 더 이상 실행 되지 않게 하는 코드이다.
[EX]
public void wantToStipInTheMiddle(boolean flag)
{
if(flag) return;
}
위의 코드를 보게되면 void 메소드에서 return을 사용한다고 의아해 할 수 있다. 위와 같이 return 뒤에 아무것도 없이 바로 세미콜론을 적어주면, "메소드를 종료해"라고 인식한다.
◼️ 3. static 메소드와 일반 메소드와 차이 ◼️
main() 메소드에서 자신의 클래스나 다른 클래스에 있는 메소드를 호출하려면 반드시 객체를 생성해왔다.
하지만 System.out.println()메소드는 왜 객체를 생성하지 않아도 될까?
==> 그 이유는 static 이라는 예약어 때문이다. 즉, static은 객체를 생성하지 않아도 메소드를 호출 할 수 있는 마법의 메소드다.
[EX]
package Part8;
public class ReferenceStatic {
public static void main(String[] args) {
ReferenceStatic.staticMethod();
}
public static void staticMethod() {
System.out.println("This is a staticMethodd.");
}
}
위의 코드처럼 static을 붙여서 메소드를 만들면 객체를 생성하지 않고 클래스이름.메소드를 이용하여 static 메소드를 사용할 수 있다.
static 메소드를 쓰면 객체를 생성하지 않고 쉽게 메소드를 사용할 수 있는데 왜 이방법을 사용하 지 않을까?
==> 왜냐하면 static 메소드는 클래스 변수만 사용할 수 있다는 단점이 있다.
[EX]
package Part8;
public class ReferenceStatic {
public static void main(String[] args) {
String name ="aa";
ReferenceStatic.staticMethod();
}
public static void staticMethod() {
System.out.println("This is a staticMethodd.");
}
public static void staticMethod2() {
System.out.println(name);
}
위의 코드를 실행하면 컴파일 에러가 난다. 이유는 static 메소드 안에 클래스 변수를 사용하지 않았기 때문이다.
==> 해결하는 방법은 static 메소드에서 static을 빼는 방법, name이라는 변수를 static으로 바꿔주는 것이다.
name이라는 변수를 static을 바꾸는 과정(클래스변수로 바꾸는 과정)에서 아무 생각 없이 인스턴스 변수에 static을 붙이면 에러가 나게된다.
==> 왜냐하면 클래스 변수가 되면, 모든 객체에서 하나의 값을 바라 보기 때문이다.
[EX]
package Part8;
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("Sangmin");
System.out.println(reference1.name);
ReferenceStaticVariable reference2 = new ReferenceStaticVariable("Sungchoon");
System.out.println(reference1.name);
}
}
Sangmin
Sungchoon
우의 코드를 보게되면 reference1에 있는 name은 "Sangmin"이고 reference2에 있는 name은 "Sungchoon"이다.
결과는 "Sangmin" "Sangmin" 두 번 출력이 될 것 같지만,
하지만 출력문의 결과는 "Sangmin" "Sungchoon" 이 출력하게 된다.
왜냐하면 name이라는 변수가 인스턴스 변수가 아닌 static으로 선언한 클래스 변수이기 때문에 name의 값을 초기화 시키면 어디서든 마지막 name의 값이 출력되기 때문이다.
◼️ 3-1. static 블록
객체는 여러 개를 생성하지만, 한 번만 호출되어야 하는 코드가 있다면 "static 블록"을 사용하면 된다.
[EX]
static {
// 딱 한번만 수행되는 코드
}
static 블록은 객체가 생성되기 전에 한 번만 호출되고, 그 이후에는 호출하려고 해도 호출 할 수가 없다.
클래스 내에 선언되어 있어야 하며, 메소드 내에서는 선언할 수 없다.
즉, 인스턴스 변수나 클래스 변수와 같이 어떤 메소드나 생성자에 속해 있으면 안된다.
예시를 살펴보자.
[EX]
package Part8;
public class StaticBlock {
static int data =1;
public StaticBlock() {
System.out.println("StaticBlccok Constructor.");
data= 5;
}
static {
System.out.println("****First static block****");
data =3;
}
static {
System.out.println("****Second static blcock****");
}
public static int getData() {
return data;
}
}
package Part8;
public class StaticBlcokCheck {
public static void main(String[] args) {
StaticBlcokCheck check = new StaticBlcokCheck();
check.makeStaticBlockObject();
}
public void makeStaticBlockObject() {
System.out.println("Creating block1");
StaticBlock blcok1 = new StaticBlock();
System.out.println("Created block1");
System.out.println("---------------");
System.out.println("Creating block2");
StaticBlock blcok2 = new StaticBlock();
System.out.println("Creating block2");
}
}
Creating block1
****First static block****
****Second static blcock****
StaticBlccok Constructor
Created block1
---------------
Creating block2
StaticBlccok Constructor.
Creating block2
StaticBlcok이라는 클래스에서 생성자와 static 블록을 형성하고 StaticBlcoKCheck클래스의 makeStaticBlockObject메소드에서 StaticBlcok클래스의 객체를 생성을 하고, StaticBlcoKCheck클래스의 main문에서 객체를 생성하여 makeStaticBlockObject메소드를 호출하는 예제이다.
여기서 결과를 보게 되면 makeStaticBlockObject 메소드에 StaticBlcok의 객체 2개를 생성했는데 생성자는 동일하게 나왔는데 static 블럭들은 한번씩만 호출이 되었다.
==> 이와 같이 static 블록은 클래스를 초기화 할 때 꼭 수행되어야 하는 작업이 있을 경우 유용하게 사용될 수 있다.
추가로 static 블록 안에서는 static 한 것(클래스 변수)들만 호출할 수 있다.
static 블록은 생성자가 불리지 않더라도 클래스에 대한 참조가 발생하자마자 호출 된다.(예를 들어 sout문이 먼저 있고 static 블럭을 포함하는 객체를 생성하는 클래스가 뒤에 있더라도 static 블럭안에 있는 코드들이 먼저 출력이 된다.)
◼️ 4 . pass by value, pass by reference ◼️
아주 복잡하고, 여롭고, 중요한 이야기다.
Pass By Value라는 말의 뜻은 "값만 전달한다"는 뜻이다.
즉, 메소드의 매개변수로 넘길 때에는 원래 값은 놔두고, 전달되는 값이 진짜인 것처럼 보이게 한다.
==> 그렇기에 매개변수를 받은 메소드에서 그 값을 어떻게 하던간에 원래의 값은 변하지 않는다.
- 기본 자료형은 무조건 "Pass by Value"로 데이터를 전달한다.
- 참조 자료형은 "Pass by Reference"로 데이터를 전달한다.
[EX] 기본자료형
package Part8;
public class ReferencePass {
public static void main(String[] args) {
ReferencePass reference = new ReferencePass();
reference.callPassByValue();
}
public void callPassByValue() {
int a=10;
String b= "b";
System.out.println("before passByValue");
System.out.println("a="+a);
System.out.println("b="+b);
passByValue(a, b);
System.out.println("after passByValue");
System.out.println("a="+a);
System.out.println("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
위의 결과를 되면 main문에서 passByValue(a,b)를 호출했을때 값이 매개변수로 넣은 a,b값이 아니라 passByValue에 있는 a,b의 값이 출력이 된다. 후에 다시 a,b의 값을 찍게 되면 인스턴스 변수의 값이 찍히게 된다.(passByValue(a,b) 때문에 기존 값이 변경되지 않음)
[EX] 참조자료형
package Part8;
public class ReferencePass {
public static void main(String[] args) {
ReferencePass reference = new ReferencePass();
// reference.callPassByValue();
reference.callpassByreference();
}
public void callPassByValue() {
int a=10;
String b= "b";
System.out.println("before passByValue");
System.out.println("a="+a);
System.out.println("b="+b);
passByValue(a, b);
System.out.println("after passByValue");
System.out.println("a="+a);
System.out.println("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);
}
public void callpassByreference() {
MemberDTO member = new MemberDTO("Sangmin");
System.out.println("before passByReference");
System.out.println("member.name="+member.name);
passByReference(member);
System.out.println("after passByReference");
System.out.println("member.name="+member.name);
}
public void passByReference(MemberDTO member) {
member.name ="SungChoon";
System.out.println("in passByReference");
System.out.println("member.name"+member.name);
}
}
before passByReference
member.name=Sangmin
in passByReference
member.nameSungChoon
after passByReference
member.name=SungChoon
위의 코드는 main()문에서 callpassByreference() 메소드를 실행하는 코드이다.
코드를 보게되면 callpassByreference() 메소드는 MemberDTO 클래스에서 member객체를 생성을 하는데 name이라는 변수에 "Sangmin"이라는 값을 초기화 시켜준다. 그 후 passByReference()메소드를 실행시킨다. 여기서 passByReference()메소드는 매개변수로 MemberDTO의 객체를 받고 매개변수의 객체의 name 변수를 "SungChoon"를 바꾸고 이 객체의 변수의 이름을 출력하는 출력문이 들어있다. 그리고 다시 callpassByreference() 메소드로 돌아와서 member 객체의 이름을 찍는것이다.
즉, 결과를 말하자면 매개변수로 받은 참조 자료형 안에 있는 객체를 변경하면, 호출된 참조 자료형 안에 있는 객체는 호출된 메소드에서 변경한대로 데이터가 바뀐다는 것이다. 이와 같이 값이 아니라 객체에 대한 참조가 넘어가는 것을 "Pass by Reference"라고 한다.
즉 간단하게 말을 한다면, 매개변수로 참조 자료형을 넘길 경우에는 메소드 안에서 객체의 상태를 변경한 결과에 영향를 받는다.
[ 메모리 관점에서 설명을 해보면,
callpasByreference()메소드에서 처음에 new를 통해 MemberDTO클래스의 객체를 생성하면 heap영역에 MemberDTO 클래스의 객체의 값과 String name = "Sangmin"의 값과 공간이 할당되어 있고 stack영역에는 "member"로 레퍼런스 변수로 heap영역에 생성된 MemberDTO 클래스의 객체의 값과 String name = "Sangmin"의 값과 공간이 할당된 곳을 참조(가르킨다)한다.
그 후 passbyValue() 메소드를 실행시키는데 매개변수로 member 레퍼런스 변수를 넣어주었고, member변수에 있는 인자 그대로 stack영역에서의 passbyValue() member 레퍼런스 변수를 새로 할당한다.
하지만 새로 할당된 member래퍼런스 변수가 heap영역의 실제값을 가르키는것은 callpassByreference()메소드에서의 member 레퍼런스가 가르키는 것과 동일한다.
또한 스코프에 의해 stack 영역에서callpasByreference()메소드에서의 meber 래퍼런스 변수는 접근할 수 없게 된다.
그 후 callpassByreference()메소드에서의 member 레퍼런스가 가르키는 것과 동일한 passbyValue() member 레퍼런스 변수는 heap에 저장되어 있는 MeberDTO 클래스의 name을 바꾼다.( member.name = "SungChoon";)
그 후 passbyValue()가 종료되는데 passbyValue()의 member 레퍼런스가 pop 되서 사라지고 callpassByreference()메소드에서의 member 레퍼런스가 나타내게 되고 이것이 참조하는 heap영역의 값(name)을 찍으면 SungChoon이라고 찍힌다.
==> 왜냐하면 callpassByreference()메소드에서의 member 레퍼런스가 가르키는 것(참조하는것)과 passbyValue()메소드에서의 member 레퍼런스 변수가 가르키는 것(참조하는것)은 동일하기 때문에 passbyValue()메소드 내에서 참조하는 것이 바꼈다면 callpassByreference()메소드에서의 member 레퍼런스가 가르키는 것(참조하는것)도 바꼈기 때문이다.
위에 있는 메모리 관점을 그림으로 표현한다면,
1. callpassByreference()메소드에서 객체를 생성하여 Heap영역에 값(Sangmin)과 공간이 할당되는데 stack 영역에는 member 래퍼런스가 생성되고 Heap영역에 값(Sangmin)을 가르킨다(참조)
2. passByReference() 메소드가 시작이 되고 callpassByreference()에서 생성된 member 래퍼런스를 매개변수로 준다. 여기서 memeber 변수 인자 그대로 전해지게 되고 stack영역에 member라는 레퍼런스 변수가 새롭게 할당이 된다. 이 새롭게 stack영역에 member라는 래퍼런스 변수가 할당이 되었는데 이것이 heap 영역의 어떤 값을 가르키나면 매개변수로 물려받았던 callpassByreference()에서 생성된 member 래퍼런스가 가르키는 heap 영역의 값을 가르킨다. 즉, 동일한 객체를 참조하다는 말이다.
3. passByReference() 메소드내에서 member 래퍼런스가 가르키는 memberDTO 객체의 String name의 값을 "SungChoon"으로 변경한다.
4. passByreference() 메소드가 종료가 되고 passByReference() 메소드에 할당된 member 래퍼런스 변수는 pop이 되었고, 다시 callpassByreference() 메소드로 돌아오게 된다. 처음에 callpassByreference()에서 생성되었는 member 래퍼런스 변수에 접근이 가능해졌다. callpassByreference()에서 member 래퍼런스가 가르키는 객체의 값(String name)을 찍게 되면 "SungChoon"이 나오게 된다.
왜냐하면, PassByreference()에서의 member 래퍼런스변수와 callpassByreference()에서의 member 래퍼런스가 다른 stack 영역에 할당되었지만 참조하는 값이 같기 때문이다. 즉, Heap 영역에서 동일한 memberDTO객체를 가르키기 때문이다.
[정리] 매우 중요하다.
- 모든 기본 자료형은 Pass by Value이며, 간단히 말을 하면 메소드의 매개변수로 기본자료형을 넘길 경우에 메소드 안에서 같은 이름의 매개변수의 값을 변경한다고 치면 그 메소드내에서만 결과가 반영이 된다는것이다.
- 모든 참조 자료형은 Pass by Reference이며, 매개변수로 참조 자료혀을 넘길 경우에는 메소드 안에서 객체의 상태를 변경한 결과에 영향을 받는다.
◼️ 5 . 매개 변수를 지정하는 특이한 방법 ◼️
지금까지는 매개 변수의 개수가 정확히 정해져 있는 경우만 알아보았다. 하지만 매개 변수가 몇 개가 될 지, 호출할 때마다 바뀌는 경우는 어떻게 해야 할 까?
==> 배열을 넘겨주는 방법을 이용하면 쉽다.
[EX]
package Part8;
public class MethodVarargs {
public static void main(String[] args) {
MethodVarargs varargs = new MethodVarargs();
varargs.calculateNumbersWithArray(new int[] {1,3,4,5,6});
varargs.calculateNumbers(1,2,3,4,5);
}
public void calculateNumbersWithArray(int []number) {
}
public void calculateNumbers(int...numbers) {
}
}
calculateNumbersWithArray(int []number) 처럼 메소드를 만들게 되면 메소드를 사용할 때 매개 변수로 계산할 숫자들을 모두 배열로 만든 후 넘겨주어야 하는 단점이 존재한다.
그렇기에 자바에서는 임의 개수에 매개변수를 념겨줄 수 있는 방법을 제공한다.
==> 메소드를 생성할때 매개변수를 지정해줄때 "타입...변수명"으로 선언하게 되면 메소드를 사용할 때 매개변수로 배열로 안주어도 된다.(매우 편함)
[EX]
public void calculateNumbers(int...numbers) {
int total=0;
for(int number:numbers) {
total+=number;
}
System.out.println("total? : "+total);
}
total? : 1
total? : 3
total? : 6
total? : 10
total? : 15
"타입...변수명"을 사용할 때 주의 할 사항은 여러 매개 변수가 있다면, 가장 마지막에 선언해야만 한다.
1.
public void arbitrary(String message, int...numbers) {
}
2.
public void arbitrary(int...numbers,String message) {
}
1번 방법은 가능하지만 2번 방법은 컴파일이 되지 않는다.
'JAVA > 자바의신 1' 카테고리의 다른 글
10장 자바는 상속이라는 것이 있어요 (1) | 2022.09.07 |
---|---|
9장 자바를 배우면 패키지와 접근 제어자는 꼭 알아야 해요 (0) | 2022.09.07 |
7장 여러 데이터를 하나에 넣을 수는 없을까?(배열) (0) | 2022.09.03 |
6장 제가 조건을 좀 따져요(if ,for, while, continue, break) (0) | 2022.09.03 |
5장 계산을 하고 싶어요 (0) | 2022.09.03 |