객체지향 핵심
- 캡슐화
- 상속
- 추상화
- 다형성
객체지향 언어 = 프로그래밍 언어 + 객체지향개념(규칙) ← 객체지향개념 규칙 외워야함
객체지향 언어 장점
- 코드 재사용성이 높음
- 유지 보수 용이
클래스와 객체
객체
- 객체 정의 : 실제로 존재하는 것. 사물 또는 개념. 클래스에 정의된 내용대로 메모리에 생성된 것
- 객체 용도 : 객체가 가지고 있는 기능과 속성에 따라 다름
- 객체가 필요한 이유 : 객체(=제품)를 사용하려고
- 객체를 사용한다는것은 객체가 가진 속성과 기능을 사용하는 것임
- 객체의 구성 요소 : 속성(변수) + 기능(메서드)
- 객체가 가지고 있는 속성과 기능을 객체의 멤버라고 부름
- 속성(property)의 여러가지 용어 : 멤버변수, 특성, 필드, 상태
- 인스턴스의 멤버변수를 사용하려면 '참조변수.멤버변수' 로 사용하면 됨
- 멤버변수 중에서 static이 붙은 것은 클래스변수(static 변수), static이 붙지 않는 것은 인스턴스 변수임
- 멤버변수는 인스턴스변수와 static변수를 모두 통칭 하는 말
- 클래스를 설계할 때, 멤버변수 중 모든 인스턴스에 공통으로 사용하는 것에 static을 붙임
- 기능(function)의 여러가지 용어 : 메서드, 함수, 행위
- 인스턴스의 메서드를 사용하려면 '참조변수.메서드'를 사용하면 됨
- 멤버변수와 메서드를 선언하는데 순서는 관계 없지만, 일반적으로 멤버변수를 먼저 선언하고 멤버 변수는 멘버변수끼리 메서드는 메서드끼리 모아놓는것이 일반적임
// 1. 클래스 TV 생성
class Tv {
// 속성 (= 멤버변수)
String color; // 색깔
boolean power; // 전원상태
int channel; // 채널
// 기능 (= 메서드)
void power() {power =!power;} // Tv를 켜거나 끄는 기능을 하는 메서드
void channelUp() {++channel;} // Tv의 채널을 높이는 기능을 하는 메서드
void channelDown() {--channel;} //Tv의 채널을 낮추는 기능을 하는 메서드
}
// 2. 객체의 생성
클래스명 변수명; // 클래스의 객체를 참조하기 위한 참조변수 선언
변수명 = new 클래스명(); // 클래스의 객체를 생성 후, 객체의 주소를 참조변수에 저장
// ex.1 객체 생성
// 참조변수 t 는 리모컨 역할임.
Tv t; // TV 클래스 타입의 참조변수 t를 선언.
t = new Tv(); // Tv 인스턴스를 생성한 후, 생선된 Tv 인스턴스의 주소를 t에 저장
// ex.2 객체 생성 한줄로 작성
Tv t = new Tv();
// 3. 객체의 사용
t.channel = 7; // Tv 인스턴스의 멤버변수 channel 의 값을 7로 함
t.channelDown(); // Tv 인스턴스의 메서드 channelDown()을 호출
System.out.println(t.channel) // 6
예제
class Tv{
// Tv의 멤버변수
String color; // 색상
boolean power; // 전원 상태(on/off)
int channel; // 채널
// Tv의 메서드
void power() {power = !power;} // tv를 켜거나 끔
void channelUp() {++channel;} // tv 채널을 올려줌
void channelDown() {--channel;} // tv 채널을 내려줌
}
class Tvtest{
public static void main(String args[]) {
Tv t1 = new Tv();
Tv t2 = new Tv();
System.out.println("[변경 전]");
System.out.println(t1.channel); // 0
System.out.println(t2.channel); // 0
System.out.println();
t1.channelUp();
t2.channel = 7;
t2.channelDown();
System.out.println("[변경 후 1]");
System.out.println(t1.channel); // 1
System.out.println(t2.channel); // 6
System.out.println();
t1 = t2; // t2가 저장하고 있는 값(주소)을 t1에 저장함. t1의 원래 참조 인스턴스는 사용할 수 없음.
System.out.println("[변경 후 2]");
System.out.println(t1.channel); // 6
System.out.println(t2.channel); // 6
System.out.println();
t1.channel = 10;
System.out.println("[변경 후 3]");
System.out.println(t1.channel); // 10
System.out.println(t2.channel); // 10
System.out.println();
t2.channel = 30;
System.out.println("[변경 후 4]");
System.out.println(t1.channel); // 30
System.out.println(t2.channel); // 30
}
}
객체와 인스턴스
- 객체 : 모든 인스턴스를 대표하는 일반적인 용어
- 인스턴스 : 특정 클래스로부터 생성된 객체 (예: Tv 인스턴스 )
- 인스턴스는 참조변수를 통해서만 다룰 수 있으며, 참조변수의 타입은 인스턴스의 타입과 일치해야함
소스파일 이름 바꾸는 방법
- 소스파일 우클릭 → Refactor → Rename → 소스파일 이름 수정 → Finish
메인 클래스(메인 메서드)를 수동으로 지정하기
- Run 카테고리 → Run Configurations → Main class 에 적기
객체 배열
- 객체 배열 == 참조변수 배열
- 객체 배열 안에는 객체가 저장되는 것이 아니고, 객체의 주소가 저장됨
// 객체 배열의 생성
Tv[] tvArr = new Tv[3]; // 길이가 3인 Tv 타입의 참조변수 배열
// 객체 배열의 생성 따로 초기화 따로
Tv[] tvArr = new Tv[3]; // 길이가 3인 Tv 타입의 참조변수 배열
tv Arr[0] = new Tv();
tv Arr[1] = new Tv();
tv Arr[2] = new Tv();
// 객체 배열의 생성과 초기화를 한 줄로 작성
Tv[] tvArr = new Tv[3]; // 길이가 3인 Tv 타입의 참조변수 배열
Tv[] tvArr = { new Tv(), new Tv(), new Tv() };
// 객체의 수가 많을 때 ㅡ for문 사용
Tv[] tvArr = new Tv[100];
for(int i=0; i<tvArr.length; i++) {
tvArr[i] = new Tv();
}
예제
class Tv{
String color;
boolean power;
int channel;
void power() {power = ![power;}
void channelUp() {++channel;}
void channelDown() {--channel;}
};
class Tvtest{
public static void main(String args[]) {
Tv[] tvArr = new Tv[3];
for(int i=0; i<tvArr.length; i++){
tvArr[i] = new Tv();
tvArr[i].channel = i+10;
}
for(int i=0; i<tvArr.length; i++){
tvArr[i].channelUp();
System.out.println(tvArr[i].channel);
}
}
}
클래스
- 클래스 정의 : 객체를 정의해 놓은것
- 객체의 설계도
- 데이터 + 함수 → 구조체 + 함수
- 사용자 정의타입 → 사용자가 원하는 타입을 직접 만들 수 있음. 클래스가 곧 사용자 정의 타입
- 클래스의 용도는 : 객체를 생성하는데 사용
- 클래스(=설계도)가 필요한 이유 : 객체(=제품)를 생성하기 위해
- 한개의 소스 파일에는 하나의 클래스만 작성하는것이 바람직함
- 하나의 소스 파일안에 클래스가 여러개일 경우, 메인 메서드가 있는 클래스 이름을 소스파일 이름으로 해야함
- 클래스 생성 방법
- 변수 : 하나의 데이터를 저장할 수 있는 공간
- 배열 : 같은 종류의 여러 데이터를 하나로 저장할 수 있는 공간
- 구조체 : 서로 관련 여러 데이터(종류 상관없음)를 하나로 저장할 수 있는 공간
- 클래스 : 데이터와 함수의 결합 (구조체 + 함수)
클래스 내 선언 위치에 따른 변수의 종류
- 변수의 선언위치가 변수의 종류가 범위(scope)를 결정함
- 클래스 내 2가지 영역 존재
- 클래스 영역 ㅡ iv 변수(인스턴스변수), cv 변수(클래스변수)
- 메서드 영역 ㅡ lv 변수(지역변수)
- 클래스 영역에는 선언문만 쓸 수 있음
- 변수 선언 또는 메서드 선언만 가능
- 클래스 영역, 메서드 영역은 순서를 따지지 않음
인스턴스 변수 iv
- 각 인스턴스는 독립적인 저장공간을 가지므로 서로 다른 값을 가질 수 있음
- 인스턴스 생성 후, ' 참조변수.인스턴스변수명 ' 으로 접근
- 인스턴스를 생성 할 때, 생성되고, 참조변수가 없을 때 gc(가비지컬렉터)에 의해 자동 제거됨
- 인스턴스가 생성될 때마다 생성되므로 인스턴스마다 각기 다른 값을 가질 수 있음
- 객체 = iv를 묶어놓은 것
- 인스턴스마다 고유한 상태를 유지해야하는 속성의 경우, iv로 선언할 것
클래스 변수 cv
- iv 변수 앞에 static을 붙이면 cv가 됨
- cv는 모든 iv가 공통된 저장공간(변수)을 공유함.
- 같은 클래스의 모든 인스턴스들이 공유하는 변수
- 인스턴스 생성없이 ' 클래스이름.클래스변수명 ' 으로 접근
- ex) Variables 클래스의 클래스 변수 cv를 사용하려면 ' Variables.cv ' 이렇게 쓸 것
- 클래스가 로딩될 때 생성되고 프로그램이 종료될 때 소멸
- 클래스 변수는 iv 변수와 달리 인스턴스를 생성하지 않고도 언제라도 바로 사용할 수 있음
- 클래스 변수는 모든 인스턴스가 하나의 저장공간을 공유하므로 항상 공통된 값을 가짐
- 클래스의 모든 인스턴스들이 공통적인 값을 유지해야하는 속성의 경우 cv로 선언할 것
- public 을 앞에 붙이면 전역변수 성격을 가짐
- 카드 내에서 숫자와 모양은 인스턴스 변수에 해당. 카드 크기나 폭은 클래스 변수에 해당됨
- iv는 인스턴스가 생성될 때 마다 생성되므로 인스턴스마다 각기 다른 값을 유지할 수 있음
- cv는 모든 인스턴스가 하나의 저장공간을 공유하므로 항상 공통된 값을 가짐
// 클래스 변수와 인스턴스 변수
class Card{
// 인스턴스 변수 선언
String kind;
int number;
// 클래스 변수 선언
static int width = 100;
static int height = 250;
}
// 객체 생성
Card c = new Card();
// 인스턴스 변수 사용
c.kind = "HEART";
c.number = 5;
// 클래스 변수 사용
Card.width = 200;
Card.hegith = 300;
예제
class Cardtest{
public static void main(String args[]) {
Card c1 = new Card();
c1.kind = "Heart";
c1.number = 7;
Card c2 = new Card();
c2.kind = "Spade";
c2.number = 4;
System.out.printf("c1 : %s %d (%d %d)%n", c1.kind, c1.number, Card.width, Card.height);
// c1 : Heart 7 (100 250)
System.out.printf("c2 : %s %d (%d %d)%n", c2.kind, c2.number, Card.width, Card.height);
// c2 : Spade 4 (100 250)
// cv는 모든 인스턴스가 하나의 저장공간을 공유하므로 cv 수정시 모든 인스턴스의 cv가 수정됨
c1.width = 50;
c1.height = 80;
System.out.printf("c1 : %s %d (%d %d)%n", c1.kind, c1.number, Card.width, Card.height);
// c1 : Heart 7 (50 80)
System.out.printf("c2 : %s %d (%d %d)%n", c2.kind, c2.number, Card.width, Card.height);
// c2 : Spade 4 (50 80)
}
}
class Card{
String kind;
int number;
static int width = 100;
static int height = 250;
}
지역변수 lv
- 조건문, 반복문의 블럭{ } 내에 선언된 지역변수는 블럭을 벗어나면 소멸
- 메서드 내에서만 사용 가능
- 메소드 종료시 소멸
메서드
- 메서드란, 문장들을 묶어놓은 것
- 메서드 = 선언부 +구현부
- 메서드의 반환값 개수는 무조건 0~1개
- 메서드의 반환값 개수를 여러개로 하고 싶으면 배열을 이용하거나 객체로 묶어서 줘야함
- 메서드는 입력값 또는 출력값이 없을 수도 있으며, 심지어는 입력값과 출력값 모두 없을 수도 있음
- 매개변수도 메서드 내에 선언된 것으로 간주되어 lv임
- 인자의 타입은 매개변수의 타입과 일치하거나 자동형변환이 가능해야함
- 같은 클래스 내의 메서드끼리는 참조변수를 사용하지 않고도 서로 호출이 가능하지만 static 메서드는 같은 클래스 내의 인스턴스 메서드를 호출할 수 없음
- 메서드 사용 이유
- 높은 재사용성
- 중복된 코드 제거
- 프로그램의 구조화
메서드 예시)
// ex.1
public static void main(String args[]){
MyMath mm = new MyMath();
long result1 = mm.add(5L, 3L); // 8
long result2 = mm.subtact(5L, 3L); // 2
long result3 = mm.multiply(5L, 3L) // 15
double result4 = mm.divide(5, 3); // 1.66666
System.out.printf("%d %d, %d, %f", result1, result2, result3, result4);
}
class MyMath{
long add(long a, long b){
return a+b;
}
long subtact(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};
}
반환타입
- 반환값이 없는 경우 반환타입으로 'void'를 적어야함
return문
- 메서드의 반환타입이 'void'가 아닌 경우, 구현부{ } 안에 'return 반환값;' 이 반드시 포함되어야함
JVM의 메모리 구조
메서드 영역 (method area)
- 프로그램 실행 중 어떤 클래스가 사용되면, JVM은 해당 클래스의 클래스파일(*.class)을 읽어서 분석하여 클래스에 대한 정보(클래스 데이터)를 이곳에 저장함
- 이 때, 그 클래스의 클래스 변수도 이 영역에 함께 생성됨
호출스택 (call stack 또는 execution stack)
- 메서드의 작업에 필요한 메모리 공간 제공
- 메서드가 호출되면, 호출스택에 호출된 메서드를 위한 메모리가 할당되며, 이 메모리는 메서드가 작업을 수행하는 동안 지역변수(매개변수 포함)들과 연산의 중간결과 등을 저장하는데 사용됨
- 메서드가 작업을 마치면 할당되었던 메모리공간은 반환되어 비워짐
- 호출스택의 제일 위에 있는 메서드가 현재 실행 중인 메서드임
- 아래에 있는 메서드가 바로 위의 메서드를 호출한 메서드임
- 반환타입(return type)이 있는 메서드는 종료되면서 결과값을 자신을 호출한 메서드(caller)에게 반환함
힙 (heap)
- 인스턴스가 생성되는 공간
- 프로그램 실행 중 생성되는 인스턴스는 모두 이곳에 생성됨
- 인스턴스 변수들이 생성되는 공간
예제
public static void main(String[] args) {
System.out.println("main(String[] args)이 시작되었음");
firstMethod();
System.out.println("main(String[] args)이 끝났음");
}
static void firstMethod() {
System.out.println("firstMethod()가 시작되었음");
secondMethod();
System.out.println("firstMethod()가 끝났음");
}
static void secondMethod() {
System.out.println("secondMethod()가 시작되었음");
System.out.println("secondMethod()가 끝났음");
}
/* 출력값
main(String[] args)이 시작되었음
firstMethod()가 시작되었음
secondMethod()가 시작되었음
secondMethod()가 끝났음
firstMethod()가 끝났음
main(String[] args)이 끝났음
*/
기본형 매개변수와 참조형 매개변수
- 메서드를 호출할 때 매개변수로 지정한 값을 메서드의 매개변수에 복사해서 넘겨줌
- 매개변수 타입이 기본형일 때는 기본형이 복사됨
- 매개변수 타입이 참조형일 때는 인스턴스 주소가 복사됨
- 메서드의 매개변수를 기본형으로 선언하면 단순히 저장된 값만 얻지만, 참조형으로 선언하면 값이 저장된 곳의 주조를 알 수 있기 때문에 값을 읽어 오는것은 물론 값을 변경하는 것도 가능함
기본형 매개변수
- 변수의 값을 읽기만 할 수 있음 (read only)
참조형 매개변수
- 변수의 값을 읽고 변경할 수 있음 (read & write)
- 매개변수가 참조형일 경우 값이 아니라 값이 저장된 주소를 메서드에게 넘겨주기 때문에 값을 변경할 수도 있음
/* ex.1 기본형 매개변수 */
class Data {int x;}
class 파일명{
public static void main (String[] agrs){
Data d = new Data();
d.x = 10;
System.out.println(d.x); // 10
changed(d.x); // 1000
System.out.println("changed(d.x) 함수 후"); // changed(d.x) 함수 후
System.out.println(d.x); // 10
// d.x 값은 changed(d.x)에 의해서 값이 바뀌지 않음
}
static void changed(int x){
x = 1000;
System.out.println(x);
}
}
/* ex.2 참조형 매개변수 */
class Data {int x;}
class 파일명{
public static void main (String[] args){
Data d = new Data();
d.x = 10;
System.out.println(d.x); // 10
changed(d); // 1000
System.out.println("changed(d) 실행 후"); // changed(d) 실행 후
System.out.println(d.x); // 1000
}
static void changed(Data d){
d.x = 1000;
System.out.println(d.x);
}
}
/* ex.3 참조형 매개변수 */
class 파일명{
public static void main(String[] args){
int[] x = {10};
System.out.println(x[0]); // 10
changed(x); // 1000
System.out.println("changed(x) 실행 후"); // changed(x) 실행 후
System.out.println(x[0]); // 1000
}
static void changed(int[] x){
x[0] = 1000;
System.out.println(x[0]);
}
}
예제
/* ex.1 */
public static void main (String[] args){
int[] arr = {3,2,1,6,5,4};
printArr(arr); // 배열의 모든 요소 출력
sortArr(arr); // 배열 정렬
printArr(arr); // 정렬후 결과 출력
System.out.println(sumArr(arr));
}
static void printArr(int[] arr){
for(int i : arr) System.out.print(i);
System.out.println();
}
static int sumArr(int[] arr){
int sum = 0;
for(int i=0; i<arr.length; i++) sum += arr[i];
return sum;
}
static void sortArr(int[] arr){
for(int i=0; i<arr.length-1; i++){
for(int j=0; j<arr.length-1-i; j++){
if(arr[j] > arr[j+1]){
int tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
} // end of if
} // end for j
} // end for i
}
/* ex.2 */
// 반환값이 있는 메서드를 반환값이 없는 메서드로 바꾸는 방법
// 참조형 매개변수를 활용하여 반환값이 없어도 메서드의 실행결과를 얻어 올 수 있음
// 메서드는 하나의 값만 반환할 수 있지만 이것을 응용하면 여러 개의 값을 반환받는 것과 같은 효과임
class prac{
public static void main(String[] args){
prac p = new prac();
int result = p.add(3, 5);
System.out.println(result);
int[] result2 ={0};
p.add(3, 5, result2);
System.out.println(result2[0]);
}
int add(int a, int b) {return a+b;}
void add(int a, int b, int[] result) {result[0] = a + b;}
}
참조형 반환타입
- 매개변수뿐만 아니라 반환타입도 참조형이 될 수 있음
- 반환타입이 '참조형'이라는 것은 메서드가 '객체의주소'를 반환한다는 것을 의미
/* ex.1 */
class Date {int x;}
class prac{
public static void main(String[] args){
Data d = new Data();
d.x = 10;
Data d2 = copy(d);
System.out.println(d.x); // 10
System.out.println(d2.x); // 10
}
static Data copy(Data d){
Data tmp = new Data();
tmp.x = d.x;
return tmp;
}
}
재귀호출 (recursive call)
- 메서드의 내부에서 메서드 자신을 다시 호출하는 것을 '재귀호출' 이라고 함
- 재귀호출을 하는 메서드를 '재귀 메서드'라고 함
- 재귀호출은 반복문으로 바꿀 수 있으며 반복문보다 성능이 나쁨
- 재귀호출을 사용하는 이유
- 논리적 간결함
- 효율이 좋고 알아보기 힘들게 작성하는것보다 다소 비효율적이더라도 알아보기 쉽게 작성하는 것이 논리적 오류가 발생할 확률이 줄어들고 나중에 수정하기도 좋음
- 이해하기 쉽고 간결한 코드로 작성할 수 있음
- 재귀호출은 반복문과 유사함
- 재귀호출은 반복문보다 수행시간이 더 오래 걸림
- 재귀호출에 드는 비용보다 재귀호출의 간결함이 주는 이득이 큰 경우에만 사용할 것
- 재귀호출을 쓸 때 조건문이 필수임
- 재귀호출의 예
- 팩토리얼, 제곱, 트리운행, 폴더목록표시 등
void method() {
method(); // 재귀호출. 메서드 자신을 호출함
}
예제
/* ex.1 팩토리얼 */
public static void main(String[] args){
int result = factorial(4);
System.out.println(result); // 24
}
static int factorial(int n) {
// n이 0보다 커야하며, 12보단 작아야함. 13보다 크면 int형 최대값을 초과함
if (n<=0 || n>12) return -1; // 매개변수 n의 유효성 검사
if(n==1) return 1;
else return n * factorial(n-1);
}
/* ex.2 팩토리얼 */
static long factorial(int n){
if(n<0 || n>20) return -1; // 매개변수 유효성 검사
if (n==1) return 1;
else return n * factorial(n-1);
}
public static void main(String[] args){
long result = 0;
int n = 21;
for(int i =1; i<=n; i++){
result = factorial(i);
if(result == -1){
System.out.printf("유효하지 않는 수입니다. (1<=n<=20) : %d%n", n);
break;
}
System.out.printf("%2d != %20d%n", i, result);
}
}
/* 출력값
1 != 1
2 != 2
3 != 6
4 != 24
5 != 120
6 != 720
7 != 5040
8 != 40320
9 != 362880
10 != 3628800
11 != 39916800
12 != 479001600
13 != 6227020800
14 != 87178291200
15 != 1307674368000
16 != 20922789888000
17 != 355687428096000
18 != 6402373705728000
19 != 121645100408832000
20 != 2432902008176640000
유효하지 않는 수입니다. (1<=n<=20) : 21
*/
/* ex.3 제곱 */
public static void main(String[] args){
int x = 2;
int n = 5;
long result = 0;
for(int i=1; i<=n; i++) result += power(x, i);
System.out.println(result); // 62
}
static long power(int x, int n){
if(n==1) return x;
return x * power(x, n-1);
}
클래스 메서드(static 메서드)와 인스턴스 메서드
- 메서드 앞에 static이 붙어있으면 클래스 메서드. 그렇지 않으면 인스턴스 메서드
- 같은 클래스에 속한 멤버들 간에는 별도의 인스턴스를 생성하지 않고도 서로 참조 또는 호출이 가능함
- 단, 클래스멤버가 인스턴스 멤버를 참조 또는 호출하고자 하는 경우에는 인스턴스를 생성해야 함
- 인스턴스 멤버가 존재하는 시점에 클래스 멤버는 항상 존재하지만. 클래스 멤버가 존재하는 시점에 인스턴스 멤버가 존재하지 않을 수 있기 때문
- 인스턴스 멤버란, 인스턴스 변수와 인스턴스 메서드를 의미
/* ex.1 */
class prac{
void instaceMethod() {} // 인스턴스 메서드
static void staticMethod() {} // static 메서드
void instanceMethod2(){ // 인스턴스 메서드
instanceMethod(); // 다른 인스턴스 메서드 호출
staticMethod(); // static 메서드 호출
}
static void staticMethod2() { // static 메서드
// instanceMethod(); // 에러. 인스턴스 메서드를 호출할 수 없음
staticMethod(); // static 메서드는 호출 할 수 있음
}
}
/* ex.2 */
class prac{
int iv; // 인스턴스 변수
static int cv; // 클래스 변수
void instanceMethod(){ // 인스턴스 메서드
System.out.println(iv); // 인스턴스 변수 사용 가능
System.out.println(cv); // 클래스 변수 사용 가능
}
static void staticMethod(){ // static 메서드
// System.out.println(iv); // 에러. 인스턴스 변수 사용 불가능
System.out.println(cv); // 클래스 변수 사용 가능
}
}
/* ex.3 */
class prac2{
int iv = 10; // 인스턴스 변수
static int cv = 20; // 클래스 변수
int iv2 = cv;
// static int cv2 = iv; // 에러. 클래스변수는 인스턴스 변수를 사용할 수 없음
static int cv2 - new prac2().iv; // 객체를 생성해야 쓸 수 있음
static void staticMethod1(){ // static 메서드
System.out.println(cv);
// System.out.println(iv); // 에러. 클래스메서드에서 인스턴스변수 사용 불가
prac2 p = new prac2();
System.out.println(p.iv); // 객체를 생성한 후에야 인스턴스 변수 참조 가능
}
void instanceMethod1() { // 인스턴스 메서드
System.out.println(cv);
System.out.println(iv); // 인스턴스메서드에서 인스턴스 변수 사용 가능
}
static void staticMethod2() { // static 메서드
staticMethod1();
// instanceMethod1(); // 에러. 클래스메서드에서 인스턴스 메서드 호출 불가능
prac2 p = new prac2();
p.instanceMethod1(); // 인스턴스를 생성한 후에 인스턴스 메서드를 호출 할 수 있음
}
// 인스턴스메서드에서 인스턴스메서드와 클래스메서드 모두 인스턴스 생성없이 호출 가능
void instanceMethod2() { // 인스턴스 메서드
staticMethod1(); //
instanceMethod1();
}
}
클래스 메서드 (static 메서드)
- 클래스 메서드도 클래스변수처럼, 객체를 생성하지 않고도 '클래스이름.메서드이름(매개변수)' 로 호출 할 수 있음
- 클래스는 '데이터(변수)와 데이터에 관련된 메서드의 집합'임
- 같은 클래스 내에 있는 메서드와 멤버변수는 아주 밀접한 관계가 있음
- 인스턴스와 관계없는(인스턴스 변수나 인스턴스 메서드를 사용하지 않는) 메서드를 클래스 메서드로 정의함
- 클래스 메서드(static 메서드)는 인스턴스 변수를 사용할 수 없음
- 클래스 메서드(static 메서드)는 인스턴스 메서드보다 호출시간이 짧음, 성능 향상
- 메서드 중에서 인스턴스 변수나 인스턴스 메서드를 사용하지 않는 메서드에 static을 붙일 것을 권장
- 메서드의 작업내용 중에서 인스턴스 변수를 필요로 한다면, static을 붙을 수 없음
인스턴스 메서드
- 인스턴스 메서드는 반드시 객체를 생성해야만 호출할 수 있음
- 인스턴스 메서드는 인스턴스 변수와 관련된 작업을 하는, 즉 메서드의 작업을 수행하는데 인스턴스 변수를 필요로 하는 메서드임
- 인스턴스 변수나 인스턴스 메서드에서 static이 붙은 멤버들을 사용하는것은 언제나 가능
- 인스턴스 변수가 존재한다는 것은 static변수가 이미 메모리에 존재한다는 것을 의미
예제
/* ex.1 */
class myMath{
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(double a, double b) {return a/b;}
}
class prac{
public static void main(String[] args){
//클래스메서드 호출. 인스턴스 생성 없이 호출 가능
System.out.println(myMath.add(200L, 100L)); // 300
System.out.println(myMath.subtract(200L,100L)); // 100
System.out.println(myMath.multiply(200L, 100L)); // 20000
System.out.println(myMath.divide(200.0, 100.0)); // 2.0
myMath mm = new myMath(); // 인스턴스 생성
mm.a = 200L;
mm.b = 100L;
// 인스턴스메서드는 객체 생성 후에 호출 가능
System.out.println(mm.add()); // 300
System.out.println(mm.subtract()); // 100
System.out.println(mm.multiply()); // 20000
System.out.println(mm.divide()); // 2.0
}
}
오버로딩 (overloading)
- 한 클래스 내에 같은 이름의 메서드를 여러 개 정의하는 것
- 한 클래스 내에 이미 사용하는 이름과 같은 이름을 가진 메서드가 있더라도 매개변수의 개수 또는 타입이 다르면, 같은 이름을 사용해서 메서드를 정의할 수 있음
- 같은 이름의 메서드를 정의한다고 해서 무조건 오버로딩이 아님
- 오버로딩 조건
- 메서드 이름이 같아야함
- 매개변수의 개수 또는 타입이 달라야함
- 매개변수는 같고 리턴타입이 다른 경우에는 오버로딩이 성립되지 않음
- 오버로딩된 매서드들은 매개변수에 의해서만 구별할 수 있음
- 반환 타입은 오버로딩을 구현하는데 아무런 영향을 주지 못함
- 오버로딩 장점
- 오버로딩을 통해 여러 메서드를 하나의 이름으로 정의할 수 있음
- 메서드 이름 절약
오버로딩 예
예제
/* ex.1 */
class prac{
public static void main(String[] args){
myMath mm = new myMath();
System.out.println("쿠쿠 " + mm.add(3,3)); // int add(int a, int b) 쿠쿠 6
System.out.println("카카 " + mm.add(3L,3)); // long add(long a, int b) 카카 6
System.out.println("우우 " + mm.add(3,3L)); // long add(int a, long b) 우우 6
System.out.println("아아 " + mm.add(3L,3L)); // long add(long a, long b) 아아 6
// 출력 순서가 바뀐 이유는 println 메서드가 결과를 출력 하려면
// add 메서드의 결과가 먼저 계산되어야 하기 때문
int[] a ={100, 200, 300};
System.out.println("유유 "+ mm.add(a)); // int add(int[] a) 유유 600
}
}
class myMath{
int add(int a, int b){
System.out.print("int add(int a, int b) ");
return a+b;
}
long add(long a, int b){
System.out.print("long add(long 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, 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;
}
}
가변인자(varargs)와 오버로딩
- 매개변수가 동적으로 지정될 수 있도록 하는 기능을 가변인자(variable arguments)라고 함
- 가변인자 외에도 매개변수가 더 있다면, 가변인자를 매개변수 중에서 제일 마지막에 선언해야 함
- 가변인자는 내부적으로 배열을 이용하는 것임
- 가변인자가 선언된 메서드를 호출할 때마다 배열이 새로 생성됨
- 비효율적이므로 꼭 필요한 경우에만 가변인자를 사용할 것
- 가변인자를 사용한 메서드는 오버로딩을 하지 않는것이 좋음
가변인자와 매개변수의 타입을 배열로 하는것의 차이점은?
- 매개변수의 타입을 배열로 하면 반드시 인자를 지정해 줘야함
- 매개변수 인자를 생략할 수 없기에 null 이나 길이가 0인 배열을 인자로 지정해줘야하는 불편함이 있음
/* 매개변수 */
String concatenate(String[] str){
return "String";
}
String result = concatenate(new String[0]); // 인자로 길이가 0인 배열을 지정
String result = concatenate(nill); // 인자로 null을 지정
// String result = concatenate(); // 에러. 인자가 필요함
가변인자 예제
/* ex.1 */
class prac {
public static void main(String[] args){
String[] strArr = {"100", "200", "300"};
System.out.println(concatenate(" ", "100", "200", "300")); // 100 200 300
System.out.println(concatenate("-", strArr)); // 100-200-300-
System.out.println(concatenate(",", new String[]{"1", "2", "3"})); // 1,2,3,
System.out.println("[" + concatenate(",", new String[0])+ "]"); // []
System.out.println("[" + concatenate(",")+ "]"); // []
}
static String concatenate(String delim, String... args){ // 가변인자 사용
String result = "";
for(String str : args){
result += str + delim;
}
return result;
}
}
생성자
- 생성자란 인스턴스가 생성될 때 호출되는 '인스턴스 변수 초기화 메서드'
- 인스턴스 변수의 초기화 작업에 주로 사용
- 모든 클래스에는 반드시 하나 이상의 생성자가 있어야 함
- 인스턴스 생성 시에 실행되어야 할 작업을 위해서 사용되기도 함
- 생성자는 클래스 내에 선언되며 메서드 구조와 유사함
- 생성자의 이름은 클래스의 이름과 같아야 함
- 생성자는 리턴값이 없으나 void는 생략함
- 생성자도 오버로딩이 가능하므로 하나의 클래스에 여러개의 생성자가 존재할 수 있음
- 컴파일 할 때, 소스파일(*.java)의 클래스에 생성자가 하나도 정의되지 않는 경우 컴파일러는 자동적으로 기본 생성자를 추가하여 컴파일함
- 컴파일러가 자동적으로 기본 생성자를 추가해주는 경우는 클래스 내에 생성자가 하나도 없을때 뿐임
- 연산자 new가 인스턴스를 생성하는 것이지 생성자가 인스턴스를 생성하는것이 아님. '클래스()' 가 생성자임
기본 생성자
- 매개변수가 없는 생성자
- 클래스에 생성자가 하나도 없으면 컴파일러가 기본 생성자를 추가함
- 생성자가 하나라도 있으면 컴파일러는 기본 생성자를 추가하지 않음
- 클래스에 생성자를 정의할 때는 반드시 내용이 없는 기본 생성자도 함께 정의해주는 것이 좋음
class card{
// 기본 생성자
card(){} // 클래스명() {}
}
예제
/* ex.1 */
class Data1{
int value;
}
class Data2{
int value;
Data2(int x){ // 매개변수가 있는 생성자
value = x;
}
}
class prac{
public static void main(String[] args){
Data1 d1 = new Data1(); // 기본 생성자가 추가됨
// Data2에는 이미 Data2(int x)가 정의되어있어서 기본 생성자가 추가되지 않음
// Data2 d2 = new Data2(); // 에러. 매개변수를 입력하지 않음
Data2 d2 = new Data2(10);
}
}
매개변수가 있는 생성자
- 생성자도 메서드처럼 매개변수를 선언하여 호출 시 값을 넘겨받아서 인스턴스의 초기화 작업에 사용할 수 있음
- 인스턴스마다 각기 다른 값으로 초기화되어야하는 경우가 많기 때문에 매개변수를 사용한 초기화는 매우 유용함
- 인스턴스를 생성한 다음에 인스턴스 변수의 값을 변경하는 것보다 매개변수를 갖는 생성자를 사용한것이 더 간결하고 직관적임
예제
class Car{
String color;
String gearType;
int door;
Car(){};
Car(String c, String g, int d){
color = c;
gearType = g;
door = d;
}
}
class prac{
public static void main(String[] args){
Car c1 = new Car();
c1.color = "white";
c1.gearType = "auto";
c1.door = 4;
Car c2 = new Car("Black", "auto", 2);
System.out.printf("c1 %s %s %d%n", c1.color, c1.gearType, c1.door); // c1 white auto 4
System.out.printf("c2 %s %s %d%n", c2.color, c2.gearType, c2.door); // c2 Black auto 2
}
}
생성자에서 다른 생성자 호출하기 - this, this()
- 같은 클래스의 멤버들 간에 서로 호출할 수 있는 것처럼 생성자 간에도 서로 호출이 가능
- 다른 생성자 호출시, 생성자의 이름으로 클래스 이름 대신 this를 사용해야함
- 한 생성자에서 다른 생성자를 호출할 때는 반드시 첫 줄에만 호출이 가능함
- 생성자의 매개변수로 선언된 변수의 이름이 인스턴스변수와 같을 경우 인스턴스 변수 앞에 this 를 붙여줌
- 'this.인스턴스변수명' 은 인스턴스 변수임. 생성자의 매개변수인 지역변수와 구별이 가능
- this는 참조변수로 인스턴스 자신을 가리킴. 인스턴스 주소가 저장되어 있음
- this를 사용할 수 있는건 인스턴스멤버뿐
- static메서드에서는 인스턴스 멤버들을 사용할 수 없는것처럼, this 역시 사용할 수 없음
- static 메서드는 인스턴스를 생성하지 않고도 호출될 수 있으므로 static 메서드가 호출된 시점에 인스턴스가 존재하지 않을수도 있기 때문
- this는 인스턴스 자신을 가리키는 참조변수, 인스턴스의 주소가 저장되어 있음
- 모든 인스턴스 메서드에 지역변수로 숨겨진채로 존재함
- this(), this(매개변수)는 생성자. 같은 클래스의 다른 생성자를 호출할 때 사용함
- this는 참조변수, this()는 생성자
예제
/* ex.1 */
class Car{
String color;
String gearType;
int door;
Car(){
this("white", "auto", 4);
}
Car(String color){
this(color, "auto", 4);
}
Car(String color, String gearType, int door){
this.color = color; // this.color는 인스턴스 변수. color는 생성자의 매개변수로 정의된 지역변수
this.gearType = gearType;
this.door = door;
}
}
class prac{
puㅠlic static void main(String[] args){
Car c1 = new Car();
Car c2 = new Car("blue");
System.out.printf("c1 %s %s %d%n", c1.color, c1.gearType, c1.door); // c1 white auto 4
System.out.printf("c2 %s %s %d%n", c2.color, c2.gearType, c2.door); // c2 blue auto 4
}
}
생성자를 이용한 인스턴스의 복사
- 인스턴스와 같은 상태를 갖는 인스턴스를 하나 더 만들고자 할 때 생성자를 이용함
- 생성자에서 참조변수를 매개변수로 받아서 인스턴스변수들의 값을 복사
- 인스턴스를 생성할 때 다음 2가지 사항을 결정 해야함
- 클래스 ㅡ 어떤 클래스의 인스턴스를 생성할 것인가
- 생성자 ㅡ 선택한 클래스의 어떤 생성자로 인스턴스를 생성할 것인가
/* ex.1 */
class Car{
String color;
String gearType;
int door;
Car(){
this("white", "auto", 4);
}
Car(Car c){ // 인스턴스의 복사를 위한 생산자
this(c.color, c.gearType, c.door);
}
Car(String color, String gearType, int door){
this.color = color;
this.gearType = gearType;
this.door = door;
}
}
class prac{
public static void main(String[] args){
Car c1 = new Car();
Car c2 = new Car(c1); // c1의 본사본 c2 생성
System.out.printf("c1 %s %s %d%n", c1.color, c1.gearType, c1.door); // c1 white auto 4
System.out.printf("c2 %s %s %d%n", c2.color, c2.gearType, c2.door); // c2 white auto 4
System.out.println("변경 후"); // 변경 후
c1.door = 100;
System.out.printf("c1 %s %s %d%n", c1.color, c1.gearType, c1.door); // c1 white auto 100
System.out.printf("c2 %s %s %d%n", c2.color, c2.gearType, c2.door); // c2 white auto 4
}
}
변수의 초기화
- 변수 초기화란 변수를 선언하고 처음으로 값을 저장하는 것
- 선언과 동시에 적절한 값으로 초기화하는것이 바람직
- 멤버변수는 초기화를 하지 않아도 자동적으로 변수의 자료형에 맞게 기본값으로 초기화 됨
- 지역변수는 사용하기 전에 반드시 초기화를 해야함
- 배열의 초기화는 선택적임
/* ex.1 */
class prac{
int x; // 인스턴스 변수
int y = x; // 인스턴스 변수
void method(){
int i; // 지역변수
// int j = i; // 에러. 지역변수를 초기화하지 않고 사용
}
}
멤버변수 초기화 방법
- 명시적 초기화
- 생성자
- 초기화 블럭
- 인스턴스 초기화 블럭 : 인스턴스 변수를 초기화하는데 사용
- 클래스 초기화 블럭 : 클래스 변수를 초기화 하는데 사용
명시적 초기화
- 명시적 초기화란. 변수를 선언과 동시에 초기화 하는 것
- 명시적 초기화가 간단하고 명료하지만, 보다 복잡한 초기화 작업이 필요할 때는 '초기화 블럭' 또는 '생성자'를 사용
class Car(){
int door = 4; // 기본형 변수의 초기화
Engin e = new Engine(); // 참조형 변수의 초기화
}
초기화 블럭
- 초기화 블럭에는 '클래스 초기화 블럭'과 '인스턴스 초기화 블럭' 두 가지 종류가 있음
- 클래스 초기화 블럭
- 클래스 변수의 초기화에 사용
- 인스턴스 초기화 블럭 앞에 단순히 static을 덧붙이기만 하면 됨
- 클래스 초기화 블럭은 클래스가 메모리에 처음 로딩될 때 한번만 수행
- 클래스가 처음 로딩 될 때 클래스 변수들이 자동적으로 메모리에 만들어지고, 곧바로 클래스 초기화 블럭이 클래스 변수들을 초기화하는것
- 인스턴스 초기화 블럭
- 인스턴스 변수의 초기화에 사용
- 단순히 클래스 내에 블럭{ }을 만들고 그 안에 코드를 작성하면 됨
- 인스턴스 초기화 블럭은 생성자와 같이 인스턴스를 생성할 때 마다 (생성자보다 먼저 )수행
- 인스턴스 변수의 초기화는 주로 생성자를 사용
- 인스턴스 초기화 블럭은 모든 생성자에서 공통으로 수행돼야하는 코드를 넣는데 사용함
- 초기화 블럭 내에서 메서드 내에서와 같이 조건문, 반복문, 예외처리구문 등 자유롭게 사용 가능
class prac{
static { /* 클래스 초기화 블럭 */ }
{ /* 인스턴스 초기화 블럭 */}
}
예제1
class prac{
static { // 클래스 초기화 블록 → 딱 1번만 실행
System.out.println("static { }");
}
{ // 인스턴스 초기화 블록 → 인스턴스가 생성될 때마다 (생성자보다 먼저) 실행
System.out.println("{ }");
}
public prac(){ // 생성자 → 인스턴스가 생성될 때마다 실행
System.out.println("생성자");
}
public static void main(String[] args){
prac p = new prac();
System.out.println();
prac p2 = new prac();
}
}
/* 출력값
static { }
{ }
생성자
{ }
생성자
*/
예제2
import java.util.Arrays;
class prac{
static int[] arr = new int[10]; // 명시적 초기화
static{ // 클래스 블럭 초기화
for(int i=0; i<arr.length; i++){
arr[i] = (int)(Math.random()*10) +1; // 1~10 까지 임의의 수 대입
}
}
public static void main(String[] args){
System.out.println(Arrays.toString(arr)); // [1, 3, 4, 9, 1, 8, 10, 8, 4, 1] 항상 값이 바뀜
}
}
배열이나 예외처리가 필요한 초기화에서는 명시적 초기화만으로는 복잡한 초기화 작업이 불가능.
이런 경우에 추가적으로 클래스 초기화 블럭을 사용할 것
멤버변수의 초기화 순서
- 클래스 변수의 초기화 순서
- 기본값 → 명시적 초기화 → 클래스 초기화 블럭
- 클래스 변수는 항상 인스턴스 변수보다 먼저 생성되고 초기화됨
- 인스턴스 변수 초기화 순서
- 기본값 → 명시적 초기화 → 인스턴스 초기화블럭 → 생성자
① cv가 메모리에 생성되고, cv에는 int형의 기본값인 0이 cv에 저장
② 명시적 초기화(int cv = 1)에 의해서 cv에 1이 저장
③ 클래스 초기화 블록(cv = 2)이 수행되어 cv에 2가 저장
④ InitTest 클래스가 인스턴스에 생성되면서 iv가 메모리(heap)에 존재, iv 역시 int형 변수이므로 기본값 0이 저장
⑤ 명시적 초기화에 의해서 iv에 1이 저장
⑥ 인스턴스 초기화 블럭이 수행되어 iv에 2가 저장
⑦ 마지막으로 생성자가 수행되어 iv에 3이 저장
예제1
class Product{
static int count = 0; // 생성된 인스턴스의 수를 저장하기 위한 변수
int serialNo; // 인스턴스 고유의 번호
{
++count;
serialNo = count;
}
public Product(){} // 기본 생성자, 생략 가능
}
class prac{
public static void main(String[] args){
Product p1 = new Product();
Product p2 = new Product();
Product p3 = new Product();
System.out.printf("p1 %d%n", p1.serialNo); // p1 1
System.out.printf("p2 %d%n", p2.serialNo); // p2 2
System.out.printf("p3 %d%n", p3.serialNo); // p3 3
System.out.printf("count %d%n", Product.count); // count 3
}
}
만약에 count를 인스턴스 변수로 선언했다면, 인스턴스가 생성될때 마다 0으로 초기화 될 것임
예제2
class Document{
static int count = 0;
String name; // 문서명
Document(){
this("제목없음" + ++count);
}
Document(String name){
this.name = name;
System.out.printf("문서 %s가 생성됨%n", this.name);
}
}
class prac{
public static void main(String[] args){
Document d1 = new Document(); // 문서 제목없음1가 생성됨
Document d2 = new Document(); // 문서 제목없음2가 생성됨
Document d3 = new Document("자바.txt"); // 문서 자바.txt가 생성됨
Document d4 = new Document(); // 문서 제목없음3가 생성됨
}
}
'[자바] > 자바의 정석 - 3판' 카테고리의 다른 글
Chapter08 예외처리 (0) | 2021.10.27 |
---|---|
Chapter07 객체지향2 (0) | 2021.10.17 |
Chapter.05 배열 (0) | 2021.08.29 |
Chapter04. 조건문과 반복문 (0) | 2021.08.27 |
Chapter03. 연산자 (0) | 2021.08.26 |