Class

원화가·2024년 3월 27일

객체 설계도, 클래스에 대하여.

객체

Object,
어떤 속성과 어떤 행동들의 집합체.
예를들면 사람은 [이름, 키, 소속 집단]등의 정보와 [걷다,먹다,말하다]의 행동이 집합된 객체이다.

클래스는 이 속성을 field(data), 동작을 method로 정의했다.

public class ClassName{
	....
}
*Camel case 관례 : 단어앞은 대문자로 표기해 구분, 단 변수의 시작은 소문자.
ex ) ClassName, ImClass / numVal, carSize

특별한 이유가 없다면 소스파일 하나당 클래스 하나를 선언하는 것이 좋다.
복수 선언을 했다면 소스파일명과 동일한 클래스만 public class로 선언할 수 있다.

Q. 🤔public 복수 선언을 하면 어떻게 되는가?

MyClass.java
-----------------------

public class MyClass{
}
public class NotClass{
}

A . 위와 같은 에러가 발생한다. public 클래스를 선언하기 위해선 파일명과 동일해야함을 알 수 있다.

객체 생성

자바에서 객체를 만들어보자.

Class val = new Class();*

Stack영역에 변수와 참조값이 저장되고, Heap영역에 Class()객체가 만들어 등록된다.
여기서 객체를 하나 더 생성해보자.

Class val = new Class();
Class val2 = new Class();

val1, val2는 각각 힙영역에 생긴 Class객체를 참조한다.

Class(설계도) 작성

클래스의 선언 형태는 다음을 따른다.

  public class ClassName{
  	//필드 선언
  	int fieldName;
    //생성자 선언
    ClassName(){...}
    //메소드 선언
    int methodName(){...}
  }

필드

필드는 데이터 저장 역할을 한다.
데이터는 3가지로 구분할 수 있다.

  • 고유 데이터 > 그 객체만의 정보.
  • 상태 데이터 > 변하는 값, 다루어야 하는 값.
  • 부품 데이터 > 객체를 구성하는데 필요한 또 다른 객체 등(집합관계).

필드 변수는 로컬변수와 마찬가지로 선언과 초기화가 가능하다.
차이점으로는 필드변수는 객체 내 외부 어디든 사용가능하다는 점.
로컬변수는 블록 내부에서만 사용가능하다는 점이다.

필드변수는 리터럴로 초기화가 가능하다.

String model = '그랜저';

초기값을 제공하지 않는다면 원시 타입은 [0, 0.0, false]등, 참조 타입은 [null]로 초기화된다.

필드 값의 사용
Class만 선언하면 설계도만 제작한 격으로 실제 객체 운용은 불가하다.
객체를 생성하고 나서야 필드 사용이 가능하다.

필드 값의 초기화는 2가지 방법이 있다.

int field1 = 1; //리터럴로 직접 입력

Class(){
    int field1 = 1; //생성자에서 초기화
}

필드변수와 로컬변수는 사용은 비슷하나 크게 다른점이 존재한다.
필드변수는 객체 내 외 어디든 사용 가능하다.
로컬변수는 블록 안에서만 사용 가능하다.

초기값을 제공하지 않는다면 다음과 같이 자동으로 초기화 된다.

정수타입실수타입참조타입
00.0null

생성자

생성자는 객체가 생성될 때 초기화를 담당한다.
가장 간단하게 볼 수 있는 생성자는 new연산자를 사용할 때 사용하는 생성자이다.

MyClass mC = new MyClass();

클래스 내부에서 따로 생성자를 지정하지 않아도 자동으로 바이트 코드에 추가된다.

생성자 선언

개발자가 생성자를 따로 선언하는 경우는 객체를 다양하게 초기화 하기 위함이다.
그 과정에서 다양한 변수를 받는 다양한 생성자를 만들 수 있다.

public Class Car{
    String name;
    String car;
    String model;
    
    Car(){}
    Car(String name){
         this.name = name;
         this.car = "audi";
    }
    Car(String name, String car, String model){
         this.name = name;
         this.car = car;
         this.model = model;
    }
}

이와 같이 동일한 함수명에 다양한 변수를 받을 수 있도록하는걸 오버로딩이라고 한다.

오버로딩 의 조건
(반환 타입 : 상관없음) (함수명 : 동일해야함) (순서, 타입, 값의 양: 모두 달라야 함)

다만 이렇게 오버로딩을 진행하면 중복 코드가 상당히 많아 지게 된다.
이때 this키워드를 통해 중복 코드를 줄일 수 있다.

this 현재 객체를 의미한다. 객체가 힙 공간에 할당되어야 사용할 수 있으며, this()는 객체내 생성자를 의미한다. 각 생성자에 맞는 매개 변수를 넘김으로 오버로딩을 시행한다.

this 키워드를 이용해서 중복코드를 제거 하면 다음과 같다.

public Class Car{
    String name;
    String car;
    String model;
    
    Car(){}
    Car(String name){
         this(name, "Audi", "i8")
    }
    Car(String name, String car, String model){
         this.name = name;
         this.car = car;
         this.model = model;
    }
}

메소드

메소는 객체의 행동을 정의한다.
객체의 내외부에서 모두 사용가능하기 때문에, 객체간 상호작용을 정의한다 볼 수 있다.

메소드명은 캐멀스타일 변수 작성법과 동일하게 한다.

double divide(int x, int y){...}

넘길 매개 값이 없다면 매개변수 생략이 가능하다.

메소드의 리턴 값을 변수에 할당하기 위해서는 변수도 같은 타입이거나 자동 타입 변환이 가능해야 한다.

객체의 외부에서 다음과 같이 접근할 수 있다.

void method(){
    Calendar calc = new Calendar();
    calc.powerOn();
}

‘.’은 클래스 내 멤버에 접근하게 해주는 연산자이다.

가변길이 매개변수

만약 여러개의 매개 값을 지정하지 않고 주고 싶다면 가변길이 매개변수를 사용한다.

int sum(int ... values){...}
sum(1,2,3,);
sum(1,2,3,4,5);
sum(new int[] {1,2,3});

메소드 역시 오버로딩을 지원한다.

인스턴스 멤버

클래스의 멤버는 두가지로 나뉜다.

정적 static 멤버인스턴스 멤버
클래스 고정 멤버(객체 없이도 사용)객체 소속 멤버(객체 선언시 사용)

인스턴스멤버는 객체를 생성하고 참조해 접근하고 사용할 수 있다.
지금까지 예시로 들어온 모든 필드와 메소드는 인스턴스 멤버이다.

메소드의 메모리 영역에 대한 참고
메소드는 필드값과 달리 코드의 집합이기 때문에 객체 생성마다 메모리 영역에 할당하면 비효율이다.
따라서 메소드영역을 따로 만들어 해당 공간에 배치하고, 객체들이 공유하도록 한다.

정적 멤버

Static멤버는 힙 공간에 할당되는 인스턴스와 다르게, 메소드 영역에 할당된다.
따라서 객체를 생성하지 않고도 사용할 수 있다.

public class myClass{
    //정적 멤버 초기화
    static int num1 = 2;
    static int compleNum;
    //정적 블록 초기화
    static{
        compleNum = 1 + 2 + 5 - 3;
    }
    static void method(...) {...}
}

객체 마다 가지고 있을 필요가 없는 공용상수등은 정적멤버로 선언하면 메모리 공간을 효율적으로 사용할 수 있다.
또한 인스턴스 필드를 사용하지 않는 메소드역시 정적 메소드로 사용하면 좋다. 객체 생성 없이 사용가능하기 때문.

정적 멤버는 다음과 같이 사용한다

int result1 = Calc.sum(10,5);
int result2 = Calc.constPi;

물론 객체를 생성하고 객체로 부터 참조가능하지만, 클래스명으로 불러오는 것이 관례이고 정석이다.

정적 멤버 초기화

객체 생성이 필요 없으므로 생성자가 호출되지 않는다.
따라서 클래스 내부에서 선언과 동시에 초기값을 준다.
상기 코드의 초기화 방법을 사용한다.
복잡한 정적 멤버의 초기화의 경우 정적 블록을 사용해 초기화 한다.

static {...}

정적 멤버의 인스턴스 멤버 참조

객체가 생성되지 않을 때 사용하므로 인스턴스 멤버 참조역시 불가능하다. this도 마찬가지.
참조하고 싶다면 객체를 생성하고 참조해야한다.

static void Method(){
    Class obj = new Class();
    obj.field1 = 10;
    obj.method();
}

main함수도 static이므로 클래스 내에서 인스턴스 접근을 위해선 동일한 과정을 거친다.

final 필드와 상수

클래스내 변경불가능한 값을 만들고 싶다면 final 키워드를 사용한다.
final 필드는 선언시 리터럴 초기값 대입 혹은 생성자 초기값 대입으로만 초기화된다.

final String nation = "Korea";

상수 선언
클래스내 상수를 선언한다면, 각 객체가 변하지 않는 값을 공유하는 것이므로 static하고 final한 값이어야 이득이다.

static final int NUM = 123456789;

복잡한 초기화는 블록으로 처리할 수 있다.
상수는 선언시 대문자와 언더바로 작성하는게 관례이다.

패키지

여러 클래스 파일을 관리하고 사용하려면 패키지를 사용해야한다.
패키지는 단순 디렉토리만을 의미하진 않는다. 패키지는 클래스의 일부분이다.

클래스를 호출시 direct.lowdirect.class 같은 경로로 통해 불러온다.

같은 패키지(디렉토리)안에 있는 클래스는 조건없이 사용가능하지만 다른 패키지 클래스는 import를 해야한다.
package 와 import모두 클래스를 불러올 수 있지만, package는 클래스 고유명을 전부 작성해야하고, import는 클래스명만 작성하면 된다는 점에 이점을 가진다.

클래스 고유명은 클래스가 위치하는 곳부터 상위 디렉토리의 이름을 . 연산자로 묶은것.

import를 사용해서 클래스를 다중으로 불러올때는 다음과 같이 할 수 있다.

import com.hankook.*;
import com.hankook.project.*;

*기호를 사용한다고 해당 패키지의 모든 하위 패키지를 불러오는 것이 아니다.
해당하는 패키지에서만 클래스를 불러온다.
추가로 하위 디렉토리의 클래스가 필요하다면 상기 코드와 같이 추가 코드가 필요하다.

만약 서로 다른 패키지에 동일한 클래스 명이 존재하고, 둘다 import를 한다면?

둘다 한 파일에 불러온다면 컴파일 에러가 발생한다. 이 경우 클래스의 고유명(전체 이름)을 사용해 어떤 패키지의 클래스인지 알려야한다. 물론 이 경우 import문은 필요없다.

접근제한자

만약 객체의 데이터가 잘못된 조작으로 손상된다면 큰일이다.
따라서 안전하게 데이터를 은닉하고, 무결성을 유지하기 위해 접근제한자를 사용한다.

접근제한자제한 대상제한범위
public클래스, 필드, 생성자, 메소드없음
protected필드, 생성자, 메소드같은 패키지,자식객체
default클래스, 필드, 생성자, 메소드같은 패키지
private필드, 생성자, 메소드객체 내부

생성자는 따로 명시하지 않으면 public으로 동작한다.
메소드와 필드, 클래스는 default로 간주된다.

Getter와 Setter

객체의 데이터를 다루는 방법을 익혔다.
하지만 데이터를 더 안전하게 다루는 방법은 없을까?

예를들어 음수면 안되는 값에 음수를 직접 넣어 데이터의 무결성이 위협받는 경우가 생기면?

따라서 안전하게 외부 필드접근을 막고, 내부 메소드를 통해 필드에 접근하는 방식이 선호된다.
이런 역할을 하는 메소드를 SetterGetter라고 부른다.

SetterGetter는 따로 지정된 키워드가 아니다.
그런 역할을 수행하는 메소드를 지칭한다.

private double speed;

public void setSpeed(double speed){
    if(speed<0){
        this.speed = 0;
        return;
    }else{
        this.speed = speed;
    }
}
public double getSpeed(){
    return speed;
}

각 역할을 수행하는 메소드의 경우 get,set으로 시작하는 것이 관례이다.
만약 boolean형 자료의 경우 is 로 시작한다.

싱글톤

어플리케이션이 실행되는 동안 단 한 개의 객체만을 생성하고 싶다면, 싱글톤을 사용한다.

싱글톤의 핵심 : 생성자를 private접근 제한하여 new연산자 사용을 막는다.

private으로 생성자를 제한하면 객체의 자유로운 생성이 불가능해진다.
대신 static메소드를 통해 간접적으로 객체를 얻을 수 있다.

public class Class{
    private static Class singleton = new Class();
    
    private Class() {}
    
    public static Class getInstance() {
        return singleton;
    }
}

외부에서는 getInstance()메소드를 호출함으로 접근할 수 있다.
힙공간에 하나만 존재하므로 어디서 호출하던 동일한 객체를 사용한다.

마무리

자바의 클래스와 객체에 관해 대략적인 인포를 알아보았다.
정확하고 깊은 공부를 통해 한발짝 더 나아가는 하루가 되길 바란다.

profile
은하수의 히치하이커

0개의 댓글