객체지향 프로그래밍(OOP)에서 가장 기본이 되는 Class는 다음과 같이 정의할 수 있습니다.
클래스는 필드를 선언할 수 있고, 생성자를 가져야 하며, 메서드를 선언할 수 있습니다.
public class Bicycle {
// Bicycle 클래스는 3개의 필드를 가지고 있습니다.
// 클래스에 선언된 필드와 함수들을 통틀어 멤버라고 합니다.
// 아래의 필드는 멤버 변수라고도 부르고, 속성(property)이라고도 합니다,
// 일반적으로 객체지향 프로그래밍을 할 때는 private 접근제어자로 속성을 감춥니다.
public int cadence;
public int gear;
public int speed;
// 다음은 생성자라고 불리는 문법입니다.
// 생성자는 특수한 형태로, 인스턴스를 초기화 할 때 사용하는 문법입니다.
// 생성자가 만약 존재하지 않는다면 기본 생성자(매개변수를 받지 않는)가 생성됩니다.
// 생성자는 클래스와 이름이 같아야 합니다.
// this 키워드는 생성되는 인스턴스를 가리킵니다. (혼란을 방지하기 위해 사용합니다.)
public Bicycle(int startCadence, int startSpeed, int startGear) {
this.cadence = startCadence;
this.speed = startSpeed;
this.gear = startGear;
}
// Bicycle 클래스는 4개의 메서드를 가집니다.
public void setCadence(int newValue) {
this.cadence = newValue;
}
public void setGear(int newValue) {
this.gear = newValue;
}
public void applyBrake(int decrement) {
this.speed -= decrement;
}
public void speedUp(int increment) {
this.speed += increment;
}
}
이 코드의 마지막 메서드 두 개는 객체지향적이라고 볼 수 있습니다. 외부에서 setter로 속도를 조절하는 것이 아니라, 객체가 직접 자신의 속성을 의미있게 변경하도록 했기 때문입니다.
이런 식의 코드를 지향한다면, 보다 읽기좋고, 변화에 유연한 코드를 작성할 수 있습니다.
클래스의 선언에는 필드, 생성자, 메서드 정의가 포함될 수 있습니다. 각각의 용도는 다음과 같습니다.
그 밖에도 부모 클래스를 상속하는 지의 여부나, 인터페이스를 구현하는 지의 여부 등을 클래스 선언 시작지점에 정의합니다.
class MyClass extends MySuperClass implements YourInterface {
// field, constructor, and
// method 정의
}
위 클래스 MyClass
는 MySuperClass
의 서브클래스이며, YourInterface
를 구현함을 의미하는 코드입니다.
또한 접근 제어자(public
, private
)를 맨 앞에 추가할 수도 있으므로 클래스 선언부는 복잡해질 수 있습니다.
두 접근 제어자는 다른 클래스가 MyClass
에 접근할 수 있는지를 결정합니다.
클래스 선언에 포함될 수 있는 요소들입니다.
public
, private
등의 제어자들extends
키워드 다음에 부모 클래스의 이름을 붙입니다. 클래스는 단 하나의 상위 클래스만 상속할 수 있습니다. 자바는 다중상속을 지원하지 않습니다.implements
키워드 뒤에 인터페이스들의 목록이 올 수 있습니다. 클래스는 여러 인터페이스를 동시에 구현할 수 있습니다.일반적으로 클래스는 객체의 청사진이라고 합니다. 이 의미는 무슨 뜻일까요?
한 클래스를 가지고 해당 클래스의 인스턴스 여러개를 만들 수 있습니다. 클래스에 정의된 필드나 메서드 정보는 인스턴스 생성시마다 다른 장소에 저장됩니다.
그렇다면 어떻게 해야 이 인스턴스를 만들어낼 수 있을까요? 위에서 말한 생성자는 어떻게 해야 이용할 수 있을까요?
Point originOne = new Point(23, 94);
Rectangle rectOne = new Rectangle(originOne, 100, 200);
Rectangle rectTwo = new Rectangle(50, 100);
위의 코드가 바로 생성자를 이용해서 객체를 만들어내는 코드라고 볼 수 있습니다. 새로운 객체를 생성할 때에는 new
키워드를 붙여주어야 하며, 이 때 사용하는 것이 생성자고, 이를 초기화한다고 합니다.
즉, 위의 구문은 다음의 세 가지 순서로 진행됩니다.
new
키워드로 컴파일러에게 해당 타입의 인스턴스를 생성할 것임을 알려줍니다.new
키워드 다음에 생성자 호출이 이루어지면서, 객체를 초기화합니다.우리는 컴파일러에게 우리가 사용할 객체 타입을 알려주고, 이에 대한 변수를 선언할 수 있습니다.
Type name;
위와같은 방식으로 변수를 선언할 수 있습니다. 이렇게 컴파일러에게 알려주면, 컴파일러는 name
이라는 변수는 Type
이라는 타입을 저장할 공간과 이에 대한 참조값을 저장할 공간을 할당합니다. 기본 타입을 사용하면 그에 맞는 적절한 메모리를 할당하며, 기본 타입 이외의 타입을 사용할 경우 참조 값을 저장할 공간만 할당됩니다.
꼭 선언과 동시에 초기화 할 필요는 없으며, 적절한 위치에 선언하고 초기화 하는 방식도 종종 사용됩니다.
하지만 초기화 되기 전에 사용된다면, 멤버 변수인 경우엔 기본값으로 초기화 되어서 괜찮을 수 있지만, 메서드 내부나 지역 변수인 경우엔 컴파일 에러가 발생하게 됩니다.
이런 상태를 참조하는 것이 없는 상태라고 합니다.
new
연산자는 새로운 객체를 위한 메모리를 할당하고, 그 메모리에 대한 참조를 반환하는 연산자입니다. new
연산자는 피연산자로 객체의 생성자를 사용합니다.
참고: 클래스 인스턴스화는 객체 생성과 동일한 의미입니다. 객체를 생성할 때, 클래스에 대한 인스턴스를 만드는 것이고 이를 클래스를 인스턴스화 한다고 합니다.
new
연산자는 연산자 뒤에 피연산자로 생성자를 받으며, 생성자의 이름은 인스턴스화 할 클래스의 이름을 제공하면 됩니다.
new
연산자는 만든 객체의 참조를 반환하며, 일반적으로 생성자와 맞는 적절한 타입의 변수에 할당됩니다.
꼭 new
연산자의 참조를 변수에 저장할 필요는 없습니다.
int height = new Rectangle().height;
초기화는 생성자에서 이루어지며, 생성자에서 기본값을 줄 수도 있고, 파라미터로 받아서 할당할 수도 있습니다. 생성자는 여러개 선언될 수 있으며, 이 때 생성자의 시그니처는 달라야 합니다.
자바의 경우에는 파라미터의 수, 타입에 따라 시그니처를 구분할 수 있습니다.
생성자에서 코드가 실행되면 객체가 초기화되고, 이렇게 초기화된 객체의 참조가 반환되게 됩니다.
모든 클래스는 하나 이상의 생성자가 있어야 합니다. 따라서 아무 생성자도 선언하지 않았더라도, 컴파일 과정에서 컴파일러가 기본 생성자를 추가해줍니다. 기본 생성자는 인자가 없는 생성자를 의미합니다.
클래스에게 다른 부모 클래스가 없다면 Object
의 생성자가 사용됩니다. 만약 부모가 생성자가 없다면, 컴파일러는 프로그램의 실행을 거부할 것입니다.
해당 부분이 궁금해서 스택오버플로우에서 찾아보았습니다.
신기하네요!
변수에는 예전에 공부했듯이 여러 종류가 있습니다.
필드 정의는 순서대로 세 가지 구성 요소로 구성됩니다.
변수를 해석할 때에는 위의 세 가지 요소로 해석하면 됩니다. 예를 들어 public String name
이라면, 어디서든 접근 가능한 String
타입 변수 name
으로 읽을 수 있습니다.
참고로 접근 제어자는 멤버 변수에만 할당 될 수 있습니다. 그 밖에도 final
, static
의 키워드가 붙을 수 있습니다.
접근 제어자는 우리가 해당 필드가 접근할 수 있는 클래스들을 제어할 수 있도록 해줍니다.
접근 제어자 | 클래스 내부 | 패키지 내부 | 패키지 외부의 서브 클래스 | 전체 |
---|---|---|---|---|
public | Y | Y | Y | Y |
protected | Y | Y | Y | N |
no modifier(default) | Y | Y | N | N |
private | Y | N | N | N |
캡슐화 때문에 일반적으로 필드는 private
으로 만듭니다. 하지만 외부에서 값에 접근하고 싶을 수 있기 때문에, 간접적으로 값에 접근할 수 있도록 할 수 있습니다.
보통 값을 가져오는 메서드를 getName()
과 같은 식으로 작성하고, 이를 getter라고 합니다.
값을 설정하는 메서드는 setName(toBe)
과 같은 식으로 작성하고, 이를 setter라고 합니다. 보통 객체지향적이지 않은 경우가 많기 때문에 사용을 지양하는 편이 좋습니다.
모든 변수는 선언시에 반드시 타입이 있어야합니다. 없으면 컴파일 에러가 발생합니다.
타입에는 기본형을 사용할 수도 있고, 문자열, 배열, 객체등의 참조 타입도 사용할 수 있습니다.
어떤 종류의 변수라도 변수 이름이 있어야 하고, 카멜케이스로 작성해야 한다는 암묵적인 규칙이 있습니다.
오라클 자바 튜토리얼(멤버 변수 정의)
오라클 자바 튜토리얼(접근제어)
일반적인 메서드 선언의 예
public double calculateAnswer(double wingSpan, int numberOfEngines, double lenghth, double grossTons) {
// 계산하기
}
메서드 선언에서 필수적인 부분은 리턴 타입, 메서드명, 괄호()
, 중괄호{}
입니다.
일반적으로 메서드 선언은 순서대로 6개의 컴포넌트로 이뤄집니다.
void
면 반환하지 않음을 나타냅니다.()
로 감쌉니다. 호출 부분에서 값을 넘겨받아 할당합니다.throws
뒤에 입력합니다.{}
로 감쌉니다. 지역변수 선언도 할 수 있습니다.메서드 시그니쳐: 메서드 명과, 파라미터 타입들은 메서드 시그니쳐라고 하는데, 이는 메서드를 구분짓는 단위가 됩니다.
메서드 명은 뭐든지 유효한 식별자가 될 수 있지만, 관례상 이름을 제한합니다. 메서드 이름은 소문자로 된 동사 또는 소문자로 된 여러 단어로 된 이름이어야하고 그 뒤에 형용사, 명사 등이 와야 합니다. 카멜케이스여야 합니다.
run
runFast
getBackground
getFinalData
compareTo
setX
isEmpty
일반적으로 메서드는 클래스 안에서 고유한 이름을 갖지만, 오버로딩 으로 인해서 중복된 이름을 가지는 다른 메서드가 존재할 수 있습니다.
자바는 메서드 오버로딩을 지원하니다. 자바는 메서드 이름이 아니라 메서드 시그니쳐로 다른 메서드들을 구분합니다. 이것이 의미하는 바는 같은 클래스에 있는 다른 파라미터 리스트를 가지는 동일한 이름을 갖는 메서드가 선언될 수 있다는 말입니다. 이를 메서드 오버로딩이라고 합니다.
예를 들어서, 그리는 역할을 하는데, 이에 대한 파라미터 타입이 다르다고, 각각에 해당하는 이름을 전부 지정해주면, 번거롭습니다. 모두 같은 이름을 사용하는 편이 훨씬 이해하는데 도움이 됩니다.
public class DataArtist {
public void draw(String s) {}
public void draw(int i) {}
public void draw(double d) {}
public void draw(int i, double d) {}
}
오버로드 된 메서드는 메서드에 전달되는 파라미터 수와 타입들로 구분됩니다. 예를 들어 draw(String s)
, draw(int i)
는 파라미터 타입이 다르므로 서로 구분됩니다.
당연히, 같은 이름을 가지고, 같은 타입을 파라미터로 받는다면 오버로딩은 성립되지 않습니다. 컴파일러는 리턴타입을 통해서 다름을 구분하지 않습니다. 따라서 다른 리턴 타입을 가진다고 하더라도, 메서드 시그니처가 같다면 정의할 수 업습니다.
int sum(int a, int b);
double sum(int a, int b);
위 코드는 컴파일 되지 않습니다.
클래스는 클래스의 청사진으로부터 객체를 생성하기 위해 호출되는 생성자를 포함합니다. 생성자 선언은 이름이 지정되어 있다는 점과 리턴타입을 선언하지 않는다는 점을 제외하면 메서드 선언과 비슷합니다.
public Bicycle(int startCadence, int startSpeed, int startGear) {
this.cadence = startCadence;
this.speed = startSpeed;
this.gear = startGear;
}
Bicycle
클래스의 생성자를 위와같이 정의했다면
Bicycle myBike = new Bicycle(30, 0, 8);
과 같은 식으로 생성자를 사용할 수 있습니다.
이 코드는 new Bicycle(30, 0, 8)
이 객체를 위한 메모리 공간을 만들고, 해당 필드를 초기화 함을 알 수 있습니다.
생성자는 여러개 선언할 수 있습니다. 여기에는 매개변수 없는 생성자도 포함됩니다. 메서드와 마찬가지로 매개변수 리스트만 다르면 됩니다.
클래스에 생성자를 제공하지 않아도 되지만, 컴파일러가 기본생성자를 자동으로 생성하고, 이는 슈퍼클래스의 인수 없는 생성자를 호출하는데, 이것이 없는 경우 오류가 발생합니다. 또한, 명시적으로 슈퍼 클래스가 존재하지 않는 경우 Object
가 슈퍼 클래스로 지정됩니다.
생성자 안에서 슈퍼 클래스의 생성자를 사용할 수 있습니다. 이는 인터페이스와 상속에서 좀 더 자세히 다루는 편입니다.
생성자도 접근 제어자를 사용하여 생성자를 호출할 수 있는 클래스를 제어할 수 있습니다.
this
키워드는 생성자, static
이 아닌 블럭에서 사용될 수 있습니다. 이것의 의미는 this
키워드를 호출한 생성자나 메서드의 현재 객체를 참조합니다.
this
를 사용하여 인스턴스 메서드 또는 생성자 내에서 현재 객체의 모든 멤버를 참조할 수 있습니다.(변수, 메서드)
혹은 this()
를 이용해 생성자를 호출할 수도 있습니다.
this
필드this
필드는 메서드에서 혹시라도 가려질 수 있는 필드가 있을 수 있기 때문에 사용됩니다.
Point
클래스를 예로 들자면,
public class Point {
public int x = 0;
public int y = 0;
public Point(int a, int b) {
x = a;
y = b;
}
}
처럼 적을 수도 있지만,
public class Point {
public int x = 0;
public int y = 0;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
이렇게 적을 수도 있습니다.
이 경우가 자바에서 훨씬 많이쓰는 형식입니다. 객체의 필드는 생성자의 파라미터에 의해 가려지고, 따라서 this
키워드를 사용하게 됩니다.
this
생성자생성자에서 this
키워드를 사용해서 동일한 클래스의 다른 생성자를 호출할 수 있습니다. 이를 명시적 생성자 호출이라고 합니다.
public class Rectangle {
private int x, y;
private int width, height;
public Rectangle() {
this(0, 0, 1, 1);
}
public Rectangle(int width, int height) {
this(0, 0, width, height);
}
public Rectangle(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
}
이 클래스는 생성자의 집합을 포함합니다. 각각의 생성자는 멤버 변수들의 일부 혹은 전부를 초기화합니다. 생성자는 초기 값이 파라미터에 의해 제공되지 않는 모든 멤버 변수에 대한 기본 값을 제공합니다.
컴파일러는 인수의 수와 타입에 따라서 호출할 생성자를 선택합니다.
중요한 규칙으로 this
생성자는 반드시 생성자의 첫번째 줄에 등장해야 합니다.(어떻게 보면 당연합니다.)