이전 게시물에서 객체의 기본 개념과 멤버 변수에 대해 정리했다. 객체의 다음 구성원인 멤버 메서드에 대해 알아보자.
package kr.s09.object.instance;
public class InstanceMain {
//클래스의 기본 구조
//멤버 변수
int var1;
String var2;
//생성자 - 생략 가능
public InstanceMain() {}
//멤버 메서드
public int sum(int a, int b) {//a, b: 인자, sum: 메서드 명
return a+b;
}
public static void main(String[] args) {
//객체 선언 및 생성
//자료형 참조자료형 생성자
InstanceMain me = new InstanceMain();
System.out.println("me: " + me);
int result = me.sum(5, 6);//메서드 내부적으로 연산한 값을 result에 반환
System.out.println("result = " + result);
}
}
출력)
me: kr.s09.object.instance.InstanceMain@372f7a8d
result = 11
클래스의 기본 구조는 위와 같다. 멤버 변수, 멤버 메서드와 달리 생성자는 구성원이 아니기 때문에 생략 가능하며 메인 영역에서 객체의 선언 및 생성이 이루어진다.
위와 같이 객체에 변수나 메서드와 같은 하위 구성원을 연결하지 않고 출력할 경우, 객체의 주소가 출력되며 이는 가공된 것으로 java에서는 별도로 사용할 수 없다.
객체의 주소에서 @ 다음에 있는 16진수 '372f7a8d'를 객체의 참조값이라고 부른다.
메서드는 함수와 같은 개념으로, 위 예제에서는 메서드 내부적으로 연산한 값을 int result에 반환하였다.
public int sum(int a, int b) {//a, b: 인자, sum: 메서드 명
return a+b;
}
int result = me.sum(5, 6);
System.out.println("result = " + result);
→ 11 출력
모든 메서드가 위와 같이 값을 반환하는(return)하는 것은 아니다. 그렇다면 리턴이 있는 경우와 없는 경우는 어떤 구조적 차이가 있을까?
package kr.s10.object.method;
public class MethodMain01 {
//반환하는 데이터가 있는 경우
public int add(int a, int b) {
return a+b;
}
//반환하는 데이터가 없는 경우
public void print(String str) {
System.out.println(str);
}
public static void main(String[] args) {
//객체 선언 및 생성
MethodMain01 method = new MethodMain01();
int result = method.add(10, 20);
System.out.println("result = " + result);
method.print("오늘은 수요일");
}
}
public int add(int a, int b) {
return a+b;
}
1) public 접근 제한자 = 접근 지정자
2) int 반환형 : 반환할 데이터의 자료형을 지정
3) add 메서드명
4) int a, int b 인자
5) a+b 반환값
public void print(String str) {
System.out.println(str);
}
1) public 접근 제한자 = 접근 지정자
2) void : 리턴할 값이 없어도 형식이 맞아야 하기에 반환할 데이터가 없다는 의미에서 'void'라고 명시해 주어야 한다.
3) print 메서드 명
4) String str 인자 : 인자가 없더라도 ()를 명시해야 한다.
위 내용을 바탕으로 메서드의 구조에 대한 이해를 돕는 실습 3가지를 진행하였다.
package kr.s10.object.method;
public class MethodMain02 {
/*
* [실습]
* 입력한 int형 정수값이 음수이면 -1을, 0이면 0을, 양수이면 1을 반환하는
* signof 메서드를 작성하자
*
* [입력 예시]
* 정수 x: 50
*
* [출력 예시]
* signof(x)는 1입니다.
*/
public int signof(int a) {
int sign = 0;
if(a > 0) {
sign = 1;
}else if(a < 0){
sign = -1;
}
return sign;
}
public static void main(String[] args) {
java.util.Scanner input = new java.util.Scanner(System.in);
System.out.print("정수 x: ");
int x = input.nextInt();
MethodMain02 object = new MethodMain02();
int result = object.signof(x);
System.out.println("signof(x)는" + result + "입니다.");
input.close();
}
}
-1, 0, 1을 반환해야 하므로 리턴이 있는 형태, 반환형은 int로 public int signof(int a){ 메서드를 작성한다.
음수면, 0이면, 양수면 등의 조건이 존재하므로 if문을 활용해 경우의 수를 나누어 준 뒤, 그때마다 변수 sign의 값을 달리한다.
if(a > 0) {
sign = 1;
}else if(a < 0){
sign = -1;
}
Scanenr를 통해 int x에 정수값을 입력 받는다.
signof 메서드를 활용하기 위해 MethodMain02 객체를 생성한다.
메서드의 인자로 입력 받은 int x의 값을 넘겨준다.
메서드 내부에서 if 조건문을 통해 반환된 값을 int result에 저장한다.
출력 예시와 같이 출력식 System.out.println("signof(x)는" + result + "입니다.");을 작성한다.
작업이 끝나면 input.close();한다.
package kr.s10.object.method;
public class MethodMain03 {
/*
* [실습]
* 배열의 요소수를 입력 받아서 배열 x 생성
* 입력 받은 정수를 배열 x에 저장하고 배열 x가 가진 모든 요소의
* 합을 구하는 sumof(int[] a) 메서드를 정의하시오.
*
* [입력 예시]
* 요소 수: 3
* x[0]: 10
* x[1]: 20
* x[2]: 30
*
* [출력 예시]
* 모든 요소의 합은 60입니다.
*/
public int sumof(int[] a) {
int sum = 0;
for(int i=0;i<a.length;i++) {
sum += a[i];
}
return sum;
}
public static void main(String[] args) {
java.util.Scanner input = new java.util.Scanner(System.in);
System.out.print("요소 수: ");
int num = input.nextInt();
int[] x = new int[num];
for(int i=0;i<num;i++) {
System.out.print("x[" + i + "]: ");
x[i] = input.nextInt();
}
MethodMain03 method = new MethodMain03();
int result = method.sumof(x);
System.out.println("모든 요소의 합은 " + result + "입니다.");
input.close();
}
}
반환해야 할 것이 정수들의 합이므로 반환형을 int, 배열 int[] a를 인자로 넘겨 받을 메서드 sumof를 만든다. public int sumof(int[] a) {
메서드 내에서 합을 저장하고 반환할 정수형 변수 sum을 생성 및 초기화 한다.
for문을 활용해 0에서부터 넘겨 받은 배열 int[] a의 배열 길이 전까지 루프를 돌며 각 인덱스(i)에 저장된 값을 읽어와 sum 변수에 누적해준다. (여기서 a는 배열명에 대한 예시일 뿐이다.)
for(int i=0;i<a.length;i++) {
sum += a[i];
}
ex. a.length = 3 → sum = a[0] + a[1] + a[2];
루프를 돌며 sum에 배열 값을 누적한 뒤 끝나면 return sum; 반환한다.
다음은 메인 영역에서 배열의 요소 수를 Scanner를 통해 입력 받는다.
이렇게 입력 받은 요소의 수는 배열의 길이를 정한다.
ex. int[] x = new int[num]; → num개의 요소를 가진 배열
for문을 돌며 0에서부터 num 전까지 각 인덱스(i)에 입력 받은 값을 저장한다.
for(int i=0;i<num;i++) {
System.out.print("x[" + i + "]: ");
x[i] = input.nextInt();
}
메서드를 이용하기 위해 MethodMain03 객체를 생성한다.
메서드에 우리가 만든 배열을 넘기고, sumof 메서드가 반환할 sum 값을 int result에 저장한다.
ex. int result = method.sumof(x);
출력 예시에 맞게 출력식을 System.out.println("모든 요소의 합은 " + result + "입니다."); 작성한다.
작업이 끝나면 input.close(); 해준다.
package kr.s10.object.method;
public class MethodMain04 {
/*
* [실습]
* 배열 요소 수(사람 수)를 입력 받아서 배열 weight 생성
* 입력 받은 정수를 배열 weight에 저장하고 배열 weight가 가진
* 모든 요소의 수 중 최소값을 구하는 minof 메서드를 작성하시오.
*
* [입력 예시]
* 사람 수: 3
* 1번의 체중: 53
* 2번의 체중: 62
* 3번의 체중: 71
*
* [출력 예시]
* 가장 마른 사람의 체중: 53kg
*/
public int minof(int[] a) {
int min = a[0];
for(int i=1;i<a.length;i++) {
if(min > a[i]) {
min = a[i];
}
}
return min;
}
public static void main(String[] args) {
java.util.Scanner input = new java.util.Scanner(System.in);
System.out.print("사람 수: ");
int num = input.nextInt();
int[] weight = new int[num];
for(int i=0;i<num;i++) {
System.out.print((i+1) + "번의 체중: ");
weight[i] = input.nextInt();
}
MethodMain04 w = new MethodMain04();
System.out.println("가장 마른 사람의 체중: " + w.minof(weight) + "kg");
input.close();
}
}
반환해야 할 것이 최소값이므로 반환형을 int, 배열 weight을 인자로 넘겨 받을 메서드 minof를 만든다.
ex. public int minof(int[] a) {
메서드 내에서 최소값을 저장하고 반환해줄 정수형 변수 min을 생성 한다. 이때 배열의 0번 값으로 초기화도 해준다.
ex. int min = a[0];
for문을 통해 자기 자신인 0번을 제외, 1에서부터 배열의 길이 전까지 인덱스(i) 값을 달리하며 정수형 변수 min과 대조를 한다.
대조 시에는 if문을 활용하며 '최소값'을 구하는 것이기 때문에 min 값이 인덱스가 가리키는 배열의 저장값보다 클 경우, min에 저장된 값을 해당 값으로 바꿔준다.
for(int i=1;i<a.length;i++) {
if(min > a[i]) {
min = a[i];
}
}
→ a[0] = 5 → min = 5 → a[1] = 3 → min(5) > a[1](4) → min = 4
그렇게 루프를 돌며 구해진 최소값(min)을 반환한다.
메인 영역에서 Scanner를 통해 사람 수가 될 배열의 요소 수를 입력 받는다.
사람 1명당 체중은 하나이므로, 사람 수 만큼의 길이를 가지는 배열 weight을 int[] weight = new int[num]; 만든다.
for문을 돌며 0에서부터 num 전까지 인덱스(i) 값을 달리하며 각각의 체중을 입력 받아 저장한다.
이때, i는 0에서부터 시작하므로 Scanner 입력을 도울 가이드 문구는 (i+1)로 보정한다.
for(int i=0;i<num;i++) {
System.out.print((i+1) + "번의 체중: ");
weight[i] = input.nextInt();
}
→ i=0 → 1번의 체중 : → weight[0] = 70
메서드 이용을 위해 MethodMain04 객체를 생성한다.
앞선 예시와 같이 반환한 최소값을 저장할 변수를 따로 만들 필요 없이 출력식에서 바로 호출해 사용한다.
(한 번 밖에 사용하지 않을 거라면 굳이 변수를 선언하는 것은 비효율적이다.)
/*
* int m = w.minof(weight);
* System.out.println("가장 마른 사람의 체중: " + m + "kg");
*/
System.out.println("가장 마른 사람의 체중: " + w.minof(weight) + "kg");
input.close(); 해준다.메서드를 배우기 전, 멤버 변수만 활용해 성적 처리 프로그램을 만들었다. 하지만 그와 같이 멤버 변수만 있는 클래스는 드물다.
따라서 멤버 변수와 메서드를 모두 활용한 성적 프로그램을 만들어 보았다.
> 객체, switch문을 활용한 성적 프로그램 만들기
이제까지의 예제는 전부 반환값이 있는 메서드였다면, 다음은 void형 메서드를 연습할 차례이다.
void형 메서드를 통해 리모컨의 작동 방식을 구현해 보고자 한다.
package kr.s10.object.method;
class Tv{
boolean power; //전원 상태(on/off)
int channel; //채널
public void isPower() {
power = !power;
}
public void channelUp() {
++channel;
}
public void channelDown() {
--channel;
}
}
public class TvMain {
public static void main(String[] args) {
Tv t = new Tv();
t.isPower();
System.out.println("Tv 실행 여부: " + t.power);
System.out.println("현재 채널: " + t.channel);
System.out.println("==================");
t.channel = 7;
System.out.println("첫번째 변경된 채널: " + t.channel);
System.out.println("==================");
t.channelDown();
System.out.println("두번째 변경된 채널: " + t.channel);
System.out.println("==================");
t.isPower();
System.out.println("Tv 실행 여부: " + t.power);
}
}
리모컨의 기능을 구현해줄 멤버 변수를 만든다. 이때 멤버 변수는 전원 상태의 값을 저장할 boolean 타입의 power와 채널 정보를 저장할 int 타입의 channel 둘로 한다.
동작은 메서드로 제어한다. 첫 번째 메서드는 리모컨의 전원을 끄고 켠다. 반환할 데이터가 없고, 멤버 변수의 값만 변경시켜 주면 되기 때문에 인자 또한 없다. 따라서 다음과 같은 형태가 된다.
ex. public void isPower() {power = !power;}
boolean 타입의 데이터에 논리 연산자인 !(부정)을 붙여 주면 true일 때는 false로, false일 때는 true로 값이 변하는 특성을 이용했다.
다음 메서드는 호출할 때마다 채널을 돌린다. 증감 연산자를 활용해 채널을 올리는 경우와 내리는 경우 두 가지를 만들었다.
ex. public void channelUp() {++channel;}, `public void channelDown() {--channel;}
메인 영역에서 Tv 클래스의 객체를 생성한다.
그리고 전원 상태를 제어하는 t.isPower() 메서드를 호출하는데, boolean 타입의 경우 기본값이 false이므로 !power = true가 된다. 즉 전원이 꺼져 있는 상태에서 메서드를 호출해 전원을 켜주는 것이다.
전원이 켜졌다는 것은 그 다음 출력식 System.out.println("Tv 실행 여부: " + t.power);의 출력값이 true라는 것을 통해 확인할 수 있다.
또한, 채널 정보를 선언만 했지 초기화한 적이 없기 때문에 System.out.println("현재 채널: " + t.channel); 출력 시 int 자료형의 기본값인 0이 출력된다.
채널 정보를 변경하고 싶은 경우에는 멤버 변수를 호출해 새로운 값을 대입해 주면 된다.
ex. t.channel = 7;
그 후, 채널을 위와 아래로 돌리는 역할은 t.channelDown(); 메서드와 t.channelUp(); 메서드가 담당한다.
ex. t.channelDown(); → System.out.println("두번째 변경된 채널: " + t.channel); → 6 출력
t.isPower(); 메서드를 한 번 더 호출하면 TV가 꺼지게 된다.
다음은 void형 메서드 사용에 익숙해지기 위한 실습을 진행하였다.
package kr.s10.object.method;
class Worker{
/*
* [실습]
* Worker
* 1)멤버 변수 : 직원 이름(name), 급여(money), 계좌 잔고(balance)
* 2)멤버 메서드: work(실행하면 money에 1,000원 누적),
* deposit(실행하면 money의 돈을 balance에
* 누적시키고 money는 0으로 처리)
* WorkerMain의 main
* 1)Worker 객체 생성
* 2)직원의 이름 지정
* 3)10번 일하는데 번 돈이 3,000원일 때마다 계좌에 저축
* 4)직원 이름, 현재 계좌에 입금되지 않고 남아 있는 급여(money),
* 계좌 잔고(balance)를 출력하시오.
*
*/
String name;
int money;
int balance;
public void work() {
money += 1000;
}
public void deposit() {
balance += money;
money = 0;
}
}
public class WorkerMain {
public static void main(String[] args) {
Worker w = new Worker();
w.name = "홍길동";
for(int i=1;i<=10;i++) {
w.work();
if(w.money >= 3000) {
w.deposit();
}
}
System.out.println("직원 이름: " + w.name);
System.out.printf("현재 계좌에 입금되지 않고 남아 있는 급여: %,d원%n", w.money);
System.out.printf("계좌 잔고: %,d원%n", w.balance);
}
}
출력)
직원 이름: 홍길동
현재 계좌에 입금되지 않고 남아 있는 급여: 1,000원
계좌 잔고: 9,000원
w.money >= 3000 외에 w.money%3000 == 0 혹은 w.money == 3000도 가능하다.지금까지는 반환 값이 있는 형태와 없는 형태로 나누어 메서드를 구분했다면, 인자 전달 방식에 따른 메서드 구분을 알아 보자.
- 값 호출
- 참조 호출
- Varargs(Variable Arguments)
package kr.s11.object.method;
public class MethodArgMain01 {
public int increase(int n) {
++n;
return n;
}
public static void main(String[] args) {
int var1 = 100;
//객체 선언 및 생성 후에 increase 호출
MethodArgMain01 ma = new MethodArgMain01();
int var2 = ma.increase(var1);
System.out.println("var1 : " + var1 + ", var2 : " + var2);
}
}
첫 번째는 값에 의한 호출이다.
기본 자료형의 값을 인자로 전달하는 방식으로 값을 복사하여 전달한다.
따라서 int var2 = ma.increase(var1);에서 인자인 var1에는 초기화할 때 저장해준 값 100이 들어가 있고, 이에 출력 결과는 var1 : 100, var2 : 101가 된다.
package kr.s11.object.method;
public class MethodArgMain02 {
public void increase(int[] n) {
for(int i=0;i<n.length;i++) {
n[i]++;
}
}
public static void main(String[] args) {
int[] ref1 = {100, 200, 300};
System.out.println("===배열 정보를 객체의 메서드에 보내기 전===");
for(int i=0;i<ref1.length;i++) {
System.out.println("ref1[" + i + "]:" + ref1[i]);
}
//객체 선언 및 생성
MethodArgMain02 me = new MethodArgMain02();
me.increase(ref1);
System.out.println("===배열 정보를 객체의 메서드에 보낸 후===");
for(int i=0;i<ref1.length;i++) {
System.out.println("ref1[" + i + "]:" + ref1[i]);
}
}
}
두 번째는 참조 호출(call by reference)이다.
객체의 주소를 인자로 전달하는 방식으로 배열 또한 객체에 해당한다.
따라서 위 예제에서 MethodArgMain02 객체를 생성 후 inr[] ref1 배열의 주소를 복사해 메서드의 인자로 전달하면, 출력 결과는 다음과 같이 달라진다.
출력)
===배열 정보를 객체의 메서드에 보내기 전===
ref1[0]:100
ref1[1]:200
ref1[2]:300
===배열 정보를 객체의 메서드에 보낸 후===
ref1[0]:101
ref1[1]:201
ref1[2]:301
package kr.s11.object.method;
public class MethodArgMain03 {
public void argTest(int ... n) {
for(int i=0;i<n.length;i++) {
System.out.println("n[" + i + "]:" + n[i]);
}
System.out.println("===============");
}
public static void main(String[] args) {
MethodArgMain03 me = new MethodArgMain03();
me.argTest();
me.argTest(10);
me.argTest(10,20);
me.argTest(10,20,30);
}
}
마지막은 Variable Arguments이다. 자료형이 일치할 때 전달하고자 하는 값의 개수를 다르게 지정할 수 있으며 전달하는 데이터는 내부적으로 배열로 인식한다.
사용 빈도는 낮은 편이고, 배열로 처리하기 때문에 데이터 간의 자료형이 일치해야 한다는 전제조건이 존재한다.
public void argTest(int ... n) {에서 n은 배열의 주소를 말하고, ... 안에 int 자료형을 가진 데이터를 원하는 개수 만큼 인자로 넘겨준다고 생각하면 이해가 쉽다.
출력 결과는 아래와 같다.
출력)
===============
n[0]:10
===============
n[0]:10
n[1]:20
===============
n[0]:10
n[1]:20
n[2]:30
===============
보다시피 me.argTest();와 같이 인자를 넘겨주지 않을 경우 아무것도 출력되지 않는다. 이는 배열이 생성은 되지만, 전달된 데이터가 없어 방이 만들어지지 않은 경우(n.length = 0)이다.