"너무 객체지향개념에 얽매여서 고민하기보다, 일단 프로그램을 기능적으로 완성한 다음 어떻게 객체지향적으로 개선할 수 있을지 고민하여 점차 개선해나가는 것이 좋다.

처음부터 이론을 많이 안다고 좋은 설계를 할 수 있는 것은 아니다."

 

위의 글로 시작하는 자바의 정석 6장의 제목은 사실 '객체지향 프로그래밍 I' 이다.

그런데 읽고 보니 객체지향의 원리와 이론적인 내용보다는

그냥 JAVA의 Class에 대한 내용이 중점적인 것 같아서 제목을 바꿨다.

 

클래스와 객체

클래스와 객체가 무엇인지는 다들 알고 있을 듯 해서 깊게 짚고 넘어가진 않겠다.

책에선 다음과 같이 클래스와 객체를 정의한다.

  • 클래스란 '객체를 정의해놓은 것', '객체의 설계도 또는 틀'
  • 객체는 '실제로 존재하는 것, 사물 또는 개념' , 프로그래밍에선 '클래스의 정의된 내용대로 메모리에 생성된 것'

보통 이 관계를 설명할때 붕어빵과 붕어빵틀이라는 비유가 많이 나오는데

우리에게 개구리 책으로 유명한 '스프링 입문을 위한 자바 객체 지향의 원리와 이해' 는 이 비유를 신나게 깐다.

//붕어빵틀 비유
붕어빵틀 붕어빵 = new 붕어빵틀();

//개념 - 실체 관계
사람 파란옷을입은사람 = new 사람();
사람 한국인 = new 사람();
사람 안경을낀사람 = new 사람();
사람 남자 = new 사람();

 

객체지향의 원리에 대해 깊게 파고드는 책은 아니라고 생각해서,

쉽게 설명하기 위해 썼다고 생각한다.

 

또 객체지향적인 설계의 관점에서 보는 것이 아니라 JAVA라는 언어의 사용법에 대해 공부할땐,

Class를 설계도 또는 틀이라고 이해하고 가도 문제가 없지 않을까?

 

객체와 인스턴스

클래스로부터 객체를 만드는 과정을 인스턴스화라고 이야기하고

만들어진 객체 인스턴스라고 한다.

 

인스턴스 = 객체?

책상 나무책상 = new 책상();

 

책상 클래스로부터 만들어진 객체를 인스턴스라고 한다.

결국 같은 의미이지만 인스턴스는 어떤 클래스로부터 만들어졌는지 강조하는 보다 구체적인 의미를 지닌다.

  • 나무책상은 객체다
  • 나무책상은 책상 클래스의 인스턴스다.

라고 이야기하는 것이 자연스럽다.

 

JAVA 객체의 생성

Tv t;
t = new Tv();
  1. Tv 클래스 타입의 참조변수 t 선언
  2. new 연산자에의해 Tv 클래스 인스턴스가 힙 영역에 생성
  3. 생성자에 의해 인스턴스 초기화
  4. t 변수에 힙 영역의 주소 할당

와 같은 순서로 객체를 생성하고 초기화한다.

 

객체 배열

Tv tvArray[3];

tvArray[0] = new Tv();
tvArray[1] = new Tv();
tvArray[2] = new Tv();

 

참조변수들을 하나로 묶은 참조 변수 배열이다.

여러 종류의 객체로 배열을 만들 수는 없을까? -> 나중에 다형성 파트에서 나온다고 한다.

 

클래스의 또 다른 정의

  1. 변수
  2. 배열 (같은 타입의 변수들의 집합)
  3. 구조체 (다른 타입의 변수들도 묶음)
  4. 클래스 (서고 관련있는 변수와 함수)

순으로 데이터 저장형태가 발전했다.

 

클래스는 여러 변수와 함수를 하나의 클래스로 정의하여 관계가 깊은 변수들을 하나로 다룰 수 있도록 해준다.

또 사용자정의 타입의 역할도 할 수 있다.

 

메서드

클래스 내에서 정의한 함수이다.

잘 사용하면 높은 재사용성, 중복 코드 제거, 프로그램의 구조화 등의 장점이 있다.

 

매개변수(parameter)와 인자(Argument)? 

  • 매개변수(parameter) : 함수에서 사용하기 위해 정의 부분에 나열된 변수들
  • 인자(Argument) : 함수를 호출할 때 전달되는 실제 값

메서드에 전달되는 인자는 파라미터로 선언된 타입과 같거나 자동 형변환이 가능해야만 한다.

또한 메서드의 반환값 역시 반환 타입으로 선언된 타입과 같거나 자동 형변환이 가능해야만 한다.

 

호출스택(call stack) 이란?

메서드 작업에 필요한 메모리 공간을 할당하는 곳이다.

메서드를 수행하는 동안 지역변수, 연산의 중간 결과등을 저장하는데 사용한다.

메서드 작업이 마치면 할당된 메모리 공간은 반환되어 비워진다.

 

call stack 영역에선 메서드 작업 중 다른 메서드 호출 시

다음 공간에 새로운 메서드를 위한 공간을 만들고 진행하던 메소드는 중단한다.

다음 메소드를 진행한 후 완료되면 다시 전의 메소드로 돌아와 이어서 진행한다.

예전에 JVM의 메모리 구조를 설명하려고 만들었던 그림인데

왜 메소드 실행 영역의 이름이 stack인지 이제야 알아버렸다.

 

Stack Area 부분의 그림을 좀 더 정확하게 그리면 이렇게 그리는게 맞을 것 같다.

 

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

public class Main{
    
	public static void main(String [] args){
        
        Data data = new Data();
        data.a = 1;
        
    	change(data.a); //기본형
    	System.out.println(data.a); //1
    	
    	change(data); //참조형
    	System.out.println(data.a); //2
	}
	
	public static void change(int a) {
	    a = 2;
	}
	public static void change(Data data){
    	data.a = 2;
	}
}

class Data{
    int a;
}

 

매개변수 타입이 primitive일 경우 값을 복사해서 넘긴다.

따라서 함수 내에서 원본 값을 바꾸는 것이 불가능하다.

 

매개변수 타입이 참조형일경우 주소를 복사해서 넘기므로 원본 변경이 가능하다.

임시적으로 주소 복사를 원할 시 길이가 1인 배열 선언하면 된다고 한다.

String Pool

위의 예시를 공부하다가 String Constant Pool에 대해서 새롭게 알게 됐다.

JAVA에선 String을 선언하는 방법이 2가지가 있다.

  • 리터럴 방식
  • new 연산자 방식
String a = "stringA";
String b = new String("stringB");

 

JVM에선 리터럴 방식으로 선언된 문자열을 독립적으로 모아 저장하는데 이 영역이 String Constant Pool 이다.

String은 불변 객체이기 때문에 문자열 생성 시 String Constant Pool에 저장된 리터럴을 재사용한다.

 

즉 같은 내용의 문자열을 재선언 할 시 새로운 메모리 영역에 문자열을 생성하는 것이 아닌

기존의 문자열의 주소만 가져와 전달해준다는 것이다.

 

그러나 new 연산자 방식은 Heap영역에 항상 새로운 문자열 객체를 생성한 후 반환한다.

따라서 성능 및 메모리 최적화를 위해선 문자열은 항상 리터럴 방식으로 선언하자.

 

https://deveric.tistory.com/123

 

[Java] 많이 헷갈려하는 String constant pool과 Runtime Constant pool, Class file constant pool

String Constant Pool과 Constant Pool 이 두 가지는 완전히 다른 개념입니다. 용어가 비슷한 형태이기 때문에 이 두 가지를 혼용하여 헷갈리는 경우가 많습니다만, 저장되는 위치부터 저장하는 데이터의

deveric.tistory.com

https://junhyunny.github.io/java/java-string-pool/

 

Java String Pool

<br /><br />

junhyunny.github.io

이와 관련된 공식문서를 참고한 더 자세히 파고들어간 블로그들이 있어 조금 정리해 봤다.

 

리터럴 방식으로 선언한 문자열은 컴파일 시 컴파일러에 의해 string constant pool 저장 대상으로 표시된다.

클래스 파일의 상수 풀(constant pool)에 'CONSTANT_String' 타입으로 저장되며,

런타임시에 스캔되어 string constant pool에 저장되는 방식이다.

 

상수 풀(constant pool)은 뭘까?

컴파일 시 클래스 파일에 존재하는 영역으로 클래스 로더에 의해 JVM에 로드 될때 메모리에 로드한다.

주로 클래스의 구성요소를 저장하고 있다.

 

클래스 파일의 constant pool은 런타임시 Runtime Constant Pool에 저장된다.

JDK 7 이하는 Perm 영역, 이후부터는 Metaspace 영역에 저장된다.

 

 

 

클래스 메서드와 인스턴스 메서드

모든 인스턴스에 공용으로 사용되는 메서드는 static으로 정의하자.

static 변수는 인스턴스를 생성하지 않아도 사용이 가능하다.

static 메서드에서는 인스턴스 변수 사용를 사용할 수 없다.

 

메서드 내에서 인스턴스 변수를 사용하지 않으면 static을 붙이는 것을 고려한다. 라고 책에선 이야기한다.

 

그러나 static 메서드는 다음과 같은 단점을 가진다.

  • 클래스 로드 시점부터 종료시까지 메모리상에 존재한다.
  • 따로 객체를 생성하지 않고도 사용가능하다는 점이 객체지향 원칙을 위반할 수 있다.
  • static 메서드는 오버라이딩이 불가능하다.

다음과 같은 이유로 여러개의 무작정 static 메소드를 사용하는 것 보단, 적절하게 사용할 필요성이 있어 보인다.

인스턴스를 생성하지 않고 호출할 필요가 있는 경우에만 static을 사용하는 것이 적절해보인다.

 

오버로딩

int max(int a, int b) { ... }
float max(float a, float b) { ... }

 

같은 이름의 메서드를 여러 개 정의하는 것을 이야기한다.

메서드 이름이 같고 매개변수의 개수나 타입이 반드시 달라야한다.

(반환 타입은 아무 영향을 주지 못함)

 

가변인자와 오버로딩

public class Main
{
	public static void main(String[] args) {

	test(1,2); //가능
        test(1,2, "hello"); //가능
        test(1,2, "hello", "hi"); //가능
        test(1,2, new String[] {"hello", "hi"}); //가능
	}
	
	public static String test(int a, int b, String... stringArr){ //불가능
	    return "hi";
	}
}

가변인자를 사용하면 매개변수의 개수를 동적으로 지정 가능하다.

Object… args 와 같은 식으로 사용한다.

 

컴파일러가 가변인자를 만나면, 크기에 맞는 배열로 전환해준다.

따라서 가변인자 자리에 배열을 넣어줘도 되며, 아예 넣지 않아도 된다.

 

가변인자 외에도 매개변수가 더 존재한다면 가변인자를 제일 마지막에 선언해줘야한다.

 

생성자

생성자는 인스턴스 생성 메서드가 아닌 초기화 메서드이다.

생성자는 단순히 초기화에만 사용되며 인스턴스 생성 메서드는 new이다.

 

생성자의 이름은 클래스와 같으며, 리턴 값이 없다.

컴파일러는 사용자지정 생성자가 없을 시 기본 생성자를 제공한다.

 

생성자에서 안에서 다른 생성자를 호출할 시 클래스 이름이 아닌 this 메소드를 사용한다.

또 생성자 안에서 다른 생성자 호출시에는 반드시 첫줄에서만 사용해야한다.

 

멤버 변수의 초기화 방법

  • 명시적 초기화 - 선언과 동시에 초기화
  • 초기화 블럭
  • 생성자

멤버 변수의 초기화 방법은 위의 3가지가 존재한다.

초기화 블럭은 다시 2가지로 나뉘는데

  1. 클래스 초기화 블럭 : static{}, 클래스 로딩될때 한 번 만 수행된다.
  2. 인스턴스 초기화 블럭 : {}, 인스턴스 생성할때마다 생성자보다 먼저 수행됨, 모든 생성자에서 공통으로 수행되어야 하는 코드를 넣는데 사용한다.

기본값 -> 명시적초기화 -> 초기화블럭 -> 생성자 순으로 진행되며

뒤에서 다시 초기화하면 뒤에 값으로 지정된다.

 

 

그런데 메소드 변수는 항상 사용하기 전에 초기화해야하며 하지 않을 시 컴파일 오류가 발생하는데

왜 인스턴스 변수와 스태틱 변수는 기본값이 존재할까?

라는 의문이 들어 찾아봤다.

 

  1. 지역변수는 메소드 내에서 계산을 위해서만 쓰이는데 초기화되지 않으면 예기치 않은 값 발생 가능
  2. 메소드 호출은 빈번한데 그때마다 변수를 찾아 초기화 시키는 것은 비효율적

등의 이유가 나오던데 사실 명쾌한 설명이 없는 것 같다.

그냥 자바에서 그렇게 하라고 해서.. 라고 밖에 못하겠는데 더 공부해봐야겠다.

 

 

 

+ Recent posts