Static

김민지·2023년 1월 1일
0

자바

목록 보기
16/21
post-custom-banner

왜 main method는 static?

  • 자바에서는 main메서드를 가장먼저실행해야한다는 규칙이있다.
    그런데 만약에 static을 붙이지 않게 되면
    클래스생성 이후 메서드를 호출하게 된다. 이렇게 되면
    main메서드를 먼저 호출한다는 규칙이 무너지게 된다
    따라서 static으로 선언하여 클래스 생성을 하지 않아도 호출할 수 있게 만들었다

public으로 main메서드를 설정하는 이유?

  • JVM이 메인메서드를 메인메모리에 적재해야해서.. 그러기 위해선
    메인 메서드에 접근할 수 있어야한다. public이여야 접근이 가능해진다
    다른접근자로 하면 컴파일은 성공하는데 메인메서드에 접근 할 수 없다

main메서드를 오버라이딩, 오버로딩할수있을까?

  • 오버로딩: 같은이름, 다른매개변수 -> 가능
  • 오버라이딩 부모의 함수재정의 -> 불가능
    메서드 오버라이딩은 런타임 시점에 결정됨
    하지만 static은 컴파일시점에 결정돼서 오버라이딩이 불가

static method의 오버라이딩 불가

  • 부모클래스에 static이 붙어있으면 자식에서 오버라이딩이 불가하다
  • Parent p = new Child()
    p.child()를 호출한다고 치자
    그러면 Child의 메서드를 호출할것이다
  • 컴파일타임에서 참조타입을 확인했다
  • 런타임에서 JVM이 객체타입에 대한 메서드를 실행시킨다

static: 메모리에 고정적으로 할당하여 프로그램이 끝날때까지 유지
static이 저장되는 영역 - method area -> 컴파일시점에 저장
인스턴스가 저장되는 영역 - heap area -> 런타임에 저장
오버라이딩은 부모에게 정의되어있는것을 재정의하는것이다
즉, 인스턴스에 있는 메서드에 접근하는것이다
부모클래스에서 이미 static으로 만들어버렸다면
자식이 접근은 가능하겠지만, 자식에서 재정의를 할 순 없다
왜지?
왜냐하면 컴파일시점에 이미 결정되어서 런타임에 변경될수없기때문에?

hiding

자식에서도 static을 선언하면 오버라이딩 된것처럼 보인다!

  • 이것은 hiding이라고 한다.
  • new로 생성한 것의 메서드가 호출되는 것이아니라
    참조되는 클래스의 메서드가 호출된다

static class 의미

static class: 상위클래스와의 분리를 해줌
만약 A클래스 하위에 B가 있다고하면
B는 A의 instance를 생성해준 다음에야 사용할 수 있다
static inner class로 선언하게 되면 외부클래스의 인스턴스 생성없이도
외부에서 직접 객체를 생성할 수 있다

질문

Q) static은 모든 객체가 공유하는 변수라는 뜻아닌가요? 그렇다면 static class는 모든 객체가 공유하는 클래스일테니 싱글톤패턴과 같은것을 보장해야하는것아닌가요? 예를들어 매번 new로 static class를 생성한다면 같은 객체를 내어주어야하는것을 보장해야하지않나요?
A) 아닙니다. static의 정의는 메모리에 한번 할당되어 프로그램이 종료될때 해제되는 변수로, new로 생성을 매번하면 매번 인스턴스가 생성이 됩니다. static멤버변수의 경우, 모든 객체가 같은 멤버변수를 공유하는것이 맞으나 static이라는 키워드가 class에 붙을 경우에는 외부(클래스)의 참조 없이 객체를 생성할 수 있는정도의 의미만 가질 뿐 매번 new로 생성하는데, 동일한 객체를 반환해주지 않습니다

개인적인 의문

static에는 왜 static만 넣어야할까?

static안에는 static외에 어떤 인스턴스 변수도 들어오면 안되는건가요?
예를들면 static메서드안에 static이 아닌 클래스 인스턴스의 생성같은거요!
이유는 뭘까요?

static이란건 jvm의 method영역에 저장된다는것을 의미해요
이건 컴파일시점에 결정되죠
그런데 이 안에 인스턴스 변수를 사용한다는것은
런타임시점에 결정되는 무언가가 추가된다는거예요
근데 static은 컴파일시점에 결정되어야하기때문에 런타임에야 알수있는 무언가를 정의할 수 없는거예요

업캐스팅과 다운캐스팅

Parent p = new Child(); //업캐스팅
Child c = (Child)p; //다운캐스팅
  • 업케스팅: 자식클래스의 객체가 부모클래스의 타입으로 형변환되는것
    -> 부모것에만 접근 가능
  • 다운캐스팅: 업캐스팅을 원래대로 되돌리는 것

업캐스팅을 왜쓰는것일까?

  • 예를들어 하나의 부모가 있고 여러 자식이 있다고 가정하자. 근데 만약에 업캐스팅이 없다면 여러자식의 개수만큼 다른 배열을 생성해야한다. 그런데 업캐스팅을 사용하면 부모의 인스턴스배열을 만들어서 자식객체를 담을 수 있다.
    그렇게 하면 가독성이 좋아지고, 코드량이 줄어든다
    그런데 이때 업캐스팅만하면 부모의 것(메서드, 변수 등)만 사용할 수 있다.
    하지만 자식의 것(메서드, 변수)도 써야할 때가 있다. 이를 위해 업캐스팅을 원래대로 되돌리는 다운캐스팅이 필요한것이다.
    하지만 다운캐스팅 사용에는 주의가 필요하다. 다운캐스팅은. 분명 업캐스팅을 원래대로 되돌릴때만 사용가능하다. 다음같은 코드는 안된다.
  • 하지만 컴파일에러가 뜨지 않는다. 이는 런타임에 에러가뜨게 돼서 잡기 어려워진다.
    항상 주의를 해서 사용해야한다

instanceof

  • 이때 사용할 수 있는게 instanceof연산자이다. 이 연산자를 사용하면 다운캐스팅을 할 수 있는지의 유무를 알수있다. a instanceof b
    a가 b인지, b를 상속받았는지의 유무를 알려준다.
  • 참조변수의 인스턴스타입을 확인하며 boolean값을 반환.
public static void main(String[] args) throws IOException {
        Parent p = new Parent();
        Child c = new Child();
        System.out.println(c instanceof Parent);//true
    }

변수 참조

Parent p = new Child();
p: 참조변수, 값을 저장하는 메모리상의 주소를 담는 변수
Child: 실제 뭔가 값

근데 p는 무엇을하는건가요?
p는 Parent의 크기만큼 메모리에서 읽어오는 역할만함 < 이게맞나요..?
왜냐면.. 만약 그 역할만하는거라면 Parent랑 같은 크기의 또다른 클래스타입을
정의해놓고
Test t = new Child();
도 가능할거라는얘기잖아요? 사실은 아닌데..
참조변수 p가 하는일이 뭐죠..?

-> 단순히 p는 주소를 가지는 일만하고, 저게 안되는 이유는 문법에 맞지 않아서임

Child c = new Parent();는 안되는데 위의 문장은 되는 이유를 알고싶어요

  • 사실 부모의 모든것을 자식이 가지고 있어야하니 자식이 더 큰 범위를 가지잖아요?
    그러다보니 Child c로 부모에 대한 메모리공간을 가리킨다는건
    실제 공간보다 많이 가리키게 되고, 그럼 이상한값까지 가리키게 되니 그렇게 가리키지 못하게 하는걸까요?
  • 또한 Child라고 선언해서 child꺼까지 모두 사용할수있을거라고 생각하지만 실제 데이터는 parent이기떄문에 사용할수없음. 이런이유때문에도 못사용하게 컴파일에러내도록 한것같음

왜 접근이 안될까?

  • 참조변수가 가리키는 영역만 접근할 수 있음
  • 저거 접근할려면 child로 다운캐스팅 해야함

static method 오버라이딩이 안되는 이유

  • static을 non-static으로 재정의하고 있다
  • static으로 선언하면 method에 있어야하고 non-static 메서드라면 힙영역에 있어야 할것이다
    재정의라는것은 같은 영역에 있어야 재정의를 할 수 있다.
static class Parent {
    public static void getData() {
        System.out.println("부모 getData");
    }
 
    public void method() {
        System.out.println("부모 method");
    }
}
static class Child extends Parent {
    public static  void getData() {
        System.out.println("자식 getData");
    }
 
    public void method() {
        System.out.println("자식 method");
    }
}
// 부모클래스
Parent c1 = new Parent();
c1.getData();
c1.method();
 
// 자식 클래스
Parent c2 = new Child();
c2.getData();
c2.method();

예상 결과

부모 getData
부모 method
자식 getData
자식 method

실제결과
부모 getData
부모 method
부모 getData
자식 method

오버라이딩 하면 new로 생성한 객체에 정의된것을 가져온다

Parent p = new Child();
Child c = (Child)p;
c.print();//"자식"
p.print();//"자식"
  • static method는 참조변수가 정의한것을 가져옴

내부 클래스 static

내부클래스 -> 메서드 변수 접근

  • jdk 1.8이상부턴 메서드의 내부클래스안에서 메서드의 변수에 접근할때 final이 안붙은 변수에도 접근할 수 있다.

public class Main{
    public static void method(){
        int a=1;
        final int aa = 2;
        class LocalClass{
            public void print(){
                System.out.println(a+" " + aa);
            }
        }
        LocalClass c = new LocalClass();
        c.print();

    }
    public static void main(String[] args) throws IOException {
        Main.method();


    }
}
  • 원래는 메서드가 끝나고 나면 지역변수에 대한 데이터는 소멸이 되기때문에 메서드 끝나고서도 유지가 되는 final변수만 참조할수있게 해논거였는데
    1.8이후로부터는 final을 붙이지 않아도 자동으로 final이 추가되도록 해놨다. 근데 final이기때문에 만약 값이 변경되면 error가 발생한다

final vs 지역변수

Q. final지역변수와 그냥 지역변수의 생명주기의 차이

A. 지역변수는 final 로 지정하면 JVM constant pool 에서 따로 변수를 관리한다.
따라서 지역클래스를 포함하고 있는 메서드와 메서드 안의 'final' 지역변수는 생명주기가 달라진다. 이 이유로 메서드가 가비지 컬렉터에 반납되어도 메서드 안의 final 지역변수는 constant pool에서 계속 보관하고 있기 때문에 이를 참조하고 있는 지역클래스의 인스턴스는 문제없이 동작할 수 있다.

내부에서 외부 변수를 참조하고싶을때

import java.io.*;
import java.lang.reflect.Array;
import java.nio.Buffer;
import java.util.ArrayList;
import java.util.Arrays;

public class Main{
    public static class Outer{
        int value = 10;
        class Inner{
            int value = 20;
            void method(){
                int value = 30;
                System.out.println(value);
                System.out.println(this.value);
                System.out.println(Outer.this.value);
            }
        }
    }
    public static void main(String[] args) throws IOException {
        Outer outer = new Outer();
        Outer.Inner inner = outer.new Inner();
        inner.method();


    }
}

면접 질문

  • Static 변수는 메모리에 한번 할당되어 프로그램이 종료될 때 해제되는 변수입니다
    class내에 static 변수는 class간에 공유되어야할 변수를 선언할때 사용하며
    class내의 static method는 class의 인스턴스 변수를 내부에서 사용하지 않을 경우에 주로 사용합니다
  • static을 사용하게되면 매번 인스턴스를 생성하지 않아도 사용할 수 있어서 속도측면에서 이득을 볼 수 있고,
    고정된 메모리영역을 사용하기때문에 매번 인스턴스를 생성하지 않아도 사용할 수 있습니다
  • 그에 반해 다음과 같은 단점도 가지고있습니다.
    프로그램 종료시 까지 메모리에 공간을 차지한다는 것입니다.
  • 가비지 컬렉터는 heap만 관리하지 static영역은 관리하지 않습니다. 그래서 프로그램 퍼포먼스에 악영향을 줍니다.
  • static이라는 키워드자체가 객체지향적이지 못합니다.
    데이터들이 캡슐화되어야한다는 객체지향의 원칙을 위반합니다.
  • 또한 static method는 인터페이스를 구현할때 사용할 수 없습니다
    코드의 재사용성을 높여주는 유용한 객체지향적기능인 인터페이스를 사용하는것을 방해하는것은 단점이 될 수 있습니다.

해결못한것

  • 이때 에러가 발생하는 이유는.. this는 인스턴스를 가리키는 키워드인데
    Outer가 static이여서 인걸까?

출처
https://developer-talk.tistory.com/412
https://jinyoungchoi95.tistory.com/16
https://wedul.site/457
https://vaert.tistory.com/101
http://sajukiller.blogspot.com/2014/01/10-5-final.html

profile
안녕하세요!
post-custom-banner

0개의 댓글