1. final 키워드

- 클래스, 메서드, 변수에 적용

- abstract와 동시에 사용 불가

- 상속 및 재정의 불가, 즉 서브클래스를 가질 수 없음

- final 변수 값 변경 불가

 

1) final class

- 클래스 선언 시 final을 사용하면 그 클래스는 상속 불가능

- 자식클래스를 가질 수 없고, 오직 외부에서 객체 생성을 통해서만 사용 가능

- final 클래스의 대표적인 예가 String 클래스

  → 사용자가 임의로 String 클래스를 상속받아 메서드를 재정의하는 것을 방지하기 위함

위와 같이 final 키워드를 가진 클래스는 상속이 불가하다는 것을 확인할 수 있다.

 

2) final method

- 메서드 선언 시 final을 사용하면 그 메서드는 상속 시 오버라이딩 불가

- 원본 메서드를 변형할 수 없도록 하기 위함

- 호출은 가능

위와 같이 상속은 가능하지만 final 키워드가 붙은 method2는 자식 측에서 재정의할 수 없는 것을 확인할 수 있다.

 

3) final 변수

- 한 번 값을 할당하면 그 값을 변경할 수 없음

- 선언 시 초기화하는 방법과 생성자를 통하여 초기화하는 방법이 있는데, 초기화하지 않고 남겨두면 컴파일 에러 발생

public class Person {
	// final 변수는 단 한 번 초기화될 수 있고 이후에는 변경이 불가능함
	// 선언 시에 아예 직접 초기화를 해주거나 생성자에서 초기화를 해줘야 함
	public final String nationality = "대한민국"; // 선언부 초기화
	public final String name; // 이렇게 선언부에서 초기화를 안하면 생성자 초기화
	public int age; // final이 안 붙은 멤버변수는 초기화 의무 X
	
	public Person(String name) {
		this.name = name;
	}
}
public class MainClass {

	public static void main(String[] args) {
		Person kim = new Person("김자바");
		//kim.nationality = "일본"; // final 변수 변경 불가
		//kim.name = "채자바";       // final 변수 변경 불가
		kim.age = 25;
		
		System.out.println("국적: " + kim.nationality); // public이므로 호출 가능
		System.out.println("이름: " + kim.name); // public이므로 호출 가능
		System.out.println("나이: " + kim.age); // public이므로 호출 가능
	}
}

위와 같이 final 키워드로 가장 처음 생성했던 값이 출력되는 것을 확인할 수 있다.

즉, final 키워드를 이용하여 변수를 생성하면 변수의 값은 변경할 수 없지만 호출하는 것에는 문제가 없다.

 

- final 배열 예시

public class Collector {
	// 참조형 변수를 가진 경우 변수 자체의 주는 final이지만,
	// 참조형 변수의 내부자료까지 바뀌지 않음을 보장하지 않음
	public final String[] stickers = {"피카츄", "꼬부기", "미뇽"};
}
import java.util.Arrays;

public class MainClass2 {
	public static void main(String[] args) {
		Collector c1 = new Collector();
		System.out.println(Arrays.toString(c1.stickers));
		
		c1.stickers[0] = "파이리";
		System.out.println(Arrays.toString(c1.stickers));
	}
}

final 키워드로 생성한 c1 객체는 해당 배열이 있는 주소를 변경하는 것이 불가능할 뿐이고,

해당 배열이 가리키고 있는 참조형 변수의 내부자료까지는 변경 방지를 보장해주지 않는다.

따라서 위와 같이 배열 내부 요소가 변경된 값이 오류 없이 출력되는 것을 확인할 수 있다.

 

4) 상수(static final)

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

- 상수는 객체마다 저장할 필요가 없는 공용성을 가져야 하고,

  여러가지 값으로 초기화될 수 없기 때문에 static과 final 제한자를 동시에 붙여 선언해야 함

- 상수 이름은 모두 대문자로 작성하는 것이 관례, 연결된 단어라면 "_"로 연결

// 상수를 선언할 때는 상수 집합을 만드는 목적으로 아래와 같이 클래스 선언
public class CountrySizes {
	// 상수는 보통 public으로 풀고 사용 (값 변경이 불가능하고, 공용성을 띔)
	public final static int KOREA_SIZE = 100431;
	public final static int STATES_SIZE = 9833519;
	public final static int THAILAND_SIZE = 513120;
}
public class MainClass {
	public static void main(String[] args) {
		// 상수만 모아둔 클래스 특성 상 클래스명이 곧 집합을 대표하는 이름이 됨
		System.out.println(CountrySizes.KOREA_SIZE);
		System.out.println(CountrySizes.STATES_SIZE);
		System.out.println(CountrySizes.THAILAND_SIZE);
	
		// 이를 잘 사용하는 예시는 자바의 Math 클래스가 있음
		System.out.println(Math.PI);
		System.out.println(Math.E);
	}
}

위와 같이 잘 출력되는 것을 확인할 수 있다.

'네트워크캠퍼스 > JAVA' 카테고리의 다른 글

인터페이스  (0) 2024.01.16
추상화  (0) 2024.01.15
싱글톤 패턴  (0) 2024.01.15
사용제한자  (0) 2024.01.12
매개변수의 다형성  (0) 2024.01.11

1. 싱글톤 패턴(Singleton Pattern)

- 어떤 클래스의 객체는 오직 하나임을 보장 → 전역의 개념으로 객체 사용

- 객체의 생성을 제한하기 위해 사용

 

public class Singleton {
	// 싱글턴 패턴 - 객체를 1개만 만들어 유지하기 위한 디자인 패턴
	// 1. 외부에서 생성자를 사용할 수 없도록 생성자에 private을 붙임
	private Singleton() {}
	
	// 2. 자신의 클래스 내부에서 스스로의 객체 1개 생성
	// 이때 멤버변수는 힙에 할당된 객체 없이 쓸 수 있도록 static
	private static Singleton instance;
	
	static {
		instance = new Singleton();
	}
	
	// 3. 외부에서 이 클래스의 객체를 필요로 하는 경우
	// 2번에서 static으로 생성된 객체의 주소를 return
	public static Singleton getInstance() {
		if(instance == null) {
			instance = new Singleton();
		}
		return instance;
	}
}

private 접근 제한자는 외부에서 접근할 수 없어 원래는 new 키워드를 이용한 객체 생성이 불가능하다.

이 때 위와 같이 static 키워드를 이용하여 singleton 타입의 객체를 생성할 수 있다.

public class MainClass {
	public static void main(String[] args) {
		// Singleton 객체는 생성자가 private이므로 인스턴스 생성 불가
		// new 키워드로 객체 생성 및 조회가 불가능
		// Singleton s1 = new Singleton();

		Singleton s1 = Singleton.getInstance();
		System.out.println("s1의 레퍼런스 : " + s1);
		
		Singleton s2 = Singleton.getInstance();
		System.out.println("s2의 레퍼런스 : " + s2);
	}
}

위와 같이 Singleton 타입을 이용하여 객체를 생성할 수 있고, s1과 s2 모두 같은 주소를 참조하고 있는 것을 확인할 수 있다.

위와 같이 단 하나의 객체만을 생성해야 할 필요가 있을 때 Singleton 패턴을 이용한다.

'네트워크캠퍼스 > JAVA' 카테고리의 다른 글

추상화  (0) 2024.01.15
final  (0) 2024.01.15
사용제한자  (0) 2024.01.12
매개변수의 다형성  (0) 2024.01.11
배열의 다형성  (0) 2024.01.10

1. 사용제한자(Usage Level modifier)

1) static

- 변수, 메서드에 적용되는 자바의 키워드

- 해당 클래스의 객체 없이도 참조 가능

- static 블록 안에는 static 변수만 사용해야 하고, static 메서드만 호출가능

- 지정된 변수와 메서드를 객체와 무관하게 만들어주기 때문에 this를 가질 수 없음

- non-static 메서드로 재정의될 수 엇음

- 대표적인 static 메서드는 main() 메서드

- static에 단순히 블록({})을 사용한 경우에는 정적 초기화자라고 부름

  → static 변수를 초기화하는 역할을 가지고 클래스가 로딩될 때 main() 메서드가 있더라도 앞서 딱 한 번 실행

 

2) 정적 변수 (static field)

- 모든 객체들이 공유하는 공유 변수

- 객체 생성 없이 클래스 이름만으로 참조 가능

- 객체를 만들어 참조할 수도 있지만 객체를 만들지 않고도 참조가 가능하기 때문에 이를 클래스 변수라고도 부름

public class Asean {
	// 출석 20, 발표 30, 기말 50
	public String name; 
	private int attendanceScore; // 출결점수
	private int finalTermScore;   // 기말점수

	public static int presentationScore; // 29
	
	public Asean(String name, int attendanceScore, int finalTermScore) {
		this.name = name;
		this.attendanceScore = attendanceScore;
		this.finalTermScore = finalTermScore;
	}
	
	// static 블록 내부 코드는 프로그램 시작 시 즉시 자동으로 한 번 호출
	static {
			presentationScore = 29;
	}
	
	public void showStudentScore() {
		System.out.println("학생명 : " + this.name);
		System.out.println("출결점수 : " + this.attendanceScore);
		System.out.println("발표점수 : " + presentationScore);
		System.out.println("기말점수 : " + this.finalTermScore);
		System.out.println("최종점수 : " + (this.attendanceScore + presentationScore + this.finalTermScore));
		System.out.println("===========================================");
	}
}
public class MainClass {
	public static void main(String[] args) {
		// 인스턴스생성 전부터 이미 조회 가능한 팀점수
		System.out.println(Asean.presentationScore);
	
		// 학생 4명 생성
		Asean a1 = new Asean("가", 20, 43);
		Asean a2 = new Asean("나", 15, 30);
		Asean a3 = new Asean("다", 10, 35);
		Asean a4 = new Asean("라", 18, 40);
	
		a1.showStudentScore();
		a2.showStudentScore();
		a3.showStudentScore();
		a4.showStudentScore();
	
		// 어떤 인스턴스를 활용해도 팀 점수 조회 가능
		System.out.println(a1.presentationScore);
		System.out.println(a2.presentationScore);
		System.out.println(a3.presentationScore);
		System.out.println(a4.presentationScore);
	}
}

위 그림과 같이 static 변수로 지정하면 main() 함수보다 앞서 실행되고, 해당 변수는 객체들이 전체적으로 공유하는 값으로 호출이 가능하다.

즉, 해당 변수는 힙 영역에 있는 것이 아니므로 this 키워드는 사용 불가하다.

위 코드에서 presentationScore가 public으로 선언되어 있으므로 어떤 인스턴스를 활용하더라도 호출이 가능하다.

 

2) 정적 메서드 (static method)

- 해당 클래스의 객체 생성 없이도 참조 가능

- static 메서드 안에서는 non-static 멤버를 객체 생성 없이 직접 참조 불가

- static 메서드 안에서는 static 변수를 선언할 수 없음

public class Asean {
	// 출석 20, 발표 30, 기말 50
	public String name; 
	private int attendanceScore; // 출결점수
	private int finalTermScore;   // 기말점수

	private static int presentationScore; // 29
	
	public Asean(String name, int attendanceScore, int finalTermScore) {
		this.name = name;
		this.attendanceScore = attendanceScore;
		this.finalTermScore = finalTermScore;
	}
	
	// static 블록 내부 코드는 프로그램 시작 시 즉시 자동으로 한 번 호출
	static {
			presentationScore = 29;
	}
	
	// 스태틱 메서드도 객체 없이 바로 호출 가능
	public static void showPresentationScore() {
		System.out.println(presentationScore);
	}
}

위 코드에서 presentationScore이 private으로 지정되어 있으므로 객체를 생성하지 않고서는 호출이 불가하다.

→ 스태틱 메서드를 활용하면 호출 가능

public class MainClass {

	public static void main(String[] args) {
		// 인스턴스생성 전부터 이미 조회 가능한 팀점수
		// System.out.println(Asean.presentationScore); // private이므로 호출 불가
		// 스태틱 메서드도 스태틱 변수처럼 호출 가능
		Asean.showPresentationScore(); 
		}
}

위와 같이 static 메서드를 활용하여 접근 가능하고 private 변수로 선언되어 있어 외부에서 변수의 값 자체를 변경할 수 없다는 장점이 있다.

'네트워크캠퍼스 > JAVA' 카테고리의 다른 글

final  (0) 2024.01.15
싱글톤 패턴  (0) 2024.01.15
매개변수의 다형성  (0) 2024.01.11
배열의 다형성  (0) 2024.01.10
클래스의 다형성  (0) 2024.01.09

1. 매개변수의 다형성

※ Promotion : 자동 형변환 vs Casting : 명시적 형변환

- promotion은 멤버 변수의 값을 대입할 때 발생하지만, 메서드 호출 시 사용하는 매개변수에도 발생 가능

- 보통 메서드 호출 시 선언부에서 지정한 데이터타입과 일치하는 매개값을 전달하여 호출하지만, 매개변수에 다형성을 적용하면 자식 객체 전달 가능

 

1) 강제 타입 변환(Type Casting)

→ 부모 타입을 자식 타입으로 변환하는 것

- 부모 타입으로 형 변환이 된 자식 객체만 강제 타입 변환 사용 가능

 

public class Parent {
	public void method1() {
		System.out.println("부모측 1번 메서드 호출");
	}
	
	public void method2() {
		System.out.println("부모측 2번 메서드 호출");
	}
}
public class Child extends Parent{
	@Override
	public void method2() {
		System.out.println("자식측에서 재정의한 2번 메서드");
	}
	
	public void method3() {
		System.out.println("자식만 가지고 있는 3번 메서드");
	}
}
public class MainClass1 {
	public static void main(String[] args) {
		// Promotion이 적용되면 자식클래스에만 정의된 요소 조회 불가능
		Parent p = new Child();
		p.method1(); // Parent에만 정의된 메서드
		p.method2(); // 오버라이딩된 메서드는 타입상관없이 인스턴스 내 자식쪽 호출
		//p.method3(); // p 타입 변수로는 자식쪽에만 정의된 요소 호출 불가
		System.out.println("-----------------");
	}
}

 

Parent p = new Child;

① 좌변에 부모클래스, 우변에 자식클래스     

② 동일한 메서드가 있어야 함 (@Override)

→ 우선순위는 자식쪽에 있음, 부모타입임에도 불구하고 자식쪽 메서드를 호출할 수 있다. → Promotion

 

public class MainClass1 {
	public static void main(String[] args) {
		// Promotion이 적용되면 자식클래스에만 정의된 요소 조회 불가능
		Parent p = new Child();
		p.method1(); // Parent에만 정의된 메서드
		p.method2(); // 오버라이딩된 메서드는 타입상관없이 인스턴스 내 자식쪽 호출
		//p.method3(); // p 타입 변수로는 자식쪽에만 정의된 요소 호출 불가
		System.out.println("------------------------");
		
		Child c = (Child)p; // 부모타입 변수를 자식타입으로 강제형변환(cast)
		c.method2(); // 위와 동일하게 자식측 method2 호출
		c.method3(); // 형 변환 후에는 자식쪽에만 있는 요소도 호출 가능
		
		Child cc = new Child(); // 애초에 자식타입으로 인스턴스 대입 시에도
		cc.method3(); // 자식쪽에만 있는 요소 호출 가능
	}
}

 

Child c = (Child) p;

 

 

★ 부모타입 변수로 자식타입 인스턴스 호출 시

① 부모클래스에만 선언된 메서드 : 부모클래스의 메서드로 호출

② 부모클래스, 자식클래스 모두에 선언된 메서드 (오버라이딩) : 자식클래스의 메서드로 호출

③ 자식클래스에만 선언된 메서드 : 부모 타입으로 호출 불가

 

public class Child2 extends Parent{
	@Override
	public void method2() {
		System.out.println("2번 자식측에서 재정의한 2번 메서드");
	}
	
	public void method3() {
		System.out.println("2번 자식만 가지고 있는 3번 메서드");
	}
}
public class MainClass2 {

	public static void main(String[] args) {
		// Parent 타입에는 Child, Child2 모두 대입 가능
		Parent p1 = new Child(); // 이 부분만 고쳐도 되는 효율성
		p1.method2(); 
		
		// Parent 타입으로는 Child1의 method2도, Child2의 method2도 호출 가능
		// method2는 Parent에도 정의되어 있지만, Child와 Child2에서 오버라이딩된 메서드이기 때문에 호출 가능
	}
}

 

public class MainClass2 {

	public static void main(String[] args) {
		// Parent 타입에는 Child, Child2 모두 대입 가능
		Parent p1 = new Child2(); // 이 부분만 고쳐도 되는 효율성
		p1.method2(); 
		
		// Parent 타입으로는 Child1의 method2도, Child2의 method2도 호출 가능
		// method2는 Parent에도 정의되어 있지만, Child와 Child2에서 오버라이딩된 메서드이기 때문에 호출 가능
	}
}

 

 

 

2. instanceof 키워드

→ 객체가 지정한 클래스의 인스턴스인지 아닌지 검사할 때 사용

Person p = new student();
p instanceof Student // true 반환

왼쪽 항의 객체가 오른쪽 항 클래스의 인스턴스, 즉 오른쪽 항의 객체가 생성되었다면 true 리턴

 

public class Human {
	
	public String name;
	public int age;
	
	public Human(String name, int age) {
		this.name = name;
		this.age = age;
	}
	
	public Human() {
		this("noname", 0);
		// this.name = "noname";
		// this.age = 0;
	}
	
	public void showInfo() {
		System.out.println("이름: " + this.name);
		System.out.println("나이: " + this.age);
		System.out.println("---------------");
	}
}
public class Student extends Human {
	public Student(String name, int age) {
		super(name, age);
	}
}
public class Cat {
	private String name;
	private int age;
	
	public Cat(String name, int age) {
		this.name = name;
		this.age = age;
	}
	
	public void meow() {
		System.out.println("야옹 야옹");
	}
}
public class MainClass {

	public static void main(String[] args) {
		// Human, Student, Cat 인스턴스 생성
		Human h1 = new Human("김자바", 20);
		h1.showInfo();
		
		Student s1 = new Student("태국인", 19);
		s1.showInfo();
		
		Cat c1 = new Cat("룰루", 6);
		c1.meow();
		
		System.out.println(h1 instanceof Human); // h1이 Human 기반인가
		System.out.println(s1 instanceof Human); // s1이 Human 기반인가
		System.out.println(h1 instanceof Student); // h1이 Student 기반인가
		System.out.println(s1 instanceof Student); // s1이 Student 기반인가
		// 관련이 아예 없는 객체간 비교는 에러 발생
		//System.out.println(h1 instanceof Cat);
		//System.out.println(s1 instanceof Cat);
	}
}

'네트워크캠퍼스 > JAVA' 카테고리의 다른 글

싱글톤 패턴  (0) 2024.01.15
사용제한자  (0) 2024.01.12
배열의 다형성  (0) 2024.01.10
클래스의 다형성  (0) 2024.01.09
정보은닉, 캡슐화  (0) 2024.01.08

1. 이종모음(Heterogeneous Collection)

→ 배열에 다형성을 적용시키는 원리

- 배열은 원래 동종모음 구조

// 기본형 자료형으로 만든 배열 예시
// 다른 자료형을 넣을 수 없음
int [] iArr = {1, 2, 3, 4, 5, "인프라"}; // 오류 발생
// A 자료형의 힙 주소를 배열로 저장 가능한 배열 생성
A[] aArr = new A[2];

A a1 = new A();
A a2 = new A();

aArr[0] = a1;
aArr[1] = a2;

위와 같이 동종모음인 경우에만 배열 생성이 가능하다.

 

☆ 다형성을 이용하면 이종모음 구조의 객체 배열 생성이 가능하다.

// 모든 클래스의 부모클래스인 Object 배열을 선언하면
// 다형성 원리(부모 객체를 요구하는 자리에 자식타입을 대입 가능함)에 의해
// 모든 자료를 다 대입할 수 있음.
Object[] oArr = new Object[3];

B b1 = new B();
// aArr[1] = b1; // A타입을 요구하는 배열에 B타입 대입 불가
oArr[0] = b1;
oArr[1] = a2;
oArr[2] = 100; // Object 배열에는 기본형 자료도 대입 가능

위와 같이 다형성 원리에 의해 이종모음 구조의 객체 배열 생성이 가능한 것을 확인할 수 있다.

 

 

 

 

'네트워크캠퍼스 > JAVA' 카테고리의 다른 글

사용제한자  (0) 2024.01.12
매개변수의 다형성  (0) 2024.01.11
클래스의 다형성  (0) 2024.01.09
정보은닉, 캡슐화  (0) 2024.01.08
접근제한자  (0) 2024.01.08

1. 클래스의 다형성

1) 클래스의 다형성

→ 부모타입의 인스턴스로 자식 타입을 대입받거나 호출하는 것이 가능한 것

※ 자료형이 다른 변수는 대입이 불가능

부모 자료형과 자식 자료형도 같은 자료형이 아니기 때문에 원칙적으로는 대입이 불가능하다.

그러나 상속을 통한 인스턴스 생성 시 부모 영역과 자식 영역이 구분되어 저장되기 때문에 일부 영역은 통제 가능한 상태가 된다.

ex) 부모클래스 사람(이름,나이,자기소개()) & 자식클래스 학생(전공, 공부하기())

      부모타입인 사람 타입의 변수에 학생 인스턴스를 대입하는 것이 가능

☆ 부모쪽에 존재하는 함수를 오버라이딩한 경우만 자식의 함수로 호출이 가능하므로 인터페이스를 이용해 다형성을 구현한다.

 

2) 다형성(Polymorphism)

- 하나의 객체가 여러가지 유형으로 사용되는 것

- 상속을 전제조건으로 함

- 자식클래스가 부모클래스의 타입을 가질 수 있도록 허용 → 부모 타입에 모든 자식객체가 대입될 수 있음

 

① 다형성이 적용되지 않은 코드 예시

package noinheri;

public class Rabbit {
	// 토끼 몬스터 hp, atk, def
	private int hp;
	private int atk;
	private int def;

	// 생성자에서 void 파라미터로 각각 3, 0, 0으로 초기화
	public Rabbit() {
		this.hp = 3;
		this.atk = 0;
		this.def = 0;
	}
	
	// setter / getter 자동생성
	// alt + shift + s 후 Generate getters and setters 선택

	public int getHp() {
		return hp;
	}

	public void setHp(int hp) {
		this.hp = hp;
	}

	public int getAtk() {
		return atk;
	}

	public void setAtk(int atk) {
		this.atk = atk;
	}

	public int getDef() {
		return def;
	}

	public void setDef(int def) {
		this.def = def;
	}
}
package noinheri;

public class Warrior {
	// 정보은닉 적용
	private String id;
	private int hp;
	private int atk;
	private int def;
	private int exp;
	
	// 생성자 id 입력받고 나머지 필드 초기화
	public Warrior(String id) {
		this.id = id;
		this.hp = 20;
		this.atk = 3;
		this.def = 1;
		this.exp = 0;
	}
	
	// 캐릭터 상태 조회
	public void showStatus() {
		System.out.println("아이디 : " + this.id);
		System.out.println("체력 : " + this.hp);
		System.out.println("공격력 : " + this.atk);
		System.out.println("방어력 : " + this.def);
		System.out.println("경험치 : " + this.exp);
		System.out.println("------------------");
	}
	
	// 단독 사냥을 하도록 메서드 생성
	public void huntRabbit(Rabbit rabbit) {
		if(rabbit.getHp() <= 0) {
			System.out.println("이미 죽은 토끼입니다.");
			return; // 죽은 토끼에 대해서는 추가 로직이 필요없음
		}
		// 1. 내가 공격한 토끼의 체력 -3
		rabbit.setHp(rabbit.getHp() - this.atk);
		// 2. 방금 공격으로 죽었다면 경험치 5 증가
		if(rabbit.getHp() <= 0) {
			System.out.println("토끼를 죽였습니다.");
			this.exp += 5;
		} else {
			System.out.println("토끼를 공격했습니다.");
		}
	}
}
package noinheri;

public class MainClass1 {

	public static void main(String[] args) {
		// 전사 하나 생성
		Warrior w1 = new Warrior("전사1");
		// 생성 직후 정보 조회
		w1.showStatus();
		// 토끼 생성
		Rabbit r1 = new Rabbit();
		// 토끼와 교전
		w1.huntRabbit(r1);
		// 죽은 토끼 한 번 더 공격
		w1.huntRabbit(r1);
		// 사냥 후 정보 조회
		w1.showStatus();
	}
}

위 상황에서 huntRabbit() 함수는 Rabbit 자료형만을 받아올 수 있게 되어 있으므로 Rat 자료형 요구 시 오류가 발생한다.

 

package noinheri;

public class Rat {
	// 쥐 몬스터 hp, atk, def
	private int hp;
	private int atk;
	private int def;

	// 생성자에서 void 파라미터로 각각 3, 0, 0으로 초기화
	public Rat() {
		this.hp = 5;
		this.atk = 1;
		this.def = 0;
	}
	
	// setter / getter 자동생성
	// alt + shift + s 후 Generate getters and setters 선택

	public int getHp() {
		return hp;
	}

	public void setHp(int hp) {
		this.hp = hp;
	}

	public int getAtk() {
		return atk;
	}

	public void setAtk(int atk) {
		this.atk = atk;
	}

	public int getDef() {
		return def;
	}

	public void setDef(int def) {
		this.def = def;
	}
}
package noinheri;

public class Warrior {
	// 정보은닉 적용
	private String id;
	private int hp;
	private int atk;
	private int def;
	private int exp;
	
	// 생성자 id 입력받고 나머지 필드 초기화
	public Warrior(String id) {
		this.id = id;
		this.hp = 20;
		this.atk = 3;
		this.def = 1;
		this.exp = 0;
	}
	
	// 캐릭터 상태 조회
	public void showStatus() {
		System.out.println("아이디 : " + this.id);
		System.out.println("체력 : " + this.hp);
		System.out.println("공격력 : " + this.atk);
		System.out.println("방어력 : " + this.def);
		System.out.println("경험치 : " + this.exp);
		System.out.println("------------------");
	}
	
	// 단독 사냥을 하도록 메서드 생성
	public void huntRabbit(Rabbit rabbit) {
		if(rabbit.getHp() <= 0) {
			System.out.println("이미 죽은 토끼입니다.");
			return; // 죽은 토끼에 대해서는 추가 로직이 필요없음
		}
		// 1. 내가 공격한 토끼의 체력 -3
		rabbit.setHp(rabbit.getHp() - this.atk);
		// 2. 방금 공격으로 죽었다면 경험치 5 증가
		if(rabbit.getHp() <= 0) {
			System.out.println("토끼를 죽였습니다.");
			this.exp += 5;
		} else {
			System.out.println("토끼를 공격했습니다.");
		}
	}
	
	// Rat은 공격을 받고 죽지 않으면 1회 반격
	// 경험치는 80
	public void huntRat(Rat rat) {
		if(rat.getHp() <= 0) {
			System.out.println("이미 죽은 쥐입니다.");
			return;
		}
		rat.setHp(rat.getHp() - this.atk);
		if(rat.getHp() <= 0) {
			System.out.println("쥐를 죽였습니다.");
			this.exp += 80;
		}
		else {
			System.out.println("쥐를 공격했습니다.");
			System.out.println("쥐가 반격합니다.");
			this.hp = this.hp - rat.getAtk();
		}
	}
}
package noinheri;

public class MainClass2 {

	public static void main(String[] args) {
		// 전사 생성
		Warrior w1 = new Warrior("전사2");
		// 상태 조회
		w1.showStatus();
		// 쥐 생성
		Rat rat1 = new Rat();
		// 쥐와 교전 (3번)
		for(int i = 0; i < 3; i++) {
			w1.huntRat(rat1);
		}
		// 상태조회
		w1.showStatus();
	}
}

 

 

② 다형성이 적용된 코드 예시

package inheri;

public class Monster {
	// 모든 몬스터 클래스의 부모타입으로 설계된 클래스
	private String name; // 다형성 특성상 몬스터 종류를 식별하기 위한 변수
	private int hp;
	private int atk;
	private int def;
	private int exp;
	
	// 부모쪽 생성자로 초기화할 때 어떤 몬스터가 생성될지 사전에 알 수 없으므로
	// 고정값이 아닌 자식쪽에서 입력받은 자료를 대입하도록 생성자 설계
	public Monster(String name, int hp, int atk, int def, int exp) {
		this.name = name;
		this.hp = hp;
		this.atk = atk;
		this.def = def;
		this.exp = exp;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getHp() {
		return hp;
	}

	public void setHp(int hp) {
		this.hp = hp;
	}

	public int getAtk() {
		return atk;
	}

	public void setAtk(int atk) {
		this.atk = atk;
	}

	public int getDef() {
		return def;
	}

	public void setDef(int def) {
		this.def = def;
	}

	public int getExp() {
		return exp;
	}

	public void setExp(int exp) {
		this.exp = exp;
	}
}
package inheri;

public class Warrior {
	// 정보은닉 적용
	private String id;
	private int hp;
	private int atk;
	private int def;
	private int exp;
	
	// 생성자 id 입력받고 나머지 필드 초기화
	public Warrior(String id) {
		this.id = id;
		this.hp = 20;
		this.atk = 3;
		this.def = 1;
		this.exp = 0;
	}
	
	// 캐릭터 상태 조회
	public void showStatus() {
		System.out.println("아이디 : " + this.id);
		System.out.println("체력 : " + this.hp);
		System.out.println("공격력 : " + this.atk);
		System.out.println("방어력 : " + this.def);
		System.out.println("경험치 : " + this.exp);
		System.out.println("------------------");
	}
	
	// 파라미터로 모든 몬스터의 부모타입인 Monster 인스턴스를 요구하면
	// 다형성 원리에 의해 상속받은 모든 몬스터를 다 대입할 수 있음
	public void hunt(Monster monster) {
		// 죽은 몬스터는 교전 불가 및 메서드 즉시 종료
		if(monster.getHp() <= 0) {
			System.out.println(monster.getName() + "은 이미 죽어서 교전할 수 없습니다.");
			return;
		}
		// 다음 공격으로 몬스터가 죽는 경우
		if(monster.getHp() - (this.atk - monster.getDef()) <= 0){
			// 경험치부여
			monster.setHp(0);
			System.out.println(monster.getName() + "(이)가 죽었습니다.");
			this.exp += monster.getExp();
		} else {
			// 다음 공격으로 몬스터가 죽지 않아서 반격을 받는 경우
			// 몬스터 체력을 (내 공격력 - 몬스터 방어력)만큼 차감
			monster.setHp(this.atk - monster.getDef());
			// 내 체력을 (몬스터 공격력 - 내 방어력)만큼 차감
			this.hp -= monster.getAtk() - this.def;
			// **가 반격했습니다.
			System.out.println(monster.getName() + "(이)가 반격했습니다.");
		}
	}
}
// 해당 클래스의 인스턴스는 Monster 타입 변수에도 저장할 수 있음.
public class Rabbit extends Monster {
	public Rabbit() {
		// 부모인 Monster에 이름, 체력, 공격력, 방어력, 경험치 전달
		super("토끼", 3, 0, 0, 5);
	}
}
package inheri;

public class Rat extends Monster {
	public Rat() {
		// 부모인 Monster에 이름, 체력, 공격력, 방어력, 경험치 전달
		super("쥐", 5, 0, 0, 5);
	}
}
package inheri;

public class MainClass1 {

	public static void main(String[] args) {
		// 전사 생성
		Warrior w1 = new Warrior("전사1");
		// 상태 조회
		w1.showStatus();
		// 토끼 생성
		Rabbit r1 = new Rabbit();
		// 전사와 교전 2회
		w1.hunt(r1);
		w1.hunt(r1);
		
		// 쥐 생성
		Rat r2 = new Rat();
		w1.hunt(r2);
		w1.hunt(r2);
		w1.hunt(r2);
		w1.showStatus();
	}
}

위와 같이 상속을 통해 Monster 범주에 들어가는 객체를 모두 호출 가능하다.

원래라면 토끼나 쥐 객체를 생성한 후 Warrior 파일에서 hunt() 함수를 생성해줘야 했는데,

다형성을 이용하여 Monster 타입을 처리하는 hunt로 재설계함으로써 코드의 확장성을 높일 수 있다.

'네트워크캠퍼스 > JAVA' 카테고리의 다른 글

매개변수의 다형성  (0) 2024.01.11
배열의 다형성  (0) 2024.01.10
정보은닉, 캡슐화  (0) 2024.01.08
접근제한자  (0) 2024.01.08
상속  (0) 2024.01.05

1. 은닉, 캡슐화 (Encapsulation)

1) 정보은닉

- 사용자에게 상세한 내부 구현을 숨기고, 필요한 부분만 보이게 하는 것

- 은닉을 사용하기 위해서는 클래스 멤버변수의 접근제한자를 private으로 설정

- 은닉된 멤버변수에 접근하기 위해서는 공개된(public) 메서드를 통해 접근

  ① 변수의 값 변경 : setter 메서드

  ② 변수의 값 얻어오기 : getter 메서드

- 공개 메서드를 이용하여 데이터 변경 시 메서드 내에 데이터 유효성을 검증할 수 있는 루틴을 넣을 수 있음

- 접근 권한 체크 로직을 통해 인가되지 않은 사용자에게 중요한 데이터나 로직을 숨길 수도 있고, 제어할 수도 있음

- 외부에 공개하고 싶지 않은 메서드도 private으로 선언 가능

 

2) 예시

① badcase

package encapsulation.bad;

public class MyBirthday {
	int year;
	int month;
	int day;
	
	void showDateInfo() {
		System.out.println("내 생일은");
		System.out.println(year + "년");
		System.out.println(month + "월");
		System.out.println(day + "일");
		System.out.println("이니까 선물을 준비하세요!");
	}
}
package encapsulation.bad;

public class MainClass {

	public static void main(String[] args) {
		// 같은 패키지 내부 클래스파일을 가져다 쓸 때는 import 필요 없음
		MyBirthday b = new MyBirthday();
		
		b.year = 2024;
		b.month = 13;  // 13월을 넣어도 그냥 동작
		b.day = 32;    // 32일을 넣어도 그냥 동작
		
		b.showDateInfo();
	}
}

위와 같이 누구나 정보에 접근할 수 있기 때문에 아무런 값을 넣어도 그냥 동작한다.

 

② goodcase

※ 리팩토링이란?

→ 기능을 유지하면서 코드의 구조를 유지보수하기 좋게 개선하는 것

package encapsulation.good;

public class MyBirthday {
	// 은닉(캡슐화) 시 변수는 무조건 private으로 처리
	private int year;
	private int month;
	private int day;
	
	// alt+shift+s 혹은 마우스 우클릭 후 source
	// generate constructor using fields 선택
	public MyBirthday(int year, int month, int day) {
		this.year = year;
		setMonth(month);
		setDay(day);
	}
	
	// 은닉된 변수에 접근하기 위해서는
	// 클래스 설계 시 미리 설정해둔 setter/getter 메서드를 이용해 접근

	// setter 메서드 선언
	// 1. setter 메서드는 은닉변수에 값을 저장(세팅)하기 위해 선언
	// 2. 메서드의 접근제한자는 public으로 설정하고, 이름은 일반적으로 set+변수명으로 지정
	
	// setMonth를 설계하여 오로지 1~12 중 하나만 받아서 저장하도록 지정
	public void setMonth(int month) {
		if(month < 1) {
			this.month = 1;
		} else if (month > 12) {
			this.month = 12;
		} else {
			this.month = month;
		}
	}
		
	public void setDay(int day) {
		if(day < 1 || day > 31) {
			this.day = 1; // 범위를 벗어나는 값이 들어올 경우 1로 고정
		} else {
			this.day = day; // 범위 내의 값이면 그대로 지정
		}
	}
	
	void showDateInfo() {
		System.out.println("내 생일은");
		System.out.println(year + "년");
		System.out.println(month + "월");
		System.out.println(day + "일");
		System.out.println("이니까 선물을 준비하세요!");
	}
}
package encapsulation.good;

public class MainClass {

	public static void main(String[] args) {
		// 12월 32일같은 없는 날짜를 걸러주는지 체크
		MyBirthday b = new MyBirthday(2024, -37, 50);
		// b.day = 50; // private이므로 외부인 main에서 직접 주입 불가능
		b.showDateInfo();
	}
}

위와 같이 잘못된 값을 넣어도 setter로 정의해둔 값에 따라 결과가 잘 출력되는 것을 확인할 수 있다.

 

생성자 생성 후 메인함수에서 값을 변경할 수 없도록 하고 싶다면 setter 함수를 private 접근 지정자로 설정해두면 된다.

일반적으로 setter 함수는 private으로 두는 것이 바람직하다.

'네트워크캠퍼스 > JAVA' 카테고리의 다른 글

배열의 다형성  (0) 2024.01.10
클래스의 다형성  (0) 2024.01.09
접근제한자  (0) 2024.01.08
상속  (0) 2024.01.05
기본 타입 vs 참조 타입  (0) 2024.01.05

+ Recent posts