[Java] 6. 클래스 Ⅰ: 객체의 구성
1. 객체 (Object)
객체란 물리적으로 존재하거나 추상적으로 생각할 수 있는 것 중에서 자신의 속성을 갖고, 다른 것들과 식별이 가능한 것을 의미한다.
현실 세계에서의 객체를 소프트웨어적인 관점으로 본다면, 구체적 혹은 추상적인 데이터의 크기를 의미하며, 현실 세계의 객체를 소프트웨어적인 객체로 설계하는 과정을 객체 모델링 이라고 한다.
1) 객체 지향 프로그래밍 vs. 절차 지향 프로그래밍
객체 지향 프로그래밍이란, C++, Java 와 같이 객체를 기반으로 하는 프로그래밍을 의미하며, 특징으로는 기능을 사용하려면 객체의 생성 위치에 상관없이, 객체를 먼저 생성한 후에 로직이 실행되는 구조를 가진다.
이에 반해 절차 지향 프로그래밍이란, 위에서부터 아래로 프로그램의 로직이 진행되는 프로그래밍을 의미하며, 객체 지향과 달리 순서가 매우 중요하며, 반드시 변수의 선언이 먼저 된 후에 로직을 작성한다.
2) 객체의 구성
객체는 크게 객체 내에서 사용되는 멤버 변수와 객체가 수행하는 기능을 구현한 메소드로 구성된다. 객체는 각각 독립적으로 존재하고, 다른 객체와 서로 상호작용을 할 때는 메소드를 이용해서 동작한다. 이를 메소드 호출이라고 하며, 객체명에 도트연산자를 붙이고 메소드 명을 작성하면 호출된다.
[메소드 호출]
반환 값 = 객체.메소드(매개값1, 매개값2, ... )
3) 객체간의 관계
앞서 객체는 각각 독립적으로 존재한다고 언급했지만, 일반적으로 다른 객체들과 관계를 맺고 있다. 서로 다른 객체간의 관계들은 다음과 같다.
- 집합 관계: 각 객체를 하나의 요소로 보며, 관계를 맺은 객체를 하나의 집합으로 보는 관계
- 사용 관계: 서로 다른 객체간의 상호작용(관계) 를 의미한다.
- 상속 관계: 상위 객체를 부모 객체로 보고, 하위 객체를 자식 객체로 봤을 때, 상위 객체의 구성요소를 하위 객체가 모두 물려받는 관계를 의미한다.
4) 객체의 특징
흔히, 객체지향 프로그래밍을 할 때의 장점 혹은 특징을 꼽으라고 하자면, 캡슐화, 상속, 다형성을 들 수 있다. 이번 절에서는 각각의 의미를 살펴보도록 하자.
(1) 캡슐화
객체지향 프로그래밍에서의 캡슐화라는 의미는 객체의 필드, 메소드를 하나로 묶어 실제 구현하는 내용을 감추는 것을 의미한다. 때문에 외부 객체의 입장에서는 이용하고자 하는 객체의 내부 구조를 알 수 없으며, 이용하려는 객체에서 제공하는 필드와 메소드만 사용할 수 있다.
이처럼 객체 내부의 필드와 메소드를 캡슐화 해서 보호하려는 이유는 외부로부터 잘못된 사용으로 인해 객체가 손상되는 것을 막기 위해서이다. 이를 위해 접근 제한자를 같이 사용해 줌으로써 객체의 필드와 메소드의 사용 범위를 제한하여, 외부로부터 객체를 보호한다.
(2) 상속
일반적으로 상속이란, 부모가 가지고 있는 재산을 자식에게 물려주는 것을 의미한다. 객체지향 프로그래밍에서도 상속의 개념이 존재하는데, 부모역할의 상위 객체가 자식역할인 하위 객체에게 본인이 갖고 있는 필드와 메소드를 물려주어 하위 객체에서 사용할 수 있게 해준다. 자세한 내용은 추후에 다시 다룰 예정이다.
(3) 다형성
다형성이란, 같은 타입이나, 실행 결과가 다양한 객체를 만들어 낼 수 있는 성질을 의미한다. 코드 측면에서, 다형성은 하나의 타입에 여러 객체를 대입함으로써 다양한 기능을 이용할 수 있도록 만들어 준다. Java의 경우에는 부모클래스 혹은 인터베이스의 타입 변환을 허용한다.
2. 클래스 (Class)
클래스는 객체를 찍어내는 일종의 거푸집, 틀 이라고 볼 수 있다. 객체를 코드화한 것이기 때문에, 객체지향 프로그래밍에서는 가장 기초가 되는 요소이기도 하다.
1) 클래스 명명 규칙
사용하고자 하는 객체를 구상했다면, 객체의 대표명을 설정해줘야하며, 해당 이름이 클래스이름으로 사용된다. 클래스 명은 다른 클래스와 식별할 목적으로 사용되기 때문에 자바에서는 아래와 같은 식별자 작성 규칙에 따라 생성되어야한다.
작성규칙 | 예시 | |
---|---|---|
1 | 하나 이상의 문자로 이뤄져야한다. | Car, SportsCar |
2 | 첫 글자로 숫자는 사용할 수 없다. | 3Car(x) |
3 | $, _ 이외의 특수문자는 사용할 수 없다. | $Car, _Car, @Car(x) |
4 | 예약어는 사용할 수 없다. | for(x), int(x) |
위의 내용을 보고, 한글로도 만들 수 있는지에 대해서 궁금증이 들 수 있다. 클래스 명은 한글이든, 영어든 상관없지만, 일반적으로 한글보다는 영어로 작성하는 것을 지향한다.
또한 클래스명이 여러 단어로 구성된 경우, 각 단어의 첫 문자는 대문자로 작성하는 것이 관례이다. 클래스 명까지 결정했다면, 클래스의 코드를 저장할 파일 역시 “클래스명.java” 형식으로 작성해야한다. 파일명 역시 대소문자 구분해서 작성해야하며, 반드시 클래스명과 동일해야 된다는 점을 유의하자.
일반적으로는 소스 파일당 하나의 클래스만 선언하지만, 2개 이상 선언하는 것도 가능하다. 단, 주의 사항으로 파일이름과 동일한 이름의 클래스명에만 public 접근제한자를 사용할 수 있다. 접근제한자에 대한 내용은 이 후에 다룰 내용이지만, 외부에서도 접근 허용을 하는 접근제한자인데, 만약 파일명과 일치하지 않는 클래스에 public 키워드를 사용하게 되면 컴파일 에러가 발생해 사용이 불가해진다.
따라서 가급적이면 하나의 클래스 파일에는 하나의 클래스만 선언해주는 것이 좋다.
2) 객체 생성과 클래스 변수
클래스를 선언한 다음 컴파일을 했다면 객체를 생성할 설계도가 완성된 것이다. 이제 main() 함수에서 실제로 클래스를 객체로 생성하는 것과 사용법을 좀 더 살펴보자.
[new 연산자 사용법 ]
new class_name();
[Java Code]
...
Ex01_Student student = new Ex01_Student();
...
new 연산자로 생성된 객체는 메모리 힙 영역에 생성되며, new 연산자의 결과를 담은 변수에는 힙 영역에 생성된 객체의 주소가 저장된다. 때문이 이 변수를 클래스변수 라고하며, 타입은 참조 타입에 해당한다.
간단하게 아래 학생에 대한 클래스를 생성하는 과정을 살펴보자.
[Java Code - Student.java]
public class Ex01_Student {
// 학생(학번, 이름, 주소)
public int studentId = 0;
public String name = null;
public String addr = null;
// showStudentInfo() 메소드 선언
public void showStudentInfo()
{
System.out.println("학번 : " + studentId);
System.out.println("학생명 : " + name);
System.out.println("주소 : " + addr);
}
}
위의 예시에서 studentId, name, addr 를 Student 객체의 멤버변수라고 할 수 있고, showStudentInfo() 는 Student 객체의 메소드라고 한다. 생성한 클래스를 사용할 때는 아래 예시에서처럼 사용 전에 무조건 선언을 해준 후에 도트연산자(.) 를 객체명 뒤에 붙여서 사용한다.
이 때, Ex01_Student 클래스로부터 만들어진 객체 student를 인스턴스 라고 하며, 이처럼 클래스를 실제 객체로 만드는 과정을 인스턴스화 라고 부른다. 자세한 내용은 아래에서 좀 더 살피도록 하자.
앞서 언급한 것처럼 클래스는 거푸집과 같은 역할이기 때문에 한 개 코드 내에서도 여러 개의 객체를 생성할 수 있다.
[Java Code]
import java.util.Scanner;
public class Ex01_StudentTest {
public static void main(String[] args)
{
//객체 생성
Ex01_Student student = new Ex01_Student();
Scanner sc = new Scanner(System.in); // 사용자 입력을 받기위한 객체 생성
// 변수에 대입
System.out.print("학번 : ");
student.studentId = Integer.parseInt(sc.nextLine().toString());
System.out.print("이름 : ");
student.name = sc.nextLine(); // 사용자 입력
System.out.print("주소 : ");
student.addr = sc.nextLine(); // 사용자 입력
System.out.println();
// 입력 정보 출력
student.showStudentInfo();
}
}
3) 클래스 구성 멤버
앞서 학생 클래스를 생성해봤다. 해당 코드를 보면 알 수 있듯이, 클래스에는 객체가 가져야할 구성요소가 몇가지 있다. 크게 필드, 생성자, 메소드가 해당되며, 해당 멤버들은 생략되거나 2개 이상 중복이 있을 수 있다.
(1) 필드
객체의 고유 데이터, 부품 객체, 상태 정보 등을 저장한다. 얼핏 보면, 변수와 비슷하나, 용도가 다르며, 변수의 경우 생성자 혹은 메소드 내에서만 사용되고, 생성자와 메소드가 실행 종료되면, 자동으로 소멸된다. 반면, 필드는 생성자와 메소드와는 별개로 존재하며, 객체가 소멸되는 시점에 같이 소멸된다.
(2) 생성자
new 연산자로 호출되는 특수 객체이며, 객체의 초기화를 담당한다. 필드의 초기화나 메소드를 호출해서 객체의 사용 준비를 하기 위해 존재하며, 메소드와 형태가 비슷하지만, 리턴타입이 없다는 것이 차이점이다.
(3) 메소드
객체의 실질적인 동작해 대한 부분을 담당하고 있다. 해당 코드는 중괄호로 감싸여져 있으며, 리턴타입을 갖고 있다. 메소드를 호출하게되면, 중괄호 블록 내의 모든 코드들이 일괄적으로 실행된다. 메소드는 필드를 읽고, 수정하는 역할도 있지만, 객체 간의 데이터를 전달해주는 역할도 담당한다. 때문에 외부로부터 매개값을 받을 수도 있고, 실행 후 결과를 리턴할 수도 있다.
4) 필드
앞선 절에서 필드는 객체 고유 데이터, 부품 객체, 객체의 현 상태 등의 데이터를 저장하는 역할이라 언급했다. 이번 절에서는 필드 선언 방법과 main() 에서의 사용법을 살펴보도록 하자.
(1) 필드 선언하기
필드는 클래스 내 어디서든 존재할 수 있다. 하지만 생성자나 메소드의 블록 내부에서는 선언될 수 없으니 사용에 중의하자. 이유는 해당 경우 모두 로컬 변수로 처리되기 때문이다.
이번에는 선언 방식을 살펴보자. 보통 필드를 클래스 멤버 변수라고도 부르는데, 선언 방식이 일반 변수의 선언 방식과 유사하기 때문이다.
[필드 선언]
필드타입 필드명 = 초기값;
여기서 필드 타입은 필드에 저장되는 데이터의 종류를 의미하고, 기본타입이나 참조타입 모두 올 수 있다. 초기값은 필드가 생성될 때 갖게 되는 값으로 생략이 가능하다. 만약 지정되지 않은 경우, 객체 생성 시 자동으로 기본 초기값으로 설정된다.
(2) 필드 사용하기
일반적으로 필드를 사용한다라고 하면, 객체의 필드를 읽고 변경하는 작업을 의미한다. 같은 클래스 내부의 생성자 혹은 메소드에서 사용하는 것은 변수를 사용하는 것과 유사하기에 생략하기로 하고, 클래스 외부, 즉, 다른 객체에서 사용하고자 할 때를 살펴보도록하자.
당연한 이야기지만, 해당 필드를 호출 및 사용하기 위해서는 반드시 필드가 속한 클래스를 객체로 만들어줘야 이용가능하다.
일반적인 변수를 사용할 때와의 차이점을 꼽자면, 변수는 자신이 선언된 생성자 또는 메소드 블록 내부에서만 사용가능한 반면, 필드의 경우 생성자의 모든 메소드에서 사용가능하다.
필드에 접근하는 방법은 클래스명.필드명 과 같이 도트연산자(.) 를 이용해서 접근할 수 있다. 예시로 앞서 만든 학생에 대한 정보를 출력하는 코드 중에서 필드의 값을 수정하는 부분을 살펴보자.
[Java Code]
...
//객체 생성
Ex01_Student student = new Ex01_Student();
...
// 변수에 대입
System.out.print("학번 : ");
student.studentId = Integer.parseInt(sc.nextLine().toString());
System.out.print("이름 : ");
student.name = sc.nextLine(); // 사용자 입력
System.out.print("주소 : ");
student.addr = sc.nextLine(); // 사용자 입력
...
위의 코드 예시처럼 먼저 student 객체를 생성한 후, student 클래스에 존재하는 각 필드의 값을 수정하는 것을 확인할 수 있다. 만약 위의 예시와 달리 별도의 초기값을 주지 않을 경우 필드 선언하기 에서 언급한 것처럼 각 데이터 타입별 초기 값이 자동으로 저장된다. 이에 대한 내용은 아래 표와 같으니 참고하기 바란다.
대분류 | 소분류 | 데이터 타입 | 초기값 |
---|---|---|---|
기본타입 | 정수타입 | byte char short int long |
0 \u0000 0 0 0L |
기본타입 | 실수타입 | float double |
0.0F 0.0 |
기본타입 | 논리타입 | boolean | false |
참조타입 | 참조타입 | 배열 클래스(String 포함) 인터페이스 |
null |
5) 생성자
생성자란 클래스로부터 객체를 생성할 때 호출되어, 객체의 초기화를 담당한다. 여기서 객체의 초기화란, 필드를 초기화하거나, 메소드를 호출해 객체를 사용할 준비를 하는 것을 의미한다.
생성자를 이용해 객체를 초기화 하는 것이 성공하게 되면, 메모리 중 힙(heap) 영역에 객체가 생성되며, 객체의 주소가 반환된다. 이 후 객체의 주소는 클래스 타입 변수에 저장되어 해당 객체에 접근할 때 사용된다.
(1) 기본 생성자
우선 생성자는 new 연산자를 이용해서 생성된다. 또한 모든 클래스는 생성자가 반드시 존재하며, 하나 이상의 생성자를 가질 수 있다는 것을 알고가자.
그 중 일반적으로 사용자가 생성자를 생성하지 않은 상태라면, 컴파일 시, 컴파일러에서 아래와 같이 중괄호 내부 블록 내용이 비어있는 생성자를 생성하게 된다. 이를 기본 생성자라고 한다.
[Java Code - 기본 생성자]
[public] 클래스명() { }
[Java Code]
public Student() {}
public 에 대한 내용은 이 후에 접근 제한자 부분에서 다룰 예정이기에, 간단히 설명하자면, 선언한 클래스가 public 으로 선언된 경우라면 기본 생성자에도 public 이 붙지만, 아닌 경우에는 생략이 되기에 위와 같이 표현한 것이니 참고하기 바란다.
위 처럼 컴파일러에서 자동으로 기본 생성자를 생성해 주기 때문에, 사용자가 생성자 선언을 생략하더라도 정상적으로 컴파일 되는 것이다. 예시로 앞서 본 학생클래스의 내용을 보면 student () { } 라는 생성자 선언 부분이 생략되었다는 것을 알 수 있다. 그럼에도 main 함수에서는 student 객체를 정상적으로 생성하였고, 필드값을 수정하는 등 student 클래스의 객체화가 정상적으로 이뤄진 것을 미뤄볼 때, 컴파일러에서 기본 생성자를 생성해주는 것을 확인했다.
(2) 생성자 선언
생성자는 메소드와 비슷한 형태이지만, 리턴 타입이 없고 클래스 명과 동일하게 선언된다. 생성자 블록 내부에서 객체 초기화 과정에 대한 코드를 작성하면 되고, 일반적으로는 필드의 초기화와 메소드를 호출하는 경우가 많다.
예시로 학생의 이름인 name 필드를 입력이름으로 초기화 하는 것을 살펴보자.
[Java Code]
...
public String name;
...
public Student(String inputName)
{
name = inputName;
}
...
위와 같이 기본 생성자가 아닌 생성자가 명시적으로 선언되어 있을 경우에는 반드시 선언된 생성자를 호출해서 객체를 생성해야한다.
(3) 필드 초기화 - 생성자 이용하기
이번에는 생성자를 이용해서 필드를 초기화 하는 방법을 알아보자. 클래스로부터 객체가 생성될 때 별도의 초기 값을 선언하지 않으면, 기본 초기값으로 자동 설정된다고 언급했다.
만약 클래스를 객체로 선언하는 시기에 초기값을 부여하고자 한다면, 생성자를 이용해서 구현할 수 있다. 이 방법은 주로 객체 생성 시점에 외부에서 다양한 값으로 초기화되어야하는 경우에 적절하다. 따라서 생성자를 선언할 때 매개값으로 값을 다양하게 받아 초기화하는 것이 맞다.
[Java Code]
import java.util.Scanner;
public class Ex05_Constructor {
public static void main(String[] args)
{
// 변수 선언 및 초기화
int id = 0;
String name = "";
String addr = "";
Scanner sc = new Scanner(System.in);
// 변수에 대입
System.out.print("학번 : ");
id = Integer.parseInt(sc.nextLine().toString());
System.out.print("이름 : ");
name = sc.nextLine();
System.out.print("주소 : ");
addr = sc.nextLine();
System.out.println();
// 객체 생성
// - 입력값이 존재하는 경우 입력을 받은 후에 생성한다.
Student student1 = new Student(id, name, addr);
// 입력 정보 출력
student1.showStudentInfo();
}
}
[실행 결과]
<입력>
학번 : 1
이름 : 김길현
주소 : 김포시
<출력>
학번 : 1
학생명 : 김길현
주소 : 김포시
학번 : 1
학생명 : 김길현
주소 : null
위의 코드에서처럼 입력으로 받은 변수와 값을 사용해 student1, 2 객체를 생성하는 것을 확인할 수 있다.
(4) 생성자 오버로딩
외부에서 제공되는 다양한 데이터들을 이용해서 객체를 초기화하려면 생성자의 종류도 다양화될 필요가 있기 때문에 매개변수를 달리해서 생성자를 여러 개 생성하는 방법을 의미한다.
이 때, 매개 변수 타입과 개수, 선언 순서가 똑같고, 매개 변수명만 다른 것은 생성자 오버로딩으로 보지 않는다.
또한 생성자가 오버로딩 되어있다면, new 연산자로 생성자를 호출할 때 제공되는 매개값의 타입과 매개 값의 수에 의해 호출되는 생성자가 결정된다.
[Java Code]
// 기본 생성자
public Student() {}
// 생성자 오버로딩
public Student(String inputName)
{
name = inputName;
}
public Student(int inputId, String inputName)
{
studentId = inputId;
name = inputName;
}
public Student(int inputId, String inputName, String inputAddr)
{
studentId = inputId;
name = inputName;
addr = inputAddr;
}
위의 내용처럼 기본생성자를 포함해 총 4개의 생성자가 이름은 같지만, 매개변수의 값, 매개 변수 숫자, 매개변수 선언 순서가 다르다는 것을 확인할 수 있다. 좀 더 확실하게 알아보기 위해 아래의 코드를 실행해보자.
[Java Code]
import java.util.Scanner;
public class Ex05_Constructor {
public static void main(String[] args)
{
// 변수 선언 및 초기화
int id = 0;
String name = "";
String addr = "";
Scanner sc = new Scanner(System.in);
// 변수에 대입
System.out.print("학번 : ");
id = Integer.parseInt(sc.nextLine().toString());
System.out.print("이름 : ");
name = sc.nextLine();
System.out.print("주소 : ");
addr = sc.nextLine();
System.out.println();
// 객체 생성
// - 입력값이 존재하는 경우 입력을 받은 후에 생성한다.
Student student1 = new Student(id, name, addr);
Student student2= new Student(id, name);
// 입력 정보 출력
student1.showStudentInfo();
System.out.println();
student2.showStudentInfo();
}
}
[실행 결과]
학번 : 1
학생명 : 김길현
주소 : 경기 김포시
학번 : 1
학생명 : 김길현
주소 : null
실행결과를 통해서 알 수 있듯이, student1, 2 모두 생성자의 이름은 같지만, student1 은 3개의 매개변수를, student2 는 2개의 매개변수를 사용하였고, 학생정보를 출력했을 때도 student2는 주소에 대한 정보를 받지 않기 때문에 주소란에 null 이 출력되는 것을 확인할 수 있다.
(5) this()
위의 예시에서 처럼 생성자가 많아지면 생성자를 사용하면서 중복된 코드가 생길 수 있다. 특히 초기화가 비슷한 생성자라면 더 많이 발생할 것이다. 이럴 경우 초기화하는 내용을 하나의 생성자에 작성하고, 나머지 생성자는 초기화한 생성자를 호출하는 식으로 개선할 수 있다.
이 때, 하나의 생성자에서 다른 생성자를 호출할 때 사용하는 것이 this 키워드다. 이 키워드는 자신의 다른 생성자를 호출하는 코드이며, 반드시 생성자의 첫줄에서만 허용된다. 호출되는 생성자의 실행이 종료되면 본래의 생성자로 돌아와서 이후의 코드를 실행한다.
앞서 언급한 내용을 확인하기 위해 앞서 본 학생 정보 클래스에 작성한 코드가 this 키워드를 활용하면 어떻게 바뀌는 지 아래의 코드와 비교해보자.
[Java Code]
public class Student {
public int inputId;
public String inputName;
public String inputAddr;
Student() {}
Student(String inputName)
{
this.inputName = inputName;
}
Student(int inputId, String inputName)
{
this(inputId, inputName, "");
}
Student(int inputId, String inputName, String inputAddr)
{
this.inputId = inputId;
this.inputName = inputName;
this.inputAddr = inputAddr;
}
public void showStudentInfo()
{
// this 연산자 구현시 아래의 코드를 사용함
System.out.println("학번 : " + inputId);
System.out.println("학생명 : " + inputName);
System.out.println("주소 : " + inputAddr);
}
public String getStudentName() {
return inputName;
}
}
위의 코드를 보면 1번째의 Student() 가 기본 생성자이고, 이후에 등장하는 다른 Student의 경우에는 생성자 오버라이딩으로 생성되는 다른 생성자이다. 이 때 3번째, 4번째의 경우 형식이 유사하기 때문에 this() 키워드를 사용하여 3번째 생성자를 4번째 생성자에서 this 로 호출하는 것을 볼 수 있다.
만약 4번째 생성자에서 2번째 생성자도 호출하는 것을 구현하고 싶다면, 4번째 생성자의 형식에 맞게 선언하되, 매개변수에 없는 값은 데이터 형식에 맞게끔 초기 값을 직접 지정해주면 된다.
[Java Code - ex. 구현 예시]
this(0, inputName, "")
6) 메소드
이번에는 메소드에 대해서 살펴보자. 메소드란 객체의 동작에 해당한다. 일반적으로 필드를 읽고 수정하는 역할을 하는데, 그 외에 다른 객체를 생성하고 해당 객체에 포함된 여러 기능을 수행하기도 한다. 때문에 메소드는 객체 간의 데이터 전달의 수단으로도 사용된다. 외부로부터 매개값을 받을 수도 있고, 실행 후에 특정 값을 반환할 수도 있다.
(1) 메소드 선언
메소드의 구성은 크게 선언부(리턴타입, 메소드이름, 매개변수선언 등) 와 실행 블록으로 구성된다. 코드 구현은 아래와 같이 하면 된다.
[메소드 선언방법]
리턴타입 메소드이름([매개변수1, ...]) {
실행 코드를 작성
}
리턴타입은 메소드가 실행 후 리턴하는 값의 타입을 의미한다. 메소드가 실행 후 결과를 호풀한 곳에 넘겨줄 경우에는 리턴값이 있어야 한다. 만약 반환값이 없는 경우에는 void 형을, 그 외에는 각 데이터 타입을 리턴타입 자리에 기입해야한다.
뿐만 아니라, 호출하는 방식에도 조금 차이가 있다. 아래의 코드를 살펴보자.
[Java Code]
// 선언부
void powerOn() { ..... }
double divide(int x, int y) { ..... }
// 메소드 호출 및 실행 시
powerOn(); // void 형으로 선언한 경우
double result = divide(10, 20); // 리턴타입이 있는 경우
위의 코드에 보이는 것처럼 리턴타입이 void 로 선언된 메소드의 경우에는 변수에 저장할 필요가 없기 때문에 메소드명만 호출하면된다. 하지만, 예시에 있는 divide() 메소드와 같이 리턴타입이 선언된 경우에는 값이 반환되기 때문에 이를 받아서 저장할 변수가 필요하다.
또한 리턴타입이랑 다른 변수에 저장을 할 경우 컴파일 에러가 발생한다.
메소드 이름은 자바 식별자 명명규칙에 따라 작성하면 되며, 내용은 다음과 같다.
- 숫자로 시작하면 안 되고, $ 와 _ 를 제외한 특수 문자를 사용하면 안된다.
- 관례적으로 메소드명은 소문자로 작성한다.
- 서로 다른 단어가 혼함된 이름인 경우 모든 단어의 첫문자는 대문자로 작성한다.
매개 변수는 메소드가 실행할 때 필요한 데이터를 외부로부터 받기 위해 사용된다. 경우에 따라 필요없기도 하다.
일반적으로는 매개 변수의 개수는 정해져 있지만. 메소드 선언 시에 매개 변수의 개수를 확인 못하는 경우도 있다.
이럴 경우 매개 변수를 배열로 선언해서 여러 항목을 받을 수 있도록 한다.
(2) 리턴 문
리턴값이 있는 메소드는 반드시 return 문을 사용해서 반환값을 지정해야한다. 없을 경우 컴파일 에러가 발생한다. 참고로 수치형인 byte, short 등의 경우에는 int 형을 자동 형변환이 되는 것처럼 일부 자료형은 자동형변환이 되어 반환된다.
단, 리턴타입이 없는 경우에 return 문을 작성하면 메소드 실행을 강제종료시킨다.
주의 사항으로는 return 문 다음 실행문을 작성하면 “Unreachable Code” 라는 컴파일 오류가 발생하니 참고하자.
(3) 메소드 호출
앞서 언급한 데로 메소드는 객체 내, 외부에서 호출하는 방식으로 사용할 수 있다고 했다. 먼저 클래스 내부에서는 단순하게 메소드 명으로 호출을 하면 된다. 하지만 클래스 외부나 다른 클래스에서 사용하고자 할 때는 객체의 요소에 해당하기 때문에 객체를 먼저 생성하고, 참조 변수명으로 메소드를 호출해야한다.
(4) 메소드 오버로딩
하나의 클래스 내에서 같은 이름의 메소드를 여러 개 선언하는 것을 메소드 오버로딩이라고 한다. 앞서 살펴봤던 생성자 오버로딩과 유사하게 메소드 오버로딩도 동일한 이름이지만, 매개변수의 타입, 개수, 순서 중 하나가 달라야지만 성립이 가능하다.
또한 생성자 오버로딩에서 언급한 것처럼 메소드 오버로딩에서도 매개 변수의 타입, 개수, 순서가 똑같고 매개변수의 이름만 바꾼다고 해서 메소드 오버로딩이 성립하는 것은 아니기 때문에 컴파일 에러가 발생한다.
메소드 오버로딩의 대표적인 예로는 System.out.println() 이 있다. 앞서 계속 사용한 코드들을 살펴보면 알 수 있듯이. 매개변수로 어떤 데이터가 넘어오냐에 따라 오버로딩된 println() 이 호출된다.
지금까지 메소드에 대한 내용을 살펴봤는데, 내용 확인 및 설명에 대한 보충을 위해 아래의 코드를 코딩해보자. 예시는 사각형의 넓이를 구하는 예시이며, 직사각형과 정사각형의 넓이를 계산하는 메소드를 구현할 것이다.
정사각형은 직사각형의 일종이기 때문에 메소드는 calculateRectangleArea() 라는 이름으로 선언할 것이며, 메소드 오버로딩을 구현한다.
[Java Code - 넓이 계산 클래스]
public class AreaCalculator {
public double calculateRectangleArea(double width)
{
return width * width;
}
public double calculateRectangleArea(double width, double height)
{
return width * height;
}
}
[Java Code]
public class CalculateSquare {
public static void main(String[] args)
{
AreaCalculator areaCalc = new AreaCalculator();
System.out.println("길이 3.5 인 정사각형의 넓이 : " + areaCalc.calculateRectangleArea(3.5));
System.out.println("가로 길이 3.5, 세로 길이 4 인 직사각형의 넓이 : " + areaCalc.calculateRectangleArea(3.5, 4));
}
}
[실행 결과]
길이 3.5 인 정사각형의 넓이 : 12.25
가로 길이 3.5, 세로 길이 4 인 직사각형의 넓이 : 14.0
위의 코드를 살펴보면 먼저 AreaCalculator 라는 클래스에 사각형 넓이 계산 메소드인 calculateRectangleArea() 를 선언했는데 정사각형의 경우에는 가로, 세로 길이가 동일하기 때문에 가로 길이 매개변수 1개만 받았으며, 직사각형의 경우에는 가로, 세로 길이가 서로 다르기 때문에 2개의 매개변수를 받았다.
메소드 명은 동일하지만, 매개변수의 개수, 순서가 다르기 때문에 메소드 오버로딩이 구현될 것이다.
위의 예제에서 눈여겨 볼 곳 중 다른 하나는 main 의 실행 코드 부분이다. 직사각형 넓이를 계산하는 메소드를 보면 매개 변수는 분명 double 형이 2개가 입력되어야한다. 하지만 main 에서는 실수, 정수 의 조합으로 입력이 들어왔고, 반환된 결과는 double 형이며, 별다른 메세지는 출력되지 않았다. 이를 통해 매개 변수에서 자동형변환이 발생한다는 내용까지 확인할 수 있다.
댓글남기기