TIL 순서가 밀리기도 했고.. Java 관련 설명이 대부분이라 Java 카테고리에 작성하였습니다.
해당 게시글의 내용은 2022년 05월 10~11일에 공부한 내용입니다.
두서없이 편하게 정리하였음을 참고해 주세요.
작성된 내용 중 틀린 내용이 있다면 알려주세요!
OOP의 맨 첫 글자인 Object, 즉 객체를 만들기 위해서 Java에서는 어떤 개념들을 사용하는가?
- 클래스(class)
- 객체(object)
- 인스턴스(instance)
- 인스턴스화(instanciate)
위 개념들을 사용하여 OOP를 구현하고 있으며, Java에서 클래스와 인스턴스를 어떻게 사용하는지 알아보자
(각 개념들의 내용은 이전에 작성했었던 게시글인 OOP - 객체 지향 프로그래밍을 참고하여 주세요.)
추가로 static과 java의 변수 그리고 클래스의 구성 요소인 필드, 메서드, 생성자에 대해서도 알아보자
클래스(class)
Java에서 클래스를 구현할 때 class 키워드를 사용하여 구현하게 되고, 클래스명의 시작은 대문자로 시작하는 관습이 존재한다. 여기서 정확한 클래스명 정의 관습은 upper camel case 방식이며, 해당 방식에 따라 정의하면 된다.
클래스는 4가지 요소로 구성이 되게 된다.
- 필드(field) : 클래스의 속성을 나타내는 변수. 멤버 변수, 객체 변수라고도 한다.
- 메서드(method) : 클래스의 기능을 나타내는 함수
- 생성자(constructor) : 클래스의 인스턴스 생성 시 자동 호출되는 초기화 메서드
- 이너 클래스(inner class) : 클래스 내부에서 정의된 클래스
여기서 3번 항목인 생성자를 제외한 나머지 3가지 요소를 클래스의 멤버라고 부르며, 이 중 필드와 메서드는 클래스가 갖는 속성(state)와 기능(behaivor)을 대표한다.
그럼 이어서 코드로는 어떤 식으로 구현하는지 알아보겠습니다.
class 클래스명 {
field
method() { ... }
}
위 코드 형태로 구현을 하게 되며, 상황에 따라 필드, 메서드, 이너 클래스는 각각 생략할 수 있다.
위 코드에선 이너 클래스를 생략하였으며, 앞으로의 설명에서도 이너 클래스는 생략하고서 진행하겠다.
또한 생성자도 생략이 되어있으며, 생성자는 아래 생성자 설명에서 자세히 다루었습니다.
클래스를 구현할 때는 구현하고자 하는 프로그램에 필요한 사물의 속성과 동작들을 뽑아내서 정의하면 된다. (추상화)
여기서 사물의 모든 속성과 동작을 뽑아내는 것이 아니라 프로그램에 필요한 핵심 속성 및 동작만을 정의하면 된다.
예를 들어 자동차 정보를 담는 클래스를 간단하게 만든다고 한다면, (자동차 정보를 담는 클래스를 추상화한다면)
필드(속성)
- 모델명
- 바퀴 개수
- 문 개수
- 차량 색상
...
메서드(동작)
- 시동 on/off
- 가속
- 감속
...
이런 식으로 정의한 후 해당 내용들을 코드로 구현한다면 그것이 클래스가 된다.
위에 정리한 내용을 클래스로 구현해 보았다.
class Car {
private String model; // 차 모델명
private int wheels; // 바퀴 개수
private int doors; // 차문 개수
private String color; // 차량 색상
void power() {...}; // 시동 on/off
void accelerate() {...}; // 가속
void stop() {...}; // 정지
}
인스턴스(instance)
위에서 클래스를 만들어 보았으니 이제 해당 클래스를 가지고 인스턴스를 만드는 방법을 알아보자
인스턴스는 new 키워드를 통해 만들 수 있으며, 클래스명을 알고 있어야 한다.
클래스명 참조_변수명 = new 클래스명();
이러한 형태로 인스턴스를 생성하여 사용할 수 있으며, 인스턴스 변수나 메서드 등을 참조하는 방법은 .(dot)을 사용한다.
참조_변수.인스턴스_변수 = 대입값; // 인스턴스 변수에 값 대입
참조_변수.메서드(); // 메서드 실행
어떠한 형태로 인스턴스를 생성하고 사용하는지 알았으니 예제 코드로 자세히 알아보도록 하자
// 위에서 만들었던 class Car가 정의되어 있다는 가정
class Main {
public static void main(String[] args) {
Car old_car = new Car(); // old_car 참조 변수에 Car 클래스로 만든 인스턴스 대입
car.model = "오래된 차"; // model에 "오래된 차" 대입
car.wheels = 4; // wheels에 4 대입
car.doors = 2; // doors에 2 대입
car.color = "흰색"; // color에 "흰색" 대입
car.accelerate(); // accelerate 메서드 실행
car.stop(); // stop 메서드 실행
}
}
여기서 조금 더 자세히 설명을 하자면, 일반 변수가 아닌 "참조 변수"라는 점에 주목을 해봐야 한다.
new 키워드는 클래스를 저장할 메모리의 크기만큼 힙 메모리에서 할당받은 후 메모리의 주소를 반환한다.
반환받은 주소를 참조 변수에 대입하여 사용하게 되는 것이다. 즉, 참조 변수란 C언어의 포인터 변수라고 볼 수 있다.
결론적으로 참조 변수는 일반 변수처럼 정보를 저장하고 있는 게 아니고 정보가 저장되어 있는 메모리의 주솟값을 갖고서 참조하는 변수라는 의미이다.
추가로 인스턴스 생성 시 저장되는 메모리 공간은 필드 정보에 따라서 달라지게 되는데
메서드의 경우 메서드 메모리 공간에 저장되어 동일 클래스의 인스턴스들이 공유하여 참조하여 사용하게 되고
인스턴스 변수의 경우 각각의 인스턴스가 생성될 때 할당받는 각자의 힙 메모리 공간에 저장되어 인스턴스 별로 사용하게 된다.
즉, 메서드는 공유하여 사용, 인스턴스 변수는 인스턴스별 각각의 값을 갖고서 사용하게 된다.
필드(field)
위에서 메모리를 언급하며 설명한 내용 중 등장한 인스턴스 변수라는 단어가 있다. 처음보는 변수인데 이건 무어냐?
이는 Java의 변수 분류에서 확인이 가능한데, 이는 간단한 예제 코드를 통해서 알아보도록 하자
class Example { // 클래스
int iv; // 인스턴스 변수
static int cv; // 클래스 변수(static 변수, 정적 변수, 공유 변수)
void main() { // 메서드
int lv = 0; // 지역 변수, 블록{} 안에서만 유효, 메서드가 종료됨과 동시에 참조 불가
}
}
예제 코드의 주석을 보면 알 수 있듯이 class 내부에서 일반적으로 사용하게 되는 변수가 "인스턴스 변수"라고 불리며,
이러한 인스턴스 변수에 "static"이라는 키워드를 사용하면 "클래스 변수(정적 변수)"라는 이름으로 바뀜을 확인 할 수 있다.
또한 메서드 내부에서 사용되는 변수는 "지역 변수"라는 또다른 이름으로 불리는 것을 알 수 있다.
왜 다 같은 변수인데 부르는 이름이 달라지는가? 당연하게도 조금씩 기능이 달라지기 때문이다.
각각의 개념을 설명한다면
- 인스턴스 변수(instance variable) : 인스턴스를 생성했을 때, 각각의 인스턴스가 각자 값을 갖는 변수
- 클래스 변수(class variable) : 위에서 설명했던 메서드처럼 동일 클래스의 인스턴스들이 공유하여 사용하는 변수 값
- 지역 변수(local variable) : 메서드 내에서 사용되는 변수, 메서드가 종료됨과 동시에 메모리에서 제거되어 참조 불가
이처럼 설명할 수 있다.
아래는 조금 더 자세히 설명을 작성해 뒀으나 위 내용으로 대강 이해가 되셨다면 넘어가셔도 무방합니다.
인스턴스 변수(iv; instance variable)는 위에서 설명했듯이 힙 메모리 공간에 저장되어 사용되고,
클래스 변수(cv; class variable)는 설명에서 유추할 수 있듯이 메서드와 동일하게 메서드 메모리 영역에 저장되어 사용된다.
마지막으로 지역 변수(lv; local variable)는 스택 메모리 공간에 저장되어 사용되며 메서드 종료와 함께 스택 메모리에서 pop되어 제거진다.
힙 메모리와 메서드 메모리는 빈 공간이 저장될 수 없기 때문에 직접 초기화를 하지 않더라도 자동으로 초기화가 된다.
하지만 지역 변수가 저장되는 스택 메모리 공간은 자동으로 초기화가 안되기 때문에 직접 초기화를 해줘야 한다. 그렇지 않으면 쓰레기 값(garbage value)이 들어가 있으므로 그대로 사용시 문제가 발생할 수 있다.
추가로, 지역 변수는 제거가 되는데 인스턴스 변수와 클래스 변수는 메모리 영역에서 제거가 안되는 건가? 싶을 수 있다.
인스턴스 변수와 클래스 변수는 생성된 인스턴스들이 더 이상 해당 변수들을 참조하지 않을 때 GC(garbage collector)를 통해 제거된다.
여기서 GC는 Java에서 사용되는 기능 중 하나이며 Java로 구현된 프로그램의 메모리를 관리하는 기능이다.
GC에 대한 자세한 내용은 여기서 다루지 않습니다. GC가 궁금하시다면 구글에 검색해보시면 많은 정보가 나오며, GC를 깊게 공부하고 한다고 하신다면 GC는 상당히 깊이가 있으며 추가로 메모리에 대해 공부가 필요해짐을 알려드립니다.
static 키워드
클래스의 멤버인 필드, 메서드, 이너 클래스에 사용 할 수 있는 키워드이며, static이 붙은 멤버를 "정적 멤버"라고 부른다.
정적 멤버는 인스턴스를 생성하지 않아도 호출하여 사용 할 수 있다는 점이 포인트
인스턴스를 생성하여 참조 변수를 통한 사용도 가능하지만 해당 멤버가 정적 멤버임을 표현하기 위해 "클래스명.정적멤버명"의 형태로 사용하는 것을 권고
정적 메서드를 구현할 때 인스턴스 변수 또는 인스턴스 메서드를 사용할 수 없다.
이유는 당연하다면 당연하겠지만.. 바로 위에서 설명했듯이 인스턴스를 생성하지 않아도 호출하여 사용이 가능하기 때문
즉, 인스턴스 변수와 인스턴스 메서드는 인스턴스가 생성될 때 만들어지게 되므로 인스턴스를 생성하지 않은 상태에서 정적 메서드를 호출하면 동작이 불가능한 상태가 되기 때문이다.
메서드(method)
메서드는 "특정 작업을 수행하는 일련의 명령문들의 집합"을 의미한다. 간단하게 C언어 기준 함수이다.
메서드는 메서드 시그니처(method signature)와 메서드 바디(method body)로 구분할 수 있다.
예제 코드를 보면서 메서드 구현 방법 및 시그니처, 바디의 정의를 알아보자
자바_제어자 반환_타입 메서드명 (매개 변수) {
메서드 내용
}
public int add(int x, int y) { // 메서드 시그니처(method signature)
return x + y; // 메서드 바디(method body)
}
위 예제 코드의 주석을 보면 메서드 구현 방법과 메서드 시그니처, 바디가 어느 부분을 의미하는지 알 수 있다.
메서드 구현 방법은 맨 처음 Java의 제어자를 작성한 후 해당 메서드의 반환 타입을 작성, 이후 해당 메서드명을 작성한다. 메서드명 다음에는 괄호를 열고 필요한 경우 매개 변수의 타입과 이름을 작성하고 괄호를 닫는다. 이때 매개 변수가 필요 없으면 그냥 괄호만 열고 닫으면 된다.
그리고 중괄호를 열고 해당 메서드의 동작을 작성하는데 반환 타입이 있는 메서드의 경우 return 문으로 끝나야 한다. 그리고 마지막에 중괄호를 꼭 닫아주어야 한다.
여기서 자바 제어자, 반환 타입, 메서드명, 매개변수를 작성하는 부분을 메서드 시그니처라고 하고,
해당 메서드 호출 시 특정 작업을 수행하도록 일련의 명령문들을 작성해 두는 부분을 메서드 바디라고 한다.
메서드 구현 시 메서드명은 관습적으로 소문자로 시작하며, 중간에 단어가 바뀌는 경우 해당 단어의 첫 글자만 대문자로 작성한다. 이를 "소문자 카멜 표기법(lowerCamelCase)"이라고 한다. 또한 메서드명은 동사/전치사로 시작하는 관습도 존재한다.
이러한 관습에 대한 자세한 사항은 "Java naming convention"을 키워드로 검색하면 알 수 있다.
메서드 오버로딩(method overloading)
메서드 구현 시 메서드명은 동일하지만 매개변수를 다르게 정의함으로써 메서드명 중복을 허용하는 개념
위 설명대로 메서드 오버로딩을 구현하는 방법은
- 동일한 메서드명으로 구현
- 매개변수를 다르게 구현
위 조건을 지켰을 때 가능하며, 하나의 클래스 내에서 동일한 메서드명을 사용하는데 매개변수까지 동일하다면 메서드 중복 정의로 간주되어 컴파일 에러가 발생한다.
메서드 오버로딩을 사용하여 얻는 장점으로는 하나의 메서드명으로 여러 경우의 수에 대한 처리가 가능하다는 점이다.
예를 들어 "add(x, y) return x + y;"라는 덧셈을 하는 간단한 메서드를 구현한다고 해보자.
메서드 오버로딩을 지원하는 경우 예시 코드처럼 구현 후 타입에 상관 없이 add() 메서드만 사용하면 덧셈 결과를 알 수 있게 된다.
int add(int x, int y) { // int형 add() 메서드
return x + y
}
float add(float x, float y) { // float형 add() 메서드
return x + y
}
double add(double x, double y) { // double형 add() 메서드
return x + y
}
long add(long x, long y) { // long형 add() 메서드
return x + y
}
하지만 메서드 오버로딩을 지원하지 않는다면 아래와 같은 예시 코드처럼 메서드명을 각 타입별로 나누어 구현을 한 후 타입에 따른 메서드를 호출해야 할 것이다.
int addInt(int x, int y) { // int형 add() 메서드
return x + y
}
float addFloat(float x, float y) { // float형 add() 메서드
return x + y
}
double addDouble(double x, double y) { // double형 add() 메서드
return x + y
}
long addLong(long x, long y) { // long형 add() 메서드
return x + y
}
생성자(constructor)
인스턴스를 생성할 때, 메모리를 항당 받은 후 자동으로 호출되는 메서드
인스턴스의 초기화에 사용
클래스에는 최소 1개의 생성자는 꼭 정의가 되어있어야 한다.
여기서 "어? 지금까지 생성자가 뭔지 몰라서 정의한 적 없는데도 잘 동작 하던데요?"라는 의문이 생길 수 있다.
생성자를 정의하지 않았다면 컴파일 시 컴파일러가 자동으로 기본 생성자를 만들어서 넣어주기 때문에 문제없이 동작할 수 있었던 것이다.
여기서 생성자의 정의를 다시 보면 "인스턴스의 초기화에 사용"이라고 되어있다.
인스턴스의 초기화에 사용이 된다고 해서 생성자는 꼭 필드값의 초기값만 설정이 가능하다고 생각하면 안된다.
첫 줄에 분명 메서드라고 표현을 했다. 즉, 생성자도 메서드와 동일하게 콘솔에 출력을 한다던가의 동작도 가능하다.
그리고 메서드이기 때문에 생성자 또한 오버로딩이 가능하다.
하지만 생성자와 메서드의 차이점이 존재하는데, 이는 생성자의 특성으로 인해 발생하는 차이점이다.
생성자는
- 반환 타입 자체가 존재하지 않는다.
- 생성자는 해당 클래스명과 동일해야 한다.
라는 특성을 갖고 있다.
따라서 생성자는 메서드와 동일하게 구현을 하지만 생성자의 특성에 따라 반환 타입을 제외, 생성자명은 클래스명으로 고정하여 구현하게 된다.
글만 봐서는 이해가 잘 안될 수 있으니 쉽게 예제 코드를 보면서 이해를 해보자
class 클래스명 {
클래스명() {
생성자 동작부 정의
}
}
----
class TestClass {
int a; // 인스턴스 변수 a 선언
int testMethod() { // 인스턴스 메서드 testMetho() 선언
return 10;
}
TestClass(int a) { // 생성자 선언, int a라는 매개변수를 받아온다.
this.a = a; // 인스턴스 변수 a에 매개변수로 받은 a값을 대입한다.
}
}
class Main {
public static void main(String[] args) {
// TestClass 인스턴스를 생성하면서 10을 매개 변수로 할당
// 생성자를 호출하여 인스턴스 a 변수에 10을 대입하여 생성
// 이후 메모리 주소를 testClass 참조 변수에 대입
TestClass testClass = new TestClass(10);
System.out.println(testClass.a); // testClass 인스턴스의 a값을 출력 : 10 출력
}
}
'Develop > Java' 카테고리의 다른 글
Java - File read 2가지 방법 (0) | 2022.11.29 |
---|---|
JPA Fetch Type (1) | 2022.09.09 |
Collection framwork (0) | 2022.07.26 |
제네릭(Generic)이란? (0) | 2022.07.25 |
Java 클래스 검색법 (0) | 2022.07.25 |