Back-end/JAVA

[JAVA] 객체지향 프로그래밍(1) - 클래스와 객체, 변수와 메서드, 오버로딩, 생성자, 초기화

K_EY 2023. 9. 9. 17:32

1. 클래스(Class)와 객체(Object)


클래스(Class)

: 객체를 정의한 것 ≒ 설계도, 틀

: 필드(Field)와 메서드(Method)의 결합

: 사용자 정의 타입

class TV {
	// 멤버변수
	String color;
	boolean power;
	int channel;
    
	// 메서드
	void power() {power = !power;}
	void channelUp() {++channel;}
	void channelDown() {--channel;}
}

 

 

객체(Object)

: 실제로 존재하는 것 ≒ 제품

: 속성(멤버변수)과 기능(메서드)으로 구성됨

 

 

인스턴스(Instance)

: 어떤 클래스로부터 생성된 객체

: 참조변수를 통해서만 다룰 수 있으며, 참조변수 타입은 인스턴스와 일치해야 함

: 하나의 참조변수가 여러 개의 인스턴스 가리키는 건 불가능

: 생성자를 통해 Heap 메모리에 생성됨

 

 

  • 생성 방법
클래스 참조변수; // 참조변수 선언
참조변수 = new 클래스(); // 생성된 객체의 주소를 참조변수에 저장

-> 클래스 참조변수 = new 클래스();
TV t;
t = new TV();
        
-> TV t = new TV();

 

 

객체 배열

: 많은 수의 객체를 다룰 때 사용

: 객체의 주소 저장

: 인스턴스의 참조변수들을 하나로 묶은 참조변수의 배열

 

 

  • 생성 방법

1) 객체를 다루기 위한 참조변수 배열 생성 -> 객체 생성하여 각 요소에 저장

클래스[] 참조변수 = new 클래스[길이]; // 참조변수 배열(객체 배열) 생성

// 객체를 생성해서 배열의 각 요소에 저장
참조변수[0] = new 클래스();
참조변수[1] = new 클래스();
. . .
참조변수[n] = new 클래스();
TV[] tvarr = new TV[3];

tvarr[0] = new TV();
tvarr[1] = new TV();
tvarr[2] = new TV();

 

 

2) 배열의 초기화 블록 사용

클래스[] 참조변수 = { new 클래스(),  new 클래스(), ... ,  new 클래스() };
TV[] tvarr = { new TV(), new TV(), new TV() };

 

 

3) for문 사용

클래스[] 참조변수 = new 클래스[길이];

for (int i = 0; i < 참조변수.length; i++) {
	참조변수[i] = new 클래스();
}
TV[] tvarr = new TV[100];

for (int i = 0; i < tvarr.length; i++) {
	tvarr[i] = new TV();
}

 

 

 

2. 변수와 메서드


변수의 선언위치가 변수의 종류와 범위를 결정한다.

 

 

선언위치에 따른 변수의 종류

변수 종류 선언 위치 생성 시기
인스턴스변수(instance variable, iv) 클래스 영역 인스턴스가 생성되었을 때
클래스변수(class variable, cv) 클래스가 메모리에 올라갈 때
지역변수(local variable, lv) 클래스 이외의 영역
(메서드, 생성자, 초기화 블럭 내부)
변수 선언문이 수행되었을 때
class Variables {
	int iv; // 인스턴스변수
	static int cv; // 클래스변수
	
	void method() {
		int lv = 0; // 지역변수
	}
}

 

 

인스턴스변수(instance variable)

: 클래스의 인스턴스를 생성할 때 만들어짐

: 각 인스턴스마다 다른 값 저장 가능

: 인스턴스 생성 후 사용 가능

: 인스턴스마다 고유한 상태를 유지해야 할 때 사용

: 인스턴스 생성 후, '참조변수.인스턴스변수명' 으로 접근

class Card {
	// 인스턴스변수. 각 Card 인스턴스마다 다른 값 가짐
	String kind;
	int number;
	
	static int width = 100;
	static int height = 250;
}

 

 

클래스변수(class variable)

: 인스턴스변수 앞에 static 붙임

: 인스턴스 생성하지 않고도 사용 가능

: 모든 인스턴스들이 공통적인 값을 유지해야 할 때 사용

: 클래스가 로딩될 때 생성되어 프로그램이 종료될 때까지 유지

: public을 앞에 붙이면 전역변수의 성격을 가짐

: 인스턴스 생성 없이 '클래스이름.클래스변수' 으로 접근

class Card {
	String kind;
	int number;
	
    // 클래스변수. 모든 Card 인스턴스는 같은 값 가짐
	static int width = 100;
	static int height = 250;
}

 

 

지역변수(local variable)

: 클래스 이외의 영역에서 생성되어 그 안에서만 사용 가능

: 메서드의 경우, 메서드가 종료되면 소멸되어 사용 불가

 

 

메서드(Method)

: 특정한 작업을 수행하기 위한 명령문의 집합

: 클래스 영역에서만 정의 가능

 

  • 선언 방법
반환타입 메서드이름 (타입 변수명, 타입 변수명, ...) {
	// 메서드 호출 시 수행될 코드
}
int add(int x, int y) {
		return x + y;
}

 

 

메서드 호출

: 메서드 호출 시 인자의 개수와 순서는 매개변수와 일치해야 함

: 인자의 타입은 매개변수와 일치하거나 자동 형변환 가능한 것이어야 함

: static 메서드는 같은 클래스 내에 있는 인스턴스 메서드를 호출할 수 없음

메서드이름(); // 매개변수가 없는 경우
메서드이름(인자1, 인자2, ...); // 매개변수가 있는 경우
int result = add(1, 3);

 

 

return문

: 현재 실행 중인 메서드를 종료하고 호출한 메서드로 되돌아감

: 반환타입이 void인 경우, 컴파일러가 자동으로 'return;' 생성

: 반환타입이 void가 아닌 경우, 반드시 return문이 있어야 함

int max(int x, int y) {
	if (x > y) {
		return x;
	}
    
    // if문 안에만 return문 있으면 오류 발생.
	return y;
}

 

 

JVM의 메모리 구조

  • 메서드 영역(Method Area)

: 클래스에 대한 정보와 클래스변수 저장

 

  • 호출스택(Call stack or execution stack)

: 메서드 작업공간

: 메서드가 호출되면 메서드 수행에 필요한 메모리 공간 할당

: 메서드가 종료되면 사용한 메모리 반환

: 호출스택의 제일 위에 있는 메서드가 현재 실행 중인 메서드

 

  • 힙(heap)

: 인스턴스가 생성되는 공간

: 인스턴스변수 생성

 

 

매개변수 - 기본형, 참조형

  • 기본형 매개변수

: 변수의 값을 읽기만 할 수 있다(read only)

: 변수의 복사본을 전송하므로 원본에는 아무런 영향을 미치지 못함

class Data {int x;}

public class PrimitiveParamEx {
	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) {
		x = 1000;
		System.out.println("change() : x = "+x);
	}
}
main() : x = 10
change() : x = 1000
After change(d.x)
main() : x = 10

 

 

  • 참조형 매개변수

: 변수의 값을 읽고 변경할 수 있다(read & write)

: 값이 저장된 주소를 넘겨줌

public class ReferenceParamEx {
	public static void main(String[] args) {
		Data d = new Data();
		d.x = 10;
		
		System.out.println("main() : x = "+d.x);
		change(d);
		System.out.println("After change(d)");
		System.out.println("main() : x = "+d.x);

	}
	
	static void change(Data d) {
		d.x = 1000;
		System.out.println("change() : x = "+d.x);
	}
}
main() : x = 10
change() : x = 1000
After change(d)
main() : x = 1000

 

 

반환타입 - 참조형

: 메서드가 객체의 주소를 반환

public class ReferenceReturnEx {
	public static void main(String[] args) {
		Data d = new Data();
		d.x = 10;
		
		Data d2 = copy(d);
		System.out.println("d.x = "+d.x);
		System.out.println("d2.x = "+d2.x);

	}
	
	static Data copy(Data d) {
		Data tmp = new Data();
		tmp.x = d.x;
		return tmp;
	}
}

 

 

재귀호출(recursive call)

: 메서드 내에서 메서드 자신을 다시 호출

: 반복문으로 변경 가능하며 반복문보다 성능 나쁜 경우 많음

public class FactorialTest {
	public static void main(String[] args) {
		int result = factorial(4);
		
		System.out.println(result);

	}
	
	static int factorial(int x) {
		int result;
		
		if (x <= 0 || x > 12) {
			return -1;
		}
		if (x == 1) {
			result = 1;
		}
		result = x * factorial(x -1);
		
		return result;
	}
}

 

 

메서드 - 클래스(static), 인스턴스

클래스 메서드와 인스턴스 메서드의 차이는 인스턴스변수(iv)의 사용 여부다.

 

 

  • 클래스 메서드(static 메서드)

: 인스턴스변수나 인스턴스메서드와 관련없는 작업을 함

: 메서드 내에서 인스턴스 사용 불가

: 객체 생성 없이 '클래스이름.메서드이름()' 으로 호출

 

 

  • 인스턴스 메서드

: 인스턴스변수나 인스턴스 메서드와 관련된 작업을 함

: 인스턴스변수나 인스턴스 메서드를 사용하지 않는다면 static 붙임 -> 클래스 메서드로 작성할 것

: 메서드 내에서 인스턴스 사용 가능

: 인스턴스 생성 후, '참조변수.메서드이름()' 으로 호출

public class MyMathTest2 {
	public static void main(String[] args) {
		System.out.println(MyMath2.sum(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.sum());
		System.out.println(mm.subtract());
		System.out.println(mm.multiply());
		System.out.println(mm.divide());
	}
}

class MyMath2{
	long a, b;
	
	// 인스턴스 메서드
	long sum() {return a + b;}
	long subtract() {return a - b;}
	long multiply() {return a *b;}
	double divide() {return a / b;}
	
	// 클래스 메서드
	static long sum(long a, long b) {return 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;}
}

 

 

참조와 호출 - 클래스 멤버, 인스턴스 멤버

같은 클래스의 멤버 간에는 객체 생성이나 참조 변수 없이 참조할 수 있다.
그러나 클래스(static) 멤버들은 인스턴스 멤버들을 참조할 수 없다.

 

: 클래스 멤버가 존재하는 시점에 인스턴스 멤버가 존재하지 않을 수 있음

: 클래스 멤버는 언제나 참조, 호출 가능

: 인스턴스 멤버 간의 호출에는 아무런 문제 없음

class MemberCall {
	int iv =10;
	static int cv = 20;
	
	int iv2 = cv;
	// static int cv2 = iv; // 에러. 클래스변수는 인스턴스변수 사용 불가
	static int cv2 = new MemberCall().iv; // 객체 생성해야 사용 가능
	
	static void staticMethod1() {
		System.out.println(cv);
		//System.out.println(iv); // 에러. 클래스메서드는 인스턴스변수 사용 불가
		MemberCall c = new MemberCall(); // 객체를 생성해야 참조 가능
		System.out.println(c.iv);
	}
	
	void instanceMethod1() {
		System.out.println(cv);
		System.out.println(iv); // 인스턴스메서드에서는 인스턴스변수 사용 가능
	}
	
	static void staticMethod2() {
		staticMethod1();
		//instanceMethod1(); // 에러. 클래스메서드에서는 인스턴스메서드 호출 불가
		MemberCall c = new MemberCall();
		c.instanceMethod1(); // 인스턴스 생성해야 호출 가능
	}
	
	void instanceMethod2() {
		staticMethod1(); // 인스턴스메서드에서는 모두 인스턴스 생성 없이 호출 가능
		instanceMethod1();
	}
}

 

 

※ 인스턴스 생성과 동시에 메서드 호출

: 참조변수 선언을 하지 않았으므로 생성된 인스턴스는 일회용

MemberCall c = new MemberCall();
int result = c.instanceMethod();

-> int result = new MemberCall().instanceMethod();

 

 

클래스 로더를 이용한 인스턴스 생성

: 문자열로 된 클래스, 메소드 이름만 있어도 인스턴스를 생성할 수 있음

Class clazz = Class.forName("패키지 이름을 포함한 클래스풀네임");
Object obj = clazz.newInstance();

 

 

3. 메서드 오버로딩(method overloading)


메서드 오버로딩

: 한 클래스 내에 같은 이름의 메서드를 여러 개 정의하는 것

: 같은 기능을 하는 메서드를 하나로 통칭하여 오류 가능성 낮춤

: 간단히 오버로딩(overloading)이라고 함

 

 

오버로딩의 조건

  1. 메서드 이름이 같아야 함
  2. 매개변수의 개수 또는 타입이 달라야 함
  3. 매개변수는 같고 반환 타입이 다른 경우는 오버로딩 성립 X
int add(int a, int b) {return a+b;}
long add(long a, long b) {return a+b;}
long add(int[] a) {
	long result = 0;
	
	for (int i = 0; i < a.length; i++) {
		result += a[i];
	}
	return result;
}

 

 

가변인자(variable arguments)

: 메서드의 매개변수를 동적으로 지정

: 매개변수 중에서 제일 마지막에 선언해야 함

: 가변인자만 있는 경우 인자가 아예 없어도 됨

: 가변인자를 사용한 메서드는 오버로딩하지 않는 것이 좋음

: '타입... 변수명' 으로 선언

<-> 매개변수의 타입을 배열로 하면, 반드시 인자를 지정해줘야 하므로 인자 생략 불가

public PrintStream printf(String format, Object... args) {...}

 

 

 

4. 생성자(Constructor)


생성자

모든 클래스에는 반드시 하나 이상의 생성자가 있어야 한다.

 

: 인스턴스가 생성될 때마다 호출되는 인스턴스 초기화 메서드

: 어떤 값을 가지고 인스턴스가 만들어지게 하고 싶을 때 사용

: 인스턴스 생성 시 수행될 작업에 사용

 

 

  • 정의 방법
클래스이름(타입 변수명, 타입 변수명, ...) {
	// 인스턴스 생성 시 수행될 코드
}

 

 

  • 인스턴스 생성 과정
Card c = new Card();
  1. 연산자 new에 의해서 메모리(heap)에 Card클래스의 인스턴스 생성
  2. 생성자 Card()가 호출되어 수행
  3. 생성된 Card인스턴스의 주소가 반환되어 참조변수 c에 저장

 

 

생성자의 조건

  1. 생성자의 이름은 클래스 이름과 같아야 함
  2. 생성자는 리턴 값이 없음

 

 

기본 생성자(default constructor)

: 매개변수가 없는 생성자

: 클래스에 생성자가 하나도 없으면 컴파일러가 기본 생성자 추가

: 생성자가 하나라도 있으면 자동 생성 X -> 필요한 경우 직접 작성

클래스이름() {}

 

 

매개변수가 있는 생성자

: 매개변수를 선언하여 호출 시 값을 넘겨받아 인스턴스의 초기화에 사용

 

 

생성자에서 다른 생성자 호출 - this()

: 생성자, 같은 클래스의 다른 생성자를 호출할 때 사용
: 다른 생성자 호출은 생성자의 첫 문장에서만 가능

: 되도록이면 파라미터를 많이 사용하는 생성자를 this로 호출해주는 것이 유지보수 편리

class Car {
	String color;
	String gearType;
	int door;
	
	Car() {
		this("white", "auto", 4);
	}
	Car(String color) {
		this(color, "auto", 4);
	}
	Car(Car c) {
		this(c.color, c.gearType, c.door);
	}
}

 

 

참조변수 this

: 인스턴스 자신을 가리키는 참조변수
: 인스턴스의 주소가 저장되어 있음
: 인스턴스변수와 지역변수를 구별하기 위해 사용

class Car {
	String color;
	String gearType;
	int door;
	
	Car(String color, String gearType, int door) {
		this.color = color; // this.인스턴스변수 = 지역변수
		this.gearType = gearType;
		this.door = door;
	}
}

 

 

 

5. 변수의 초기화


: 지역변수는 사용 전에 반드시 초기화해야 함
: 멤버변수와 배열은 기본값으로 자동초기화되므로 초기화 생략 가능

 

 

멤버변수의 초기화 방법

  1. 명시적 초기화
  2. 생성자
  3. 초기화 블럭
  • 인스턴스 초기화 블럭 : { }
    생성자에서 공통적으로 수행되는 작업에 사용
    인스턴스가 생성될 때마다 생성자보다 먼저 실행

 

  • 클래스 초기화 블럭 : static { }
    클래스가 로딩될 때 실행

 

멤버변수의 초기화 시기와 순서

  • 클래스변수
    : 클래스가 처음 로딩될 때 단 한 번

 

  • 인스턴스변수
    : 인스턴스가 생성될 때마다
클래스 초기화 블럭 -> 인스턴스 초기화 블럭 -> 생성자
class BlockTest {
	static {
		System.out.println("static { }");
	}
	{
		System.out.println("{ }");
	}
	BlockTest() {
		System.out.println("생성자");
	}

	public static void main(String[] args) {
		System.out.println("BlockTest bt = new BlockTest();");
		BlockTest bt = new BlockTest();
	}
}
static { }
BlockTest bt = new BlockTest();
{ }
생성자