본문 바로가기

Studying/JAVA

[JAVA]_자바 메모리 구조와 static

1. 자바 메모리 구조

 

● 종류 ●

① 메서드 영역

     - 클래스 정보를 보관. (예를 들면 붕어빵 틀)

     - 프로그램을 실행하는데 필요한 공통 데이터 관리. 이 영역은 프로그램의 모든 영역에서 공유.

② 스택 영역

     - 실제 프로그램이 실행되는 영역.

     - 자바 실행 시, 하나의 실행 스택이 생성. 각 스택 프레임은 지역변수, 중간 연산 결과, 메서드 호출 정보 등을 포함.

     - 메서드 종료되면 해당 스택 프레임 제거.

③ 힙 영역

     - 객체(인스턴스)와 배열이 생성되는 영역. (new 명령어 사용 시 이 영역 사용.)

     - GC(가비지컬렉션)가 이루어지는 주요 영역이며, 더 이상 참조되지 않은 객체는 GC에 의해 제거.

 

!참고!

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

지금은 스레드 1개만 사용하므로 스택 영역도 하나이지만 스레드에 대한 부분은 멀티 스레드를 학습해야 이해할 수 있으므로 추후에 나옴.


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

 

자바에서 특정 클래스로 100개의 인스턴스를 생성하면, 힙 메모리에 100개의 인스턴스가 생성됨.

각각의 인스턴스는 내부에 변수와 메서드를 가짐.

같은 클래스로부터 생성된 객체라도, 인스턴스 내부의 변수 값은 서로 다를 수 있지만, 메서드는 공통된 코드를 공유함.

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


▶ 스택과 큐자료 구조

 

① 스택(Stack)

- 후입선출(LIFO, Last In First Out) : 나중에 넣은 것이 가장 먼저 나오는 것을 후입선출.

 

② 큐(Queue)

- 선입선출(FIFO, Fist In First Out) : 가장 먼저 넣은 것이 가장 먼저 나오는 것.


▶ 스택영역

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 start
method1 start
method2 start
method2 end
method1 end
main end

[JavaMemoryMain1]

 

 

처음 자바 프로그램을 실행하면 main()을 실행. 이때 main()을 위한 스택 프레임이 하나 생성.

   - main()  스택 프레임은 내부에 args 매개변수를 가지는데 자세한 설명은 추후.

main()은 method1()을 호출하고, method1() 스택 프레임이 생성됨.

   - method1()은 m1, cal 지역변수(매개변수 포함)를 가지므로 해당 지역 변수들이 스택 프레임에 포함.

method1()은 method2()를 호출하므로 method2() 스택 프레임이 생성.

mtehod2()는 m2 지역변수(매개변수 포함) 가지므로 해당 지역 변수가 스택 프레임에 포함.


 

method2()가 종료됨. 이때 method2() 스택 프레임이 제거되고, 매개변수 m2도 제거됨.

method2() 스택 프레임이 제거되었으므로 프로그램은 method1()로 돌아감. method1()을 처음부터 시작하는 것이 아닌 method1()에서 method2()를 호출한 지점으로 돌아감.

method1()이 종료됨. 이때 method1() 스택 프레임이 제거되고, 지역변수 m1, cal도 제거됨. 프로그램은 main()으로 돌아감.

main()이 종료됨. 더 이상 호출할 메서드 없고, 스택 프레임도 완전히 비워져서 자바는 프로그램을 정리하고 종료함.

 

!정리!

- 자바는 스택 영역을 사용해서 메서드 호출과 지역변수(매개변수 포함)를 관리.

- 메서드 계속 호출하면 스택 프레임이 계속 쌓임.

- 지역변수(매개변수 포함)는 스택 영역에서 관리.

- 스택 프레임이 종료되면 지역 변수도 함께 제거.

- 스택 프레임이 모두 제거되면 프로그램도 종료.


▶ 스택 영역과 힙 영역

package memory;

public class Data {
    private int value;

    public Data(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}
package memory;

import static memory.JavaMemoryMain1.method2;

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");
    }

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

main start
method1 start
method2 start
data.value = 10
method2 end
method1 end
main end

[Data, JavaMemoryMain2]

 

main() → method1() → method2() 순서로 호출하는 단순한 코드. method1()에서 Data 클래스의 인스턴스 생성.

method1()에서 method2()를 호출할 때 매개변수에 Data 인스턴스의 참조값 전달.

 

여기에서 method1()은 지역변수로 Data data1을 가지고 있고, 이 지역 변수는 스택 프레임에 포함됨.

method1()은 new Data(10)을 사용해 힙 영역에 Data 인스턴스 생성하고, 참조값을 data1에 보관함.

method1()은 method2()를 호출하면서 Data data2 매개변수에 참조값을 넘김.

따라서 method1()의 data1과 method2()의 data2 지역 변수(매개변수 포함)는 둘 다 같은 인스턴스를 참조함.

 

!참고!

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

 

!정리!

지역변수는 스택 영역에, 객체(인스턴스)는 힙 영역에 관리되는 것을 확인함. 

이제 나머지 하나는 메서드 영역인데, 메서드 영역이 관리하는 변수도 있음. 이것을 이해하기 위해 static 키워드를 알아야 함.

static 키워드는 메서드 영역과 밀접한 연관이 있음.


▶ static 변수

 

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

 - 생성할 인스턴스 내부에 카운트를 저장

 

package static1;

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

    public Data1(String name) {
        this.name = name;
        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 = " + data1.count);

        Data1 data3 = new Data1("C");
        System.out.println("C count = " + data1.count);
    }
}
실행결과

A count = 1
B count = 1
C count = 1

[Data1, DataCountMain1]

 

생성된 객체의 수를 세기 위한 코드를 작성했다면 기대한 대로 작동하지 않음.

객체 생성 시 Data1 인스턴스는 새로 만들어지면서 인스턴스에 포함된 count 변수도 새로 만들어지기 때문.

인스턴스에 사용되는 멤버변수 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);
    }
}
실행결과

A count = 1
B count = 2
C count = 3

[Counter]

 이 객체를 공유해서 필요할 때마다 카운트 값 증가.

 

[Data2]

 기존 코드 유지하기 위해 새로운 Data2 클래스 생성. 여기에는 count 멤버 변수가 없음.

 대신 생성자에서 Counter 인스턴스를 추가로 전달받음. 생성자 호출되면 counter 인스턴스에 있는 count 변수의 값을 하나 증가.

 

[DataCountMain2]

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

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

 

하지만 여기에는 불편한 점이 있는데 Data2 클래스와 관련해서 Counter라는 별도의 클래스를 추가로 사용해야 함.

생성자의 매개변수도 추가되고, 생성자도 복잡해짐. 생성자를 호출하는 부분도 복잡해짐.

 

▷ static 변수 사용

 

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

package static1;

public class Data3 {

    public String name;
    public static int count;    // static변수(= 정적변수, 클래스변수)

    public Data3(String name) {
        this.name = name;
        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);

    }
}
실행결과

A count = 1
B count = 2
C count = 3

[Data3]

static int count를 보면 변수타입(int) 앞에 static 키워드 붙임. 이 변수는 static 변수, 정적변수, 클래스 변수라고 함.

객체 생성되면 생성자에서 정적변수 count의 값을 하나 증가함.

 

[DataCountMain3]

count 정적 변수에 접근하는 방법이 조금 다름.

인스턴스를 통한 접근이 아닌 클래스를 통한 접근방법으로, 클래스명에 .(dot)을 사용하여 마치 클래스에 직접 접근하는 것처럼 느낌.

 

static이 붙은 멤버 변수 count는 메서드 영역에서 관리하게 되고, 인스턴스 영역에 생성되지 않음.

 

Data3("A") 인스턴스를 생성하면 생성자가 호출되는데, 생성자에는 count++ 코드가 있음.

count는 static이 붙은 정적 변수임.

인스턴스 영역이 아닌 메서드 영역에서 관리가 되며, 메서드 영역에 있는 count의 값이 하나 증가.

Data3("B"), Data3("C")도 마찬가지임.

 

따라서 static 변수를 사용한 덕분에 공용 변수를 사용해서 편리하게 문제 해결 가능.

 

!정리!

static 변수는 쉽게 이야기해서 클래스인 붕어빵 틀이 특별히 관리하는 변수라고 생각하면 됨.

붕어빵 틀은 1개이므로 클래스 변수도 하나만 존재하는 반면에 인스턴스 변수는 붕어빵인 인스턴스의 수만큼 존재함.


▷ 용어정리

package static1;

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에 로딩되는 순간 생성되고, 종료될 때까지 생명주기가 이어지므로 가장 긴 생명주기를 가짐.


▷ 정적변수 접근법

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

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);

        // 추가
        // 인스턴스를 통한 접근 (권장하지 않음)
        Data3 data4 = new Data3("D");
        System.out.println(data4.count);

        // 클래스를 통한 접근
        System.out.println(Data3.count);
    }
}
실행결과

A count = 1
B count = 2
C count = 3
4
4

[DataCountMain3_추가]

 

① 인스턴스 통한 접근 data4.count - 정적 변수의 경우 인스턴스 통한 접근은 추천하지 않음.

 - 그 이유는 코드를 읽을 때 마치 인스턴스 변수에 접근하는 것처럼 오해할 수 있기 때문.

② 클래스를 통한 접근 Data3.count

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

 - 따라서 정적변수에 접근할 때는 클래스를 통해 접근하는 것이 좋음.


▶ static 메서드

 

package static2;

public class DecoUtil1 {

    public String deco(String str) {
        return "*" + str + "*";
    }
}
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);
    }
}
실행결과

before : hello java
after : *hello java*

[DecoUtil1, DecoMain1]

 

특정 문자열을 꾸며주는 간단한 기능을 가진 코드.

"hello"라는 문자열 뒤에 *을 붙여서 "*hello*"와 같이 꾸며주는 기능.

 

deco() 메서드는 문자열을 꾸미는 기능을 제공.문자열이 들어오면 앞 뒤에 *을 붙여서 반환.

이 메서드를 사용하려면 DecoUtil1의 인스턴스를 먼저 생성 후 사용해야 하지만 deco() 메서드는 멤버 변수도 없고 단순히 기능만 제공.

인스턴스가 필요한 이유는 멤버변수(인스턴스 변수)등을 사용하는 목적이 크지만 이 메서드는 그렇지 않음.


package static2;

public class DecoUtil2 {

    /*
    DecoUtil1과 다르게 static이 붙음.
    정적메소드(=클래스메소드)이고, 정적변수처럼 인스턴스 생성없이 클래스명을 통해 바로 호출 가능.
     <-> 인스턴스메소드 : static이 붙지 않은 메소드. 인스턴스 생성해야 호출 가능.
     */

    public static String deco(String str) {

        return "*" + str + "*";
    }
}
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);
    }
}
실행결과

before : hello java
after : *hello java*

[DecoUtirl2, DecoMain2]

 

DecoUtil1과 비슷하지만 메서드 앞에 static이 붙음.

이렇게 하면 정적 메서드 생성 가능하고, 정적 변수처럼 인스턴스 생성 없이 클래스 명을 통해 바로 호출 가능.

String deco = DecoUtil2.deco(s);를 보면 클래스명 + .(dot) + 메서드명으로 바로 호출한 것을 볼 수 있음.

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

 

▷ 클래스 메서드

- 메서드 앞에도 static을 붙일 수 있음.

- 정적 메서드 또는 클래스 메서드라고 함.

 

▷ 인스턴스 메서드

- static이 붙지 않은 메서드.

- 인스턴스 생성해야 호출 가능.

 


▷ 정적 메서드 사용법

- static 메서드는 static만 사용 가능.

     - 클래스 내부의 기능 사용 시, 정적 메서드는 static이 붙은 정적 메서드나 정적 변수만 사용 가능.

     - 클래스 내부의 기능 사용 시, 정적 메서드는 인스턴스 변수나, 인스턴스 메서드 사용 불가능.

- 반대로 모든 곳에서 static 호출 가능.

     - 정적 메서드는 공용 기능.

     - 접근제어자만 허락하면 클래스 통해 모든 곳에서 static 호출 가능.

 

package static2;

public class DecoData {

    private int instanceValue;
    private static int staticValue;

    public static void staticCall() {

        /*
        참조값을 알 수 없기 때문에 불가능.
         */

        //instanceValue++;    // 인스턴스 변수 접근, compile error
        //instanceMethod();   // 인스턴스 메서드 접근, compile error

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

    public static void staticCall(DecoData data) {

        /*
        참조값을 알 수 있기 때문에 가능.
         */

        data.instanceValue++;
        data.instanceMethod();
    }

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

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

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

    private static void staticMethod() {
        System.out.println("staticValue = " + staticValue);
    }
}
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();
    }
}
실행결과

1. 정적 호출
staticValue = 1
2, 인스턴스 호출1
instanceValue = 1
staticValue = 2
3. 인스턴스 호출2
instanceValue = 1
staticValue = 3

[DecoData, DecoDataMain]

 

instanceValue는 인스턴스 변수.

staticValue는 정적 변수(클래스변수).

instanceMethod()는 인스턴스 메서드.

staticMethod()는 정적메서드(클래스메서드)

 

○ staticCall()

- 정적 메서드로, static만 사용 가능하므로 인스턴스 변수나 인스턴스 메서드에 접근하면 컴파일 오류 발생.

 

○ instanceCall()

- 인스턴스 메서드로, 모든 곳에서 공용인 static 호출 가능하여 정적변수, 정적메서드, 인스턴스변수, 인스턴스메서드 접근 가능.

 

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

정적 메서드는 클래스의 이름 통해 바로 호출 가능. 그래서 인스턴스처럼 참조값의 개념이 없음.

특정 인스턴스의 기능을 사용하려면 참조값을 알아야 하는데, 정적메서드는 그렇지 않으므로 인스턴스 변수나 인스턴스 메서드 사용 불가능.

 

public static void staticCall(DecoData data) {

    data.instanceValue++;
    data.instanceMethod();
}

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


▷ 용어정리

 

● 종류 ●

① 인스턴스 메서드 : static이 붙지 않은 멤버 메서드

② 클래스 메서드 : static이 붙은 멤버 메서드로, 클래스메서드, 정적메서드, static메서드 등으로 불림.

 

▷ 정적 메서드 접근 법

static 메서드는 static 변수와 마찬가지로 클래스 통해 바로 접근 가능. 인스턴스 통해 접근도 가능.

[DecoDataMain 추가]

// 추가
// 인스턴스를 통한 접근 (권장하지 않음)
DecoData data3 = new DecoData();
data3.staticCall();

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

▷ static import

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

DecoData.staticCall();
DecoData.staticCall();
DecoData.staticCall();
DecoData.staticCall();

이 기능 사용하면 다음과 같이 클래스명 생략하고 메서드 호출 가능

staticCall();
staticCall();
staticCall();
staticCall();
package static2;

//import static static2.DecoData.staticCall;
import static static2.DecoData.*;

public class DecoDataMain {
    public static void main(String[] args) {

        System.out.println("1. 정적 호출");
        staticCall();
        staticCall();
        staticCall();
        staticCall();
        // DecoData.staticCall();   // 이렇게 작성 가능하지만 여러번 호출 할 시 import 사용하는 것이 편리함.

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

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

        staticCall(data1);

        // 추가
        // 인스턴스를 통한 접근 (권장하지 않음)
        DecoData data3 = new DecoData();
        data3.staticCall();

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

    }
}

[DecoDataMain_static import 적용]

 

특정 클래스의 정적 메서드 하나만 적용하려면 다음과 같이 생략할 메서드 명을 적어주면 됨.

import static static2.DecoData.staticCall;

 

특정 클래스의 모든 정적 메서드에 적용하려면 다음과 같이 *을 사용하면 됨.

import static static2.DecoData.*;

 

참고로 import static은 정적 메서드뿐만 아니라 정적 변수에도 사용 가능.


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

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

main() 메서드는 프로그램을 시작하는 시작점이 되는데, 생각해 보면 객체 생성하지 않아도 main() 메서드가 작동함.

그 이유는 main() 메서드가 static이기 때문.

정적 메서드는 정적 메서드만 호출 가능한데, 정적 메서드인 main()이 호출하는 메서드에는 정적 메서드를 사용함.

더 정확히 말하자면 정적 메서드는 같은 클래스 내부에서 정적 메서드만 호출할 수 있으므로 정적 메서드인 main() 메서드가 같은 클래스에서 호출하는 메서드도 정적 메서드로 선언해서 사용함.

public class ValueDataMain {
	public static void main(String[] args) {
    	ValueData valueData = new ValueData();
        add(valueData);
    }
    
    static void add(ValueData valueData) {
    	valueData.value++;
        System.out.println("숫자 증가 value = " + valueData.value);
    }
}

1-1. 자바 메모리 구조와 static 문제

문제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 메서드
    }
}
실행결과

차량 구입, 이름 : K3
차량 구입, 이름 : G80
차량 구입, 이름 : Model Y
구매한 차량 수 : 3
package static2.ex;

public class Car {

    private String model;
    private static int totalCars;

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


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

문제 2 : 수학 유틸리티 클래스

 

다음 기능을 제공하는 배열용 수학 유틸리티 클래스(MathArrayUtils) 생성

- sum(int[] values) : 배열의 모든 요소 더하여 합계 반환.

- average(int[] values) : 배열의 모든 요소의 평균값 계산.

- min(int[] values) : 배열에서 최솟값 찾기.

- max(int[] values) : 배열에서 최댓값 찾기.

 

요구사항

- MathArrayUtils는 객체 생성하지 않고 사용. 누군가 실수로 MathArrayUtils의 인스턴스를 생성하지 못하게 하기.

- 실행코드에 static import 사용해도 됨.

 

package static2.ex;

import static static2.ex.MathArrayUtils.*;

public class MathArrayUtilsMain {
    public static void main(String[] args) {

        int[] values = {5, 4, 3, 2, 1};

        System.out.println("sum = " + sum(values));
        System.out.println("average = " + average(values));
        System.out.println("min = " + min(values));
        System.out.println("max = " + max(values));
    }
}
실행결과

sum = 15
average = 3.0
min = 1
max = 5
package static2.ex;

public class MathArrayUtils {

    private MathArrayUtils() {
        // private 인스턴스 생성을 막음.
    }

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

    public static double average(int[] values) {
        return (double) sum(values) / values.length;
    }

    public static int min(int[] values) {
        int minValue = values[0];

        for (int value : values) {
            if (value < minValue) {
                minValue = value;
            }
        }
        return minValue;
    }

    public static int max(int[] values) {
        int maxValue = values[0];

        for(int i = 0; i < values.length; i++) {
            if(values[i] > maxValue) {
                maxValue = values[i];
            }
        }
        return maxValue;
    }
}

'Studying > JAVA' 카테고리의 다른 글

[JAVA]_Inheritance(상속)  (0) 2025.02.24
[JAVA]_final  (0) 2025.02.18
[JAVA]_Access Modifier(접근제어자)  (0) 2025.01.31
[JAVA]_Package(패키지)  (0) 2025.01.31
[JAVA]_Constructor(생성자)  (0) 2025.01.24