DEV/Java

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

Bi3a 2023. 10. 5. 03:36

반응형

java 기타 제어자의 이해와 구현
java 기초 깨부시기

 

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

 

[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 메소드만 참조 가능합니다.
    • 이유 : 메모리를 할당받는 시점이 다르기 때문
    • 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. 추상 메소드를 미완성으로 남김으로써 추상 클래스를 상속받는 자식 클래스의 메소드 구현에 유동성을 부여합니다.
-> 추상 클래스 상속에 따라 추상 메서드와 같이 클래스에 반드시 필요한 부분을 구현하게끔 규정을 함으로써 오류를 줄이고, 미완성으로 남긴 메서드는 자식 클래스 별도로 구현을 통해 추가 기능을 확장시켜 나갈 수 있습니다.

 

 

# 구현 코드

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

반응형