티스토리 뷰

development/Java

[Java] Class #2 - static

Happyoon ~ 2021. 11. 2. 00:19
728x90
반응형

저번 게시물에서 클래스의 용도에는 3가지가 있으며 그 중 두가지를 살펴보았다. 데이터 타입의 역할과 객체의 설계도 역할이 그 두가지인데, 아래 글에서 확인할 수 있다!

https://live-for-myself.tistory.com/80

 

[Java] Class #1 - Data type의 역할 / 객체의 설계도 역할

클래스의 용도에는 다음의 3가지가 있다. [ 클래스의 용도 ] 1. 데이터 type의 역할 2. 객체의 설계도 역할 3. static 필드나 static 메소드를 감싸는(boxing) 역할 이번 글에서는 클래스의 용도 1, 2에 대해

live-for-myself.tistory.com

이번에는 클래스의 마지막 용도인 "static 필드와 static메소드를 감싸는 역할"에 대해 알아보려 한다.

 

그렇다면, static이란 무엇일까?

 

이제 스택과 힙을 그림으로 표현하는 것은 익숙할 것이다. 

힙은 참조값, 즉 주소값으로 구분을 하고, 스택은 지역변수명으로 서로 구분된다.

static은 위 그림에서 보다시피 "클래스명"으로 구분됨을 확인할 수 있다. 

 

예제를 통해 빠르게 알아보자!


2) MyUtil.java - static 필드와 static 메소드가 담긴 클래스 MyUtil

package test.mypac;

public class MyUtil {
	//static 필드
	public static String version;
	
	//static 메소드
	public static void send() {
		System.out.println("전송합니다.");
	}
}

static 필드와 메소드는 리턴타입 앞에 static을 붙여준다.

MyUtil 클래스는 static한 요소들만 담고 있는데, 만약 non static 요소가 들어있다면 non static요소는 객체 생성 시 new를 함과 동시에 힙 영역에 생성이 된다.

MyUtil의 version과 send는 static 영역에 MyUtil 클래스 내에 생성된다. 

 

그러면 클래스의 활용 모습을 확인해보자.

 


2-1) MainClass04 - MyUtil의 static 필드와 static 메소드 활용하기

package test.main;

import test.mypac.MyUtil;

public class MainClass04 {
	//프로그램의 시잠점, 도입점이 되는 메인 메소드
	public static void main(String[] args) {
		//MyUtil 클래스의 static 필드 참조해서 값 대입해보기
		MyUtil.version = "1.0";
		//MYUtil 클래스의 static 메소드 호출하기 
		MyUtil.send();
	}

}

 

static필드와 메소드는 객체를 생성하지 않고 즉, new를 하지 않고 클래스명에 바로 .을 찍어 사용할 수 있다.

run 결과는 "전송합니다."를 확인할 수 있다.

 


3) News.java - 제목, 내용, 작성자를 필드로 갖고, showInfo() 메소드로 정보를 보여주는 News 클래스

                      static과 nonstatic

package test.mypac;
/*
 * 	뉴스의 제목, 내용, 작성자를 객체 혹은 클래스에 담고 싶다. 
 *	어디에 담는게 맞는 것일까? 한번 생각해 보자!
 */
public class News {
	/*
	 * 제목의 필드명 : title
	 * data type : String 
	 * static or non static
	 */
	
	//static
	/*
	 public static String title;
	 public static String content;
	 public static String writer;
	 */
	
	//non static
	public String title;
	public String content;
	public String writer;
	
	
	public void showInfo() {
		System.out.println("제목: "+this.title);
		System.out.println("제목: "+this.content);
		System.out.println("제목: "+this.writer);
		
	}

}

필드와 메소드를 생성할 때, static이 좋을 지, nonstatic이 좋을 지 한번 생각해보자.

 

static은 static 영역에 클래스명으로 정의되므로, 따로 관리할 수 없고 한번에 관리되어야 하는 것들을 처리하는 데에 좋다.

반면, nonstatic은 객체를 각각 생성하기 때문에 각각 관리하는 것에 좋다

 

제목, 내용, 작성자는 뉴스별로 다르기 때문에 각각 따로 관리되어야할 것이다.

따라서, nonstatic을 활용하는 것이 좋을 것이다.

 

그러면 활용모습을 확인해보자.

 


3-1) MainClass05.java - News 객체 생성하고 활용하기

package test.main;

import test.mypac.News;

public class MainClass05 {
	public static void main(String[] args) {
		// 뉴스 하나를 News 클래스를 이용해서 담고 싶다.
	/* 
		 
		News.title = "얀센 백신 추가 접종";
		News.content ="1차 접종 예약 오늘부터 받아요.";
		News.writer = "김구라 기자";
		
		System.out.println(n1.title);
		System.out.println(n1.content);
		System.out.println(n1.writer);
	*/
		
		//뉴스 하나를 News 클래스를 이용해서 담고 싶다.
		News n1 = new News();
		n1.title = "얀센 백신 추가 접종";
		n1.content ="1차 접종 예약 오늘부터 받아요.";
		n1.writer = "김구라 기자";
		
		//뉴스가 하나 더 있다면?
		News n2 = new News();
		n2.title = "xxx";
		n2.content ="xxxxx";
		n2.writer = "xxx 기자";
		
		n1.showInfo();
		System.out.println();
		n2.showInfo();
		
	}
}

클래스의 필드와 메소드를 모두 nonstatic으로 설졍했기 떄문에 new를 통해 객체를 생성하고 활용하는 모습이다. 

 


4) DarkTemplar.java - 체력, 공격력을 필드로 갖고, 공격과 이동을 메소드로 갖는 DarkTemplar 클래스 

package test.mypac;

public class DarkTemplar {
	//필드
	/*
	 * 체력
	 * 필드명: hp
	 * data type: int
	 * non static
	 */
	public int hp = 80;
	/*
	 * [공격력]
	 * 
	 * 필드명: damage
	 * data type: int
	 * static(공격력 업데이트 시 일괄 업데이트)
	 */
	public static int damage = 40;
	
	//메소드
	
	//공격하는 메소드
	public void attack() {
		System.out.println(DarkTemplar.damage+" 의 공격력으로 공격합니다.");
		
	}
	//움직이는 메소드
	public void move() {
		System.out.println(this.hp+" 체력의 다크 템플러가 움직여요.");
	}
	

}

 

이번 클래스에서도 각각의 필드와 메소드를 static으로 선언할 것인지, non static으로 선언할 것인지 생각해보자.

체력은, 사용자마다 다를 것이기 때문에 각각 다뤄야 하므로, non static이 적합할 것이다.

하지만, 다크템플러의 공격력은 패치 등을 통해 업데이트 시 모든 다크템플러의 공격력이 한번에 조정이 될 것이다.

따라서, static으로 선언한다.

공격하는 메소드와 움직이는 메소드는 각각 다뤄야 하므로, non static으로 선언한다.

 

활용 모습을 살펴보자.


4-1) MainClass06.java - 다크템플러 객체의 메소드 사용해보기

package test.main;

import test.mypac.DarkTemplar;

public class MainClass06 {
	public static void main(String[] args) {
		//DarkTemplar 객체를 생성해서 참조값을 dark1이라는 이름의 지역 변수에 담아보세요.
		DarkTemplar dark1 = new DarkTemplar();
		//dark1 안에 있는 참조값을 이용해서 move(), attack() 메소드를 호출해 보세요.
		dark1.move();
		dark1.attack();
		
	}

}

 

move()와 attack() 메소드는 모두 non static 이기 때문에, 객체를 생성해야 사용할 수 있다.

따라서. new 를 통해 다크템플러 객체를 생성하고 .move(), .attack()의 방식으로 호출한다.


4-2) MainClass07.java - 다크템플러 객체를 두개 생성하고, static과 non static 변화에 따른 각 객체의 반응 확인하기

package test.main;

import test.mypac.DarkTemplar;

public class MainClass07 {
	public static void main(String[] args) {
		DarkTemplar dark1 = new DarkTemplar();
		DarkTemplar dark2 = new DarkTemplar();
		
		//dark1의 체력을 10 감소시키기
		dark1.hp -= 10;
		//감소 시킨 후 move()
		dark1.move();
		
		dark2.move();
		
		//다크 템플러의 공격력을 10 증가시켜 보세요.
		DarkTemplar.damage+=10;
		
		dark1.attack();
		dark2.attack();
	}

}

 

출력

dark1의 체력 필드를 10 감소시키고 dark1과 dark2의 move를 호출하자, dark1에만 영향이 간 것을 확인할 수 있다.

하지만, static 필드인 damage를 10 증가시키고 attack 메소드를 각각 호출하니, 모든 다크 템플러 객체에게 영향이 간 것을 확인할 수 있다.

 

그림으로 나타내면 다음과 같다.


4-3) MainClass08.java - 클래스 객체 참조하는 형태 확인하기 

package test.main;

import test.mypac.DarkTemplar;

public class MainClass08 {
	public static void main(String[] args) {
		DarkTemplar dark1 = new DarkTemplar();
		DarkTemplar dark2 = new DarkTemplar();
		DarkTemplar dark3 = dark1;
		
		//dark1에 . 찍어서 사용하나 dark3에 . 찍어서 사용하나 모두 같은 객체를 사용하는 것이다. 
		dark1.hp-=10;
		dark3.move();
		
		boolean result1 = dark1 == dark2; //다른 객체(참조값이 다름) 이므로 false
		boolean result2 = dark1 == dark3; //같은 객체(참조값이 같음) 이므로 true
		
	}

}

 

dark1과 dark2 변수는 new를 통해 객체를 생성하여 각각의 객체의 참조값을 담고 있다.

그렇다면 dark3는 어떨까?

dark3는, 대입연산자가 사용되었으므로 dark1 객체의 참조값을 가리킨다. 

즉, dark1과 dark3는 같은 객체를 사용하는 것이다.

반면에, dark1과 dark2는 각각 new했으므로, 모양은 DarkTemplar 형식으로 동일하지만, 참조값이 다른 서로 다른 두 객체이다.

 

따라서, result1은 dark1과 dark2 다른 객체이기 때문에 false가 나오고, dark1과 dark3는 동일한 객체, 즉 참조값이 같기 때문에 result2는 true가 나온다.


4-4) MainClass09.java - 지역변수와 필드 비교하기

package test.main;

import test.mypac.DarkTemplar;

public class MainClass09 {
	public static void main(String[] args) {
		System.out.println("main메소드가 시작되었습니다.");
		//객체는 1개, 변수는 2개 만들어짐 
		//지역변수는 선언만 하고 값을 넣지 않으면 만들어지지도 않음
		//필드는 선언만 해도 자동으로 null이 들어감
		DarkTemplar dark1;
		DarkTemplar dark2 = null;
		DarkTemplar dark3 = new DarkTemplar();
		
		//dark1.attack(); //만들어지지 않은 변수에서 사용 불가
		//dark2에는 null이 들어있어서(비어있어서, 참조값이 없어서) Exception이 발생한다.
		dark2.attack();
	}

}

이전 게시물에서, 지역변수는 선언만 하면 생성이 되지 않아 초기화가 꼭 필요하고, 필드는 선언만 해도 자동으로 null 값이 들어가 변수가 생성된다고 언급했다.

 

이번 예제에서, dark1, dark2, dark3 변수 중 어떤 것이 생성되는지, 객체는 몇개 생성되는지 확인할 수 있다.

 

우선, dark1은 선언만 하였고 초기화시켜주지 않았다.

dark1은 필드가 아닌 지역변수이기 때문에 dark1 변수는 생성되지 않는다.

dark2는 null을 대입해주었기 때문에, 변수가 생성된다. 

 

dark3는 new DarkTemplar()로 객체를 생성하여, 객체의 참조값을 변수에 담아주었기 때문에 dark3 변수가 생성된다.

 

따라서, 객체는 통틀어서 1개, 변수는 2개 생성되었음을 알 수 있다.

생성된 객체의 갯수가 헷갈린다면, 간단하게 new의 갯수를 세어보면 된다. 객체는 new를 통해 생성하기 때문이다.

 

따라서, dark1.attack()을 할 경우, 에러가 뜬다. 

객체가 만들어지지 않았기 때문이다.

 

dark2는, 변수는 생성되었지만 연결된 객체가 없기 때문에, 마찬가지로 attkack() 호출 시 에러가 뜬다. 

 

객체와 변수의 생성은 디버깅을 통해서 쉽게 확인할 수 있다.


5) MyPocket.java - DarkTemplar 형태의 static 필드와 반환형태가 DarkTemplar인 static 메소드를 갖는 MyPocket 클래스

package test.mypac;

public class MyPocket {
	//필드 선언과 동시에 객체 생성해서 참조갑을 넣어두는 것도 가능하다.
	public static DarkTemplar templar = new DarkTemplar();
	
	//호출될 때마다 새로운 Dark Templar 객체를 생성해서 리턴해주는 static method
	public static DarkTemplar orderTemplar() {
		
		return new DarkTemplar();		
	}
	
}

MyPocket 클래스는 DarkTemplar 객체를 생성하고, 그 참조값을 담는 Darktemplar static필드인 templar와, DarkTemplar 객체를 생성하여 반환하는 orderTemplar 메소드를 가진다. 

 

이 둘의 활용법을 아래에서 살펴보자!


5-1) MainClass10.java - MyPocket 클래스의 DarkTemplar static 필드와 메소드 활용해보기

package test.main;

import test.mypac.DarkTemplar;
import test.mypac.MyPocket;

public class MainClass10 {
public static void main(String[] args) {
		/*
		 * MyPocket 클래스의 templar 라는 static 필드에 저장된 DarkTemplar 객체의 
		 * .move() 메소드를 호출해 보세요.
		 */
		MyPocket.templar.move();
		/*
		 * MyPocket 클래스의 templar 라는 static 필드에 저장된 DarkTemplar 객체의
		 * .attack() 메소드를 호출해 보세요.
		 */
		MyPocket.templar.attack();
		System.out.println("---------");
		DarkTemplar dark1 = MyPocket.orderTemplar();
		dark1.move();
		
		System.out.println("---------");
		MyPocket.orderTemplar().attack();
	}
}

templar는 static 필드이므로, MyPocket.templar로 사용할 수 있다.

문제는 templar의 move 메소드를 호출하는 것이기 때문에, MyPocket.templar.move()를 통해 호출할 수 있다.

 

마찬가지의 방식으로,  MyPocket.templar.attack()으로 templar의 attack() 메소드를 호출할 수 있다. 

 

orderTemplar() 메소드는 static이므로 MyPocket.orderTemplar()으로 호출할 수 있고, 반환값이 DarkTemplar 형식이기 때문에, DarkTemplar 형식의 변수 dark1에 담아줄 수 있다.

변수를 활용하면, 반환한 그 객체를 나중에 재사용할 수 있다.

dark1.move() 이 그 예시이다.

 

변수에 담지 않고 바로 메소드를 호출하면 MyPocket.orderTemplar().attack()과 같이 호출할 수 있다. 

이 경우, 반환값을 저장하지 않으므로, 해당 객체를 재사용할 수 없다. 

 

구조는 다음과 같다.

 

 

 

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함