클래스(접근제한자,전역변수,지역변수,상속,오버라이드,toString,다형성)

최동민·2022년 5월 21일
0

JAVA

목록 보기
5/13

접근 권한 제어자

default
public
protected
private

  • default 같은 패키지에서는 접근 가능
    default는 다른 말로 friendly라고 한다.

  • public 하나의 파일 안에 여러개 클래스를 만들 때에는 메인 클래스에만 public이 붙을 수 있다.
    같은 패키지에 다른 클래스 안, 그리고 다른 패키지에서도 접근이 가능하다.

  • protected 같은 패키지에서는 접근 가능
    protected는 영역 처리가 굉장히 애매하다.

  • private은 선언된 영역에서만 접근이 가능함
    '다른 곳에서 쉽게 접근을 하지 못하게' 라는 의미도 있다. 접근 할 수는 있다는 뜻.
    직접 접근은 못하지만 간접 접근은 할 수 있다는 것. 사용자로부터 이거 private이니 주의하라는
    메시지를 줄 수 있다는 것. 다른 곳에서 선언 위해서는 우린 메소드를 이용한다.

package Class;

public class Access {

	int data1 = 10;
	public int data2 = 20;
	protected int data3 = 30;
	private int data4 = 40;
	

	void f() {
		
	}	
}

	class Cloud {
		
		void function() {
			Access a = new Access();	
		}
	}

다른 패키지에서 호출해보자.

package Access;

import Class.Access;

public class Access3 {

	void function() {
		Access a = new Access();
		a.data2=10;
		
	}
}

다른 패키지에서 불러올 때는 import를 통해 내가 그 위치를 알려주어야 한다.
여기서 접근 가능한 제한자는 data2 인 public 뿐인 것을 확인 할 수 있다. private 접근제한자인 data4를 호출하기 위해서는 어떻게 해야할까?

Access클래스에 추가

public int getData4() {
		return data4;
	}

	public void setData4(int data4) {
		this.data4 = data4;
	}

다른 패키지에 있는 private이라도 그 메소드를 public으로 지정한다면 접근이 가능하다.

private을 만들어 외부에서 접근하고 싶다면 이렇게 getter,setter을 만든다.

private이라 해도 프로그래밍 언어에서 접근제한자 자체는 보안이 낮다. 개발자들 사이에서 서로 주의를 주고자 하는 뜻의 목적일 뿐이다.

getter setter

getter()

  • public 제어자를 붙여서 다른 클래스 혹은 패키지에서도 private 변수에
    접근할 수 있도록 해준다. 데이터 가져오기 역할

setter()

  • 데이터 변경을 목적으로 한다

자 그럼 data4도 다른 패키지에서 불러와보자.

 package Access;

import Class.Access;

public class Access3 {

	void function() {
		Access a = new Access();
		a.data2=10;
		System.out.println(a.data2);
		System.out.println(a.getData4());
	
	}
	
	public static void main(String[] args) {
		Access3 a = new Access3();
		a.function();  // 10과 40이 출력됨 	
	}
}

지역변수 (stack 영역)

지역 안에서 선언된 변수
그 영역의 닫는 중괄호 } 를 만났을 때
메모리에서 해제된다.
사용자가 직접 초기화 해야한다.
다른 영역에서 접근할 수 없기 때문에
보안성이 뛰어나다. 

전역변수 (data 영역)

클래스 영역 안에 있고, 전체 영역에서 사용가능한 변수
클래스 영역 외의 어떠한 영역에도 포함되어 있지 않다. 
new를 만났을 때 초기화 된다. 
프로그램 종료시 메모리에서 해제된다. 
다른 영역에서도 접근할 수 있기 때문에
보안성이 상대적으로 낮다. 
public class VariableTest {

	//전역 변수
	int data = 0;
	
	void f() {
		System.out.println(++data);
	}
	
	void f2() {
		data = 20;
	}

public class VariableTest2 {

	public static void main(String[] args) {
		
		VariableTest vt = new VariableTest();
		vt.f();
		vt.f();
		vt.f2();
		vt.f();
		vt = new VariableTest();
		vt.f(); // 출력값 1

	}

main에서 전역 변수가
vt = new VariableTest(); 를 만나는 순간 다시 읽는다.
초기화 되어 다음 vt.f(); 값을 출력했을 때 결과는 1이 되어 나온다.

만약 new를 만나도 그대로 그 값을 유지하고 싶다면? -> static

static변수 (data 영역)

컴파일을 하게 되면 가장 먼저 메모리에 올라가고,
어떠한 경우에도 초기화가 되지 않는다.
단, 프로그램이 종료시 메모리에서 해제된다.
메모리에 고정되어 있기 때문에 남용 시
메모리 혹은 프로그램 실행 속도에 악영향을 준다. 

전역변수를 static int data = 0; 해주었을 때
vt.f(); 값을 다시 출력하면 결과는 22가 되어 나온다.

A클래스를 B클래스가 쓰고 싶을 때 new를 해서 불러와 20이라는 데이터 값을 로직에 담았다, 그리고 C클래스는 그 데이터를 불러오기 위해 new를 하게 될텐데 그 때 전역변수는 초기화가 된다는 것.
즉, 여러 클래스를 이용할 때 우리는 생성자를 통해 접근하는데 그 때 값이 초기화되는 것을 원치 않는다면 static이 필요한 것이다.


상속(inheritance)

부모님이 돌아가셨을 때 물려받는 것이 상속이다.

기존 클래스 안에 있는 field(필드) 들을 내가 지금부터 만드는 클래스 안에 사용하고 싶다.
휴대폰을 예로 들자면,
2G부터 5G까지 개발이 되어 오는 과정에서
2G에서 3G를 개발할 때 2G안에 있던 field들을 사용하는 것이다.
맨땅에 헤딩보다 훨씬 많은 도움이 될 것.

기존에 사용 중인 클래스의 필드를
다른 클래스에서 계속 사용을 해야 한다면
상속을 받는다. 그렇게 되면 부모 클래스의 필드를
마치 자신의 것처럼 사용할 수 있다.

다만 다중 상속은 불가능하다. 한 자식이 하나의 부모만 있어야 한다.

오버라이딩(Overriding)

모든 인간은 먹고 자고 두 발로 걷는다.
원숭이는 먹고 자는 것은 동일하나 항상 두 발로 걷지만은 않는다.
그럼 '먹는다' '잔다' 는 동일하게 쓰되, '걷는다' 는 부모(인간)의 '걷는다' 메소드 이름을 자식(원숭이)에 그대로 가져온 후 그 내용을 내가 원하는대로 바꿀 수 있다. 원숭이의 '걷는다'에 맞게끔.
즉 재정의를 하는 것.
이것을 오버라이딩이라 한다.

class A {
	A필드
}

class B extends A {
	A,B필드
}

A : 부모클래스, 상위클래스, 슈퍼클래스, 기반클래스
B : 자식클래스, 하위클래스, 서브클래스, 파생클래스

  • 부모 클래스로부터 상속받은 멤버 변수의 접근 권한이 private일 때
  1. 부모 클래스의 생성자를 호출해서 초기화 시킬 수 있다.
  2. 부모 클래스에 setter 메소드를 통해서 초기화 시킬 수 있다.
  3. 변수에 저장된 값은 getter 메소드를 통해서 가져온다.
  • 부모 클래스로부터 상속받은 멤버 변수의 접근 권한이 protected일 때
  1. 자식 클래스에서는(상속한 클래스) 접근이 가능하기 때문에 this를 사용해서 초기화한다.
  2. 자식 클래스에서 접근이 가능하므로 getter 메소드를 사용하지 않고 변수명으로 값에 직접 접근하면 된다.

자식 클래스의 생성자가 실행되기 전 부모 클래스의 생성자가 먼저 실행된다. 항상 부모 클래스의 생성자가 먼저라는 것.
실행할 부모 클래스의 생성자를 지정하지 않으면 부모 클래스의 기본 생성자가 자동으로 실행된다.
자동으로 입력되는 super()를 지우더라도 자바 컴파일러가 자동으로 넣어준다. 내부적으로 super는 있는 것.

package Variable;

class A {
	int data = 10;
	
	public A() {       // 기본생성자 
		System.out.println("부모 클래스 생성자");
	}
	
	void show() {	   // A의 메소드 
		System.out.println("A 클래스");
	}
}

class B extends A {
	
	@Override      // 부모 메소드를 가져와 재정의해주었음.
	void show() {
		// TODO Auto-generated method stub
		System.out.println("B 클래스");
	}    
	
	void check() { // B의 메소드 
		show();
	}
}

public class InhTest {
	public static void main(String[] args) {
		
	
	B instance = new B();
	instance.check();
	// 원래는 부모 클래스 생성자(기본 생성자), A 클래스(A메소드 show) 가 호출되었지만
    // class B에서 부모 메소드 show를 재정의 해줌으로써
    // 부모 클래스 생성자, B 클래스 가 호출 되었다.
    (단, 기본 생성자를 호출하였을 때만 부모 것을 가져옴.)

	}
}

package Variable;

public class Car {
	final int wheel = 4;  // final : 상수. 수정할 수 없게끔 막아줌. 
	public String brand;
	protected String color;
	private int price;
	
	public Car() {	// 기본생성자
		System.out.println("부모 클래스의 기본 생성자");
	}

	//오버로딩
	public Car(String brand, String color, int price) {
		this.brand = brand;
		this.color = color;
		this.price = price;
	}
		
	//private를 getter,setter로 접근 
	public int getPrice() {
		return price;
	}

	public void setPrice(int price) {
		this.price = price;
	}

	//메소드
	void engineStart() {
		System.out.println("열쇠로 시동 킴");
	}
	
	void engineStop() {
		System.out.println("열쇠로 시동 끔");
	}

SuperCar라는 새로운 클래스를 만들어 Car를 상속해보자.

package Variable;

// Car를 상속
public class SuperCar extends Car {
	String mode;
	
	public SuperCar() {       
		System.out.println("자식 클래스의 기본 생성자");
	}
	
	// 오버로딩 
	public SuperCar(String brand, String color, int price, String mode) {
		super(brand, color, price);// 부모클래스를 호출. 이미 초기화되어있는 것들.
//		setPrice(price); // price는 private이기 때문에 set을 통해 초기화할 수도 있음.
		this.mode = mode; // SuperCar의 멤버변수이기 때문에 따로 초기화 해주어야 함. 
	}
	
	@Override
	void engineStart() {    // 부모 클래스 메소드를 오버라이딩 
		System.out.println("음성으로 시동 킴");
	}
	
	@Override
	void engineStop() {
		System.out.println("음성으로 시동 끔");
	}
}
	
}

이제는 main에서 값을 출력해보겠다.

package Variable;

public class CarTest {

	public static void main(String[] args) {
		
		SuperCar ferrari = new SuperCar("ferrai","Red",45000,"sport");
		ferrari.engineStart(); // 오버라이딩 한 값으로 출력 
		ferrari.engineStop(); 
		System.out.println(ferrari);
//		Variable.SuperCar@15db9742이 출력. 
	}

}

toString();

현재 ferrari를 출력해보면 주소값이 출력이 된다.
우리가 ferrari라는 객체명만 호출해도 그 정보가 담겨 나오게 할 수는 없을까?
모든 클래스는 Object 클래스를 상속받는다. 이 내용은 추후에 다룰 것.
Object라는 클래스에는 toString이라는게 있다.
즉 우리가 객체명을 찍었을 때에도 뒤에 .toString이 생략되어있는 것이다.

toString을 찾아보니 주소값을 호출하고 있는 내용임을 확인할 수 있다.
그럼 toString을 재정의 하면 되겠다는 생각을 하게된다.

	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return "슈퍼카입니다.";
	}

SuperCar에서 toString을 재정의하고 CarTest로 돌아와 System.out.println(ferrari.toString()); 출력해보니

슈퍼카입니다.

가 출력됨을 확인할 수 있다. toString()은 생략가능 하다 했으니
System.out.println(ferrari); 이렇게 하여도 출력값이 같은 것을 확인할 수 있다.

객체명만을 출력하여도 내가 원하는 값을 나오게 할 수 있게 된 것이다.

내용을 수정해보았다.

	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return "브랜드 : " + brand + "\n색상 : " + color + "\n가격 : " +
				getPrice() + "만원";
		
	}

Price는 private이기 때문에 get으로 호출한다.

System.out.println(ferrari); 의 출력값을 확인해보자.

브랜드 : ferrai
색상 : Red
가격 : 45000만원

this는 객체가 마침표를 찍었을 때 자동으로 넘어가는 주소값을 담고 있다고 했었다. superCar의 brand. superCar의 color.
this 생략 가능

다형성 (polymorphism)

다양한 형태의 성질을 띄고 있다.
메소드가 이름은 같지만 다양한 형태를 가진다.

하나의 메소드가 서로 다른 클래스에서 다양하게 실행 되는 것을 말한다.
다형성을 구현하기 위해서는 다형성을 구현할 메소드를 포함할 클래스에게 부모 클래스가 상속해줘야 한다.
부모 클래스와 자식 클래스에 같은 이름의 메소드가 있어야 하고, 자식 클래스에서는 재정의(Overriding)해야한다.
따라서 다형성의 종류에는 Overloading, Overriding이 있다.

profile
코드를 두드리면 문이 열린다

0개의 댓글