프로그래밍 언어/Java

[Java] 접근 제어자와 기타 제어자

happy_life 2022. 6. 24. 00:55

목차

1. 제어자란?

2. 기타 제어자

 2.1 static

 2.2 final

2.3 abstract

3. 접근 제어자

3.1 개요

 

 

1. 제어자란?

제어자란 클래스와 클래스 맴버, 메서드 선언부에 함께 사용되어 부가적인 의미를 부여하는 키워드를 의미한다. 제어자는 크게 접근 제어자와 기타 제어자로 구분할 수 있다. 기타 제어자는 여러 개를 함께 사용할 수도 있지만, 접근 제어자는 4개 중 단 하나만을 사용해야 한다. 이런 접근 제어자와 기타 제어자는 함께 사용하기도 한다.

접근 제어자 public, protected, default, private
기타 제어자 static, final, abstract, native, transient, synchronized, volatile, strictfp

 

2. 기타 제어자

2.1 static

static이 붙으면 클래스와 관계된 것이다. 

인스턴스 변수는 인스턴스마다 각기 다른 값을 갖지만, static이 붙은 클래스 변수는 인스턴스 모두가 공유한다.

인스턴스 메서드는 인스턴스 맴버들을 사용하지만, static 메서드에서는 인스턴스 맴버를 사용할 수 없다.

 

코드예제

static 메서드 add 안에서 인스턴스 변수를 참조하려고 하니 빨갛게 오류가 난다.

또한 main(String[] args)를 보면 알 수 있듯 인스턴스와는 상관없는 값(100, 50)을 출력하는 메서드이다.

 

 

한편, static이 붙은 맴버변수와 메서드, 초기화 블럭은 인스턴스가 아닌 클래스에 관계된 것이기 때문에 인스턴스를 생성하지 않고도 사용할 수 있다.

 

코드예제

public class StaticEx {
    public static void main(String[] args) {
        Static.method(50);// 객체를 생성하지 않고 바로 사용
    }
}

class Static {
    static void method(int x) {
        System.out.println(x);
    }
}

 

 

 

2.2 final

final 제어자를 변수에 사용하면 값을 변경할 수 없는 상수가 된다.  메서드에 사용하게 되면 오버라이딩을 할 수 없게 되고 클래스에 사용하면 자식 클래스를 정의하지 못하게 된다.

제어자 대상 의미
final 클래스 변경할 수 없는 클래스가 된다. 자식 클래스를 생성할 수 없다.
메서드 오버라이딩으로 재정의될 수 없다.
멤버 변수 값을 변경할 수 없는 상수가 된다.
지역 변수

 

코드 예제

변경하려고 하면 빨간줄로 에러가 뜬다.

 

예외적으로 final 멤버 변수를 변경할 수 있는 경우

final이 붙은 변수는 상수이므로 일반적으로 선언과 동시에 초기화를 하지만, 인스턴스 변수의 경우 생성자로 딱 1번 초기화될 수 있도록 할 수 있다.

코드예제

public class InherentEx8 {
    public static void main(String[] args) {
        Card card = new Card(1);
        Card card2 = new Card(2);
    }
}

class Card {
    final int id;

    Card(int id) {
        this.id = id; // 한번 초기화 가능
    }
}

 

 

2.3 abstract

메서드의 선언부만 작성하고 실제 수행내용은 구현하지 않은 추상 메서드를 선언할 때 사용되는 기타 제어자이다. 이 메서드는 자체로는 쓸모가 없고, 다른 클래스가 이 클래스를 상속받아 이를 오버라이딩하여 사용하게 된다.

 

코드예제

public class InherentEx9 {
    public static void main(String[] args) {
//        Animals animals = new Animals(); // 추상 클래스는 인스턴스 생성 불가
        Cat cat = new Cat();
        cat.cry();
        //야옹야옹

        Dog dog = new Dog();
        dog.cry();
        //멍멍

    }
}

abstract class Animals {
    abstract void cry();
}
class Cat extends Animals{
    void cry() {
        System.out.println("야옹야옹");
    }
}
class Dog extends Animals{
    void cry() {
        System.out.println("멍멍");
    }
}

 

 

3. 접근 제어자

3.1 개요

접근 제어자는 멤버 또는 메서드에 사용되어 해당 멤버 또는 메서드를 외부에서 접근하지 못하도록 제어하는 역할을 한다. 외부에서 수정하지 말아야하는 코드가 있기 존재할 수 있기 때문에 접근 범위에 따라  4가지로 접근 제어자를 구분하게 된다. (default는 아무런 제어자도 붙이지 않는 경우에 해당한다.)

접근 위치 public protected default private
동일 패키지 내 접근 같은 클래스에서의 접근 O O O O
상속 클래스에서의 접근 O O O X
다른 클래스에서의 접근 O O O X
다른 패키지 내 접근 일반 클래스에서의 접근 O X X X
상속 클래스에서의 접근 O O X X

 

접근 범위

public > protected > default > private

 

동일 패키지 내 코드 예제

package1.Modifier.class

public class Modifier {
    // 멤버 변수
    public String publicMember = "public 멤버 변수";
    protected String protectedMember = "protected 멤버 변수";
    String defaultMember = "default 멤버 변수";
    private String privateMember = "private 멤버 변수";


    // 동일 패키지 내 같은 클래스에서 모두 접근 가능
    public static void main(String[] args) {
        Modifier modifier = new Modifier();
        // 멤버 변수
        System.out.println(modifier.publicMember);
        System.out.println(modifier.protectedMember);
        System.out.println(modifier.defaultMember);
        System.out.println(modifier.privateMember);

    }
}
// 동일 패키지 내 상속 클래스에서 접근
class ChildModifier extends Modifier {
    public static void main(String[] args) {
       Modifier modifier = new Modifier();
        // 멤버 변수
        System.out.println(modifier.publicMember);
        System.out.println(modifier.protectedMember);
        System.out.println(modifier.defaultMember);
//        System.out.println(modifier.privateMember);

    }
}

// 동일 패키지 내 다른 클래스에서 접근
class AnotherClass {
    public static void main(String[] args) {
        Modifier modifier = new Modifier();
        // 멤버 변수
        System.out.println(modifier.publicMember);
        System.out.println(modifier.protectedMember);
        System.out.println(modifier.defaultMember);
//        System.out.println(modifier.privateMember);
    }
}
 

 

다른 패키지 내 코드 예제

package2.AnotherPackageClass.class

// 다른 패키지의 다른 클래스에서의 접근
public class AnotherPackageClass {
    public static void main(String[] args) {
    	// 다른 패키지에서의 접근
        Modifier modifier = new Modifier();
        // 멤버 변수
        System.out.println(modifier.publicMember);
//        System.out.println(modifier.protectedMember);
//        System.out.println(modifier.defaultMember);
//        System.out.println(modifier.privateMember);

    }
}

class ChildInAnotherPackageClass extends Modifier {

    public static void main(String[] args) {
	// 다른 패키지의 자식 클래스에서의 접근
        ChildInAnotherPackageClass childInAnotherPackageClass = new ChildInAnotherPackageClass();
        // 멤버 변수
        System.out.println(childInAnotherPackageClass.publicMember);
        System.out.println(childInAnotherPackageClass.protectedMember);


        /**
         * modifier는 다른 패키지에서의 상속받은 클래스가 아니므로 접근 불가능하다.
         */
        Modifier modifier = new Modifier();
//        System.out.println(modifier.protectdMember);
    }
}

정확히는 다른 패키지자식 클래스 내부에서만 protected를 참조할 수 있다.

 

 

 

 

접근 제어자가 필요한 이유

아래의 예시 코드를 보며 접근 제어자가 필요한 이유를 이해해보자.

 

코드예제

Time.class

public class Time {
    // 멤버 변수에 public 접근 제어자가 있어 다른 어떤 곳에서든 접근이 가능함.
    public int hour;
    public int minute;
}

InherentEx10.class

public class InherentEx10 {
    public static void main(String[] args) {
        Time time = new Time();
        time.hour = 100; // 24시간까지인데 누군가 100을 넣어버린다면?
    }
}

hour은 24시까지인데 누군가 100이라는 수를 입력해버릴 수도 있다. 따라서 이런 경우 아래와 같이 코드를 바꿔 외부에서의 접근을 차단해야 한다.

 

수정후 코드 예제

Time.class

public class Time {
    // 멤버 변수에 public 접근 제어자가 있어 다른 어떤 곳에서든 접근이 가능함.
    private int hour;
    private int minute;

    //메서드는 외부에서 값을 넣을 수 있도록 열어둔다
    public void setHour(int hour) {
        if (!(0 <= hour && hour <= 24)) {
            System.out.println("0에서 24범위의 수만 입력해주세요");
            return;
        }
        this.hour = hour;
    }
    public void setMinute(int minute) {
        if (!(0 <= minute && minute <= 60)) {
            System.out.println("0에서 60범위의 수만 입력해주세요");
            return;
        }
        this.minute = minute;
    }

    public int getHour() {
        return hour;
    }

    public int getMinute() {
        return minute;
    }
}

private으로 설정해 멤버에 직접 접근할 수 없게 하고

public으로 메서드를 열어 값을 넣고 얻을 수 있게 코드를 작성하였다.

if 문을 사용하여 원하는 범위가 아닐 때 값을 넣지 않고 return해주는 코드를 작성하여 기존의 문제를 해결할 수 있게 되었다.

 

InherentEx10.class

public class InherentEx10 {
    public static void main(String[] args) {
        Time time = new Time();
        // 메서드로 우회해서 값을 넣거나 얻어올 수 있다.
        time.setHour(25);
        //0에서 24범위의 수만 입력해주세요
        time.setHour(24); // public이므로 메서드는 사용 가능 - 간접적으로 값 넣기
        System.out.println("time.hour = " + time.getHour()); // public 메서드로 값 꺼내오기
        //time.hour = 24

        time.setMinute(70);
        //0에서 60범위의 수만 입력해주세요
        time.setMinute(55);
        System.out.println("time.getMinute() = " + time.getMinute());
        //time.getMinute() = 55
    }
}

메서드로 우회해서 값을 넣거나 가져올 수 있다.

 

 

3.2 주의사항

1. 메서드에 static과 abstract를 동시에 사용할 수 없다. static 메서드는 몸통이 있는 메서드에만 사용할 수 있기 때문이다.

 

2. 클래스에 abstract와 final을 동시에 사용할 수 없다. 클래스에 final은 상속할 수 없다는 뜻이고 abstract는 상속을 한다는 뜻이기 때문이다.

 

3. abstract메서드의 접근 제어자가 private일 수 없다. abstract는 자식 클래스에서 구현해야 하는데 private 제어자는 상속클래스에서 접근할 수 없기 때문이다.

 

4. 메서드에 private과 final을 같이 사용할 필요는 없다. private인 메서드는 오버라이딩이 불가능하기 때문에 어차피 변경할 수 없다는 뜻을 가지고 있다.