DEV/Java

[java 기초] 기타 제어자(static, final, abstract)의 이해와 구현

Bi3a 2023. 10. 5. 03:36

728x90

java 기초 깨부시기

 

 

Table of Contents

     

    주인장 학습용 포스팅입니다. 열람에 참고해주세요

     

    [java 기초] java final, static + private

    1. final 접근제한자 final : final로 선언된 클래스, 메소드, 변수는 수정 불가능하게끔 그 값과 의미를 고정하는 데 그 목적이 있다. - final int 로 선언된 변수 : 최초 값이 초기화되면 그 이후에 값 변

    doinitright.tistory.com

    이전 글이 설명이 미흡하여 보충 작성합니다(^^&)

     

    이전 포스팅 :: 접근 제어자의 이해와 구현 예제(Access Modifier) 에서 이어집니다.

     

    [java 기초] 접근 제어자의 이해와 구현 예제(Access Modifier)

    Table of Contents 접근 제어자의 이해 접근 제어자란? 선언한 클래스와 클래스의 멤버(변수와 메소드 등)에 대해 외부에서 접근할 수 있는 범위를 제어하는 키워드입니다. 객체 지향에서의 정보 은

    doinitright.tistory.com

     


     

    기타 제어자

    • 기타 제어자도 접근 제어자와 동일하게  메소드, 클래스, 변수 선언부의 앞에 붙여 사용합니다.
    • 외부로의 정보 은닉 및 접근 권한을 제어하는데 사용됩니다.
    • 종류로는 3가지가 있습니다. (static, final, abstract)
    • 접근 제어자와는 달리 기타 제어자는 상황에 맞게 여러개를 함께 사용할 수 있습니다.

     

    static

    static 제어자는 '공통적인' 의미로 사용되며, 해당 클래스의 모든 객체가 값을 공유한다는 특징이 있습니다.
    static 제어자는 객체 단위의 변수가 아닌 클래스 공통의 속성을 제어하고 규정하기 위해 사용합니다.

     

    static의 특징과 구현

    • 클래스를 제외한 메소드, 필드, 초기화 블록에 선언이 가능합니다.
      • 클래스는 중첩 클래스로 사용되는 경우(클래스 내부의 클래스로 선언 시) 사용 가능합니다. (외부 클래스는 X)
    static class a { } // 컴파일 에러 : 외부 클래스로는 static 선언 불가능
    
    public class ectModifier {
        static class b { // 내부 클래스(nested class)로는 static 선언 가능
     		// ...   
        }
    }
    public class ectModifier {
        /** 정적 전역 변수 : 다른 클래스에서도 호출 가능 **/
        static int global1;
        static int global2;
        static { // 정적 변수 초기화 블록
            global1 = 3; // == static int global1 = 3;
            global2 = 2; // == static int global2 = 2;
        }
    }
    • static 제어자를 변수나 메소드에 사용 시 객체의 생성 없이 클래스로 호출하여 사용할 수 있습니다.
      • 따라서 해당 클래스의 가 아닌, '클래스명.메소드', '클래스.변수'로 호출합니다.
      • static 필드의 값은 클래스로 호출하던 해당 클래스의 인스턴스로 호출하던 그 값을 공유합니다.

     

    • non-static은 static을 호출할 수 있습니다. (객체, this로 호출하는 것은 권장하지 않음)
    public class ectModifier {
        static class B {
            static int value = 1; // static 변수
            static void printValue() { // static 메소드
                System.out.println(value);
            }
        }
        public static void main(String[] args) {
            B b1 = new B();
            B b2 = new B();
            b1.printValue(); // out : 1
            b1.value = 2; // 객체를 통한 static 변수 접근 및 값 변경(권장하지 않는 방법)
            b2.printValue(); // out : 2
            b2.value = 3; // 객체를 통한 static 변수 접근 및 값 변경(권장하지 않는 방법)
            B.printValue(); // out : 3 
            B.value = 4; // 클래스를 통한 static 변수 접근 및 값 변경
            B.printValue(); // out : 4
        }
    }

    B라는 static 클래스 내 static 필드인 value와 그 값을 출력하는 static 메소드를 구현했습니다.

    main에서는 static value에 접근하는 방식을 두 가지로 나누어서 실행하고 있습니다.

       1. 클래스 B의 객체 b1, b2 생성 후 static value 값을 참조해 접근하는 방법(권장하지 않음)

       2. 클래스를 통한 접근 방법

    1번의 방법으로도 static으로 선언된 멤버에 접근이 가능하지만, 제어자의 의도와 맞지 않으므로 지양하는게 좋습니다.

    (static의 의도 : 객체 단위의 변수 제어보다는 클래스 공통의 변수 제어)

     

    • static 변수의 메모리는 데이터 영역에 적재되며, 프로그램 시작과 동시에 할당되어 종료 시에 해제됩니다.
      • 데이터 영역은 Garbage Collector의 영역 외부이므로 static의 남발은 시스템 퍼포먼스를 하락시킬 수 있습니다.
        • Garbage Collection  : 사용하지 않는 변수와 인스턴스를 정리해주는 JVM의 기능입니다.

     

    * 참고 : static 변수의 메모리 할당 위치(데이터 영역)

     

     

    • static으로 선언된 메소드는 static 변수와 static 메소드만 참조 가능합니다.
      • 이유 : 메모리를 할당받는 시점이 다르기 때문
      • static 메소드 :  프로그램 시작과 동시에 메모리를 할당
      • non-static 메소드 : 객체 생성 후 new 연산을 통해 메모리를 할당 받은 뒤 사용
    public class ectModifier {
        static class B {
            int nonStatic = 0; // non-static 변수
            static void printnonStatic(){ // static 메소드
                System.out.println(nonStatic); // 컴파일 에러 : 
                // non-static field cannot be referenced from a static context
            }
        }

    non-static 변수인 nonStatic을 호출하는 static 메소드 printnonStatic를 구현 시 컴파일 에러가 발생합니다.

     

    public class ectModifier {
        /** static main에서는 non-static 클래스 생성 불가 **/
            // 이유 : non-static 클래스 C는 static main에서 직접 생성 불가
            // non-static 클래스 C의 객체 생성은 ectModifier의 클래스 수준에서 호출과 시점이 무관함
        class C {
            int value = 0;
        }
        public static void main(String[] args) {
        	C c1 = new C(); // 컴파일 에러
            // non-static variable this cannot be referenced from a static context
        }
    }

    동일하게 static main의 경우에도 내부 클래스로 구현되어 있는 C의 객체를 main에서 참조하고 구현할 수 없습니다.

     

    [참고] non-static으로 선언된 내부 클래스의 객체를 static main에서 생성하는 법  : 외부 클래스의 객체를 통한 생성

    public class ectModifier {
        class C {
            int value = 0;
        }
    
        public static void main(String[] args) {
            /** 참고 : non-static 클래스의 객체를 static 메소드에서 생성하는 법 **/
            ectModifier outerObj = new ectModifier(); // 외부 클래스의 객체 생성
            C c1 = outerObj.new C(); // 외부 클래스 객체 생성을 통한 클래스 C의 객체 생성
        }
    }

     

    Q. main 메소드가 static으로 정의되어야 하는 이유?

    외부 클래스에서 접근 가능하도록 선언이 되어야 되기 때문입니다. (static, 즉 정적인 메소드 특성)
    + 반면, 가장 외부의 클래스는 객체를 정의하기 위해 사용되어야 되기 때문에 non-static으로 선언되어야 합니다.

     

    static, non-static의 일반적인 쓰임새

    static은 주로 구현할 객체가 공통적으로 공유할 특성이나 유틸리티 메소드, 상수 값을 구현하는 데 사용합니다. 
    non-static은 객체 개별의 특성과 상태, 그리고 변수를 사용하고 변경이 잦은 필드인 경우 사용합니다.
    /**
     * static과 non-static을 활용한 개발 방법
     * static : 클래스의 공통적인 특성, 주로 유틸리티 클래스 혹은 상수 값의 클래스
        * 따라서 static은 다형성을 지원하지 않으므로 메소드 오버라이딩이 불가능
     * non-static : 객체 고유의 특성, 객체 상태나 인스턴스 변수를 사용하는 작업
     */
    class Human{
        static String species = "H.SAPIENS"; // 공통적인 특성 : 인류의 종족명
        static void breathe(){ // 공통적인 행위 : 숨쉬기
            System.out.println("사람은 숨을 쉽니다");
        }
        String name;
        int age;
        int height;
        int weight;
        int bloodType;
    }
    
    class Person extends Human{
        static void breathe(){ // 오버라이딩 X, static hiding으로 새로 추가, 별개의 메소드
            System.out.println("나는 독특하게 숨을 쉽니다");
        };
    }

     

    • 추가적으로 static은 정적인 쓰임새를 가지므로 다형성에 있어 메소드 오버라이딩을 지원하지 않습니다.

    * static 메소드 없는 경우 : 메소드 오버라이딩 적용

    public class ectModifier {
        // .. 생략
        public static void main(String[] args) {
        Person person = new Person();
        Human human = person;
        person.breathe(); // out : 나는 독특하게 숨을 쉽니다
        human.breathe(); // out : 나는 독특하게 숨을 쉽니다(오버라이딩 적용)
    }
    class Human{
        void breathe(){
            System.out.println("사람은 숨을 쉽니다");
        }
    }
    class Person extends Human{
        @Override
        void breathe(){
            System.out.println("나는 독특하게 숨을 쉽니다");
        };
    }

    *static 메소드 사용하는 경우 : 메소드 오버라이딩 적용 불가

    public class ectModifier {
        // .. 생략
        
        public static void main(String[] args) {
        // 정적 메소드는 오버라이딩이 불가능하다.
        Person person = new Person();
        Human human = person;
        person.breathe(); // out : 나는 독특하게 숨을 쉽니다
        human.breathe(); // out : 사람은 숨을 쉽니다
    }
    class Human{
        static void breathe(){
            System.out.println("사람은 숨을 쉽니다");
        }
    }
    class Person extends Human{
        // @Override : error, Static methods cannot be annotated with @Override
        static void breathe(){ // 오버라이딩 X, static hiding으로 새로 추가된 별개의 메소드
            System.out.println("나는 독특하게 숨을 쉽니다");
        };
    }

     

    • java 클래스에서 기본적으로 지원하는 자료구조의 메소드에도 많은 static 변수, 메소드들이 포함되어 있습니다.
    import java.util.*;
    
    public static void main(String[] args){
    	int a = Integer.parseInt("2");
    	int b = Integer.MIN_VALUE; // -2147483648
    	int[] arr = {1, 2, 6, 4, 3}
        Arrays.sort(arr); 
        System.out.println(Arrays.toString(arr)); // out : 1, 2, 3, 4, 6
        
        // 기타 등등
    }

     

     

    final

    final 제어자는 변경할 수 없다는 의미로 사용됩니다.

     

    final의 특징과 구현

    • final은 클래스, 메소드, 필드, 지역 변수에 사용할 수 있습니다.
      • 클래스에 사용 시 해당 클래스는 다른 클래스가 상속받을 수 없습니다.
      • 메소드에 사용 시 해당 메소드는 오버라이딩이 불가능합니다.
      • 필드, 지역 변수에 사용 시 변경할 수 없는 상수가 됩니다.

    클래스 예제

    public class ectModifierTest {
    	
        // .. 생략
    
    	public static void main(String[] args){
        
     	F f = new F();
        // 1. final로 선언된 변수는 값을 변경할 수 없다.
        f.fInt = 4; // 컴파일 에러
     }
     
     final class F { // final 클래스 : 상속 불가
        final int fInt = 0; // final 변수/필드 : 값을 변경할 수 없는 상수로 초기화
        final void finalMethod(){ // final 메소드 : 오버라이딩을 통한 재정의 불가
            System.out.println("i am final method");
        }
    }
    // 2. final 클래스는 상속 불가능하다.
    final class FSon extends F{} // 컴파일 에러

     

    final의 쓰임새

    • 변수에 사용 시 첫 초기화 이후 재할당이 불가능하므로 상수로서 많이 활용됩니다.
      • 전역 변수로 사용 시에는 주로 static 제어자와 함께 사용되며, 변수명은 대문자, 언더바를 활용해 구성합니다.
    public class ectModifierTest {
    	public static final int FINAL_NUM = 7;
        public static final String FINAL_STR = "HELLO!";
        
        // .. 생략
        
        }
    }

     

     

    • 메소드의 입력 인자값의 제어자를 final을 선언 시 메소드 내부에서 인자값의 변경을 방지할 수 있습니다.
    final class F {
        // 인자값 final로 선언하여 메소드 내부에서 인자값 변경 방지
        public int sum(final int a, final int b){
            a = 3; // 컴파일 에러 
            return a + b;
        }
    }

     

    abstract

    abstract 제어자는 추상적인 메소드를 정의, 제어하는 용도로 사용합니다.

     

    abstract의 특징

    • 클래스와 메소드에만 선언 가능한 제어자입니다.
      • abstract 메소드는 추상 클래스인터페이스에 사용됩니다.
        • 하나 이상의 추상 메소드를 가지고 있는 클래스를 추상 클래스라 하며, 선언부에 abstract를 붙입니다.
        • 하나 이상의 추상 메소드를 가지고 있는 추상 자료형을 인터페이스라 합니다. (이후 포스팅에 후술)
        • abstract 메소드는 선언부만 있고 구현부가 없습니다.

     

    abstract(추상) 메소드와 클래스의 이해, 구현

    • abstract 메소드는 자식 클래스에서 반드시 오버라이딩해만 사용할 수 있는 메소드입니다.
      • 추상 메소드를 포함한 클래스는 추상 클래스가 되며, 이러한 클래스를 상속받는 자식 클래스는 반드시 부모 클래스에 있는 모든 추상 메소드를 오버라이딩하여 구현해야만 합니다.
    • 추상 클래스는 객체를 생성할 수 없습니다.
    public class ectModifierTest {
    	
        // .. 생략
    
    	public static void main(String[] args){
        
            /* abstract 클래스와 abstract의 자식 클래스 구현 */
            Eatable eatable = new Eatable(); // 컴파일 에러
            // abstract 클래스는 객체 생성이 불가능
            
            Burger burger = new Burger();
            burger.eat(); // out : "버거를 먹습니다"
            burger.smell(); // out : "버거 냄새가 좋습니다"
     }
    
    /* 먹을 수 있는 것을 정의하는 abstract 클래스 */
    abstract class Eatable{
        abstract void eat(); // abstract 메소드 : 구현부 없음
        abstract void smell(); // abstract 메소드 : 구현부 없음
    }
    
    /* abstract 클래스를 상속받은 클래스는 abstract 메소드를 모두 오버라이딩하여 구현해야 한다 */
    class Burger extends Eatable {
        @Override
        public void eat(){
            System.out.println("버거를 먹습니다");
        }
        public void smell(){
            System.out.println("버거 냄새가 좋습니다");
        }
    }

     

    Q. abstract 메소드의 사용 목적?

    1. 추상 메소드를 포함한 클래스의 자식 클래스가 구현하지 않은 추상 메소드의 구현을 강제하기 위함입니다.
    -> 부모 클래스의 추상 메소드의 구현 강제성은 미리 정의된 공통 기능을 구현할 수 있게끔 개발 지침을 제공합니다.
     
    2. 추상 메소드를 미완성으로 남김으로써 추상 클래스를 상속받는 자식 클래스의 메소드 구현에 유동성을 부여합니다.
    -> 추상 클래스 상속에 따라 추상 메소드와 같이 클래스에 반드시 필요한 부분을 구현하게끔 규정을 함으로써 오류를 줄이고, 미완성으로 남긴 메소드는 자식 클래스 별도로 구현을 통해 추가 기능을 확장시켜 나갈수 있습니다.

     

     

    # 구현 코드

    클릭시 깃허브로 이동합니다.