본문 바로가기
Java/이것이 자바다

클래스

by k-mozzi 2022. 10. 13.
반응형
Preface

 

이번 장에선 객체, 클래스, 필드, 생성자, 메소드, 인스턴스 멤버, 정적 멤버, 패키지, 접근 제한자 등 자바에서 OOP를 구현하기 위해 사용되는 다양한 개념을 종합적으로 공부했다.

 

내용도 많을 뿐더러 static 키워드가 잘 이해되지 않아 시간이 꽤 오래 걸렸다.

 

그래도 같은 내용을 천천히 여러번 읽다보니 어느정도 감을 잡을 수 있었다.

 

이번 장의 마지막 부분에 어노테이션에 관한 내용도 있지만, 내용이 많고 복잡해 따로 글을 업로드할 생각이다.

 


 

1. 객체 지향 프로그래밍

 

 

- 객체(object): 물리적으로 존재하거나 추상적으로 생각할 수 있는 것 중에서 자신의 속성을 가지고 있고 다른 것과 식별 가능한 것

1) 속성: 필드(field)

2) 동작: 메소드(method)

 

 

- 객체 모델링: 현실 세계의 객체를 소프트웨어 객체로 설계하는 것

 

 

- 메소드 호출: 객체가 다른 객체의 기능을 이용하는 것

1) 객체에 도트 연산자를 붙이고 메소드 이름을 기술한다.

2) 도트 연산자: 객체의 필드와 메소드에 접근할 때 사용한다.

 

 

- 매개값: 메소드를 실행하기 위해 필요한 데이터

 

 

- 리턴값: 메소드가 실행하고 나서 호출한 곳으로 리턴하는 값

 

 

- 객체의 상호작용은 객체 간의 메소드 호출을 의미한다.

 

 

- 객체 간의 관계

1) 집합 관계: 하나는 부품이고 하나는 완성품에 해당하는 관계

2) 사용 관계: 객체 간의 상호작용

3) 상속 관계: 상위 객체를 기반으로 하위 객체를 생성하는 관계

 

 

- 객체 지향 프로그래밍(OOP)의 특징

1) 캡슐화(encapsulation): 객체의 필드, 메소드를 하나로 묶고 실제 구현 내용을 감추는 것

2) 상속(inheritance): 상위 객체가 가지고 있는 필드와 메소드를 하위 객체에 물려주는 것

3) 다형성(polymorphism): 같은 타입이지만 실행 결과가 다양한 객체를 이용할 수 있는 성질

→ 하나의 객체가 여러 타입에 속하는 것이 허가되는 성질: 오버로딩, 오버라이딩을 통해 구현

 

 

- 인스턴스(instance): 클래스로부터 만들어진 객체

 

 

- 인스턴스화: 클래스로부터 객체를 만드는 과정

 

 

- 클래스 선언 규칙

1) 하나 이상의 문자로 이루어져야 한다.

2) 첫 번째 글자는 숫자가 올 수 없다.

3) '$', '_' 외의 특수문자는 사용할 수 없다.

4) 자바 키워드는 사용할 수 없다.

5) 단일 단어라면 첫 자를 대문자로, 나머지는 소문자로 작성한다.

6) 서로 다른 단어가 혼합된 경우 각 단어의 첫 글자는 대문자로 작성한다.

 

 

- 파일 이름과 동일한 이름의 클래스 선언에만 public 접근 제한자를 붙일 수 있다.

→ 하나의 파일에 public 키워드가 붙은 클래스는 하나이다.

 

 

- 클래스로부터 객체를 생성하는 방법: new 연산자를 사용한다.

 

 

- new: 클래스로부터 객체를 생성시키는 연산자

→ new 연산자 뒤에는 생성자가 오는데, 생성자는 '클래스( )' 형태를 지닌다.

클래스 변수 = new 클래스();

 

 

- 같은 클래스에서 생성한 객체라 하더라도 각각의 변수가 참조하는 객체는 완전히 독립된 서로 다른 객체이다.

 

 

- 클래스의 용도

1) 라이브러리(API)

2) 실행용

 

 

- 클래스의 구성 멤버

1) 필드(field)

2) 생성자(constructor)

3) 메소드(method)

 

 

- 필드(전역 변수)와 변수(지역 변수)

1) 필드: 객체의 고유 데이터, 부품 객체, 상태 정보를 저장하는 곳

→ 필드와 변수는 다른 개념이다.

2) 변수: 생성자와 메소드 내에서만 사용되고 생성자와 메소드가 실행 종료되면 자동 소멸된다.

→ 필드는 생성자와 메소드 전체에서 사용되며 객체가 소멸되지 않는 한 객체와 함께 존재한다.

 

 

- 생성자 선언과 메소드 선언의 앞과 뒤에서도 필드 선언이 가능하다.

→ 생성자와 메소드 중괄호 블록 내부에 선언된 것은 모두 로컬 변수이다.

 

 

- 필드의 초기값은 필드 선언 시 주어질 수도 있고, 생략될 수도 있다.

1) 로컬 변수는 초기화가 필수이다.

2) 초기값이 지정되지 않은 필드들은 객체 생성 시 자동으로 기본 초기값으로 설정된다.

 

 

- 클래스 외부에서 필드를 사용할 경우 클래스로부터 객체를 먼저 생성해야 한다.

→ 외부 클래스에서 원본 클래스의 필드를 사용하려면 원본 클래스 객체를 생성한 후 '객체명.필드명' 형식으로 사용해야 한다.

 

 

- 생성자: new 연산자와 같이 사용되어 클래스로부터 객체를 생성할 때 호출되어 객체의 초기화를 담당한다.

→ 생성자를 실행시키지 않고는 클래스로부터 객체를 만들 수 없다.

 

 

- 모든 클래스에는 생성자가 반드시 존재해야 하며, 하나 이상을 가질 수 없다.

→ 생성자 선언을 생략하면 컴파일러에서 자동으로 기본 생성자를 바이트 코드에 자동 추가한다.

 

 

- 생성자 괄호 안의 매개 변수는 new 연산자로 객체를 호출할 때 외부의 값을 생성자 블록 내부로 전달하는 역할을 한다.

 

 

- 클래스에 생성자가 명시적으로 선언된 경우엔 반드시 선언된 생성자를 호출해 객체를 생성해야 한다.

package ch6;

class Car2 {
	Car2(String color, int cc) {

	}
}

public class carExample2 {

	public static void main(String[] args) {

		Car2 myCar2 = new Car2("black", 2000);
//		Car2 myCar2 = new Car2(); 기본 생성자를 호출할 수 없다.

	}

}

 

 

- 필드를 초기화하는 방법

1) 필드를 선언할 때 초기값을 주는 방법

2) 생성자에서 초기값을 주는 방법

 

 

- 필드를 선언할 때 초기값을 주면 객체를 생성한 후 따로 값을 변경해야 하지만, 생성자에서 초기화하면 객체를 생성함과 동시에 값을 설정할 수 있다.

package ch6;

class Korean {
	String nation = "대한민국";
	String name;
	String ssn;

	public Korean(String name, String ssn) {
		this.name = name;
		this.ssn = ssn;
	}
}

public class koreanExample {

	public static void main(String[] args) {

		Korean k1 = new Korean("kkm", "1234");
		System.out.println("k1.name: " + k1.name);
		System.out.println("k1.ssn: " + k1.ssn);

	}

}

 

 

- 매개 변수 이름은 필드와 동일한 이름을 사용한느 것이 국룰이다.

 

 

- 생성자 오버로딩: 매개 변수를 달리하는 생성자를 여러 개 선언하는 것

1) 매개 변수의 타입, 개수, 순서가 다르게 선언되어야 한다.

2) 매개 변수의 타입과 개수, 순서가 동일한 경우에 변수 이름만 바꾸는 것은 오버로딩이라고 볼 수 없다.

package ch6;

class Car3 {
	String company = "kia";
	String model;
	String color;
	int maxSpeed;

	Car3() {

	}

	Car3(String model) {
		this.model = model;
	}

	Car3(String model, String color) {
		this.model = model;
		this.color = color;
	}

	Car3(String model, String color, int maxspeed) {
		this.model = model;
		this.color = color;
		this.maxSpeed = maxspeed;
	}
}

public class carExample3 {

	public static void main(String[] args) {

		Car3 car1 = new Car3();
		System.out.println("car1.company: " + car1.company);
		System.out.println("");

		Car3 car2 = new Car3("k3");
		System.out.println("car2.company: " + car2.company);
		System.out.println("car2.model: " + car2.model);
		System.out.println("");

		Car3 car3 = new Car3("k3", "white");
		System.out.println("car3.company: " + car3.company);
		System.out.println("car3.model: " + car3.model);
		System.out.println("car3.color: " + car3.color);
		System.out.println("");

		Car3 car4 = new Car3("k3", "white", 200);
		System.out.println("car4.company: " + car4.company);
		System.out.println("car4.model: " + car4.model);
		System.out.println("car4.color: " + car4.color);
		System.out.println("car4.maxspeed: " + car4.maxSpeed);

	}

}

 

 

- 생성자 오버로딩이 많아지면 필드 초기화 내용을 한 생성자에 집중적으로 작성한 후 나머지 생성자는 초기화 내용을 가지고 있는 생성자를 호출하는 방법을 사용하는 것이 좋다.

1) 생성자에서 다른 생성자를 호출할 땐 'this( )' 형식을 사용한다.

2) this( )는 생성자의 첫줄에서만 사용할 수 있다.

3) this( ) 다음에 추가적인 실행문들이 올 수 있다.

package ch6;

class Car4 {
	String company = "kia";
	String model;
	String color;
	int maxSpeed;

	Car4() {

	}

	Car4(String model) {
		this(model, "silver", 250);
	}

	Car4(String model, String color) {
		this(model, color, 250);
	}

	Car4(String model, String color, int maxspeed) {
		this.model = model;
		this.color = color;
		this.maxSpeed = maxspeed;
	}
}

public class carExample4 {

	public static void main(String[] args) {

		Car4 car1 = new Car4();
		System.out.println("car1.company: " + car1.company);
		System.out.println("");

		Car4 car2 = new Car4("k3");
		System.out.println("car2.company: " + car2.company);
		System.out.println("car2.model: " + car2.model);
		System.out.println(car2.color);
		System.out.println("");

		Car4 car3 = new Car4("k3", "white");
		System.out.println("car3.company: " + car3.company);
		System.out.println("car3.model: " + car3.model);
		System.out.println("car3.color: " + car3.color);
		System.out.println(car3.maxSpeed);
		System.out.println("");

		Car4 car4 = new Car4("k3", "white", 200);
		System.out.println("car4.company: " + car4.company);
		System.out.println("car4.model: " + car4.model);
		System.out.println("car4.color: " + car4.color);
		System.out.println("car4.maxspeed: " + car4.maxSpeed);

	}

}

 

 

- 메소드 선언부를 메소드 시그니쳐라고도 한다.

 

 

- 리턴값이 없는 메소드는 리턴 타입에 void 키워드가 와야 한다.

→ 리턴 값이 있을 땐 리턴 값의 타입이 온다.

 

 

- 메소드 선언 규칙

1) 숫자로 시작하며 안 된다.

2) '$', '_'를 제외한 특수문자를 사용할 수 없다.

3) 메소드명은 소문자로 작성하는 것이 국룰이다.

4) 서로 다른 단어가 혼합된 경우 각 단어의 첫 글자는 대문자로 작성한다.

 

 

- 메소드 호출 코드

package ch6;

class Calculator {

	void powerOn() {
		System.out.println("power on");
	}

	int plus(int x, int y) {
		int result = x + y;
		return result;
	}

	double divide(int x, int y) {
		double result = (double) x / y;
		return result;
	}

	void powerOff() {
		System.out.println("power off");
	}

}

public class calculatorExample {

	public static void main(String[] args) {

		Calculator myCalc = new Calculator();
		myCalc.powerOn();

		int result1 = myCalc.plus(5, 6);
		System.out.println("result1: " + result1);

		byte x = 10;
		byte y = 4;
		double result2 = myCalc.divide(x, y);
		System.out.println("result2: " + result2);

		myCalc.powerOff();

	}

}

 

 

- 매개 변수의 수를 모를 경우 매개 변수를 배열 타입으로 선언한다.

1) 배열의 항목 수는 호출할 때 결정된다.

2) 매개 변수를 "..."를 사용해서 선언할 경우, 메소드 호출 시 넘겨준 값의 수에 따라 자동으로 배열이 생성되고 매개값으로 사용된다.

package ch6;

class Computer {
	int sum1(int[] values) {
		int sum = 0;
		for (int i = 0; i < values.length; i++) {
			sum += values[i];
		}
		return sum;
	}

	int sum2(int... values) {
		int sum = 0;
		for (int i = 0; i < values.length; i++) {
			sum += values[i];
		}
		return sum;
	}
}

public class computerExample {

	public static void main(String[] args) {

		Computer myCom = new Computer();

		int[] values1 = { 1, 2, 3 };
		int result1 = myCom.sum1(values1);
		System.out.println("result1: " + result1);

		int result2 = myCom.sum1(new int[] { 1, 2, 3, 4, 5 });
		System.out.println("result2: " + result2);

		int result3 = myCom.sum2(1, 2, 3);
		System.out.println("result3: " + result3);

		int result4 = myCom.sum2(new int[] { 1, 2, 3, 4, 5 });
		System.out.println("result4: " + result4);

	}

}

 

 

- return문의 리턴값은 리턴 타입이거나 리턴 타입으로 변환될 수 있어야 한다.

 

 

- 리턴값이 없는 메소드(void): return문을 사용하면 메소드 실행을 강제 종료시킨다.

 

 

- 객체 외부에서 메소드 호출

참조변수.메소드( 매개값, ··· );		// 리턴값이 없거나, 있어도 받지 않을 경우
타입 변수 = 참조변수.메소드( 매개값, ··· );	// 리턴값이 있고, 리턴값을 받고 싶을 경우

 

 

- 메소드 오버로딩: 클래스 내에 같은 이름의 메소드를 여러 개 선언하는 것

1) 매개 변수의 타입, 개수, 순서 중 하나가 달라야 한다.

2) JVM은 일차적으로 매개 변수 타입을 보지만, 타입이 일치하지 않는 경우 자동 타입 변환이 가능한지 검사한다.

 

 

- 인스턴스 멤버: 객체(인스턴스)를 생성한 후 사용할 수 있는 필드와 메소드

 

 

- 인스턴스 필드는 객체마다 따로 존재하지만, 인스턴스 메소드는 객체마다 존재하지 않고 메소드 영역에 저장되고 공유된다.

package ch6;

class ThisInstance {
	String model;
	int speed;

	ThisInstance(String model) {
		this.model = model;
	}

	void setSpeed(int speed) {
		this.speed = speed;
	}

	void run() {
		for (int i = 10; i <= 50; i += 10) {
			this.setSpeed(i);
			System.out.println(this.model + " is running. (speed: " + this.speed + "km/h");
		}
	}
}

public class InstanceMemberExample {

	public static void main(String[] args) {

		ThisInstance myCar = new ThisInstance("porsche");
		ThisInstance yourCar = new ThisInstance("benz");

		myCar.run();
		yourCar.run();

	}

}

 

 

- 정적(클래스) 멤버: 클래스에 고정된 멤버로서 객체를 생성하지 않고 사용할 수 있는 필드와 메소드

 

 

- 정적 멤버 선언 방법: 필드와 메소드 선언 시 static 키워드를 붙이면 된다.

 

 

- 필드의 구분

1) 인스턴스 필드: 객체마다 따로 가지고 있어야 할 데이터

2) 정적 필드: 공용적인 데이터

 

 

- 인스턴스 필드를 이용해서 실행해야 한다면 인스턴스 메소드로, 인스턴스 필드를 이용하지 않는다면 정적 메소드로 선언한다.

 

 

- static 키워드가 붙은 메소드는 클래스에서만 사용 가능하지만, static 키워드가 없어도 클래스에서 객체를 생성하면 해당 인스턴스 멤버의 사용이 가능하다.

1) static 키워드가 붙은 메소드는 인스턴스 영역에서 사용할 수 없다.

2) static field는 인스턴스에서 사용 가능하지만, instance field는 클래스에서 사용할 수 없다.

package ch6;

class StaticMember {
	static double pi = 3.141592;

	static int plus(int x, int y) {
		return x + y;
	}

	static int minus(int x, int y) {
		return x - y;
	}
}

public class StaticMemberExample {

	public static void main(String[] args) {

		double result1 = 10 * 10 * StaticMember.pi;
		int result2 = StaticMember.plus(10, 5);
		int result3 = StaticMember.minus(10, 5);

		System.out.println("result1: " + result1);
		System.out.println("result2: " + result2);
		System.out.println("result3: " + result3);

	}

}

 

 

- 생성자는 객체 생성 시에만 실행되므로 정적 필드는 생성자에서 초기화를 진행할 수 없다.

→ 정적 필드 초기화는 정적 블록에서 진행한다.

1) 정적 블록은 클래스 내부에 여러 개가 선언되어도 상관없다.

2) 정적 블록은 선언된 순서대로 실행된다.

static {
	···
}

 

 

- 정적 메소드와 정적 블록에서 인스턴스 멤버를 사용하려면 객체를 먼저 생성한 후 참조 변수로 접근해야 한다.

→ this 키워드를 사용할 수 없다.

 

 

- 싱글톤(singleton): 전체 프로그램에서 단 하나만 생성된 객체

 

 

- 싱글톤 생성 순서

1) 클래스 외부에서 new 연산자로 생성자를 호출할 수 없도록 막는다.

→ 생성자 앞에 private 생성 제한자를 붙이면 된다.

2) 자신의 타입인 정적 필드를 하나 선언하고 자신의 객체를 생성해 초기화한다.

→ 클래스 내부에서는 new 연산자로 생성자 호출이 가능하다.

3) 정적 필드도 private 접근 제한자를 붙여 외부에서 필드값을 변경하지 못하도록 막는다.

4) 외부에서 호출할 수 있는 정적 메소드를 선언한 후 정적 필드에서 참조하고 있는 자신의 객체를 리턴한다.

→ 외부에서 객체를 얻는 유일한 방법: getInstance( ) 매소드 호출

public class 클래스 {
	// 정적 필드
    private static 클래스 singleton = new 클래스();
    
    // 생성자
    privvate 클래스() {}
    
    // 정적 메소드
    static 클래스 getInstance() {
    	return singleton;
    }
}
package ch6;

class Singleton {
	private static Singleton singleton = new Singleton();

	private Singleton() {
	}

	static Singleton getInstance() {
		return singleton;
	}
}

public class SingletonExample {

	public static void main(String[] args) {

		/*
		 * Singleton obj1 = new Singleton(); compile error
		 * Singleton obj2 = new Singleton(); compile error
		 */

		Singleton obj1 = Singleton.getInstance();
		Singleton obj2 = Singleton.getInstance();

		if (obj1 == obj2) {
			System.out.println("They are same singleton object");
		} else {
			System.out.println("They are different singleton object");
		}

	}

}

 

 

- final 필드: 초기값이 저장되면 이것이 최종적인 값이 되어 프로그램 실행 도중 값을 수정할 수 없게 되는 필드

1) 필드 선언 시에 줄 수 있다.

2) 생성자에서 줄 수 있다.

→ 초기화되지 않은 final 필드를 그대로 두면 컴파일 에러가 발생한다.

package ch6;

class Person {
	final String nation = "Korea";
	final String ssn;
	String name;

	public Person(String ssn, String name) {
		this.ssn = ssn;
		this.name = name;
	}
}

public class PersonExample {

	public static void main(String[] args) {

		Person p1 = new Person("123456-1234567", "KKM");

		System.out.println(p1.nation);
		System.out.println(p1.ssn);
		System.out.println(p1.name);

		p1.name = "LSM";
		System.out.println(p1.name);

	}

}

 

 

- 상수(constant): 불변의 값을 저장하는 필드

1) 객체마다 저장되지 않고 클래스에만 포함되며 한 번 초기값이 저장되면 변경할 수 없으므로 static이면서 final이다.

2) 객체를 생성하지 않으므로 정적 블록에서 초기화를 진행한다.

3) 상수 이름은 모두 대문자로 작성한다.

4) 서로 다른 단어가 혼합된 경우엔 언더바로 단어들을 연결한다.

package ch6;

class Earth {
	static final double EARTH_RADIUS = 6400;
	static final double EARTH_SURFACE_AREA;

	static {
		EARTH_SURFACE_AREA = 4 * Math.PI * EARTH_RADIUS * EARTH_RADIUS;
	}
}

public class EarthExample {

	public static void main(String[] args) {

		System.out.println("지구의 반지름: " + Earth.EARTH_RADIUS + "km");
		System.out.println("지구의 반지름: " + Earth.EARTH_SURFACE_AREA + "km^2");

	}

}

 

 

- 패기지의 특징

1) 클래스를 유일하게 만들어주는 식별자 역할을 한다.

2) 패키지가 상·하위로 구분되어 있다면 도트를 사용해 계층을 표현한다.

3) 클래스를 이동할 경우에는 패키지 전체를 이동시켜야 한다.

4) java로 시작하는 이름을 사용할 수 없다.

5) 이름은 모두 소문자로 작성한다.

6) 도메인과 기능 이름으로 구성된 패키지를 생성해 클래스를 관리하는 것이 좋다.

 

 

- 다른 패키지에 속하는 클래스를 사용하는 방법

1) 패키지와 클래스를 모두 기술한다.

→ 서로 다른 패키지에 동일한 클래스 이름이 존재하고, 두 패키지가 모두 import되어 있는 경우엔 반드시 패키지 이름 전체를 기술해야 한다.

2) import문을 사용한다.

→ 작성 위치는 패키지 선언과 클래스 선언 사이이며, 지정된 패키지의 하위 패키지는 import 대상이 아니므로 하위 패키지에 있는 클래스를 사용하려면 또다른 import문을 작성해야 한다.

package abc.def;

public class ABC {
	com.hankook.Tire tire = new com.hankook.Tire();
}

 

 

- 접근 제한자(access modifier)의 종류

1) public: 외부 클래스가 자유롭게 사용할 수 있는 공개 멤버를 만든다.

2) protected: 같은 패키지 또는 자식 클래스에서 사용할 수 있는 멤버를 만든다.

3) private: 외부에 노출되지 않는 멤버를 만든다.

4) default: 같은 패키지에 소속된 클래스에서만 사용할 수 있는 멤버를 만든다.

→ default 키워드를 사용하는 것이 아니라 아무것도 작성하지 않은 상태를 말한다.

 

 

- 클래스에 적용할 수 있는 접근 제한은 public과 default 두 가지이다.

 

 

- 자동으로 생성되는 기본 생성자의 접근 제한은 클래스의 접근 제환과 동일하다.

 

 

- 일반적으로 OOP에선 외부에서 객체의 데이터에 직접적으로 접근하는 것을 막는다.

→ 메소드를 통해 데이터를 변경하는 방법을 주로 사용한다.

 

 

- Setter 메소드: 매개값을 검증해서 유효한 값만 데이터로 저장

→ 메소드 이름이 'set'으로 시작한다.

 

 

- Getter 메소드: 메소드로 필드값을 가공한 후 외부로 전달

1) 메소드 이름이 'get'으로 시작한다.

2) 필드 타입이 boolean일 경우 'get'이 아닌 'is'로 시작한다.

 

 

- 클래스 선언 시 가능하면 필드를 private로 선언해서 외부로부터 보호하고, 필드에 대한 Setter와 Getter 메소드를 작성해서 필드값을 안전하게 변경/사용하는 것이 좋다.

package ch6;

class Getter_Setter {
	private int speed;
	private boolean stop;

	public int getSpeed() {
		return speed;
	}

	public void setSpeed(int speed) {
		if (speed < 0) {
			this.speed = 0;
			return;
		} else {
			this.speed = speed;
		}

	}

	public boolean isStop() {
		return stop;
	}

	public void setStop(boolean stop) {
		this.stop = stop;
		this.speed = 0;
	}
}

public class Getter_Setter_Example {

	public static void main(String[] args) {

		Getter_Setter myCar = new Getter_Setter();

		myCar.setSpeed(-50);

		System.out.println("현재 속도: " + myCar.getSpeed());

		myCar.setSpeed(90);

		if (!myCar.isStop()) {
			myCar.setStop(true);
		}

		System.out.println("현재 속도: " + myCar.getSpeed());

	}

}

 

728x90
반응형

'Java > 이것이 자바다' 카테고리의 다른 글

이자바 4장(조건문과 반복문) 확인문제  (0) 2022.11.23
어노테이션  (0) 2022.10.21
타입 변환 메소드  (2) 2022.10.07
참조 타입  (0) 2022.10.07
조건문과 반복문  (0) 2022.10.02

댓글