변수란?
Java에서는 변수를 사용하기 위해서 어떤 형태의 자료를 저장할 것인지 명시해줘야합니다.
예를 들면, 사람의 나이를 저장하려면 정수 타입( 1살, 2살, 3살 .... 50살..)이라던지,
이름을 저장하려면 문자 타입(손흥민, 박찬호, 이순신 ... 등 )를 써야겠죠..
이 형태를 변수의 '자료형'이라고 부릅니다.
위와 같이 변수의 자료형을 정해주었다면 변수의 이름도 정해주어야합니다.
Java에서는 변수의 자료형을 선택하고 이름을 정하는 것을 '변수를 선언한다'라고 합니다.
변수는 값을 넣을 수 있는 빈 그릇과 같다고 생각하시면 됩니다.
int nameAge = 10;
위와 같은 내용을 설명할 때는 nameAge변수 선언과 동시에 값을 대입(초기화)했다고 말합니다.
초기화는 변수에 처음 값을 대입하는 것을 의미합니다.
클래스 자료형? 참조 변수? 인스턴스?
JavaProgrammer jProgrammer = new JavaProgrammer();
위 코드는
JavaProgrammer 클래스 자료형으로 jProgrammer 변수를 선언하고 new JavaProgrammer()로
JavaProgrammer클래스를 생성하여 JavaProgrammer에 대입하는 의미입니다.
jProgrammer를 참조 변수라고 부르며, 이 변수는 생성된 인스턴스를 가르킵니다.
인스턴스? 힙 메모리(Heep Memory)? 지역변수? 스택 메모리?
new JavaProgrammer() 를 선언하면 JavaProgrammer 하나가 생성되는데, 각 JavaProgrammer 는 멤버
변수를 가지고 있습니다. 이들 변수를 저장할 공간이 있어야 하는데 이때 사용하는 메모리가 힙 메모리입니다.
JavaProgrammer jProgrammer = new JavaProgrammer();
위와 같이 생성된 jProgrammer 변수에 대입하면, 인스턴스가 저장된 메모리를 jProgrammer 변수가 가르킵니다.
jProgrammer 변수는 지역 변수입니다. 지역 변수는 스택(Stack) 메모리에 생성됩니다.
지역 변수 jProgrammer에 생성된 인스턴스를 대입하는 것은
jProgrammer 에 인스턴스가 생성된 힙 메모리의 주소를 대입한다는 것과 같은 의미입니다.
힙 메모리(Heep Memory)란?
힙(Heep)은 프로그램에서 사용하는 동적 메모리 공간을 말합니다.
일반적으로 프로그램은 스택(Stack), 힙(Heep), 데이터(Data) 3개의 영역을 사용합니다.
객체가 생성될 때. 즉, 인스턴스가 생성될 때에 사용하는 공간은 힙 이라는 공간입니다.
힙은 동적으로 할당되며 사용이 끝나면 메모리를 해제해 주어야합니다.
C나 C++은 프로그래머가 직접 해제를 했지만, Java에서는 가비지 컬렉터(garbage collector)가 자동으로 해줍니다.
힙 메모리에 생성된 인스턴스의 메모리 주소는 참조 변수에 저장됩니다.
출력 내용은 클래스 이름@ 주소 값이며, 이 주소 값은 해시 코드 값이라고도 부릅니다.
자바 가상 머신에서 객체가 생성되었을 때 생성된 객체에 할당하는 가상 주소입니다.
따라서 참조 변수를 사용하여 생성된 인스턴스를 참조할 수 있는데
jProgrammer 을 참조 변수라고 하며, 주소 값을 참조 값이라고도 합니다.
헷갈리는 용어를 아래와 같이 정리했습니다.
- 객체 : 객체 지향 프로그래밍의 대상, 인스턴스
- 클래스: 객체를 프로그래밍 하기 위해 코드로 만든 상태
- 인스턴스: 클래스가 힙 메모리에 생성된 상태
- 멤버 변수: 클래스의 속성 또는 특성
- 메서드: 멤버 변수를 이용하여 클래스의 기능 구현
- 참조 변수:힙 메모리에 생성된 인스턴스를 가르키는 변수
- 참조 값: 생성된 인스턴스의 메모리 주소 값(해시 코드 값)
상속받은 클래스(하위 클래스)는 상위 클래스의 기능을 모두 사용할 수 있을 뿐만 아니라, 추가로 더 많은 기능을 구현합니다.
위 그림을 통해 알 수 있듯이, Car는 Car형이면서 동시에 Vehicle형이기도 합니다.
즉, Car클래스로 인스턴스를 생성할 때 자료형을 Vehicle형으로 클래스 형 변환 하여 선언할 수 있습니다.
Vehecle v1 = new Car();
왜냐하면 Car클래스는 Vehicle클래스를 상속받았기 때문입니다.
- Vehicle: 선언된 클래스형(상위클래스)
- Car : 생성된 인스턴스의 클래스형(하위 클래스형)
혹시 반대는 가능할까요? 안됩니다. 그 이유는, 상위 클래스가 하위 클래스 기능을 다 가지고 있지 않기 때문입니다.
클래스 형변환이 되었을 때 선언한 클래스형에 기반하여 멤버 변수와 메서드에 접근할 수 있습니다.
따라서 v1 참조 변수가 가르킬 수 있는 변수와 메서드는 Vehicle 클래스 멤버 뿐입니다.
다운 캐스팅과 instanceof
위와 같은 계층 구조에서 상위 클래스를 자료형으로 선언하는 Vehicle v1 = new Truck(); 코드를 쓸 수 있습니다.
이때 생성된 인스턴스 Truck은 Vehicle형이며, Vehicle클래스에서 선언한 메서드와 멤버 변수만 사용할 수 있습니다.
위의 내용을 다시 설명하자면, Truck클래스에 더 많은 메서드가 구현되어 있고 다양한 멤버 변수가 있다고 하더라도 자료형이 Vehicle형인 상태에는 사용할 수가 없다는 의미입니다.
이런 이유로 인해서 원래 인스턴스의 자료형(여기에서는 Truck을 의미)으로 되돌아가야 하는 경우가 있습니다.
이렇게 상위 클래스로 형 변환되었던 하위 클래스가 다시 원래 자료형으로 형 변환하는 것을 다운 캐스팅(down Casting)이라고 합니다.
Vehicle v1 = new Truck();
if(v1 instanceof Truck) { // v1 인스턴스 자료형이 Truck형이라면
Truck truck= (Truck)v1 ; // 인스턴스 v1 을 Truck 형으로 다운 캐스팅
}
instanceof의 반환값이 true이면 다운 캐스팅이 진행되며,
Truck truck= (Truck)v1 ; 와 같이 명시적으로 자료형을 반드시 써 주어야 합니다.
상위 클래스로는 묵시적으로 형 변환이 되지만, 하위 클래스로 형 변환을 할때는 명시적으로 해야 하기 때문입니다.
추상 클래스는 항상 추상 메서드를 포함합니다.
추상 메서드는 구현 코드가 없습니다. 함수의 구현 코드가 없다는 것은 함수 몸체(Body)가 없다는 뜻입니다.
int add(int x, int y) {
return x + y;
}
구현 코드가 없는 인터페이스
public interface Calc {
double PI = 3.14;
int ERROR = -999999999;
int add( int num1, int num2 );
int substract( int num1, num2 );
int times( int num1, int num2 );
int divide( int num1, int num2 );
}
위 처럼 인터페이스에 선언한 메서드는 모두 구현 코드가 없는 추상 메서드입니다.
public abstract 예약어를 명시적으로 쓰지 않아도 컴파일 과정에서 자동으로 추상 메서드로 변환됩니다.
인터페이스에 선언한 변수는 모두 컴파일 과정에서 값이 변하지 않는 상수로 자동 변합니다.
public static final 예약어를 쓰지 않아도 무조건 상수로 인식합니다.
위와 같은 인터페이스를 클래스가 사용하는 것을 '클래스에서 인터페이스를 구현한다'라고 표현합니다.
인터페이스 구현과 형 변환
인터페이스를 구현한 클래스가 있을 때 그 클래스는 해당 인터페이스형으로 묵시적 형 변환이 이루어지며,
형 변환되었을 때 사용할 수 있는 메서드는 인터페이스에서 선언한 메서드 뿐입니다.
따라서, 인터페이스의 역할은 인터페이스를 구현한 클래스가 어떤 기능의 메서드를 제공하는지 명시하는 것입니다.
그리고 클라이언트 프로그램은 인터페이스에서 약속한 명세대로 구현한 클래스를 생성해서 사용하면 됩니다.
public interface Calc {
double PI = 3.14;
int ERROR = -999999999;
...
}
인터페이스는 추상 메서드로 이루어지므로 인스턴스를 생성할 수 없으며 멤버 변수도 사용할 수 없습니다.
위와 같이 변수를 선언해도 오류가 발생하지 않는 이유는 선언한 변수는 컴파일하면 상수로 변환되기 때문입니다.
default 메서드
인터페이스의 디폴트 메서드는 인터페이스에서 구현 코드까지 작성한 메서드입니다.
인터페이스를 구현한 클래스에 기본적으로 제공할 메서드라는 것입니다.
정적 메서드
정적 메서드는 인스턴스 생성과 상관없이 사용할 수 있는 메서드입니다.
그렇지만 인터페이스에 디폴트/정적 메서드를 추가했다고 해서 인스턴스를 생성할 수 있는 것은 아닙니다.
디폴트 메서드는 일반 메서드와 똑같이 구현하면 되고, 메서드 자료형 앞에 default 예약어만 써 주면 됩니다.
만약,
이미 인터페이스에 구현되어 있는 디폴트 메서드가 새로 생성한 클래스에서 원하는 기능과 맞지 않는다면,
하위 클래스에서 디폴트 매서드를 재정의 할 수 있습니다.
정적 메서드는 static 예약어를 사용하여 선언하며 클래스 생성과 무관하게 사용할 수 있습니다.
정적 메서드를 사용할 때는 인터페이스 이름으로 직접 참조하여 사용합니다.
댓글