자바 객체지향 기본개념 - 클래스

Vinci·2024년 3월 22일
0
post-thumbnail

본 자료는 ‘소설같은 자바 2nd Edition’ (최영관 저)을 기반으로 작성하였습니다.

클래스

구조체

클래스가 나오기 이전, 2개 이상의 데이터를 담기 위해서는 구조체라는 데이터 타입을 사용했어야 했습니다. 현재까지도 C에서는 구조체를 사용하고 있죠.

//C 구조체 예시
struct Person {
	int age;
	float height;
	float weight;
}

struct Person sister; //변수 선언과 동시에 메모리공간 할당

sister.age = 21;
sister.height = 165.5;
sister.weight = 55;

클래스

클래스는 구조체와 함수(앞으로는 메서드라고 부르겠습니다)를 합쳐 놓은 데이터 타입이라고 보면 됩니다.

//클래스 예시
public class Person {

    private int age;
    private float height;
    private float weight;

    //메서드
    public void setAge(int age) {
        this.age = age;
    }

    public float getAge() {
        return age;
    }

    public static void main(String[] args) {
        Person person; //클래스 변수 선언
        person = new Person(); //메모리공간 할당
    }
}

클래스는 구조체와는 다르게 변수를 선언한다고 해서 메모리 공간이 할당되지 않습니다. 위의 예시처럼 new 연산자를 사용하여 메모리를 할당해주어야 합니다.

만약 new를 메모리를 할당하지 않고 클래스 변수를 사용한다면 null pointer exception이 발생합니다.

클래스는 변수와 메소드로 구성되며 이들은 멤버(Member)라고 불립니다.

Encapsulation(은폐화)이 뭐지?
접근 지정자(Access Modifier)를 사용하여 객체 내의 멤버 변수의 값을 직접 접근/변경할 수 없도록 하는 설계방식을 말합니다.

사용하는 이유

  • 외부에서 멤버 변수에 어떠한 방식으로 값을 할당해야하는지 알 필요가 없기 때문입니다.
  • Ex) 외부 클래스는 멤버변수가 int인지, String인지 알 필요가 없고 오직 파라미터로 넘겨주기만 하면 됩니다.
  • 값이 함부로 바뀌는 걸 막기 위해 사용합니다.

Modifier에 따른 접근범위는 아래의 표를 참고해주세요!

참조변수

자바에서는 new 연산자로 객체의 메모리를 생성하고 나면, 클래스 변수에는 메모리 주소가 아닌 메모리 주소의 4-byte 크기의 참조값(Reference Value)이 할당됩니다.

C++에서의 클래스 변수에는 메모리 주소가 직접 할당되는 것과 차이가 있어서, 자바에서의 클래스 변수는 참조변수라고도 불립니다.

그럼 클래스가 생성되는 과정을 한번 알아보도록 하죠!

Person person; 

이렇게 person 이라는 클래스 변수를 생성하게 되면 이는 참조값을 보관할 4-byte 크기의 그릇을 만든 것과 같습니다.

person = new Person();

다음으로 new 연산자를 이용해서 person 에 대한 메모리 공간을 할당하였다면!

컴퓨터 내부에서는 JVM(Java Virtual Machine)이 객체가 저장되어있는 실제 메모리 주소의 해시값으로 참조값을 생성합니다.
그리고 해당 참조값이 person에 할당되게 됩니다.

다시 말해, 사용자가 person의 멤버변수와 메서드를 사용하는 것은!
person에 저장된 참조값을 통해 실제 메모리 주소에 접근하고, 해당 메모리에서 멤버변수와 메서드를 찾아내는 과정을 포함한다고 보면 됩니다.

Static

Java 프로젝트를 처음으로 생성할 때 만들어지는 Main 클래스에는 다음과 같은 메서드가 있는 걸 볼 수 있습니다.

public static void main(String[] args) {...}

위의 static 이라는 단어는 무엇을 의미할까요?

static 키워드의 특징을 세가지로 정리해보겠습니다.

  • static 변수와 메서드의 메모리는 클래스가 처음 로드될 때 생성됩니다.
  • 딱 한번만 만들어집니다.
  • 같은 클래스로 부터 생성된 인스턴스들은 하나의 static 변수와 메서드의 메모리를 공유합니다.

Static 멤버 변수

public class Example {

    public static int a;
    public int b;

    public static void main(String[] args) {
        Example example1 = new Example();
        Example example2 = new Example();
        Example example3 = new Example();

예를 들어, 위의 코드의 Example 클래스는 a, b라는 멤버변수를 가지고 있습니다. example1, example2, example3의 멤버변수 b는 각각 서로 다른 메모리를 가지고 있지만 static 멤버변수 a 만은 같은 메모리를 공유합니다.

    	example1.a = 1;
        example1.b = 2;
        example2.b = 3;
        example3.b = 4;

        System.out.println("example1.a: " + example1.a);
        System.out.println("example2.a: " + example2.a);
        System.out.println("example3.a: " + example3.a);

        System.out.println("example1.b: " + example1.b);
        System.out.println("example2.b: " + example2.b);
        System.out.println("example3.b: " + example3.b);
    }
}

위의 코드를 실행시키면 아래와 같은 출력 결과가 나옵니다. example2.a,와 example3.a에 값을 할당해주지 않았음에도 1을 출력하는 이유는 Example 클래스에서 파생된 인스턴스들은 하나의 static 멤버변수를 공유하기 때문입니다.

example1.a: 1
example2.a: 1
example3.a: 1
example1.b: 2
example2.b: 3
example3.b: 4

Static 멤버 메서드

public class StaticMethodExample {
    
    public int z;
    
    public static int sum(int x, int y) {
        return x + y;
    }

    public static void main(String[] args) {
        int result = StaticMethodExample.sum(3, 6);
				System.out.println("result = " + result);

    }
}
result = 9

위의 예제에서는 StaticMethodExample의 인스턴스를 생성하지 않았는데도 sum() 메서드를 사용하고 있습니다. 이것이 가능한 이유는 sum()이 static 메서드이기 때문에 StaticMethodExample이 언급(로드)되는 순간 이미 메모리가 할당되었기 때문입니다.

Static 메서드 안에서 일반 멤버변수를 사용할 수 없는 이유
static 메서드와 일반 멤버변수의 메모리가 생성되는 시기가 다르기 때문입니다.

일반 멤버변수는 new 연산자를 통해 인스턴스가 생성되고 나서 메모리를 할당 받지만 static 메서드는 그 이전에 생성되고 사용될 수 있기 때문에 그렇습니다.

Constructor(생성자)

생성자란 클래스가 생성 될 때 자동으로 호출되는 메서드입니다. 모든 클래스는 최소 하나의 생성자를 가져야 하며 따로 생성자를 정의해주지 않을 시 디폴트 생성자가 자동으로 만들어집니다. 생성자는 주로 멤버변수 값을 정의 해주는 등의 용도로 사용됩니다. 예제를 한번 살펴봅시다

public class ConstructorExample {

    public int x;
    public int y;
    public int z;
    
    public static void main(String[] args) {
        ConstructorExample constructorExample = new ConstructorExample();
    }
}

위의 ConstructorExample() 이라는 메서드가 바로 생성자입니다. 위 코드에서 해당 메서드를 정의해주지 않았으나 디폴트 생성자인 ConstructorExample()가 자동으로 만들어졌기 때문에 constructorExample 인스턴스를 생성할 수 있었습니다.

다음은 멤버변수를 초기화하는 생성자를 따로 정의한 예제입니다.

public class ConstructorExample {

    public int x;
    public int y;
    public int z;
    public int result;

    public ConstructorExample(int x, int y, int z) {
        this.x = x;
        this.y = y;
        this.z = z;
        
        result = x + y + z;
    }

    public ConstructorExample(int x, int y) {
        this.x = x;
        this.y = y;
        
        result = x + y;
    }

    public static void main(String[] args) {
        ConstructorExample constructorExample1 = new ConstructorExample(1,2,3);
        ConstructorExample constructorExample2 = new ConstructorExample(1,2);

				System.out.println("constructorExample1.result = " + constructorExample1.result);
        System.out.println("constructorExample2.result = " + constructorExample2.result);

    }
}
// 결과값
constructorExample1.result = 6
constructorExample2.result = 3

ConstructorExample 생성자도 메서드이므로 오버로딩에 의해 파라미터를 달리 정의하면 같은 이름의 여러 생성자를 만들 수 있습니다. 그리고 constructorExample1과 constructorExample2는 서로 다른 생성자를 사용해서 만들어진 인스턴스이기 때문에 둘의 result 값 또한 다르게 나올 수 밖에 없는 것입니다.

오버로딩(Overloading)이란?
오버로딩은
메서드 이름 하나로 여러개의 메서드를 만들 수 있다는 개념입니다. 메서드를 구분하는 기준은 파라미터의 개수와 데이터 타입입니다.

예를 들어, println 메서드는 대부분의 데이터 타입을 파라미터로 받아 출력할 수 있어야 합니다.
하지만 오버로딩 즉, 같은 이름으로 메서드를 만들지 못한다면 우리는 다음과 같이 파라미터의 데이터 타입에 따라 메서드명을 달리 정의해주어야 할 것 입니다

printlnInt(int), printlnDouble(double), printlnBoolean(boolean), printlnChar(char) …..

소름 끼치지 않나요..?

*TMI: 파라미터의 데이터타입과 개수가 매우매우 다양한 경우에는 가변인자(varargs)를 사용하는 것이 적합합니다.
사용 방법이 궁금하시면 아래 블로그를 참고해보세요!

자바[JAVA] 가변 매개변수, 가변인자(varargs)


다음은 자바 객체지향에서 중요한 개념인 다형성에 대해서 다루겠습니다.

References

profile
Chase the Truth!

0개의 댓글