한빛미디어의 <혼자 공부하는 자바>를 요약 정리했습니다.
필드(field)
객체의 고유 데이터, 부품 객체, 상태 정보를 저장하는 곳
생성자(constructor)
new 연산자로 호출되는 특별한 중괄호 {} 블록
메소드(method)
객체의 동작에 해당하는 중괄호 {} 블록
싱글톤(Singleton) 패턴
하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴
하나의 인스턴스를 만들어 놓고 다른 모듈들이 공유
장점
단점
→ 단위 테스트를 할 때, 테스트가 서로 독립적이어야 하며 테스트를 어떤 순서로든 실행할 수 있어야 하는데, 싱글톤 인스턴스는 자원을 공유하고 있기 때문에 테스트가 결함없이 수행되려면 매번 인스턴스의 상태를 초기화시켜주어야 함
class Singleton {
constructor() {
if (!Singleton.instance) {
Singleton.instance = this
}
return Singleton.instance
}
getInstance() {
return this
}
}
const a = new Singleton()
const b = new Singleton()
console.log(a === b) // true
객체란 물리적으로 존재하거나 추상적으로 생각할 수 있는 것 중에서 자신의 속성을 가지고 있으면서 식별 가능한 것
-> 현실 세계의 객체를 소프트웨어 객체로 설계하는 것을 객체 모델링(object modeling)이라고 함
메소드 호출
리턴값 = 객체.메소드(매개값1, 매개값2, ...);
// 계산기 호출 예제
int result = Calculator.add(10, 20);
객체 간의 관계
객체와 클래스
식별자 작성 규칙
클래스 이름.java
로 소스 파일 생성
public class 클래스이름 {
}
클래스로부터 객체를 생성하려면 new 연산자 사용
new 클래스();
객체를 생성시킨 후 객체의 번지를 리턴
-> 참조 타입인 클래스 변수에 저장해두면 변수를 통해 객체를 사용 가능
클래스 변수;
변수 = new 클래스();
// 1개의 실행문으로 작성
클래스 변수 = new 클래스();
클래스의 두 가지 용도
public class Student {
// 라이브러리로서의 코드 (필드, 생성자, 메소드)
...
// 실행하기 위한 코드
public static void main(String[] args) {
Student s1 = new Student();
System.out.println("s1 변수가 Student 객체를 참조합니다.");
Student s2 = new Student();
System.out.println("s2 변수가 또 다른 Student 객체를 참조합니다.");
}
}
public class className {
// 필드 : 객체의 데이터가 저장되는 곳
int fieldname;
// 생성자 : 객체 생성 시 초기화 역할 담당
ClassName() { ... }
// 메소드 : 객체의 동작에 해당하는 실행 블록
void methodName() { ... }
필드(field)
객체의 고유 데이터, 부품 객체, 상태 정보를 저장하는 곳
생성자(constructor)
new 연산자로 호출되는 특별한 중괄호 {} 블록
메소드(method)
객체의 동작에 해당하는 중괄호 {} 블록
객체 고유의 데이터, 객체가 가져야 할 부품, 객체의 현재 상태 데이터를 저장하는 곳
ex. 자동차 객체
클래스 중괄호 {} 블록 어디서든 존재 가능
타입 필드 [ = 초기값];
String company = "현대자동차";
String model = "그랜저";
int maxSpeed = 300;
int productionYear;
boolean engineStart;
필드값을 읽고 변경하는 작업
public class Car {
// 필드
String company = "현대자동차";
String model = "그랜저";
int maxSpeed = 350;
}
public class CarExample {
public static void main(String[] args) {
// 객체 생성
Car myCar = new Car();
// 필드값 읽기
System.out.println("제작회사: " + myCar.company);
System.out.println("모델명: " + myCar.model);
System.out.println("최고속도: " + myCar.maxSpeed);
// 필드값 변경
myCar.maxSpeed = 60;
new 연산자로 클래스로부터 객체를 생성할 때 호출되어 객체의 초기화를 담당
모든 클래스는 생성자가 반드시 존재하며, 하나 이상 가질 수 있음
[public] 클래스() { }
생성자는 리턴 타입이 없고 클래스 이름과 동일
클래스(매개변수선언, ...) {
// 객체의 초기화 코드
}
// Car 예제
public class Car {
// 생성자
Car(String color, int cc) { ... }
}
public class CarExample {
public static void main(String[] args) {
Car myCar = new Car("검정", 3000);
Car myCar = new Car(); // x: 기본 생성자 호출 불가능
}
}
클래스로부터 객체가 생성될 때 필드는 기본 초기값으로 자동 설정
public class Korean {
// 필드
String nation = "대한민국";
String name;
String ssn;
// 생성자
public Korean(String n, String s) {
name = n;
ssn = s;
}
}
Korean k1 = new Korean("박자바", "011225-1234567");
필드와 매개 변수 이름이 동일한 경우, 필드 앞에 this.
를 붙여 구분
public Korean(String name, String ssn) {
this.name = name;
this.ssn = ssn;
}
외부에서 제공되는 다양한 데이터들을 이용해서 객체를 초기화하려면 생성자도 다양해야 함
-> 생성자 오버로딩 : 매개 변수를 달리하는 생성자를 여러 개 선언하는 것
public class 클래스 {
클래스 ([타입 매개변수, ... ]) { ... }
클래스 ([타입 매개변수, ... ]) { ... }
}
public class Car {
Car() { ... }
Car(String model) { ... }
Car(String model, String color) { ... }
}
생성자 오버로딩이 많아질 경우 생성자 간의 중복된 코드 발생 가능
-> 필드 초기화 내용은 한 생성자에만 집중적으로 작성하고 나머지 생성자는 초기화 내용을 가지고 있는 생성자를 호출
클래스([매개변수, ...]) {
this(매개변수, ..., 값, ...); // 클래스의 다른 생성자 호출
실행문;
}
this()는 자신의 다른 생성자를 호출하는 코드로 반드시 생성자의 첫 줄에서만 허용
Car(String model) {
this.model = model;
this.color = "은색";
this.maxSpeed = 250;
}
Car(String model, String color) {
this.model = model;
this.color = color;
this.maxSpeed = 250;
}
Car(String model, String color, int maxSpeed) {
this.model = model;
this.color = color;
this.maxSpeed = maxSpeed;
}
// this() 사용
Car (String model) {
this(model, "은색", 250)
}
Car (String model, String color) {
this(model, color, 250);
}
Car(String model, String color, int maxSpeed) {
this.model = model;
this.color = color;
this.maxSpeed = maxSpeed;
}
메소드 선언은 선언부와 실행 블록으로 구성: 메소드 시그니처
리턴타입 메소드이름([매개변수선언, ...]) {
실행 코드 작성
}
// example
void powerOn() { ... }
double divide(int x, int y) { ... }
powerOn();
double result = divide(10, 20);
// 매개 변수를 배열 타입으로 선언
int sum1(int[] values) { }
int[] values = {1, 2, 3};
int result = sum1(values);
int result = sum1(new int[] {1, 2, 3, 4, 5});
// 매개 변수를 ··· 으로 선언
int sum2(int ··· values) { }
int result = sum2(1, 2, 3);
int result = sum2(1, 2, 3, 4);
···
사용
리턴값이 있는 메소드
리턴 타입이 있는 메소드는 반드시 리턴문을 사용해서 리턴값을 지정
return 리턴값;
// example
int plus(int x, int y) {
int result = x + y;
return result;
리턴값이 없는 메소드: void
void로 선언된 메소드에서도 return문을 사용 가능
return;
// example
void run() {
while(true) {
if(gas > 0) {
gas -= 1;
} else {
return; // run() 메소드 실행 종료
}
}
}
메소드는 클래스 내/외부의 호출에 의해 실행
객체 내부에서 호출
메소드(매개값, ...);
// example
public class ClassName {
void method1(String p1, int p2) { }
void method2() {
method1("홍길동", 100);
}
}
리턴 값을 받고 싶은 경우
public class ClassName {
int method1(int x, int y) {
int result = x + y;
return result;
}
void method2() {
int result1 = method1(10, 20); // 30
double result2 = method2(10, 20); // 30.0
}
}
객체 외부에서 호출
외부 클래스에서 메소드를 호출하려면 클래스로부터 객체를 생성
클래스 참조변수 = new 클래스(매개값, ...);
참조변수.메소드(매개값, ...); // 리턴값이 없거나 리턴값을 받지 않을 경우
타입 변수 = 참조변수.메소드(매개값, ... ); // 리턴값이 있고, 리턴값을 받고 싶을 경우
//example
Car myCar = new Car();
myCar.keyTurnOn();
int speed = myCar.getSpeed();
클래스 내에 같은 이름의 메소드를 여러 개 선언하는 것
class 클래스 {
리턴 타입 메소드이름(타입 변수, ...) { ... }
리턴 타입 메소드이름(타입 변수, ...) { ... }
}
// example
public class Calculator {
// 정사각형의 넓이
double areaRectangle(double width) {
return width * width;
}
// 직사각형의 넓이
double areaRectangle(double width, double height) {
reutrn width * height;
}
}
인스턴스 멤버는 객체(인스턴스)를 생성한 후 사용할 수 있는 필드ㅘ 메소드
인스턴스 멤버 선언
public class Car {
// 필드
int gas;
// 메소드
void setSpeed(int speed) { ... }
}
Car myCar = new Car();
myCar.gas = 10;
myCar.setSpeed(60);
this
객체 내부에서 인스턴스 멤버에 접근하기 위해 사용
Car(String model) {
this.model = model;
}
void setModel(String model) {
this.model = model;
}
클래스에 고정된 멤버로서 객체를 생성하지 않고 사용할 수 있는 필드와 메소드
정적 멤버 선언
정적 필드와 정적 메소드를 선언하려면 필드와 메소드 선언 시 static 키워드를 붙여줌
public class 클래스 {
// 정적 필드
static 타입 필드 [=초기값];
// 정적 메소드
static 리턴 타입 메소드(매개변수선언, ...) { ... }
}
정적 멤버 사용
클래스가 메모리로 로딩되면 정적 멤버를 바로 사용 가능
클래스.필드;
클래스.메소드(매개값, ...);
// example
public class Calculator {
static double pi = 3.14159;
static int plub(int x, int y) { ... }
}
double result1 = 10 * 10 * Calculator.pi;
int result2 = Calculator.plus(10, 5);
전체 프로그램에서 단 하나의 객체만 만들도록 보장해야 하는 경우
public class 클래스 {
// 정적 필드
private static 클래스 singleton = new 클래스();
// 생성자
private 클래스() {}
// 정적 메소드
static 클래스 getInstance() {
return singleton;
}
}
// getInstance 호출
클래스 변수1 = 클래스.getInstance();
final 필드
초기값이 저장되면, 프로그램 실행 도중에 수정할 수 없다는 것
final 타입 필드 [=초기값];
상수(static final)
객체마다 존재하지 않고 클래스에만 존재하며, 한 번 초기값이 저장되면 변경할 수 없음
static final 타입 상수 = 초기값;
// example
static final double PI = 3.14159;
static final double EARTH_RADIUS = 6400;
패키지의 물리적인 형태는 파일 시스템의 폴더
상위패키지.하위패키지.클래스
해당 클래스가 어떤 패키지에 속할 것인지를 선언
package 상위패키지.하위패키지;
public class ClassName { ... }
패키지 명명 규칙
import문
사용하고자 하는 클래스 또는 인터페이스가 다른 패키지에 소속되어 있다면, import문으로 해당 패키지의 클래스 또는 인터페이스를 가져와 사용할 것임을 컴파일러에게 알려주어야 함
com.hankook
과 com.hankook.project
는 서로 다른 패키지import 상위패키지.하위패키지.클래스이름;
import 상위패키지.하위패키지.*;
접근을 제한하기 위해 사용(클래스 및 인터페이스, 이들이 가지고있는 멤버의 접근)
클래스를 선언할 때 해당 클래스를 같은 패키지 내에서만 사용할 것인지 다른 패키지에서도 사용할 수 있도록 할 것인지 결정
default 접근 제한
public 접근 제한
package sec.exam.package1;
class A {} // default 접근 제한
public class B {
A a; // A 클래스 접근 가능
}
package sec.exam.package2;
import sec.exam.package1.*;
public class C {
A a; // A 클래스 접근 불가(컴파일 에러)
B b; // B 클래스 접근 가능
}
객체를 생성하기 위해서는 new 연산자로 생성자를 호출
package sec.exam.package1;
public class A {
// 필드
A a1 = new A(true);
A a2 = new A(1);
A a3 = newA("문자열");
// 생성자
public A(boolean b) {} // public 접근 제한
A(int b) {} // default 접근 제한
private A(String s) {} // private 접근 제한
}
public class B {
// 필드
A a1 = new A(str); // public 생성자 접근 가능
A a2 = new A(1); // default 생성자 접근 가능
A a3 = new A("문자열"); // private 생성자 접근 불가(컴파일 에러)
package sec.exam.package2;
import sec.sxam.package1.A;
public class C {
// 필드
A a1 = new A(str); // public 생성자 접근 가능
A a2 = new A(1); // default 접근 불가(컴파일 에러)
A a3 = new A("문자열"); // private 생성자 접근 불가(컴파일 에러)
}
필드와 메소드를 선언할 때 클래스 내부에서만 사용할 것인지, 패키지 내에서만 사용할 것인지, 다른 패키지에서도 사용할 수 있도록 할 것인지 결정
// 필드 선언
[public | protected | private] [static] 타입 필드;
// 메소드 선언
[publi | protected | private] [static] 리턴 타입 메소드(...) {...}
객체의 필드를 객체 외부에서 직접적으로 접근하는 것을 막음
-> 외부에서 마음대로 변경할 경우 객체의 무결성이 깨질 수 있기 때문
필드는 private로 선언하여 외부에서 접근할 수 없도록 막고, 메소드는 공개해서 외부에서 메소드를 통해 필드에 접근하도록 유도
private 타입 fieldName; // 필드 접근 제한자: private
// Getter
public 리턴 타입 getFieldName() { // 접근 제한자: Public
return fieldName;
}
// Setter
public void setFieldName(타입 fieldName) { // 접근 제한자: public
this.fieldName = fieldName;
}