C++에서 배우는 자바-클래스(1)
이번 포스팅에선 클래스와 관련된 내용이 많다.
static의 사용
클래스 변수와 메소드는 인스턴스 변수와 메소드의 사용이 불가능하다. 인스턴스 변수와 메소드는 클래스 변수와 메소드를 사용 가능하다.
왜냐하면 앞에 static을 붙여 생성되는 클래스 변수와 메소드는 프로그램 실행 시에 static 메모리에 탑재된다. 그렇기에 인스턴스가 하나도 없어도 사용이 가능하다. 하지만 인스턴스 변수와 메소드는 프로그램 내에서 new를 사용해 동적 할당하여 생성된다. 그렇기에 인스턴스가 없어도 클래스 메소드를 사용하려면 인스턴스 변수와 메소드의 사용이 불가능한 것이다.
가변인자
또 신기한 기능이다. java에선 자료형이 공통되어 있다면 몇개가 오던 하나의 함수로 받을 수 있다. 방법은 간단하다. 매개변수 마지막 자리를 가변인자로 선언해주면 된다. 예를 들면
class cl {
void func(String... str) {}
public static void main(String[] args) {
func();
func("a");
func("a","b");
func("a","b","c");
}
}
위와 같이 가변인자를 활용하여 코드를 작성하면 매개변수가 몇 개더라도, 아예 없어도 str 배열을 통해 받을 수 있다. 매개변수로 배열을 사용하는 것과 비슷하지만 매개변수가 없을 때도 안전하게 받을 수 있다는 장점이 있다.
지역변수의 초기화
C++에선 지역변수를 초기화하지 않으면 쓰레기값이 들어가긴 하지만 코드 자체는 컴파일 되었다. 그래서 나도 가끔 초기화를 안하고 이상한 결과를 마주하는 일이 있었는데 자바에선 지역변수의 초기화가 필수적이다.
인스턴스 변수, 클래스 변수는 초기화하지 않아도 기본값으로 초기화되지만 지역변수는 초기화하지 않고 사용하면 컴파일 시 에러가 난다. 예를 들어
class cl {
public static void main(String[] args) {
int i;
int j=i;
}
}
int i만 선언 시 사용되지 않기에 상관 없지만 초기화되지 않은 i를 사용하면 에러가 난다. 실수를 방지해주는 것 같아 좋은 것 같다.
초기화 블럭
인스턴스 초기화 블럭, 클래스 초기화 블럭이 존재한다. 클래스 초기화 블럭은 컴파일 시 한번만, 인스턴스 초기화 블럭은 인스턴스가 생성될 때마다 실행된다. 예를 들어
class cl {
static {System.out.println("static");}
{System.out.println("instance");}
public static void main(String[] args) {
cl ins=new cl();
cl ins2=new cl();
}
}
위와 같이 코드를 작성하면 클래스가 컴파일될 때 한번, 이후 인스턴스를 두번 생성하므로 static instance instance 란 값이 출력될 것이다. 여러 생성자가 동일하게 사용하는 코드는 이렇게 클래스의 초기화 블럭을 이용하면 코드의 중복을 막을 수 있다.
상속
C++에서 클래스의 상속은 클래스 이름 뒤에 : 를 붙여 이루어졌다. 하지만 java에선 클래스 이름 뒤에 : 대신 extends를 붙여 이루어진다. 또한 C++에서는 한 클래스가 여러 부모 클래스에게서 상속받는 다중 상속을 지원했지만 java는 오직 단일 상속만을 허락한다.
잠깐 java의 상속 구조를 살펴보자. java에서 모든 클래스의 생성자 첫 줄은 자신의 다른 생성자, 혹은 부모의 생성자가 작성되어야 한다. 예를 들어보자.
class Point {
int x, y;
Point(int x, int y) {
this.x=x; this.y=y;
}
}
class Point3D extends Point {
int z;
Point3D(int x, int y, int z) {
this.x=x; this.y=y; this.z=z;
}
}
위의 코드를 살펴보자. 위의 코드는 컴파일 시 에러가 난다. 왜일꺼?
java에서 만약 생성자의 첫 줄에 생성자가 없다면 자동으로 부모 클래스의 생성자를 뜻하는 super() 를 넣어준다. 하지만 부모 클래스의 Point는 따로 생성자를 정의했으므로 기본 생성자가 없다. 그렇기에 에러가 나는 것이다.
Point3D 생성자 첫줄에 super(x,y); 를 작성해준다면 코드는 문제없아 돌아간다. 부모의 멤버 변수는 부모의 생성자로 초기화하는 것이 원칙이다.
그런데 잠깐, 부모 클래스인 Point는 부모가 없는데 super()를 컴파일러가 추가해줘도 괜찮은가? 모든 클래스의 상속 계층도 최상단에는 Object 클래스가 있다. 따라서 Point 클래스의 생성자에 추가된 super()는 Object()로 치환되기에 문제가 없고, 그렇기에 우리는 Object 클래스의 서브메소드인 toString(), equals() 등을 사용할 수 있었던 것이다.
오버로딩, 오버라이딩
함수의 오버로딩은 기본적으로 C++과 동일하다. 함수의 이름이 같고, 매개변수를 다르게 함으로써 같은 이름의 함수가 매개변수에 따라 다른 역할을 하도록 작성하는 것이 가능하다.
하지만 오버로딩은 조금 다르다. C++에서 사용하던 가상 함수, virtual 등을 사용하지 않는다. 왜냐하면 모든 메서드가 가상 메서드이기 때문이다. java에선 각 클래스마다 가상 메서드 테이블을 가지고 오버라이딩 된다면 재정의된 메서드의 주소를 가리키는 식으로 동작한다. 그렇기에 따로 virtual 등을 써줄 필요가 없다.
super
위에서 잠깐 봤던 super를 살펴보자. super()는 부모의 기본 생성자를 의미하지만 super는 부모 클래스 자체를 의미한다. 따라서 this 대신 super를 사용하여 부모 클래스의 변수와 메소드에 접근하는 것이 가능하다. 예를 들어
class Point {
int x, y;
int size=10;
String getlocate() {
return "x : " + x + " y : " + y;
}
}
class Point3D extends Point {
int z;
int size=20;
void getsize() {
System.out.println(super.size);
}
String getlocate() {
return super.getlocate() + " z : " + z;
}
}
위의 코드를 살펴보자. 자식 클래스에서 super를 이용하여 부모 클래스의 메소드를 활용함으로써 코드의 중복을 막고 간결하게 하였다.
또한 불필요하지만 멤버변수 size를 부모와 자식 모두 선언하였는데 이렇게 되면 이름은 size로 같지만 this.size는 20, super.size는 10으로 각기 다른 값을 가지게 된다. 이러한 부모 자식의 구분을 super를 활용하면 간단하게 할 수 있다.
패키지
java에서는 클래스들을 하나의 패키지로 묶어 관리하는 것이 가능하다. 패키지는 소스파일의 첫줄에
package 패키지명;
class Point {
int x, y;
int size=10;
String getlocate() {
return "x : " + x + " y : " + y;
}
}
을 적어서 선언할 수 있다. 패키지란 물리적으로는 우리가 컴퓨터에서 사용하는 디렉토리이다. 따라서 위의 패키지 선언을 package com.mycompany; 라고 한다면 그것은 루트 디렉토리의 com\mycompany 폴더에 Point.class가 존재한다는 것을 의미한다.
패키지 시스템에는 여러 장점이 있다. 여러 클래스를 하나로 묶어 관리하는 것 이외에도 같은 이름의 클래스여도 패키지 이름이 다르다면 다른 클래스로 식별되므로 충돌과 혼동을 방지할 수 있다.
또한 import 문을 사용하여 다른 패키지의 클래스들을 현재의 소스코드에서 제한 없이 사용할 수 있는데 예를 들어
import com.mycompany.*;
class Point3D extends Point {
int z;
int size=20;
void getsize() {
System.out.println(super.size);
}
String getlocate() {
return super.getlocate() + " z : " + z;
}
}
위와 같이 import문을 활용하여 아까 작성한 패키지를 가져오면 현재 소스파일에 Point 클래스가 없음에도 Point 클래스를 상속받는 것이 가능해진다.
많은 클래스를 관리하는데 분명 편리하지만 패키지의 하위 패키지는 import 대상이 아니므로 따로 import 문을 작성해줘야 하는 것을 주의해야 한다.
vector & arraylist
벡터가 존재한다! C++에서 굉장히 많이 사용한 자료구조였는데 java에서는 arraylist에 밀려 잘 사용되지 않는다고 한다. 하지만 멀티쓰레드 환경에서 상호배제를 구현해주는 장점이 있어 멀티스레딩에서는 사용된다고 한다.
사용법은 C++과 비슷하지만 조금 다르다. 일단 선언 자체는 비슷하다.
Vector<integer> v=new Vector<integer>();
v.add(1);
v.add(4);
v.add(1,3);
제네릭스의 도입으로 int 객체만 들어갈 수 있도록 한정하고 제네릭스에선 기본 자료형인 int를 못써서 int의 객체화된 wrapper 자료형인 integer를 쓴다고 한다.
제네릭스를 아직 배운적이 없어서 잘 모르겠으니 인지만 하고 넘어가자.
C++의 push_back과 달리 add()를 사용한다. index를 생략하면 push_back과 같이 맨 뒤에 값을 넣어주고 index를 첫번째 매개변수로 입력하면 그 자리에 값을 추가해준다.
Vector<integer> v=new Vector<integer>();
v.add(1);
v.add(4);
v.remove(1);
System.out.println(v.get(0));
삭제는 remove() 함수로 index의 값을 삭제한다.
값에 접근하기 위해서는 get() 함수를 사용하거나 iterator를 사용하여 내부 값에 접근할 수 있다.
arraylist는 위의 코드들에서 Vector만 ArrayList로 바꿔주면 될 정도로 매우 유사하다.
현재 책을 읽은 부분까지 정리를 끝냈다. 책을 더 읽고 차이점, 모르는 점을 정리하여 또 포스팅 하도록 하겠다.