자바는 객체 지향 프로그래밍 언어, Object Oriented Programming (OOP)로서 객체를 활용하는 것을 지향한다.
객체를 사용할 때 우리는 클래스라는 틀을 만들어서 사용한다. 이론과 함께 예시를 살펴보면서 이해하도록 하자.
💡객체
객체는 사전적인 정의로 실제 존재하는 것을 말한다. 객체지향 이론에서는 사물과 같은 유형적인 것뿐만 아니라, 개념이나 논리와 같은 무형적인 것(성격, 기분)들도 객체로 간주하며, 프로그래밍에서의 객체는 클래스에 정의된 내용대로 메모리에 생성된 것을 뜻한다.
한 가지 예시로 맥도날드는 2000년대에는 주문을 사람이 받았다. 그러나 지금은 모든 주문을 키오스크가 받고 있다. 이처럼 사람이 하던 일을 컴퓨터로 옮겨 놓는 과정에서 어떤 요소를 인식하고 구분짓기 위한 개념이 필요했고, 이를 객체로 표현하기로 한 것이다. 따라서 객체는 전산 용어보다는 철학 용어에 가깝다.
객체는 데이터 + 행동의 집합으로 정의한다. 이때 데이터는 멤버변수, 행동은 메소드이다.
isaac을 예시로 들면 isaac = 데이터(나이, 몸무게, 피부색, 머리색) + 행동(말한다, 먹는다, 잔다, 달린다, 생각한다)로 나타낼 수 있다.
💡클래스
클래스는 객체를 분류하는 단위이자 객체를 생성하는 단위이다.
클래스는 추상적인 단위로서 눈에 보이지 않는다.
클래스 예시 (붕어빵, 붕어빵틀)
a. 붕어빵틀을 만든다. > 클래스
b. a를 사용해서 붕어빵을 만든다. > 객체
c. 붕어빵을 먹는다.
이는 객체와 클래스 간의 사이를 잘 나타내는 예시이다. 실제로는 객체(Object)를 사용하는 것이 목적인데, 그 객체를 설계하는 과정을 클래스라고 한다. 설계하고 정의를 하면서 클래스(틀)가 만들어진다. 이를 가지고 실제적으로 눈에 보이는 객체(Object)를 만들 수 있다.
클래스의 사용
지도를 만드는 중인데, 우리 집과 마트의 좌표(위도, 경도)가 있다고 하자.
이를 저장할 수 있는 방법으로는 넘버링하는 방법, 배열을 사용하는 방법, 클래스를 사용하는 방법이 있다.
3가지 방법을 살펴보며 클래스를 사용하는 이유에 대해 알아보도록 하자.
Case 1. 넘버링
// 우리집 좌표
int x = 100;
int y = 200;
System.out.printf("우리집은 [%d,%d]에 위치합니다.\n", x, y);
// 마트 좌표
int x2 = 300;
int y2 = 400;
System.out.printf("마트는 [%d,%d]에 위치합니다.\n", x2, y2);
넘버링은 변수 뒤에 숫자를 바꿔 선언하는 가장 간단한 방법이다.
하지만 이러한 방식은 같은 성질의 식별자가 서로 다른 이름을 쓰고 있다는 문제와 한 쌍의 집합을 물리적으로 관리하는 게 불가능하다는 문제가 발생한다.
경험상으로 이름이 중복되는 것을 방지하기 위해 사용했다는 것이 이해가 되지만, 엄밀히 따지면 다른 이름에 데이터가 저장된 것이다. 심지어 x의 짝꿍이 y라는 것도 경험상 이해되는 것이지, x의 짝꿍이 y2라는 것일지도 모르는 일이다. 이는 개발자만이 알고 있는 것이다.
넘버링 방식을 사용하다보면 나중에 실수가 생길 확률이 굉장히 커지고 논리적인 오류가 발생하며 오류의 원인을 찾기가 힘들어진다. 또한 구조가 없고, 규칙이 애매하기 때문에 조작이 불편해진다.
Case 2. 배열
// 우리집 좌표
int[] a1 = {100, 200};
System.out.printf("우리집은 [%d,%d]에 위치합니다.\n", a1[0], a1[1]);
// 마트 좌표
int[] a2 = {300, 400};
System.out.printf("마트는 [%d,%d]에 위치합니다.\n", a2[0], a2[1]);
배열은 x와 y를 물리적으로 한 쌍으로 만들면서 데이터를 집합으로 관리하므로 구조가 생겼다는 장점이 있다.
그러나 의미를 가지지 않는 배열첨자를 사용하기 때문에 요소간의 성질을 구분하기 힘들다. 몇 번째 방에 어떤 데이터를 넣었는지 모두 기억할 수 없으므로 관리가 어렵다.
Case 3. 클래스
class Point {
public int x;
public int y;
}
// 우리집 좌표
Point p1 = new Point();
p1.x = 100;
p1.y = 200;
System.out.printf("우리집은 [%d,%d]에 위치합니다.\n", p1.x, p1.y);
// 마트 좌표
Point p2 = new Point();
p2.x = 300;
p2.y = 400;
System.out.printf("마트는 [%d,%d]에 위치합니다.\n", p2.x, p2.y);
클래스는 배열과 같은 데이터의 집합이다.
클래스를 사용하면 데이터 저장 구조가 생성되며 관리가 수월해지며, 같은 클래스의 객체이면 멤버 이름이 동일하여 데이터의 의미가 명확해진다.
그리고 배열과 달리 멤버 변수의 이름이 존재하기 때문에 멤버의 의미가 명확해진다는 장점이 있다.
클래스의 단점으로는 클래스 선언 비용이 발생하는데, 이것이 고가라는 점이다.
클래스의 구조
public class Ex_Class {
public static void main(String[] args) {
// 학생 한 명의 이름, 성적
Score isaac = new Score();
isaac.name = "아이작";
isaac.kor = 100;
isaac.eng = 90;
isaac.math = 80;
System.out.println(isaac.name);
System.out.println(isaac.kor);
System.out.println(isaac.eng);
System.out.println(isaac.math);
isaac.hello();
}// main
}// class
class Score {
public String name;
public int kor;
public int eng;
public int math;
public void hello() {
System.out.printf("안녕하세요. 저는 %s입니다.\n", name);
}
}
클래스는 public class Ex_Class의 바깥에 생성된다는 특징이 있다.
클래스명은 파스칼 표기법으로 작성하며, 클래스 내부에는 클래스 멤버(변수 or 메소드)가 선언된다.
이때 선언되는 변수는 클래스의 멤버 변수로, 지역 변수와 반대되는 개념이다.
클래스명 변수명 = new 생성자();
위와 같이 new를 이용해 객체를 생성하며, 위 소스코드에서는 isaac 객체 참조 변수를 만들고, isaac.name과 isaac.kor, isaac.eng, isaac.math 객체 멤버 변수를 생성한다. 단순히 객체 참조 변수를 객체라고 부르는 경우도 있지만 용어가 겹치는 경우가 있으므로, 상황을 고려하는 것이 좋다.
클래스 멤버 변수
클래스 멤버 변수는 개성있게 만들어주는 것과 동시에 유일하게 만들어주기 때문에 프로퍼티(특성)라고 부른다.
클래스 멤버 메소드
클래스 멤버 메소드는 멤버 변수를 활용해서 행동해야 하며, 호출되는 객체에 따라 다른 결과가 발생되어야 한다.
왜 그런지 잘못된 메소드 사용 예시를 살펴보도록 하자.
public class Ex_Class {
public static void main(String[] args) {
User u1 = new User();
u1.name = "Isaac";
u1.age = 20;
u1.hello(); // 안녕하세요.
User u2 = new User();
u2.name = "Sopia";
u2.age = 22;
u2.hello(); // 안녕하세요.
// u1, u2의 hello() 메소드가 동일한 결과를 출력하고 있다.
}
}
class User {
public String name;
public int age;
// 잘못 만들어진 메소드
public void hello() {
System.out.println("안녕하세요.");
}
}
이 소스코드에서 hello() 메소드는 굳이 따지자면 잘못 만들어진 메소드이다. 그 이유는 u1이 hello() 메소드를 호출하든 u2이 hello() 메소드를 호출하든 결과가 똑같고, 이는 메모리 낭비로 이어지기 때문이다.
두 사람에게 호출받는데 아무런 차이가 없다면 굳이 가지고 있을 필요가 없다. 그러므로 멤버 메소드는 반드시 멤버 변수를 활용하여 활용 가치를 높여야 한다.
클래스의 규칙
package com.test.java.obj;
public class Student {
public String name;
public int age;
public int kor;
public int eng;
public int math;
}
package com.test.java.obj;
public class Ex_Class {
public static void main(String[] args) {
// 학생 클래스
student.name = "Isaac";
student.age = 20;
student.kor = 90;
student.eng = 85;
student.math = 95;
System.out.println("Name: " + student.name);
System.out.println("Age: " + student.age);
System.out.println("Korean: " + student.kor);
System.out.println("English: " + student.eng);
System.out.println("Math: " + student.math);
}
}
파일과 클래스의 이름
클래스의 이름을 정할 때 반드시 지켜야 하는 규칙이 있다.
파일의 이름과 클래스의 이름이 반드시 동일해야 한다. 이를 지키지 않으면 컴파일 에러가 발생한다.
클래스 선언 권장 사항
클래스를 선언할 때 클래스 1개당 물리적인 파일 1개를 만들어야 하는 것이 대체로 권장된다. 하나의 *.java 안에 여러 개의 클래스를 선언하게 되면 관리하기가 힘들어지기 때문이다.
클래스의 영역
한 파일 내에서 2개의 클래스를 만들 때 파일 내의 모든 클래스 중 public 키워드를 가지는 클래스는 반드시 1개만 있어야 한다. public 클래스가 대표 클래스 역할을 하며, 대표 클래스의 이름이 파일명이 되기 때문이다. 또한 클래스는 같은 패키지 내에서 동일한 이름을 2개 이상 가질 수 없는데, 그 이유는 클래스의 영역은 물리적인 파일이 아닌 패키지이며, 작성하는 파일이 바뀌었다고 남이 되는 것이 아니기 때문이다.