[JAVA] Class와 객체, 접근 제어자, Getter, Setter 메서드

dejeong·2024년 9월 7일
0

JAVA

목록 보기
9/24
post-thumbnail

생성자와 객체의 초기화

  • 생성자(Constructor)는 객체가 생성될 때 호출되어 객체의 초기화 작업을 담당하는 메서드이다.

클래스의 구성 요소

  • 속성: 클래스의 필드로 표현되며, 객체의 상태나 데이터를 저장한다.
  • 기능: 클래스의 메서드로 정의되며, 객체가 수행할 동작을 나타낸다.

클래스는 필드메서드로 구성되며, 생성자는 객체를 초기화하는 역할을 담당한다. 객체를 생성할 때 필드에 값을 할당하거나 초기 작업을 수행하기 위해 생성자가 사용된다.

생성자, 메서드, 필드는 하나도 없거나 일부만 있어도 되지만, 특정 기능을 위해 존재하는 것이 좋다.

클래스 이름 작성 규칙 (식별자 규칙)

클래스 이름은 식별자 규칙에 따라 작성해야 한다.

  1. 하나 이상의 문자로 이루어져야 한다. 예: Car, SportsCar.
  2. 첫 번째 글자는 숫자가 올 수 없다. 예: 3Car(X).
  3. 특수문자$_만 사용할 수 있다. 예: $Car, _Car (O), @Car, #Car(X).
  4. *자바 키워드(예약어)**는 사용할 수 없다. 예: int, for(X).

생성자 선언과 호출

  • 생성자는 클래스와 동일한 이름을 가져야 하며, 리턴 타입이 없다.
  • 생성자는 객체가 생성될 때 자동으로 호출

기본 생성자

public class Car {
    // 기본 생성자
    public Car() {
        System.out.println("생성자 호출 확인");
    }

    public static void main(String[] args) {
        Car car1 = new Car();  // Car 객체 생성, 생성자 호출
    }
}

이 코드에서 Car 클래스에는 기본 생성자가 선언되어 있으며, 객체가 생성될 때 Car() 생성자가 호출된다. car1 객체를 생성하면 자동으로 생성자가 호출되어 “생성자 호출 확인"이라는 메시지가 출력된다.

객체의 생성

클래스를 정의하면 해당 클래스로부터 여러 객체를 생성할 수 있다.

public class Car {

    public static void main(String[] args) {
        Car sonata = new Car();  // sonata 객체 생성
        Car avante = new Car();  // avante 객체 생성
        Car ferrari = new Car();  // ferrari 객체 생성
    }
}

이 예제에서는 Car 클래스로부터 세 개의 객체(sonata, avante, ferrari)가 생성된다.
각 객체는 new Car() 구문을 통해 개별적으로 생성되며, 각각의 객체는 서로 독립적인 인스턴스이다.

생성자에 매개변수 추가

생성자는 매개변수를 가질 수 있으며, 이를 통해 객체 생성 시 필드를 초기화할 수 있다.

public class Car {
    String model;

    // 매개변수를 받는 생성자
    public Car(String model) {
        this.model = model;  // 필드를 초기화
    }

    public static void main(String[] args) {
        Car sonata = new Car("Sonata");  // model을 "Sonata"로 초기화
        Car avante = new Car("Avante");  // model을 "Avante"로 초기화

        System.out.println(sonata.model);  // "Sonata" 출력
        System.out.println(avante.model);  // "Avante" 출력
    }
}

이 코드는 Car 클래스에 매개변수를 받는 생성자를 정의하여 객체 생성 시 필드를 초기화하는 방법을 보여준다. sonata 객체와 avante 객체는 서로 다른 model 값을 가지며, 각각의 생성자 호출에서 전달된 값으로 초기화된다.

정리 :

  • 생성자는 객체가 생성될 때 반드시 호출되며, 객체의 초기 상태를 설정하는 중요한 역할을 한다.
  • 필드 값 초기화객체가 준비되어야 할 작업을 생성자에서 처리할 수 있다.
  • 생성자는 필드를 초기화하고, 객체가 필요한 상태로 생성될 수 있도록 돕는다.

클래스의 구성 멤버 살펴보기 (필드, 생성자, 메서드)

필드(멤버 변수)

필드는 객체의 고유 데이터를 저장하는 곳이다. 변수와 비슷하게 클래스 전역에서 사용될 수 있으며, 객체가 소멸되지 않는 한 객체와 함께 존재한다. 클래스와 함께 선언된 변수를 멤버 변수 혹은 필드라고 부른다.

public class Car {
	String company; // Car 객체에 대한 생명 주기를 함께함, 멤버 변수 또는 필드라고 한다.

	public static void main(String[] args) {
	  Car car = new Car();  // 객체 생성
	}
}

보통 멤버 변수를 만들면, 생성자에서 멤버 변수를 초기화해준다.

디폴트 생성자

입력인수를 받는 생성자도 있고, 입력인수를 받지 않는 생성자도 있다. 생성자 앞에는 접근제어자 public 또는 private이 올 수 있다. 생성자는 객체가 생성될 때 호출되며, new 클래스명(입력인수, ...)처럼 new 키워드를 사용할 때 호출된다.


public class Car {
	String company;

	// 디폴트 생성자: 입력받는 매개변수가 없는 생성자
	Car() {
		company = "현대자동차";
	}

	public static void main(String[] args) {
		Car car1 = new Car();  // 디폴트 생성자를 이용해 객체 생성
	}
}

생성자 오버로딩

생성자 오버로딩은 클래스 내에서 같은 이름의 생성자를 여러 개 선언하는 것을 말한다. 이는 하나의 생성자 이름으로 여러 기능을 수행하게끔 할 수 있다. 오버로딩은 생성자뿐만 아니라 메서드에서도 사용할 수 있다.

public class Car {
	String company;
	String model;
	int maxSpeed;

	// 1번 생성자: 회사명만 초기화
	Car(String company) {
		this.company = company;
	}

	// 2번 생성자: 회사명과 모델명 초기화
	Car(String company, String model) {
		this.company = company;
		this.model = model;
	}

	// 3번 생성자: 회사명, 모델명, 최고속도 초기화
	Car(String company, String model, int maxSpeed) {
		this.company = company;
		this.model = model;
		this.maxSpeed = maxSpeed;
	}
}

public class CarExample {

	public static void main(String[] args) {
		Car sonata = new Car("현대");                 // 1번 생성자 이용
		Car sportage = new Car("기아", "sportage");   // 2번 생성자 이용
		Car gv80 = new Car("제네시스", "gv80", 300);   // 3번 생성자 이용
	}
}

위의 예시에서 볼 수 있듯, 객체를 생성할 때 다양한 매개변수를 전달해 그에 맞는 생성자를 호출할 수 있다. 이는 객체 생성 시 유연하게 초기화 작업을 할 수 있게 해준다

메서드

메서드는 클래스 내에 정의된 함수이다. 메서드는 호출하면 특정 기능을 수행하며, 입력값(인풋)을 받아 결과값(아웃풋)을 반환할 수 있다. 입력값이 없을 경우 빈 괄호를 사용하고, 출력값이 없을 경우에는 void를 사용한다.

// 인풋이 없는 메서드
String printField() {
	return "~~~";
}

// 아웃풋이 없는 메서드
void method2(String input) {
	// 실행문
}

개발자는 필요한 기능을 메서드로 작성하고, 외부에서 해당 메서드를 호출하여 기능을 실행한다. 인풋을 받아 결과를 출력하거나, 인풋을 받아 기능만 수행하거나, 인풋 없이 결과만 출력하는 방식도 가능하다.

인풋과 아웃풋 둘 다 없는 메서드

인풋과 아웃풋이 모두 없는 메서드는 다음과 같이 정의할 수 있다.

void method() {
	// 실행문
}

메서드 선언

메서드는 선언부와 실행블록으로 구성된다. 메서드 선언부에는 리턴타입과 메서드 이름이 포함되며, 필요 시 매개변수를 받는다.

리턴타입 메서드이름([매개변수 선언, ...]) {  // 선언부

 // 실행블록

}

⭐ 리턴(return) 문

return문은 메서드가 실행된 후 결과를 호출한 곳으로 돌려주는 역할을 한다. 메서드에 리턴값이 있을 수도 없을 수도 있는데, 리턴값이 있는 경우에는 return문을 사용하여 그 값을 반환해야 한다. 리턴문은 메서드 내에서 마지막에 위치해야 하며, 중간에 리턴을 사용하면 그 이후의 코드는 실행되지 않는다.

java코드 복사
void powerOn() {
	System.out.println("전원을 켭니다");  // void 리턴타입: 리턴값이 없을 경우
}

double divide(int x, int y) {
	return (double) x / y;  // double로 자동 형 변환 가능
}

double divide(int x, int y) {
	return (double) x / y;
	System.out.println("출력");  // 리턴문 뒤에 실행문이 있어서 에러 발생
}

리턴문과 조건문

특정 조건이 만족되었을 때만 리턴할 수도 있으며, 그 외의 경우에도 반드시 리턴문을 제공해야 한다. 그렇지 않으면 에러가 발생할 수 있다. else 구문을 사용하여 조건에 맞지 않을 때의 리턴값을 지정할 수 있다.

// 1. 조건에 해당하지 않으면 56.7을 리턴
double divide(int x, int y) {
	if ((double) x / y == 4.5) {
		return (double) x / y;
	}
	return 56.7;
}

// 2. else 구문으로 리턴 처리
double divide(int x, int y) {
	if ((double) x / y == 4.5) {
		return (double) x / y;
	} else {
		return 56.7;
	}
}

else 문을 사용하는 것은 컨벤션 차이로, 의미를 명확하게 하고 싶다면 else 문에 리턴을 포함할 수 있지만, 밖으로 빼더라도 동작에는 문제가 없다.

매개변수와 인수

매개변수는 메서드에 전달된 입력값을 저장하는 변수이며, 인수는 메서드를 호출할 때 전달하는 값이다. 메서드 선언 시에는 매개변수라고 부르며, 메서드를 호출할 때는 인수라고 한다.

void setMaxSpeed(int maxSpeed) {  // 매개변수
    this.maxSpeed = maxSpeed;
}

car1.setMaxSpeed(200);  // 인수

메서드 내 변수의 효력 범위

메서드 내에서 선언된 변수는 해당 메서드 내에서만 유효하다. 예를 들어, 메서드 내부에서 값을 변경하더라도 메서드가 종료되면 변경된 값은 사라진다.

public class Calculator {
    int postfixOperator(int a) {
        a++;
        return a;
    }

    public static void main(String[] args) {
        int a = 1;
        Calculator calculator = new Calculator();
        a = calculator.postfixOperator(a);
        System.out.println(a);  // 출력: 2
    }
}

변수 a는 메서드가 실행되는 동안에만 값이 증가하며, 이후 호출한 곳에 반환된다.

⭐ 메서드 내에서 선언된 변수의 효력 범위와 객체 전달

객체를 매개변수로 전달할 때의 동작을 이해하려면, 자바에서의 값 전달(call by value) 개념을 이해해야 한다. 자바는 기본적으로 값을 복사해서 전달하는 방식을 사용한다. 그러나 객체는 참조 타입이기 때문에, 참조하는 주소값이 매개변수로 전달된다. 즉, 참조된 객체의 속성값을 변경하면 원본 객체에도 영향을 미친다.

예시 코드

public class Calculator {
    int a;  // 인스턴스 변수

    // 기본 생성자
    Calculator() {
    }

    // 매개변수를 받는 생성자
    Calculator(int a) {
        this.a = a;  // this는 인스턴스 변수 a를 가리킴
    }

    // 기본 타입(프리미티브) 변수를 매개변수로 받는 메서드
    int postfixOperator(int a) {
        a++;  // 매개변수로 받은 값 증가, 원본 객체에는 영향 없음
        return a;
    }

    // 객체를 매개변수로 받는 메서드
    void postfixOperator(Calculator cal) {
        cal.a++;  // 전달된 객체의 a 값을 증가시킴
    }

    public static void main(String[] args) {
        // 1. 기본 타입 매개변수 전달
        int a = 1;
        Calculator calculator = new Calculator();
        a = calculator.postfixOperator(a);  // 메서드 실행 후 반환값을 a에 다시 할당
        System.out.println(a);  // 출력: 2

        // 2. 객체 매개변수 전달
        Calculator cal1 = new Calculator(1);  // a 값을 1로 초기화한 객체 생성
        cal1.postfixOperator(cal1);  // 객체 자체를 매개변수로 전달
        System.out.println(cal1.a);  // 출력: 2
    }
}

[동작 설명]

1. 기본 타입(프리미티브) 변수의 전달

프리미티브 타입 변수 a는 값이 복사되어 postfixOperator 메서드에 전달된다. 이 복사된 값은 메서드 안에서 증가되지만, 원본 변수 a에는 영향이 없기 때문에 리턴된 값을 다시 할당해야 한다.

int a = 1;
a = calculator.postfixOperator(a);  // 2가 리턴되고 다시 a에 할당됨
System.out.println(a);  // 출력: 2

2. 객체 타입 변수의 전달

객체 cal1을 메서드에 전달할 때는 주소값이 복사되어 전달된다. 즉, cal1과 매개변수 cal같은 객체를 참조하고 있으며, 하나에서 변경된 값은 다른 곳에서도 동일하게 반영된다.

Calculator cal1 = new Calculator(1);
cal1.postfixOperator(cal1);  // 같은 주소를 참조하고 있어 a 값이 증가됨
System.out.println(cal1.a);  // 출력: 2

참조 타입과 생명 주기

  • cal1의 주소값이 cal에 복사되었으므로, 메서드 내에서 cala 값을 변경해도 cal1도 같은 값을 가리키므로 변경된 값이 반영된다. 메서드가 종료되어 cal 변수가 소멸되더라도, cal1은 여전히 동일한 주소를 참조하므로 변경된 값을 유지한다.

요약

  • 프리미티브 타입은 값이 복사되기 때문에 메서드 내에서 변경된 값이 원본 변수에 영향을 미치지 않는다.
  • 참조 타입(객체)은 주소값이 복사되기 때문에 원본 객체의 속성 값을 메서드 내에서 변경할 수 있다.

객체 전달 방식의 권장 방법

자바에서 객체를 매개변수로 전달할 때는, 같은 객체를 직접 수정하는 방식은 선호되지 않는다. 대신 새로운 변수를 만들어 그 변수로 작업을 처리하거나, 메서드에서 리턴값을 반환하는 방식이 더 좋다.

권장 코드

  • 새로운 결과 변수를 사용하는 방식
java코드 복사
int postfixOperator(int a) {
    a++;
    return a;
}
  • 리턴값을 활용하여 결과를 처리하는 방식
Calculator incrementA(Calculator cal) {
    cal.a++;
    return cal;
}

this 키워드 사용

this현재 객체를 가리킨다. 동일한 이름의 매개변수와 인스턴스 변수가 있을 때, this를 사용하여 인스턴스 변수를 명확히 지칭할 수 있다.

Calculator(int a) {
    this.a = a;  // this는 현재 객체의 a를 가리킴
}

생성자 관련 설명

  • 디폴트 생성자는 개발자가 명시적으로 생성자를 작성하지 않은 경우에만 자동으로 추가된다.
  • 만약 개발자가 하나라도 생성자를 정의했다면, 컴파일러는 디폴트 생성자를 추가하지 않는다.

예시

public class DefultConstructor {
    String field;

    // 매개변수를 받는 생성자
    DefultConstructor(String field) {
        this.field = field;
    }

    public static void main(String[] args) {
        // DefultConstructor c2 = new DefultConstructor();  // 오류 발생: 디폴트 생성자가 없음
    }
}

컴파일러는 개발자가 생성자를 정의한 경우, 디폴트 생성자를 추가하지 않으므로 명시적으로 디폴트 생성자를 추가해야 한다.


객체 생성과 디폴트 생성자

public class DefultConstructor {
    String field;

    // 매개변수 1개를 가진 생성자
    DefultConstructor(String field) {
        field = "필드 값 초기화";
    }

    public static void main(String[] args) {
        // 매개변수가 있는 생성자 호출
        DefultConstructor constructorTest = new DefultConstructor("특정값");

        // 주석처리 후 생성자를 삭제하면 디폴트 생성자가 문제 없이 생성된다.
//        DefultConstructor c2 = new DefultConstructor();
    }
}
  • 객체를 생성할 때 디폴트 생성자로 생성하려면, 매개변수를 받지 않는 생성자를 명시적으로 작성해야 한다.
DefultConstructor() {
    field = "특정 값으로 초기화";
}
  • 이렇게 정의하면, 객체 생성 시 매개변수를 전달하지 않아도 기본값이 설정된다.

this 활용하기

  • this는 해당 객체 자신을 가리킨다. 메서드나 생성자에서 자신의 필드에 접근할 때 사용하며, 주로 매개변수와 필드 이름이 같을 때 사용된다.
this.a = a; // this.a는 객체의 필드, 오른쪽 a는 매개변수
  • 굳이 객체를 메서드의 매개변수로 전달하지 않아도, 메서드 내부에서 this를 통해 해당 객체에 접근할 수 있다.

final 필드와 상수

final 필드

  • final 필드는 한 번 값이 설정되면 변경할 수 없는 필드이다. 프로그램 실행 중 수정할 수 없다.
final int MAX_SPEED = 120; // final 필드
  • final 필드는 두 가지 방법으로 초기화할 수 있다.
    1. 필드 선언 시 초기화
    2. 생성자에서 초기화
public class Car {
    final int maxSpeed;

    Car(int maxSpeed) {
        this.maxSpeed = maxSpeed; // 생성자에서 final 필드 초기화
    }
}
  • 만약 초기값이 설정되지 않으면 컴파일 에러가 발생한다.

상수 (static final)

  • 변경되지 않는 값은 상수라고 한다. 상수는 클래스가 로드될 때 한 번만 초기화되며, 이후 값이 변경되지 않는다.
public class MathConstants {
    public static final double PI = 3.14159; // 상수 선언
}
  • 상수는 객체가 아니라 클래스 단위로 존재하며, 모든 인스턴스가 같은 값을 공유한다.

⭐ 접근 제어자

접근 제어자는 변수나 메서드에 대한 접근 권한을 설정할 수 있다.

종류

  • public: 모든 클래스에서 접근 가능.
  • protected: 같은 패키지 또는 상속받은 클래스에서 접근 가능.
  • default: (제어자를 명시하지 않을 경우) 같은 패키지에서만 접근 가능.
  • private: 해당 클래스 내부에서만 접근 가능.

private 접근 제어자

  • private으로 선언된 필드나 메서드는 해당 클래스 내부에서만 접근할 수 있다. 외부 클래스에서는 접근이 불가능하므로, 데이터 보호에 유리하다.
public class Secret {
    private String name;

    private String getName() {
        return this.name; // 클래스 내부에서만 접근 가능
    }
}

default 접근 제어자

  • default 접근 제어자는 같은 패키지 내에서만 접근이 가능하며, 다른 패키지에서는 사용할 수 없다.
package house;  // 패키지가 동일할 경우

public class HouseKim {
    String lastname = "Kim";  // default 접근 제어자
}

예시: default 접근 제어자

package house;  // 같은 패키지

public class HousePark {
    public static void main(String[] args) {
        HouseKim kim = new HouseKim();
        System.out.println(kim.lastname);  // default 접근 제어자이므로 접근 가능
    }
}
  • HouseKim 클래스의 lastname 필드는 default 접근 제어자로 설정되어 있어 같은 패키지 내에서 접근할 수 있다.

protected 접근 제어자

  • protected가 붙은 필드나 메서드는 같은 패키지 내에서 또는 상속받은 자식 클래스에서 접근이 가능하다.

상속 (Inheritance)

  • 상속이란 부모 클래스의 모든 멤버(필드와 메서드)를 자식 클래스가 물려받는 것을 의미한다. 자바에서 상속은 extends 키워드를 사용하여 선언된다.
class Sonata extends Car {
    // Sonata 클래스가 Car 클래스를 상속받음
}

예시

package chap06.car;

public class Car {
    protected String company = "르노삼성";  // protected 변수
    private String model = "모델";  // private 변수 (상속 불가)
}
package car.example;  // 서로 다른 패키지

import chap06.car.Car;

public class Sonata extends Car {  // Car 클래스를 상속받음
    public static void main(String[] args) {
        Sonata sonata = new Sonata();
        System.out.println(sonata.company);  // 상속받은 protected 변수에 접근 가능
    }
}
  • protected 접근 제어자는 다른 패키지에 있어도 상속받은 자식 클래스에서 접근할 수 있다.
  • 만약 company 필드를 default로 선언했다면, 패키지가 달라 컴파일 오류가 발생한다.

⭐ Getter와 Setter 메서드

Setter 메서드

  • Setter 메서드는 필드에 값을 설정하는 메서드이다.
  • 외부에서 데이터에 직접 접근하여 수정하지 않고, 무결성을 유지하며 데이터를 설정할 수 있다.
public class Sonata {
    private int speed;  // private 필드 (캡슐화)

    // speed 값을 설정하는 Setter 메서드
    public void setSpeed(int speed) {
        if (speed < 0) {
            this.speed = 0;  // 유효성 검사: 음수일 경우 0으로 설정
        } else {
            this.speed = speed;
        }
    }
}
  • Setter 메서드는 필드 값을 검증하여 유효한 값만 필드에 저장할 수 있도록 해준다.

Getter 메서드

  • Getter 메서드는 필드의 값을 반환하는 메서드로, 외부에서 필드 값을 간접적으로 가져올 수 있다.
public class Sonata {
    private int speed;  // private 필드

    // speed 값을 반환하는 Getter 메서드
    public int getSpeed() {
        return speed;
    }
}
  • 객체 외부에서 필드 값에 직접 접근하는 대신, Getter를 통해 가공된 값을 반환할 수 있다.

예시: Getter와 Setter 메서드

public class Sonata {
    private int speed;
    private boolean stop;

    // Setter 메서드
    public void setSpeed(int speed) {
        if (speed < 0) {
            this.speed = 0;
        } else {
            this.speed = speed;
        }
    }

    // Getter 메서드
    public int getSpeed() {
        return speed;
    }

    // boolean 타입 Getter 메서드: 관례상 is로 시작
    public boolean isStop() {
        return stop;
    }

    // Setter 메서드
    public void setStop(boolean stop) {
        this.stop = stop;
    }

    public static void main(String[] args) {
        Sonata sonata = new Sonata();

        // 잘못된 속도 변경 시도
        sonata.setSpeed(-50);
        System.out.println("현재 속도: " + sonata.getSpeed());  // 0 출력

        // 올바른 속도 설정
        sonata.setSpeed(60);
        System.out.println("변경 후 속도: " + sonata.getSpeed());  // 60 출력

        // 멈추기
        if (!sonata.isStop()) {
            sonata.setStop(true);
            sonata.setSpeed(0);  // 멈춘 후 속도 0 설정
        }

        System.out.println("멈춘 후 속도: " + sonata.getSpeed());  // 0 출력
    }
}

Setter와 Getter의 중요성

  • 필드를 직접 접근할 수 없도록 private으로 선언하고, SetterGetter를 통해 필드에 접근하는 것이 권장된다.
  • Setter 메서드에서 값의 유효성을 검증할 수 있고, Getter 메서드는 필드 값을 안전하게 반환할 수 있다.

Lombok 라이브러리 활용

  • 자바에서는 Lombok 라이브러리를 통해 GetterSetter 메서드를 자동으로 생성할 수 있다.
@Getter
@Setter
public class Sonata {
    private int speed;
    private boolean stop;
}
  • Lombok을 사용하면 코드의 양을 줄이고, 필드에 대한 접근 메서드를 쉽게 생성할 수 있다.
profile
룰루

0개의 댓글