Jasontreks Blog

DM 보내기


Send

클래스와 객체

자바의 가장 큰 특징은 객체지햐 언어라는 점이다. 객체란 현실 세계의 모든 사물을 말하며 각각의 성질과 행동을 가지고 있음을 전제로 한다. 현실 세계가 작동하는 원리와 비슷하게 프로그램을 짜는것을 객체지향적 프로그래밍이라고 하는데, 이 과정에서 클래스는 매우 중요한 문법이다.

I. 객체지향 언어의 특성

캡슐화

기능을 외부에서 보이지 않도록 감싸 보호하고, 외부에는 필요한 정보만 제공하는 것이다. 캡슐화는 다음과 같은 이유로 객체지향 프로그래밍에서 중요하다

  • 보안성: 내부 코드를 공개하지 않아 보안 위협으로부터 안전하다.
  • 가독성: 외부 인터페이스는 내부 로직이 어떤 일을 하는지 명시하므로 코드 가독성이 높다.
  • 유지 보수 용이: 내부 로직과 외부 인터페이스가 구분되어있어 수정, 오류해결 등의 관리가 용이하다.
class Student {
    private String name; // 외부 접근 불가

    // Getter: 데이터를 안전하게 읽기
    public String getName() {
        return name;
    }

    // Setter: 데이터를 안전하게 저장 (유효성 검사 가능)
    public void setName(String name) {
        if (name != null) {
            this.name = name;
        }
    }
}

상속

상속은 상위 개체의 속성을 하위 개체에게 물려주어 개체의 특징을 이어나가는 것이다.


graph TD
생물 --> 동물
생물 --> 식물
동물 --> 개
동물 --> 인간
식물 --> 나무
식물 --> 꽃

상속을 이용하면 부모클래스의 코드 일부를 자식클래스가 재사용하게 되므로 개발 비용을 줄일 수 있다.

// 부모 클래스
class Animal {
    void eat() {
        System.out.println("음식을 먹습니다.");
    }
}

// 자식 클래스 (Animal을 상속받음)
class Dog extends Animal {
    void bark() {
        System.out.println("멍멍!");
    }
}

// 실행 예시
Dog d = new Dog();
d.eat();  // 부모의 메서드 사용 가능
d.bark(); // 자신의 메서드 사용
자바에서의 상속 용어

자바에서는 부모클래스를 슈퍼클래스, 자식클래스를 서브클래스라고 부른다.

다형성

같은 객체의 행동을 메서드라고 한다, 근데 같은 이름의 메서드가 객체에 따라 다르게 동작할수도 있다. 사람 객체 A와 B가 있다고 해보자. 같은 사람이더라도 말투나 목소리는 각자 다르다. 따라서 A와 B에게 "말하기" 라는 공통된 메서드가 있다면 이를 A, B 내부에서 조금 다르게 작동하도록 보완해야 한다. 이것을 오버라이딩 이라고 한다.

class Shape {
    void draw() {
        System.out.println("도형을 그립니다.");
    }
}

class Circle extends Shape {
    @Override
    void draw() {
        System.out.println("원형을 그립니다.");
    }
}

class Rectangle extends Shape {
    @Override
    void draw() {
        System.out.println("사각형을 그립니다.");
    }
}

// 실행 예시
Shape s1 = new Circle();    // 다형성: 부모 타입으로 자식 객체 참조
Shape s2 = new Rectangle();
s1.draw(); // "원형을 그립니다." 실행 (오버라이딩된 메서드 호출)
s2.draw(); // "사각형을 그립니다." 실행

객체지향 언어의 목적

  • 소프트웨어 생산성 향상
  • 실세계에 대한 쉬운 모델링

II. 클래스

클래스는 객체를 생성하기 위한 틀 같은 것으로, 어떤 속성을 가지질지 그리고 어떤 행동을 할지 정의하는 것이다. 사람 클래스를 만든다면 이름, 키, 취미, 직업 등의 속성과 먹다, 걷다, 일하다 등의 행동을 정의할 수 있다. 그리고 그 사람 클래스를 이용해 김연아, 박지성과 같은 각각의 인물을 생성해낸 것이 객체이다.

클래스 선언

클래스는 다음 네가지로 구성된다.

  • 선언: 클래스를 만드는 것.
  • 필드와 메서드: 클래스 내 변수. 객체가 갖게 될 속성.
  • 접근 지정자: 외부로부터 접근을 허용할지 지정하는 키워드.
  • 생성자: 클래스를 생성할 때 기본으로 초기화될 값과 로직을 작성하는 메서드.
// 클래스 선언
public class Circle {
	// 필드
	public int radius;
	public String name;

	// 생성자
	public Cirlce() {
	}
	// 메서드
	public double getArea() {
		return 3.14 * radius * radius;
	}
}

객체 생성 및 접근

클래스를 선언했다면 이제 객체를 만들 수 있다. 이 때 new 키워드를 이용한다.

public static void main(String args[]) {
	Circle pizza; // 레퍼런스 변수. Circle 객체를 가리킬 변수이다.
	pizza = new Circle(); // 객체 생성. 레퍼런스 변수 pizza가 이 객체를 가리키게 된다.

	// 객체 멤버변수 접근
	pizza.radius = 10;
	pizza.name = "피자";
	// 객체 메서드 호출
	double area = pizza.getArea();
}

III. 생성자

생성자는 객체의 초기화를 위해 실행되는 메서드이다. 생성자는 기본 생성자도 있지만 개발자가 따로 정의해 원하는 초기화 작업을 구현할 수 있다.

생성자 선언

생성자는 다음과 같은 특징을 가진다.

  • 생성자 이름은 클래스 이름과 동일하다.
  • 생성자는 여러 개 작성할 수 있다.
  • 생성자는 new를 통해 객체를 생성할 때 한번만 호출된다.
  • 생성자는 리턴하지 않는다.
public class Circle {

	public int radius;
	public String name;
	// 매개변수 없는 생성자 -> 기본 생성자를 대체
	public Cirlce() {
		radius = 1;
		name = "";
	}
	// 매개변수 있는 생성자 -> 원하는 값으로 초기화
	public Circle(int r, String n) {
		radius = r;
		name = n;
	}
}
생성자를 만들면 기본 생성자는 사라진다.

클래스에 어떤 생성자도 만들지 않으면 기본 생성자가 자동으로 만들어진다. 기본 생성자는 아무 작업도 하지 않는 빈 메서드이다. 하지만 클래스에 생성자를 하나라도 만들면 이 기본 생성자는 만들어지지 않는다.

this 레퍼런스

this메서드를 호출한 자기 자신에 대한 참조이다. 즉 메서드 안에서 그것을 호출한 스스로의 객체에 접근할 때 this를 쓴다. this를 쓰면 코드의 직관성이 높아지며 메서드의 매개변수와 자신의 멤버변수 이름의 중복을 예방할 수 있다.

public class Circle {

	public int radius;
	public String name;
	// 매개변수 없는 생성자 -> 기본 생성자를 대체
	public Cirlce() {
		this.radius = 1;
		this.name = "";
	}
	// 매개변수 있는 생성자 -> 원하는 값으로 초기화
	public Circle(int r, String n) {
		this.radius = r;
		this.name = n;
	}
}

this를 이용하면 다른 생성자도 호출할 수 있다.

public class Circle {

	public int radius;
	public String name;
	// 매개변수 없는 생성자 -> 기본 생성자를 대체
	public Circle() {
		// this로 Circle(int r, String n) 생성자를 호출
		this(1, "");
	}
	// 매개변수 있는 생성자 -> 원하는 값으로 초기화
	public Circle(int r, String n) {
		this.radius = r;
		this.name = n;
	}
}
this() 사용 시 주의사항
  • this()는 생성자 코드 안에서만 호출 가능.
  • this()는 같은 클래스 내 다른 생성자 호출할때만 사용.
  • this()는 생성자의 첫 번째 문장이 되어야 함.

IV. 객체 배열

자바에서 객체를 원소로 하는 배열을 객체에 대한 레퍼런스를 원소로 갖는 배열이다.

아래는 Circle을 원소로 하는 5개 배열을 만들고 각각의 원소에 생성자로 생성한 객체의 레퍼런스를 전달하는 예제이다.

// c는 Circle[5] 배열에 대한 레퍼런스
Circle[] c;
// 베열 객체 생성 후 레퍼런스 할당
c = new Circle[5];
// 각각의 원소에 Circle 객체 생성 후 레퍼런스 할당
for (int i = 0; i < c.length; i++) {
	c[i] = new Circle(i + 1);
}

아래는 자바에서 만든 객체 배열에 접근하는 예제이다.

for (int i = 0; i < c.length; i++)
	System.out.println((int)(c[i].getArea()));
9.8596
19.7192
29.5788
39.4384
49.298

객체에 대한 레퍼런스의 개념

자바에서 객체를 만들면 객체가 그 변수에 저장되는것이 아니라, 지바 가상머신이 관리하는 별도의 저장소에 저장이 된다. 그리고 이에 접근할 수 있게 해주는 주소와 비슷한 개념인 암호키같은걸 건네주는데, 그것이 레퍼런스이다.

그래서 객체를 그냥 출력하면 다음과 같이 고유한 키값 같은게 출력되는것을 볼 수 있다.

for (int i = 0; i < c.length; i++) {
	System.out.println(c[i]);
}
study1.Circle@51521cc1
study1.Circle@1b4fb997
study1.Circle@deb6432
study1.Circle@28ba21f3
study1.Circle@694f9431

V. 메소드

메소드는 클래스의 멤버 함수로, 접근 지정자를 필요로 한다.

접근 지정자

접근 지정자는 다른 클래스에서 그 메소드를 호출함에 있어 허용 범위이다.

접근 지정자동일 클래스동일 패키지자식 클래스전체
publicOOOO
protectedOOOX
defaultOOXX
privateOXXX

아래는 메소드 선언/정의 예제이다.

// 접근지정자 + 반환타입 + 함수명(파라미터)
public int getSum(int i, int j) {
	int sum;
	sum = i + j;
	return sum;
}

값 전달

메소드를 호출할 때, 매개변수로 값을 넣어주는것을 전달 이라고 한다. 매개변수 타입에 따라 전달은 값 복사가 이루어지기도 하고, 레퍼런스가 전달되기도 한다.

  • 기본 타입: int, float과 같은 기본 타입은 그 값이 통쨰로 복사되어 함수 내부로 전달된다.
  • 객체: 객체에 대한 레퍼런스가 전달된다. 따라서 함수 내부에서 객체를 접근하더라도 같은 원본을 가리키고 있게 된다.
  • 배열: 배열 역시 배열에 대한 레퍼런스가 전달된다.

메소드 오버로딩

오버로딩은 같은 이름의 함수를 여러 번 작성하는것을 말한다. 같은 기능임을 명시하되 받는 값을 달리 하거나 반환 형태를 달리할 때 이 문법이 이용된다.

오버로딩을 작성할 때는 다음과 같은 규칙이 있다.

  • 메소드 이름이 같아야 한다.
  • 매개변수 구성이 달라야 한다.

다음과 같은 경우 오버로딩에 실패한다.

  • 반환 타입만 다른 경우
  • 변수명만 다른 경우
  • 접근 지정자만 다른 경우

아래는 메소드 오버로딩 예제이다.

class Calculator {
    // 1. 기본 메소드
    void multiply(int a, int b) {
        System.out.println(a * b);
    }

    // 2. 매개변수 개수가 다름 (성공)
    void multiply(int a, int b, int c) {
        System.out.println(a * b * c);
    }

    // 3. 매개변수 타입이 다름 (성공)
    void multiply(double a, double b) {
        System.out.println(a * b);
    }

    // 4. 매개변수 순서가 다름 (성공)
    void multiply(int a, double b) {
        System.out.println(a * b);
    }

    void multiply(double a, int b) {
        System.out.println(a * b);
    }
}

객체가 오버로딩이 된 메서드를 호출할 때는 전달된 매개변수에 맞는 메서드를 찾아 호출된다.


VI. 객체의 소멸

소멸이랑 객체를 위해 할당된 공간에 대한 권한을 자바 가상머신이 다시 가져가는 것으로, 객체가 가지고 있던 데이터들을 삭제한 후 가용 메모리로 전환된다. 자바는 C++처럼 delete와 같은 소멸 연산자나 소멸자가 존재하지 않는다. 자바가 객체를 소멸하는 방법은 해당 객체를 참조하는 레퍼런스 변수가 한개도 없으면 가비지로 판단하고 소멸 작업에 들어가게 된다.

Circle a = new Circle(1);
Circle b = new Circle(1);
// a -> 51521cc1
// b -> 1b4fb997

System.out.println(a);
System.out.println(b);
study1.Circle@51521cc1
study1.Circle@1b4fb997
// a -> 1b4fb997
// b -> 1b4fb997
// X -> 51521cc1 : 어느 레퍼런스 변수도 51521cc1을 참조하지 않음 -> 가비지 판정
a = b;

System.out.println(a);
System.out.println(b);
study1.Circle@1b4fb997
study1.Circle@1b4fb997

가비지 컬렉션

이러한 가비지들이 늘어나게 되면 자바 가상머신은 일정 시점에서 가비지들을 회수해 가용 메모리로 바꾼다. 이 작업을 가비지 컬렉션 이라고 하며 객체 소멸도 이때 일어난다.

가비지 컬렉션은 자바 가상머신이 자동으로 해주지만 개발자가 직접 강제로 요청할수도 있다.

System.gc();

VII. 접근 지정자

객체는 캡슐화라는 특징을 갖고 있기에 외부로부터의 접근을 얼마나 허용할지 제어가 필요하다. 객체의 어떤 속성은 객체 내부에서만 필요할 수도 있고, 어떤건 외부에서 자유롭게 접근할 수 있어야 하고, 어떤건 자신을 상속한 자식 클래스에서만 접근할 수 있도록 해야한다.

  • public: 다른 패키지나 다른 클래스 어디서나 접근 가능
  • protected: 같은 패키지 안에서 접근 가능, 다른 패키지더라도 자식 클래스라면 접근 가능
  • 생략(defualt): 같은 패키지 안에서만 접근 가능
  • private: 자기 안에서만 접근 가능

VIII. static 멤버

static(정적) 멤버는 객체가 아닌 클래스 자체가 소유하는 멤버이다. 그래서 클래스 멤버라고도 부른다 static 멤버는 아래와 같은 특징을 가진다.

  • 클래스 당 하나 생성
  • 객체 내부가 아닌 클래스 코드 공간에 생성
  • 객체 없이도 접근 가능
  • 프로그램이 종료될 때 사라짐
class Circle {
	public int radius;
	// static 멤버 변수 -> Circle.PI
	static public double PI;
	// static 메소드 -> Circle.getPowPI
	static double getPowPI() {
		return PI * PI;
	}
}
public class Hello {
	public static void main(String[] args) {
		// static 멤버 접근
		Circle.PI = 3.141592;
		// static 메소드 호출 (객체 없이 클래스 이름만으로 호출)
		System.out.println(Circle.getPowPI());
	}
}

static 멤버는 다음 제약조건이 있다.

  • static 메소드는 static 멤버에만 접근 가능
  • static 메소드는 this를 사용할 수 없음

VIIII. final

final 키워드는 세 가지 사용방법이 있다.

final 클래스

클래스 선언 시 final 키워드를 쓰면 그 클래스는 상속받아 서브클래스를 만들 수 없는 클래스가 된다.

final calss FinalClass {
	...
}

final 메소드

메소드 선언 시 final을 사용하면 오버라이딩을 할 수 없는 메소드가 된다.

public class Super {
	protected final int finalMethod() {
		...
	}
class Sub extends Super {
	// 컴파일 오류 발생
	protected int finalMethod() {

	}
}
}

final 필드

멤버 변수 선언 시 final 키워드를 사용하면 상수가 된다.

class Circle {
	public int radius;
	// 상수. 초기화 이후 값 변경 X
	final double PI = 3.141592;
}