Seren dev's blog
article thumbnail
이 글은 남궁성 님의 "자바의 정석: 기초편" 책을 읽고 정리한 내용입니다.

객체지향 언어

객체지향 언어는 기존의 프로그래밍 언어와 다른 전혀 새로운 것이 아니라, 기존의 프로그래밍 언어에 몇 가지 새로운 규칙을 추가한 보다 발전된 형태의 것이다. 이러한 규칙들을 이용해서 코드 간에 관계를 맺어줌으로써 보다 유기적으로 프로그램을 구성하는 것이 가능해졌다.

 

객체지향언어의 주요 특징

1. 코드의 재사용성이 높다.

새로운 코드를 작성할 때 기존의 코드를 이용하여 쉽게 작성할 수 있다.

2. 코드의 관리가 용이하다.

코드간의 관계를 이용해서 적은 노력으로 쉽게 코드를 변경할 수 있다.

3. 신뢰성이 높은 프로그래밍을 가능하게 한다.

제어자와 메서드를 이용해서 데이터를 보호하고 올바른 값을 유지하도록 하며,

코드의 중복을 제거하여 코드의 불일치로 인한 오동작을 방지할 수 있다.

 

객체지향언어의 가장 큰 장점: '코드의 재사용성이 높고 유지보수가 용이'

재사용성, 유지보수, 중복된 코드의 제거

 

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

 

클래스와 객체

클래스의 정의: 객체를 정의해놓은 것 / 객체의 설계도 또는 틀

클래스의 용도: 클래스는 객체를 생성하는데 사용

 

객체의 정의: 실제로 존재하는 것. 사물 또는 개념

객체의 용도: 객체가 가지고 있는 기능과 속성에 따라 다름

유형의 객체/무형의 객체: 사물/ 논리나 개념

 

객체의 구성요소 - 속성과 기능

객체는 속성과 기능으로 이루어져 있으며, 일반적으로 객체는 다수의 속성과 다수의 기능을 갖는다. 즉, 객체는 속성과 기능의 집합이라고 할 수 있다. 객체가 가지고 있는 속성과 기능을 그 객체의 멤버라 한다.

속성(property) -> 멤버변수(variable)
기능(function) -> 메서드(method)

객체와 인스턴스

클래스로부터 객체를 만드는 과정을 클래스의 인스턴스화라고 하며, 어떤 클래스로부터 만들어진 객체를 그 클래스의 인스턴스(instance)라고 한다.

인스턴스와 객체는 같은 의미이지만, 객체는 모든 인스턴스를 대표하는 포괄적인 의미를 갖고 있으며, 인스턴스는 어떤 클래스로부터 만들어진 것인지를 보다 강조하는 의미를 갖고 있다.

"책상은 객체다."

"책상은 책상 클래스의 인스턴스다."

클래스 -> 인스턴스(객체)

 

한 파일에 여러 클래스 작성하기

소스파일의 이름은 public class의 이름과 일치해야 한다. 만일 소스파일 내에 public class가 없다면, 소스파일의 이름은 어떤 클래스의 이름으로 해도 상관없다.

소스파일(*.java)과 달리 클래스파일(*.class)은 클래스마다 하나씩 만들어지므로 Hello2.java를 컴파일하면 Hello2.class와 Hello3.class 두 개의 클래스 파일이 생성된다.

 

객체의 생성과 사용

클래스명 변수명; // 클래스의 객체를 참조하기 위한 참조변수를 선언

변수명 = new 클래스명(); // 클래스의 객체 생성 후, 객체의 주소를 참조변수에 저장

Tv t;

t = new Tv();

 

객체 배열

Tv[] tvArr = new Tv[3];

배열의 각 요소는 참조변수의 기본값인 null로 자동 초기화된다.

객체 배열을 생성하는 것은 그저 객체를 다루기 위한 참조변수들이 만들어진 것일 뿐, 아직 객체가 저장되지 않았다.

Tv[] tvArr = { new Tv(), new Tv(), new Tv() };

 

클래스의 정의 - 데이터와 함수의 결합

1. 변수 - 하나의 데이터를 저장하는 공간

2. 배열 - 같은 종류의 여러 데이터를 하나의 집합으로 저장하는 공간

3. 구조체 - 서로 관련된 여러 데이터를 종류에 관계없이 하나의 집합으로 저장하는 공간

4. 클래스 - 데이터와 함수의 결합(구조체 + 함수)

 

자바와 같은 객체지향언어에서는 변수(데이터)와 함수를 하나의 클래스에 정의하여 서로 관계가 깊은 변수와 함수들을 함께 다룰 수 있게 했다.

public class Time {
	private int hour;
    private int minute;
    private float second;
    
    //hour의 값을 변경하기 위한 setter
    //유효한 값이 아니면 변경하지 않고 메서드 종료
    public void setHour(int h) {
    	if (h < 0 || h > 23) return;
        hour = h;
    }
}

setter를 이용해서 변수의 값을 직접 변경하지 못하게 하고, 메서드를 통해서 값을 변경하도록 한다.

값을 변경할 때 지정된 값의 유효성을 점검한 다음에 유효한 값일 경우에만 변경한다.

 

클래스 변수와 인스턴스 변수

변수의 종류를 결정짓은 중요한 요소는 '변수의 선언 위치'

class Variables {	//클래스 영역

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

인스턴스 변수는 인스턴스가 생성될 때마다 생성되므로 인스턴스마다 각기 다른 값을 유지한다.

클래스 변수는 모든 인스턴스가 하나의 저장공간을 공유하므로, 항상 공통된 값을 가진다.

멤버 변수 = 인스턴스 변수 + 클래스 변수

메서드

반환타입 메서드이름(매개변수 선언) { //선언부
...					//구현부
}

int add(int x, int y) {
	return x + y;
}
매개변수도 메서드 내에 선언된 것으로 간주되므로 지역변수이다.

main 메서드는 프로그램 실행 시 OS에 의해 자동적으로 호출된다.

 

인수와 매개변수

메서드를 호출할 때 괄호() 안에 지정해준 값들을 인수(argument) 또는 인자라고 한다.

인자의 개수와 순서는 호출된 메서드에 서언된 매개변수와 일치해야 한다.

return 반환값은 반환타입과 일치하거나 적어도 자동 형변환이 가능한 것이어야 한다. 인자의 타입도 매개변의 타입과 일치하거나 자동 형변환이 가능한 것이어야 한다.

 

원래는 반환값의 유무에 관계없이 모든 메서드에는 적어도 하나의 return문이 있어야 한다. 반환타입이 void인 경우, return문 없이도 아무런 문제가 없는 이유는 컴파일러가 메서드의 마지막에 'return;'을 자동적으로 추가해주었기 때문이다.

 

호출스택(call stack)

- 메서드가 호출되면 수행에 필요한 만큼의 메모리를 스택에 할당받는다.

- 메서드가 수행을 마치고 나면 사용했던 메모리를 반환하고 스택에서 제거된다.

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

- 아래에 있는 메서드가 바로 위의 메서드를 호출한 메서드다.

 

매개변수 Call by value

기본형 매개변수: 변수의 값을 읽기만 할 수 있다. (read only)

참조형 매개변수: 인스턴스의 주소가 복사되기 때문에 변수의 값을 읽고 변경할 수 있다. (read & write)

 

But String 객체를 매개변수로 주면?

class Car {
    public static void change(String str) {
        str += "456";
    }

    public static void main(String[] args) {
        String str = "ABC123";
        System.out.println(str);
        change(str);
        System.out.println(str);
    }
}

결과:ABC123
ABC123

이러한 결과가 나오는 이유

함수 내에서 text += "world" 명령을 통해 text에 내용을 추가해주면 별도의 문자열이 힙 영역에 생성되고 text가 가지고 있는 주소값이 변경된다. 원래 arr이 가리키고 있는 주소에는 전혀 영향을 주지도 않고 힙 영역에 들어 있는 "Hello"라는 문자열에도 전혀 영향을 주지 않는다.

public class Main {

	public static void main(String[] args) {
	
		int[] arr = {0,1,2,3};
		
		print(arr);
		System.out.println(Arrays.toString(arr));
	}

	static void print(int[] arr2) {
		
		arr2 = new int[] {2,3,4,5};
	}
}

 

Static 메서드(클래스 메서드)와 인스턴스 메서드

인스턴스 변수를 사용하지 않는다고 해서 반드시 클래스 메서드로 정의해야 하는 것은 아니지만 특별한 이유가 없는 한 그렇게 하는 것이 일반적이다.

클래스 메서드, 클래스 변수는 객체 생성 없이 바로 사용 가능하다.

static을 언제 붙여야 할까?

1. 클래스를 설계할 때, 멤버변수 중 모든 인스턴스에 공통으로 사용하는 것(모든 인스턴스에서 같은 값이 유지되어야 하는 변수)에 static을 붙인다.

 

2. 클래스 변수(static 변수)는 인스턴스를 생성하지 않아도 사용할 수 있다.

- static이 붙은 변수는 클래스가 메모리에 올라갈 때 이미 자동적으로 생성되기 때문이다.

 

3. 클래스 메서드(static 메서드)는 인스턴스 변수를 사용할 수 없다.

- 클래스 메서드는 인스턴스 생성 없이 호출 가능

- 인스턴스 변수는 인스턴스가 반드시 존재해야만 사용 가능

- 클래스 메서드에서 인스턴스 변수 사용 불가능

- 인스턴스 메서드에서 클래스 변수 사용 가능

 

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

- 클래스 메서드는 호출시간이 짧아지므로 성능이 향상된다.

- 인스턴스 메서드는 실행 시 호출되어야할 메서드를 찾는 과정이 추가적으로 필요하기 때문에 시간이 더 걸린다.

정리

- 클래스의 멤버변수 중 모든 인스턴스에서 같은 값이 유지되어야 하는 변수에 static을 붙인다.

- 메서드 중에서 인스턴스 변수와 인스턴스 메서드를 사용하지 않는 메서드에 static을 붙이는 것을 고려한다.

 

오버로딩(overloading)

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

1. 메서드 이름이 같아야 한다.
2. 매개변수의 개수 또는 타입이 달라야 한다.
3. 반환 타입은 관계없다.

ex) println() 메서드를 호출할 때 매개변수로 넘겨주는 값의 타입에 따라서 오버로딩된 메서드들 중의 하나가 선택되어 실행된다.

//오버로딩
long add(int a, long b) { return a+b; }
long add(long a, int b) { return a+b; }

 

생성자(constructor)

생성자: 인스턴스가 생성될 때 호출되는 '인스턴스 초기화 메서드' -> 인스턴스 변수 초기화 작업

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

연산자 new가 인스턴스를 생성하는 것이지 생성자가 인스턴스를 생성하는 것이 아니다.

기본 생성자

컴파일 할 때, 소스파일(*.java)의 클래스에 생성자가 하나도 정의되지 않은 경우 컴파일러는 기본 생성자를 추가하여 컴파일 한다.

클래스이름() {} //기본 생성자
Point() {}
클래스의 접근제어자가 public인 경우에는 기본 생성자로 'public 클래스이름() {}'이 추가된다.

컴파일러가 기본생성자를 추가하는 경우는 클래스에 정의된 생성자가 하나도 없을 때 뿐이다.

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

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

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

 

생성자에서 다른 생성자를 첫 줄에서만 호출이 가능하도록 한 이유는 생성자 내에서 초기화 작업도중에 다른 생성자를 호출하게 되면, 호출된 다른 생성자 내에서도 초기화 작업을 할 것이므로 이전의 초기화 작업이 무의미해질 수 있기 때문이다.

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.gearType = gearType;
        this.door = door;
    }
}

기본 생성자에서 다른 생성자를 호출하도록 하여, 아무 옵션도 주지 않으면 기본적으로 "white", "auto", 4로 초기화되도록 한다.

 

객체 자신을 가리키는 참조변수 this

    Car(String color, String gearType, int door) {
        this.color = color;
        this.gearType = gearType;
        this.door = door;
    }

this.color : 인스턴스 변수

color : 매개변수로 정의된 지역변수

 

this: 인스턴스 자신을 가리키는 참조변수. 인스턴스의 주소가 저장되어있다.
생성자를 포함한 모든 인스턴스 메서드에 참조변수 this가 지역변수로 숨겨진 채로 존재한다.

this(). this(매개변수): 생성자, 같은 클래스의 다른 생성자를 호출할 때 사용한다.

 

변수의 초기화

- 변수를 선언하고 처음으로 값을 저장하는 것

 

멤버변수는 초기화를 하지 않아도 자동적으로 변수의 자료형에 맞는 기본값으로 초기화가 이루어지지만, 지역변수는 사용하기 전에 반드시 초기화해야 한다.

class Test {
    int x;          //인스턴스 변수
    int y = x;      //인스턴스 변수
    
    void method1() {
        int i;      //지역변수
        int j = i;  //에러
    }
}

멤버변수의 초기화

1. 클래스 변수가 인스턴스 변수보다 먼저 초기화된다.

2. 각 타입의 기본값으로 자동 초기화 -> 명시적 초기화(간단) -> 초기화 블럭(복잡) -> 생성자(복잡)

명시적 초기화

변수를 선언과 동시에 초기화하는 것

class Car {
    int door = 4;
    Engine e = new Engine();
}

초기화 블럭

클래스 초기화 블럭: 클래스 변수 초기화

인스턴스 초기화 블럭: 인스턴스 변수 초기화

class Car {
    //클래스 초기화 블럭
    static {
        System.out.println("static { }");
    }

    //인스턴스 초기화 블럭
    {
        System.out.println("{ }");
    }

    public Car() {
        System.out.println("생성자");
    }

    public static void main(String[] args) {
        System.out.println("Car c = new Car();");
        Car c = new Car();

        System.out.println("Car c2 = new Car();");
        Car c2 = new Car();
    }
}
static { }
Car c = new Car();
{ }
생성자
Car c2 = new Car();
{ }
생성자

Car이 메모리에 로딩될 때, 클래스 초기화 블럭이 가장 먼저 수행되어 'static { }"이 화면에 출력된다.

그 다음 main 메서드가 수행되어 Car의 인스턴스가 생성되면서 인스턴스 초기화 블럭이 먼저 수행되고, 끝으로 생성자가 수행된다.

클래스 초기화 블럭은 처음 메모리에 로딩될 때 한번만 수행된다.

728x90
profile

Seren dev's blog

@Seren dev

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!