자바는 객체 지향 언어로, 객체 지향 프로그래밍을 하기에 용이하다.
객체 지향 프로그래밍이란, 객체를 정의하고, 객체 사이에 협력을 구현하는 프로그래밍이라고 한다.
먼저, 객체는 우리 주변의 모든 것들이다.
눈에 보이는 모든 사물, 그리고 어떤 행동을 나타내는 단어도 객체라고 할 수 있다.
즉, 객체는 어떤 대상을 의미한다.
예를 들어 학생이 카페에서 커피를 사서 마신다고 가정해보자.
여기서 객체는 아마 학생, 카페, 커피 등이 될 것이다.
그렇다면 이 객체들끼리 하는 상호작용에는 무엇이 있을까?
학생이라는 객체가 카페 객체에게 커피를 주문한다.
카페라는 객체는 학생 객체에게 커피를 주문받는다.
학생 객체는 커피를 받은 대가로 카페 객체에 돈을 지불한다.
학생 객체는 커피 객체를 마신다.
...
학생과 카페 사이에는 주문이라는 상호작용이 일어난다.
학생과 커피 사이에는 구매 및 소비라는 상호작용이 일어난다.
이와 같이 객체를 중심으로 객체들 간의 상호작용을 구현하는 것이 객체 지향 프로그래밍이다.
Write Once, Run Anywhere
Write Once, Run Anywhere
은 자바의 슬로건으로, 문장에서 잘 나타나듯이 자바는 플랫폼에 종속되지 않는 프로그램을 만들 수 있다.
C언어로 만든 프로그램은 컴파일 할 때의 운영체제에 맞게 만들어지기 때문에 프로그램을 실행하는 환경이 바뀌면 실행할 수 없다. 만약 Mac에서 컴파일했다면 이 실행파일은 Window에서 실행할 수 없게 되는 것이다.
이와 달리 자바는 java
파일을 컴파일하면, 1차적으로 class
파일이 생성되는데, 이 파일을 바이트 코드라고 한다. 바이트 코드는 운영체제에 맞는 완벽한 기계어가 아니므로, C언어의 실행파일처럼 바로 실행시킬 수 없다.
바이트 코드를 실행하려면 자바 가상 머신(JVM)이 필요한데, JVM은 바이트 코드를 운영체제에 맞는 실행파일로 바꿔준다. 즉 JVM 과 class
파일만 있다면, 어느 환경에서나 실행파일을 실행시킬 수 있다.
java는 C언어와 달리 계층 구조가 명확한 편이기 때문에 파일 생성할 때 구조를 지켜서 만들어야 한다. 대략적인 구조는 아래와 같다.
|- workspace
|- project1 'Practice'
|- bin
|- package1 'one'
|- class 'A.class'
|- class 'B.class'
|- class 'C.class'
|- package2 'two'
|- class 'A.class'
|- class 'B.class'
|- class 'C.class'
|- src
|- package1 'one'
|- class 'A.java'
|- class 'B.java'
|- class 'C.java'
|- package2 'two'
|- class 'A.java'
|- class 'B.java'
|- class 'C.java'
|- project2 `Practice`
|- bin
|- package1 `zero`
|- package1_1 `zeroOne`
|- class `test.class`
|- package1_2 `zeroTwo`
|- class `test.class`
|- src
|- package1 `zero`
|- package1_1 `zeroOne`
|- class `test.java`
|- package1_2 `zeroTwo`
|- class `test.java`
workspace
: 하나의 프로그램 단위project
: 하나의 실행파일 단위package
: 클래스를 기능별로 묶어둔 일종의 카테고리로, 하나만 있을 수도 있고, 상위/하위 패키지로 나눌 수도 있다.class
: 비슷한 유형의 메소드와 변수를 모아놓은 소스 코드src
폴더 안에 패키지와 클래스가 있어야 하고, 1차적으로 컴파일 된 class 파일이 bin
폴더 안에 저장된다.
클래스들 중에서 main()
함수가 있는 클래스가 프로그램의 시작 클래스가 된다.
그래서 만약 클래스가 제대로 수행되는지 테스트해보고 싶을 때 클래스 내부에 main()
함수를 만들고 실행하거나, main()
함수를 실행시킬 클래스를 따로 만들어 실행하기도 한다.
Hello world!
를 출력하고 싶다면HelloWorld.java
package helloworld;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello world!");
}
}
HelloWorld.java
의 위치
|- workspace
|- project 'Hello'
|- bin
|- package 'helloworld'
|- class 'HelloWorld.class'
|- src
|- package 'helloworld'
|- class 'HelloWorld.java'
Shell
에서 자바 파일 실행해보기javac [파일명].java
java [파일명]
객체는 간단히 말해서 '어떤 대상'을 의미한다. 존재하는 모든 사물, 또는 어떤 행동을 나타내는 단어(예를 들어 주문, 생산 등)를 포함 모두 객체라고 할 수 있다.
객체 프로그래밍에서는 이러한 객체들을 모델링하여 프로그램에 구현하고 사용하게 된다.
객체의 속성과 기능을 코드로 구현한 것으로, 객체의 설계도라고 할 수 있다.
개인적으로 클래스는, 실제 세상의 물체를 프로그램에서 사용하고 싶어 코드로써 간략히 구현한 것으로 이해하고 있다.
('간략히'라고 표현한 것은, 실제 객체의 모든 속성과 기능을 코드화 할 수 없고, 애초에 그럴 필요도 없기 때문이다. 프로그램에서 필요한 속성과 기능만 구현하면 된다.)
이처럼 코드로 객체를 구현하는 것을 클래스를 정의한다고 표현하며, 클래스를 정의하려면, 클래스 이름, 속성 및 특성(멤버 변수), 기능(메서드)이 필요하다.
위에서 객체는 실세계의 모든 대상이고, 클래스는 이러한 객체들을 코드상에 구현한 일종의 설계도라고 표현하였다. 붕어빵 틀을 사용하여 붕어빵을 만들듯이, 객체의 설계도인 클래스를 사용하여 프로그램상에 객체를 만들어낼 수 있다.
예를 들어 내가 '학생' 이라는 클래스를 만들어두었다면, 이 클래스를 이용하여 같은 기능을 하는 다양한 학생들(학생1, 학생2, ...)을 만들 수 있다.
이때, 학생들(학생1, 학생2, ...)은 모두 '학생'이지만 동시에 각각 다른 객체이다.
실세계에서 학교에 다니는 학생들을 '학생'이라고 묶어서 표현할 수 있지만, 이름, 성별, 학년, 나이 등 각자 고유의 속성을 갖고 있는 다른 개체인 것과 같은 이치다.
또 다른 예를 들어보자면,
고양이는 클래스이다.
우리집 고양이 나비와, 옆집 고양이 삼색이는 객체다.
김영희와 박철수, 그리고 최민수 모두 사람이지만, 전부 다른 사람이다.
여기서 클래스는 사람이고, 김영희, 박철수, 최민수는 객체가 된다.
객체는 속성을 갖고 있다.
만약 학생이라는 객체가 있다면 학생 객체의 속성은 이름, 학번, 전공 등이 될 것이다.
객체를 클래스로 구현할 때, 객체가 가진 속성은 멤버 변수로 구현된다.
멤버 변수는 다른 말로 '속성'이나 '특성'이라고도 한다.
멤버 변수는 속성에 알맞은 자료형으로 선언해주어야한다.
이름이면 String
, 학번이면 int
자료형으로 선언하면 될 것이다.
public class Student {
/* 멤버 변수 */
int id; // 학번
String name; // 이름
int grade; // 학년
String major; // 전공
/* 학번, 이름, 학년, 전공 모두 'Student'의 속성을 나타내는 변수 */
}
메서드는 함수의 한 종류로, 함수에 객체 지향 개념이 포함된 용어이다. 간단히 말해 클래스에서 사용하는 함수다. 메서드는 클래스의 멤버 변수를 활용하여 클래스의 기능을 구현한다.
public class Student {
/* 멤버 변수 */
int id;
String name;
int grade;
String major;
/* 메서드 */
public void showStudentInfo() {
System.out.println(name + ',' + major);
} // 멤버 변수를 활용하여 학생의 정보를 출력하는 기능을 하는 메서드
public String getStudentName() {
return name;
} // 학생의 이름을 반환해주는 메서드
public void setStudentName(String studentName) {
name = studentName;
} // 학생의 이름을 설정해주는 메서드
}
함수가 호출되면 그 함수만을 위한 메모리 공간이 할당되는데, 이 메모리 공간을 스택이라고 한다. (스택 메모리는 이후에 인스턴스에 관한 개념에서 등장하는 힙 메모리와 다른 메모리 공간이다.)
스택은 LIFO(Last In First Out) 구조로 호출된 순서의 역순으로 메모리 공간이 해제된다.
가장 처음에 호출되는 main()
함수가 A()
함수를 호출하고, A()
함수가 B1()
과 B2()
함수를 차례로 호출한다고 해보자. 전체적인 흐름은 아래와 같으며, 함수의 매개변수나 지역변수 및 반환값은 함수가 반환되는 순간 메모리 공간에서 해제 된다.
호출되는 순서 :
main()
->A()
->B1()
->B2()
반환되는 순서 :
B2()
->B1()
->A()
->main()