[JAVA] 클래스

SungBum Park·2021년 3월 7일
0

JAVA

목록 보기
5/9
post-thumbnail

자바는 객체지향 프로그래밍(Object-Oriented Programming, OOP) 언어이다. 따라서 객체를 중심으로 프로그래밍하는 언어로 볼 수 있다.

객체(Object)는 실제 세계의 사물 또는 개념을 한 곳에 모아놓을 것으로 말할 수 있다. 사실 정확히 실제 세계와 같진 않지만, 같은 책임과 역할을 갖는 상태와 행동을 한 곳에 모아서 관리하는데 중점을 두어야 한다.

클래스(Class)는 위 객체의 틀 또는 설계도로 볼 수 있다. 자바는 이러한 설계도인 클래스를 가지고 여러 객체를 생성하는데 사용한다.

인스턴스(Instance)는 클래스라는 설계도로 객체를 만드는데, 이렇게 만들어진 객체를 인스턴스라고 부른다. 이와같이 객체를 사용하기 위해 클래스로 객체를 만드는 과정을 '인스턴스 화'라고 한다. 인스턴스는 정확히 실제 메모리에 할당된 객체 데이터를 말하며, 프로그램이 동작하는 동안 객체는 인스턴스 형태로 만들어져 사용된다. 따라서 하나의 객체가 여러 인스턴스로 만들어져 사용할 수 있다.(객체를 변수로 선언하는 과정이 인스턴스 화이다.)

클래스 정의하는 방법

클래스 구성

필드(field)

객체의 상태 또는 속성을 나타낸다.

  • 인스턴스 변수: 객체를 생성한 이후에 사용할 수 있는 변수를 말한다. (static 키워드가 없는 변수)
  • 클래스 변수(정적 변수): 객체 생성 여부와 관계없이 사용할 수 있는 변수를 말한다. ( static 키워드를 명시한 변수)

static 변수는 자바 실행 시간에 단 한번만 메모리에 할당되는 변수이다. 실행 시간에 메모리에 할당되므로, 인스턴스 생성없이도 사용할 수 있다. 따라서 모든 인스턴스가 하나의 변수를 공유한다. static 변수가 실제 저장되는 메모리 공간은 Heap의 Metasapce(Java 8 이전에는 PerGem) 공간이고 이는 GC 대상이 아니다. (static 키워드 관련 메모리에 대해서는 이 링크를 참조)

public class Dog {
    String name;
    int age;

    static long id;
}

위는 간단하게 클래스의 필드를 선언한 모습이다. 자바는 초기화하지 않은 변수는 자동으로 초기화값을 삽입한다. (int/long = 0, boolean = false, 객체 = null 등)

메서드(method)

메서드는 객체의 행동을 나타낸다.

  • 인스턴스 메서드: 객체를 생성한 이후에 사용할 수 있는 메서드
  • 클래스 메서드(정적 메서드): 객체 생성 여부와 관계없이 사용할 수 있는 메서드
public class Dog {
    String name;
    int age;

    static long id;

		void move() {
	    // 메서드 구현   
    }
}

위는 move() 메서드를 구현한 모습이다. 메서드 정의는 아래에서 자세히 다룬다.

생성자(constructor)

객체를 생성한 직후에 필요한 초기화 작업을 수행할 수 있는 메서드를 말한다.

public class Dog {
    String name;
    int age;

    static long id;
    
    Dog() {
        // 생성자 구현
    }

    void move() {

    }
}

초기화 블럭(initializer)

초기화 블럭은 생성자와 같이 객체를 초기화할 때 사용된다. 대부분 객체를 생성할 때는 생성자를 사용하지만, 클래스 자체를 초기화할 때 해야할 로직이 있거나, 여러 생성자에서 중복되는 로직이 있다면 이를 중복 제거하는데 초기화 블럭을 사용할 수 있다.

  • 클래스 초기화 블럭: 클래스가 최초로 로딩될 때 초기화 작업을 할 수 있는 공간
  • 인스턴스 초기화 블럭: 인스턴스 생성시 초기화 작업을 할 수 있는 공간
public class Dog {
    String name;
    int age;

    static long id;
    
		// 클래스 초기화 블럭
    static {
        id = 1;
    }

		// 인스턴스 초기화 블럭    
    {
        id = 2;          // 인스턴스 블럭에서도 물론 클래스 변수에 접근이 가능하다.
        name = "바둑이";
        age = 3;
    }
}

클래스 내부의 필드 초기화 순서는 다음과 같다.

  • 클래스 변수: 기본값 → 명시적 초기화 → 클래스 초기화 블럭
  • 인스턴스 변수: 기본값 → 명시적 초기화 → 인스턴스 초기화 블럭 → 생성자
public class Dog {
	// String name;         // String은 객체이므로 기본값은 null
	String name = "바둑이";  // 명시적 초기화
	
	{
		name = "바둑이";       // 인스턴스 초기화 블럭
	}

	Dog() {
		name = "바둑이";       // 생성자 초기화
	}
}

클래스/메서드/변수 키워드

접근 제어자

그 외 키워드

객체 만드는 방법 (new 키워드 이해하기)

자바에서 객체를 만드는 일반적인 방법은 new 키워드를 사용하는 것이다.

public class Dog {
    String name;
    int age;

    static long id;
    
    Dog() {
        // 생성자 구현
    }

    void move() {

    }
}
Dog dog = new Dog();

위는 Dog 클래스를 new 키워드를 사용하여 인스턴스화하는 모습이다. 대략적인 과정은 다음과 같을 것이다.

  1. new 키워드가 Heap 메모리의 영역중 선언된 객체를 저장할 수 있는 연속된 저장공간을 찾아 할당받는다. 그리고 반환값으로 해당 저장공간의 첫번째 주소값이 반환된다.
  2. Dog() 을 호출하여 객체를 초기화한다.(기본 생성자를 직접 호출하는 모습이며, 다른 생성자 역시 호출가능하다.)

아마 위 두 과정을 거쳐 new 키워드는 객체를 생성할 것이며, 결과로 저장된 메모리 주소를 반환한다. new 키워드 자체가 생성된 객체의 주소를 반환하므로, 반드시 위처럼 객체 타입에 저장할 필요는 없고 바로 사용도 가능하다.

new Dog().move();

메소드 정의하는 방법

기본 메서드 정의, orverride, overload

메서드는 기본적으로 다음과 같이 구성된다.

<접근 제어자> <기타 키워드> <반환 타입> <메서드 이름> (<매개변수>) {
	// 구현
}
  • 접근 제어자: 해당 메서드를 접근할 수 있는 범위를 지정한다.
  • 기타 키워드: final, static 등을 선언할 수 있다.
  • 반환 타입: 해당 메서드의 결과로 반환할 타입을 지정한다.(void 를 선언하여 반환값이 없을 수도 있다.)
  • 메서드 이름: 해당 메서드의 이름을 선언한다.(호출할 때 이를 사용함.)
  • 매개변수: 메서드를 호출할 때 메서드 내부에서 사용할 변수 타입과 변수 이름을 선언한다. (매개변수가 없을 때는 빈 공간으로 남겨둔다.)

<메서드 이름> (<매개변수>): 메서드 시그니처

<접근 제어자> <기타 키워드> <반환 타입> <메서드 이름> (<매개변수>): 선언부

오버라이딩(Overriding)

메서드 오버라이딩은 메서드를 재정의를 하는 것이다. 메서드 재정의는 같은 선언부를 가진 메서드지만 내부 구현을 다르게 하는 것을 말한다. 따라서 같은 클래스 내에서는 오버라이딩이 불가능하며, 상속 또는 인터페이스 구현을 통해 다른 클래스에서 수행할 수 있다.(같은 클래스 내부에서 오버라이딩을 하면, 외부에서 해당 메서드를 호출할 때 둘 중 어느 것을 선택할 지 알 수 없으므로 컴파일 에러가 발생한다.)

public abstract class Animal {
	public abstract void speak();
}
public class Dog extends Animal {
	// ...

	@Override
	public void speak() {
		System.out.println("멍멍!");
	}
}

메서드 오버라이딩은 객체지향 프로그래밍 언어인 자바에서 다형성을 구현하기 위해 사용되는 매우 중요한 개념이다.

오버로딩(Overloading)

메서드 오버로딩은 메서드의 시그니처 중 매개변수를 다르게 선언하여, 같은 이름의 메서드를 여러 개 구현할 수 있는 방법이다.

public int calculate(int a, int b) {
	return a + b;
}

public long calculate(Long a, Long b) {
	return a + b;
}

public float calculate(float a, float b) {
	return a + b;
}
assertThat(calculate(1, 2)).isEqualTo(3);
assertThat(calculate(10L, 20L)).isEqualTo(30);
assertThat(calculate(1.2f, 2.3f)).isEqualTo(3.5);

위는 메서드 오버로딩을 사용하는 예제이다. 주의할 점은 <반환 타입>은 메서드 시그니처에 포함되지 않으므로, <반환 타입>만 다르게 해서는 오버로딩을 할 수 없다.

메서드 오버로딩을 사용하는 대표적인 곳은 생성자이다. 흔히 생성자 오버로딩이라 부르며, 방법은 메서드 오버로딩을 하는 방법과 같다.

생성자 정의하는 방법

생성자는 위에서 설명했듯이, 객체를 초기화하는 방법 중 하나다. 생성자는 생성자 오버로딩을 통해 여러 개의 생성자를 만들 수 있으며, 만약 생성자를 선언하지 않은 경우는 기본 생성자가 자동으로 생성된다.(물론 선언하지 않았다면, 코드상으로 보이지는 않음)

기본 생성자(Default Constructor)

public class Dog {
	String name;
	int age;
}
Dog dog = new Dog();

생성자를 선언하지 않았지만, 클래스 내부에 아무런 생성자가 없을 때는 내부에서 기본 생성자를 만들어준다. 주의할 점은 다른 생성자가 선언되어 있다면, 기본 생성자는 생성해주지 않는다.

public class Dog {
	String name;
	int age;

	Dog (String name, int age) {
		// ...
	}
}
Dog dog = new Dog();   // 컴파일 에러

Dog 클래스 내부에 이미 다른 생성자가 구현되어 있으므로, 기본 생성자를 자동으로 만들어주지 않는다. 따라서 위 코드는 컴파일 에러가 발생한다.

생성자 오버로딩

public class Dog {
	private String name;
	private int age;

	Dog() {
	}

	Dog(Sting name) {
		this.name = name;
	}

	Dog(int age) {
		this.age = age;
	}

	Dog(Sting name, int age) {
		this.name = name;
		this.age = age;
	}
}
Dog dog1 = new Dog();
Dog dog2 = new Dog("바둑이");
Dog dog3 = new Dog(3);
Dog dog4 = new Dog("바둑이", 3);

this 키워드 이해하기

this 키워드는 클래스 내부에서 사용하는 키워드이며, 자기 자신의 객체를 가리킨다. 객체라는 것에 주의하자! 즉, 인스턴스화되지 않은 경우에는 이 키워드를 사용할 수 없다.

this 키워드를 사용하는 경우는 크게 3가지이다.

지역변수와 인스턴스 변수와 구분할 때

대표적으로 생성자에서 인스턴스 변수를 초기화할 때 사용한다.

public class Dog {
	private String name;
	private int age;

	Dog(Sting name, int age) {
		this.name = name;
		this.age = age;
	}
}

메서드 또는 생성자의 매개변수로 넘어오는 변수 역시 메서드 내부에서만 사용할 수 있는 지역변수이다. 따라서 지역변수와 인스턴스 변수가 이름이 같으면 둘을 구분할 수가 없다. 이 때 구분해줄 수 있는 것이 this 키워드를 사용하는 것이다.

객체 자신을 반환하고 싶을 때

public class Dog {
	// ...
	public Dog getDog() {
		return this;
	}
}

객체 자신의 생성자를 호출하고 싶을 때

this() 는 생성자를 호출한다. 기본 생성자든 내부에 다른 매개변수가 있는 생성자든 호출할 수 있다.

public class Dog {
	private String name;
	private int age;

	Dog() {
		this("바둑이", 3);
	}

	Dog(Sting name, int age) {
		this.name = name;
		this.age = age;
	}
}

super 키워드

this 가 자기 자신의 객체를 가리키는 참조값이라면, super 는 자신이 상속받은 상위 클래스의 객체를 가르키는 참조값으로 사용할 수 있다.

public class Animal {
	protected String name;
	protected int age;

	Animal(String name, int age) {
		this.name = name;
		this.age = age;
	}
}
public class Dog extends Animal {
	private String ownerName;

	Dog() {
		super("바둑이", 3);
		this.ownerName = "홍길동";
	}

	public String getName() {
		return super.name;
	}

	// ...
}

super 키워드 역시 this 키워드와 사용법은 같다. 단지 가리키는 참조의 종류가 다를 뿐이다.

참고자료


profile
https://parker1609.github.io/ 블로그 이전

0개의 댓글