패스트캠퍼스의 국비지원교육인 Java & Spring 웹 개발 종합반의 3주차는 객체지향의 개념을 본격적으로 학습한다. 패스트캠퍼스에서는 객체 지향 입문은 물론 객체 지향 핵심 강의를 별도로 제공하고 있어 3주차의 내용이 방대하다. 이 학습 노트에서는 주된 내용만 핵심을 짚어 빠르게 넘어가도록 한다.
Java는 접근 제어자를 통해 변수나 메서드의 사용 권한을 설정할 수 있다.
접근 제어자는 private
default
protected
public
4가지가 있으며, 오른쪽으로 갈수록 넓은 접근 범위를 갖는다.
private
이 붙은 변수, 메서드는 해당 클래스에서만 접근이 가능하다.
public class A {
// 프라이빗 변수
private int number = 10;
private int getNumber() {
return 10;
}
}
class B {
A a = new A();
a.number; // [에러] 다른 클래스에서 참조 불가
a.getNumber(); // [에러]
}
위 예제의 number 변수와 getNumber 메서드는 A 클래스 내부에 선언되어 있으므로 A클래스 내부에서만 참조, 호출이 가능하고 B 클래스에서는 접근할 수 없다.
접근 제어자가 없는 경우 default
로 설정되며 같은 패키지 내에서만 접근이 가능하다. 즉 클래스는 달라도 패키지가 같다면 참조할 수 있다.
package a;
public class A {
int number = 10; // 접근 제어자가 없다면 default
}
---
package b;
public class B {
A a = new a();
a.number; // [에러] 다른 패키지에서 참조 불가
}
클래스 A는 a 패키지에 있고 클래스 B는 b 패키지에 속해있다. default 변수나 메서드는 동일 패키지내에 있는 클래스라면 참조가 가능하지만 다른 패키지에서는 접근할 수 없다.
protected
가 붙은 변수 또는 메서드는 같은 패키지 혹은 해당 클래스를 상속받은 다른 클래스에서 접근이 가능하다.
package a;
public class A {
protected int number = 10; // 접근 제어자가 없다면 default
}
---
package b;
public class B extends A {
A a = new a();
a.number; // 상속을 받은 경우 사용 가능
}
클래스 A는 a 패키지에 있고 클래스 B는 b 패키지에 속해있지만 A클래스를 상속받았다. default 였다면 클래스 B에서 A의 멤버에 접근할 수 없지만 protected 및 extends 를 통해 접근이 가능하다.
public
은 어떠한 클래스에서라도 접근이 가능하다.
package a;
public class A {
public int number = 10; // 접근 제어자가 없다면 default
}
---
package b;
public class B {
A a = new a();
a.number; // public = 어디에서나 참조 가능
}
접근 제어자를 사용하면 프로그래머의 코딩 실수를 방지할 수 있고 기타 위험요소를 제거할 수 있다.
그렇다면 외부의 접근에 대해 보호를 받고있는 멤버변수를 다른 클래스에서는 어떻게 참조할까? 이때 사용하는 것이 getter & setter 이다. private로 설정된 멤버변수를 가져오거나 재설정하는 메서드를 통해 접근할 수 있게 한다.
public class Car {
// private member variable
private String modelName;
private String carType;
// getter
public String getModelName() {
return modelName;
}
public String getcarType() {
return carType;
}
// setter
public void setModelName(String modelName) {
this.modelName = modelName
}
public void setCarType(String carType) {
this.carType = carType
}
}
데이터와, 데이터를 처리하는 행위를 묶고, 외부에는 그 행위를 보여주지 않는 것을 캡슐화라 한다. 꼭 필요한 정보와 기능만 외부에서 참조가 가능하도록 접근 제어자를 설정하고 그 외 변수나 메서드를 private 으로 설정한다. 이렇게 하면 외부에는 통합된 인터페이스만 제공하므로 일관된 기능을 구현하게 할 수 있고 각각의 메서드나 변수에 중구난방으로 접근하며 발생되는 오류를 최소화 할 수 있다.
class Capsule {
int number;
public Capsule(int number) {
this.number = number;
}
public double getHalf() {
return number / 2;
}
}
------------------------------------------------------
class Main {
public static void main(String[] args) {
Capsule capsule = new Capsule(10);
System.out.println(capsule.getHalf());
}
}
캡슐화의 간단한 예제로 int값을 초기값으로 갖는 객체가 있고, 그 값의 절반을 반환하는 gethalf() 라는 메소드가 존재한다. 캡슐화란 Capsule 클래스 그 자체이며 위에서 말한대로 데이터와, 데이터를 처리하는 행위를 묶고, 외부에는 그 행위를 보여주지 않는 행위를 한다.
캡슐화를 통해 우리가 얻을 수 있는 이점중 가장 큰것은 코드의 중복을 피할 수 있다는 점과, 데이터를 처리하는 동작 방식을 외부에서 알 필요가 없다는 점이다.
Java에서 this는 객체 자기 자신을 나타낸다. 주로 3가지 형태로 사용되고 있다.
첫 번째, 클래스의 속성과 생성자, 메서드의 매개변수 이름이 같은 경우
클래스의 속성을 나타낼 때 this 를 사용한다.
public class Car {
public String color;
public Car(String color) {
this.color = color;
}
}
위 예제는 color
라는 속성값이 있으며 매개변수로 color
를 갖는 생성자가 있다. 매개변수로 가져온 color
를 자신의 속성인 color
에 할당하여 객체를 생성한다. 생성자 내부 블록에서 color
는 파라메터로 가져온 변수명을, this.color
는 객체 자신의 속성을 말한다.
두 번째 클래스에 오버로딩된 다른 생성자 호출
시 사용한다. 이 때 주의할 점은 생성자의 가장 최상단, 가장 먼저 사용되어야 한다.
public class Car() {
// 매개변수가 없는 생성자
public Car() {
this("Blue", 4); // 다른 생성자를 호출하며 매개변수를 넘겨줌
}
// 2개의 매개변수를 갖는 생성자
public Car(String color, int door) {
this.color = color;
this.door = door;
}
}
위 예제에서 Car car = new Car();
를 하게 된다면 기본 생성자에서 다른 생성자를 호출하며 "Blue"
와 4
를 넘겨준다. 이는 다른 생성자에 매개변수로 참조되며 인스턴스가 생성된다.
세 번째는 객체 자신의 참조값을 전달하고 싶을 때
사용한다.
public class Car() {
public Car getInstance() {
return this;
}
}
위 코드의 getInstance()
메서드는 Car 타입을 반환하는 메서드로 객체 자기 자신의 참조값을 반환한다.
정적이라는 뜻을 가진 static
키워드는 변수 또는 메서드 앞에 붙여 사용하며 이러한 변수나 메서드를 스태틱 변수, 스태틱 메서드 또는 클래스 변수, 클래스 메서드라고도 한다.
static
멤버들은 객체(인스턴스)에 소속된 멤버가 아니라 클래스 자체에 고정된 멤버이다. 따라서 클래스 로더가 클래스를 로딩해서 메소드 메모리 영역에 적재할때 클래스별로 관리된다. 따라서 클래스의 로딩이 끝나는 즉시, 객체 생성 없이 바로 사용할 수 있다.
static
키워드를 통해 생성된 정적멤버들은 Heap영역이 아닌 Static영역에 할당되며 할당된 메모리는 모든 객체가 공유하여 하나의 멤버를 어디서든지 참조할 수 있는 장점을 가진다.
다만 static
영역은 Garbage Collector의 관리 영역 밖에 존재하기에 Static영역에 있는 멤버들은 프로그램의 종료시까지 메모리가 할당된 채로 존재한다. 너무 많은 static 멤버의 선언은 시스템 성능에 악영향을 끼친다.
public class A {
static char getA() {
return 'A';
}
}
------------------------------------
public class B {
// A a = new A();
// a.getA();
// 인스턴스 생성이 불필요함
char a = A.getA();
}
Java의 디자인 패턴 중 하나인 싱글톤 패턴은 프로그램에서 인스턴스가 단 한개만 존재해야 하는 경우 사용되는 패턴이다. static 메서드를 통해 구현할 수 있으며 private
생성자를 통해 접근을 막는다.
public class Singleton {
// 생성자는 private - new 연산자를 통해 타 클래스에서 인스턴스 생성 불가
private Singleton() {}
// private static 으로 객체 생성
private static Singleton singleton = new Singleton();
// 위 생성된 인스턴스를 전달
public Singleton getInstance() {
return singleton;
}
}