본문 바로가기

항해 99/Java

Java 기초 11 - 자바 메모리 구조와 Static

자바 메모리 구조

자바 메모리 구조 - 비유

자바 메모리 구조는 크게 메서드 영역, 스택 영역, 힙 영역 3개로 나뉨.

  • 메서드 영역: 클래스 정보를 보관한다. 이 클래스 정보가 붕어빵 틀
  • 스탭 영역: 실제 프로그램이 실행되는 영역(메서드 실행마다 1개씩 쌓임)
  • 힙 영역: 객체가 생성되는 영역, new 명령어 사용 시 이 영역을 사용(붕어빵이 존재하는 공간, 배열로 해당 영역에서 생성)

자바 메모리 구조 - 실제

 

  • 메서드 영역(Method Area) : 프로그램을 실행하는데 필요한 공통 데이터를 관리(프로그램 모든 영역에서 공유)
    • 클래스 정보: 클래스의 실행 코드(바이트 코드), 필드, 메서드와 생성자 코드 등 모든 실행 코드 존재
    • static 영역: static 변수들을 보관
    • 런타임 상수 풀: 프로그램을 실행하는데 필요한 공통 리터럴 상수를 보관, 이외에 프로그램을 효율적으로 관리하기 위한 상수들을 관리함.
  • 스택 영영(Stack Area) : 자바 실행 시, 하나의 실행 스택이 생성된다. 각 스택 프레임은 지역 변수, 중간 연산 결과, 메서드 호출 정보 등을 포함
    • 스택 프레임: 스택 영역에 쌓이는 네모 박스가 하나의 스택 프레임이다. 메서드를 호출할 때 마다 하나의 스택 프레임이 쌓이고, 메서드가 종료되면 해당 스택 프레임이 제거됨
  • 힙 영역(Heap Area) : 객체(인스턴스)와 배열이 생성되는 영역. 가비지 컬렉션(GC)이 이루어지는 주요 영역이며, 더 이상 참조되지 않는 객체는 GC에 의해 제거됨.

참고: 스택 영역은 더 정확히는 각 쓰레드별로 하나의 실행 스택이 생성된다. 따라서 쓰레드 수 만큼 스택 영역이 생성

 

 

메서드 코드는 메서드 영역에

 

같은 클래스로부터 생성된 객체라도, 인스턴스 내부의 변수 값은 서로 다를 수 있지만, 메서드는 공통된 코드를 공유함. 따라서 객체가 생성될 때, 인스턴스 변수에는 메모리가 할당되지만, 메서드에 대한 새로운 메모리 할당은 없다. 메서드는 메서드 영역에서 공통으로 관리되고 실행됨(인스턴스 메서드를 호출하면 실제로는 메서드 영역에 있는 코드를 불러서 수행).

 

 

스택과 큐 자료 구조

스택 구조(Stack)

후입 선출(LIFO, Last In First Out)

가장 마지막에 넣은 것이 가장 먼저 나오는 것, 이런 자료 구조를 스택이라 한다.

 

큐 자료 구조(Queue)

선입 선출(FIFO, First In First Out)

후입 선출과 반대로 가장 먼저 넣은 것이 가장 먼저 나오는 것을 선입 선출이라 한다, 이런 자료 구조를 큐(Queue)라 한다.

 

 

스택 영역

package memory;

public class JavaMemoryMain1 {
    public static void main(String[] args) {
        System.out.println("main start");
        method1(10);
        System.out.println("main end");
    }

    static void method1(int m1) {
        System.out.println("method1 start");
        int cal = m1 * 2;
        method2(cal);
        System.out.println("method1 end");
    }

    static void method2(int m2) {
        System.out.println("method2 start");
        System.out.println("method2 end");
    }
}

 

호출

  • 처음 자바 프로그램 실행 시 main( ) 실행, main( )을 위한 스택 프레임이 하나 생성
    • main( ) 스택 프레임은 내부에 args라는 매개변수를 가진다. args는 뒤에서 다룬다.
  • main( )은 method1( )을 호출한다. method1( ) 스택 프레임이 생성
    • method1( )은 m1, cal 지역 변수(매개변수 포함)를 가지므로 해당 지역 변수들이 스택 프레임에 포함
  • method1( )은 method2( )를 호출, method2( ) 스택 프레임이 생성, method2( )는 m2 지역 변수(매개변수 포함)을 가지므로 해당 지역 변수가 스택 프레임에 포함

종료

  • method2( )가 종료, method2( ) 스택 프레임 제거, 매개변수 m2도 제거. method2( )  스택 프레임이 제거 되었으므로 프로그램은 method1( )로 돌아감(method1( )에서 method2( )를 호출한 지점으로 돌아감).
  • method1( )가 종료, method1( ) 스택 프레임 제거, 지경변수 m1, cal도 제거 됨, 프로그램은 main( )으로 돌아감
  • main( ) 이 종료, 더 이상 호출한 메서드가 없고, 스택 프레임도 완전히 비워짐. 자바는 프로그램을 정리 후 종료함.

정리

  • 자바는 스택 영역을 사용해 메서드 호출과 지역변수(매개변수 포함)를 관리
  • 멤서드를 계속 호출하면 스택 프레임이 계속 쌓임
  • 지역변수(매개변수 포함)는 스택 영역에서 관리
  • 스택 프레임이 종료되면 지역변수도 함께 제거
  • 스택 프레임이 모두 제거되면 프로그램도 종료

 

 

스택 영역과 힙 영역

스택 영역과 힙 영역이 함께 사용되는 경우

package memory;

public class JavaMemoryMain2 {
    public static void main(String[] args) {
        System.out.println("main start");
        method1();
        System.out.println("main end");
    }

    static void method1() {
        System.out.println("method1 start");
        Data data1 = new Data(10);
        method2(data1);
        System.out.println("method1 end");
    }

    private static void method2(Data data2) {
        System.out.println("method2 start");
        System.out.println("data.value=" + data2.getValue());
        System.out.println("method2 end");
    }
}

 

  • main( ) → method1( ) → method2( ) 순서로 호출하는 단순한 코드
  • method1( )에서 Data 클래스의 인스턴스를 생성
  • method1( )에서 method2( )를 호출할 때 매개변수에 Data 인스턴스의 참조값을 전달

호출

  • main( ) 메서드를 실행, main( ) 스택 프레임이 생성
  • main( ) 에서 method1( ) 을 실행,  method1( )  스택 프레임 생성
  • method1( ) 은 지역 변수로 Data data1을 가짐, 지역 변수도 스택 프레임에 포함
  • method1( ) 은 new Data(10)를 사용해 힙 영역에 Data 인스턴스를 생성, 참조값은 data1에 보관
  • method1( ) 은 method2( )를 호출하면서 Data data2 매개변수에 x001 참조값을 넘김
  • method1( )에 있는 data1과 method2( ) 에 있는 data2 지역변수는 둘 다 같은 x001인스턴스를 참조

종료

  • method2( ) 가 종료, method2( )의 스택 프레임 제거, 매개변수 data2도 제거
  • method1( ) 이 종료, method1( )의 스택 프레임 제거, 지역변수 data1도 함께 제거
  • method1( ) 종료 직후, 지역변수 data1이 제거되므로 x001 참조값을 가진 Data 인스턴스를 참조하는 곳이 없어짐
  • 프로그램에서 더는 사용하지 않는 객체는 GC가 메모리에서 제거(참조가 모두 사라진 인스턴스가 대상)

참고: 힙 영역 외부가 아닌, 힙 영역 안에서만 인스턴스끼리 서로 참조하는 경우도 GC의 대상

 

정리

지역 변수는 스택 영역에, 객체(인스턴스)는 힙 영역에 관리 됨, 메서드 영역이 관리하는 변수도 있음. 이것을 이해하기 위해서는 static 키워드를 알아야 함(static 키워드는 메서드 영역과 밀접한 연관이 있음).

 

 

Static 변수

static 키워드는 주로 멤버 변수와 메서드에 사용

 

인스턴스 내부 변수에 카운트 저장

package static1;

public class Data1 {
    public String name;
    public int count;

    public Data1(String name) {
        this.name = name;
        count++;
    }
}

 

생성된 객체의 수를 세기 위해 객체가 생성될 때마다 인스턴스의 멤버 변수인 count  값을 증가.

package static1;

public class DataCountMain1 {
    public static void main(String[] args) {
        Data1 data1 = new Data1("A");
        System.out.println("A count=" + data1.count);

        Data1 data2 = new Data1("B");
        System.out.println("B count=" + data2.count);

        Data1 data3 = new Data1("C");
        System.out.println("C count=" + data3.count);
    }
}

 

위 프로그램은 기대한 대로 작동하지 않음, 객체 생성 시 마다 Data1 인스턴스는 새로 만들어짐, 인스턴스에 포함된 count 변수도 새로 만들어짐(count = 0에서 ++로 증가해도 1).

 

인스턴스에 사용되는 멤버 변수 count 값은 인스턴스끼리 서로 공유되지 않음(이 문제를 해결하기 위해서는 변수를 서로 공유해야 함). 

 

 

외부 인스턴스에 카운트 저장

카운트 값을 저장하는 별도의 객체 만들기

package static1;

public class Counter {
    public int count;
}

 

package static1;

public  class Data2 {
    public String name;

    public Data2(String name, Counter counter) {
        this.name = name;
        counter.count++;
    }
}

 

package static1;

public class DataCountMain2 {
    public static void main(String[] args) {
        Counter counter = new Counter();
        Data2 data1 = new Data2("A", counter);
        System.out.println("A count=" + counter.count);

        Data2 data2 = new Data2("B", counter);
        System.out.println("B count=" + counter.count);

        Data2 data3 = new Data2("C", counter);
        System.out.println("C count=" + counter.count);

    }
}

 

Counter 인스턴스를 공용으로 사용한 덕분에 객체를 생성할 때 마다 값을 정확하게 증가시킬 수 있다.

 

  • 인스턴스 생성 시 생성자를 통해 Counter 인스턴스에 있는 count 값을 하나 씩 증가 시킨다.

결과적으로 Data2의 인스턴스 3개가 생성되고, count 값도 인스턴스 숫자와 같은 3으로 정확하게 측정됨.

 

불편한 점

  • Data2 클래스와 관련된 일인데, Counter라는 별도의 클래스를 추가 사용해야 함
  • 생성자의 매개변수도 추가되고, 생성자가 복잡해짐(생성자 호출부분도 복잡해짐)

 

 

Static 변수 사용

특정 클래스에서 공용으로 함께 사용할 수 있는 변수를 만들 수 있다면 편리할 것.

static 키워드를 사용하면 공용으로 함께 사용하는 변수를 만들 수 있다.

package static1;

public class Data3 {
    public String name;
    public static int count; // static

    public Data3(String name) {
        this.name = name;
        count++;
    }
}
  • 기존 코드 유지를 위해 새로운 클래스 Data3을 만듦
  • static int count 부분을 보면 변수 타입(int) 앞에 static 키워드가 붙음
  • 멤버 변수에 static을 붙이면 static 변수, 정적 변수 또는 클래스 변수라 함
  • 객체가 생성되면 생성자에서 정적 변수 count의 값을 하나 증가시킴
package static1;

public class DataCountMain3 {
    public static void main(String[] args) {
        Data3 data1 = new Data3("A");
        System.out.println("A count=" + Data3.count);

        Data3 data2 = new Data3("B");
        System.out.println("B count=" + Data3.count);

        Data3 data3 = new Data3("C");
        System.out.println("C count=" + Data3.count);
    }
}

 

count  정적 변수에 접근하는 방법이 조금 특이한데 Data3.count와 같이 클래스명에 .(dot)을 사용한다. 마치 클래스에 직접 접근하는 것처럼 느껴짐.

 

 

  • static 이 붙은 멤버 변수는 메서드 영역에서 관리
    • static이 붙은 멤버 변수 count는 인스턴스 영역에서 생성되지 않음(메서드 영역에서 변수를 관리)
  • Data3("A") 인스턴스를 생성 시 생성자가 호출
  • 생성자에는 count++ 코드가 있음, count는 static이 붙은 정적 변수(정적 변수는 인스턴승 영역이 아니라 메서드 영역에서 관리). 따라서 이 경우 메서드 영역에 있는 count 값이 하나 증가
  • 최종적으로 메서드 영역에 있는 count 값은 3이 됨(B, C 추가도 A 때와 동일).

 

static이 붙은 정적 변수에 접근하려면 Data3.count와 같이 클래스명 + .(dot) + 변수명으로 접근(Data3의 생성자와 같이 자신의 클래스에 있는 정적 변수라면 클래스명을 생략할 수 있음).

 

정리

static 변수는 쉽게 말하면 클래스인 붕어빵 틀이 관리하는 변수, 붕어빵 틀은 1개이므로 클래스 변수도 하나만 존재함(반면 인스턴스 변수는 붕어빵인 인스턴스의 수 만큼 존재).

 

 

용어 정리

Public class Data3 {
    public String name;
    public static int count; // static
}

 

예제 코드의 name과 count는 둘 다 멤버 변수이다.

멤버 변수(필드)는 static이 붙은 것과 아닌 것에 따라 다음과 같이 분류할 수 있다.

 

멤버 변수(필드)의 종류

  • 인스턴스 변수: static이 붙지 않는 멤버 변수, 예) name
    • static이 붙지 않은 멤버 변수는 인스턴스를 생성해야 사용할 수 있고, 인스턴스에 소속되어 있다. 따라서 인스턴스 변수라 함
    • 인스턴스 변수는 인스턴스를 만들 때 마다 새로 만들어짐
  • 클래스 변수: static이 붙은 멤버 변수, 예) count
    • 클래스 변수, 정적 변수, static 변수등으로 부름. 용어를 모두 사용하니 주의
    • static이 붙은 멤버 변수는 인스턴스와 무관하게 클래스에 바로 접근해서 사용할 수 있고, 클래스 자체에 소속되어 있다, 따라서 클래스 변수라 한다.
    • 클래스 변수는 자바 프로그램을 시작할 때 딱 1개가 만들어짐, 인스턴스와는 다르게 보통 여러 곳에서 공유하는 목적으로 사용

 

변수와 생명주기

  • 지역 변수(매개변수 포함) : 지역 변수는 스택 영역에 잇는 스택 프레임 안에 보관, 메서드 종료 시 스택 프레임도 제거 되는데 이때 해당 스택 프레임에 포함된 지역 변수도 함께 제거 됨(생존 주기가 짧음)
  • 인스턴스 변수 : 인스턴스에 있는 멤버 변수를 인스턴스 변수라 함, 인스턴스 변수는 힙 영역을 사용. 힙 영역은 GC가 발생하기 전까지는 생존(보통 지역 변수보다 생존 주기가 길다)
  • 클래스 변수 : 메서드 영역의 static 영역에 보관되는 변수, 메서드 영역은 프로그램 전체에서 사용하는 공용 공간. 클래스 변수는 해당 클래스가 JVM에 로딩되는 순간 생성, JVM 종료 시까지 생명주기가 이어짐(가장 긴 생명주기)

static이 정적인 이유, 힙 영역의 인스턴스 변수는 동적으로 생성되고, 제거 됨. 반면 static 변수는 거의 프로그램 실행 시점에 만들어지고, 프로그램 종료 시 제거 됨.

 

 

정적 변수 접근 법

static 변수는 클래스를 통해 바로 접근할 수도 있고, 인스턴스를 통해서도 접근할 수 있다.

// 인스턴스를 통한 접근
Data3 data4 = new Data3("D");
System.out.println(data4.count);

// 클래스를 통한 접근
System.out.println(Data3.count);

 

인스턴스를 통한 접근

  • 정적 변수의 경우 인스턴스를 통한 접근은 추천하지 않음, 코드를 읽을 때 마치 인스턴스 변수에 접근하는 것처럼 오해할 수 있기 때문

클래스를 통한 접근

  • 정적 변수는 클래스에서 공용으로 관리하기 때문에 클래스를 통해서 접근하는 것이 더 명확함.

 

 

static 메서드

 

인스턴스 메서드

package static2;

public class DecoUtil1 {
    public String deco(String str) {
        String result = "*" + str + "*";
        return result;
    }
}

 

deco( )는 문자열을 꾸미는 기능을 제공

package static2;

public class DecoMain1 {
    public static void main(String[] args) {
        String s = "hello java";
        DecoUtil1 utils = new DecoUtil1();
        String deco = utils.deco(s);

        System.out.println("before: " + s);
        System.out.println("after: " + deco);
    }
}

 

앞서 개발한 deco( ) 메서드를 호출하기 위해서는 DecoUtil1의 인스턴스를 먼저 생성해야 한다. 그런데 deco( )라는 기능은 멤버 변수도 없고, 단순히 기능만 제공할 뿐, 인스턴스가 필요한 이유는 멤버 변수(인스턴스 변수)등을 사용하는 목적이 큰 데, 이 메서드는 사용하는 인스턴스 변수도 없고 단순히 기능만 제공함

 

 

static 메서드

package static2;

public class DecoUtil2 {
    public static String deco(String str) {
        String result = "*" + str + "*";
        return result;
    }
}

 

메서드 앞에 static이 붙음(이 부분에 주의), 이렇게 하면 정적 메서드를 만들 수 있다. 정적 메서드는 정적 변수처럼 인스턴스생성 없이 클래스 명을 통해서 바로 호출할 수 있음

package static2;

public class DecoMain2 {
    public static void main(String[] args) {
        String s = "hello java";
        String deco = DecoUtil2.deco(s);

        System.out.println("before: " + s);
        System.out.println("after: " + deco);
    }
}

 

static이 붙은 정적 메서드는 객체 생성 없이 클래스 명 + .(dot) + 메서드 명으로 바로 호출할 수 있음.

정적 메서드 덕분에 불필요한 객체 생성 없이 편리하게 메서드를 사용.

 

클래스 메서드

메서드 앞에도 static을 붙일 수 있음, 이것을 정적 메서드 또는 클래스 메서드라 한다. 정적 메서드라는 용어는 static이 정적이라는 뜻이기 때문, 클래스 메서드라는 용어는 인스턴스 생성 없이 마치 클래스에 있는 메서드를 바로 호출하는 것처럼 느껴지기 때문.

 

인스턴스 메서드

static이 붙지 않은 메서드는 인스턴스를 생성해야 호출할 수 있다. 이를 인스턴스 메서드라 한다.

 

 

static 메서드 2

정적 메서드는 객체 생성 없이 클래스에 있는 메서드를 바로 호출할 수 있다는 장점이 있음.

정적 메서드는 언제나 사용할 수 있는 것이 아님.

 

정적 메서드 사용법

  • static 메서느는 static만 사용할 수 있다.
    • 클래스 내부의 기능을 사용할 때, 정적 메서드는 static이 붙은 정적 메서드나 정적 변수만 사용할 수 있다.
    • 클래스 내부의 기능을 사용할 때, 정적 메서드는 인스턴스 변수나, 인스턴스 메서드를 사용할 수 없다.
  • 반대로 모든 곳에서 static을 호출할 수 있다.
    • 정적 메서드는 공용 기능, 따라서 접근 제어자만 허락한다면 클래스를 통해 모든 곳에서 static을 호출할 수 있다.
package static2;

public class DecoData {
    private int instanceValue;
    private static int staticValue;

    public static void staticCall() {
//        instanceValue++; // 인스턴스 변수 접근, compiler error
//        instanceMethod(); // 인스턴스 메서드 접근, compiler error

        staticValue++; // 정적 변수 접근
        staticMethod(); // 정적 메서드 접근
    }

    public void instanceCall() {
        instanceValue++; // 인스턴스 변수 접근, compiler error
        instanceMethod(); // 인스턴스 메서드 접근, compiler error

        staticValue++; // 정적 변수 접근
        staticMethod(); // 정적 메서드 접근
    }


    private void instanceMethod() {
        System.out.println("instanceValue=" + instanceValue);
    }

    private static void staticMethod() {
        System.out.println("staticValue=" + staticValue);
    }
}

 

접근 제어자를 활용해 필드를 포함한 외부에서 직접 필요하지 않은 기능은 모두 막아 둔 예제

  • instanceValue는 인스턴스 변수
  • staticValue는 정적 변수(클래스 변수)
  • instanceMethod( )는 인스턴스 메서드
  • staticMethod( )는 정적 메서드(클래스 메서드)

staticCall( ) 메서드

이 메서드는 정적 메서드, 따라서 static만 사용할 수 있다. 정적 변수, 정적 메서드에는 접근할 수 있지만, static이 없는 인스턴스 변수나 인스턴스 메서드에 접근하면 컴파일 오류가 발생한다.

코드에서는 static에서 static을 호출하는 것을 확인 가능.

 

instanceCall( ) 메서드

이 메서드는 인스턴스 메서드, 모든 곳에서 공용인 static을 호출할 수 있음. 따라서 정적 변수, 정적 메서드에 접근할 수 있다. 물론 인스턴스 변수, 인스턴스 메서드에도 접근할 수 있음.

package static2;

public class DecoDataMain {
    public static void main(String[] args) {
        System.out.println("1. 정적 호출");
        DecoData.staticCall();

        System.out.println("2. 인스턴스 호출 1");
        DecoData data1 = new DecoData();
        data1.instanceCall();

        System.out.println("3. 인스턴스 호출 2");
        DecoData data2 = new DecoData();
        data2.instanceCall();
    }
}

 

 

정적 메서드가 인스턴스의 기능을 사용할 수 없는 이유

정적 메서드는 클래스의 이름을 통해 바로 호출할 수 있다. 그래서 인스턴스처럼 참조값의 개념이 없다.

특정 인스턴스의 기능을 사용하라면 참조값을 알아야 하는데, 정적 메서드는 참조값 없이 호출한다. 따라서 정적 메서드 내부에서 인스턴스 변수나 인스턴스 메서드를 사용할 수 없다.

 

객체의 참조값을 직접 매개변수로 전달하면 정적 메서드도 인스턴스 변수나 메서드를 호출할 수 있다.

public static void staticCall(DecoData data) {
    data.instanceValue++;
    data.instanceMethod();
}

 

 

용어 정리

멤버 메서드의 종류

  • 인스턴스 메서드 : static이 붙지 않은 멤버 메서드
  • 클래스 메서드 : static이 붙은 멤버 메서드
    • 클래스 메서드, 정적 메서드, static 메서드 등으로 부름

static이 붙지 않은 멤버 메서드는 인스턴스를 생성해야 사용할 수 있고, 인스턴스에 소속(인스턴스 메서드).

static이 붙은 멤버 메서드는 인스턴스와 무관하게 클래스에 바로 접근해서 사용할 수 있고, 클래스 자체에 소속(클래스 메서드)

 

정적 메서드

정적 메서드는 객체 생성이 필요 없이 메서드 호출만으로 필요 기능을 수행할 때 주로 사용.

간단한 메서드 하나로 끝나는 유틸리티성 메서드에 자주 사용(수학의 여러 기능을 담은 클래스를 만들 때 인스턴스 변수 없이 입력 값을 계산하고 반환하는 것이 대부분, 이런 경우에 정적 메서드를 사용해 유틸리티성 메서드를 만들면 좋음).

 

 

정적 메서드 접근 법

static 메서드는 static 변수와 마찬가지로 클래스를 통해 바로 접근할 수 있고, 인스턴스를 통해서도 접근할 수 있다.

//인스턴스를 통한 접근
DecoData data3 = new DecoData();
data3.staticCall();

//클래스를 통한 접근
DecoData.staticCall();

 

인스턴스를 통한 접근

  • 정적 메서드의 경우 인스턴스를 통한 접근은 추천하지 않음, 코드를 읽을 때 마치 인스턴스 메서드에 접근하는 것처럼 오해할 수 있기 때문.

클래스를 통한 접근

  • 정적 메서드는 클래스에 공용으로 관리하기 때문에 클래스를 통해서 접근한느 것이 명확, 정적 메서드에 접근할 대는 클래스를 통해서 접근

 

static import

정적 메서드를 사용할 때 해당 메서드를 다음과 같이 자주 호출해야 한다면 static import 기능을 고려

//import static static2.DecoData.staticCall;
import static static2.DecoData.*;
  • 특정 클래스의 정적 메서드 하나만 적용하려면 생략할 메서드 명을 적어주면 된다.
  • 특정 클래스의 모든 정적 메서드에 적용하려면 *을 사용하면 된다.

 

main( ) 메서드는 정적 메서드

인스턴스 생성 없이 실행하는 가장 대표적인 메서드가 바로 main( )메서드

프로그램이 시작하는 시작점, 객체를 생성하지 않아도 main( )메서드가 작동하는 이유는 static 메서드이기 때문.

 

정적 메서드는 정적 메서드만 호출할 수 있음, 정적 메서드인 main( )이 호출하는 메서드에는 정적 메서드를 사용. 정적 메서드는 같은 클래스 내부에서 정적 메서드만 호출할 수 있다. 따라서 정적 메서드인 main( ) 메서드가 같은 클래스에서 호출하는 메서드도 정적 메서드로 선언해서 사용.

 

 

문제 풀이

1. 구매한 자동차 수

코드를 참고해서 생성한 차량 수를 출력하는 프로그램을 작성

package static2.ex;

public class CarMain {
    public static void main(String[] args) {
        Car car1 = new Car("K3");
        Car car2 = new Car("G80");
        Car car3 = new Car("Model Y");

        Car.showTotalCars(); // 구매한 차량 수를 출력하는 static 메서드
    }
}

 

클래스 Car

package static2.ex;

public class Car {
    private String name;
    private static int count;

    public Car(String name) {
        this.name = name;
        System.out.println("차량 구입, 이름: " + name);
        count++;
    }

    public static void showTotalCars() {
        System.out.println("구매한 차량 수: " + count);
    }
}

 

 

2. 수학 유틸리티 클래스

다음 기능을 제공하는 배열용 수학 유틸리티 클래스(MathArrayUtils)를 만드세요.

  • sum(int[ ] array) : 배열의 모든 요소를 더하여 합계를 반환
  • average(int[ ] array) : 배열의 모든 요소의 평균값을 계산
  • min(int[ ] array) : 배열에서 최소값을 찾음
  • max(int[ ] array) : 배열에서 최대값을 찾음

요구사항

  • MathArrayUtils은 객체를 생성하지 않고 사용, MathArrayUtils의 인스턴스를 생성하지 못하게 막을 것
  • 실행 코드에 static import를 사용해도 됨

실행 코드

package static2.ex;

public class MathArrayUtilsMain {
    public static void main(String[] args) {
        int[] values = {1,2,3,4,5};
        System.out.println("sum=" + MathArrayUtils.sum(values));
        System.out.println("average=" + MathArrayUtils.average(values));
        System.out.println("min=" + MathArrayUtils.min(values));
        System.out.println("max=" + MathArrayUtils.max(values));
    }
}

// static import 사용 간략화
package static2.ex;

import static static2.ex.MathArrayUtils.*; // static 임포트 사용

public class MathArrayUtilsMain {
    public static void main(String[] args) {
        int[] values = {1,2,3,4,5};
        System.out.println("sum=" + sum(values));
        System.out.println("average=" + average(values));
        System.out.println("min=" + min(values));
        System.out.println("max=" + max(values));
    }
}

 

클래스

package static2.ex;

public class MathArrayUtils {
    private MathArrayUtils() {
        // 인스턴스 생성을 막는다.
    }

    public static int sum(int[] values) {
        int sum = 0;
        for (int value : values) {
            sum += value;
        }
        return sum;
    }

    public static double average(int[] values) {
        double average = 0; // 0 대신 sum(values) 가능, 이 때는 for문 제거
        for (int value : values) {
            average += value;
        }
        return average / (double) values.length;
    }

    public static int min(int[] values) {
        int min = values[0];
        for (int value : values) {
            min = Math.min(min, value);
        }
        return min;
    }

    public static int max(int[] values) {
        int max = values[0];
        for (int value : values) {
            max = Math.max(max, value);
        }
        return max;
    }
}

 

 

정리

'항해 99 > Java' 카테고리의 다른 글

Java 기초 개념  (0) 2024.02.05
Java 기초 12 - final , 상속  (1) 2024.01.30
Java 기초 10 - 패키지, 접근 제어자  (2) 2024.01.29
Java 기초 9 - 생성자  (1) 2024.01.25
Java 기초 8 - 객체 지향 프로그래밍  (0) 2024.01.25