[Java 입문기] Java Script와 Java는 무엇이 다를까 2

파이·2021년 12월 21일
1

저번에는 비교적 간단하고 단편적으로 이해 가능한 내용이었다고 하면, 이번엔 좀 더 핵심적이고 연관적인 내용이다.

1. 인스턴스와 클래스 (Instance & Class)

자바를 배우면서 정말 핵심적인 내용인 것 같은 인스턴스와 클래스.
심지어 첫 자바 프로젝트를 만들며 생긴 구조적인 의문점에 대해서도 어느정도 해결 할 수 있을 만큼 자바에서 아주 중요한 개념이다.

우리가 자바를 배우면서 새로운 클래스를 사용할 때, 수 많은 메서드와 변수를 만들게 될 것이다. 그리고 이 메서드와 변수는 크게
인스턴스 메서드(변수)와 클래스 메서드(변수) 로 나뉜다.

무슨 차이가 있을까?

class Foo {
	public static String classVar = "클래스 변수"; // static 으로 고정된 건 클래스변수
	public String instanceVar = "인스턴스 변수"; // 고정이 안되어있는건 인스턴스변수
	
	// 클래스 메소드
	public static void classMethod() {
		System.out.println(classVar);
//		System.out.println(instanceVar); // 에러, instance 메소드는 바로 접근 불가능하다.
	}
	
	// 인스턴스 메소드
	public void instanceMethod() {
		System.out.println(classVar); // 잘 출력됨
		System.out.println(instanceVar); // 얘도 잘 출력됨
// 인스턴스 메소드에서는 예외적으로 인스턴스 변수에 바로 접근 가능하다.
	}
}

별 의미없는 클래스 안에 각각 인스턴스 메서드(변수)와 클래스 메서드(변수)를 만들어 보았다.
흔히 우리가 새로운 메서드(변수)를 선언할 때, static이 붙으면 클래스 메서드(변수), 붙지 않으면 인스턴스 메서드(변수) 이다.

지금껏 써왔듯, 클래스 메서드(변수)는 별다른 조건 없이 바로 사용이 가능하지만 인스턴스 메서드는

Foo f1 = new Foo();  // 인스턴스 생성

이런식으로 꼭 새로운 인스턴스 메서드를 생성해야 사용할 수 있다.
이러한 조건에는 이유가 있는데,

인스턴스의 생성시기가 다르기 때문이다.
인스턴스 메서드(변수)는 새 인스턴스를 생성하기 전까지, 메모리에 할당 되지 않는다. 때문에 인스턴스 선언 전에는 인스턴스 메서드는 접근 자체가 오류가 나는 것이다.

그럼 여기서도 예외케이스가 하나 보일 것이다.

인스턴스 메서드 안에서 인스턴스 변수는 왜 사용이 가능해?

이유는 인스턴스 메서드의 사용 시기에 이미 인스턴스가 생성되었기 때문에, 별다른 오류가 나지 않는 것이다.

또한, 클래스 메서드는 값이 고정되어 있기 때문에

	Foo f1 = new Foo();  // 인스턴스 생성
    	Foo f2 = new Foo(); 
		
  // 각 변수 수정하기.
  f1.classVar = "클래스변수 f1에서 수정";
  System.out.println(f1.classVar); // 출력: "클래스변수 f1에서 수정"
  Foo.classVar = "클래스변수 Foo에서 수정";
  System.out.println(Foo.classVar); // 출력: "클래스변수 Foo에서 수정"
  // 인스턴스나 클래스에서 접근시, Foo 클래스 원본의 변수가 수정된다.

이런식으로 새로 생성한 인스턴스에서 클래서 변수(메서드) 접근하여 값을 수정하면

클래스 변수(메서드)의 원본 값이 바뀐다. (인스턴스 메서드(변수)는 각 인스턴스 안에서 값이 바뀐다)

이러한 성질 때문에 count처럼 바뀌는 값을 기록할 때 클래스 변수를 활용하기도 한다.


2. 생성자(contructor)와 상속(extends)

위에서 클래스 안에서 인스턴스와 클래스 메서드(변수)의 차이를 정리했다면, 이번엔 클래스의 상속을 위한 중요한 요소들을 정리해보고자 한다.

클래스를 이용하여 변수를 찍어내는 방식은 JS와 비슷하지만, 약간 다른 부분을 위주로 보면 될 것 같다.

A. 기본 생성자 (default, 생성자의 자동선언)

class Car {
	public String modelName = "소나타";
	public int modelYear = 2014;
	public String color = "파란색";
	public int maxSpeed = 180;

	public String getModel() {
		return this.modelYear + "년식 " + this.modelName + " " + this.color;
	}
}

// Car 클래스 사용
public class Constructor {
	public static void main(String[] args) {
		Car myCar = new Car();					// 기본 생성자의 호출
		System.out.println(myCar.getModel());
	}
}

// 출력 값: 2014년식 소나타 파란색

this와 인자를 받는 생성자를 만들지 않아도 자동으로 생성자가 생성된다. 여태까지 그렇게 해왔던 거임.
때문에 인자는 당연히 없으며, getModel이라는 메서드를 사용시 고정된 변수로 값이 출력된다.

이 경우에 값을 변경하고 싶다면,
myCar.modelName = “다른 차 이름”;
이렇게 직접 인스턴스 변수를 수정해주자.


B. 생성자의 선언 (feat. extends)

이번엔 위의 Car 클래스를 상속하여, 새 클래스를 만들었다.

class Avante extends Car {
	// 기본 변수 값들을 가져와서 따로 변수 선언이 필요하지 않다.

	// 이번엔 생성자를 선언해주자.
	Avante(String modelName, int modelYear, String color, int maxSpeed) {
		this.modelName = modelName;
		this.modelYear = modelYear;
		this.color = color;
		this.maxSpeed = maxSpeed;
	}
    
	// 상속을 안받아도 작동은 하지만, 안 받는다면 "소나타" 값들로 출력됨
	public String getModel() { 
		return this.modelYear + "년식 " + this.modelName + " " + this.color;
	}
}

public class Constructor {
	public static void main(String[] args) {
		Avante avante = new Avante("아반떼", 2016, "흰색", 200);
		System.out.println(avante.getModel());
	}
}

// 출력 값: 2016년식 아반떼 흰색

일반적으로 생성자를 활용한 클래스는

변수선언
this.를 활용한 생성자 선언
생성자 활용

이런 형식이 필요하지만, 이번엔 Car를 상속받았기 때문에 변수 선언은 생략할 수 있다.
또한, 중간에 getModel() 메서드 또한 인자와 리턴값이 같기에 생략 할 수 있지만,

생략하면 위 Car의 "소나타..."를 출력하는 값 까지 그대로 상속하여, 고정된 값만 출력하게 된다. (생성자의 의미가 사라져...)

아무튼, 메서드까지 다시 선언하고 새 인스턴스를 만들어 사용해보면, 인자로 받은 값들을 생성자로 받아 잘 출력하는 결과를 볼 수 있다.


C. 상속받고 생성자 추가하기

이번엔 상속받은 자식 클래스에서 생성자를 추가하여 더 유연하게 사용하는 방법이다.

class AvanteHybrid extends Avante {
	private boolean isHybrid = false;
	private String hybridValue = "";
	
	AvanteHybrid(String modelName, int modelYear, String color, int maxSpeed, boolean isHybrid) {
		super(modelName, modelYear, color, maxSpeed);
		this.isHybrid = isHybrid;
		
		if (isHybrid) {
			hybridValue = "하이브리드 차량입니다."; 
		} else {
			hybridValue = "하이브리드 X";
		}
	}
	
	public String getModel() { // 상속을 안받아도 작동은 하지만, 안 받는다면 "소나타" 값들로 출력됨
		return this.modelYear + "년식 " + this.modelName + " " + this.color + " " + hybridValue;
	}
}

public class Constructor {
	public static void main(String[] args) {
		AvanteHybrid avanteHybrid = new AvanteHybrid("신형 아반떼", 2020, "흰색", 210, true);
		System.out.println(avanteHybrid.getModel());
	}
}

// 출력 값: 2020년식 신형 아반떼 흰색 하이브리드 차량입니다.
  1. 추가할 생성자는 바로 변수로 선언해준다.
  2. 그 외 기본 생성자들은 super()라는 형식을 통해, this...라고 적은 많은 생성자들을 자동으로 가져와준다.
  3. 이제 추가할 생성자를 위와 같이 지정해주고
  4. 생성자의 구조를 짤 때 조건문에 따른 변수는 생성자 구조 안에서 지정해 준다.

굉장히 복잡하게 써 놓았지만, 생성자는 super를 통해 상속을 받고 새로운 생성자를 만들수 있으며, 조건문은 생성자 구조 안에서 사용한다. 가 핵심일 것 같다.


3. 오버로딩과 오버라이딩

A. 오버로딩 (Overloading)

class Eating {

	// 인자가 하나인 print
	void print (String dish) {
		System.out.println(dish + "를(을) 먹었다.");
	}
		
       	 //인자가 두개인 print
	 // 메서드명은 같으나, 인자의 갯수가 달라 다른 메서드가 정의되었다. (오버로딩)
	void print (String dish, int potion) {
		System.out.println(dish + "를(을) " + potion + "인분 " +  "먹었다.");
	}
}

public class OverApp {

	public static void main(String[] args) {
		Eating eat = new Eating();
		eat.print("치킨"); // 치킨를(을) 먹었다.
		eat.print("피자", 2); // 피자를(을) 2인분 먹었다.
		
	}
}

위 클래스에서 print 메서드가 두 번 선언되었으나, 두 print가 받는 인자의 갯수와 타입이 달라 각각 다른 메서드로 선언되었다. (오버로딩)

때문에 두 메서드 모두 사용 가능하다.

B. 오버라이딩(Overriding)

class EatingDelicious extends Eating {
	// 인자가 하나인 print 메서드를 덮어씀 (오버라이딩)
	void print(String dish) { 
		System.out.println(dish + "를(을) 맛있게 먹었다.");
	}

// 인자 갯수가 같은데 리턴 타입만 다른 메서드는 오버라이딩이 불가능하다. (void -> String)
//	String print (String dish, int potion) {
//		System.out.println(dish + "를(을) " + potion + "인분 " +  "맛있게 먹었다.");
//              return dish + "를(을) " + potion + "인분 " +  "맛있게 먹었다."
//	}
}

public class OverApp {

	public static void main(String[] args) {
		EatingDelicious eat2 = new EatingDelicious();
		eat2.print("치킨"); // 치킨를 맛있게 먹었다.
		eat2.print("피자", 2); // 피자를(을) 2인분 먹었다. (변화 x)
	}
}

상속받은 클래스에 인자 갯수와 타입, 리턴값이 같은 메서드가 있었기 때문에, 이를 덮어썼다. (오버라이딩 했다)
그러나 인자의 타입과 갯수가 같으나, 리턴 타입이 다른 함수라면 함수를 사용 시 구분할 수가 없기 때문에 오버라이딩 및 오버로딩 둘다 되지 않는다.


4. 접근제어자와 구조

드디어 첫 Java 프로젝트를 생성할 때, 맨 앞에 따라오던 public과 관련된 개념이다. 사실 JS에서도 있지만, 제대로 몰랐기 때문에 이번 기회에 제대로 배웠다.

접근제어자를 배우기에 앞서, Java의 구조는 크게

프로젝트(Project) > 패키지(Package) > 클래스(Class) > 메서드와 변수 (Method & Variable)

로 이루어진다.

Package는 뭔가요?

사실 접근제어자를 처음 배울 때 꽤나 고생했는데, Package에 대해 몰랐기 때문이었다.
Package는 쉽게 말해서 비슷한 유형의 클래스들을 묶어놓은 자바 디렉토리이다.

쉽게 말하면 우리가 import 한 값들을 사용할 때,

import javax.swing...

이런식으로 사용할 때, 처음 나오는 javax가 패키지의 개념이다.

직접 만들면 요로코롬 구조를 갖는다.

접근제어자는 크게 네 가지로 나뉜다.

  1. private
  2. default
  3. protected
  4. public

위로 갈수록 폐쇄성이 증가하며, default는 아무것도 붙이지 않을 때가 default다.

A. private: 재정의란 없다.

private는 해당 클래스 안에서만 메서드와 변수에 접근이 가능하다.
때문에,

class privateClass {
	 private static String park = "park"; // private
}

public class accessApp {
	public static void main(String[] args) {
//		privateClass.park = "lee"; // 오류, private에 접근 불가
	}
}

이런식으로 private 값에 직접 접근하려고 했으나, 접근 자체가 불가능하다. 값이 변할 일이 없는 메서드와 변수를 활용할 때 쓸 듯 하다.

B. default: 패키지 내에선 가능!

변수나 메서드를 선언할때 접근제어자를 아무것도 붙이지 않는다면 default로 정의되며,
같은 패키지 안에서만 접근이 가능하다

package test
class privateClass {
	 static String lee = "lee";
}

...
public class accessApp {
	public static void main(String[] args) {
		privateClass.lee = "kim";
	}
}

같은 패키지 내에서는 전부 접근 가능하지만, import한 package에선 접근이 불가능하다.

C. protected: 집나간 자식도 내자식은 챙긴다

protected는 특이하게 외부 패키지여도, 외부 패키지에서 상속시에는 접근이 가능하다.

package testPack

public class PTC {
	protected static String packVar = "패키지 변수";
    ...
import testPack.PTC

class extendsPTC extends PTC {
	static String packVar2 = packVar + " 상속 완료!"; // OK
]

public class AccessPackageApp {
	public static void main (String[] args) {
 //   	System.out.println(PTC.packVar) // 오류, 접근 불가.
    }
}

PTC란 외부 패키지를 생성하고 import 할때, PTC 클래스를 상속하는 클래스를 만들고, protected로 만든 변수에 접근이 가능하다.

그러나 메인 메서드에서 print 할 때에는 상속관계가 아니기 때문에, 아예 출력이 불가능하다.

D. public: 다 가능.

사실 제일 마음 편한 접근제어자다. 그래서 개인 프로젝트를 만들 때에는 다 public으로 설정해도 되지만...

보안 문제상 private 등 여러 접근제어자를 구분한다고 들었다.
개념은 잡았으니... 나중에 무리 없길...


오늘 내용을 정리하면서 느낀 점은,
JS에서 배웠던 부분이 많았으며, 그 부분을 얼마나 소홀히 했는가 에 대해 느꼈다.
JS도 class를 활용하긴 하지만, 개인적으로 class는 복잡해서 많이 활용하지 않았고, 때문에 상속이나 public 등 넘어간 부분이 많았다.

그러나 Java에선 Class 계속 다루는 만큼 이 내용이 핵심적이라고 느꼈고, 또 두번째 언어를 배울때는 대충 배우고 싶지 않았다.

어서 Java를 열심히 배우고, Java Script도 다시 제대로 배워보고 싶다.

profile
기록

0개의 댓글