본문 바로가기
(2024-10) 스파르타 내일배움캠프 - 백엔드/Java 기초문법

클래스

by 어뫄어뫄 2024. 10. 29.

 

 

프로그래밍 구조가 조금 더 현실 세계를 모방하기 위해, 절차지향에서는 여러 변수를 묶어 구조체struct를 보인 바 있습니다. 이제  클래스 class개체(객체) object 정의됩니다. class의 의미는 특정한 유형의 설계도를 나타내며, 이는 특정 속성과 동작을 공유하는 개체들을 정의합니다. OOP에서 클래스는 새로운 개체를 만들 때 사용하는 청사진으로, 모든 개체는 반드시 클래스로부터 생성되어야 합니다. 흔히 하는 비유들이 있습니다. 이러한 비유들은 클래스가 개체를 생성할 때 필요한 기반 구조를 제공한다는 점을 강조합니다.

 

 

 

항목함수 매개변수클래스 멤버 변수일반 변수

 

struct Car* carPtr = (struct Car*)malloc(sizeof(struct Car));

 

Car car1 = new Car();

 

struct Car* myCar = (struct Car*)malloc(sizeof(struct Car));

myCar->year = 2020;
myCar->model = "Toyota";

printf("Model: %s, Year: %d\n", myCar->model, myCar->year);

 

Car myCar = new Car();

myCar.year = 2020;
myCar.model = "Toyota";

System.out.println("Model: " + myCar.model + ", Year: " + myCar.year);

 

  • package-private (default): 접근 제어자를 명시하지 않으면 기본적으로 설정되는 접근 제어자입니다. 같은 패키지 내의 다른 클래스에서만 접근 가능하며, 다른 패키지에서는 접근할 수 없습니다.

 

public class Company {
    public String name;
    public int employeeCount;

    // 직원 수를 1 늘리는 메서드
    public void hireWorkers() {
        this.employeeCount++;
    }
}

public class CompanyExample {
    public static void main(String[] args) {
        Company techCorp = new Company();
        
        // 직원 수 기본값 출력
        System.out.printf("TechCorp's employee count is %d\n", techCorp.employeeCount);

        // 값 설정
        techCorp.name = "TechCorp";
        techCorp.employeeCount = 100;

        System.out.printf("TechCorp's employee count is %d\n", techCorp.employeeCount);

        // hireWorkers 메서드 호출
        techCorp.hireWorkers();

        // 직원 수 출력
        System.out.printf("TechCorp's employee count after hiring is %d\n", techCorp.employeeCount);
    }
}

 

① Java의 클래스는 포인터 연산이 불가능한 참조형이다.

 

마치 C 구조체에서

 

#include <stdio.h>
#include <string.h>

struct Company {
    char name[50];
    int employeeCount;
};

// 직원 수를 1 늘리는 함수
void hireWorkers(struct Company* company) {
	// 원본 값을 건드려야 해서 포인터가 됨에 유의
    // -> 문법으로 포인터가 지정하는 구조체의 값 원본에 접근한다
    company->employeeCount++;
}

int main() {
    struct Company techCorp;

    printf("TechCorp's employee count is %d\n", techCorp.employeeCount);

    strcpy(techCorp.name, "TechCorp");
    techCorp.employeeCount = 100;

	// 주소값 전달에 유의. 일종의 참조형이 된다.
    hireWorkers(&techCorp);

    printf("TechCorp's employee count after hiring is %d\n", techCorp.employeeCount);

    return 0;
}

 

C와 Java에서의 멤버 변수 초기화와 . 연산자의 사용에 대해 설명하자면, 객체 생성 시 C 구조체 변수는 선언할 때 초기화되지 않아 메모리에 남아 있던 쓰레기 값을 유지합니다. 반면 Java는 멤버 변수를 0에 준하는 값으로 자동 초기화합니다. 예를 들어, int는 0, float는 0.0, 참조형은 null로 초기화됩니다. 이는 C 언어는 성능을 중요시하지만, Java는 실수를 방지하는 것을 중시하기 때문입니다. 만약 초기값을 0이 아닌 다른 값으로 설정하고 싶다면 선언문에서 직접 값을 대입할 수 있습니다.

또한, . 연산자의 사용에 있어서 C에서는 구조체의 멤버 변수에 접근할 때 . 연산자를 사용하며, 포인터를 통해 접근할 때는 -> 연산자를 사용합니다. Java는 모든 객체가 포인터 형태로, 객체 멤버에 접근할 때 . 연산자를 사용합니다. 따라서 .와 ->의 구분이 필요 없으며, Java에는 역참조를 위한 * 연산자도 존재하지 않아서 주소를 직접 읽는 방법이 없습니다.

 

생성자

 

public class Company {
    public String name;
    public int employeeCount;

    // 생성자
    public Company(String name, int employeeCount) {
        this.name = name;
        this.employeeCount = employeeCount;
    }

    // 직원 수를 1 늘리는 메서드
    public void hireWorkers() {
        employeeCount++;
    }
}

 

이제 생성자가 생기고 초기화까지 수행해준다.

 

생성자로 객체를 초기화해야 하는 이유는 크게 세 가지입니다.

첫째, 개념적 문제로서, 마치 공장에서 완제품이 나왔는데 비어있는 상태와 같은 상황을 방지합니다. 둘째, 생성자의 후조건 문제는 객체가 생성되자마자 유효하고 사용 가능한 상태임을 보장해야 한다는 점입니다. 셋째, 사용자를 고려하지 않아 발생하는 문제인데, 이는 자신의 실수뿐만 아니라 다른 사람의 실수도 초래할 수 있습니다.

구체적으로, 클래스의 어떤 멤버 변수를 초기화해야 하는지 알기 위해서는 클래스 파일을 직접 확인해야 하며, 어떤 변수를 초기화해야 할지 몰라 혼란을 초래할 수 있습니다. 또한, 멤버 변수가 나중에 추가될 경우, 생성자가 있다면 컴파일 오류를 통해 이러한 문제를 조기에 발견할 수 있지만, 그렇지 않으면 추가된 변수를 초기화하지 않을 위험이 있습니다.

초기화해야 할 값 또한 문제입니다. 특정 분야에 대한 전문 지식이 있어야 알 수 있는 초기값이 있을 수 있으며, 초기화 과정에서 필요한 계산이 코드 내에서 중복될 수 있습니다. 또한, 나중에 멤버 변수가 추가될 때 초기화 코드를 업데이트하지 않으면 문제가 발생하며, 이는 특히 외부 라이브러리를 사용할 경우 더욱 심각해집니다.

결론적으로, 생성자는 객체 생성 시 올바른 상태를 보장하는 계약과 같습니다. 이는 올바른 함수 작성법에서 배운 모든 내용을 적용하여, 함수가 블랙박스처럼 작용하고 호출자가 함수의 책임을 분명히 분리할 수 있도록 합니다. 따라서 외부에서는 클래스 내부의 데이터를 알 필요 없이 사용할 수 있으며, 이는 추상화와 캡슐화를 통해 구현됩니다.

 

 

생성자에 오버로딩 활용

생성자의 오버로딩은 같은 이름의 생성자를 여러 개 정의하되, 매개변수의 타입이나 개수가 다르게 하는 것을 의미합니다. 이를 통해 객체를 다양한 방법으로 초기화할 수 있습니다.

 

import java.util.ArrayList;

public class Company {
    public String name;
    public int employeeCount;
    public ArrayList<String> employees;

    // 기본 생성자
    public Company() {
        this("Unnamed Company", 0, new ArrayList<String>());
    }

    // 이름과 직원 수를 받는 생성자
    public Company(String name, int employeeCount) {
        this(name, employeeCount, new ArrayList<String>());
    }

    // 이름과 직원 목록을 받는 생성자
    public Company(String name, ArrayList<String> employees) {
        this(name, employees.size(), employees);
    }

    // 이름, 직원 수, 직원 목록을 받는 생성자
    public Company(String name, int employeeCount, ArrayList<String> employees) {
        this.name = name;
        this.employeeCount = employeeCount;
        this.employees = employees;
    }

    // 직원 수를 1 늘리는 메서드
    public void hireWorker(String employeeName) {
        employees.add(employeeName);
        employeeCount++;
    }

    // 직원 수를 1 줄이는 메서드
    public void fireWorker(String employeeName) {
        if (employees.remove(employeeName)) {
            employeeCount--;
        }
    }
}

 

기본 선언

 public static void main(String[] args) {
        Company company1 = new Company();
        

    }

 

        // 도식
        +-----------+-------------------------+---------+
        | 변수명    | 값                      | 자료형  |
        +-----------+-------------------------+---------+
        | type      | CompanyType.DEFAULT     | CompanyType |
        +-----------+-------------------------+---------+
        | passengers | []                     | ArrayList<Employees> |
        +-----------+-------------------------+---------+
        | fuelAmount | 0.0                    | double  |
        +-----------+-------------------------+---------+
        | mileage   | 0                       | int     |
        +-----------+-------------------------+---------+

 

 

        ArrayList<Employees> employees = new ArrayList<>();
        employees.add(new Passenger("Alice"));
        employees.add(new Passenger("Bob"));
        
        Company company2 = new Company(CompanyType.SUV, employees);

 

        // 도식
        +-----------+-------------------------+---------+
        | 변수명    | 값                      | 자료형  |
        +-----------+-------------------------+---------+
        | type      | CompanyType.SUV        | CompanyType |
        +-----------+-------------------------+---------+
        | employees | ["Alice", "Bob"]       | ArrayList<Employees> |
        +-----------+-------------------------+---------+
        | fuelAmount | 0.0                    | double  |
        +-----------+-------------------------+---------+
        | mileage   | 0                       | int     |
        +-----------+-------------------------+---------+

 

 

public class Main {
    public static void main(String[] args) {
        Vehicle vehicle3 = new Vehicle(VehicleType.SEDAN, 50.0);
        
    }
}

 

        // 도식
        +-----------+-------------------------+---------+
        | 변수명    | 값                      | 자료형  |
        +-----------+-------------------------+---------+
        | type      | VehicleType.SEDAN      | VehicleType |
        +-----------+-------------------------+---------+
        | passengers | []                     | ArrayList<Passenger> |
        +-----------+-------------------------+---------+
        | fuelAmount | 50.0                   | double  |
        +-----------+-------------------------+---------+
        | mileage   | 0                       | int     |
        +-----------+-------------------------+---------+

 

 

public class Main {
    public static void main(String[] args) {
        ArrayList<Passenger> passengers = new ArrayList<>();
        passengers.add(new Passenger("Charlie"));
        
        Vehicle vehicle4 = new Vehicle(VehicleType.TRUCK, passengers, 75.0);
        
    }
}

 

        // 도식
        +-----------+-------------------------+---------+
        | 변수명    | 값                      | 자료형  |
        +-----------+-------------------------+---------+
        | type      | VehicleType.TRUCK       | VehicleType |
        +-----------+-------------------------+---------+
        | passengers | [Charlie]             | ArrayList<Passenger> |
        +-----------+-------------------------+---------+
        | fuelAmount | 75.0                   | double  |
        +-----------+-------------------------+---------+
        | mileage   | 0                       | int     |
        +-----------+-------------------------+---------+

 

Setter 베스트 프랙티스 요약
멤버 변수 공개 지양: 멤버 변수를 public으로 설정하는 것을 피해야 한다. 극단적인 소수설에 따르면, setter가 없는 것이 가장 좋지만, 접근을 허용하는 것이 결국 문제를 야기할 수 있다.


setter 자제: setter를 사용해야 한다면, 자제하는 것이 바람직하다.

멤버 변수는 private로 설정: 멤버 변수는 항상 private로 설정해야 한다.

개체의 유효성 유지: 새 개체는 항상 유효해야 하며, 이를 위해 생성자에서 강제로 검증을 수행한다. 매개변수가 잘못되면 컴파일이 되지 않으며, 매개변수가 변경되어도 컴파일이 되지 않는다.

getter는 자유롭게 추가: getter는 자유롭게 추가할 수 있으며, 이는 큰 문제가 되지 않는다. C++의 경우 getter에서 읽기 전용 레퍼런스를 반환할 수 있어 이와 관련된 고민이 덜하다.

setter는 신중하게 추가: setter는 추가하기 전에 신중하게 고려해야 한다. ideal한 상태 수정 방식은 개체의 사용자가 특정 동작을 지시하고, 그 결과로 개체의 상태가 변해야 한다. 즉, 개체는 자신의 상태를 스스로 변경해야 하며, setter는 가능한 한 피하는 것이 좋다.

 

 

Static

 

Pocu 아카데미 예제

// 포큐 아카데미 Java 강의 예제

public class Math {
    // Math.java
    private Math() {
    }

    public static int abs(int n) {
        return n < 0 ? -n : n;
    }

    public static int min(int a, int b) {
        return a < b ? a : b;
    }

    public static int max(int a, int b) {
        return a > b ? a : b;
    }
    
}


public class App {
    public static void main(String[] args) {
        int absValue1 = Math.abs(-2); // OK
        System.out.println(absValue1);
        // Math math = new Math(); // 컴파일 오류
        // int absValue2 = math.abs(-2);
    }
}