Notice
Recent Posts
Recent Comments
Link
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Tags
more
Archives
Today
Total
관리 메뉴

forDevLife

[자바의정석] Chap 6. 객체지향 프로그래밍 본문

Java

[자바의정석] Chap 6. 객체지향 프로그래밍

JH_Lucid 2020. 12. 1. 08:32

1) 인스턴스의 생성과 사용

 

- new( )에 의해 클래스의 인스턴스가 메모리의 빈 공간에 생성.

 

3.7) JVM 메모리 구조

 

- App이 실행되면, JVM은 시스템으로보터 프로그램을 수행하는데 필요한 메모리를 할당받고 JVM은 이 메모리를 용도에 따라 여러 영역으로 나누어 관리한다.

 

1>메서드 영역

: 프로그램 실행 중 어떤 클래스가 사용되면, JVM은 해당 클래스의 클래스 파일(*.class)을 읽어 분석하여 클래스에 대한 정보(클래스 데이터)를 이곳에 저장한다. 이 때, 그 클래스의 클래스변수도(static variable) 해당 영역에 생성된다. 

 

2> 힙 영역

: 인스턴스가 생성되는 공간. 프로그램 실행 중 생성되는 인스턴스는 모두 이곳에 생성된다.(instance variable)

 

3> 호출 스택(Call stack) 영역

: 메서드의 작업에 필요한 메모리 공간을 제공. 메서드가 호출되면, 호출스택에 호출된 메서드를 위한 메모리가 할당되며 메서드가 작업을 수행하는 동안 지역변수, 연산의 중간결과 등을 저장하는데 사용된다. 메서드가 작업을 마치면 해당 공간은 반환되어 비워진다.

 

3.9) 참조형 반환 타입

 

- 매개변수 뿐 아니라 반환 타입도 참조형이 될 수 있다. 모든 참조형 타입의 값은 '객체의 주소'이므로 그저 정수값이 반환될 뿐 특별한 건 없다.

 

3.10) 재귀 호출

 

- 메소드를 static으로 지정할 경우, 같은 클래스 내에서(예를 들면 main) 생성할 필요 없이 해당 메서드를 사용 가능

- 재귀로 할 경우, 계속 메소드가 call 되면 메소드의 영역인 호출스택에 메소드용 공간이 계속 할당됨. 많은 횟수를 반복할 경우 스택 오버플로우가 발생

- 재귀가 아닌 반복문으로 작성하면 스택 오버플로우는 발생하지 않는다.

 

3.11) 클래스 / 인스턴스 메서드

 

- 클래스의 멤버변수 중 모든 인스턴스에 공통으로 값을 유지할 경우, static을 붙여준다.

- 작성한 메서드 중에서 인스턴스 변수나 인스턴스 메소드를 사용하지 않는 메소드에 static을 붙일 것을 고려한다.

- 클래스 메서드는 인스턴스 변수를 사용할 수 없다.(인스턴스가 생성된지 모르므로)

- 클래스 변수(static 변수)는 클래스가 메모리에 올라갈 때 JVM의 메소드 영역에 자동으로 생성되므로, 인스턴스 필요없.

- 참고로, math 클래스의 random()과 같은 메소드는 모두 클래스 메소드이다. math 클래스에는 인스턴스 변수가 하나도 없는데, 작업에 수행하는 모든 값들을 매개변수로 받아서 처리하기 때문이다.

 

3.12) 클래스 멤버와 인스턴스 멤버 간의 참조와 호출 // 멤버는 변수 + 메소드를 말함

 

- 클래스 멤버가 인스턴스 멤버를 참조 / 호출할 때는 반드시 인스턴스가 생성되어있지는 않으므로 오류가 난다.

  정의 시, 인스턴스를 new로 생성 + 멤버를 불러서 호출은 가능하다.

 

  static int cv2 = new Membercall().iv; // iv가 Membercall 클래스의 인스턴스 변수일 경우, 아래의 방법으로만 대입가능

  static int cv2 = iv; // 오류

 

- 반대의 경우, 인스턴스 멤버는 클래스 멤버를 언제나 참조할 수 있다.

 

4.1) 오버로딩

한 클래스 내에 같은 이름의 메서드를 여러 개 정의하는 것을 오버로딩이라 한다. 이는 많이 싣는다는 의미로, 보통 하나의 이름으로 여러 메서드를 구현하기 때문에 붙여진 이름이라 생각할 수 있다.

 

- 조건 : 메서드 이름이 같고, 매개변수 개수 또는 타입이 달라야 한다.

아래는 오버로딩의 예시이며, println의 "mm.add() 결과 :" 부분과 mm.add의 반환 문장 중 뭐가 먼저 출력되는지를 유의한다.

public class FactorialTest{

	public static void main(String[] args) {
		MyMath3 mm = new MyMath3();
		System.out.println("mm.add(3,3) 결과 : " + mm.add(3, 3));
		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 {
	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;
	}
}

결과를 보면 println의 string 앞 문장보다도, add 메소드의 내용이 먼저 출력된다. add 메소드의 결과가 먼저 계산되어야 println을 출력할 수 있기 때문이다.

 

4.5) 가변인자 오버로딩

기존에는 메서드의 매개변수 개수가 고정적이었으나, JDK1.5부터 동적으로 지정할 수 있게 되었으며 이 기능을 가변인자(variable arguments)라 한다.

매개변수가 여러 개일 때 이 개수를 다르게 하여 아래처럼 여러 메서드를 작성해야 한다.

 

String concatenate(String s1, String s2) { ... }

String concatenate(String s1, String s2, String s3) { ... }

 

가변인자는 '타입...변수명'과 같은 형식으로 선언하며, 예를 들어 String을 하고 싶다면 아래와 같이 한다.

 

"String concatenate(String... str) { ... }"

 

이 메서드를 호출할 때는 다음처럼 인자 개수를 가변적으로 할 수 있다. 인자가 아예 없어도되고, 배열도 인자로 가능.

 

"System.out.println(concatenate());" // 인자 없음

"System.out.println(concatenate("a"));" // 인자 하나

"System.out.println(concatenate("a", "b"));" // 인자 둘

"System.out.println(concatenate(new String[]{"A", "B"));" //배열도 가능

 

위처럼 가변인자는 내부적으로 배열을 생성한다. 그래서 가변인자가 선언된 메서드를 호출할 때마다 배열이 새로 생성된다. 이 부분은 비효율적이므로 꼭 필요할 때만 사용하도록 한다.

 

public class FactorialTest{

	public static void main(String[] args) {
		String[] strArr = {"100", "200", "300"};
		
		System.out.println(concatenate("", "100", "200", "300"));
		System.out.println(concatenate("-", strArr));
		System.out.println(concatenate(",", new String[] {"1", "2", "3"}));
		System.out.println("["+concatenate(",", new String[0])+"]");
		System.out.println("["+concatenate(",")+"]");
		//System.out.println(concatenate("-", {"100", "200"})); 이 부분은 에러
}
	static String concatenate(String delim, String... args) {
		String result = "";
		
		for(String str : args) {
			result += str + delim;
		}
		return result;
	}
	/*static String concatenate(String... args) {
		String result = "";
		return result;
	}*/
}

다음은 String... args를 이용한 가변인자의 활용이다. 이를 통해 println을 통해 전해지는 문자열의 개수에 제약없이 매개변수로 지정할 수 있다. 다만 아래의 두 부분에 유의한다.

1. 주석된 System.out.println(concatenate("-", {"100", "200"})) 과 같은 방식으로 전달 불가하다. 인자로 전달될 때는 무조건 new String[] {"100", "200"} 형식으로 전달한다.

 

2. 아래의 주석된 오버로딩은, 가변인자로 인해 두 메소드가 구분되지 않아 오류를 발생시킨다. 따라서 가능하면 가변인자를 사용한 메서드는 오버로딩하지 않는 것이 좋다.

 

 

5) 생성자

- 생성자는 인스턴스가 생성될 때 호출되는 '인스턴스 초기화 메서드' 이다. 따라서 heap에서 생성된다.

- 생성자의 이름은 클래스의 이름과 동일하며, 리턴값이 없다.

- 생성자도 오버로딩이 가능하며(매개변수로 구분) 하나의 클래스에 여러 생성자가 존재 가능하다.

 

Card c = new Card();

1. 연산자 new에 의해서 메모리(heap)에 Card class의 인스턴스가 생성된다.

2. 생성자 Card()가 호출되어 수행된다.

3. 연산자 new의 결과로, 생성된 Card 인스턴스의 주소가 반환되어 참조변수 c에 저장된다.

 

- 기본 생성자가 컴파일러에 의해 자동으로 추가되는 경우는, 사용자 정의 생성자가 하나도 없을 때이다.

 

5.3) 매개변수가 있는 생성자

- 생성자를 user define(매개변수를 갖도록 정의) 하는 방시그올, 코드를 보다 직관적이고 간결하게 만들 수 있다.

 

Car c = new Car("white", "auto", 4);

 

5.4) 생성자에서 다른 생성자 호출하기 - this, this( )

같은 클래스의 멤버들 간에 서로 호출할 수 있는 것처럼 생성자 간에도 서로 호출이 가능하다. 아래 2가지 조건이 있다.

 

1. 생성자의 이름으로 클래스 대신 this를 사용한다.

2. 한 생성자에서 다른 생성자를 호출할 때는 반드시 첫 줄에서만 호출이 가능하다.

   → 생성자 내에서 초기화 작업 도중 다른 생성자를 호출하게 되면, 그 이전의 초기화 작업이 무의미해지기 때문

 

this : 인스턴스 변수와 매개변수의 이름이 같을 경우, 구분을 위해 this.color = color 이렇게 대입을 한다. 아래의 경우 매개변수가 다르긴 하지만, 매개변수를 동일한 이름으로 해야 대입이 편하므로 이렇게 사용하면 깔끔하다.

static메서드에서는 인스턴스 멤버와 마찬가지로 this역시 사용 불가하다. 

 

this() : 생성자, 같은 클래스의 다른 생성자를 호출할 때 사용한다. 코드 중복 제거의 효과가 있는데 이는 인스턴스의 복사에서 유용하다. 아래서 다시 설명.

class Car {
	String color;
	String gearType;
	int door;
	
	Car() {
		this("white", "auto", 4);
	}
	Car(String color) {
		this(color, "auto", 4);
	}
	Car(String c, String g, int d) {
		this.color = c;
		this.gearType = g;
		this.door = d;
		
		//color = c;
		//gearType = g;
		//door = d;
		
	}
}
class FactorialTest{
	public static void main(String[] args) {
		Car c1 = new Car("black");
		Car c2 = new Car("blue", "auto", 4);
		Car c3 = new Car();

		System.out.println("c1의 type" + " " + c1.color + " " + c1.gearType + " " + c1.door);
		System.out.println("c2의 type" + " "+c2.color + " " + c2.gearType + " " + c2.door);
		System.out.println("c3의 type" + " "+c3.color + " " + c3.gearType + " " + c3.door);
	}
}

 

5.5) 생성자를 이용한 인스턴스의 복사

이후에 배울 Object 클래스의 clone 메서드로 인스턴스를 복사할 수는 있지만, 배운 생성자를 이용해 복사를 진행한다.

Car(Car c) 생성자를 정의하여 아래와 같이 매개변수로 전달된 Car 인스턴스의 인스턴스 변수들을 복사한다. 주석처리된 것처럼 Car 이름을 생성자 내부에서 사용 불가하므로 this( )를 통해 다른 생성자를 사용할 수 있다.

결과적으로 c1, c2는 생김새는 완전히 같으며, 서로 독립된 메모리 영역에 존재하므로 영향을 미치지 않는다.

class Car {
	String color;
	String gearType;
	int door;
	
	Car() {
		this("white", "auto", 4);
	}
	Car(Car c) {
//		Car(c.color, c.gearType, c.door);
		this(c.color, c.gearType, c.door);
	}
	Car(String c, String g, int d) {
		this.color = c;
		this.gearType = g;
		this.door = d;
	}
}
class FactorialTest{
	public static void main(String[] args) {
		Car c1 = new Car();
		Car c2 = new Car(c1);

		System.out.println("c1의 type" + " " + c1.color + " " + c1.gearType + " " + c1.door);
		System.out.println("c2의 type" + " " + c2.color + " " + c2.gearType + " " + c2.door);
	}
}

 

Comments