Static 이란?
static은 정적인, 고정된이란 의미로 컴파일 단계에서 클래스로더가 클래스를 로딩할 때 함께 메모리에 할당되어 프로그램이 종료될 때까지 유지되는 메모리를 말한다.
static은 클래스, Method, 상수 등과 함께 Method Area에 클래스 별로 저장되어 관리하며 모든 쓰레드가 공유한다.
즉 모든 인스턴스가 하나의 주소를 가리키고 있어 하나의 값을 공유한다.
static 키워드를 가지는 메소드와 변수를 클래스 메소드, 클래스 변수 혹은 정적 필드, 정적 메소드라고 부른다.
static 키워드가 붙은 멤버는 객체에 소속된 것이 아닌 클래스에 소속되어 있기 때문이다.
- 정적 멤버 선언
class Sclass{
static int cVar = 123; //클래스 변수 선언 및 초기화
static void cMethod(){
}// 클래스 메소드 선언
}
Method Area에 있는 Runtime Constant Pool이 해당 데이터의 참조값을 저장하고 있기 때문에 static의 주소값을 찾을 수 있기 때문이다.
그렇기 때문에 클래스 멤버는 인스턴스를 선언하지 않고 해당 클래스 내에서 호출이 가능하다.
다른 클래스에서 호출을 하려면 "Class명." 으로 호출 할 수 있다.
class StaticClass {
static int classVar;
int instanceVar = 0;
public static void main(String[] args) {
System.out.println(classVar); // 접근 가능
System.out.println(instanceVar); // error
}
}
class GetVal{
int a = StaticClass.classVar; //외부 클래스에서 static 멤버 호출
}
클래스 메소드 내에서 인스턴스 변수를 사용할 수 없다. 인스턴스 선언 없이도 실행이 가능하기 때문에 인스턴스 변수를 사용하려면 메소드 내부에서 인스턴스를 생성 한뒤 사용해야 한다.
public class StaticClass {
static int classVar;
int instanceVar = 0;
static void classMethod(){
int a = classVar;
int b = instanceVar; // 인스턴스 없이 사용 불가
StaticClass s = new StaticClass();
int c = s.instanceVar; // 인스턴스 생성 후 참조하여 접근 가능
}
}
그렇다면 어떤 경우에 static을 사용하는 것이 좋을까?
객체마다 각각 가지고 있어야 할 데이터라면 인스턴스 멤버로 선언을 하고
모든 프로그램에서 하나의 값을 공유해야 할때는 정적으로 선언하는 것이 좋다.
예를 들어 계산을 하는 기능을 가진 함수는 프로그램에서 동일한 기능으로 작동하므로 하나의 함수를 공유하는 것이 좋을 것이다.
또는 웹페이지에서 로그인을 했을때 하나의 로그인 정보를 저장하고 공유해야하는 상황일 때 사용할 수 있다.
class Calculate {
static int sum(int a, int b){
return (a + b);
}
}
만약 static 변수를 초기화 할때 복잡한 단계가 필요하다면 어떻게 해야할까?
생성자를 통해 초기화 할 수 없으므로 정적필드를 위한 정적블록을 제공한다.
아래와 같이 static 블록으로 초기화하여 사용할 수 있다.
class StaticClass {
static int a = 10;
static int b = 20;
static int classVar;
static { //static 블록을 통한 초기화
classVar = a + b;
}
}
디자인 패턴이란?
디자인 패턴이란 객체지향 프로그래밍을 할때 자주 발생하는 문제를 해결할 수 있는 패턴이다.
알고리즘과는 다른 개념으로 상황에 따라 자주 쓰이는 설계 방법을 이름을 붙여서 정리한 방법론 정도로 생각하면 되겠다.
Singleton Patton
싱글턴 패턴은 디자인 패턴 중 생성 패던의 하나로 단 하나의 객체만 만들어야 하고 그것이 꼭 보장되어야 하는 경우에 사용할 수 있는 방법이다.
싱글턴 패턴의 공통적인 특징은 private 생성자를 가지고 있으며 static 메소드를 사용한다.
싱글턴 구현 방법의 종류에는 사전 초기화(이른 초기화)와 사후 초기화(게으른 초기화)가 있다.
사전초기화(Eager Initialization)는 클래스를 로딩할 시점에 인스턴스를 생성하는 방법이다.
먼저 코드 예시를 보자
class SingletonEager{
static private SingletonEager eagerInstance = new SingletonEager(); //static 객체 생성
private SingletonEager(){} //private 생성자
public SingletonEager getInstance(){
return eagerInstance;
}
}
static을 사용해서 인스턴스를 초기화한 뒤 Singleton 클래스의 생성자는 외부에서 접근하지 못하도록 private으로 설정하고
getInstance()로 동일한 instance를 리턴한다.
클래스가 최초로 로딩 될 때 생성됨으로 하나의 인스턴스만 생성되어 Thread-safe 한 방식이다.
Thread-safe : 멀티스레드 환경에서 객체 혹은 함수, 변수가 여러 스레드로부터 동시 접근이 되어도 정상 작동하는 것을 뜻함
단점은 객체를 사용하지 않는 상황에서도 메모리가 할당되어 낭비가 된다.
사후초기화(Lazy initialization)는 반대로 객체가 필요할 때 생성하는 방식이다.
만약 객체의 크기가 크고 잘 사용되지 않는다면 불필요한 할당을 하지 않아 자원을 아낄 수 있어 Eager Initialization의 단점을 보완할 수 있다.
class SingletonLazy{
static private SingletonLazy lazyInstance;
private SingletonLazy(){}
public static SingletonLazy getInstance(){
if (lazyInstance == null){ //인스턴스가 생성되지 않았을 시
lazyInstance = new SingletonLazy();
}
return lazyInstance;
}
}
Array(배열)
자바의 배열은 하나의 객체이다.
동적으로 배열의 사이즈를 할당을 해주고 arr 변수에는 배열의 첫번째 인자의 주소값을 참조하고 있다.
int [] arr1 = new int[10];
int [] arr2 = {1, 2, 3, 4, 5} // 선언과 동시에 초기화
int [] arr3;
arr3 = new int[]{0, 1, 2, 3, 4};
System.out.println(arr3.length); // arr3의 크기는 5
heap 영역에 저장되어 있는 배열의 실제 값은 연속적으로 데이터가 저장이 되어 있어 다음 인덱스에 접근할 수 있는 것이다.
배열을 선언한 후 초기화시에 사이즈를 정할 수 있다.
Arrays의 메소드는 배열을 다룰때 편리한 기능들을 제공한다.
메소드 | 기능 |
static <T> T[] copyOf(T[] original, int newLength) | 전달받은 배열을 특정 길이만큼 복사 반환 |
static void fill(Object[] a, Object val) | 전달받은 배열의 모든 요소를 특정값으로 초기화 |
static void sort(Object[] a) | 전달받은 배열의 모든 요소를 오름차순으로 정렬 |
배열은 for문을 통해 복사를 하면 요소 하나하나를 접근하여 복사하지만 copyOf()를
사용하게 되면 배열을 통채로 복사하기 때문에 성능면에서 더 빠르게 처리할 수 있다.
ArrayList
자바에는 다양한 방식으로 데이터를 저장하고 핸들링할 수 있는 컬렉션 프레임워크를 제공하고 있다.

그중 ArrayList는 List 인터페이스의 구현 클래스이다. 기존 Vector를 개선한 것으로 기능과 원리가 비슷하다.
ArrayList는 사이즈를 초과해 새로운 인덱스가 추가되면 자동으로 더 큰 배열을 생성하기 때문에 동적으로 크기를 바꾸는 특징이 있다.
ArrayList의 내부에는 모든 클래스의 조상인 Object 타입의 배열로 데이터를 저장하고 있어 배열과 다르게 다른 형의 데이터들과도 저장이 가능하다.
transient Object[] elementData; // non-private to simplify nested class access
다음과 같이 사용할 수 있으며 제네릭을 통해서 ArrayList의 형을 명시할 수 있다.
ArrayList arr = new ArrayList(); // 기본 Object 타입
ArrayList<String> arrString = new ArrayList<String>();
//String 타입
List 인터페이스 메소드
메소드 | 기능 |
boolean add(E e) | 해당 리스트에 전달된 요소 추가 |
void add(int index, E e) | 해당 리스트의 특정 위치에 전달된 요소 추가 |
void clear() | 해당 리스트의 모든 요소 제거 |
E get(int index) | 해당 리스트의 특정 인덱스에 있는 요소 리턴 |
boolean isEmpty() | 해당 리스트가 비어있는지 확인 |
int size() | 해당 리스트 요소의 개수 리턴 |
size vs length
배열을 처음 선언할때 크기를 10으로 정하고 ArrayList의 크기도 10으로 정해주었다.
length를 출력하면 처음 사이즈를 정했던 10이 나오지만 size는 0으로 나온다. 무엇이 다른 걸까
int [] arr = new int[10];
ArrayList arrayList = new ArrayList(10);
System.out.println(arr.length); // 10
System.out.println(arrayList.size()); // 0
배열의 length 는 사이즈가 한번 정해지면 바꿀 수 없고 해당 값은 0으로 초기화된 10개의 배열이 있고 아직 데이터를 따로 넣지 않아도
할당된 사이즈를 반환한다.
ArrayList의 size는 실제 add 메소드를 사용해서 들어간 인자의 개수를 리턴하는 것으로 생성시에 capacity는 출력하지 않는다.
그렇다면 ArrayList는 어떤 방식으로 인자를 넣으며 사이즈를 변경할까?
add 메소드가 실행될 때의 실제 코드를 확인 해 보자
만약 현재 ArrayList의 사이즈와 할당되어 있는 Object 배열의 length 가 같다면 grow() 를 호출한다.
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
minCapacity = size + 1 을 넘겨주고 copyOf를 해서 사이즈를 측정해서 배열 복사를 한다.
private Object[] grow(int minCapacity) {
return elementData = Arrays.copyOf(elementData,
newCapacity(minCapacity));
}
아래 코드는 얼만큼의 배열의 사이즈를 늘려줄지에 대한 메소드이다.
oldCapacity는 현재 사이즈를 측정하고 newCapacity는 old의 절반을 더한 크기를 저장한다. (우측 쉬프트 연산으로 50% 추가)
만약 minCapacity가 더 크거나 같고 배열의 요소가 비어있다면 기본 capacity인 10의 크기와 min의 크기 중 큰 값을 넣는다.
즉 최소 ArrayList의 capacity는 10인것이다.
만약 newCapacity가 더 크고 MAX_ARRAY_SIZE() 보다 작다면 (MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8)
newCapacity만큼 사이즈를 정한다.
private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length; // 기존 사이즈
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 기존사이즈의 절반을 더한다.
if (newCapacity - minCapacity <= 0) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return minCapacity;
}
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
결론적으로 ArrayList 는 사이즈를 가변적으로 바꿀 수 있는데 인덱스는 1개씩 add되지만
실제 배열의 사이즈는 한번씩 늘려 copy하는 것에는 많은 연산이 필요하기에 첫 원소를 add 할때 10이 할당되고 그 후
ArrayList의 기존 사이즈에 절반을 더해 복사하는 방식으로 크기를 늘려가는 것을 알 수 있다.
'Language & Framework > Java' 카테고리의 다른 글
[JAVA] :: 자바 기초 5 :: 다형성 / InstanceOf / Abstract Class / Upcasting & Downcasting (0) | 2021.07.22 |
---|---|
[JAVA] :: 자바 기초 4 :: 상속 / 오버라이딩(overriding) / IS-A vs HAS-A / 가상함수 (0) | 2021.07.19 |
[JAVA] :: 자바 기초 2 :: 생성자 / 오버로딩 / 접근제어자 / 정보은닉 (0) | 2021.07.06 |
[JAVA] :: 자바 기초 1 :: 객체 / 클래스 / 인스턴스 / 속성 / 함수 / 메소드 (2) | 2021.07.04 |
[JAVA] 소켓(socket) 프로그래밍 (0) | 2021.01.20 |