ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [19회차] JAVA (상속)
    JAVA 2021. 4. 17. 20:26

    강의명 : 오픈프레임워크 활용 디지털융합 SW엔지니어 양성 과정

    강의 날짜 : 21.04.16

     

    <상속>

     

     

    1. 클래스의 상속과 오버라이딩(자동은폐)

     

    class B1 {
    	int x; //객체 변수 x
    }
    class B2 extends B1{ //상속 키워드 extends
    	String x; //객체 변수 x
    }
    class Ex01 {
    
    	public static void main(String[] args) {
    		
    		B2 b2 = new B2(); //객체 생성
    		b2.x = "자바 상속 extends";
    		
    		System.out.println("b2.x : "+b2.x);
    		B1 b1 = new B1();
    		b1.x = 5000;
    		System.out.println("b1.x : "+b1.x);
    	}
    }

     

    extends 는 상속 키워드로 동격의 자료형을 받을 때는 extends를 사용한다.

    즉 위의 예제는 클래스가 클래스를 상속 받았으므로 extends를 사용한다.

     

    상속을 당하는 클래스는 상위 클래스=슈퍼(super) 클래스=부모 클래스라고 하고,

    상속이 되는 클래스는 하위 클래스=서브(this) 클래스=자식 클래스라고 한다.

    자바 클래스는 단일 상속만 가능하다. 

     

    main 함수에서 자료형이 B2인 객체 b2를 생성하고 메모리를 할당받는다.

    메모리를 할당 받을 때, B2 클래스는 B1 클래스를 상속받았으니 B1 클래스만큼의 메모리를 먼저 할당받고,

    그 후에 B2 클래스만큼의 메모리를 또 할당 받는다.

    B1 클래스에서 정의한 속성과 기능에 추가적으로 B2 클래스에서 정의한 속성과 기능을 다 가지고 있는것이다.

     

    B2의 경우는 String x 라는 속성을 정의해 두었는데 이것은 문자열 객체를 정의해 둔 것이다.

    객체는 관리할 메모리의 주소값을 가지게 되는데 이 String x는 초기화된 적이 없으므로 NULL값을 가지게 된다.

     

    그런데, B1 클래스에서 정의한 변수명과 B2 클래스에서 정의한 변수명이 x로 같게 되면,

    자동적으로 B1클래스의 int형 x는 오버라이딩(자동 은폐)되어서 객체 b2가 보지 못하게 된다.

    즉, b2에게 x는 B2 클래스에서 정의한 String x만 보이게 되는 것이다.

     

    만약 B1에서 정의한 int x에 접근하고 싶다면 자료형인 B1인 객체를 생성하여 접근하여야 한다.

    그래서 문자열 형식으로 접근할 때는 b2객체로, 정수형으로 접근할 때는 b1객체로 접근하는 것이다.

     

    * 클래스 다이어그램 (관계도)

    상위 클래스
    =슈퍼 클래스
    =부모클래스

                 

    하위 클래스
    =서브 클래스
    =자식 클래스

    화살표의 Head가 향하는 곳이 부모 클래스이다.

     

     

    2. static과 함수

     

    class C1 {
    	static int x; //클래스 변수
    	static int y; //클래스 변수
    }
    class C2 extends C1 { //상속
    	static String x; //클래스 변수
    }
    class Ex02 {
    
    	public static void main(String[] args) {
    	
    	C2.x = "알기쉽게 해석한 자바";	
    	C2.y = 20000;
    	C1.x = 30000;
    	System.out.println("클래스 변수 C2.x : "+C2.x);
    	System.out.println("클래스 변수 C2.y(C1상속) : "+C2.y);
    	System.out.println("클래스 변수 C1.x : "+C1.x);
    	System.out.println("클래스 변수 C1.y : "+C1.y);
        
    	C1.y=1;
    	System.out.println(C2.y);	
    	}
    }

    C1클래스와 C2클래스에서 정의한 변수들은 모두 클래스(static)변수로 객체 생성을 하지 않고도 클래스 명으로 접근이 가능한 변수들이다. 그래서 main 메소드에서도 클래스 명으로 바로 접근을 하고 있다.

     

    클래스 C2는 C1을 상속받았는데 이때 C2 고유의 String x와 C1클래스에서 정의한 int x의 변수명이 같으므로,

    int x가 오버라이딩(자동은폐) 되어 C2 클래스가 접근을 하려고 할때 보이지 않게 된다.

    만약 int x에 접근을 하려면 C1 클래스명으로 접근을 해야하는 것이다.

     

    하지만 static의 경우는 값의 변경에 주의해야 할 점이 있다.

    C2 클래스명으로 y에 접근하여 30000으로 초기화가 된 후,

    C1 클래스명으로 y에 접근하여 1로 초기화를 하면 C2로 접근한 y의 값이 1로 바뀌게 된다.

    이것이 바로 static의 큰 특징 데이터 공유이다.

     

    상속이라는 것은 부모 클래스의 메모리를 복사하여 자식 클래스가 가져다 쓰는 것인데

    static은 데이터 공유, 메모리 공유라는 특징으로 고유의 저장 공간이 있다.

    그러므로 위의 예제에서는 부모 클래스를 복사하여 가져오는 개념이 성립되지 못한 것이다.

     

    class AA { //부모 클래스
    	int i,j; //객체 변수
    	public void setij(int x, int y) { //객체 메소드
    		i=x;
    		j=y;
    	}
    }
    class BB extends AA{ //자식 클래스
    	int total ; //객체 변수
    	public void sum() { //객체 메소드
    		total = i + j;
    	}
    }
    
    public class Ex03 {
    
    	public static void main(String[] args) {
    		
    		BB subOb = new BB(); //객체 생성
    		subOb.setij(10, 12);
    		subOb.sum();
    		System.out.println("합계 : "+subOb.total);
    	}
    }

     

    클래스 AA와 AA를 상속받은 클래스 BB간에는 중복되는 변수명이나 메소드가 존재하지 않으므로,

    객체 subOb로 다 접근이 가능하다.

     

    다들 static이 아니기 때문에 메모리를 공유하지 않고, BB클래스는 AA클래스의 복사본을 가지고 있는 것이다.

     

     

    3. 오버로딩(중복 정의)과 상속

     

    class Pa {
    	void show(String str) { //객체 메소드
    		System.out.println("상위클래스 show(String str) "+ str);
    	}
    }
    
    class Ch extends Pa { //상속
    	void show() { //오버로딩
    		System.out.println("하위클래스 show()");
    	}
    }
    
    public class Ex04 {
    
    	public static void main(String[] args) {
    	
    		Ch over = new Ch();
    		over.show("상속 오버로딩");
    		over.show();		
    	}
    }

     

    Pa클래스를 상속받은 Ch 클래스에는 Pa와 같은 show라는 메소드명을 가지고 있다.

    하지만 매개변수의 개수가 다르다. 이는 오버로딩의 규칙 세 가지 중 하나를 충족하기 때문에 오버로딩이 가능한 경우이다. 즉, 오버로딩은 규칙만 지켜준다면 상속과 상관없이 일어날 수 있다.

     

     

    * 오버로딩 규칙을 지켜주지 않은 경우

     

    class Pa1 {
    	void show() { //객체 메소드
    		System.out.println("상위클래스 show() 오버라이딩");
    	}
    }
    
    class Ch1 extends Pa1 { //상속
    	void show() { //객체 메소드
    		System.out.println("하위클래스 show()");
    	}
    }
    
    public class Ex05 {
    	public static void main(String[] args) {
    		Ch1 over = new Ch1(); //객체 생성
    		over.show();		
    	}
    }

     

    Pa1을 상속받은 Ch1에는 Pa1과 같은 show메소드가 존재하는데, 오버로딩의 규칙을 지키고 있지 않다.

    그런 경우에는 over라는 개체가 보지 못하도록 오버라이딩(자동은폐) 되어, over로 접근이 불가하도록 한다.

     

     

    4. this와 super

     

    class Pa3 {
    	int x =1000; //객체 변수
    	void display() { //객체 메소드
    		System.out.println("상위클래스 display()");
    	}
    }
    
    class Ch3 extends Pa3 { //상속
    	int x = 2000; //객체 변수
    	void display() { // 객체 메소드
    		System.out.println("하위 클래스 display()");
    	}
    	void write() { //객체 메소드
    		this.display();
    		super.display();
    		System.out.println("this.x : "+x);
    		System.out.println("super.x : "+super.x);
    	}
    }
    
    class Ex06 {
    	public static void main(String[] args) {
    		Ch3 d = new Ch3(); //Ch3 자료형 객체 생성
    		d.write();
    	}
    }

     

    Ch3 클래스는 Pa3 클래스를 상속받는데, 변수명과 메소드명이 동일하고 오버로딩 규칙도 지키지 않았다.

    그러므로 Pa3의 변수와 메소드는 객체 d가 보지 못하도록 오버라이딩(자동은폐)되었다.

     

    하지만 만약 Pa3클래스의 변수와 메소드에 접근하고 싶을 때는 어떻게 해야할까?
    superthis 키워드로 해결할 수 있다.

     

    Ch3 클래스의 객체 메소드 write()에서 정의된 내용을 보면 this와 super가 나온다.

    this는 자기 자신의 객체를 의미하므로 객체 d이다.

    반면 super는 자신의 부모 클래스를 뜻하므로 Ch3의 부모 클래스 Pa3을 의미하여,

    Pa3 클래스의 메소드나 변수를 호출한다.

     

    즉, 새로운 객체를 생성하여 해결하려고 하는 것이 아니라 그 클래스 안에서 해결하기 위해서 

    this나 super를 사용하는 것이다.

     

    다만 super는 바로 한 단계위의 클래스만을 지칭하는 것이지 더 위의 클래스로는 접근하지 못한다. (단일 상속)

    만약 Pa3이 다른 어떤 클래스를 상속받은 클래스라면 Ch3클래스에서는 super로는 그 클래스로 접근이 불가능하다.

     

     

    5. 생성자와 상속

     

    class AA1 {
    	double d1;
    	AA1() { //AA1의 생성자
    		System.out.println("클래스 AA1 생성자");
    		d1=10*10;
    	}
    }
    
    class AA2 extends AA1 { //상속
    	double d2 ;
    	AA2(){ //AA2의 생성자
    		System.out.println("클래스 AA2 생성자");
    		d2=10*10*10;
    	}
    }
    
    class AA3 extends AA2 { //상속
    	double d3 ;
    	AA3(){ //AA3의 생성자
    		System.out.println("클래스 AA3 생성자");
    		d3=10*10*10*10;
    	}
    }
    
    public class Ex07 {
    
    	public static void main(String[] args) {
    		
    		AA3 super1 = new AA3(); //객체 생성와 생성자 호출
    		System.out.println("10의 2제곱 : "+super1.d1);
    		System.out.println("10의 3제곱 : "+super1.d2);
    		System.out.println("10의 4제곱 : "+super1.d3);
    	}
    }

     

    main함수에서 AA3자료형을 가진 super1 객체를 생성하고 생성자 AA3()를 호출한다.

    생성자는 자신의 가장 상위 클래스부터 해당 클래스까지 순서대로 호출된다.

     

    상속을 받은 경우는 부모 클래스 메모리를 복사하여 할당받는데, 생성자는 복사되지 않는다.

    static과 비슷하게 공통구역에 놓고 사용한다.(메모리 공유)

     

    * 디폴트 생성자

    매개변수도, 초기화 실행문도 없는 생성자를 의미한다. (빈 소괄호, 빈 중괄호)

    이것이 눈에는 보이지 않아도 생성자를 따로 정의하지 않았다면 자바 알아서 번역할 때 적어준다.

    그래서 객체를 생성할 때 안보이는 생성자를 호출해도, 실상은 있는 것이니 자동 초기화가 되는 것이다.

     

    하지만 만약 생성자가 오버로딩되면 디폴트 생성자를 써주어야 한다.

    버전이 올라가면서 상관없어졌지만 써주는 버릇을 들이는 것이 좋다.

     

     

    6. 생성자 재호출

     

    부모 클래스의 생성자가 매개변수를 단 하나라도 가지고 있다면

    무조건 부모클래스의 생성자를 하위에서 호출하는 작업이 필수이다.

     

    class One {
    	int d1,s ;
    	One (int s1){ //부모 클래스의 생성자
    		s=s1;
    		d1=s*s;
    	}
    }
    
    class Two extends One { //상속
    	int d2,t;
    	Two(int s1, int t1){ //자식 클래스의 생성자
    		super(s1); //부모클래스 생성자 재호출
    		t=t1;
    		d2=t*t;
    	}
    }
    
    public class Ex08 {
    	public static void main(String[] args) {
    		Two super2 = new Two(10,20); //객체 생성과 생성자 호출
    		System.out.println("10의 제곱 : "+super2.d1);
    		System.out.println("20의 제곱 : "+super2.d2);
    	}
    }

     

    main함수에서 Two 자료형의 객체 super2를 생성하고 Two의 생성자를 호출하는데 매개변수를 10과 20을 주었다.

    생성자가 호출되면 가장 상위 클래스의 생성자부터 순차적으로 호출되므로

    Two의 부모 클래스인 One클래스의 생성자가 먼저 호출이 된다.

     

    One클래스의 생성자는 매개변수를 하나만 받으므로 10을 받고 20은 버린다.

    그래서 s에 10을 대입하고, d1에는 100이 저장된다.

     

    그 후 Two 클래스의 생성자가 호출되는데 필요한 매개변수의 개수가 두 개이므로 10과 20을 모두 받는다.

    그리고 그 안에서 부모 클래스에게 매개변수 하나를 주고 생성자를 재호출한다.

     

    이 작업은 부모 클래스의 생성자가 제대로 멤버 변수를 초기화했는지 확인하는 절차로 필수이다.

    다만 재호출할때 다른 값을 주면 초기화된 부모 클래스의 멤버 변수가 다른 값을 덮어쓰는 경우가 존재하므로 유의해야한다.

    이렇듯 상속의 경우에는 표면적인 호출만 하는 것이 아니기 때문에 그 안에서 호출이 어떻게 일어나고 있는지 파악하는 것이 중요하다.

     

     

    7. instance of

     

    class AB1{
    	int i,j;
    	public AB1() {}	
    }
    
    class AB2 extends AB1{
    	int k;
    }
    
    class AB3 extends AB2{
    	int l;
    }
    
    public class Ex10 {
    	public static void main(String[] args) {
    
    		AB1 a=new AB1();
    		AB2 b=new AB2();
    		AB3 c=new AB3();
    		
    		if(a instanceof AB1) System.out.println("a는 AB1 클래스의 객체");
    		if(b instanceof AB2) System.out.println("b는 AB2 클래스의 객체");
    		if(c instanceof AB3) System.out.println("c는 AB3 클래스의 객체");
    		if(c instanceof AB1) System.out.println("c는 AB1 클래스의 객체 : 형변환");
    		if(a instanceof AB3) System.out.println("a는 AB3 클래스의 객체 : 형변환");
    		else System.out.println("a는 AB3 클래스의 객체가 아님 : 형변환 불가");		
    	}
    }

    instance of는 포함 관계를 물어보고 있다.

     

    if(c instance of AB1)의 경우는 객체 c가 AB1 자료형을 포함하고 있니? 라는 물어보고 있으므로

    AB3 자료형인 c는 상속받은  AB1을 포함하고 있으므로 true가 된다.

    객체 c로 AB1에서 정의한 변수나 메소드 등에 접근할 수 있다는 것이다.

     

     

    8. 추상 클래스와 오버라이드(재정의)

     

    abstract class Shape {
    	public abstract void draw();
    }
    
    class Circle extends Shape { //상속
    	public void draw() { //오버라이드
    		System.out.println("원을 그리다");
    	}
    }
    
    public class Ex11 {
    	public static void main(String[] args) {
    		Shape ref; //객체 선언
    		ref = new Circle(); //메모리 할당
    		ref.draw();
    	}
    }

     

    추상 클래스는 추상메소드를 한 개 이상 가지고 있는 클래스이다.

    추상 메소드는 정의되지 않는 메소드이고, 추상메소드와 추상클래스는 앞에 abstract가 붙어서 사용된다.

    클래스이기 때문에 일반 클래스가 가지고 있는 것을 모두 가질 수 있다.

     

    추상 클래스에서 정의되지 않은 추상 메소드를 상속받은 자식 클래스가 재정의를 하는데(선택임),

    이것을 오버라이드라고 한다.

    오버라이드를 할때는 @Override 라는 어노테이션(Anotaion)을 주는데 버전이 올라가면서 없어도 오류가 안 난다.

     

    추상 클래스는 객체 생성을 할 수가 없다.

    new라는 키워드를 가질 수 없는 것이다.

     

    <단어 정리>

    * 오버로딩 : 중복 정의

    * 오버라이딩 : 자동 은폐

    * 오버라이드 : 재정의

     

     

    9. 추상메소드와 형변환

     

    abstract class Shape2 { //추상 클래스
    	public double res = 0;
    	public abstract double area(); //추상메소드
    	public void printArea() {
    		System.out.println("면적은 "+res);
    	}	
    }
    
    class Circle1 extends Shape2{
    	public int r =5;
    	public double area() { //오버라이드
    		res = r*r*Math.PI;
    		return res;
    	}
    }
    
    class Rectangle extends Shape2 {
    	public int w =10,h =10;
    	public double area() { //오버라이드
    		res=w*h;
    		return res;
    	}
    }
    
    public class Ex12 {
    	public static void main(String[] args) {
    		Shape2 ref = null;
    		ref = new Circle1();
    		ref.area();
    		ref.printArea();
    		ref = new Rectangle();
    		ref.area();
    		ref.printArea();
    	}
    }

    여러 자식 클래스들이 하나의 부모 클래스(추상클래스)를 상속받은 상황이다.

    자식 클래스들은 부모 클래스의 추상 메소드를 오버라이드(재정의)해주었다.

     

    main함수에서 부모클래스를 자료형으로 객체를 선언하여 여러 자식 클래스들로 자료형변환을 하고 있다.

    이렇게 하나의 객체를 선언해서 상속받은 여러 자료형으로 형 변환이 가능한 것을 다형성이라고 한다.

     

    그래서 오버라이딩을 해결할 수 있는 방법에는 두 가지가 있다.

    첫 번째는 super를 사용하는 방법,

    두 번째는 오버라이드인 것이다. (그냥 선언만 하고 하위에서 재정의해!)

     

     

    10. 인터페이스

     

    interface Drawable {
    	public abstract void draw();
    }
    
    class Circle2 implements Drawable {
    	public void draw() {
    		System.out.println("원을 그리다");
    	}
    }
    
    public class Ex13 {
    	public static void main(String[] args) {
    		Drawable ref;
    		ref = new Circle2();
    		ref.draw();
    	}
    }

     

    인터페이스는 자료형의 하나로 추상 클래스보다도 규격이 더 강하다.

    인터페이스는 클래스가 가질 수 있는 그 어느 것도 가질 수가 없다.

    객체 변수, 메소드 정의, 생성자 등등 못 오는 것이다.

     

    단, final 상수abstract 메소드만 가질 수 있다.

    final 상수는 접근 권한자가 무조건 public static인데 생략이 되도 public static으로 사용 가능하다.

    그리고 어차피 추상 메소드만 가질 수 있기 때문에 abstract도 생략이 가능하다.

     

    JAVA에서 다중상속이 되는 것처럼 보이게 하기 위해서 만들어진 개념이다.

    JAVA는 무조건 단일 상속이기 때문에!!

    어차피 오버로딩이 안 되서 오버라이딩이 될 것이라면,

    껍데기만 남아있는 애들 가져다가 오버라이드해서 사용하기위해 인터페이스가 생겨났다.

    즉, 상수 데이터 공유하고 메소드 활요하라고 만들어진 개념인 것이다.

     

    implements는 동격 상속이 아닐 경우에 사용되는데, 클래스가 인터페이스를 상속받을 때의 경우밖에 존재하지 않는다.

     

    단, 인터페이스를 상속받은 클래스는 반드시 추상 메소드를 오버라이드(재정의)해야만 한다.

     

    그리고 추상메소드와 마찬가지로 객체를 생성할 수 없다. (선언만 가능)

    여러 자료형으로 사용하라는 다형성을 위한 것이기 때문이다.

     

     

    11. 다중상속

     

    interface Drawable1 { //인터페이스
    	public abstract void draw();
    }
    
    abstract class Shape3 { //추상 클래스
    	public double res = 0;
    	public abstract double area();
    	public void printArea() {
    		System.out.println("면적은 "+res);
    	}
    }
    
    class Rectangle1 extends Shape3 implements Drawable1 { //다중상속(처럼보임)
    	public int w =10,h =10;
    
    	public void draw() { //오버라이드
    		System.out.println("사각형을 그리다");
    	}
    	@Override
    	public double area() { //오버라이드	
    		res=w*h;
    		return res;
    	}	
    }
    
    class Circle3 implements Drawable1 {
    	public void draw() { //오버라이드
    		System.out.println("원을 그리다");
    	}
    }
    
    public class Ex14 {
    	public static void main(String[] args) {
    		Rectangle1 ref=null; //객체 선언
    		ref = new Rectangle1();
    		ref.area();
    		ref.printArea();
    		ref.draw();
    	}
    }

     

    Rectangle1은 추상클래스 Shape3을 extends(상속)받고, 인터페이스 Drawable을 implements(상속)받았다.

    다중 상속처럼 보이게 한 것이지 다중상속이 아니다.

    JAVA 클래스는 무조건 단일상속!

     

    인터페이스와 추상 클래스가 가지고 있던 추상 메소드를 오버라이드(재정의) 해주었다.

    이때 어노테이션이 있는 재정의는 추상클래스 메소드 재정의이고,

    어노테이션이 없는 재정의는 인터페이스 메소드 재정의인데, 인터페이스의 추상메소드는 재정의는 필수이기 때문이다.

     

     

    * 인터페이스의 다중 상속

     

    interface A2 {
    	void ameth1();
    	void ameth2();
    }
    interface B {
    	void bmeth1();
    }
    interface C extends A2,B { //인터페이스 다중상속
    	void cmeth1();
    }
    class InterfaceClass implements C{
    	public void ameth1() { //오버라이드
    		System.out.println("ameth1() 메소드의 구현");
    	}
    	public void ameth2() { //오버라이드
    		System.out.println("ameth2() 메소드의 구현");
    	}
    	public void bmeth1() { //오버라이드
    		System.out.println("bmeth1() 메소드의 구현");
    	}
    	public void cmeth1() { //오버라이드
    		System.out.println("cmeth1() 메소드의 구현");
    	}
    }
    public class Ex17 {
    	public static void main(String[] args) {
    		InterfaceClass ic = new InterfaceClass(); //객체 생성
    		ic.ameth1();
    		ic.ameth2();
    		ic.bmeth1();
    		ic.cmeth1();
    	}
    }

     

    JAVA의 클래스는 다중 상속이 불가능하여 extends 옆에 , 가 올 수 없다.

    하지만 implements 뒤에는 인터페이스가 나열될 수 있고, 인터페이스는 여러 개 상속이 가능하다.

    물론 다중상속처럼 보이는 현상이다.

     

     

    12.  접근 불가의 경우

     

    interface A {
    	final int CONS = 5;
    	public void display(String s);
    }
    
    class A1 implements A{
    	int a = 10;
    	public void display(String s) {
    		System.out.println("display 메소드 구현"+s);
     }
    }	
    	
    public class Ex16 {
    	public static void main(String[] args) {
    		A ob = new A1();
    		ob.display("테스트1");
    		System.out.println("A의 상수값은 "+ob.CONS);
    		
    		A1 ob2=new A1();
    		ob2.display("테스트2");
    		System.out.println("A의 상수 값은 "+ob2.CONS);
    		System.out.println("A1의 a 값 출력"+ob2.a);
    	}
    }

     

    인터페이스 A를 자료형으로 하는 객체 ob를 만들어 A1을 참조하게 한다.

     

    이 경우 ob로는 A1클래스의 변수 a에 접근할 수 없다.

    이유는 A 인터페이스는 A1 클래스의 변수a를 포함하고 있지 않기 때문이다.

    그렇기 때문에 인터페이스 A 자료형 안에 a를 담을 수 없다.

     

     

    13. protected

     

    접근 권한 지정자 접근 범위
    public 어디서든 누구나 
    default 해당 패키지 내
    private 해당 클래스 내
    protected 상속용

    protected는 상속용이다.

    default와 비슷하게 해당 패키지 외부에서는 접근하지 못한다.

    하지만 상속받은 자식 클래스들이 패키지 외부에 있다면, 자식들은 접근이 가능하다.

Designed by Tistory.