[스터디 할래] 5주차 - 클래스
본 글은 백기선님의 라이브 스터디 과제를 블로그에 옮기며 다시 작성한 글입니다. 과제 링크
1. 클래스와 객체
클래스는 객체지향 프로그래밍(object-oriented programming)에서 객체를 생성하기 위해 속성(field)와 행위(behavior)을 정의하는 일종의 설계도이다.
객체는 하나의 애플리케이션을 구현하기 위해 서로 협력하는 개별적인 구성품이다.
정리하면 객체들를 만들기 위한 설계도를 클래스라고 하고 그 클래스를 기반으로 만든 것들을 객체라고 한다.
축구선수를 예시로 들어보자.
class FootballPlayer{
// 필드(field) : 모든 축구선수들이 가지는 속성
String name; // 선수 이름
int speed; // 속력
int acceleration ; // 가속력
// 메서드(method) : 모든 축구선수들이 가능한 행위
void run() {
System.out.println(name + "가 뛴다.");
}
void shoot(){
System.out.println(name + "가 슈팅을 한다.");
}
void tackle() {
System.out.println(name + "가 태클을 한다.");
}
// 생성자(constructor) : 객체 생성, 인스턴스 변수 초기화
FootballPlayer() {}
FootballPlayer(String name, int speed, int acceleration) {
this.name = name;
this.speed = speed;
this.acceleration = acceleration;
}
}
설계도라고 할 수 있는 클래스(축구선수)는 인물들의 상위개념이다. 메시, 호날두, 호나우두 등 여러 인물들은 축구선수라는 추상적인 개념의 하위개념에 속한다.
따라서 축구선수라는 추상적인 클래스로 메시, 호날두 등의 객체(인스턴스)들를 만들어 낼 수 있다.
여기서 주목할 점은 클래스(축구선수)들의 속성(field)와 행위(behavior)이다.
- 모든 축구선수들은 이름, 속력, 가속력이라는 속성(field)을 공통적으로 가지고 있다.
- 또한 뛰기, 슈팅하기, 태클하기라는 행위(behavior)가 가능한 것도 동일하다.
- 이와 같은 속성(field)와 행위(behavior)을 클래스에서는 필드(field)와 메서드(method)라고 부른다.
2. 클래스의 구성 맴버
필드(field)
필드는 해당 클래스의 속성을 나타내며, 멤버 변수라고도 불린다.
1) 필드의 종류
다시 축구선수로 예를 들어보자.
class FootballPlayer{
static String countryName; // 클래스 변수(국가 이름)
String name; // 인스턴스 변수(선수 이름)
void shoot(){
String ball = "ChampionsLeagueLBall"; // 지역 변수
}
}
- 인스턴스 변수
- 이름에서 알 수 있듯이 객체(인스턴스)가 갖는 변수이다. 그렇기에 인스턴스를 생성할 때 만들어진다.
- 서로 독립적인 값을 갖으므로 heap 영역에 할당되고 gc에 의해 관리된다.
- 클래스 변수
- 정적을 의미하는 static 키워드가 인스턴스 변수 앞에 붙으면 클래스 변수이다.
- 해당 클래스에서 파생된 모든 인스턴스는 이 변수를 공유한다.
- 그렇기 때문에 heap 영역이 아닌 static 영역에 할당되고 gc의 관리를 받지 않는다.
- 또한 public 키워드까지 앞에 붙이면 전역 변수라 볼 수 있다.
- 지역 변수
- 특정 메서드 내에서만 사용되는 변수이다.
- 변수가 선언되는 시점부터 블럭이 종료되는 시점까지만 stack 영역에 존재한다.
- 위 예시의 ball은 지역 변수이기 때문에 shoot 메서드 내에서만 존재한다.
2) 필드 초기화
필드를 초기화하는 방법은 여러가지가 있다.
- 필드 선언과 동시에 초기화
- 필드를 선언하며 동시에 초기값을 선언하는 방식이다.
class FootballPlayer{ String countryName = "England"; // 필드를 선언과 동시에 초기화 }
- 필드를 선언하며 동시에 초기값을 선언하는 방식이다.
- 생성자를 통한 초기화
- 필드에는 선언만 하고 생성자를 작성할 때 초기화 해주는 방식이다.
class FootballPlayer{ String countryName; FootballPlayer(String countryName){ this.countryName = countryName; // 생성자의 매개변수를 통해 필드를 초기화 } }
- 필드에는 선언만 하고 생성자를 작성할 때 초기화 해주는 방식이다.
- 초기화 블럭을 통한 초기화
- 인스턴스 변수를 초기화 하는 방식이다.
- 초기화 블럭은 자바 컴파일러를 통해 모든 생성자의 맨 앞에 복사된다. 따라서 여러 생성자안에 공유하고자 하는 코드를 초기화 블럭으로 분리할 수 있다.
- 생성자의 맨 앞에 복사되기 때문에, 동일한 변수값을 초기화 블럭과 생성자에서 초기화 한다면 변수값은 생성자에 의해 결정되는 것을 주의해야 한다
public class Init { int num; { System.out.println("인스턴스 초기화 블록"); num = 10; } public Init(int num1) { System.out.println("생성자 블록"); this.num = num; } public static void main(String[] args) { Init init = new Init(100); System.out.println("num : " + init.num); } }
- 결과화면
클래스 초기화 블록 생성자 블록 num : 100 // 변수값은 생성자에 의해 결정된다
- static 초기화 블럭을 통한 초기화
- 클래스 변수(static이 붙은 필드)를 초기화 하는 방법이다.
- 그 외에는 일반 초기화 블럭과 동일하다.
메서드
메서드는 해당 객체의 행동을 나타내며, 보통 필드의 값을 조정하는데 쓰인다.
1. 메서드의 종류
다시 축구선수로 예를 들어보자.
class FootballPlayer{
static String countryName; // 클래스 변수(국가이름)
String name; // 인스턴스 변수(선수이름)
static void changeCountryName(String newCountryName){ // 클래스 메서드(클래스 변수만 사용 가능)
countryName = newCountryName;
}
void shoot(){ // 인스턴스 메서드(클래스, 인스턴스 변수 둘 다 사용 가능)
String ball = "ChampionsLeagueLBall"; // 지역 변수
}
}
- 인스턴스 메서드
- 인스턴스 변수와 연관된 작업을 하는 메서드이다.
- 인스턴스를 통해 호출할 수 있으므로 반드시 먼저 인스턴스를 생성해야 한다.
- 또한 해당 시점은 클래스가 메모리에 올라간 이후이기 때문에 클래스 변수를 사용할 수 있다.
- 클래스 메서드
- 정적 메서드라고도 한다.
- 일반적으로 인스턴스와 관계없는 메서드를 클래스 메서드로 정의한다.
- 클래스 메서드는 클래스가 메모리에 올라간 시점부터 사용가능하다.
- 해당 시점은 인스턴스가 생성되기 전이므로, 인스턴스 변수를 사용할 수 없다.
2. 메서드 오버로딩
- 자바에서는 메서드를 메서드 시그니쳐를 통해 구별할 수 있기 때문에, 메서드 오버로딩을 사용할 수 있다.
- 따라서 같은 이름이 같은 메서드여도 매개변수의 타입이나 갯수가 다르면 서로 다른 메서드로 인식해 선언이 가능하다.
- 주의할 점은 반환 타입은 메서드 시그니처가 아니기 때문에 반환 타입만 다른 경우는 선언이 불가능하다.
메소드 시그니처란?
메소드를 구분지을 수 있는 근거가 되는 것을 메소드 시그니처라고 한다.
자바에서 메서드 시그니처는 메서드명, 인자 타입, 인자 갯수이다.
생성자
생성자는 객체가 생성된 직후에 클래스의 객체를 초기화하는 데 사용되는 코드 블록이다. 메서드와 달리 리턴 타입이 없으며, 클래스엔 최소 한 개 이상의 생성자가 존재한다.
class FootballPlayer{
String name;
FootballPlayer() {} // 디폴트 생성자(묵시적 생성자)
FootballPlayer(String name) {
this.name = name;
}
}
- 생성자는 클래스 이름과 같아야 한다.
- 반환값이 없지만 반환타입을 void로 선언하지 않는다.
- 아래 예제와 같이 다양한 FootballPlayer 클래스의 생성자를 만들어 생성자 오버로딩이 가능하다.
- 클래스 내부에 생성자를 선언하지 않으면 컴파일러가 디폴트 생성자를 선언해 사용한다.
- 명시적 생성자만 선언되있는 경우 파라미터가 없는 생성자를 사용하고 싶다면 묵시적 생성자를 선언해주어야한다.
- 명시적 생성자 : 매개 변수가 있는 생성자
- 묵시적 생성자 : 매개 변수가 없는 생성자
접근제어자
접근 제어자는 해당 클래스 또는 필드를 정해진 범위에서만 접근할 수 있도록 통제하는 역할을 한다. 클래스는 public과 default밖에 쓸 수 없다. 범위는 다음과 같다. 참고로 default는 아무것도 덧붙이지 않았을 때를 의미한다.
접근 제어자 | 클래스 내부 | 동일 패키지 | 하위 클래스 | 그 외 영역 |
public | o | o | o | o |
protected | o | o | o | x |
private | o | o | x | x |
default | o | x | x | x |
- static - 변수, 메서드는 객체가 아닌 클래스에 속한다.
- final
- 클래스 앞에 붙으면 해당 클래스는 상속될 수 없다.
- 변수 또는 메서드 앞에 붙으면 수정되거나 오버라이딩 될 수 없다.
- abstract
- 클래스 앞에 붙으면 추상 클래스가 되어 객체 생성이 불가하고, 접근을 위해선 상속받아야 한다.
- 변수 앞에 지정할 수 없다.
- 자세한 내용은 상속을 공부할 때 알아보겠다.
- transient - 변수 또는 메서드가 포함된 객체를 직렬화할 때 해당 내용은 무시된다.
- synchronized - 메서드는 한 번에 하나의 쓰레드에 의해서만 접근 가능하다.
- volatile - 해당 변수의 조작에 CPU 캐시가 쓰이지 않고 항상 메인 메모리로부터 읽힌다.
3. 객체 만드는 방법과 생성 과정
객체 만드는 방법
- 클래스에서 객체를 생성하려면 아래와 같이 new키워드를 생성자 중 하나와 함께 사용하면 된다.
- new키워드는 새 객체에 메모리를 할당하고 해당 메모리에 대한 참조값을 반환하여 클래스를 인스턴스화한다.
- 일반적으로 객체가 메모리에 할당되면 인스턴스라 부른다.
FootballPlayer footballPlayer1 = new FootballPlayer("Messi");
FootballPlayer footballPlayer2 = new FootballPlayer("Ronaldo");
인스턴스 생성 과정 분석
FootballPlayer 라는 클래스의 인스턴스를 생성하는 간단한 코드를 작성해봤다.
예제 코드
class FootballPlayer {
String name;
int speed;
int acceleration ;
FootballPlayer(String name, int speed, int acceleration) {
this.name = name;
this.speed = speed;
this.acceleration = acceleration;
}
}
public static void main(String[] args) {
FootballPlayer ronaldo = new FootballPlayer("ronaldo", 100, 105);
}
바이트 코드
바이트 코드로 인스턴스의 생성과정을 살펴보았다.
public class test/Test {
// compiled from: Test.java
// access flags 0x1
public <init>()V
L0
LINENUMBER 3 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this Ltest/Test; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x9
public static main([Ljava/lang/String;)V
L0
LINENUMBER 5 L0
NEW test/FootballPlayer
DUP
LDC "ronaldo"
BIPUSH 100
BIPUSH 105
INVOKESPECIAL test/FootballPlayer.<init> (Ljava/lang/String;II)V
ASTORE 1
L1
LINENUMBER 6 L1
RETURN
L2
LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
LOCALVARIABLE ronaldo Ltest/FootballPlayer; L1 L2 1
MAXSTACK = 5
MAXLOCALS = 2
}
참고
Local Variables Array : 로컬 변수 배열은 메소드의 지역 변수들을 갖는 공간이다.
Operand Stack : 오퍼랜드 스택은 메소드 내 계산을 위한 작업 공간이다.
Constant Pool : 상수 풀은 Integer, String 같은 레퍼런스 타입 의 데이터 값 등을 저장하는 메모리 공간이다.
오류 : 이미지의 LVA 내부의 지역 변수는 FootballPlayer가 아닌 ronaldo이다
1) NEW test/FootballPlayer
- FootballPlayer 클래스의 인스턴스가 heap 영역에 생성
- heap으로의 참조값(@100)이 오퍼랜드 스택(Operand Stack)에 push된다.
2) DUP
앞에서 생성한 인스턴스의 참조자를 복사(duplicate)
- 객체를 생성한 뒤 생성자를 호출하기 전에 dup 명령어를 사용해 참조값(@100)을 오퍼랜드 스택에 복사한다.
- 그 이유는 생성자를 호출해 heap 영역의 인스턴스 변수들을 초기화할 때 참조자가 스택에서 제거되기 때문이다.
3) LDC "ronaldo"
리터럴형태로 “ronaldo” 문자열 생성
- 런타임 상수풀(Runtime Constant Pool)에 참조값과 ronaldo를 push
- 오퍼랜드 스택에는 String 참조값 push
4) BIPUSH 100, BIPUSH 105
생성자에 전달할 인자(100, 105)를 스택에 추가
5) INVOKESPECIAL test/FootballPlayer. <init> (Ljava/lang/String;II)V
하나의 String과 두 개의 int형 매개변수를 갖는 생성자 호출
- 매개변수 값으로 인스턴스 변수를 초기화
- int 100, int 105, @500, @100를 오퍼랜드 스택에서 pop시켜, 인스턴스 변수를 초기화한다.
6) ASTORE 1
지역변수에 오퍼랜드 스택에 있는 참조값을 넣는다.
- 지역변수 ronaldo에 초기화가 완료된 인스턴스의 참조값(@100)을 넣는다.
7) 작업완료된 상태
4. 참고
'프로그래밍 언어 > Java' 카테고리의 다른 글
[Java] JVM (0) | 2021.10.25 |
---|---|
자바(Java) 버전별 특징 (0) | 2021.10.19 |
[스터디 할래] 6주차 - 상속 (0) | 2021.05.26 |
java.util.Optional이란? (1) | 2021.05.26 |
int와 Integer의 차이(Wrapper Class) (0) | 2021.05.26 |
댓글