백기선님께서 주최하시는 자바 스터디
github.com/whiteship/live-study
whiteship/live-study
온라인 스터디. Contribute to whiteship/live-study development by creating an account on GitHub.
github.com
필수과제
먼저 과제를 수행하기 전에, 클래스가 뭔지에 대해서 알아보자.
▶ 클래스(Class)
클래스는 값을 저장하기 위한 데이터 필드와 데이터 필드를 가지고 조작하는 메소드로 이루어진 컬렉션이다.
■ 클래스의 특징
- 클래스는 데이터 타입이며, 자바의 참조 타입(Reference Type) 중 하나이다
- 클래스를 정의하는 것은 하나의 타입을 정의하는 것과 같다
- 클래스의 값은 객체(Object) 이다
■ 객체(Object) 는??
=> 클래스의 인스턴스(instance) 이다
위에서 클래스에 대해서 대충 알아봤으므로 과제를 수행하면서 더 자세히 알아보자
▶ 클래스는 어떻게 정의할까?
클래스를 정의하는 방법은 다음과 같다
// class signature
public class Point {
/*
여기서 부터 클래스의 body
*/
// 클래스의 필드를 정의
public int x;
public int y;
// 클래스의 생성자
public Point(int x, int y) {
this.x = x;
this.y = y;
}
// 클래스의 메소드
public float getDistanceFrom() {
return Math.sqrt(x * x, y * y);
}
}
≫ 클래스는 signature 와 body 로 구성되어 있다
class signature => 클래스의 이름과 다른 중요한 정보를 명시한다 (위의 코드에서 public class Point 부분)
class body => 중괄호 안에 클래스의 멤버들을 정한다. (위의 코드에서 중괄호로 감싸진 부분)
※ 클래스의 멤버: field 와 method 를 포함하며, 생성자, 초기자, 중첩 타입 또한 포함될 수 있다
≫ 클래스는 signature 에 대해서..
클래스는 제어자 키워드를 포함해서 선언할 수도 있다. 부가적으로 접근 제어자(public, protected, 등등) 을 포함해서 이 아래의 키워드들도 포함할 수 있다
- abstract: 추상 클래스를 정의할 때 사용한다. 인스턴스화가 될 수 없으며 하나 이상의 추상 메소드를 포함해야 한다.
아마 다음주 과제에서 자세히 알아볼 기회가 있을 것이다 - final: final 로 선언된 클래스는 상속을 하지 않는 클래스 임을 명시한다. abstract 제어자와 동시에 사용할 수 없다
- strictfp: strictfp 로 선언된 클래스는 클래스의 모든 메소드가 정확하게 부동소수점 표준을 따른다
(잘 사용되지 않는다고 한다, 그리고 부동소수점의 표준이 정확히 무슨 말인지 잘 모르겠다)
■ static member 와 non-static member
♣ static member: 클래스 자체에 속하는 멤버이다.
♣ non-static member: 클래스가 아닌 클래스의 인스턴스와 연관된 멤버이다.
또, 위의 분류에 따라 static field, static method, instance field, instance method 로
자세히 분류하는데 아래서 더 자세히 알아보도록 하자
■ Field 와 Method
필드와 메소드의 의미에 대해서 다시 생각해보면
필드: 데이터를 저장하기 위한 역할
메소드: 필드의 값을 바탕으로 연산을 하기 위한 역할
이제 위에서 알아보기로 했듯이, Class Member(static member) 와 Instance Member(nonstatic member) 에 대해서
알아보자
각각의 멤버를 더 세분화해서 분류해보면 총 4가지가 있다:
- Class field
- Class method
- Instance field
- Instance method
필드면 필드고 메소드면 메소드지 앞에 붙는 애들은 무슨 의미를 지니는 것일까.....??
자세한 의미를 알아보기 전에 먼저 각 분류가 코드로 어떻게 표현되는지 알아보자
<예> Circle class
public class Circle {
// class field
public static final double PI = 3.141592;
// class method
public static double radiansToDegrees(double radius) {
return radians * 180 / PI;
}
// instance field
public double r;
// instance method
public double area() {
return PI * r * r;
}
}
≫ Field 선언 문법
필드 제어자 키워드는 변수명 앞에 붙어지며, 하나도 없어도 되고 아래에 명시된 키워드를 하나 이상 붙여도 된다
(※ 아래 명시한 키워드의 순서대로 변수를 선언 또는 정의해야 한다)
- public, protected, private
이 접근 제어자들은 field 가 클래스의 밖에서도 접근 될 수 있는지 아닌지를 명시한다 - static
이 제어자는 필드가 클래스 자체에만 연관되어 있는지를 명시한다 - final
이 제어자는 필드가 한 번 초기화 되고 나면 이후에는 절대 바뀔 일이 없을거라고 장담한다는 것을 명시
static 과 final 은 컴파일 타임 상수이다
(※ 컴파일 타임 상수는 컴파일러 시 코드 모든 곳에서 이 변수의 이름과 값을 그대로 사용한다는 것을 의미.) - transisent
해당 필드가 객체의 영구적인 상태가 아님을 명시하고 객체의 나머지 부분에서 serialized 될 필요가 없다는 것을 명시 - volatile
두 개 이상의 Thread 에서 병렬적으로 사용 되는 필드임을 명시한다
이 필드의 값이 반드시 항상 메인 메모리로 부터 읽히고 메인 메모리고 flush 되어야 함을 의미하고 Thread 에 의해 캐시 되지 않음을 의미한다
♣ Class Field
클래스 필드는 이 필드가 정의된 '클래스와 연관이 있는' 필드이다.
선언은 아래와 같이 하며, 'static' 키워드가 붙는다는 것이 핵심이다 (이 때문에 static field 라 불리기도 한다)
public static final double PI = 3.14159
public static field 는 전역 변수이다. 주의할 점은 클래스 이름.필드명 형태로 접근해야 하므로 이름은 유일할 수 밖에 없다
위에 적어놓은 Circle 클래스를 다시 생각해보면, PI 라는 변수가 클래스 필드로 선언되어 있다.
이 필드를 클래스 내에서 사용하고 싶을 때에는 단순히 PI 를 명시하면 되지만
클래스 밖에서 접근하고 싶을 때에는 Circle.PI 로 하면 된다.
♣ Class Method
클래스 메소드도 클래스 필드와 정의는 비슷하다. 단순히 단어가 필드에서 메소드로 바뀐 것일 뿐.
클래스 메소드를 선언하는 방법도 클래스 필드를 선언하는 것과 크게 다르지 않다
public static double radiansToDegrees(double rads) {
return rads * 180 / PI;
}
클래스 메소드는 클래스 필드와 마찬가지로 객체와 연관되어 있는게 아니라 '클래스 자체' 와 연관 되어 있다
또, 클래스 필드와 마찬가지로 클래스 메소드를 클래스 외부에서 사용하고 싶을 때에는
클래스명.메소드이름 으로 호출하면 된다
double d = Circle.radiansToDegrees(2.0);
클래스 메소드는 해당 클래스에 속하는 클래스 필드와 클래스 메소드를 자유롭게 사용할 수 있지만
인스턴스 필드와 인스턴스 메소드는 사용할 수 없다 (당연히 this 키워드도 사용할 수 없다)
(이유는 위에서도 계속 명시했듯이 클래스 멤버는 클래스와 연관되어 있기 때문에)
♣ Instance Field
static 키워드 없이 선언된 모든 필드를 인스턴스 필드라고 한다
인스턴스 필드는 객체지향 프로그램의 핵심이며 객체의 상태를 저장한다.
그리고 각각의 객체는 서로 독립적이다. (같은 클래스더라도!!!)
public double r;
인스턴스는 클래스의 인스턴스와 연관되어 있어서 객체를 만들 때마다 독립적인 필드를 갖는다
위 예제를 보면 인스턴스 필드 r 은 특정한 원을 표현한다. 그리고 각각의 Circle 객체는 서로 모두 독립적인 r 값을 갖는다.
인스턴스 필드에 접근하기 위해서는 클래스 변수와는 다르게 선언 당시의 변수 이름으로 접근해야 한다.
아래 코드를 보자
Circle c = new Circle(); // Circle 객체를 만들고 그 참조를 변수 c 에 저장한다
c.r = 2.0; // c의 인스턴스 필드 r에 값을 저장한다
Circle d = new Circle(); // Circle 객체를 만들고 그 참조를 변수 d 에 저장한다
d.r = 3.0; // d의 인스턴스 필드 r에 값을 저장한다
위의 클래스 변수를 참조했던 것과 비교해보면 차이가 있는 것을 알 수 있다
♣ Instance Method
인스턴스 메소드는 특정한 클래스의 인스턴스 상에서 작동하고 인스턴스 필드와
마찬가지로 static 키워드 없이 선언된 모든 메소드이다
인스턴스 메소드를 클래스 외부에서 사용하기 위해서는 인스턴스 필드와 마찬가지로
특정 객체.메소드명 으로 접근하면 된다
코드를 보자
Circle c = new Circle();
c.r = 2.0;
c.area(); // 인스턴스 메소드를 실행한다
우리가 호출한 객체 메소드 내부에서는 당연히 호출한 객체에 속하는 모든 인스턴스 필드에 접근할 수 있다
그리고 클래스 메소드와는 다르게 인스턴스 메소드는 클래스 내부에 모든 멤버들에 대해서 접근할 수 있다
모든 인스턴스 메소드는 메소드 signature 에서 보이지 않는 암시적인 파라미터를 사용해서 구현된다
암시적인 인자는 'this' 라고 불리며 메소드가 실행된 객체에 대한 참조를 가지고 있다
그럼 this 는 어떻게 동작하는 걸까.....???
▶ this 참조는 어떻게 동작할까??
자바 메소드가 클래스 내부에 있는 인스턴스 필드에 접근할 때에
this 파라미터에 의해 참조되고 있는 객체의 필드로 접근하는 것을 암시한다
그러므로 this 파라미터는 메소드의 signature 에 나타나지 않는 것이다
그리고 인스턴스 메소드에서 같은 클래스 내부에 다른 인스턴스 메소드를 호출하는 것은
현재 객체에서 그 인스턴스 메소드를 호출하라 는 것을 의미한다
하지만 현재 필드 또는 메소드에 접근한다는 것을 명확하게 하기 위해서
this 키워드를 명시적으로 사용할 수도 있다
public double area() {
return PI * this.r * this.r;
}
하지만 this 키워드를 반드시 명시 해줘야 하는 상황도 있다.
예를 들어 클래스 내부의 필드 변수명이 메소드의 파라미터 이름 또는 메소드 내부의 지역변수의 이름과 같다면
반드시 this 키워드를 사용해서 현재 인스턴스 내부의 필드를 참조하고 있다는 것을 알려줘야 한다
public double setRadius(double r) {
this.r = r;
}
▶ 객체를 만들고 초기화 하는 방법 (생성자, new 연산자)
여태까지 클래스를 정의하는 방법, 클래스 멤버와 인스턴스 멤버, 그리고 this 키워드에 대해 알아봤다
그리고 예제 코드에서 설명하고 넘어가진 않았지만 대충 객체를 어떻게 생성하고
그에 속하는 필드에 어떻게 접근하고 메소드를 사용하는지도 보여왔다.
이제 객체를 어떻게 만들고 초기화하는 지에 대해서 조금 더 자세히 알아보자
우리가 객체를 만들기 위해서는 다음과 같이 코드를 작성할 수 있다
Circle c = new Circle();
위의 코드에서 Circle() 이라는 표현식은 곧 살펴볼 생성자(constructor) 이다
생성자는 클래스의 멤버이며 새로 만들어질 클래스의 새로운 인스턴스의 필드들을 초기화 하는 역할을 한다
new 연산자는 클래스의 새로운 인스턴스를 만드는 것이 필요하다는 것을 나타낸다.
☞ 이제 생성자가 어떻게 동작하는지 보자
- 새로운 객체 인스턴스를 갖기 위한 메모리가 할당된다
- 그러고 나면 생성자 몸체가 명시된 인자들과 함께 호출 된다. 생성자는 인자들을 가지고
새로운 객체에 대해 필요한 초기화 작업을 진행한다
자바 내에 모든 클래스는 적어도 하나의 생성자를 가진다.
생성자의 목적은 새로운 객체를 위해 필요한 초기화 작업을 실행하는 것이다
※ 생성자를 명시해주지 않으면...?
걱정할 필요없다. javac 컴파일러가 자동적으로 기본 생성자(인자도 없고, 몸통도 없는) 를 생성해주기 때문이다
위의 예제에서 사용했던 Circle 클래스도 자동적으로 생성자가 선언된 것이다.
♣ 생성자를 정의하기
(위의 Circle 예제에 덧붙여서) 새로운 객체를 인스턴스화 할 때마다, r 의 값이 세팅 되길 원한다고 가정하고
그에 맞는 초기화를 진행하기 위한 생성자를 정의해보자
public class Circle {
public static final double PI = 3.141592;
protected double r;
// field r 을 초기화 한다
public Circle(double r) {
this.r = r;
}
public double circumference() {
return 2 * PI * r;
}
public double area() {
return PI * r * r;
}
public radius() {
return r;
}
}
이제 우리는 새로운 생성자로 객체를 생성할 때 객체에 대한 초기화를 진행할 수 있다
Circle c = new Circle(2.5);
☞ 생성자를 작성하고 선언할 때에 기본적인 사실들:
- 생성자의 이름은 항상 클래스의 이름과 같다
- 생성자는 리턴 타입 없이 선언된다 (명시하지 마라)
- 생성자의 몸통부분은 객체를 초기화하기 위한 코드이다. this 참조에 대한 내용을 세팅하는 것이라고 생각해도 된다
- 생성자는 'this' 를 비롯해 아무 것도 반환하지 않는다
생성자는 여러 개 정의해서 사용할 수 있다
♠ 생성자 여러 개 정의하기
예를 들어 어떠한 상황에 따라 여러 방법으로 새로운 객체를 초기화 하길 원한다고 하면
우리는 여러 개의 생성자를 정의하는 방법으로 이를 해결할 수 있다
// 기본 생성자
public Circle() {
r = 1.0;
}
// 변수를 하나 받는 생성자
public Circle(double r) {
this.r = r;
}
여러 개의 생성자를 정의하는 것은 당연히 되는 일이고, 각 생성자는 서로 다른 매개변수 리스트를 갖는다
컴파일러는 우리가 객체를 생성할 때 생성자에 어떤 타입의 인자를 몇 개 넘겼는지를 보고 어떤 생성자를 사용할 지 결정한다
그러므로 클래스에 필드가 얼마나 있고, 작성자들의 필요에 따라서 생성자를 여러 개 정의해서 써도 된다
일단 여기까지 하면 과제에서 나온 내용을 모두 수행한 것 같다.
하지만 보고 있는 책에서는 유용한 정보가 있어서(적어도 나한테는??) 더 적어볼까 한다.
▶ 하나의 생성자로 부터 다른 생성자를 실행하는 방법
클래스가 여러 생성자들을 가지고 있을 때 this 키워드를 특별하게 사용할 수 있다
하나의 생성자로 부터 다른 생성자를 호출하는 방법이다
public Circle(double r) {
this.r = r;
}
public Circle() {
this(2.0);
}
이러한 테크닉은 다수의 생성자가 초기화에 필요한 코드를 많이 공유할 때 반복되는 코드를 피하기 위해 유용하다
※ this() 사용에 대한 제한: 생성자 내에서 반드시 첫번째 statement 에 등장해야 한다
▶ 필드 초기화에 대해서
자바에서 필드의 초기화는 어떻게 이루어질까??
먼저 인스턴스 변수와 클래스 변수는 초기화 하는 방식이 약간 다르다
먼저 인스턴스 변수의 초기화에 대해 알아보고, 그 다음 클래스 변수 초기화에 대해 알아보자
● 인스턴스 필드 초기화
인스턴스 필드에 대한 초기화는 생성자 내부에서도 일어나지만,
아래 코드 처럼선언과 동시에 이루어질 수도 있다
class Example {
// 선언과 동시에 초기화
private int length = 10;
private int[] numbers = new int[length];
public Example() {
for(int i = 0; i < length; i++) {
numbers[i] = i;
}
}
}
javac 컴파일러는 위의 생성자에 대한 코드를 정확히 아래와 같이 생성한다
public Example() {
length = 10;
numbers = new int[length];
for(int i = 0; i < length; i++) {
numbers[i] = i;
}
}
※ 생성자가 다른 생성자를 호출하기 위해서 this() 메소드로 시작하면 필드에 대한 초기화 코드는 첫번째 생성자에 나타나지 않는다
대신에 초기화는 this() 에 의해서 호출 된 생성자에 의해서 이루어진다
이제 클래스 필드 초기화에 대해서 알아보자
● 클래스 필드 초기화
클래스 필드는 클래스와 연관이 되어있다. 그러므로 생성자에서 클래스 필드를 초기화 할 수 없다. (왜냐면 인스턴스 필드가 객체와 연관이 되어있으니까)
클래스 필드는 클래스의 인스턴스가 생성되기 전에 즉, 인스턴스가 만들어지기 전에 만들어지고 초기화 되어야한다
클래스가 생성되기 전에 초기화를 하기 위해서 javac 는 모든 클래스에 대한 클래스 초기화 메소드를 자동으로 생성한다
클래스 필드는 이 메소드 내에서 초기화 되고 클래스가 쓰이기 전에 딱 한 번만 실행된다
우리가 클래스 필드를 초기화 하기 위한 메소드를 직접 정의할 수는 없지만(생성자 처럼), static initializer 를 통해서
초기화를 진행할 수 있다
public class TrigCircle {
private static final int = NUMPTS = 500;
private static double sines[] = new double[NUMPTS];
private static double cosines[] new double[NUMPTS];
// static 블록에 expression 을 명시해서 sines, cosines
// 클래스 필드를 초기화 할 수 있다
static {
double x = 0.0;
double delta_x = (Circle.PI/2) / (NUMPTS-1);
for (int i = 0, x = 0.0 i < NUMPTS; i++, x += delta_x) {
sines[i] = Math.sin(x);
cosines[i] = Math.cos(x);
}
}
}
※ static initializer 는 클래스 메소드와 마찬가지로 this 키워드와 인스턴스 필드/메소드에 접근할 수 없다
∴ 느낀점
- 예전 JAVA 공부를 배웠을 때에 클래스 생성, 사용방법 등은 알고 있었다
- 하지만 이번 과제를 진행하면서 명확하지 않았던 개념들에 대해 알게 되었다
- 예를 들어, 객체를 인스턴스화 할 때(new) 에 자바의 내부 동작이라든지 클래스 필드/메소드와 인스턴스 필드/메소드의 차이라든지
- this 를 사용할 때 아무 생각 없이 썼었던 것 같은데, 그거에 대해서도 명확해진 것 같은 느낌이다
[참조 내용]
Java in a Nutshell, 7th Edition by Ben Evans, David Flanagan (www.oreilly.com/library/view/java-in-a/9781492037248/)
'공부 > whiteship-java' 카테고리의 다른 글
7주차: 패키지 (0) | 2021.01.01 |
---|---|
6주차: 상속 (0) | 2020.12.24 |
4주차 과제: live-study 대시보드 (0) | 2020.12.12 |
4주차 과제: JUnit5 (0) | 2020.12.11 |
4주차: 조건문, 반복문 (0) | 2020.12.10 |