1. 변수
변수란?
데이터를 저장하기 위해 프로그램에 의해 이름을 할당받은 메모리 공간을 의미한다.
1.1 변수의 종류
- 클래스 변수 : 클래스가 로드될 때 선언되는 변수, 타입 앞에 static을 붙여 사용한다.
- 인스턴스 변수 : 인스턴스가 생성될때 선언되는 변수
- 지역 변수 : 메서드 내에서 선언되며 메서드 종료 시 소멸되는 변수
public static int max; // 클래스 변수
public int max; // 인스턴스 변수
public method() {
int m; // 지역 변수
}
1.1.1. 클래스 변수 (class variable)
- 인스턴스 변수 앞에 static을 추가한다.
- 한 클래스의 모든 인스턴스들이 공통적인 값을 유지한다.
- 인스턴스가 생성되지 않아도 사용가능하다.
- 클래스가 메모리에 로딩될 때 생성되어 프로그램이 종료될 때까지 유지한다.
// 1. 인스턴스 변수 앞에 static을 추가
public int max; // 인스턴스 변수
public static max; // 클래스 변수
// 2. 한 클래스의 모든 인스턴스들이 공통적인 값을 유지한다.
class Tv {
public static int max = 100;
}
Tv t1 = new Tv();
Tv t2 = new Tv();
t1.max; // 100
t2.max; // 100
// 3. 인스턴스가 생성되지 않아도 사용가능하다.
Tv.max; // 100
1.1.2. 인스턴스 변수 (instance variable)
- 클래스 영역에 선언한다.
- 인스턴스 생성할 때 만들어진다.
- 인스턴스가 생성되어야 컨트롤 가능하다.
public Tv {
public int max; // 1. 클래스 영역에 선언
}
Tv t = new Tv(); // 2. 인스턴스가 생성이 되어야 변수가 만들어짐
t.max; // 3. 인스턴스가 생성되어야 사용 가능
1.1.3. 지역 변수 (local variable)
- 메서드 내에서만 선언 가능하다.
- 메서드 종료 시 소멸된다.
public set() {
int num = 0; // 1. 메소드 내에서 지역 변수 선언
}
num; // 2. 변수가 선언 되어 있지 않다는 에러
1.2. 클래스 변수와 인스턴스 변수
class Card {
String kind; // 각 인스턴스 고유
int num; // 각 인스턴스 고유
static int height = 250; // 모든 인스턴스 공유
static int width = 100; // 모든 인스턴스 공유
}
클래스 변수를 변경 시 모든 인스턴스의 값이 변경된다.
클래스 변수는 "클래스.클래스변수" 형태로 사용하는 것이 좋다. ex) Card.width;
참조 변수를 이용해서 사용할 수 있지만 인스턴스 변수와 혼동이 온다.
클래스 변수는 하나의 저장공간을 갖는다.
2. 메서드
메서드란?
특정 작업을 수행하는 문장들을 하나로 묶은 것이다.
입력과 출력이 있을 수도 있고 없을 수도 있으며 모두 없을 수도 있다.
2.1. 메서드를 사용하는 이유
- 높은 재사용성
한번 만들어 놓은 메서드들을 변경이고 호출하여 사용할 수 있다. - 중복된 코드의 제거
공통된 소스를 하나의 메서드로 만들어 중복된 소스를 줄일 수 있다. - 프로그램의 구조화
소스가 많아질수록 가독성이 떨어져 메서드로 작성하여 구조화하면 쉽게 알아볼 수 있다.
2.2. 메서드의 선언과 구현
메서드는 선언부(header)와 구현부(body)로 구분된다.
2.2.1. 메서드 선언부 (Method declaration, Method header)
메서드 이름, 매개변수 선언, 반환 타입으로 구성한다.
- 메서드 이름 (method name)
메서드 이름만으로 어떤 동작을 하는지 알기 쉽게 명명해야 한다.
ex) add(), change() 등등 - 매개변수 선언 (parameter declaration)
1. 메서드가 작업에 필요한 값을 선언한다.
2. ', '(콤마)로 구분하며 타입이 같아도 타입 생략 불가능하다.
3. 개수의 제한이 없다.
4. 매개변수도 메서드 내에서 선언되기 때문에 지역변수에 해당한다. - 반환 타입 (return type)
작업 수행 결과인 반환 값의 타입을 적는다.
반환 값이 없으면 'void'를 적는다.
// 메서드 명 : getName
// 매개변수 : int phone
// 반환 타입 : String
public String getName(int phone) {
...
}
2.2.2. 메서드의 구현부(Method body)
- return문
메서드의 수행한 결과를 반환한다.
최대 1개의 반환 값을 반환한다. - 지역변수(local variable)
서로 다른 메서드에 같은 지역 변수를 선언할 수 있다.
메서드가 끝나면 지역변수는 소멸된다.
// 메서드 명 : getName
// 매개변수 : int phone
// 반환 타입 : String
// 지역 변수: name
// 반환 값 : name
public String getName(int phone) {
String name = "";
...
return name;
}
2.3. 메서드의 호출
메서드를 호출해야 구현부가 실행된다.
// 메서드 명 : getName
// 매개변수 : int phone
// 반환 타입 : String
// 지역 변수: name
// 반환 값 : name
public String getName(int phone) {
String name = "";
...
return name;
}
public static void main(String[] args) {
System.out.println(getName(5678));
}
위의 예제처럼 getName의 인자 값으로 전화번호를 넣어 호출한다.
- 인자 값 : 메서드 호출 시 () 안에 넣는 값 ex) getName(5678)
- 매개변수 값 : 메서드 선언 시 선언하는 값 ex) public String getName(int phone)
인자와 매개변수는 서로 타입이 맞아야 하며 왼쪽부터 순서대로 대입한다. 타입이 다르거나 개수가 다른 경우 컴파일러가 경고한다. 그리고 인자와 매개변수의 형 변환이 가능하다.
ex) getName(long phone), getName((int) 5678)
2.4. 메서드의 실행 흐름
public static void main(String[] args) { // 1번
String res = ""; // 2번
res = getName(5678); // 5번
System.out.println(res); // 6번
}
// 3번
public String getName(long phone) {
...
return "홍길동"; // 4번
}
위의 예제의 주석에 순서대로 아래와 같이 진행된다.
- main메서드가 실행
- res가 선언 및 초기화
- getName 호출
- getName return
- res에 getName에서 return 된 문자열 값 대입
- res 출력
2.5. return 문
return문은 반환 값 유무와 상관없이 무조건 있어야 한다. void인 경우 return문을 사용하지 않아도 컴파일러가 자동으로 추가한다.
public String getName(long phone) {
if(phone > 5000) {
return "홍길동";
}
}
위와 같이 if문은 조건문에만 실행되는 return은 에러가 발생한다. getName 메서드는 반환 타입으로 String을 반환하는데 구현부에서 if문에 속해야만 return이 되기 때문에 에러가 발생한다.
2.5.1. 반환 값 (return value)
반환 값으로 반드시 변수가 오는 것이 아닌 수식, 메서드 등이 올 수 있고 해당 return문의 수행 결과 값이 반환된다.
// return 수식
public int getAge(String name) {
return (country.equals("KR")) ? age + 1 : age;
}
// return method
public String getName(long phone) {
return get();
}
public String get() {
return "무명";
}
2.5.2. 매개 변수의 유효성 검사
매개 변수로 들어오는 값은 타입만 일치하면 되므로 유효하지 않는 값이 들어올 수 있어 유효성 검사가 필요하다.
public int divide(int a, int b) {
if(a == 0 || b == 0) return 0; // 유효성 검사
return a/b;
}
위와 같이 a, b가 0일 때 0을 return 한다. 0으로 나누기가 불가하여 에러가 발생한다.
2.6. 기본형 매개변수와 참조형 매개변수
기본형 : 변수의 값을 읽기만 가능 (read only)
참조형 : 변수의 값을 읽고 변경 가능 (read & write)
2.6.1. 기본형 매개변수 (call by value)
public static void main(String[] args) {
int x = 1000;
get(x);
System.out.println(x); // 1000
}
public int get(int x) {
x = 100;
System.out.println(x); // 100
return x;
}
기본형 매개변수는 값이 복사되기 때문에 원본이 변하지 않고 전달해줘서 읽기만 가능하다. 그래서 main 메서드에서 x를 출력했을 때와 get메서드에서 x를 출력했을때 값이 다른 것을 알 수 있다.
2.6.2. 참조형 매개변수 (call by reference)
public static void main(String[] args) {
int[] x = {100, 200};
System.out.println(get(x));
System.out.println(x[0]); // 1000
}
public void get(int[] x) {
x[0] = 1000;
System.out.println(x[0]); // 1000
}
참조형 매개변수는 객체의 주소 값을 복사하기 때문에 원복에 접근할 수 있다. 그래서 이런점을 이용해서 void method를 이용하여 값을 변경할 수 있다.
2.7. 참조형 반환 타입
참조형 반환 타입은 객체의 주소를 반환하는 것이다.
public static void main(String[] args) {
int[] x = {100, 200};
get(x); // Ox100
System.out.println(x[0]); // 1000
}
public int[] get(int[] x) {
x[0] = 1000;
System.out.println(x[0]); // 1000
return x; // Ox100
}
get 메서드를 호출할 때 인자 값으로 x 배열의 주소 값을 주고, get 메서드가 반환할 때 x 배열의 주소 값을 반환한다.
배열의 주소를 통해 값을 변경해서 get 메서드에서 x배열을 반환할 필요는 없지만 예시로 만들었다.
2.8. 재귀 호출 (recursive call)
- 재귀 호출 메서드를 재귀 메서드라고 한다.
- call by value로 호출이 된다.
- 무한으로 자기 자신을 호출하기 때문에 조건문을 통해 메서드 종료 시점이 필요하다. 종료가 없을 경우 무한 루프에 빠져 에러가 발생한다.
- 대부분의 재귀 호출은 반목 문으로 변경 가능하다.
// 재귀 호출
public int minus(int a) {
if(a == 0) return 0;
minus(--a);
}
// 재귀 호출 -> 반복문
public int minus(int a) {
while(a != 0) {
--a;
}
}
위의 예제 중 반복문으로 실행하는 게 더 빠르다. 재귀 호출은 값 복사, 값 반환 등과 같은 작업이 필요하기 때문이다.
그럼에도 재귀 함수를 사용하는 이유는 아래와 같다.
- 논리적 간결함
- 비효율적이지만 쉽게 작성
- 논리적 오류 발생 확률이 낮고 수정이 용이
public static main(String[] args) {
factorial(5);
}
public int factorial(int n) {
return (n == 1) ? 1 : n * factorial(n-1);
}
public int factorial(int n) {
int res = 1;
while(n != 0) { // 1번 호출
res *= n--;
}
return res;
}
factorial에 0 또는 100,000 같은 수를 넣으면 스택오버플로우가 발생하며 재귀 호출이 무수히 많이 호출되어 스택이 가득 찬다.
위의 예제 중 가장 아래의 factorial 메서드를 보면 while문으로 작성되어 있다.
재귀 함수로 호출할 경우 factorial을 매번 호출하고 while의 경우 한 번의 호출로 값을 얻을 수 있고 스택오버플로우가 발생하지 않으며 재귀 호출 보다 빠르다.
2.9. 클래스 메서드(static method)와 인스턴스 메서드(instance method)
클래스 변수 : static이 붙은 변수
인스턴스 변수 : static이 붙지 않은 변수
멤버 변수 : 클래스 변수와 인스턴스 변수
- 멤버 변수 중 모든 인스턴스에 공통으로 사용하면 static을 붙이면 된다.
- 클래스 변수는 클래스가 메모리에 올라갈 때 생성된다.
- 클래스 메서드는 인스턴스 변수 사용 불가하다.
- 인스턴스 변수는 인스턴스가 존재해야 하므로 클래스 메서드가 사용할 수 없는 경우가 생겨 금지한다.
단, 인스턴스 메서드는 클래스 변수를 사용할 수 있다. 인스턴스가 생성되기 전에 클래스 변수가 생성되어 있어서 가능하다. - 메서드 내에서 인스턴스 변수를 사용하지 않는다면 static을 붙이는 것을 고려하자
- 메서드 내에서 인스턴스 변수를 사용하면 static 붙이는 게 불가능하다. 하지만 인스턴스 변수를 사용하지 않는다면 static을 붙여 미리 로드시켜 놓으면 인스턴스 생성 시 호출되어야 할 메서드를 찾는 과정이 줄어들면서 성능이 향상된다.
3. 클래스 멤버와 인스턴스 멤버 간의 참조와 호출
멤버 종류 | 클래스 멤버 | 인스턴스 멤버 |
인스턴스 변수 | X | O |
인스턴스 메서드 | X | O |
클래스 변수 | O | O |
클래스 메서드 | O | O |
단, 인스턴스 멤버를 사용하기 위해서 인스턴스를 생성해야 한다.
인스턴스 멤버와 클래스 멤버 간의 존재 시점이 다르기 때문에 참조와 호출이 불가능할 수 있다.
만약 인스턴스 멤버와 클래스 멤버가 참조, 호출이 되고 있다면 클래스 멤버를 잘못 사용한 것은 아닌지 확인해 볼 필요가 있다.