# JAVA Ch07. 객체지향 프로그래밍2_1

uuuu.jini·2022년 2월 21일
0

JAVA -자바의 정석

목록 보기
7/18
post-thumbnail

목차

  1. 상속
  2. 오버라이딩
  3. pacakge와 import
  4. 제어자

1. 상속 [ inheritance ]

1.1 ] 상속의 정의와 장점

상속이란?

기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것이다. 보다 적은 양의 코드로 새로운 클래스를 작성할 수 있고, 코드를 공통적으로 관리하여 추가 및 변경이 매우 용이하다. 코드의 재사용성을 높이고 코드의 중복을 제거하여 프로그램의 생산성과 유지보수에 크게 기여한다.

새로 작성하고자 하는 클래스의 이름 뒤에 상속받고자 하는 클래스의 이름을 키워드 extends 와 함께 써주면 된다.

	class Child extends Parent {
    // ...
    }

상속해주는 클래스를 조상 클래스, 상속받는 클래스를 자손 클래스라고 한다.

조상 클래스자손 클래스
부모 클래스자식 클래스
상위 클래스하위 클래스
기반 클래스파생된 클래스

클래스는 멤버들의 집합이므로 상속을 받았다는 것은 자식 클래스 안에 부모클래스가 포함되는 집합 관계로 표현할 수 있다. 자손 클래스는 부모 클래스의 멤버를 모두 상속받는다. ( 부모 클래스의 멤버 변수는 자손 클래스에서 자동으로 추가되것 같은 효과를 얻는다. )

자손 클래스에 새로운 코드가 추가되어도 조상클래스에는 아무런 영향을 주지 않는다. 즉, 조상 클래스가 변경되면 자손 클래스는 자동적으로 영향을 받게 되지만, 자손 클래스가 변경되는 것은 조상 클래스에 아무런 영향을 주지 못한다.

자손 클래스는 조상 클래스의 멤버를 상속받으므로 항상 조상클래스보다 같거나 많은 멤버를 갖는다. ( 상속이 거듭될수록 상속 받는 클래스의 멤버 개수는 점점 늘어나게 된다. ) -- 확장한다는 의미로 해석할 수 있다.

생성자와 초기화 블럭은 상속되지 않는다. 멤버만 상속된다. , 자손 클래스의 멤버 개수는 조상 클래스보다 항상 같거나 많다.

클래스 관계에서 형제 관계와 같은 것은 없다. 만일 두개의 클래스에 공통적으로 추가되어야 하는 멤버가 있다면, 이 두 클래스에 각각 따로 추가해주는 것보다는 이들의 공통조상 클래스에 추가하는 것이 좋다. ( 부모 클래스 하나만 변경하면 되므로 작업이 간단해진다. 같은 내용의 코드를 한곳에서 관리함으로써 코드의 중복이 줄어들며 유지보수가 유용해진다. )

조상 클래스만 변경해도 모든 자손클래스와 자손의 자손클래스에까지 영향을 미치기 때문에, 상속관계를 맺어주게 되면 자손 클래스들의 공통적인 부분은 조상클래스에서 관리하고 자손 클래스는 자신에 정의된 멤버들만 관리하면 되므로 각 클래스의 코드가 적어져서 관리가 쉬워진다.

클래스간의 적절한 상속관계를 맺어주는것이 객체 지향 프로그래밍에서 가장 중요한 부분이다.

자손 클래스의 인스턴스를 생성하면 조상 클래스의 멤버와 자손 클래스의 멤버가 합쳐진 하나의 인스턴스로 생성된다.

1.2 ] 클래스간의 관계 - 포함관계

상속이외에도 클래스를 재사용하는 또 다른 방법이 있다. 클래스간에 포함(Composite) 관계 를 맺어주는 것이다. 한 클래스의 멤버변수로 다른 클래스 타입의 참조 변수를 선언하는 것을 뜻한다.

	class Circle{
    	Point c = new Point();
        int r;
	}

한 클래스를 작성하는 데 다른 클래스를 멤버 변수로 선언하여 포함시키는 것은 좋은 생각이다. - 재사용하면 보다 간결 & 손쉽게 클래스 작성 가능하다. ( 단위 클래스를 여러번 재사용할 수 있다.)

1.3 ] 클래스간의 관계 설정하기

상속관계를 맺을지 포함관계를 맺을 지 결정하는 것은 혼돈스러울 수 있다. 위의 포함관계를 맺은 클래스를 상속관계로 포현하면 밑과 같다.

	calss Circle extends Point{
		int r;
	}

결과적으로 별 차이가 없어보인다. 이럴때는 is-a와 has-a 를 넣어 문장을 만들어 보면 명확해진다.

원(Circle)은 점(Point)이다. - Circle is a Point.
원(Circle)은 점(Point)을 가지고 있다. - Cirst has a Point.

위와 같이 ~은 ~이다(is-a) 라는 문장이 성립한다면, 서로 상속관게를 맺어주고 ~은 ~을 가지고 있다.(has-a) 라는 문장이 성립한다면 포함관계를 맺어주어야 한다.

상속관계포함관계
is-ahas-a
package ch07_01;

public class DrawShape {

	public static void main(String[] args) {
		Point[] p = {
				new Point(100,100),
				new Point(140,50),
				new Point(200,100)
		};
		
		Triangle t = new Triangle(p);
		Circle c = new Circle(new Point(150,150),50);
		
		t.draw();
		c.draw();
	}

}

class Shape{
	String color = "black";
	void draw() {
		System.out.printf("[color=%s]%n",color);
	}
}

class Point extends Shape{
	int x;
	int y;
	
	Point(int x, int y ){
		this.x = x;
		this.y = y;
	}
	Point(){
		this(0,0);
	}
	String getXY() {
		return "(" + x + ","+y + ")";
	}
}

class Circle extends Shape{
	Point center;
	int r;
	
	Circle(Point center,int r){
		this.center = center;
		this.r = r;
	}
	
	Circle(){
		this(new Point(0,0),100);
	}
	void draw() {
		System.out.printf("[center = %s, r = %d, color = %s]%n",center.getXY(),r,color);
	}
	
	
}


class Triangle extends Shape{
	Point p[] = new Point[3];
	
	Triangle(Point p[]) {
		this.p = p;
	}
	
	void draw() {
		System.out.printf("p1 = %s, p2 = %s, p3 = %s, color = %s%n", p[0].getXY(),p[1].getXY(),p[2].getXY(),color);
	}
}

조상 클래스에 정의된 메서드와 같은 메서드는 자손 클래스에 정의하는 것이 가능하며 이를 오버 라이딩 이라고 한다.

ToString()
: 인스턴스의 정보를 문자열로 반환할 목적으로 정의된 것이다. 참조변수의 출력이나 덧셈연산자를 이용한 참조변수와 문자열의 결합에는 toString()이 자동적으로 호출되어 참조변수를 문자열로 대치한 후 처리한다. 모든 클래스의 조상인 Object클래스에 정의된 것으로, 어떤 종류의 객체에서도 호출이 가능하다

1.4 ] 단일 상속 ( single inheritance )

자바에서는 오직 단일 상속만을 허용한다. 둘 이상의 클래스로부터 상속을 받을 수 없다. 다중 상속을 허용하면 여러 클래스로부터 상속 받을수 있기 때문에 여러가지 기능을 가진 클래스를 쉽게 작성할 수 있다는 장점이 있지만, 서로 다른 클래스로부터 상속받은 멤버간의 이름이 같은 경우 구별할 수 있는 방법이 없다는 단점을 가지고 있다.

이러한 단점때문에 다중 상속의 장점을 포기하고 단일 상속만을 허용한다. 하나의 조상 클래스만을 가질 수 있기 떄문에, 클래스간의 관계가 더욱 명확해지고 코드를 더욱 신뢰할 수 있게 만들어 준다는 점에서 유리하다.

1.5 ] Object클래스 - 모든 클래스의 조상

Object클래스는 모든 클래스 상속계층도의 최상위에 있는 조상클래이다. 모든 클래스는 자동적으로 Object 클래스로부터 상속받게 한다. 컴파일러가 자동으로 extends Object를 달아주는 것이다. 다른 클래스로부터 상속을 받는다고 해도 조상 클래스를 찾아 올라가다 보면 결국 마지막 최상위 조상은 Object 클래스일 것이다.

자바의 모든 클래스는 Object 클래스의 멤버들을 상속 받는다. toString() 이나 equals() 와 같은 모든 인스턴스가 가져야 할 기본적인 11개의 메서드가 정의되어 있다.


2. 오버라이딩 [ overriding ]

2.1 ] 오버라이딩이란 ?

조상 클래스로부터 상속받은 메서드의 내용을 변경하는 것을 오버라이딩이라고 한다. 상속받은 메서드를 자손 클래스 자신에 맞게 변경해야 하는 경우에 조상의 메서드를 오버라이딩 한다.

2.2 ] 오버라이딩의 조건

오버라이딩은 조상의 메서드를 새로 작성하는 것이므로 밑의 조건을 따라야 한다.

  • 이름이 같아야 한다.
  • 매개변수가 같아야 한다.
  • 반환타입이 같아야 한다.

한마디로 선언부가 일치해야 한다 는 것이다. 다만 접근 제어자와 예외는 제한된 조건에서만 다르게 변경할 수 있다.

  1. 접근 제어자는 조상 클래스의 메서드보다 좁은 범위로 변경 할 수 없다.
    만약 조상클래스의 메서드가 protected라면 이를 오버라이딩한 메서드는 protected나 public이어야 한다. 대부분 같은 범위를 사용한다.( public -> protected -> (defualt) -> private )
  2. 조상 클래스의 메서드보다 많은 예외를 선언할 수 없다.
  3. 인스턴스 메서드를 static메서드로 또는 그 반대로 변경할 수 없다.

2.3 ] 오버로딩 vs. 오버라이딩

오버로딩오버라이딩
기존에 없는 새로운 메서드를 정의하는 것상속받은 메서드의 내용을 변경하는 것

2.4 ] super

super는 자손 클래스에서 조상 클래스로부터 상속받은 멤버를 참조하는데 사용되는 참조 변수이다. 멤버변수와 지역변수의 이름이 같을 떄 this를 붙여 구분하듯이 상속받은 멤버와 자신의 멤버의 이름이 같을 때는 super를 붙여서 구별 할 수 있다.

조상 클래스로부터 상속받은 멤버도 자손 클래스 자신의 멤버이므로 this를 사용할 수 있다. 조상 클래스의 멤버와 자손 클래스의 멤버가 중복 정의되어 서로 구별해야 하는 경우에만 super 를 사용하는 것이 좋다.

모든 인스턴스메서드에는 자신이 속한 인스턴스의 주소가 지역변수로 저장되는데, 이것이 참조변수인 thissuper의 값이 된다.

static 메서드에서는 인스턴스와 관련이 없다. 그래서 this와 마찬가지로 super역시 static메서드에서는 사용할 수 없고 인스턴스메서드에서만 사용이 가능하다.

package ch07_01;
class Child2 extends Parent{
	int x = 20;
	
	void method() {
		System.out.println("x = " + x);
		System.out.println("this.x = " + this.x);
		System.out.println("super.x = " + super.x);
	}
}

class Parent{
	int x = 10;
}

public class SuperTest2 {

	public static void main(String[] args) {
		Child2 c = new Child2();
		c.method();
	}

}

같은 이름의 멤버변수가 조상클래스와 자손클래스에 동일하게 있을 경우 super.x와 this.x는 서로 다른 값을 참조하게 된다. super.x는 조상클래스로부터 상속받은 멤버변수 x를 뜻하며, this.x는 자손클래스의 멤버변수인 x를 뜻한다. ( 조상 클래스에 선언된 멤버변수와 같은 이름의 멤버변수를 자손 클래스에서 중복해서 정의하는 것이 가능하며, 참조 변수 super를 이용해서 서로 구별한다. )

변수만이 아니라 메서드역시 super 를 써서 호출 할 수 있다. 특히 조상 클래스의 메서드를 자손 클래스에서 오버라이딩 한 경우에 사용한다.

	class Point{
    	int x;
        int y;
        
        String getLocation(){
			return "x: " + x + ", y: " + y;
        }
	}
    
    class Point3D extends Point{
    	int z;
        String getLocation(){
        	//return "x: " + x + ",y: " + y + "z: " + z;
            return super.getLocation() + "z: " + z;
		}
	}

getLocation()메서드를 오버라이딩 할때 조상클래스의 메서드를 super로 호출하였다. --> 조상클래스의 메서드가 변경되면 자동적으로 자손 클래스에 반영된다.

2.5 ] super() - 조상 클래스의 생성자

this() 는 같은 클래스의 다른 생성자를 호출하는데 사용되지만, super()는 조상클래스의 생성자를 호출하는데 사용된다.

자손클래스의 인스턴스가 생성되면, 자손의 멤버와 조상의 멤버가 모두 합쳐진 하나의 인스턴스가 생성된다. 이때 조상 클래스 멤버의 초기화 작업이 수행되어야 하기 때문에 자손 클래스의 생성자에서 조상 클래스의 생성자가 호출되어야 한다.

Object클래스를 제외한 모든 클래스의 생성자 첫 줄에 생성자 this()또는 super() 를 호출해야한다. 그렇지 않으면 컴파일러가 자동적으로 super();를 생성자의 첫 줄에 삽입한다.

생성자 선택
1. 클래스 - 어떤 클래스의 인스턴스를 생성할 것인가?
2. 생성자 - 선택한 클래스의 어떤 생성자를 이용해서 인스턴스를 생성할 것인가 ?

package ch07_01;
class Point{
	int x, y;
	
	Point(int x, int y){
		this.x = x;
		this.y = y;
	}
	
	String getLocation() {
		return "x: " + x + " y: " + y;
		
	}
}

class Point3D extends Point{
	int z;
	
	Point3D(int x, int y, int z){
		// 여기서 super(); 가 수행된다. -- 조상클래스의 기본생성자가 호출된다. 기본생성자가 존재하지 않아서 에러 발생 
		this.x = x;
		this.y = y;
		this.z = z;
	}
	
	String getLocation() {
		return super.getLocation() + " z: "  + z;
	}
}
public class PointTest {

	public static void main(String[] args) {
		Point3D p  = new Point3D(1,2,3);
		p.getLocation();
	}

}

위의 예제는 조상클래스의 기본생성자가 존재하지 않아서 에러가 발생한다. 자손 클래스의 생성자 첫줄에서 super();를 호출하기 때문이다. 위의 예제를 해결하기 위해서는 자손클래스의 생성자 첫줄에 조상클래스에서 생성해둔 생성자를 호출하여 사용하는 것이다.

class Point3D extends Point{
	int z;
    Point3D(int x, int y, int z){
    	super(x,y);
        this.z  = z;
	}
...
}

조상 클래스의 멤버변수는 이처럼 조상의 생성자에 의해 초기화되도록 해야 하는 것이다.


3. package와 import

3.1 ] 패키지 (package)

패키지란,클래스의 묶음 이다. 패키지는 클래스 또는 인터페이스를 포함시킬 수 있으며, 서로 관련된 클래스들끼리 그룹 단위로 묶어 놓음으로써 클래스를 효율적으로 관리 할 수 있다. ( 같은 이름의 클래스가 다른 패키지에 존재할 수 있다. -- 자신만의 패키지 체계 유지 )

클래스의 실제이름은 패키지명을 포함한 것이다. ( 예 - String : java.lang.String / java.lang패키지에 포함된 String클래스 ) -- 같은 이름의 클래스일지라도 서로 다른 패키지에 속하면 패키지명으로 구분이 가능하다.

클래스가 물리적으로 하나의 클래스파일(.class) 인 것과 같이 패키지는 물리적으로 하나의 디렉토리이다. 어떤 패키지의 클래스는 해당 패키지 디렉터리 에 존재하는 클래스파일이어야 한다.

패키지는 하위 패키지를 가질 수 있다. .(점)으로 계층을 표현한다.

  • 하나의 소스파일에는 첫번째 문장으로 단 한번의 패키지 선언만을 허용한다.
  • 모든 클래스는 반드시 하나의 패키지에 속해야 한다.
  • 패키지는 점(.)을 구분자로 하여 계층구조를 구성할 수 있다.
  • 패키지는 물리적으로 클래스파일(.class)을 포함하는 하나의 디렉터리이다.

3.2 ] 패키지의 선언

클래스나 인터페이스의 소스파일(.java)의 맨위에 다음과 같이 한줄만 적어주면 된다.
package 패키지명;

반드시 소스파일에서 주석과 공백을 제외한 첫번째 문장이어야 하며, 하나의 소스파일에는 단 한번만 선언될 수 있다. 해당 소스파일에 포함된 모든 클래스나,인터페이스는 해당 패키지에 속하게 된다.

패키지명은 대소문자 모두 가능하지만 클래스와 구별되기 위해 소문자를 원칙으로 한다.

패키지를 선언하지 않아도 실행이 되었던 이유는 이름없는 패키지때문이다. 소스파일에서 자신이 속할 패키지를 지정하지 않은 클래스는 자동적으로 이름 없는 패키지에 속하게 된다.

컴파일을 위해서는 -d옵션을 추가하여 컴파일을 한다.

	javac -d . PackageTest.java

-d옵션은 소스파일에 지정된 경로를 통해 패키지의 위치를 찾아서 클래스파일을 생성한다. 지정된 패키지와 일치하는 디렉토리가 존재하지 않는다면 자동적으로 생성한다. -d 뒤에는 해당 패키지의 루트(root)디렉토리의 경로를 적어준다.

	java com.codechobo.book.PackageTest

위의 명령어로 해당 파일을 실행시킬 수 있다.

3.3 ] import 문

소스코드를 작성시 다른 패키지의 클래스를 사용하려면 패키지명이 포함된 클래스 이름을 사용하여야 한다. 매번 패키지명을 붙이는 것은 불편하다. 클래스 코드의 작성 전 import 문으로 사용하고자 하는 클래스의 패키지를 미리 명시해주면 소스코드에 사용되는 클래스이름에서 패키지명을 생략할 수 있따.

import문의 역할은 컴파일러에게 소스파일에 사용될 클래스의 패키지에 대한 정보를 제공하는 것이다. 컴파일 시에 import문을 통해 소스파일에 사용된 클래스들의 패키지를 알아낸 다음 , 모든 클래스이름앞에 패키지명을 붙여준다.

Ctrl+Shift+o : 자동 import 문 추가

3.4 ] import 문의 선언

모든 소스파일에서 import문은 package 문 다음에 클래스 선언문 이전에 위치해야 한다. import 문은 여러번 선언할 수 있다. ( 여러개의 패키지 사용 )

일반적인 소스파일의 구성
1. package 문
2. import 문
3. 클래스 선언

import 문을 선언하는 방법

	import 패키지명.클래스명;
    또는
    import 패키지명.*;

import 키워드와 패키지명과 클래스이름을 함꼐 써주거나 하나의 패키지에서 여러개의 클래스를 사용하는 경우 *를 사용하여 지정된 패키지에 속하는 모든 클래스를 패키지명없이 사용할 수 있다.

* 는 해당 패키지에 일치하는 클래스이름을 모두 찾아준다. 즉, 하나의 패키지에서 여러 클래스를 사용하는 경우 클래스의 이름을 일일이 지정해주는 것보다 패키지명.*과 같이 하는 것이 편리하다. ( 하위 패키지의 클래스까지 포함하는 것은 아니다. -- 해당 패키지의 클래스이름만 대체가능 )

import 문으로 패키지를 지정하지 않으면 모든 클래스이름 앞에 패키지명을 반드시 붙여야 한다. 단 , 같은 패키지 내의 클래스들은 import 문을 지정하지 않고도 패키지명을 생략할 수 있다.

String,System 과 같은 java.lang패키지의 클래스들을 패키지명없이 사용할 수 있었던 이유는 모든 소스파일에는 묵시적으로 import java.lang.* 문이 선언되어 있기 때문이다. -- 매우 중요한 클래스들이 속한 패키지이므로 따로 import 문 선언 안해도 되게 해둠

3.5 ] static import 문

static import 문을 사용하면 static멤버를 호출할때 클래스이름을 생략할 수 있다. 특정 클래스의 static 멤버 변수를 자주 사용할 떄 유용하다. ( 코드 간결 )

package ch07_01;

import static java.lang.Math.*; //java.lang 패키지의 Math클래스의 모든 static 멤버 클래스이름 없이 사용가능 
import static java.lang.System.out;
//java.lang 패키지의 System클래스의 static out 멤버변수 클래스이름 없이 사용가능 

public class StaticImportTest {

	public static void main(String[] args) {
		out.println(random());
		out.println("Math.PI " + PI);
	}

}

4. 제어자 [ modifier ]

4.1 ] 제어자란?

제어자는 클래스,변수 또는 메서드의 선언부에 함꼐 사용되어 부가적인 의미를 부여한다. 종류는 크게 접근 제어자와 그 외의 제어자로 나눌 수 있다.

  • 접근 제어자 : public, protected, default, private
  • 그 외 : static, final, abstract, native, transient, synchronized, volatile, strictfp

제어자는 클래스나 멤버변수와 메서드에 주로 사용되며, 하나의 대상에 대해서 여러 제어자를 조합하여 사용하는 것이 가능하다. 단. 접근제어자는 네가지 중 하나만 선택해서 사용할 수 있다. ( 보통 접근 제어자를 가장 왼쪽에 둔다. )

4.2 ] static - 클래스의 , 공통적인

static은 클래스의, 공통적인 의 의미를 가지고 있다. 인스턴스 변수는 하나의 클래스로부터 생성되었더라도 각기 다른 값을 유지하지만, 클래스변수는 인스턴스에 관계없이 같은 값을 갖는다. ( 하나의 변수를 모든 인스턴스가 공유)

static 이 붙은 멤버변수와 메서드, 초기화 블럭은 인스턴스가 아닌 클래스에 관계된 것이기 때문에 인스턴스를 생성하지 않고도 사용할 수 있다. 인스턴스 메서드와 static메서드의 근본적인 차이는 메서드 내에서 인스턴스 멤버를 사용하는가의 여부에 있다.

static이 사용될수 있는 곳 - 멤버변수,메서드,초기화 블럭

제어자대상의미
static멤버변수- 모든 인스턴스에 공통적으로 사용되는 클래스 변수가 된다.
- 클래스변수는 인스턴스를 생성하지 않고도 사용가능하다.
- 클래스가 메모리에 로드 될때 생성된다.
메서드- 인스턴스를 생성하지 않고도 호출이 가능한 static 메서드가 된다.
- static메서드 내에서는 인스턴스 멤버들을 직접 사용할 수 없다.

가능하다면 static메서드로 하는 것이 인스턴스를 생성하지 않고도 호출이 가능해서 더 편리하고 속도가 빠르다. ( static 은 클래스가 메모리에 로드될 때 한 번만 수행된다. )

4.3 ] final - 마지막의, 변경될 수 없는

final은 마지막의 , 변경될 수 없는 의 의미를 가지고 있으며, 거의 모든 대상에 사용 할 수 있다. 변수에 사용되면 값을 변경할 수 없는 상수가 되며, 메서드에 사용되면 오버라이딩을 할 수 없게 되고 클래스에 사용되면 자신을 확장하는 자손클래스를 정의하지 못하게 된다.

final이 사용될 수 있는 곳 - 멤버변수,메서드,클래스,지역변수

제어자대상의미
final클래스변경 될 수 없는 클래스, 확장될 수 없는 클래스가 된다.
그래서 final로 지정한 클래스는 다른 클래스의 조상이 될 수 없다.
메서드변경 될 수 없는 메서드, final로 지정한 메서드는 오버라이딩을 통해 재정의 될 수 없다.
멤버변수변수 앞에 final이 붙으면 값을 변경할 수 없는 상수가 된다.
지역변수'멤버변수' 동일

대표적인 final클래스 - String,Math

final class FinalTest{ //조상이 될 수 없는 클래스
	final int MAX_SIZE = 10; //값을 변경할 수 없는 멤버변수
    
    final void getMaxSize(){  //오버라이딩 되지 못한는 메서드
    	final int LV  = MAX_SIZE; //값을 변경할 수 없는 지역변수
        return MAX_SIZE;
	}
}

생성자를 이용한 final멤버 변수의 초기화

final이 붙은 변수는 상수이므로 일반적으로 선언과 초기화를 동시에 하지만, 인스턴스 변수의 경우 생성자에서 초기화 되도록 할 수 있다. 클래스 내에 매개변수를 받는 생성자를 선언하여, 인스턴스를 생성할때 final이 붙은 멤버변수를 초기화하는데 필요한 값을 생성자의 매개변수로 부터 제공받는 것이다. -- 각 인스턴스마다 다른 값을 가지는 final변수를 가질 수 있다. 이것이 불가능할 경우 모든 인스턴스의 final변수는 모두 동일한 값을 가져야 할 것이다.

예로, 카드의 경우 각 카드마다 다른 종류와 숫자를 가지지만, 일단 카드가 생성시 카드의 값이 변경되어서는 안된다.


class Card{
	final int NUMBER;
	final String KIND;	
	Card(String kind,int number){ //생성자의 매개변수로 final변수들 초기화 
		this.NUMBER = number;
		this.KIND = kind;
	}	
	Card(){
		this("HEART",1);
	}	
	public String toString() {
		return KIND + ", " + NUMBER;
	}
}

public class FinalCardTest {

	public static void main(String[] args) {
		Card c = new Card("HEART",10);
		
		System.out.println(c.KIND);
		System.out.println(c.NUMBER);
		
		System.out.println(c);
	}

}

4.4 ] abstract - 추상의 , 미완성의

abstract는 미완성의 의미를 가지고 있다. 메서드의 선언부만 작성하고 실제 수행내용은 구현하지 않은 추상 메서드를 선언한느데 사용된다. 클래스에 사용되어 클래스 내에 추상메서드가 존재한다는 것을 쉽게 알 수 있게 한다.

abstract가 사용될 수 있는 곳 - 메서드,클래스

제어자대상의미
abstrac클래스클래스 내에 추상메서드가 선언되어 있음을 의미한다.
메서드선언부만 작성하고 구현부는 작성하지 않은 추상 메서드임을 알린다.

추상 클래스는 아직 완성되지 않은 미완성 설계도이므로 인스턴스를 생성할 수 없다.

abstract class AbstractTest{ //추상메서드를 가진 클래스
	abstract void move(){}; //선언부만 작성한 추상메서드
}

추상 메서드가 없는 클래스(완성된 클래스)에도 abstract를 붙여서 추상 클래스로 만드는 경우도 있다. ( 인스턴스 생성 필요없을 시 )
인스턴스를 생성하지 못하게 클래스 앞에 제어자 abstract를 붙여 놓은 것이다. 이 클래스만으로는 쓸모가 없지만, 해당 클래스를 상속받아 원하는 메서드를 오버라이딩 해도 된다는 장점이 있다.

4.5 ] 접근 제어자 ( access modifier )

접근 제어자는 멤버 또는 클래스에 사용되어, 해당하는 멤버 또는 클래스를 외부에서 접근하지 못하도록 제한하는 역할을 한다. 클래스나,멤버변수,메서드,생성자에 접근 제어자가 지정되어 있지 않다면, 접근 제어자가 default임을 뜻한다.

접근 제어자가 사용될 수 있는 곳 - 클래스,메서드,멤버변수,생성자

  • private : 같은 클래스 내에서만 접근이 가능하다.
  • default : 같은 패키지 내에서만 접근이 가능하다.
  • protected : 같은 패키지 내에서, 그리고 다른 패키지의 자손 클래스에서 접근이 가능하다.
  • public : 접근 제한이 없다.

접근 범위가 넓은 쪽에서 좁은 쪽의 순서로 왼쪽부터 나열하면
public > protected > default > private 이다.

대상 별 사용가능한 접근 제어자는 밑과 같다.

  • 클래스 : public,default
  • 메서드&멤버변수 : public,protected, default, private
  • 지역변수 : 없음

접근 제어자를 이용한 캡슐화

클래스나 멤버, 주로 멤버에 접근 제어자를 사용하는 이유는 클래스의 내부에 선언된 데이터를 보호하기 위해서이다. 이것을 데이터 감추기라고 하며, 객체지향 개념의 캡슐화에 해당하는 내용이다.

또다른 이유는, 클래스 내에서만 사용되는, 내부작업을 위해 임시로 사용되는 멤버변수나 부분작업을 처리하기 위해 메서드등의 멤버들을 클래스 내부에 감추기 위해서이다. -- 외부 접근 필요 없는 멤버를 private으로 지정하여 노출시키지 않아 복잡성을 줄인다.

접근제어자를 사용하는 이유

  • 외부로부터 데이터를 보호하기 위해서
  • 외부에는 불필요한,내부적으로만 사용되는 , 부분을 감추기 위해서

접근 제어자를 적절히 선택하여 접근 범위를 최소하하는 것은 테스트 범위를 줄일 수 있어 효율적이다.

멤버변수를 외부에서 접근하지 못하도록 private으로 선언한 뒤 멤버변수에 값을 변경하기 위해서 제공하는 메서드를 public으로 따로 선언해두면 해당 멤버변수의 값을 직접적으로 바꾸지 못하게 하는 것이 바람직하다.

get으로 시작하는 메서드는 단순히 멤버변수의 값을 반환하는 일을 하고, set으로 시작하는 메서드는 매개변수에 지정된 값을 검사하여 조건에 맞는 값일 떄만 멤버변수의 값을 변경하도록 작성한다.(상속을 통한 확장이 예상되는 클래스인 경우에는 자손 클래스에서 해당 set과 get메서드의 사용을 허가하기 위해 해당 메서드의 접근제어자를 protected나 public으로 지정해야 한다. )

  • 멤버변수의 값을 읽는 메서드의 이름 : get멤버변수이름 / getter
  • 멤버변수의 값을 변경하는 메서드의 이름 : set멤버변수이름 / setter

생성자의 접근 제어자

생성자에 접근 제어자를 사용함으로써 인스턴스의 생성을 제한 할 수 있다. 보통 생성자의 접근제어자는 클래스의 접근제어자와 같지만 다르게 지정할 수도 있다.

생성자의 접근 제어자를 private으로 지정하면, 외부에서 생성자에 접근할 수 없으므로 인스턴스를 생성할 수 없게 된다. 그래도 클래스내부에서는 인스턴스를 생성할 수 있다. 대신 인스턴스를 생성해서 반환해주는 public메서드를 제공하여 외부에서 이 클래스의 인스턴스를 사용하도록 할 수 있다. 이 메서드는 public인 동시에 static이어야 한다. (인스턴스 생성없이 사용할 수 있도록 )

class Singleton{
	...
    private static Singleton s = new Singleton();
    private Singleton(){
    	...
	}
    
    //인스턴스를 생성하지 않ㄴ고도 호출할 수 있어야 하므로 static
    public static Singleton getInstance(){
    	return s;
	}
    ...
}

이처럼 생성자를 통해 직접 인스턴스를 생성하지 못하고 public메서드(static)를 통해 인스턴스에 접근하게 함으로써 사용할 수 있는 인스턴스의 개수를 제한할 수 있다.

생성자가 private인 클래스는 다른 클래스의 조상이 될수 없다.
자손 클래스의 인스턴스를 생성할 때 조상클래스의 생성자를 호출해야만 하는데 , 생성자의 접근 제어자가 private 이므로 자손 클래스에서 호출하는 것이 불가능하기 때문이다. 그래서 클래스앞에 final을 붙여서 상속할 수 없는 클래스임을 표시해주는 것이 좋다.

4.6 ] 제어자의 조합

대상사용가능한 제어자
클래스public, default,final,abstract
메서드모든접근제어자,final,static,abstract
멤버변수모든접근제어자,final,static
지역변수final

제어자 조합 사용시 주의해야 할 사항

  1. 메서드에 static과 abstract을 함께 사용할 수 없다.
    static 메서드는 몸통이 있는 메서드에만 사용할 수 있기 때문이다.
  2. 클래스에 abstract와 final을 동시에 사용할 수 없다.
    클래스에 사용되는 final은 클래스를 확장할 수 없다는 의미이고 abstract는 상속을 통해서 완성되어야 한다는 의미이므로 서로 모순되기 때문이다.
  3. abstract메서드의 접근 제어자가 private일 수 없다.
    abstrac메서드는 자손 클래스에서 구현해주어야 하는데 접근 제어자가 private이면, 자손 클래스에서 접근 할 수 없기 때문이다.
  4. 메서드에 private과 final을 같이 사용할 수 없다.
    적븐제어자가 private인 메서드는 오버라이딩 될 수 없기 때문이다. 둘중하나만 사용하여도 의미가 충분하다.
profile
멋쟁이 토마토

0개의 댓글