메소드 선언은 객체의 동작을 실행 블록으로 정의하는 것을 말하고, 메소드 호출은 실행 블록을 실제로 실행하는 것을 말한다. 메소드는 객체 내부에서도 호출되지만 다른 객체에서도 호출될 수 있기 때문에 객체간의 상호작용하는 방법을 정의하는 것이라고 볼 수 있다.
다음은 메소드를 선언하는 방법이다.
리턴 타입은 메소드가 실행한 후 호출한 곳으로 전달하는 결과값의 타입을 말한다. 리턴값이 없는 메소드는 void로 작성해야 한다.
void powerOn() {} //리턴값이 없는 메소드 선언
double divide(int x, int y) { } //double 타입 값을 리턴하는 메소드 선언
메소느명은 첫 문자를 소문자로 시작하고, 캐멀 스타일로 작성한다.
매개변수는 메소드를 호출할 때 전달한 매개값을 받기 위해 사용된다. 다음 에에서 divide() 메소드는 연산할 두 수를 전달받아야 하므로 매개변수가 2개 필요하다. 전달할 매개값이 없다면 매개변수는 생략할 수 있다.
double divide(int x, int y) {}
메소드 호출 시 실행되는 부분이다.
메소드를 호출한다는 것은 메소드 블록을 실행하는 것을 말한다. 클래스에서 메소드를 선언했다고 해서 바로 호출할 수 있는 것은 아니다. 메소드는 객체의 동작이므로 객체가 존재하지 않으면 메소드를 호출할 수 없다.
클래스로부터 객체가 생성된 후에 메소도는 생성자와 다른 메소드 내부에서 호출될 수 있고, 객체 외부에서도 호출될 수 있다.
객체 내부에서는 단순히 메소드명으로 호출하면 되지만, 외부 객체에서는 참조 변수와 도트(.) 연산자를 이용해서 호출한다. 또한 메소드가 매개변수를 가지고 있을 때는 호출할 때 매개변수의 타입과 수에 맞게 매개값을 제공해야 한다.
메소드가 리턴값이 있을 경우에는 대입 연산자를 사용해서 다음과 같이 리턴값을 변수에 저장할 수 있다. 이때 변수 타입은 메소드의 리턴 타입과 동일하거나 자동 타입 변환될 수 있어야 한다.
타입 변수 = 메소드();
메소드를 호출할 때에는 매개변수의 개수에 맞게 매개값을 제공해야 한다. 만약 메소드가 가변길이 매개변수를 가지고 있다면 매개변수의 개수와 상관없이 매개값을 줄 수 있다. 가변길이 매개변수는 다음과 같이 선언한다.
int sum(int ...values){
}
가변길이 매개변수는 메소드 호출 시 매개값을 쉼표로 구분해서 개수와 상관없이 제공할 수 있다.
매개값들은 자동으로 배열 할목으로 변환되어 메소드에서 사용된다. 그렇기 때문에 메소드 호출 시 직접 배열을 매개값으로 제공해도 된다.
int[] values = {1, 2, 3};
int result = sum(values);
-----
int reuslt = sum(new int[] {1,2,3});
return 문은 메소드의 실행을 강제 종료하고 호출한 곳으로 돌아간다라는 의미이다. 메소드 선언에 리턴 타입이 있을 경우에는 return문 뒤에 리턴값을 추가로 지정해야 한다.
return [리턴값];
return 문 이후에 실행문을 작성하면 'Unreachable code'라는 컴파일 에러가 발생한다. 왜냐하면 return 문 이후의 실행문은 결코 실행되지 않기 때문이다.
int plus(int x, int y){
int result = x +y;
return result;
// System.out.println(result); // Unreachable Code
}
하지만 다음과 같은 경우에는 컴파일 에러가 발생하지 않는다.
boolean isleftGas(){
if(gas ==0){
System.out.println("gas가 없습니다."); // 1
return false;
}
System.out.println("gas가 있습니다."); // 2
return true;
}
if 문의 조건식이 false가 되면 정상적으로 2가 실행되기 때문에 2는 Unreachable code에러를 발행시키지 않는다. if 문의 조건식이 true가 되면 1이 실행되고 return false;가 실행되어 메소드는 즉시 종료되므로 당연히 2는 실행되지 않는다.
메소드 오버로딩은 메소드 이름을 같되 매개변수의 타입, 개수, 순서가 다른 메소드를 여러개 선언하는 것을 말한다.
class 클래스 {
리턴타입 메소드이름 (타입 변수, ...) { }
//무관 //무관 //타입, 개수, 순서가 달라야 함
리턴타입 메소드이름 (타입 변수, ...) { }
}
메소드 오버로딩의 목적은 다양한 매개값을 처리하기 위해서이다. 다음 에에서 plus() 메소드는 두 개의 int 타입 매개값만 처리하고 double 타입 매개값은 처리할 수 없다.
int plus(int x, int y){
int result = x + y
return result;
}
만약 double 타입 값도 처리하고 싶다면 다음과 같이 메소드 오버로딩하면 된다.
double plus(double x, double y){
double result = x + y;
return result;
}
필드와 메소드는 선언 방법에 따라 인스턴스 멤버와 정적 멤버로 분류할 수 있다. 인스턴스 멤버로 선언되면 객체 생성 후 사용할 수 있고, 정적 멤버로 선언되면 객체 생성 없이도 사용할 수 있다.
| 구분 | 설명 |
|---|---|
| 인스턴스(instance) 멤버 | 객체에 소속된 멤버 (객체를 생성해야만 사용할 수 있는 멤버) |
| 정적(Static) 멤버 | 클래스에 고정된 멤버 (객체 없이도 사용할 수 있는 멤버) |
인스턴스 멤버란 객체에서 소속된 멤버를 말한다. 따라서 객체가 있어야만 사용할 수 있는 멤버다. 우리가 지금까지 선언한 필드와 메소드는 인스턴스 멤버였다. 다음과 같이 Car 클래스에 gas 필드와 setSpeed() 메소드를 다음과 같이 선언하면 인스턴스 멤버가 된다.
public class Car{
//인스턴스 필드 선언
int gas;
//인스턴스 메소드 선언
void setSpeed(int speed) { }
}
gas 필드와 setSpeed() 메소드는 인스턴스 멤버이기 때문에 외부 클래스에서 사용하기 위해서는 Car 객체를 먼저 생성하고 참조 변수로 접근해서 사용해야 한다.
Car myCar = new Car();
myCar.gas = 10;
myCar.setSpeed(60);
------------
Car yourCar = new Car();
yourCar.gas = 20;
yourCar.setSpeed(80);
위 코드가 실행된 후 메모리 상태를 보면 gas 필드는 객체마다 따로 존재하며, setSpped() 메소느는 각 객체마다 존재하지 않고 메소드 영역에 저장되고 공유된다.

인스턴스 멤버는 객체에 소속된 멤버라고 했다. gas 필드는 객체에 소속된 멤버가 분명하지만, setSpeed() 메소드는 객체에 포함되지 않는다. 여기서 우리는 '객체에 소속된'을 '객체에 포함된' 이라고 해석하면 안 된다.
메소드는 코드의 덩어리이므로 객체마다 저장한다면 중복 저장으로 인해 메모리 효율이 떨어진다. 따라서 메소드 코드는 메소드 영역에 두되 공유해서 사용하고, 이때 객체 없이는 사용하지 못하도록 제한을 걸어둔 것이다.
객체 내부에서는 인스턴스 멤버에 접근하기 위해 this를 사용할 수 있다. 우리가 자신을 '나'라고 하듯이, 객체는 자신을 this.라고 한다. 생성자와 메소드의 매개변수명이 인스턴스 멤버인 필드명과 동일한 경우, 인스턴스 필드임을 강조하고자 할 때 this를 주로 사용한다.
package ch06.sec09
public class Car {
//필드 선언
String model;
int speed;
//생성자 선언
Car(String model){
this.model = model; //매개변수를 필드에 대입(this 생략 불가)
}
//메소드 선언
void setSpeed(int speed){
this.speed = speed; //매개변수를 필드에 대입(this 생략 불가)
}
void run(){
this.setSpeed(100);
System.out.println(this.model +"가 달립니다.(시속:"+ this.speed + "km/h");
}
}
```java
package ch06.sec09;
public class CarExample{
public static void main(String[] args){
Car myCar = new Car("포르쉐");
Car yourCar = new Car("벤츠");
myCar.run();
yourCar.run();
}
}
포르쉐가 달립니다. (시속 : 100km/h)
벤츠가 달립니다. (시속 : 100km/h)
자바는 클래스 로터를 이용해서 클래스를 메소드 영역에 저장하고 사용한다. 정적멤버란 메소드 영역의 클래스에 고정적으로 위치하는 멤버를 말한다. 그렇기 때문에 정적 멤버는 객체를 생성한 필요 없이 클래스를 통해 바로 사용이 가능하다.

필드와 메소드는 모두 정적 멤버가 될 수 있다. 정적 필드와 정적 메소드로 선언하려면 다음과 같이 static 키워드를 추가하면 된다.
public class 클래스 {
//정적 필드 선언
static 타입 필드 [= 초기값];
//정적 메소드
static 리턴타입 메소드( 매개변수, ...) {..}
}
객체마다 가지고 있을 필요성이 없는 공용적인 필드는 정적 필드로 선언하는 것이 좋다. 예를 들어 Calculator 클래스에서 원의 넓이나 둘레를 구할 때 필요한 파이는 Calculator 객체마다 가지고 있을 필요가 없기 때문에 정적 필드로 선언하는 것이 좋다.
public class Calculator{
String color; //계산기별로 색깔이 다를 수 있다.
static double pi = 3.14159; //계산기에서 사용하는 파이값은 동일하다
}
인스턴스 필드를 이용하지 않는 메소드는 정적 메소드로 선언하는것이 좋다. 에를 들어 Calculator의 plus() 메소드는 외부에서 주어진 매개값들을 가지고 처리하므로 정적 메소드로 선언하는 것이 좋다. 그러나 인스턴스 필드인 color를 변경하는 setColor() 메소드는 인스턴스 메소드로 선언해야 한다.
public class Calculator {
String color; //인스턴스 필드
void setColor(String color) {this.color = color; } //인스턴스 메소드
static int plus ( int x, int y) { return x + y ; } //정적 메소드
static int minus(int x, int y) {return x - y; } //정적 메소드
}
클래스가 메모리에 로딩되면 정적 멤버를 바로 사용할 수 있는데, 클래스 이름과 함께 도트(.) 연산자로 접근하면 된다. 예를 들어 Calcuator 클래스가 다음과 같이 작성되었다면,
public class Calculator{
static double pi = 3.14159;
static int plus(int x, int y) {}
static int minus(int x, int y) {}
}
정적 필드 pi와 정적 메소드 plus(), minus()는 다음과 같이 사용할 수 있다.
double result1 = 10 * 10* Calculator.pi;
int result2 = Calculator.plus(10,5);
int result3 = Calculator.minus(10,5);
정적 필드와 정적 메소드는 다음과 같이 객체 참조 변수로도 접근이 가능하다.
Calculator myCalcu = new Calculator();
double result1 = 10 * 10 * myCalcu.pi;
int reulst2 = myCalcu.plus(10,5);
int reulst3 = myCalcu.minus(10,5);
하지만 정적 요소는 클래스 이름으로 접근하는 것이 정석이다. 이클립스에서는 정적 멤버를 객체 참조로 접근했을 경우, 경고 표시를 낸다.
정적 필드는 다음과 같이 필드 선언과 동시에 초기값을 주는 것이 일반적이다.
static double pi = 3.14159;
하지만 복잡한 초기화 작업이 필요하다면 정적 블록을 이용해야 한다. 다음은 정적 블록의 형태이다.
static {
...
}
정적 블록은 클래스가 메모리로 로딩될 때 자동으로 실행한다. 정적 브록이 클래스 내부에 여러 개가 선언되어 있을 경우에는 선언된 순서대로 실행된다.
생성자에서 초기화를 하지 않는 정적필드
정적 필드는 객체 생성 없이도 사용할 수 있기 때문에 생성자에서 초기화 작업을 하지 않는다. 생성자는 객체 생성 후 실행되기 때문이다.
다음 예제를 보면 Television은 3개의 정적 필드를 가지고 있다. company와 model은 선언 시 초기값을 주었고, info는 정적 블록에서 company와 model을 서로 연결하여 초기값으로 주었다.
package ch06.sec10.exam02
public class Television{
static String company = "MyCompany";
static String model = "LCD";
static String info;
static{
info = company + "-" + model;
}
}
package ch06.sec10.exam02
public class TelevisionExample{
public static void main(String[] args){
System.out.println(Television.info);
}
}
MyCompany-LCD
정적 메소드와 정적 블록은 객체가 없어도 실행된다는 특징 때문에 내부에 인스턴스 필드나 인스턴스 메소드를 사용할 수 없다. 또한 객체 자신의 참조인 this도 사용할 수 없다.
인스턴스 필드와 정적 필드는 언제든지 값을 변경할 수 있다. 그러나 경우에 따라서는 값을 변경하는 것을 막고 읽기만 허용해야 할 때가 있다. 이때 final필드와 상수를 선언해서 사용한다.
final은 최종적이란 뜻을 가지고 있다. final 필드는 초기값이 저장되면 이것이 최종적인 값이 되어서 프로그램 실행 도중에 수정할 수 없게 된다. final 필드는 다음과 같이 선언한다.
final 타입 필드 [=초기값];
final 필드에 초기값을 줄 수 있는 방법은 다음 두 가지 밖에 없다.
1. 필드 선언 시에 초기값을 대입
2. 생성자에서 초기값 대입
고정된 값이라면 필드 선언 시에 주는 것이 제일 간단하다. 하지만 복잡한 초기화 코드가 필요하거나 객체 생성 시에 외부에서 전달된 값으로 초기화한다면 생성자에서 해야 한다. 이 두 방법을 사용하지 않고 final 필드를 그대로 남겨두면 컴파일 에러가 발생한다.
우리 주변에는 불변의 값이 있다. 불변의 값은 수학에서 사용되는 원주율 파이나 지구의 무게 및 둘레 등이 해당된다. 이런 불변의 값을 저장하는 필드를 자바에서는 상수라고 부른다.
상수는 객체마다 저장할 필요가 없고, 여러 개의 값을 가져도 안되기 때문에 static이면서 final인 특성을 가져야 한다. 따라서 상수는 다음과 같이 선언한다.
static final 타입 상수 = [=초기값];
초기값은 선언 시에 주는 것이 일반적이지만, 복잡한 초기화가 필요할 경우에는 정적 블록에서 초기화할 수도 있다.
static final 타입 상수;
static {
상수 = 초기값;
}
상수 이름은 모두 대문자로 작성하는 것이 관례. 만약 서로 다른 단어가 혼합된 이름이라면 언더바 _로 단어들을 연결한다.
또한 상수는 정적 필드이므로 클래스로 접근해서 읽을 수 있다.
패키지는 클래스의 일부분이며, 클래스를 식별하는 용도로 사용된다.
주로 개발 회사의 도메인 이름의 역순으로 만든다. 예를 들어 mycompany.com회사의 패키지는 com.mycompany로, yourcompany 회사의 패키지는 com.yourcompany로 만든다. 이렇게 하면 두 회사에서 개발한 Car 클래스가 있을 경우 같이 관리할 수 있다.
패키지는 상위 패키지와 하위 패키지를 도트로 구분한다. 도트는 물리적으로 하위 디렉토리임을 뜻한다. 예를 들어 com.mycompany 패키지의 com은 상위 디렉토리 mycompany는 하위 디렉토리이다.
패키지 디렉토리는 클래스를 컴파일하는 과정에서 자동으로 생성된다. 컴파일러는 클래스의 패키지 선언을 보고 디렉토리를 자동 생성시킨다. 패키지 선언은 package 키워드와 함께 패키지 이름을 기술한 것으로, 항상 소스 파일 최상단에 위치해야 한다.
package 상위패키지.하위패키지
public class 클래스명 {}
패키지 이름은 모두 소문자로 작성하는 것이 관례이다. 그리고 패키지 이름이 서로 중복되지 않도록 회사 도메인 이름의 역순으로 작성하고, 마지막에는 프로젝트 이름을 붙여주는 것이 일반적이다.
com.samsung.projectname
com.lg.projectname
com.apache.projectname
같은 패키지에 있는 클래스는 아무런 조건 없이 사용할 수 있지만, 다른 패키지에 있는 클래스를 사용하려면 import 문을 이용해서 어떤 패키지의 클래스를 사용하는지 명시해야 한다.
다음은 com.mycompany 패키지의 Car 클래스에서 com.hankook 패키지의 Tire 클래스를 사용하기 위해 import 문을 사용한 것이다.
package com.mycompany;
import com.hankook.Tire;
public class Car{
Tire tire = new Tire();
}
만약 동일한 패키지에 포함된 다수의 클래스를 사용해야 한다면 클래스 이름을 생략하고 *를 사용할 수 있다.
경우에 따라서는 객체의 필드를 외부에서 변경하거나 메소드를 호출할 수 없도록 막아야 할 필요가 있다. 중요한 필드와 메소드가 외부로 노출되지 않도록 해 객체의 무결성(결점이 없는 성질)을 유지하기 위해서이다.

자바는 이러한 기능을 구현하기 위해 접근 제한자를 사용한다. 접근 제한자는 public, protected, private의 세 가지 종류가 있다.

default는 접근 제한자가 아니라 접근 제한자가 붙지 않은 상태를 말한다.
클래스를 어디에서나 사용할 수 있는 것은 아니다. 클래스가 어떤 접근 제한을 갖느냐에 따라 사용가능 여부가 결정된다. 클래스는 public과 default 접근 제한을 가질 수 있다.
[ public ] class 클래스 {}
클래스를 선언할 때 public 접근 제한자를 생략했다면 클래스는 default 접근 제한을 가진다. 이 경우 클래스는 같은 패키지에서는 아무런 제한 없이 사용할 수 있지만 다른 패키지에서는 사용할 수 없게 된다.
객체를 생성하기 위해 생성자를 어디에서나 호출할 수 있는 것은 아니다. 생성자가 어떤 접근 제한을 갖느냐에 따라 호출 가능 여부가 결정된다. 생성자는 public, default, private 접근 제한을 가질 수 있다.
public class ClassName{
//생성자 선언
[ public | private ] ClassName() {}
}

필드와 메소드도 어디에서나 읽고 호출할 수 있는 것은 아니고, 어떤 접근 제한을 갖느냐에 따라 호출 여부가 결정된다. 필드와 메소드는 public, default, private 접근 제한을 가질 수 있다.
//필드 선언
[ public | private ] 타입 필드;
//메소드 선언
[ public | private ] 리턴타입 메소드(..) {}

객체의 필드(데이터)를 외부에서 마음대로 읽고 변경할 경우 객체의 무결성(결점이 없는 성질)이 깨질 수 있다. 예를 들어 자동차의 속력은 음수가 될 수 없는데, 외부에서 음수로 변경하면 객체의 무결성이 깨진다.
Car myCar = new Car();
myCar.speed = -100;
이러한 문제점 때문에 객체 지향 프로그래밍에서는 직접적인 외부에서의 필드 접근을 막고 대신 메소드를 통해 필드에 접근하는 것을 선호한다. 그 이유는 메소드는 데이터를 검증해서 유효한 값만 필드에 저장할 수 있기 때문이다 이러한 역할을 하는 메소드가 Setter이다.
다음 코드를 보자. speed 필드는 private 접근 제한을 가지므로 외부에서 접근하지 못한다. speed필드를 변경하기 위해서는 Setter인 setSpeed() 메소드를 이용해야 한다. setSpeed() 메소드는 외부에서 제공된 변경값(매개값)을 if 문으로 검증하는데, 음수일 경우 0을 필드값으로 저장한다.
private double speed;
public void setSpeed(double speed){
if(speed < 0 ){
this.speed = 0; //매개값이 음수일 경우 speed필드에 0으로 저장하고, 메소드 실행 종료
return;
}else{
this.speed = speed;
}
}
외부에서 객체의 필드를 읽을 때에도 메소드가 필요한 경우가 있다. 필드값이 객체 외부에서 사용하기에 부적절한 경우, 메소드로 적잘한 값으로 변환해서 리턴할 수 있기 떄문이다. 이러한 역할을 하는 메소드가 Getter이다.
다음 예시를 보자. speed 필드는 private 접근 제한을 가지므로 외부에서 읽지 못한다. speed 필드를 읽기 위해서는 Getter인 getSpeed() 메소드를 이용해야한다. getSpeed() 메소드는 마일 단위의 필드 값을 km 단위로 변환해서 외부로 리턴한다.
private double speed; // speed의 단위는 마일
public double getSpeed(){
double km = speed*1.6;
return km; //필드값인 마일을 km단위로 환산 후 외부로 리턴
}
애플리케이션 전체에서 단 한 개의 객체만 생성해서 사용하고 싶다면 싱글톤 패턴을 적용할 수 있다. 싱글톤 패턴의 핵심은 생성자를 private 접근 제한해서 외부에서 new 연산자로 생성자를 호출할 수 없도록 막는 것이다.
provate 클래스 (){}
생성자를 호출할 수 없으니 외부에서 마음대로 객체를 생성하는 것이 불가능해진다. 대신 싱글톤 패턴이 제공하는 정적 메소드를 통해 간접적으로 객체를 얻을 수 있다.
다음은 싱글톤 패턴의 전체 코드를 보여준다.
public class 클래스{
//private 접근 권한을 갖는 정적 필드 선언과 초기화
private static 클래스 singleton = new 클래스(); // 1
//private 접권 권한을 갖는 생성자 선언
private 클래스(){}
//public 접근 권한을 갖는 정적 메소드 선언
public static 클래스 getIntsnce(){
return singleton;
}
}
1에서는 자신의 타입으로 정적 필드를 선언하고 미리 객체를 생성해서 초기화시킨다. 그리고 private 접근 제한자를 붙여 외부에서 정적 필드값을 변경하지 못하도록 막는다.
2에서는 정적 필드값을 리턴하는 getIntsnce() 정적 메소드를 public으로 선언하였다.
외부에서 객체를 얻는 유일한 방법은 getInstance() 메소드를 호출하는 것이다. getInstance() 메소드가 리턴하는 객체는 정적 필드가 참조하는 싱글톤 객체이다. 따라서 아래 코드에서 변수1과 변수2개 참조하는 객체는 동일한 객체가 된다.
클래스 변수1 = 클래스.getInstance();
클래스 변수2 = 클래스.getInstance();