
💡클래스는 객체가 아는 것과 객체가 하는 것을 기술한다.
클래스는 객체에 설계도 입니다. 클래스를 만든다는 것은 JVM에서 어떻게 그 타입의 객체를 만드는지 기술하는 것 입니다.
어떤 타입의 모든 객체는 서로 다른 인스턴스 변숫값을 가질수 있습니다. 하지만 메서드는 다릅니다.
특정 클래스의 각 인스턴스는 동일한 메서드를 갖지만, 메서드의 행동은 인스턴스 변숫값에 따라 달라질수 있습니다.
⭐메서드에 특정 값을 전달할 수 있다. [메서드의 매개변수]
다른 프로그램밍 언어와 마찬가지로 자바도 메서드에 특정 값을 전달할 수 있다.
메서드로 전달하는 값을 지칭할 때는 보통 매개변수(parameter) 또는 인자(argument)라고 쓴다.
여기서는 호출하는 쪽에서 넘기는 것은 인자, 메서드에서 받는 것은 매개변수 입니다.
그리고 메서드에서 매개변수를 받도록 선언 했다면 그 메서드를 호출할 때 반드시 무언가를 전달해야 합니다. 그리고 그 ‘무언가’는 반드시 정해진 타입이어야 합니다.
- Dog 레퍼런스의 bark 메서드를 호출하는데, 이 때 3 이라는 값을 전달합니다.
- Dog d = new Dog(); d.bark(3); // 이때 3의 '인자'를 bark() 메서드로 보낸다. void bark(int numOfBarks) { // 이때 numOfBarks 는 매개변수가 되고, 3을 받는다. System.out.println("ruff"); numOfBarks = numOfBarks -1; } }
- bark 메서드에는 3을 나타내는 비트들이 전달된다.
- 이 비트들은 numOfBarks 매개변수(int 크기의 변수)에 들어간다.
- numOfBarks 매개변수는 메서드 코드 내에서 변수로 쓰인다.
메서드에서 특정 값을 리턴할 수도 있다?
메서드에서 특정 값을 리턴할 수 있습니다. 이렇게 특정 값을 반환하는 것을 ‘리턴한다’ 라고 합니다.
리턴 타입을 void로 선언하면 그 메서드에서는 아무것도 리턴하지 않습니다.
void go() {
}
하지만 다음과 같은 식으로 호출한 쪽에 특정한 타입의 값을 리턴하도록 메서드를 선언할 수 있습니다.
int giceSecret() {
return 42;
}
메서드를 선언할 때 어떤 값을 리턴하겠다고 선언했다면 그렇게 선언한 타입의 값을 반드시 리턴해야 합니다.
메서드에 두 개 이상의 인자를 전달할 수있다?
메서드에 매개변수 여러 개가 있을 수도 있습니다. 매개변수 여러 개가 필요하다면 선언할 때 각 매개변수를 쉼표로 구분해서 쓰면 됩니다. 그리고 인자를 전달할 때도 각 인자를 쉼표로 구분하면 됩니다. 가장 중요한것은 메서드의 매개변수가 있을때 그 타입과 순서를 올바르게 맞춰 전달해야 합니다.
매개변수 두 개가 있는 메서드를 호출하면서 인자 두개를 보낼때
void go() {
TestStuff t = new TestStuff();
t.takeTwo(12, 34) // takeTwo 메서드에 두 개의 인자 전달.
}
void takeTwo(int x, int y) { // 두 개의 매개변수 선언
int z = x + y;
System.out.println("Total is " + z);
}
변수 타입이 매개변수 타입과 일치한다면 변수를 매개변수로 전달할 수 도 있습니다.
void go() {
int foo = 7;
int bar = 3;
t.takeTwo(foo, bar); // 선어된 변수 'foo', 'bar'가 인자로 전달.
}
void takeTwo(int x, int y) {
int z = x + y;
System.out.println("Total is " + z);
}
자바는 값으로 전달한다. 즉, 복사본을 전달하는 것이다.
- int 변수를 선언하고 거기에 “7”이라는 값을 대입한다. 그 러면 7에 해당하는 비트 패턴이 x라는 이름의 변수에 들어간다.
- int x = 7; // x라는 변수에 7이 대입됨.
- z 라는 이름의 int 매개변수가 있는 메서드 go()를 선언 한다.
- void go(int z) { }
- go() 메서드를 호출하는데, 이때 x 변수를 인자로 전달한다. x에 들어 있는 비트들이 복사 되며, 그 복사본들은 z로 들어간다.
- foo.go(x) // 변수 x의 값이 go() 메소드로 복사되어 전송된다.
- 메서드 안에 있는 z 의 값을 바꾸더라도 x의 값은 바뀌지 않는다. z 매개변수로 전달된 인자는 x의 복사 본일 뿐이다.
- 따라서 메서드에서는 그 메서드를 호출할 때 사용했던 x 변수에 들어 있는 비트는 바꿀수 없다.
✅핵심정리
- 클래스에서는 객체가 아는 것과 객체가 하는 것을 정의한다.
- 인스턴스 변수는 객체가 아는 것이다.
- 메서드는 객체가 하는 것이다.
- 메서드에서 인스턴스 변수를 이용해서 같은 타입의 객체가 다른 식으로 행동하게 할 수 있다.
- 메서드에서 매개변수를 사용할 수 있다. 즉, 메서드에 값 한개 이상을 전달 할 수 있다.
- 전달하는 값의 개수와 타입은 반드시 메서드를 선언할 때 지정한 것과 같아야 하며 그 순서도 같아야 한다.
- 메서드 안팎으로 전달되고 리턴되는 값은 상황에 따라 자동으로 더 큰 타입으로 올라갈 수 있다. 더 작은 으로 바꿔야 한다면 강제로 캐스팅 해야한다.
- 메서드 인자를 전달할 때는 리터럴 값을 사용할 수도 있고 선언된 매개 변수 타입의 변수를 사용할수 있다.
- 메서드를 선언할때 반드시 리턴 타입을 지정해야 한다. 리턴 타입을 void로 지정하면 아무것도 리턴하지 않아도 된다.
- 메서드를 선언할때 void가 아닌 리턴 타입을 지정했을때는 반드시 선언된 리턴 타입과 호환 가능한 값을 리턴해야 한다.
❗매개변수와 리턴 타입 활용 방법
매개변수와 리턴 타입에 대해 배웠으니 이제 그 활용 방법을 알아보자. 가장 대표적인 것은 게터(getter)와 세터(setter) 이다. 정식명칭으로는 접근자(accessor)와 변경자(mutator)이다.
게터와 세터라는 명칭은 자바에서 일반적으로 부르는 메서드에 이름을 붙이는 방법이다.
게터는 어떤 것을 가져오고, 세터는 어떤것을 설정하는 역할을 한다.
class ElectricGuitar {
String brand;
int numOfPickups;
boolean rockStarUsesIt;
String getBrand() { // getter 브랜드 변수의 값을 가져온다.
return brand;
}
void setBrand(String abrand) { // setter 브랜드 변의 값을 설정한다.
brand = abrand
}
int getNumOfPickups() {
return numOfPickups;
}
void setNumOfPickups(int num) {
numOfPickups = num;
}
boolean getRockStarUsesIt() {
return rockStarUsesIt;
}
void setRockStarUsesIt(boolean yseOrNo) {
rockStarUsesIt = yesOrNo;
}
}
// 인스턴스 변수와 메서드명은 이런식으로 작성하는 것이 좋다.
⭐캡슐화
지금까지 코드를 작성할때 객제치향에서의 가장 큰 잘못을 하고 있다. 바로 우리의 데이터를 완전히 노출 시키고 있다는 점 입니다. 심지어 데이터를 아무나 건드릴수 있도록 무관심하게 방치해 놓고 있습니다.
여기서 노출 되어있다는 다음과 같은 점 연산자를 써서 직접 적으로 접근할 수 있다를 말합니다.
theCat.height = 27; // 데이터의 노출
이렇게 인스턴스 변수를 직접적으로 변경시키는 것은 잘못된 것입니다.
theCat.higeht = 0; // 이런식으로 특정 크기를 0으로 지정하면 코드에 의도와 달라질수 있기 때문 입니다.
그러므로 모든 인스턴스 변수에 대해 세터를 만들어야 합니다. 그리고 다른 코드에서는 그 데이터에 절대 직접 접근할 수 없고, 반드시 게터 메서드를 사용해야 하도록 강제해야 합니다.
public void setHeight(int ht) {
if (ht > 9) { // 고양이 키의 최솟값을 보장하기 위한 검사 코드.
height = ht;
}
}
⭐데이터를 숨기자!
정확하게 어떻게 해야 데이터를 숨길수 있을까요? 바로 public과 private라는 접근 변경자를 사용하면 됩니다.
캡슐화를 하려면 인스턴스 변수를 private으로 지정하고 접근 제어를 위해 public으로 지정된 게터와 세터를 만들면 됩니다.
<aside> 💡
인스턴스 변수는 private / 게터와 세터는 public
</aside>
캡슐화를 하면 메서드에 새로운 기능을 추가하지 않더라도 나중에 마음을 바꿀 수 있다는 큰 장점이 있으며, 나중에 메서드를 더 안전하고, 더 빠르게, 더 좋게 고칠 수 있다.
배열에 있는 객체는 어떤 식으로 행동할까요?
다른 객체와 똑같습니다. 접근하는 방법이 조금 다를 뿐입니다. 배열에 들어 있는 Dog 객체를 예로 들겠습니다.
- Dog 레퍼런스 일곱 개를 담을 수 있는 Dog 배열을 선언하고 생성한다.
- Dog[] pets; pets = new Dog[7];
- Dog 객체 두 개를 새로 만들고 첫 번째와 두 번째 배열 원소에 대입한다.
- pets[0] = new Dog(); pets[1] = new Dog();
- Dog 객체 두 개의 대해 메서드를 호출한다.
- pets[0].setSize(30); int x = pets[0].getSize(); pets[1].setSize(8); int y = pets[1].getSize();
이렇게 배열은 배열 하나 하나의 값을 setter 와 getter로 설정하고 불러 올수 있다.
인스턴스 변수 선언과 초기화
인스턴스 변수에는 항상 어떤 기본 값이 들어있다.
인스턴스 변수에 직접 어떤 값을 대입하거나 세터 메서드를 호출하지 않으면 그 인스턴스 변수에는 여전히 기본값이 들어 있다.
$$ \mathbb {정수: 0}\\ {부동소수점 수: 0.0}\\ {불리언: false}\\ {레퍼런스: null} $$
인스턴스 변수와 로컬 변수의 차이점
로컬 변수에는 기본값이 없습니다. 따라서 로컬 변수를 초기화하기 전에 사용하려고 하면 컴파일 과정에서 오류가 발생합니다.
- 인스턴스 변수는 클래스 내에서 선언 됩니다. 메서드 내에서 선언되는 것이 아닙니다.
- class Horse { private double height = 15.2; private String breed; // 다른 코드 }
- 로컬변수는 메서드 내에서 선언 됩니다.
- class AddThing{ // 인스턴스 변수 int a; int b = 12 public int add() { int total = a + b // 로컬 변수 return total; } }
- 로컬변수는 사용하기 전에 반드시 초기화 해야합니다.
- class Foo { public void go() { int x; int z = x + 3 } } // 컴파일이 진행되지 않습니다! x를 선언할때 아무 값도 저장하지 않아도 되긴 하지만 // 그렇게 값이 선어되지 않은 값을 사용하려고 하면 컴파일 할때 오류가 납니다. **// 로컬 변수는 꼭 초기화를한 후 사용 해야합니다.**
그렇다면 매개변수는 로컬변수와 관련된 규칙이 매개변수에 적용될까?
메서드의 매개변수는 로컬변수와 아주 밀접하다. 하지만 메서드 매개변수는 한 번 초기화 되면 절대 해체 되지 않았으므로 컴파일러에서 매개변수가 초기화 되지 않았을 수 있다고 알리는 오류메시지가 나오는 일이 없다.
그러나 메서드를 호출할때 메서드에서 필요한 인자를 전달하지 않는다면 컴파일할때 오류가 발생한다. 따라서 매개변수는 인자를 넘겨주므로서 변수를 초기화 한다.
⭐원시 변수와 레퍼런스 변수 비교 [객체 동치]
원시 타입 두개를 비교하거나 레퍼런스 두 개가 같은 객체를 참조하고 있는지 알고 싶다면 == 연산자를 쓰면 된다.
서로 다른 두 객체가 똑같은지를 알고 싶을때는 .equals() 메서드를 사용하면 된다.
객체 동치의 개념은 객체의 타입에 따라 달라질수 있다.
예를 들어 , String 객체 두 개에 똑같은 문자가 들어 있다면 힙에서 서로 다른 두객체일지라도 똑같다고 할 수 있다. 하지만 Dog 객체의 크기(size 변수) 와 무게(weight 변수)가 같은 두 Dog 객체는 그냥 같다고 할 수 없다 서로 다른 두 객체를 같은 것으로 간주 할 수 있을지는 객체 타입에 따라 달라진다.
두 원시 값을 비교할 때는 == 연산자를 쓴다.
== 연산자는 임의 타입의 두 변수를 비교하기 위한 용도로 쓸 수 있다. 단순하게 비트들을 비교하는 역할을 한다.
int a = 3;
byte b = 3;
if(a == b) { // 비트만을 비교하므로 참이 된다.
System.out.print("true)
}
레퍼런스 두 개가 똑같은지(즉, 힙에 들어 있는 똑같은 객체를 참조하는지)확인할 때도 == 연산자를 쓸 수 있다.
== 연산자는 변수에 들어 있는 비트들의 패턴에만 신경을 쓴다는 점을 꼭 기억해야 한다. 변수가 레퍼런스든, 원시 변수든 상관 없이 똑같은 규칙이 적용 된다. 따라서 레퍼런스 변수 두 개가 똑같은 객체를 참조하면 == 연산자에서 참을 리턴한다.
Foo a = new Foo();
Foo b = new Foo();
Foo c = a;
if(a == b) {} // 거짓
if(a == c) {} // 참
if(b == c) {} // 거짓
✅ 핵심 정리
- 캡슐화는 클래스의 데이터를 변경하는 사람과 방법을 통제합니다.
- 인스턴스 변수는 private으로 만들어서, 변수를 직접 건드려서 바꿀수 없도록 해야 합니다.
- 다른 코드에서 객체의 데이터를 건드리는 것을 통재하려면 세터처럼 값을 바꾸는 공개 메서드를 만들면 됩니다.
- 인스턴스 변수에는 명시적으로 값을 설정하지 않아도 기본값이 대입됩니다.
- 로컬 변수, 즉 메서드 안에 있는 변수는 기본값이 대입되지 않습니다. 항상 초기화를 해야 합니다.
- 두 원시 값이 같은지 확인 할ㄷ 때는 == 연산자를 이용 합니다.
- 두 레펀런스가 같은지 확인 할때, 즉 두 객체 변수가 실제로 동일한 객체를 참조하는지 확인 할때는 == 연산자를 이용합니다.
- 두 String 값에 저장된 글자들이 똑같은지 확인하는 것처럼 두 객체가 동치인지(동일한 객체일 필요 없음) 확인 하고 싶다면 .equals()를 이용합니다.