정적 멤버 변수와 비정적 멤버 변수의 차이점
다음 예를 예로 들어 보겠습니다.
package cn.galc.test;public class Cat { /** * 정적 멤버 변수*/ private static int sid = 0; int id; } public void info() { System.out.println("내 이름은 " + name + ",NO." + id) } public static void main(String[] args) { Cat.sid = 100; 고양이 mimi = new Cat("mimi"); 고양이 pipi = new Cat("pipi");메모리 분석 다이어그램을 그려 전체 프로그램의 실행 과정을 이해합니다.
프로그램의 첫 번째 문장 실행 시: Cat.sid = 100;, 여기서 sid는 정적 멤버 변수입니다. 정적 변수는 데이터 영역(data seg)에 저장되므로 먼저 데이터 영역에 작은 공간 sid를 할당합니다. . 문장이 실행된 후 sid에는 100의 값이 포함됩니다.
이때 메모리 레이아웃 다이어그램은 다음과 같습니다.
다음으로 프로그램은 다음을 실행합니다.
고양이 미미 = new Cat(“미미”);
여기서는 Cat 클래스의 생성자 메서드 Cat(String name)이 호출됩니다. 생성자 메서드는 다음과 같이 정의됩니다.
Cat(문자열 이름){ this.name = id=sid++ } 호출할 때 먼저 힙 메모리에 있는 Cat 클래스의 인스턴스 개체 주소를 포함하는 작은 메모리 조각 mm을 스택 메모리에 할당합니다. mm은 힙 메모리에 있는 Cat 클래스 개체의 참조 개체입니다. 이 생성자는 문자열 형태의 형식적인 매개변수 변수를 선언하므로 "mimi"가 실제 매개변수로 생성자에 전달된다. 문자열 상수가 데이터 영역에 할당되어 저장되기 때문에 데이터 영역에 메모리가 조금 남는다. . "mimi" 문자열을 저장하는 데 사용됩니다. 이때의 메모리 분포는 아래 그림과 같습니다.
생성자를 호출할 때 먼저 스택 메모리에 형식 매개변수 이름을 위한 작은 공간을 할당한 다음 문자열 "mimi"를 name에 실제 매개변수로 전달합니다. 이 문자열도 참조 유형입니다. 단, 그 4개와 8개는 제외됩니다. 기본 데이터 유형이고 나머지는 모두 참조 유형이므로 문자열도 객체라고 간주할 수 있습니다. 따라서 이는 "mimi" 개체의 참조를 name에 전달하는 것과 동일하므로 이제 name은 "mimi"를 가리킵니다. 따라서 이때의 메모리 레이아웃은 아래와 같습니다.
다음으로 생성자 본문에서 코드를 실행합니다.
this.name=이름;
여기서는 힙 메모리에 있는 고양이를 나타내는 현재 개체를 나타냅니다. 여기서 스택에 있는 name에 포함된 값은 힙 메모리에 있는 cat 객체의 name 속성으로 전달되므로 이때 name에 포함된 값은 힙 메모리에 위치한 문자열 객체 "mimi"에서도 찾을 수 있습니다. 이때 이 이름은 문자열 개체 "mimi"의 참조 개체이기도 합니다. 해당 속성 값을 통해 데이터 영역에 있는 문자열 개체 "mimi"를 찾을 수 있습니다. 이때의 메모리 분포는 아래 그림과 같습니다.
그런 다음 메서드 본문에서 다른 코드 줄을 실행합니다.
아이디=ID++;
여기서는 sid의 값이 id에 전달되므로 id의 값은 100이 됩니다. sid를 전달한 후 1을 더합니다. 이때 sid는 101이 됩니다. 이때의 메모리 레이아웃은 아래 그림과 같습니다.
이때 생성자 메소드가 호출되고, 이 생성자 메소드에 할당된 지역변수가 차지하는 메모리 공간이 모두 사라지게 되므로 스택공간에 위치한 네임메모리도 사라진다. 스택 메모리의 데이터 영역에 있는 문자열 개체 "mimi"에 대한 참조도 사라지고, 이때 힙 메모리에 있는 문자열 개체 "mimi"에 대한 참조만 남습니다. 이때 메모리 레이아웃은 아래와 같습니다.
다음 실행:
고양이 삐삐 = new Cat("삐삐"); 다음은 생성자 메서드 Cat()에 대한 두 번째 호출입니다. 전체 호출 과정은 첫 번째 호출과 동일합니다. 이때의 메모리 레이아웃은 아래 그림과 같습니다.
info() 메서드를 호출하면 코드의 마지막 두 줄이 인쇄됩니다. 인쇄 결과는 다음과 같습니다.
이 프로그램을 통해 우리는 셀 수 있는 정적 멤버 변수 sid의 역할을 볼 수 있습니다. 새로운 고양이가 나올 때마다 번호를 알려주세요. 그 자체로 1을 더하도록 하세요.
프로그램이 실행된 후 메모리의 전체 레이아웃은 위 그림과 같습니다. 기본 메서드 호출이 완료되기 직전까지 계속됩니다.
여기서 생성자 메소드 Cat(String name)을 호출하여 두 마리의 고양이를 생성합니다. 먼저 mimi와 pipi라는 두 개의 작은 공간이 스택 메모리에 할당됩니다. 여기에는 두 고양이가 대응하는 주소가 포함됩니다. 힙 메모리의 주소에 두 마리의 인용문이 있습니다. 여기서 생성 방법은 문자열 유형의 변수를 선언합니다. 문자열 상수는 데이터 영역에 할당되므로 전달된 문자열 mimi 및 pipi는 데이터 영역에 저장됩니다. 따라서 데이터 영역에는 문자열 "mimi" 및 "pipi"를 포함하는 문자열 mimi 및 pipi를 저장하기 위한 두 개의 작은 메모리 블록이 할당됩니다. 문자열도 4개 및 8개의 기본 데이터 유형 외에 다른 유형입니다. 모든 데이터 유형은 참조 유형입니다. 따라서 문자열을 객체로 생각할 수 있습니다.
여기에 두 마리의 새로운 고양이가 있습니다. 두 고양이 모두 고유한 id와 name 속성을 갖고 있으므로 여기의 id와 name은 비정적 멤버 변수입니다. 즉, 정적 수정이 없습니다. 따라서 새 고양이가 생성될 때마다 이 새 고양이는 고유한 ID와 이름을 갖습니다. 즉, 비정적 멤버 변수 id와 name은 각 개체에 대해 별도의 복사본을 갖습니다. 그러나 정적 멤버 변수의 경우 새 개체 수에 관계없이 복사본이 하나만 있습니다. 새 개체가 없더라도 정적 멤버 변수는 데이터 영역에 하나의 복사본을 유지합니다. 여기의 sid와 마찬가지로 sid는 데이터 영역에 저장되는데, 힙 메모리에 새로운 고양이가 아무리 많이 있어도 sid의 복사본은 하나만 있고 데이터 영역에는 복사본 하나만 보관됩니다.
정적 멤버 변수는 특정 개체가 아닌 전체 클래스에 속합니다. 그렇다면 이 정적 멤버 변수의 값에 액세스하는 방법은 무엇입니까? 우선, 모든 개체는 이 정적 값에 액세스할 수 있으며, 액세스할 때 동일한 메모리에 액세스합니다. 두 번째 점은 객체가 없어도 이 정적 값에 접근할 수 있다는 점이다. "클래스 이름.정적 멤버 변수 이름"을 통해 이 정적 값에 접근할 수 있으므로 앞으로는 특정 클래스 이름에 "."가 붙은 것을 볼 수 있을 것이다. 뒤에 하나가 있으면 다음은 "System.out"과 같이 정적이어야 합니다. 여기서 이 out은 클래스 이름(System 클래스)에 "."을 더해 액세스하므로 이 out은 정적이어야 합니다.
클래스 멤버가 정적으로 선언되면 개체를 참조하지 않고도 클래스의 개체가 생성되기 전에 액세스할 수 있습니다. 정적 멤버의 가장 일반적인 예는 main()입니다. main()은 프로그램 실행이 시작될 때 호출되어야 하므로 static으로 선언됩니다.
static으로 선언된 변수는 본질적으로 전역 변수입니다. 객체가 선언되면 정적 변수의 복사본이 생성되지 않지만 클래스의 모든 인스턴스 변수는 동일한 정적 변수를 공유합니다. 예를 들어 정적 변수 개수를 새 클래스 인스턴스의 개수로 선언합니다. static으로 선언된 메서드에는 다음과 같은 제한 사항이 있습니다.
(1) 다른 정적 메서드만 호출할 수 있습니다.
(2) 정적 데이터에만 액세스할 수 있습니다.
(3) 어떤 식으로든 this 또는 super를 참조할 수 없습니다.
계산을 통해 정적 변수를 초기화해야 하는 경우 정적 블록을 선언하면 클래스가 로드될 때 한 번만 실행됩니다. 아래 예는 다음과 같습니다.
클래스에는 정적 메서드, 일부 정적 변수 및 정적 초기화 블록이 있습니다. public class UserStatic { static int a = 3; static void meth(int x) { System.out.println("x = " + x); System.out.println("a = " + a); System.out.println("b = " + b) } static { System.out.println("정적 블록 초기화되었습니다."); b = a * 4; } public static void main(String args[]) { meth(42); } } UseStatic 클래스가 로드되면 모든 정적 문이 실행됩니다. 먼저 a를 3으로 설정한 다음 정적 블록이 실행되고(메시지 인쇄) 마지막으로 b가 a*4 또는 12로 초기화됩니다. 그런 다음 main()이 호출되고, main()이 meth()를 호출하여 값 42를 x에 전달합니다. 세 개의 println() 문은 두 개의 정적 변수 a와 b, 지역 변수 x를 참조합니다.
참고: 정적 메서드에서 인스턴스 변수를 참조하는 것은 불법입니다.
이 프로그램의 출력은 다음과 같습니다.
정적 블록이 초기화되었습니다. x = 42 a = 3 b = 12
정적 메서드와 변수는 자신이 정의된 클래스 외부의 개체와 독립적으로 사용할 수 있습니다. 이렇게 하면 클래스 이름 뒤에 점(.) 연산자를 추가하기만 하면 됩니다. 예를 들어, 클래스 외부에서 정적 메서드를 호출하려는 경우 다음과 같은 일반 형식을 사용할 수 있습니다.
클래스명.메소드()
여기서 classname은 정적 메소드가 정의된 클래스의 이름입니다. 보시다시피 이 형식은 개체 참조 변수를 통해 비정적 메서드를 호출하는 형식과 유사합니다. 정적 변수는 동일한 형식(클래스 이름 뒤에 점 연산자가 오는 형식)으로 액세스할 수 있습니다. 이것이 Java가 전역 함수 및 전역 변수의 제어된 버전을 구현하는 방법입니다.
요약:
(1) 정적 멤버는 해당 멤버가 위치한 클래스에서 생성된 인스턴스에서 액세스할 수 없습니다.
(2) 정적 수정이 없는 멤버가 객체 멤버인 경우 해당 멤버는 각 객체의 소유가 됩니다.
(3) static으로 수정된 멤버는 클래스 멤버로, 클래스에서 직접 호출할 수 있으며 모든 객체에 공통적으로 적용됩니다.
Java Static: 수정자로서 변수, 메소드 및 코드 블록을 수정하는 데 사용할 수 있습니다(그러나 클래스를 수정해서는 안 됩니다).
(1) 변수 수정:
클래스 변수라고도 하는 클래스의 모든 객체가 공유하는 속성입니다. 이는 C 언어의 전역 변수와 유사합니다. 클래스 변수는 클래스가 로드될 때 초기화되며 한 번만 초기화됩니다. 프로그램의 개체가 정적 변수를 수정하면 다른 개체에도 수정된 값이 표시됩니다. 따라서 클래스 변수를 카운터로 사용할 수 있습니다. 또한 Java Static 변수는 객체 없이 클래스 이름을 사용하여 직접 액세스할 수 있습니다.
(2) 수정 방법:
클래스의 모든 객체에 공통되는 함수를 정적 메서드라고 합니다. 객체를 요구하지 않고 클래스 이름을 사용하여 정적 메서드에 직접 액세스할 수도 있습니다. 따라서 정적 메서드에서는 비정적 변수, 비정적 메서드에 직접 접근할 수 없으며, 정적 메서드에서는 this, super 등의 키워드가 나타날 수 없습니다.
(3) Java 코드 블록 수정:
정적 코드 블록이라고 하는 클래스의 독립적인 코드 블록을 수정하려면 static을 사용합니다. 정적 코드 블록은 클래스가 처음 로드될 때 한 번만 실행됩니다. 정적 코드 블록에는 이름이 없으므로 명시적으로 호출할 수 없으며 클래스가 로드될 때만 가상 머신에 의해 호출됩니다. 주로 일부 초기화 작업을 완료하는 데 사용됩니다.
(4) 클래스 로딩에 대해 이야기해보자:
JVM이 처음으로 클래스를 사용할 때 클래스 경로에 지정된 경로로 이동하여 해당 클래스에 해당하는 바이트코드 파일을 찾아 JVM으로 읽어들이고 저장하는 과정을 클래스 로딩이라고 합니다.
변수든, 메서드든, 코드 블록이든 정적으로 수정되기만 하면 클래스가 로드되면 "준비" 상태가 됩니다. 즉, 사용할 수 있거나 실행이 완료되었음을 알 수 있습니다. 모두 객체 없이 실행될 수 있습니다. 반대로 static이 없으면 객체를 통해 접근해야 합니다.