프로그래밍 언어/Java

[스터디 할래] 5주차 - 클래스

highright96 2021. 5. 26.

 

본 글은 백기선님의 라이브 스터디 과제를 블로그에 옮기며 다시 작성한 글입니다. 과제 링크

 

highright96/java-livestudy

백기선님의 자바 온라인 스터디. Contribute to highright96/java-livestudy development by creating an account on GitHub.

github.com

 

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

댓글