- 소스파일(.java)을 작성한 후 컴파일한다.
- javac (java compiler)명령어는 소스를 컴파일한다.
- 컴파일 결과는 확장명이 .class인 바이트코드 파일로 생성된다.
- 바이트코드 파일(~.class)을 특정 운영체제가 이해하는 기계어로 번역하고 실행시키는 명령어는 java이다.
- java 명령어는 JDK와 함께 설치된 자바 가상 머신(JVM)을 구동시켜 바이트코드 파일을 완전한 기계어로 번역하고 실행시킨다.
- 바이트코드 파일은 운영체제와 상관없이 모두 동일한 내용으로 생성된다.
- JVM은 각 운영체제에서 이해하는 기계어로 번역해야 하므로 운영체제별로 다르게 설치된다.
배열(Array) | 리스트(List) | |
---|---|---|
크기 | 고정적 (선언할 때 크기가 고정적) | 동적 (데이터를 삽입할 때마다 증가) |
주소 | 순차적 | 랜덤 |
접근속도 | 인덱스만 안다면 접근 가능 (속도 빠름) | 하나하나 위치를 따라가서 접근 (배열에 비해 속도 느림) |
삽입/삭제 | 삽입 시, 기존의 데이터들의 위치를 뒤로 이동시킴. 삭제 시 기존의 데이터들의 위치를 앞으로 이동시킴. (비효율적) | 삽입 시, 리스트에 연결되어 있는 위치에 접근한 우 리스트 추가. 삭제 시, 리스트에 연결되어 있는 위치에 접근하여 삭제 후, 기존의 리스트들을 연결. 삽입/삭제 시 메모리를 할당/해제할 필요가 있음. (효율적) |
- 클래스의 정의 : 객체를 정의해 놓은 것 (그릇, 틀)
- 클래스의 용도 : 객체를 생성하는데 사용 (음식)
- 클래스를 실체화 시키는 것 : 인스턴스화
// 클래스 영역에 선언된 변수를 멤버변수라고 부른다.
class Variables {
int iv; // 인스턴스 변수
static int cv; // 클래스 변수 (static 변수, 공유 변수)
void method() {
int lv = 0; // 지역 변수
}
}
변수의 종류 | 선언위치 | 생성시기 |
---|---|---|
클래스변수 (Class Variable) | 클래스영역 | 클래스가 메모리에 올라갔을 때 |
인스턴스변수 (Instance Variable) | 클래스영역 | 인스턴스가 생성되었을 때 |
지역변수 (Local Variable) | 클래스 영역 이외의 영역 (메서드, 생성자, 초기화 블럭 내부 | 변수 선언문이 수행되었을 때 |
하나의 메서드 이름으로 비슷한 여러 기능을 구현할 때 쓴다.
// '타입... 변수명'
public void variableArguments(String a, int b, Object... obj) {}
인스턴스가 생성될 때 호출되는 '인스턴스 초기화 메서드'
class Car {
String color;
String gearType;
int door;
Car() { } // 생성자
Car(String c, String g, int d) { // 매개변수가 있는 생성자
this.color = c;
this.gearType = g;
this.door = d;
}
}
->
Car c = new Car("white", "auto", 4)
Car(String color) {
door = 5; // 첫 번째 줄
Car(color, "auto", 4); // 에러1. 생성자의 두 번째 줄에서 다른 생성자 호출
} // 에러2. this(color, "auto", 4); 로 해야 함
class Car {
String color;
String gearType;
int door;
Car() {
this("white", "auto", 4);
}
Car(String color, String gearType, int door) {
this.color = color;
this.gearType = gearType;
this.door = door;
}
Car(Car c) { // 인스턴스 복사를 위한 생성자
this(c.color, c.gearType, c.door);
}
}
// 인스턴스 복사
Car c1 = new Car();
Car c2 = new Car(c1); // c1의 복사본 c2를 생성
class Car {
int x; // 인스턴스 변수
int y = x; // 인스턴스 변수
void method() {
int i; // 지역 변수
int j = i; // 에러. 지역변수를 초기화하지 않고 사용
}
}
멤버변수(클래스, 인스턴스 변수)와 배열의 초기화는 선택적이지만, 지역변수의 초기화는 필수
자료형 | 기본값 |
---|---|
boolean | false |
char | '\u0000' |
byte, short, int | 0 |
long | 0L |
float | 0.0f |
double | 0.0d 또는 0.0 |
참조형 변수 | null |
변수의 선언과 동시에 초기화하는 것을 명시적 초기화라고 한다.
가장 기본적이면서도 간단한 초기화 방법으로 여러 초기화 방법 중에서 가장 우선적으로 고려되어야 한다.class Car { int door = 4; // 기본형(primitive type) 변수의 초기화 Engine e = new Engine(); // 참조형(reference type) 변수의 초기화 //... }
class InitBlock {
static { /* 클래스 초기화 블럭 */ }
{ /* 인스턴스 초기화 블럭 */}
}
초기화 시점 | 초기화 순서 | |
---|---|---|
클래스 변수 | 클래스가 처음 로딩될 때 단 한 번 초기화된다. | 기본값 ➡ 명시적 초기화 ➡ 클래스 초기화 블럭 |
인스턴스 변수 | 인스턴스가 생성될 때마다 각 인스턴스별로 초기화된다. | 기본값 ➡ 명시적 초기화 ➡ 인스턴스 초기화 블럭 ➡ 생성자 |
기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것
- 적은 양의 코드로 새로운 클래스를 작성
- 코드를 콩통적으로 관리할 수 있기 때문에 코드의 추가 및 변경이 용이
- 코드의 재사용성을 높이고 코드의 중복을 제거하여 프로그램의 생산성과 유지보수에 기여
- 생성자와 초기화 블럭은 상속되지 않는다. 멤버만 상속된다.
- 자손 클래스의 멤버 개수는 조상 클래스보다 항상 같거나 많다.
❗접근 제어자가 private 또는 default인 멤버들은 상속은 받지만 자손 클래스로부터의 접근이 제한된다.
자바는 단일 상속만을 허용한다. (C++은 다중상속 허용)
class TVCR extends TV, VCR {} // 에러. 조상은 하나만 허용
class TVCR : public TV, public VCR {}
👍 장점
- 다중 상속을 허용하면 여러 클래스로부터 상속받을 수 있기 때문에 복합적인 기능을 가진 클래스를 쉽게 작성할 수 있다.
👎 단점
- 클래스간의 관계가 매우 복잡해진다.
- 서로 다른 클래스로부터 상속받은 멤버간의 이름이 같은 경우 구별할 수 있는 방법이 없다.
Object클래스는 모든 클래스 상속계층도의 최상위에 있는 조상클래스이다.
상속을 받지 않은 Tv클래스를 정의해보자.
class TV {}
위의 코드를 컴파일하면 컴파일러는 위의 코드를 다음과 같이 자동적으로 'extends Object'를 추가해준다.
class Tv extends Object {}
❗ 이미 어떤 클래스로부터 상속받도록 작성된 클래스에 대해서는 컴파일러가 'extends Object'를 추가하지 않는다.
class Tv {}
class CaptionTv extends TV {}
위와 같은 클래스 관계의 상속 계층도는 아래 그림과 같다.
모든 상속계층도의 최상위에는 Object클래스가 위치한다.
그래서 자바의 모든 클래스들은 Object클래스에 정의된 멤버들을 사용할 수 있다.
그동안 toString()이나 equals(Object o)와 같은 메서드를 따로 정의하지 않고도 사용할 수 있었던 이유는 이 메서드들이 Object클래스에 정의된 것들이기 때문이다.
조상 클래스로부터 상속받은 메서드의 내용을 변경하는 것을 오버라이딩이라고 한다.
상속받은 메서드를 그대로 사용하기도 하지만, 자손 클래스 자신에 맞게 변경해야 하는 경우가 많다.
이럴 때 오버라이딩을 한다.
- 이름이 같아야 한다.
- 매개변수가 같아야 한다.
- 반환타입이 같아야 한다.
- 접근 제어자는 조상 클래스의 메서드보다 좁은 범위로 변경할 수 없다.
- public ➡ protected ➡ default ➡ private
- 예외는 조상 클래스의 메서드보다 많이 선언할 수 없다.
- 인스턴스 메서드를 static메서드 또는 그 반대로 변경할 수 없다.
Q. 조상 클래스에 정의된 static 메서드를 자손 클래스에서 똑같은 이름의 static메서드로 정의할 수 있나요?
A. 가능하지만 오버라이딩이 아니다. static멤버들은 자신들이 정의된 클래스에 묶여있다고 생각해야 함.
- 객체 자신을 갈리키는 참조변수.
- 상속받은 멤버와 자신의 클래스에 정의된 멤버의 이름이 같을 때 super를 붙여서 구별한다.
class SuperTest {
public static void main(String[] args) {
Child c = new Child();
c.method();
}
}
class Parent{
int x =10; // super.x
}
class Child extends Parent{
int x = 20;
void method() {
System.out.println("x=" + x);
System.out.println("this.x=" + this.x);
System.out.println("super.x="+super.x);
}
}
/** 결과
* x=20
* this.x=20
* super.x=10
**/
- 조상의 생성자를 호출할 때 사용
- 조상의 멤버는 조상의 생성자를 호출해서 초기화 (조상이 먼저 실행되어야 함)
class JavaApp {
public static void main(String[] args) {
Point3D point3d = new Point3D(); // Point3D() 생성자로 초기화 및 인스턴스 생성
System.out.println("point3d.x=" + point3d.x);
System.out.println("point3d.y=" + point3d.y);
System.out.println("point3d.z=" + point3d.z);
}
}
class Point {
int x = 10;
int y = 20;
#1 Point() {
this(0, 0);
}
Point(int x, int y) {
// 생성자의 첫줄에 다른 생성자를 호출하지 않았기 때문에,
// 컴파일러가 이 부분에 super()를 호출합니다.
// 부모 클래스이므로 Object 클래스의 super()가 호출됩니다.
this.x = x;
this.y = y;
}
}
class Point3D extends Point {
int z = 30;
Point3D() {
this(100, 200, 300); // 자신의 클래스의 또다른 생성자 호출
}
Point3D(int x, int y, int z) {
super(x, y); // 부모 클래스 생성자 호출 or #1번을 넣어야 함
this.z = z;
}
}
/** 결과
* p3.x=100
* p3.y=200
* p3.z=300
**/
제어자는 클래스, 변수 또는 메서드의 선언부에 함께 사용되어 부가적인 의미를 부여한다.
제어자의 종류는 크게 접근 제어자와 그 외의 제어자로 나눌 수 있다.
인스턴스 변수는 하나의 클래스로부터 생성되었더라도 각기 다른 값을 유지하지만, 클래스변수(static멤버변수)는 인스턴스에 관계없이 같은 값을 갖는다.
대상 | 의미 |
---|---|
멤버변수 | - 모든 인스턴스에 공통적으로 사용되는 클래스변수가 된다. - 클래스 변수는 인스턴스를 생성하지 않고도 사용이 가능하다. - 클래스가 메모리에 로드될 때 생성된다. |
메서드 | - 인스턴스를 생성하지 않고도 호출이 가능한 static 메서드가 된다. - static메서드 내에서는 인스턴스 멤버들을 직접 사용할 수 없다. |
class StaticTest {
static int width = 200; // 클래스 변수 (static변수)
static int height = 120;// 클래스 변수 (static변수)
static {
// static변수의 복잡한 초기화 수행
}
static int max(int a, int b) { // 클래스 메서드 (static 메서드)
return a > b ? a : b;
}
}
- 변수에 사용하면 값을 변경할 수 없는 상수가 된다
- 메서드에 사용하면 오버라이딩을 할 수 없게 된다.
- 클래스에 사용하게 되면 자신을 확장하는 자손클래스를 정의하지 못하게 된다.
대상 | 의미 |
---|---|
클래스 | 변경될 수 없는 클래스, 확장될 수 없는 클래스가 된다. 그래서 final로 지정된 클래스는 다른 클래스의 조상이 될 수 없다. |
메서드 | 변경될 수 없는 메서드, final로 지정된 메서드는 오버라이딩을 통해 재정의 될 수 없다. |
멤버변수 | 변수 앞에 final이 붙으면 값을 변경할 수 없는 상수가 된다. |
지역변수 | "" |
final class FinalTest { // 조상이 될 수 없는 클래스
final int MAX_SIZE = 10; // 값을 변경할 수 없는 멤버변수 (상수)
final void getMaxSize() { // 오버라이딩할 수 ㅇ벗는 메서드 (변경 불가)
final int LV = MAX_SIZE; // 값을 변경할 수 없는 지역변수 (상수)
return MAX_SIZE;
}
}
메서드의 선언부만 작성하고 실제 수행내용은 구현하지 않은 추상 메서드를 선언하는데 사용한다.
대상 | 의미 |
---|---|
클래스 | 클래스 내에 추상 메서드가 선언되어 있음을 의미한다. |
메서드 | 선언부만 작성하고 구현부는 작성하지 않은 추상 메서드임을 알린다. |
abstract class AbstractTest { // 추상 클래스 (추상 메서드를 포함한 클래스)
abstract void move(); // 추상 메서드 (구현부가 없는 메서드)
}
- 멤버 또는 클래스에 사용되어, 해당하는 멤버 또는 클래스를 외부에서 접근하지 못하도록 제한하는 역할을 한다.
- 접근 제어자가 default임을 알리기 위해 실제로 default를 붙이지는 않는다. 클래스나 멤버변수, 메서드, 생성자에 접근 제어자가 있지 않다면, 접근 제어자가 default임을 뜻한다.
제어자 | 같은 클래스 | 같은 패키지 | 자손 클래스 | 전체 |
---|---|---|---|---|
public | ○ | ○ | ○ | ○ |
protected | ○ | ○ | ○ | X |
default | ○ | ○ | X | X |
private | ○ | X | X | X |
public > protected > default > private
클래스나 멤버, 주로 멤버에 접근 제어자를 사용하는 이유는 클래스 내부에 있는 선언된 데이터를 보호하기 위해서이다.
데이터를 외부에서 함부로 변경하지 못하도록 하기 위해서 외부로부터 접근을 제한하는 것이 필요하다.
이것을 데이터 감추기(data hiding)라고 하고 하며, 객체지향개념의 캡슐화(encapsulation)에 해당하는 내용이다.접근 제어자를 사용하는 이유
- 외부로부터 데이터를 보호하기 위해서
- 외부에는 불필요한, 내부적으로만 사용되는, 부분을 감추기 위해서
public class Time {
public int hour;
public int minute;
public int second;
}
// 인스턴스 생성
Time t = new Time();
t.hour = 123456;
위와 같이 접근제어자를 크게 열어두게 되면 외부에서 데이터를 아무렇게 변경할 수 있어서 개발시 매우 치명적이다.
보통은 멤버변수를 private, protected로 제한하고 멤버변수를 읽고 쓸 변경할 수 있는 public 메서드를 제공함으로써 간접적으로 멤버변수 값을 다룰 수 있도록 하는 것이 바람직하다.
✡ 쉽게 getter, setter를 생각하면 된다.
생성자에 접근 제어자를 사용함으로써 인스턴스의 생성을 제한할 수 있다.
생성자의 접근 제어자를 private으로 지정하면, 외부에서 생성자에 접근할 수 없으므로 인스턴스를 생성할 수 없게 된다.
final class Singleton { // final 키워드 추가해서 상속할 수 없는 클래스라는 것을 명시
private static Singleton s = new Singleton();
// 기본 생성자를 private으로 지정하여 외부에서 접근할 수 없게 한다.
private Singleton() { ... }
// 인스턴스를 생성하지 않고도 호출할 수 있어야 하므로 static이어야 한다.
public static Songleton getInstance() {
if(s == null) {
s = new Singleton();
}
return s;
}
생성자가 private인 클래스는 다른 클래스의 조상이 될 수 없다.
왜냐하면, 자손클래스의 인스턴스를 생성할 때 조상클래스의 생성자를 호출해야만 하는데, 생성자의 접근 제어자가 private이므로 자손 클래스에서 호출하는 것이 불가능하기 때문이다.
그래서 클래스 앞에 final을 더 추가하여 상속할 수 없는 클래스라는 것을 알리는 것이 좋다.
✡ 시간이 나면 Sngleton에 대해서도 공부해보자.
대상 | 사용가능한 제어자 |
---|---|
클래스 | public, default, final, abstract |
메서드 | 모든 접근 제어자, final, abstract, static |
멤버변수 | 모든 접근 제어자, final, static |
지역변수 | final |
- 메서드에 static과 abstract를 함께 사용할 수 없다.
- static메서드는 몸통이 있는 메서드에만 사용할 수 있기 때문이다.
- 클래스에 abstract와 final을 동시에 사용할 수 없다.
- 클래스에 사용되는 final은 클래스를 확장할 수 없다는 의미이고 abstract는 상속을 통해서 완성되어야 한다는 의미이므로 서로 모순이 되기 때문이다.
- abstract메서드의 접근 제어자가 private일 수 없다.
- abstract메서드는 자손클래스에서 구현해주어야 하는데 접근 제어자가 private이면, 자손 클래스에서 접근할 수 없기 때문이다.
- 메서드에 private와 final을 같이 사용할 필요는 없다.
- 접근 제어자가 private인 메서드는 오버라이딩 될 수 없기 때문이다.
이 둘 중 하나만 사용해도 의미가 충분하다.
'여러 가지 형태를 가질 수 있는 능력'을 의미하며, 자바에서는 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 하는 것이다.
조금 더 구체적으로 말하면, 조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있는는 것.