[자바의 정석] ch6. 객체지향개념 (클래스, 객체, 메소드, 오버로딩, 생성자, 초기화)
1. 객체지향 언어
- 빠른 변화를 못쫓아가는 80년대 소프트웨어의 위기를 해결하기 위해 객체지향 언어 도입
- 절차적(C언어, Fortram등) → 객체지향
- 객체지향 언어의 특징
- 코드의 재사용성 (한번 만들면 다른데서도 쉽게 사용)
- 유지보수가 용이(소프트웨어가 변경되더라도 적은 노력으로 대응가능)
- 중복 코드 제거 - 객체지향 언어 = 프로그래밍 언어 + 객체지향개념(규칙) 추가
- 객체지향 언어의 핵심개념 (object-oriented programming)
1. 캡슐화
2. 상속
3. 추상화
4. 다형성
- 객체지향 공부방법 6장~7장 다형성까지 반복, 규칙 요약해서 외우기 (A4 2장)
자바의정석 3편 → jsp, spring 등 웹이나 모바일 실전(응용) → 객체지향 개념서
2. 클래스와 객체
2-1. 클래스와 객체
클래스의 정의 : 객체를 정의해 놓은 것
클래스의 용도 : 객체를 생성하는 데 사용
객체의 정의 : 실제로 존재하는 것. 사물 또는 개념
객체의 용도 : 객체가 가지고 있는 기능과 속성에 따라 다름
클래스 | 객체 |
제품 설계도 | 제품 |
TV 설계도 | TV |
2-2. 객체의 구성요소 - 속성과 기능
객체 = 속성 + 기능
( * 객체가 가지고 있는 속성과 기능을 그 객체의 멤버라고 한다. )
속성(property) = 멤버변수(member variable), 특성(attribute) , 필드(field), 상태(state)
기능(function) = 메서드(method), 함수(function), 행위(behaviod)
하드웨어 관찰, 분석 (속성, 기능 파악) | ➡️ | 소프트웨어로 구현 (변수, 메서드를 통해) |
HW (TV) | ➡️ | SW (TV클래스) |
속성 | ➡️ | 변수 |
(크기, 길이, 높이, 색상, 볼륨 등) | class Tv { | |
String color, | ||
boolean power; | ||
int channel; | ||
기능 | ➡️ | 메소드 |
(켜기, 볼륨높이기, 채널변경 등) | void power() { power = !power; } | |
void channelUp() { channel++; } | ||
void channelDown() { channel--; } |
* ' power = !power; '
if문의 사용 없이 power의 값을 항상 반대로 변경
( power()를 실행했을 때, power가 true인 상태였으면 false로 변경 )
2-3. 객체와 인스턴스
객체 : 모든 인스턴스를 대표하는 일반적 용어 (ex. "책상은 객체다" )
인스턴스 : 특정 클래스로부터 생성된 객체 (ex. "책상은 책상클래스의 인스턴스다" )
클래스 —————> 인스턴스 (객체)
(설계도) (인스턴스화) (제품)
Q. 클래스가 왜 필요한가?
A. 객체를 생성하기 위해 (클래스-설계도, 객체-제품)
Q. 객체가 왜 필요한가?
A. 객체(제품)를 사용하기 위해
Q. 객체를 사용한다는 것은?
A. 객체가 가진 속성(변수)과 기능(메서드)을 사용하는 것
2-4. 하나의 소스파일에 여러 클래스 작성
- 보통 하나의 소스파일에 하나의 클래스를 작성하는 것이 바람직하지만 , 여러 클래스 필요할 수도 있음
올바른 작성 예 | 설명 |
Hello2.java (파일) public class Hello2 { } (클래스1) class Hello3 { } (클래스2) |
public class가 있는 경우, 소스파일의 이름은 반드시 public class (메인메서드) 의 이름과 일치해야 한다. |
Hello2.java (파일) class Hello2 { } class Hello3 { } |
public class가 하나도 없는 경우, 소스파일의 이름은 ‘Hello2.java’, ‘Hello3.java’ 둘다 가능하다. |
잘못된 작성 예 | 설명 |
Hello2.java public class Hello2 { } public class Hello3 { } |
하나의 소스파일에 둘 이상의 public class가 존재하면 안 된다. 각 클래스를 별도의 소스파일에 나눠서 저장하든가 아니면 둘 중의 한 클래스에 public을 붙이지 않아야 한다. |
Hello3.java public class Hello2 { } class Hello3 { } |
소스파일의 이름이 public class의 이름과 일치하지 않는다. 소스파일의 이름을 ‘Hello2.java’로 변경해야 함. (우클릭 → Refactor → rename) |
hello2.java public class Hello2 { } public class Hello3 { } |
소스파일의 이름과 public class의 이름이 일치하지 않는다. 대소문자를 구분하므로 대소문자도 일치해야 함. 소스파일의 이름을 ‘Hello2.java’로 바꿔야 함. |
2-5. 객체의 생성과 사용
1) 객체의 생성
클래스명 변수명; // 클래스의 객체를 참조하기 위한 참조변수 선언(객체를 다루려면 참조변수 필요)
변수명 = new 클래스명 (); // 클래스의 객체 생성 후, 객체의 주소를 참조변수에 저장
Tv t; // Tv클래스 타입의 참조변수(리모컨) t를 선언
t = new Tv(); // Tv인스턴스를 생성한 후, 생성된 Tv인스턴스의 주소를 t에 저장
⇒ 한줄로 하면
Tv t = new Tv();
t : 객체를 다루려면 참조변수(리모컨) 필요
new Tv() : 객체 생성 후
‘=’ : 대입연산자로 참조변수(리모컨)과 객체 연결
2) 객체의 사용 ( 변수, 메소드 사용 )
t.channel = 7; // Tv인스턴스의 멤버변수 channel의 값을 7로 설정
(참조변수t가 가리키는 객체에 변수 channel에 7 저장)
t.channelDown(); // Tv인스턴스의 메서드 channelDown()을 호출 (메서드 사용)
1. 클래스 작성 (설계도)
2. 객체 생성 (제품)
3. 객체 사용 (제품)
객체 생성, 사용에는 반드시 참조변수(리모컨)가 있어야 함
참조변수(리모컨)의 타입 = 생성하려는 객체의 타입
(tv는 tv리모컨으로 해야하니까)
3) 객체의 생성과 사용 예제
class Ex6_2 {
public static void main(String args[]) {
Tv t1 = new Tv(); // Tv t1; t1 = new Tv();를 한 문장으로 가능
Tv t2 = new Tv();
System.out.println("t1의 channel값은 " + t1.channel + "입니다.");
System.out.println("t2의 channel값은 " + t2.channel + "입니다.");
t2=t1; //참조변수 t1의 값을 t2에 저장. 원래 가리키던 객체와 연결 끊어지고 t1과 연결됨
t1.channel = 7; // channel 값을 7으로 한다.
System.out.println("t1의 channel값을 7로 변경하였습니다.");
System.out.println("t1의 channel값은 " + t1.channel + "입니다.");
System.out.println("t2의 channel값은 " + t2.channel + "입니다.");
}
}
1. Tv t1 = new Tv();
Tv t2 = new Tv();
2. t2 = t1;
t1.channel = 7;
: t1이 저장하고 있는 주소값을 t2에 저장
t2의 주소값이 0x100으로 바뀌어 버려서, t2객체와 연결하고 있던 참조변수가 없어짐.
→ 주소값이 같은 t1하고 연결이 되어버림. t2 객체 사용불가
=> t1.channel = 7 출력
t2.channel = 7 출력 ( 주소값이 같으면, 여러개의 리모컨이 하나의 객체를 사용할 수 있음 )
2-6. 객체 배열
- 객체 배열 = 참조변수 배열 (참조변수들을 하나로 묶음)
Tv tv1, tv2, tv3; —————————> Tv[] tvArr = new Tv[3];
(Tv타입의 참조변수 tv1,2,3) (Tv타입 참조변수 *3 배열)
- Tv[] tvArr = new Tv[3]; // 길이가 3인 Tv타입의 참조변수 배열
* 객체의 배열은 객체를 담기 위한 거니까 배열 생성 후, 객체를 생성해서 배열의 각 참조변수 요소에 저장해야 함 !
= 객체 배열 생성 ≠ 객체 생성 !!!
tvArr[0] = new Tv(); //객체를 생성해서 참조변수 요소에 저장
tvArr[1] = new Tv();
tvArr[2] = new Tv();
이 세문장을 간단히 하면
Tv[] tvArr = { new Tv(), new Tv(), new Tv() } ;
Tv t1 = new Tv();
Tv t2 = new Tv();
Tv t3 = new Tv();
각 문장이 실행될 때, 참조변수와 TV 객체 그림
그러면 객체 배열을 만들고 Tv객체를 저장했을 때 어떻게 그려지냐면,
Tv[] tvArr = new Tv[3]; // Tv[3]: 객체배열(Tv타입 참조변수 3개), tvArr: 참조변수
tvArr[0] = new Tv();
tvArr[1] = new Tv();
tvArr[2] = new Tv();
tvArr이라는 참조변수 만들어짐
→ Tv타입 참조변수 3개가 만들어짐
→ 주소가 0x1000라고 하면, tvArr참조변수에도 1000이 저장되고 참조변수를 가리키게 됨
→ tvArr[0]~[2]인덱스가 생기고
→ tvArr[0]=new Tv(); 문장이 실행되면, new Tv() 라는 객체 만들고, 그 주소를 tvArr[0]에 넣음
→ tv객체가 생겨서 그 주소를 각 tvArr[]에 저장
이거 잘 보면
위위그림의 참조변수 3개를 하나의 배열로 만든 것
따라서 객체 배열 ==참조변수 배열
Tv[] tvArr = new Tv[3]; //참조변수 배열 생성
여기서 끝나면 안됨! 참조변수 배열(객체배열) 생성 후에는 객체를 생성해서 각각 하나씩 넣어줘야 함
객체 배열을 생성했다고 해서 객체가 생성되는 것이 아님!
tvArr[0] = new Tv();
tvArr[1] = new Tv();
tvArr[2] = new Tv();
2-7. 클래스의 정의
- 설계도
- 데이터 + 함수
- 사용자 정의 타입
1) 데이터와 함수의 결합
* 저장공간의 발전과정
- 변수 : 하나의 데이터를 저장할 수 있는 공간
- 배열 : 같은 종류의 여러 데이터를 하나로 저장할 수 있는 공간
- 구조체 : 서로 관련된 여러 데이터(종류 관계X)를 하나로 저장할 수 있는 공간
- 클래스 : 서로 관련된 데이터(변수)+함수(메서드, 명령문) 결합 (구조체+함수)
2) 사용자정의 타입 : 원하는 타입(클래스)을 직접 만듦
* 기본형 8개 타입 중에는 시간을 다루는 타입이 없어서 새로 만들면 됨
int hour; // 시간, 분, 초를 표현하기 위한 변수 선언
int minute;
int second;
int hour1, hour2, hour3 // 3개의 시간을 다루기 위해서는 이렇게 복잡하게 됨
int minute1, minute2, minute3
int second1, second2, second3
int[] hour = new int[3]; // 배열을 사용하면 묶이지만, 시간끼리,분끼리,초끼리 묶여서 프로그램 수행이 복잡
int[] minute = new int[3];
int[] second = new int[3];
이 문제를 해결하기 위해서 (코드 간결+시분초 하나로 묶기)
3개의 변수를 묶어서 Time 이라는 클래스 정의 (사용자 정의 타입)
class Time (
int hour;
int minute;
int second;
}
3개의 변수를 생성하는 대신, 하나의 객체 생성하는 것으로 대신할 수 있음
위의 첫번째 문단(변수선언)은 아래와 같이 대체 가능
Time t = new Time();
두번째 문단은 아래와 같이 대체
Time t1 = new Time(); //Time객체를 3개 생성
Time t2 = new Time();
Time t3 = new Time();
세번째 문단은 아래와 같이 대체
Time[] t = new Time[3]; //객체 배열로
t[0] = new Time();
t[1] = new Time();
t[2] = new Time();
그림으로 나타내면,
- 변수 3개 선언할 때: - 저장공간 3개
- 비객체지향 코드 - Time 객체 생성할 때: - 참조변수 t 생성
- 저장공간 3개
- 객체지향 코드
- 시분초가 강하게 묶여있음
3. 변수와 메소드
3-1. 선언위치에 따른 변수의 종류 (클래스변수, 인스턴스변수, 지역변수)
(지금까지는 변수의 타입에 대해서만 배웠고, 이제 변수의 종류)
class Variables -> 클래스 영역
{
① int iv ; // 인스턴스 변수 (instance)
② static int cv ; // 클래스 변수 (static변수, 공유변수)
void method() -> 메소드 영역
{
③ int lv = 0; // 지역 변수 (local variable)
}
}
1) 클래스변수 (cv) : - 클래스 영역 내
- static + iv
- 아무때나 사용 가능
- 객체 생성 x
2) 인스턴스변수 (iv) : - 클래스 영역 내 (클래스 영역의 기본)
- 인스턴스가 생성되었을 때 (= 객체생성시(객체 생성 필요)) 사용 가능
- 객체 생성 -> 클래스의 변수를 보고 -> iv 생성
=> 객체 = iv를 묶어놓은 것 (= 변수 여러 개를 묶어놓은 것)
3) 지역변수 (lv) : - 클래스 영역 외의 영역 (메소드, 생성자, 초기화 블럭 내)
*메모리에 로딩
cpu ↔ 메모리장치(RAM) ↔ 저장장치(SSD, HDD)에 time.class라는 파일이 있을 때
cpu의 속도가 너무 빨라서 저장장치의 내용을 직접 가져올 수 없음
그래서 ram 이라는 메모리장치를 통해서만 cpu에 갈 수 있음
그래서 파일을 읽으려면 파일(클래스)을 메모리에 올려야 함 (로딩)
로딩이 되어야만 cpu가 읽을 수 있음
메모리의 내용을 다시 디스크에 저장 (세이브)
3-2. 클래스변수와 인스턴스변수
<속성> class Card {
무늬, 숫자 (개별) ——( 인스턴스변수 (개별속성) )——> String kind; //무늬
int number; //숫자
폭, 높이 (공통) —— ( 클래스변수 (공통속성) ) ——> static int width = 100; //폭
static int height = 250; //높이
* 무늬, 숫자는 고유한 값을 가져야 하므로 인스턴스변수 ( 객체마다 1개씩 )
폭, 높이는 모든 인스턴스가 공통으로 가져야 하므로 클래스변수 ( 공통적으로 1개만 )
Card c = new Card(); //객체 생성
c.kind = "HEART"; //객체 사용
c.number = 5;
Card.width = 200; //cv는 클래스이름 붙이는 것 권장 (c.width도 되긴 됨)
Card.height = 300;
class Ex6_3 {
public static void main(String args[]) {
System.out.println("Card.width = " + Card.width);
System.out.println("Card.height = " + Card.height);
Card c1 = new Card();
c1.kind = "Heart";
c1.number = 7;
Card c2 = new Card();
c2.kind = "Spade";
c2.number = 4;
System.out.println("c1은 " + c1.kind + ", " + c1.number + "이며, 크기는 (" + c1.width + ", " + c1.height + ")");
System.out.println("c2는 " + c2.kind + ", " + c2.number + "이며, 크기는 (" + c2.width + ", " + c2.height + ")");
System.out.println("c1의 width와 height를 각각 50, 80으로 변경합니다.");
Card.width = 50;
Card.height = 80;
System.out.println("c1은 " + c1.kind + ", " + c1.number + "이며, 크기는 (" + c1.width + ", " + c1.height + ")");
System.out.println("c2는 " + c2.kind + ", " + c2.number + "이며, 크기는 (" + c2.width + ", " + c2.height + ")");
}
}
class Card {
String kind;
int number;
static int width = 100;
static int height = 250;
}
Card.width = 100
Cart.height = 250
c1은 Heart, 7이며, 크기는 (100, 250)
c2는 Spade, 4이며, 크기는 (100, 250)
c1의 width와 height를 각각 50, 80으로 변경합니다.
c1은 Heart, 7이며, 크기는 (50, 80)
c2는 Spade, 4이며, 크기는 (50, 80)
3-3. 메소드
1) 메소드란?
- 문장들을 묶어놓은 것 ( 작업단위별로 { } 중괄호를 이용해서 묶음 )
- 똑같은 작업을 하는 코드의 중복은 반드시 제거
- 오르쪽 코드는 배열을 출력하는 작업을 별도의 메소드로 만든 것
- 배열출력하는 코드를 { } 안에 넣고, printArr 이라는 메서드 이름을 붙임.
- 하나의 작업을 하는 코드를 { } 안에 넣어서 하나로 묶고 이름을 붙이면 → 메서드
- 메서드 이름에 괄호치고 사용하면 → 메서드 호출 (사용)
- 값(입력)을 받아서 처리하고, 결과를 반환(출력)
- 메서드 == 함수(函) : 작업에 필요한 값들을 받아서 처리 후 , 결과를 반환함
- 메서드 -> 객체지향개념, 클래스안에 있어야 함
- 함수 -> 클래스에 독립적
2) 메소드의 장점
- 높은 재사용성
: 몇번이고 호출 가능, 다른 프로그램에서도 사용 가능 - 중복된 코드 제거
: 반복되는 문장들을 메소드로 만들어서 사용 -> 중복 제거, 오류 수정 용이 - 프로그램의 구조화
: 작업단위로 나눠서, 프로그램의 전체 흐름이 한눈에 들어올 정도로 단순하게 구조화 - 코드 간결 -> 코드의 관리, 이해 용이
3) 메소드의 작성
- 반복적으로 수행되는 여러 문장을 메서드로 작성
- 하나의 메서드는 한 가지 기능만 수행하도록 작성 ( 다른 기능을 하나의 메서드로 만들 수 X )
4) 메소드의 선언과 구현
- 메소드 = 선언부 + 구현부
- 선언부
- 매개변수(입력) : 0~n개 가능
- 반환(출력) : 0~1개
작업결과가 여러개 - 배열 or 객체 (변수를 묶은 것) 로 묶어서 준다.
작업결과가 0개 - 반환타입 void(=없다) 로 적어준다.
- 구현부
- 지역변수(lv) : 메소드 내에 선언된 변수 (메소드의 매개변수도 지역변수)
- 반환값
반환타입이 void가 아닐 때,
int add (int x, int y) {
int result = x+y;
return result ; // result = 반환값 = int와 타입일치해야 함 (or 자동형변환 가능한 값 = char,byte,short )
}
int result = mm.add(3,5) ; // 결과를 담을 변수 타입
int add(int x, int y) { // = 메서드의 반환 타입
return x + y ; // = 반환값의 타입
}
5) 메소드 호출
메서드 이름(값1, 값2, …); // 메서드 호출방법 ( 괄호 안에는 작업에 필요한 값들 )
print99danAll(); // void print99danAll()이라는 메서드 호출 (구구단전체가 필요하기 때문에 입력값x)
// 반환타입 void = 출력x = 구구단을 콘솔에 출력하는 걸로 끝 = 변수 저장x = 값 반환x
int result = add(3,5); // int add(int x, int y)를 호출하고, 결과를 result에 저장
// int = 출력, int x,y = 입력값 2개 필요
// 작업결과가 있기 때문에 그 결과값을 저장할 변수 필요
- print99danAll(); : 반환할 작업결과가 없기 때문에 최종결과의 타입으로 선언한 변수에 저장할 필요 X
- add(3,5); : add메서드를 실행해서 반환되는 결과값이 존재하기 때문에, 그 결과값을 저장할 int타입의 변수 result 필요
- 매개변수 : 메서드를 호출한 쪽에서 주는 값을 add메서드에 전달하는 중간역할
* 메소드는 클래스 영역에서만 정의 가능
* static 메소드는 같은 클래스 내의 인스턴스 메소드를 호출할 수 없음
6) 메소드 실행 흐름
class Ex6_4 {
public static void main(String args[]) {
MyMath mm = new MyMath(); // 1. MyMath 객체 생성
long result1 = mm.add(5L, 3L); // 2. MyMath 객체의 메서드 호출
long result2 = mm.subtract(5L, 3L);
long result3 = mm.multiply(5L, 3L);
double result4 = mm.divide(5L, 3L);
System.out.println("add(5L, 3L) = " + result1);
System.out.println("subtract(5L, 3L) = " + result2);
System.out.println("multiply(5L, 3L) = " + result3);
System.out.println("divide(5L, 3L) = " + result4);
}
}
class MyMath {
long add(long a, long b) { // 3. MyMath 객체의 메소드 실행
long result = a + b;
return result; // 4. 결과값 반환 -> 호출한 곳으로 돌아간다.
// return a + b; // 위의 두 줄을 이와 같이 한 줄로 간단히 할 수 있다.
}
long subtract(long a, long b) { return a - b; }
long multiply(long a, long b) { return a * b; }
double divide(double a, double b) {
return a / b;
}
}
add(5L,3L) = 8
subtract(5L,3L) = 2
multiply(5L,3L) = 15
divide(5L,3L) = 1.66666666666667
* double result4 = mm.divide(5L, 3L); // long타입의 값으로 호출
double divide(double a, double b) { // 매개변수는 double 타입
return a / b;
}
-> 호출 시 입력된 값은 메소드의 매개변수에 대입되는 값이므로
-> double a = 5L; 와 같이 long타입의 값인 5L가 double타입의 값인 5.0으로 자동 형변환!
- 객체 생성
- 메서드 호출 (작업하는데 필요한 입력값 입력)
- 호출된 메서드의 문장 실행
- 작업이 끝나면 결과값을 가지고 자기를 호출한 곳으로 돌아감
- 메서드 호출부분의 값이 결과값으로 바뀌고, 그걸 변수에 대입
7) return문
- 실행 중인 메서드를 종료하고 호출한 곳으로 되돌아간다.
-printGugudan 메서드 = 단을 입력하면 해당 단을 출력하는 메서드 (for문 실행)
-근데 if문처럼 조건에 해당하지 않으면, return (작업 실행하지 않고, 호출한 곳으로 되돌아감)
-원래는 메서드가 작업을 마쳤을 때도 호출한 곳으로 되돌아 가기 위해 return을 써줘야 하는데,
반환 타입이 void인 경우 생략가능
- 반환타입이 void가 아닌 경우, 반드시 return문 필요 (작업결과를 반환해야 하므로)
* 두번째 메서드 = 오류 (참일 때만 실행되고, 거짓일 때는 return문이 없다고 뜸)
참일 때와 거짓일 때 모두 return문이 있도록 작성해야 함
class Ex6_4 {
public static void main(String args[]) {
MyMath mm = new MyMath();
mm.printGugudan(5); //구구단 3단 출력 (메서드 호출)
class MyMath {
void printGugudan(int dan) {
if(!(2<=dan&&dan<=9))
return; //입력받은 단이 2~9단이 아니면, 메서드 종료하고 돌아가기
for(int i=1;i<=9;i++) {
System.out.printf("%d * %d = %d%n", dan, i, dan*i);
}
//return (void일때는 생략 가능)
}
// 두 값을 받아서 둘 중에 큰 값을 반환하는 메서드
long max(long a, long b) {
if(a>b)
return a; // 조건식이 참일 때만 실행
else
return b; // 조건식이 거짓일 때 실행
}
- 매개변수의 유효성 검사
: 메소드의 구현부{ } 를 작성할 떄, 제일 먼저 해야 하는 일 ( 매개변수의 값이 적절한지 확인 )- float divide(int x, int y) {
// 작업을 하기 전에 나누는 수(y)가 0인지 확인한다
if(y==0) {
System.out.println("0으로 나눌 수 없습니다.");
return 0; // 매개변수가 유효하지 않으므로 메소드 종료
}
return x / (float) y;
}
- float divide(int x, int y) {
8) JVM의 메모리 구조 ( 메소드 영역, 힙 영역, 호출 스택 )
- 스택(stack) : 밑이 막힌 상자. 위에 차곡차곡 쌓임
- 호출 스택 (call stack)
메소드 수행에 필요한 메모리가 제공되는 공간
메소드가 호출되면 호출스택에 메모리 할당, 종료되면 해제
-main 메서드가 호출되어 실행
-main 메서드가 println 메서드를 호출
-println 메서드 실행, main 메서드는 대기
-main 메서드는 println 메서드가 작업을 마칠 때까지 기다려야 함
-println 메서드가 작업을 종료하면 스택에서 사라짐
-main 메서드가 println 메서드를 호출한 곳으로 돌아와서 main 메서드가 다시 실행 상태
메서드들의 호출 관계
아래 있는 메서드가 위의 메서드를 호출한 것
맨 위의 메서드 하나만 실행중, 나머지는 대기중
class Ex6_5 {
public static void main(String[] args) {
System.out.println("Hello");
}
}
-main 메서드가 println 메서드 호출
-main 메서드는 대기, println 메서드 실행
-println 메서드가 자신이 사용한 메모리 반환하고 작업 종료, 스택에서 사라짐
-println 메서드를 호출한 main 메서드로 돌아감
-main 메서드 실행하는데 실행할 작업이 없으면 스택 비워지고, 프로그램 종료
3-4. 매개변수
1) 기본형 매개변수 ( 메소드의 매개변수 타입이 기본형 )
※ 기본형 매개변수 ~ 참조형 매개변수
아주 어렵고 중요하므로
하나하나 그림 계속 따라그려보기 !!
기본형 매개변수 - 변수의 값을 읽기만 할 수 있다. (read only) (기본형8개)
참조형 매개변수 - 변수의 값을 읽고 변경할 수 있다. (read & write)
class Data { int x; }
class Ex6_6 {
public static void main(String[] args) {
Data d = new Data();
d.x = 10;
System.out.println("main() : x = " + d.x);
change(d.x);
System.out.println("After change(d.x)");
System.out.println("main() : x = " + d.x);
}
static void change(int x) { // 기본형(int) 매개변수 =x (여기서 참조형/기본형 결정)
x = 1000;
System.out.println("change() : x = " + x);
}
}
main() : x = 10
change() : x = 1000
After change(d.x)
main() : x = 10
- main 메서드가 스택에 올라감 → 프로그램 실행
- Data 객체 생성 → 이 객체의 주소값이 참조변수 d에 저장
- d가 가리키는 객체의 멤버변수 x에 10을 저장
- ”main() : x = 10” 출력
- change 메서드 호출 → d.x의 값(참조변수d가 가리키는 x의 값)이 change메서드의 (int x)로 복사
( 호출할 때 괄호 안에 지정한 값은 매개변수로 복사됨 )
- static void change의 x가 지역변수이므로 change메서드의 지역변수 x에 x객체의 값 10이 대입
- change 메서드 문장 실행 → 1000을 지역변수 x에 대입
( 여기서 x 값을 1000으로 바꾼 것은 멤버변수 x의 값을 바꾼게 아니라, 지역변수 x의 값을 바꾼 것
→ x는 기본형 매개변수(int x) 이기 때문에 읽기만 가능 → 변경 불가
- change 메서드의 지역변수 x와 멤버변수 x는 이름은 같지만 다른 저장공간
- 다음 문장 실행 → 여기서 x는 지역변수 x의 영향을 받으므로
-”change() : x = 1000” 출력
- change 메서드의 작업이 완료됐으므로, 자신을 호출한 곳으로 다시 돌아감 ( change(d.x); )
- change 메서드가 호출스택에서 사라지고, 지역변수x도 같이 사라지고, main메서드 실행
- change호출 다음 문장 실행
- ”After change(d.x)” 출력
- ”main() : x 10” 출력
- 여기서 main 메서드의 할일도 끝나고 프로그램 종료
2) 참조형 매개변수
기본형 매개변수 - 변수의 값을 읽기만 할 수 있다. (read only) (기본형8개)
참조형 매개변수 - 변수의 값을 읽고 변경할 수 있다.(read & write)
class Data2 { int x; }
class Ex6_7 {
public static void main(String[] args) {
Data2 d = new Data2();
d.x = 10;
System.out.println("main() : x = " + d.x);
change(d); //얘는 main메서드의 지역변수
System.out.println("After change(d)");
System.out.println("main() : x = " + d.x);
}
static void change(Data2 c) { // 참조형(Data2) 매개변수 =c (여기서 참조형/기본형 결정)
c.x = 1000;
System.out.println("change() : x = " + c.x);
}
}
main() : x = 10
change() : x = 1000
After change(d.x)
main() : x = 1000
- 프로그램 실행 → main 메서드 실행 → 스택에 main 메서드가 올라감
→ 참조변수 d 생성 → Data2 객체 생성
→ Data2 객체의 멤버는 x 1개 → x객체를 0으로 초기화, 주소값 100번지
→ 대입연산자에 의해서 x객체의 주소값이 참조변수 d에 대입 → d가 x객체를 가리킴 (연결)
→ 참조변수 d가 가리키는 객체 x값에 10을 저장
→ 스택에서 println 메서드가 main 메서드 위로 올라감 → println 메서드 실행
→ “main() : x = 10” 출력
→ println 메서드 종료 후 main 메서드로 돌아옴
→ change 메서드 호출 → main 메서드 대기, change 메서드 실행
→ d값(100번지)를 static void change(Data2 c) 의 c로 복사
→ Data2가 기본형이 아니므로 c는 참조형 매개변수
→ 그러면 change 메서드의 참조변수 c도 x객체의 주소를 가지고 있으니까 x객체를 가리킴(연결)
→ c.x=1000 은 c가 가리키는 x의 값을 1000으로 바꾸는 것인데, 여기서 c는 change 메서드의 지역변수. 지역변수 c가 가리키는 x의 값을 1000으로 바꿈
→ 지역변수 c가 가리키는 x객체의 값이 1000으로 바뀜
→ “change() : x = 1000” 출력
→ 더 실행할 문장이 없으므로 change 메서드 종료 → 호출한 곳으로 돌아감 ( change(d); )
→ main 메서드로 돌아감 → 실행
→ “After change(d)” 출력
→ “main() : x = 1000” 출력
이처럼, change 메서드한테 참조형 매개변수를 통해서 객체의 주소를 전달하면,
change 메서드에서 참조변수(리모컨)를 이용해서 객체를 다룰 수 있다.
(객체의 값을 읽기, 쓰기 가능)
* 도움되는 댓글
Data2 d = new Data2(); 의 d와
change 메서드의 매개변수 d를 둘다 d로 하지말고
매개변수는 c로 하는게 덜 헷갈리고 이해가 잘 될 거다.
change메서드의 매개변수를 c로 하면 change메서드 안에서 c.x의 값을 1000으로 주었을 때 main메서드의 d.x의 값도 1000으로 바뀌는게 ‘c.x의 값을 바꿨는데 왜 d.x의 값이 바뀌지?’ 라는 의문과 같이 주소의 개념이 확 이해가 되면서 의문이 해결되고 더 쉽게 이해가 갈듯하다.
기본형 매개변수와 참조형 반환타입도 마찬가지로 메서드의 매개변수를 main메서드에 Data클래스 객체의 주소를 담은 d와 같은 이름이 아닌 c로 넣고 설명을 하는게 안헷갈릴듯.
3) 참조형 반환타입
class Data3 { int x; }
class Ex6_8 {
public static void main(String[] args) {
Data3 d = new Data3();
d.x = 10;
Data3 d2 = copy(d);
System.out.println("d.x ="+d.x);
System.out.println("d2.x="+d2.x);
}
static Data3 copy(Data3 c) {
Data3 tmp = new Data3(); // 새로운 객체 tmp를 생성한다.
tmp.x = c.x; // d.x의 값을 tmp.x에 복사한다.
return tmp; // 복사한 객체의 주소를 반환한다.
}
}
- main 메서드 실행
→ 참조변수 d 생성 → 객체 Data3 생성 → Data3의 객체 x 생성
→ 객체x의 주소값(100번지)이 참조변수 d에 대입 (대입연산자) → 참조변수와 객체x 연결
→ d가 가리키는 x값에 10 저장
→ main 메서드에 d2가 먼저 만들어짐 → copy 메서드 호출
→ copy 메서드 실행 → 참조변수 d(copy(d))의 값을 매개변수 c(Data3 c) 에 복사
→ copy 메서드에 참조변수 c 생성 후 d의 값 복사
→ 참조변수 c도 객체 x를 가리키게 됨 (연결)
→ copy 메서드에 변수 tmp 생성 → 객체 Data3 (의 멤버인 객체x) 생성 (다른 객체니까 200번지, 0으로 초기화)
→ 이 x값을 변수 tmp에 저장
→ 참조변수 tmp가 새로운 객체 x를 가리키게 됨 (연결)
→ tmp.x = c.x ; 에서 참조변수 c가 가리키는 값은 첫번째 객체 x의 값인 10
→ 이 10이 tmp가 가리키는 x에 저장 (새로 생긴 x에 저장)
→ return tmp; 는 tmp의 주소값인 200번지를 반환
→ 이 메서드의 반환타입은 Data3 (참조형) → 반환타입이 참조형일 때는 객체의 주소(객체)를 반환
→ 이 값이 copy(d)의 d 값으로 반환
→ 이 값은 Data3 d2의 d2에 대입
→ tmp의 주소값 ⇒ main 메서드의 매개변수 d2 ⇒ tmp가 가리키는 객체x의 값 = 10
→ 호출된 copy 메서드 종료
→ println 메서드 실행 → d가 가리키는 x의 값 = 10 → d2가 가리키는 x의 값 = 10
- 반환타입이 참조형 = 객체의 주소(객체)를 반환
→ 반환타입이 참조형이니까 그 반환값을 받는 변수도 참조형이어야 함
( static Data3 의 Data3 = Data3 d2 의 Data3 )
copy메서드가 하는 일
1) 새로운 객체 tmp를 생성
2) 매개변수로 넘겨받은 Data3 객체에 저장된 d.x의 값을 tmp.x에 복사
3) 복사한 객체의 주소를 d2로 반환
→ copy메서드가 생성한 객체를 main메서드에서 사용할 수 있게 된 것
→ copy 메소드 내에서 생성한 객체를 main메소드에서 사용할 수 있으려면, 새로운 객체의 주소를 반환해줘야 함
( copy 메소드가 종료되면서 새로운 객체의 참조가 사라지기 때문에 더이상 이 객체를 이용할 수 없음 )
*같은 클래스 내의 static 메서드 (main, copy)
- copy(d) 앞에 참조변수 생략 ( 같은 클래스라서 )
- static 메서드는 객체 생성 없이 호출 가능
(원래는 Ex6_8 e = new Ex6_8(); 객체 생성 후 e.copy (); 로 호출 해줘야 함 )
4) 클래스 메소드(static 메소드)와 인스턴스 메소드
클래스 영역에 선언된 변수 : 멤버변수
static + 멤버변수 : 클래스변수 (static변수)static +멤버변수 : 인스턴스변수
- static 메서드 (=클래스 메서드)
- 메서드 앞에 static이 붙은 것
- 객체생성없이 ‘클래스이름.메서드이름()’ 으로 호출 ( Math.random() )
- 인스턴스 멤버 (iv,im) 와 관련없는 작업을 하는 메서드
- 메서드 내에서 인스턴스 변수(iv) 사용 불가 - 인스턴스 메서드
- 메서드 앞에 static이 안 붙은 것
- 인스턴스(객체) 생성 후, ‘참조변수.메서드이름()’ 으로 호출
- 인스턴스 멤버 (iv,im) 와 관련된 작업을 하는 메서드
( 메소드의 작업을 수행하는데 인스턴스 변수를 필요로 하는 메소드 )
- 메서드 내에서 인스턴스 변수(iv) 사용 가능
⇒ iv 사용여부에 따라 갈림 !
class MyMath2 {
long a, b; // iv(인스턴스변수) - 클래스 전체에서 사용 가능
long add () { // 인스턴스메서드 ( iv 사용 )
return a + b ;
}
static long add(long a, long b) { // 클래스메서드(static메서드), lv(지역변수) - 유효범위 ㅇ
return a + b ; ( iv 사용 X )
}
}
public static void main(String args[]) {
// 클래스메소드는 객체 생성 없이 호출가능
System.out.println(MyMath2.add(200L,100L); // 클래스메서드 호출 (객체생성X)
// 인스턴스메소드는 객체 생성 후에만 호출 가능
MyMath2 mm = new MyMath2 () ; // 1. 인스턴스 생성 (객체생성)
mm.a = 200L;
mm.b = 100L;
System.out.println(mm.add()); // 2. 인스턴스메서드 호출 ( 참조변수 mm 사용 )
객체 = iv의 묶음 (변수 묶음)
- static 메서드는 자기 작업에 필요한 값들을 다 매개변수로 받아서 lv 로 해결함
→ iv가 필요 없음 (=객체 필요없음)
-인스턴스 메서드는 매개변수(입력값)가 없어서 iv값 쓰는 것
→ 인스턴스 메서드는 iv로 작업하기 때문에 iv가 필요한데, 객체는 iv의 묶음이니까 객체 필요
< static을 언제 붙여야 할까? >
- 속성(멤버 변수)중에서 공통 속성에 static을 붙인다
: 모든 인스턴스에서 같은 값이 유지되어야 하는 변수는 static을 붙여서 클래스변수로 정의
- 클래스 변수는 인스턴스를 생성하지 않아도 사용할 수 있다
: 클래스가 메모리에 올라갈 때 이미 자동적으로 생성되기 때문 - 클래스 메소드는 인스턴스 변수를 사용할 수 없다
: 클래스 메소드가 호출되었을 때 인스턴스가 존재하지 않을 수도 있으니까 - 인스턴스 멤버 (iv, im)을 사용하지 않는 메서드에 static을 붙인다
: 객체를 생성하지 않으므로, 메소드 호출시간이 짧아져 성능 향상
5) 클래스 멤버와 인스턴스 멤버간의 참조와 호출
< 메소드 -> 변수 호출 >
* static 메서드는 인스턴스 변수 (iv)를 사용할 수 없다.
* 인스턴스 변수(iv) - 꼭 객체 생성 후 사용 가능
* 클래스 변수(cv) - 언제나 사용 가능
* 인스턴스 메소드 => 인스턴스 변수 O , 클래스 변수 O
- 객체 생성 후 호출 가능
- iv로 작업하니까 iv를 쓸 수 있다.
- 얘가 호출 됐다는 얘기는 이미 객체가 생성 됐다는 얘기 = iv가 존재 한다는 의미
→ iv 사용 가능
- cv도 쓸 수 있다. cv는 객체 생성 안해도 메모리가 바로 올라가기 때문.
* static 메서드 => 인스턴스 변수 X, 클래스 변수 O
- 객체 생성 없이 호출 가능
- static 메서드를 호출됐을 때는 객체가 있는지 없는지 아직 몰라 ( 객체 생성 없이 호출 가능하니까 )
→ 객체가 있다는 보장이 없으므로, iv 사용 불가
- cv는 항상 가능
< 메소드 -> 메소드 호출 >
* static 메서드는 인스턴스 메서드 (im)를 호출할 수 없다.
* 인스턴스 메서드 —(호출 O)→ 다른 인스턴스 메서드 : 이미 객체 있다는 뜻이니까
* 인스턴스 메서드 —(호출 O)→ static 메서드 : static 메서드는 객체 있든 없든 호출 가능이니까
* static 메서드 —(호출 X)→ 인스턴스 메서드 : 객체가 있는지 보장이 안되니까
* static 메서드 —(호출 X)→ static 메서드 : 있든 없든 호출 가능하니까
Q. static 메서드는 static 메서드 호출 가능?
A. 네
Q. static 메서드는 인스턴스 변수 사용 가능?
A. 아니요 (인스턴스 변수는 객체 생성해야 사용 가능하므로)
Q. static 메서드는 인스턴스 메서드 호출 가능?
A. 아니요 (인스턴스 메서드는 iv로 작업하기 때문에)
Q. static 메서드는 왜 인스턴스 멤버를 쓸 수 없나요? (iv, im)
A. static 메서드 호출 시 객체(iv묶음)가 있을지 없을지 몰라서
class MyMath2 {
long a, b;
// 인스턴스 변수 a, b만을 이용해서 작업하므로 매개변수가 필요없다.
long add() { return a + b; } // a, b는 인스턴스 변수
long subtract() { return a - b; }
long multiply() { return a * b; }
double divide() { return a / b; }
// 인스턴스 변수와 관계없이 매개변수만으로 작업이 가능하다.
static long add(long a, long b) { return a + b; } // a, b는 지역변수
static long subtract(long a, long b) { return a - b; }
static long multiply(long a, long b) { return a * b; }
static double divide(long a, long b) { return a / (double)b; }
}
class Ex6_9 {
public static void main(String args[]) {
// 클래스 메서드 호출. 인스턴스 생성없이 호출가능
System.out.println(MyMath2.add(200L, 100L));
System.out.println(MyMath2.subtract(200L, 100L));
System.out.println(MyMath2.multiply(200L, 100L));
System.out.println(MyMath2.divide(200L, 100L));
MyMath2 mm = new MyMath2(); // 인스턴스를 생성
mm.a = 200L;
mm.b = 100L;
// 인스턴스 메서드는 객체생성 후에만 호출이 가능함.
System.out.println(mm.add());
System.out.println(mm.subtract());
System.out.println(mm.multiply());
System.out.println(mm.divide());
}
}
4. 오버로딩 (Overloading)
4-1. 오버로딩 ( = 중복정의 )
- 한 클래스 안에 같은 이름의 메서드 여러 개를 정의하는 것
( 메소드 : 메소드 이름 = n : 1 ) - println(String x)
println(int x)
println(char x)
메서드의 이름은 모두 같지만, 매개변수가 전부 다르다.
만약 오버로딩이 지원되지 않는다면.
printlnStr(“ABC”), printlnint(123) 등 외우기도 어렵고 불편함
4-2. 오버로딩의 조건
- 메서드 이름이 같아야 한다.
- 매개변수의 개수 또는 타입이 달라야 한다.
- 반환 타입은 영향 없다.
보기1) 메서드 두개의 이름이 같고(add), 매개변수 개수가 같고(2개), 타입이 같음(int)
→ 오버로딩 x ( 1번만 충족 )
보기2) 메서드 두개의 이름이 같고(add), 매개변수의 개수와 타입이 같음, 반환타입이 다르지만 이건 영향 x
→ 오버로딩 x
보기3) 메서드 두개의 이름이 같고 (1번 ok), 매개변수의 타입이 다름(2번 ok)
→ 오버로딩 o
여기서,
add(3,3L); 을 호출하면 첫번째 꺼가 호출 되지만
add(3,3); 을 호출하면 둘중에 뭘 호출할지 몰라서 에러 ambiguous(애매함)
4-3. 오버로딩 예제
오버로딩의 올바른 예 - 매개변수는 다르지만 같은 의미의 기능수행
- 메서드 이름 같음 , 대신 매개변수 타입이 다 다름
- 메서드 이름: add, println, round, random 등 → 동사(작업수행)
- 메서드 이름이 같다 = 하는 작업이 같다
class Ex6_10 {
public static void main(String args[]) {
MyMath3 mm = new MyMath3();
System.out.println("mm.add(3, 3) 결과:" + mm.add(3,3)); // ① mm.add메서드 먼저 호출/ ③ 출력
System.out.println("mm.add(3L, 3) 결과: " + mm.add(3L,3));
System.out.println("mm.add(3, 3L) 결과: " + mm.add(3,3L));
System.out.println("mm.add(3L, 3L) 결과: " + mm.add(3L,3L));
int[] a = {100, 200, 300};
System.out.println("mm.add(a) 결과: " + mm.add(a));
}
}
class MyMath3 { // ② add메서드 먼저 실행, 출력
int add(int a, int b) {
System.out.print("int add(int a, int b) - ");
return a+b;
}
long add(int a, long b) {
System.out.print("long add(int a, long b) - ");
return a+b;
}
long add(long a, int b) {
System.out.print("long add(long a, int b) - ");
return a+b;
}
long add(long a, long b) {
System.out.print("long add(long a, long b) - ");
return a+b;
}
int add(int[] a) { // 배열의 모든 요소의 합을 결과로 돌려준다.
System.out.print("int add(int[] a) - ");
int result = 0;
for(int i=0; i < a.length;i++)
result += a[i];
return result;
}
}
출력
int add(int a, int b) - mm.add(3, 3) 결과:6
long add(long a, int b) - mm.add(3L, 3) 결과: 6
long add(int a, long b) - mm.add(3, 3L) 결과: 6
long add(long a, long b) - mm.add(3L, 3L) 결과: 6
int add(int[] a) - mm.add(a) 결과: 600
5. 생성자 (constructor)
5-1. 생성자란
-인스턴스가 생성될 때마다 호출되는 ‘인스턴스(=객체=iv묶음) 초기화 메서드’ = iv초기화 메서드
-인스턴스 생성시 수행할 작업(iv 초기화)에 사용
- 참조변수 t를 선언하고, Time 객체 생성
Time 객체에는 멤버가 3개 (hour, minute, second)는 0으로 자동 초기화
- 내가 원하는 시간으로 초기화
이렇게,
- 객체 생성
- iv 초기화 의 단계를 거쳐야 되는데 좀 번거로움
그래서
⇒ 생성자 = 객체 생성 + iv초기화(원하는 값 세팅)
※ 생성자 작성 규칙 (암기)
1) 생성자 이름이 클래스 이름과 같아야 한다.
2) 리턴값이 없다. (void 안붙임)
3) 모든 클래스는 반드시 생성자를 가져야 한다. ( 1개 이상 )
- 클래스 이름 (Card) = 생성자 이름 (Card)
- 똑같은 이름의 생성자가 2개 존재할 수 있는 이유 = 생성자 오버로딩 (같은 이름의 메서드, 다른 매개변수)
- iv초기화 = 대입문 ⇒ 반환할 결과가 없음 ⇒ 리턴값이 없음
(원래 메서드 앞에 리턴값이 없으면 void를 붙이는데, 생성자는 안붙임 → 항상 반환값이 없어서) - Card c = new Card(); 객체 생성인데 이 부분이 클래스 이름이 아니라 생성자 호출하는 부분
생성자를 만든적이 없는데 어떻게 호출? → 기본 생성자 (안만들어도 쓸 수 있음)
5-3. 기본 생성자 (default constructor) - 기본생성자를 클래스 만들 때 습관으로 넣어줘야 함 !
- 매개변수가 없는 생성자 (ex. Point() {} = Point클래스의 기본 생성자 ) - 하는 일은 없음
- 생성자가 하나도 없을 때만, 컴파일러가 자동 추가 ( 생성자가 1개 이상은 꼭 있어야 해서 원래는 직접 작성 )
이걸 실행하면,
마지막줄 compilt error 발생 → cannot resolve symbol : 이름에 문제 있다. 뭘 호출하는 지 못찾겠다.
⇒ Data_2(); 가 기본 생성자를 호출 한건데, 기본 생성자가 없어서 에러
⇒ Data_2 클래스 안에 Data_2() {} 라고 기본 생성자를 추가해야 함!
⇒ Data_1(); 도 Data_1 클래스 안에 기본 생성자가 없는데 얘는 왜 오류가 없냐?
⇒ Data_2 클래스 안에는 Data_2 (int x) {} 라는, 매개변수가 있는 생성자가 1개 이미 있음 → 자동 추가 안되니까 직접 추가 해야 함
⇒ Data_1 클래스 안에는 생성자가 0개 → 컴파일러가 기본 생성자를 자동 추가
class Data_1 {
int value;
// Data_1(){} // 기본생성자 - 얘는 기본생성자가 0개니까 자동추가
}
class Data_2 {
int value;
Data_2(){} // 기본 생성자 넣어줘야 함
Data_2(int x) { // 매개변수가 있는 생성자.
value = x;
}
}
class Ex6_11 {
public static void main(String[] args) {
Data_1 d1 = new Data_1();
Data_2 d2 = new Data_2(); // compile error발생
}
}
- 오류를 해결하기 위해서는
1) Data2 d2 = new Data2(); : 기본생성자를 추가해준 뒤, 기본생성자를 호출한다
2) Data2 d2 = new Data(10); : 기본생성자가 아닌, 매개변수가 있는 생성자를 호출한다
기본생성자가 컴파일러에 의해서 자동으로 추가되는 경우는
클래스에 정의된 생성자가 하나도 없을 때 뿐 !
5-4. 매개변수가 있는 생성자
- 생성자 Car( ) 를 사용한다면, 인스턴스를 생성한 다음에 인스턴스 변수들을 따로 초기화 해야 함
- 생성자 Car(String c, String g, int d)를 사용한다면, 인스턴스를 생성하는 동시에 원하는 값으로 초기화 할 수 있음
- 매개변수가 있는 생성자는 밖에서 초기화할 코드를 클래스 안으로 감춘 것 뿐
그러나 객체를 생성할 때는 코드가 간결해짐
설계도를 만든 사람이 매개변수가 있는 생성자를 한번만 잘 작성해 놓으면,
사용하는 쪽에서 편리하게 여러번 사용할 수 있다.
- 실행되는 과정
1) 참조변수 c 생성 ( Car c )
2) new 연산자가 클래스를 보고 객체 생성 ( new ) - 이 객체의 멤버: color, gearType, door
3) 생성자 호출 = 객체 초기화 ( Car(”white”, “auto”, 4); )
4) 대입 - 객체와 참조변수 연결을 위해 대입연산으로 주소값 저장 (new 연산자의 반환값은 객체주소)
5-5. 생성자 this( ), 참조변수 this
1) 생성자 this( )
- 생성자에서 다른 생성자 호출할 때 사용
- 다른 생성자 호출시 생성자의 첫번째 줄에서만 사용가능
class Car2 {
String color; // 색상
String gearType; // 변속기 종류 - auto(자동), manual(수동)
int door; // 문의 개수
Car2() {
this("white", "auto", 4);
}
Car2(String color) {
this(color, "auto", 4);
}
Car2(String color, String gearType, int door) {
this.color = color;
this.gearType = gearType;
this.door = door;
}
}
class CarTest2 {
public static void main(String[] args) {
Car c1 = new Car();
Car c2 = new Car("blue");
System.out.println("c1의 color=" + c1.color + ", gearType=" + c1.gearType + ", door=" + c1.door);
System.out.println("c2의 color=" + c2.color + ", gearType=" + c2.gearType + ", door=" + c2.door);
}
}
c1의 color = white, gearType = auto, door = 4
c2의 color = blue, gearType = auto, door = 4
- 생성자가 3개 있다. ( Car2( ){ } / Car2(String color){ } / Car2(String color, String gearType, int door){ } )
- 1,2번 생성자가 3번 생성자를 호출한다. ( this("white", "auto", 4); )
- 호출할 때 이름을 Car2를 써야되는데 this라고 쓴다.
this("white", "auto", 4); 라고 쓰지만, Car2(String color, String gearType, int door)를 호출하는 것
- 같은 클래스 내에 있는 생성자들끼리 호출할 때는 this
- 첫번째 줄에서만 this 호출 가능
Car (String color) {
door = 5;
Car (color , “auto”, 4 );
} // 2번째 줄에서 호출 → 에러 / this로 호출 안함 → 에러
- 생성자 3개가 하는 일은 모두 iv 초기화
- 오버로딩에서 이름이 같은 메서드들은 하는일이 같다 = 이름 같은 생성자도 마찬가지
- 코드 중복 제거를 위해서 생성자들끼리 서로 호출
- 근데 그때 클래스 이름이 아닌 this를 쓰는 것
- Car 클래스의 생성자 2개
- 첫번째 생성자 = 기본 생성자 ( 매개변수 x ) → iv 초기화 값 x (지정x) → 디폴트 값으로 초기화
- 원래는 두번째 생성자처럼 매개변수를 주는대로 만드는데 첫번째처럼 지정해주지 않으면 디폴트 값
- 첫번째와 두번째 생성자가 거의 같음 = 코드 중복
→ 위의 생성자가 아래 생성자를 호출하도록 변경 (이 때 이름을 this ) = 코드 중복 제거
⇒ 코드 중복을 제거하려면 어느 한쪽이 다른 한쪽을 호출하도록 하면 됨.
5-6. 생성자 this( ) ≠ 참조변수 this
* 참조변수 this
- 인스턴스 자신을 가리키는 참조변수
- 인스턴스 메서드(생성자 포함)에서 사용가능
- 지역변수(lv)와 인스턴스 변수(iv)를 구별할 때 사용
- 왼쪽 코드
c, g, d = lv (지역변수) = 매개변수
color, gearType, door = iv (인스턴스 변수)
⇒ 이 생성자에서는 두 변수의 이름이 달라서 구별 가능 - 오른쪽 코드
color, gearType, door = lv (지역변수) = 매개변수
this.color 등 this가 붙은 애들 = iv (인스턴스 변수)
this가 없으면 둘다 lv로 간주 (String color, String gearType, int door 얘네랑 가까운 쪽이라서)
원래 iv 쓸 때는 ‘참조변수.변수이름’ 인데 같은 클래스 내에서는 this 생략 가능 (왼쪽 코드)
오른쪽은 생략해버리면 이름이 똑같기 때문에 구별이 안되어서 생략 불가
this.color = 그 객체의 color (→ 인스턴스(객체) 자신을 가리키는 참조변수)
참조변수 this와 생성자 this( )
this: 인스턴스 자신을 가리키는 참조변수.
인스턴스의 주소가 저장되어 있다.
모든 인스턴스메서드에 지역변수로 숨겨진 채로 존재한다.
→ 그래서 따로 선언을 하지 않고 사용 가능.
this( ), this(매개변수): 생성자, 같은 클래스의 다른 생성자를 호출할 때 사용한다.
클래스 이름 대신 this( )
** this 와 this( ) 는 완전히 다른 것 !
this는 ‘참조 변수’이고, this( ) 는 ‘생성자’
- long a, b = 인스턴스변수 = this.a, this.b (참조변수.변수이름) = this 생략 가능 (구별 필요x)
- this.a = a; 에서 this.a는 인스턴스변수 , a는 지역변수 (=(int a)의 a =매개변수) → 여기서는 lv, iv 구별하려고 this 사용
- return a + b; 에서 a,b는 인스턴스변수. = return this.a + this.b; (this 생략 가능 (구별 필요x) )
- static long add(long a, long b) 메서드 내의 return a +b; 는 지역변수
( 클래스 메서드= 객체 생성x → 인스턴스변수 (=this =객체) 사용불가)
6. 변수의 초기화
6-1. 변수의 초기화
-멤버변수(인스턴스변수, 클래스변수)는 자동 초기화 된다
-지역변수는 수동 초기화(직접) 해야함 (사용전 꼭 !!!!)
x, y = 인스턴스변수
i , j = 지역변수 (메서드 안에 선언됐으니까)
이 때, 지역변수 i를 선언하고, i를 j에 넣으면 에러 ( i값이 뭔지 모르는데 이걸 j에 넣으려고 해서 )
왜 모르냐 → 호출스택에서 재사용이 빈번한 메모리(여러 메소드가 짧은 시간동안 같은 공간을 올랐다 내려갔다)
→ 메소드 호출될 때마다 메모리 공간을 0으로 초기화 하면 성능 저하 → 그래서 새로운 값으로 덮어쓰는 방식으로 초기화
→ 지역변수는 메소드가 작업하는 동안만 존재하기 때문에 주기가 짧은데, 이걸 매번 0으로 초기화하면 성능 저하
→ 다른 메모리에서 사용하는 값을 모르지만 그 값을 덮어씀 → 뭔지 모르는 값이 들어있음
→ 그래서 lv는 수동 초기화 필요
→ 인스턴스 변수는 비교적 유지기간이 길어서 0으로 초기화 하고 써도 됨 ( 0으로 자동 초기화 )
인스턴스 변수의 객체를 만들면 InitTest it = new InitTest( ) ;
x,y 멤버가 만들어지는데 이 값이 0으로 자동 초기화 → int x = 0; 이라고 직접 초기화를 안해줘도 됨
(객체는 여러 변수를 묶어 놓은 것이기 때문에 그 변수를 다 0으로 초기화 하려면,
배열도 그렇고 자동 초기화를 하지 않으면 우리가 초기화해야 하는 변수가 너무 많음. 그래서 자동으로 초기화)
*자동 초기화 되는 값
6-2. 멤버변수의 초기화
1) 명시적 초기화 (=) (간단한 초기화)
class Car {
int door = 4; // 기본형(primitive type) 변수의 초기화 (iv)
Engine e = new Engine(); // 참조형(reference type) 변수의 초기화 (iv)
- 선언할 때, 대입 연산자를 이용해 초기화
- 참조형 변수가 갖는 값
null(기본값)-얘는 초기화가 아님
객체 주소- 객체로 초기화를 해줘야 함
2) 초기화 블럭 (복잡한 초기화 - 여러 문장 넣기)
-인스턴스 초기화 블럭: { } ← iv 초기화
-클래스 초기화 블럭: static { } ← cv 초기화
class BlockTest {
static {
System.out.println("static { }"); // 클래스 초기화 블럭
}
{
System.out.println("{ }"); // 인스턴스 초기화 블럭
}
public BlockTest() {
System.out.println("생성자");
}
public static void main(String args[]) {
System.out.println("BlockTest bt = new BlockTest();");
BlockTest bt = new BlockTest();
System.out.println("BlockTest bt2 = new BlockTest();");
BlockTest bt2 = new BlockTest();
}
}
static { }
BlockTest bt = new BlockTest();
{ }
생성자
BlockTest bt2 = new BlockTest();
{ }
생성자
- BlockTest 클래스가 메모리에 로딩되면
1) 클래스 초기화 블럭 수행 ---> static { }
2) static main 메소드 수행 ---> BlockTest 출력 ---> 객체 생성 ---> 인스턴스초기화 블럭 수행 ---> 생성자
3) 다음 문장 수행 ---> BloctTest2 출력 ---> 객체 생성 ---> 인스턴스초기화 블럭 수행 ---> 생성자
3) 생성자 (복잡한 초기화) : iv 초기화
멤버변수의 초기화 (iv, cv)
1. 자동초기화 : 0으로 초기화
2. 간단초기화 : = 이용한 초기화
3. 복잡초기화 :{ }( - iv 초기화, 거의 안씀 )
static { } - cv 초기화
생성자 - iv 초기화
4) 멤버변수의 초기화 - static { } : 복잡초기화 중 cv 초기화
arr int 배열이 static이다. 즉, arr은 cv이다.
내가 하려는 건, 배열 arr을 난수로 채우고 싶은 것
근데 대입연산자(=)로 할 수 있는 건, 배열 생성 밖에 없음.
난수를 채워넣는 것은 명시적 초기화(대입연산자)로 할 수 없음.
그럼에도, 명시적 초기화가 제일 먼저 고려되어야 함. ( 간단히 할 수 있으면 간단히 해야지, 복잡한 초기화 x)
근데 이걸로 부족하니까 난수로 채우는 작업은 static 블럭으로 함
cv의 간단 초기화 = 대입연산자 (명시적 초기화)
cv의 복잡 초기화 = static { }
6-3. 멤버변수의 초기화 시점
- 클래스 변수 초기화 시점 : 클래스가 처음 로딩될 때 단 한 번 (클래스가 메모리에 올라갈 때)
- 인스턴스 변수 초기화 시점 : 인스턴스가 생성될 때 마다 (객체 만들 때마다 iv가 초기화되니까)
InitTest it = new InitTest ( ) ; 라고 InitTest 클래스의 객체 생성
→ 클래스가 메모리에 올라감 → cv 초기화
→ ① 자동 초기화 ( cv=0 ) - int니까
→ ② 간단 초기화 ( cv=1 ) - 명시적 초기화
→ ③ 복잡 초기화 ( cv=2 ) - static { }
===⇒ cv 초기화 끝 , iv 초기화 시작
→ ④ 자동 초기화 ( cv=2 , iv=0 ) - int니까
→ ⑤ 간단 초기화 ( cv=2, iv=1 ) - 명시적 초기화
→ ⑥ 복잡 초기화 ( cv=2 , iv=2 ) - static { }
→ ⑦ 복잡 초기화 ( cv=2 , iv=3 ) - 생성자 ( InitTest () { iv=3; } )
→ 객체가 또 생기면, 클래스 변수x ( 처음 한번만 초기화니까 ) , 인스턴스 변수만 다시 실행
초기화 순서
1. cv → iv
2. 자동 → 간단 → 복잡
(0) (=) ( static{ }, 생성자 )