자식 내부에서 부모의 기능을 온전하게 호출 하고 싶을때에 super 를 사용 하여 부모 class 에 대한 참조를 가리킬 수 있다.
→ super 를 사용 하면 본인 타입을 찾지 않고 부모 타입을 확인하여 갖고 온다.
super - 생성자
자식 class 의 객체를 생성 하면 내부 메모리에 자식과 부모 class 가 각각 만들어진다. 따라서 각각의 생성자도 모두 호출 되어진다.
상속 관계 에서는 자식 class 의 생성자에서 부모 class 의 생성자를 반드시 호출 해야 한다.
생성자의 호출은 부모 class 부터 호출 하고 자식 class 가 호출 된다.
→ 조부 classA
package extends1.super2;
public class ClassA {
public ClassA() {
System.out.println("ClassA 생성자");
}
}
→ 부모 classB
package extends1.super2;
public class ClassB extends ClassA{
public ClassB(int a) {
// super(); // 기본생성자 생략 가능
System.out.println("ClassB 생성자 a=" + a);
}
public ClassB(int a, int b) {
// super(); // 기본 생성자 생략 가능
System.out.println("ClassB 생성자 a=" + a + " b=" + b);
}
}
→ 자식 classC
package extends1.super2;
public class ClassC extends ClassB{
public ClassC() {
super(10, 20);// 기본 생성자가 없으므로 직접 호출 해주어야 한다.
System.out.println("ClassC 생성자");
}
}
→ 자식 classC 객체 생성
package extends1.super2;
public class Super2Main {
public static void main(String[] args) {
ClassC classC = new ClassC();
}
}
기존에 배웠던 접근제어자에서 protected 인 같은 패키지만 허용 하고, 다른 패키지 일지라도 상속된 관계의 호출 허용
→ 부모 class 와 자식 class 를 다른 class 에 생성 하고, 부모 class 에 접근 제한자를 적용 하여 만들어보자 🔽
// 부모 class
package extends1.access.parent;
public class Parent {
public int publicValue;
protected int protectedValue;
int defaultValue;
private int privateValue;
public void publicMethod(){
System.out.println("Parent.publicMethod");
}
protected void protectedMethod() {
System.out.println("Parent.protectedMethod");
}
void defaultMethod() {
System.out.println("Parent.defaultMethod");
}
private void privateMethod() {
System.out.println("Parent.privateMethod");
}
public void printParent() {
System.out.println("==Parent 메서드 안==");
System.out.println("publicValue = " + publicValue);
System.out.println("protectedValue = " + protectedValue);
System.out.println("defaultValue = " + defaultValue);
System.out.println("privateValue = " + privateValue);
// 부모 메서드 안에서 모두 접근 가능
defaultMethod();
privateMethod();
}
}
→ 자식 class 가 상속 받은 후 부모 class 의 변수와 메소드를 호출 해보면 🔽
// 자식 class
package extends1.access.child;
import extends1.access.parent.Parent;
public class Child extends Parent {
public void call() {
publicValue = 1;
protectedValue = 1; // 상속 관계 or 같은 패키지
// defaultValue = 1; // 다른 패키지 접근 불가, 컴파일 오류
// privateValue = 1; // 접근 불가, 컴파일 오류
publicMethod();
protectedMethod(); // 상속 관계 or 같은 패키지
// defaultMethod(); // 다른 패키지 접근 불가, 컴파일 오류
// privateMethod(); // 접근 불가, 컴파일 오류
printParent(); // 부모의 public 메서드 내부에 있는 default, private 접근 제한자는
// 상관없이 호출 가능하다
}
}
→ 자식 class 객체를 생성 하여 작동 시켜 보면
package extends1.access;
import extends1.access.child.Child;
public class ExtendsAccessMain {
public static void main(String[] args) {
Child child = new Child();
child.call();
}
}
부모 타입의 기능을 자식에서 다르게 재정의 하고 싶을 때 메서드 오버라이딩을 사용해 재정의 할 수 있다.
메서드 오버라이딩
→ 사용 방법 🔽
package extends1.overriding;
public class Car {
public void move() {
System.out.println("차를 이동합니다");
}
// 추가
public void openDoor() {
System.out.println("문을 엽니다.");
}
}
// 부모 class
→ 부모 class 를 상속 받아 move 메서드를 재정의 🔽
package extends1.overriding;
public class ElectricCar extends Car {
@Override // <- 오버라이딩 했다는 표시 필수
public void move() {
System.out.println("전기차를 빠르게 이동합니다");
}
public void charge() {
System.out.println("충전 합니다.");
}
}
// 자식 class
→ 이런 식으로 자식 class 에서 재정의 하여 사용 가능하다.
메서드 오버라이딩시 구조
→ 자식class 에서 부모 class 의 기능을 overriding 하면 다음과 같은 구조를 갖는다.🔽
→ ElectricCar 객체를 생성시켜보면 🔽
ElectritcCar electricCar = new ElectricCar();
electricCar.move();
→ 다음과 같은 객체 메모리 영역에 부모 class 와 자식 class 구역이 생성 되고 move() 메서드도 동일 하게 생성 된다.🔽
→ 자식 class 를 호출 했기 때문에 자식 move() 메서드를 먼저 탐색하고 존재 한다면 자식의 기능을 사용하고 부모 타입은 찾지 않는다.
추가 - 오버로딩, 오버라이딩의 차이
오버로딩 : 메서드의 이름은 갖지만 매개변수의 타입, 개수에 따라 다른 기능을 호출 하여 사용 하는 것을 오버로딩 이라고 한다.
오버라이딩 : 자식 class 가 부모 class 의 기능을 재정의 하여 자식의 기능으로 덮어 씌우는 것을 말한다.
메서드 오버라이딩 조건
메서드 이름이 동일
메서드 의 파라미터 타입, 순서, 개수가 동일
반환 타입이 동일
상위 메서드 보다 하위 메서드보다 더 제한되면 안된다. ex) 상위 [ protected ] , 하위 [ public, protected ] O 상위 [ protected ] , 하위 [ private , default ] X
하지만 지금은 변수에 붙는 final 키워드만 배우고 나머지는 상속을 배운 다음에 들을 예정
fianl 변수
final 키워드는 이름 그래도 끝! 이라는 뜻
변수에 final 키워드가 붙으면 한번 값이 들어간 후에는 더는 값을 변경할 수 없다.
→ 지역변수에 fianl 을 사용한 경우 🔽
package final1;
public class FinalLocalMain {
public static void main(String[] args) {
// final 지역 변수
final int data1;
data1 = 10; // 초최 한번만 할당 가능
// data1 = 20; // 컴파일 오류 발생
// final 지역 변수2
final int data2 = 10;
// data2 = 20; // 컴파일오류
}
}
→ 메서드의 매개변수에 final 을 사용한 경우 🔽
package final1;
public class FinalLocalMain {
public static void main(String[] args) {
// 매개변수 주기
method(10);
}
// final 을 배개변수로 사용할때
static void method(final int parameter) {
// 인자로 전해 받기 때문에 무조건 초기값이 있다.
// parameter = 20; // 컴파일 오류
}
}
→ 클래스의 초기 값이 없는 멤버 변수에 final 을 사용한 경우 꼭 생성자로 초기 값을 지정해주어야 한다.🔽
package final1;
public class ConstructInit {
final int value; // <- 초기 값이 없는 멤버변수 선언
public ConstructInit(int value) {
this.value = value; // <- 생성자 호출시 인자 값으로 초기화
}
}
// 메서드 final 호출이랑 비슷해 보이네?? 생성자도 메서드여서 그런가??
(생성자 초기화 방식)
→ 클래스의 초기 값이 선언된 멤버 변수의 final을 사용한 경우 생성자로 값 설정을 못받는다.🔽
package final1;
public class FieldInit {
final int value = 10; //<-이미 초기 값이 주어진 경우
// 생성자로 초기값 선언시 컴파일 오류를 발생 시킨다🔽
// public FieldInit(int value) {
// this.value = value;
// }
}
(필드 초기화 방식)
→ 이런식으로 필드 초기화를 하게 되면 동일한 값을 객체 생성시 마다 불러와 메모리를 낭비하게 된다.
이렇게 중복인 메모리 낭비를 막기위해 사용하면 좋은 것이 static 을 사용해 메서드 영역에서 관리 시키는 것이 좋다. 이런 static 과 fianl을 같이 사용한 방식을 사용한 변수를 상수 라고 한다.
상수
변하지 않는 값
변수 앞에 static final 이 같이 붙은 변수를 상수라고 한다.
상수 변수 설정 규칙으로는 모두 대문자로 작성해주고 단어마다 언더라인 으로 구분해준다. EX) static final int *CONST_VALUE* = 10; 이런식으로 사용한다.
static - fianl
변수를 상수로 만들어주기 위해 변수 앞에 두 개를 같이 사용한다
이렇게 사용하면 공용 변수 이면서 바뀌지 않는 변수가 된다.
package final1;
public class FieldInit {
static final int CONST_VALUE = 10;
// 상수 사용
}
package final1;
public class FinalFieldMain {
public static void main(String[] args) {
// 상수
System.out.println("상수");
System.out.println(FieldInit.CONST_VALUE);
}
}
// 상수는 static (정적)변수 이므로 호출 시에도 class 명으로 바로 호출 한다.
자바가 끝날때까지 상수는 변함없이 값을 유지하고
메서드 영역에서 관리 하기 때문에 메모리 낭비를 막을 수 있다.
fianl 상수
상수는 변하지 않고 일정한 값을 갖는 수를 말한다.
static final 두 키워드를 같이 붙여 사용한다.
대문자를 사용하고 구분은 언더바로 구분 한다.
상수는 기능이 아니기 때문에 필드를 직접 접근하여 사용한다.
→ 고정 된 값을 사용하는 예 🔽 (PI, 하루 총 시간 등등)
package final1;
public class Constant {
// 수학 상수
public static final double PI = 3.14;
// 시간 상수
public static final int HOURS_IN_DAY = 24;
public static final int MINUTES_IN_HOURS = 60;
public static final int SECONDS_IN_MINUTE = 60;
// 애플리케이션 설정 상수
public static final int MAX_USERS = 1000;
}
// 이런식으로 사용할 수 있다.
상수들은 거의 애플리케이션전반에 사용 되기 때문에 public 을 주로 접근 제한자로 사용한다.
상수를 변경하려면 프로그램을 종료하고 → 코드를 변경 해야 한다.
final 변수와 참조
final 은 변수의 밧을 변경하지 못하게 막는다.
변수는 기본형과 참조형으로 나뉘는데
final 기본형 변수는 → 값을 변경할 수 없다.
final 참조형 변수는 → 참조값을 변경할 수 없다.
→ final 참조형 변수의 특징 🔽
package final1;
public class Data {
public int value;
}
// final 이 아닌 멤버 변수를 갖는 class 를 만들고
package final1;
// 해당 클래스를 사용하는 class 를 보자
public class FinalRefMain {
public static void main(String[] args) {
final Data data = new Data(); // 참조형
//data = new Data();
// 참조형 을 이미 final 을 설정 했기 때문에 새로운 객체를 참조할 수없다.
// 그러나, 참조 대상의 값은 final 이 아니기때문에 변경 가능하다.
data.value = 10;
System.out.println(data.value);
data.value = 20;
System.out.println(data.value);
}
}
→ 결론 참조하는 대상 자체를 다른 대상으로 변경 못 할 뿐 해당 대상의 값은 변경 가능하다.
package static2;
public class DecoUtil1 {
public String deco(String str) {
String result = "*" + str + "*";
return result;
}
}
→ DecoUtil1 이라는 클래스에 deco 메서드만 있다고 가정 하고 이 기능만 사용하는 클래서가 있다고 보면.
package static2;
public class DecoMain1 {
public static void main(String[] args) {
String s = "hello Java";
DecoUtil1 utils = new DecoUtil1();
String deco = utils.deco(s);
System.out.println("before: " + s );
System.out.println("after: " + deco );
String s2 = "hello world";
DecoUtil1 utils2 = new DecoUtil1(); // <- 또 동일한 기능을 하는 객체를 생성 해야 한다.
String deco2 = utils2.deco(s);
System.out.println("before: " + s2 );
System.out.println("after: " + deco2 );
}
}
→ 동일한 기능을 사용하기 위해 매번 객체를 새로 생성해주는 번거로움이 발생한다.
→ 이렇게 매번 단순한 동일 작업(메서드)을 생략하기 위해 static 을 메서드에 붙여준다.
static 메서드를 사용한 예 🔽
package static2;
public class DecoUtil2 {
public static String deco(String str) { // <- 메서드에 static을 붙여준다.
String result = "*" + str + "*";
return result;
}
}
package static2;
public class DecoMain2 {
public static void main(String[] args) {
String s = "hello Java";
String deco = DecoUtil2.deco(s);
System.out.println("before: " + s );
System.out.println("after: " + deco );
String b = "static Method";
String deco2 = DecoUtil2.deco(b);
// 2번째 객체 생성없이 바로 클래스 명에 메서드 호출이 가능해진다.
System.out.println("before: " + b);
System.out.println("after: " + deco2);
}
}
→ 이렇게 사용하면 불필요한 객체 생성 없이 편리하게 메서드 사용이 가능해진다.
메서드의 종류
클래스 메서드 : 메서드 앞에 staitc 이 붙은 형태로, 정적 메서드, 클래스 메서드라고 부른다. 인스턴스 생성 없이 마치 클래스에 있는 메서드를 바로 호출 하는 것 처럼 느껴진다.
인스턴스 메서드 : static이 붙지 않은 메서드로 항상 인스턴스(객체)를 생성해야 호출할 수 있다.
static 메서드 사용 시 주의 사항
static 메서드는 static 이 선언된 것만 사용 가는하다
staitc 메서드가 class 내부의 기능을 사용할때에 → static 메서드는 static 이붙은 메서드나, 변수 만 사용 가능하다.
static 메서드는 인스턴스 변수, 인스턴스 메서드를 사용할 수 없다.
접근 제어자가 허용하는 범위 내에서 클래스를 통해 staic변수,메서드를 호출할 수 있다.
static 메서드 결론
static 붙은 것만 staitc메서드에서 사용 가능 하고 stiatc 메서드 자신은 접근제한자 범위 내에서 마음껏 사용될 수 있다.
static 메서드 사용 범위 예 🔽
package static2;
public class DecoData {
private int instanceValue;
private static int staticValue;
public static void staticCall() { // <- 정적 메서드
// instanceValue++; // 인스턴스 변수 접근, compile error
// instanceMethod(); // 인스턴스 메서드 접근, compile error
staticValue++; // 정적 변수 접근 가능
staticMethod(); // 정적 메서드 접근 가능
}
public void instanceCall() { // <- 인스턴스 메서드
instanceValue++; // 인스턴스 변수 접근 가능
instanceMethod(); // 인스턴스 메서드 접근 가능
staticValue++; // 정적 변수 접근 가능
staticMethod(); // 정적 메서드 접근 가능
}
private void instanceMethod() { // <- private 인스턴스 메서드
System.out.println("instanceValue = " + instanceValue);
}
private static void staticMethod() { // private 정적 메서드
System.out.println("staticValue = " + staticValue);
}
}
→ 해당 코드를 작동 시켜 보면
package static2;
public class DecoDataMain {
public static void main(String[] args) {
System.out.println("1. 정적 호출");
DecoData.staticCall();
System.out.println("2. 인스턴스 호출1");
DecoData data1 = new DecoData();
data1.instanceCall();
System.out.println("3. 인스턴스 호출2");
DecoData data2 = new DecoData();
data2.instanceCall();
}
}
staticValue = 1 // <- static 변수는 메서드 영역에서 관리되므로 호출 하면 계속 증가 될것
2. 인스턴스 호출1
instanceValue = 1 // <- instance 변수는 힙 영역에 각각 생성 되므로 호출해도 계속 1 유지
staticValue = 2
3. 인스턴스 호출2
instanceValue = 1
staticValue = 3
// 다음과 같이 출력된다.
→ 그림으로 보면 다음과 같다 🔽
static 메서드의 용어 정리
클래스에 정의한 메서드 (맴버 메서드)의 종류
인스턴스 메서드 : static이 안 붙은 것 → 객체(인스턴스)를 생성해야 사용할 수 있다.
class 메서드(static 메서드) : static이 붙은 것 → 객체생성하지 않고 바로 class 에 접근 해 사용 가능하다.
package static1;
public class Data3 {
public String name;
public static int count; // <- 이런식으로 맴버 변수에 붙여 사용한다.
public Data3(String name) {
this.name = name;
count++;
}
}
멤버 변수에 staitc 을 붙이면 static변수, 정적 변수, 클래스 변수라 부른다.
static 변수에 접근하는 방식은 객체를 저장한 변수가 아닌 클래스 명을 호출하여 사용한다.
package static1;
public class DataCountMain3 {
public static void main(String[] args) {
Data3 data1 = new Data3("A");
System.out.println("A count = " + Data3.count); // <- class 명 호출
Data3 data2 = new Data3("B");
System.out.println("B count = " + Data3.count); // <- class 명 호출
Data3 data3 = new Data3("C");
System.out.println("C count = " + Data3.count); // <- class 명 호출
}
}
A count = 1
B count = 2
C count = 3
// 실행결과는 다음과 같다.
static 이 붙은 변수는 힙 영역이 아닌 메서드 영역에서 메서드와 같이 관리된다.
최종적으로 위 코드가 작동 되면 다음과 같은 상태가 된다.
그래서 static 변수에 접근하려면 메서드 영역에 접근 해야 하기 때문에 Data3.count 로 클래스에 직접 들어가야하는것 이다.
staitc 변수 사용 장점
static 변수를 사용함으로써 공용 변수를 사용해 편리하게 문제를 해결 할 수 있다.
static 변수 정리
static 변수는 클래스인 붕어빵 틀이 특별히 관리하는 변수인 붕어빵이다.
붕어빵 틀(클래스)은 1개 이므로 붕어빵(static 변수)도 1개만 존재한다.
반면 객체(인스턴스)로 생성한 변수들 (맴버 변수)은 객체를 생성한 만큼 변수가 존재하게 된다.
변수와 생명주기
지역변수(매개변수 포함) : 스택 영역안에서만 활동 하고, 메서드가 종료되면 메서드 스택 프레임이 제거되고, 변수도 함께 제거 된다. → 가장 짧은 생명 주기
인스턴스 변수 : 인스턴스에 있는 멤버 변수를 인스턴스라 하고, 인스턴스 변수는 힙 영역을 사용한다. CG가 발생하기 전까지 생존해 있다. → 중간 생명 주기
클래스 변수 : 클래스 변수는 메서드 영역의 static 영역에 보관된다, 해당 class 가 JVM에 로딩 되는 순간 생성 되고, JVM이 종료되면 제거된다. → 가장 긴 생명 주기
static 변수가 정정 변수라고 하는 이유 ?
힙 영역에 생성되는 인스턴스 변수는 동적으로 생성되고, 제거된다 반면 staitc 변수는 거의 프로그램 실행 시점에 딱 만들어지고, 프로그램 종료 시점에 제거된다. → 이런 특성 때문에 정적 변수라고 부른다.
static 변수 추가 사항
static 변수를 인스턴스 접근이 가능하나 권장하진 않는다.
// 추가
// 인스턴스를 통한 접근 (비 권장)
Data3 data4 = new Data3("D");
System.out.println("D count = " + data4.count);
이유는 가져다 쓰는 사람 입장에서 힙 영역의 인스턴스 변수라 착각 할 수도 있기 때문이다.