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의 기능입니다.
- 데이터 영역은 Garbage Collector의 영역 외부이므로 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(추상) 메소드와 클래스의 이해, 구현
- 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. 추상 메소드를 미완성으로 남김으로써 추상 클래스를 상속받는 자식 클래스의 메소드 구현에 유동성을 부여합니다.
-> 추상 클래스 상속에 따라 추상 메소드와 같이 클래스에 반드시 필요한 부분을 구현하게끔 규정을 함으로써 오류를 줄이고, 미완성으로 남긴 메소드는 자식 클래스 별도로 구현을 통해 추가 기능을 확장시켜 나갈수 있습니다.
# 구현 코드
'DEV > Java' 카테고리의 다른 글
[java 기초] 중첩 클래스(Nested Class)의 이해와 구현 (1) | 2023.10.09 |
---|---|
[java] 코딩테스트 대비 java 클래스 별 메소드 정리 (2 / 2)(작성중) (0) | 2023.10.05 |
[java 기초] 스트림을 사용하자(1 / 4) - 스트림의 이해와 생성 (0) | 2023.09.21 |
[java 기초] Iterator 활용 ConcurrentModificationException 예외 해결 (0) | 2023.09.01 |
[java] 코딩테스트 대비 java 클래스 별 메소드 정리 (1 / 2) (0) | 2023.08.23 |