[LG CNS AM CAMP 1기] 백엔드 I 2 | Java

letthem·2025년 1월 10일
0

LG CNS AM CAMP 1기

목록 보기
12/31
post-thumbnail

참조 자료형

배열

동일한 자료형을 묶어 저장하는 참조 자료형
생성할 때 크기를 지정해야 하고, 한 번 크기를 지정하면 절대 변경할 수 없는 특징이 있음

  ⬇️ 보통 이렇게 쓴다
자료형[] 변수명; 또는 자료형 변수명[];
int[] a;          int a[];
String[] b;       String b[];


null 이라고 하면 힙 메모리에 값이 없다는 것을 명시적으로 나타낸다.

// 선언과 동시에 객체를 생성
int[] a = new int[3];
~~~~~ ~   ~~~ ~~~~~~
|     |    |     |
|     |    |     +-- int 자료형 3개를 저장할 수 있는 공간을
|     |    +-- 힙 메모리에 넣어라
|     +-- 참조 변수
+-- int 배열 자료형

// 선언 이후에 객체를 생성
int[] a;
a = new int[3];

1차원 배열의 생성 및 값 대입

방법1. 배열 객체의 생성 + 값 대입 🥵

int[] a = new int[3];
a[0] = 3;
a[1] = 4;
a[2] = 5;

방법2. 배열 객체의 생성 및 값 대입 👍

int[] a = new int[] { 3, 4, 5 };
              ~~~~~ ~~~~~~~~~~~
              |     |
              |     +-- 배열의 길이는 값의 개수로 결정
              +-- 배열의 크기(길이)를 쓰지 않음

방법3. 대입할 값만 입력 👍👍👍

int[] a = { 3, 4, 5 };
package com.test;

import java.util.Arrays;

public class MyTest {
    public static void main(String[] args) {
        // 개별 항목에 값을 할당
        int[] i1 = new int[3];
        i1[0] = 1;
        i1[1] = 2;
        i1[2] = 3;
        System.out.println(Arrays.toString(i1));
        
        int[] i2;
        i2 = new int[3];
        i2[0] = 1;
        i2[1] = 2;
        i2[2] = 3;
        System.out.println(Arrays.toString(i1));

        // 선언과 할당을 분리할 수 있음
        int[] x1 = new int[] { 4, 5, 6 };
        int[] x2;
        x2 = new int[] { 4, 5, 6 };

        // 선언과 할당을 분리할 수 없음
        int[] y1 = { 7, 8, 9 };
        int[] y2;
        y2 = { 7, 8, 9 }; // error
    }
}

메모리 초기화

스택 메모리

  • 초기값을 부여하지 않는 경우 빈 칸으로 존재
  • 읽기 불가능
int a;
System.out.println(a); // 오류 발생

힙 메모리

  • 빈 칸으로 존재할 수 없으며, 디폴트 초기값이 강제 설정
  • 기본 자료형 => 숫자 : 0 / boolean : false
  • 참조 자료형 => null
package com.test;

import java.util.Arrays;

public class MyTest {
    public static void main(String[] args) {
        int[] a = new int[3];
        System.out.println(Arrays.toString(a)); // [0, 0, 0]
        
        boolean[] b = new boolean[3];
        System.out.println(Arrays.toString(b)); // [false, false, false]
        
        String[] c = new String[3];
        System.out.println(Arrays.toString(c)); // [[null, null, null]
    }
}
package com.test;

import java.util.Arrays;

public class MyTest {
    public static void main(String[] args) {
        int x = 10;
        int y = x;
        System.out.println(x + " : " + y); // 10 : 10
        
        x = 20;
        System.out.println(x + " : " + y); // 20 : 10
        
        int[] a = { 1, 2, 3 };
        int[] b = a;
        System.out.println(Arrays.toString(a)); // [1, 2, 3]
        System.out.println(Arrays.toString(b)); // [1, 2, 3]
        
        a[1] = 200;
        System.out.println(Arrays.toString(a)); // [1, 200, 3]
        System.out.println(Arrays.toString(b)); // [1, 200, 3]
    }
}

배열은 주소를 복사한다.


배열 데이터를 읽는 방법

방법1. for 반복문 => 배열의 크기 만큼 loop를 돌면서 인덱스로 참조

int[] a = new int[100];
for (int index = 0; index < a.length; index++) {
	System.out.println(a[index]);
}

방법2. for-each 반복문 👍 => 루프를 돌면서 배열 데이터를 가져와서 사용

int[] a = new int[100];
for (int element: a) {
	System.out.pringln(element);
}

javascript 의 for-of 구문과 유사하다 ! (값 자체를 가져오기)


main() 메서드의 입력 매개변수

public void static main(String[] args)
                        ~~~~~~~~~~~~~
                        프로그램이 실행될 때 입력되는 값 (전달되는 값)
package com.test;

import java.util.Arrays;

public class MyTest {
    public static void main(String[] args) {
        System.out.println("입력 매개변수의 개수 >>> " + args.length);

        System.out.println("입력 매개변수 >>> " + Arrays.toString(args));

        System.out.println();

        for (String arg : args) {
            System.out.println(arg);
        }
    }
}



타입 변환 메서드

문자열 -> 정수 => Integer.parseInt(문자열)
문자열 -> 실수 => Double.parseDouble(문자열)
정수 -> 문자열 => String.valueOf(정수)
실수 -> 문자열 => String.valueOf(실수)

valueOf 메서드는 오버로딩 되어있어서 다양한 형태가 들어오더라도 동작할 수 있도록 제공


String

String 객체 생성 방법

방법1. new 키워드 사용 (권장)

String str = new String("안녕");

             ~~~~~~ ~~~~~
              생성자  저장할 문자열
              

방법2. 문자열 리터럴 이용

String str = "안녕";

String 클래스의 특징

  • 한 번 정의된 문자열은 변경할 수 없다. => 문자열의 내용을 변경하면 문자열을 수정하는 것이 아니고, 새로운 문자열을 포함하고 있는 객체를 생성해서 사용 (기존 문자열 객체는 버림)

  • 문자열 리터럴을 바로 입력해 객체를 생성할 때 같은 문자열끼리는 객체를 공유

    힙 메모리에 문자열 객체가 생성되면서 쌓이고 안 쓰이는 객체는 검사하면서 지워버린다.



같은 객체를 공유하던 str1과 str2.
str1이 바뀌어도 배열과 다르게 str2의 값이 유지가 되는 이유는 새로운 객체가 만들어지기 때문이다.

package com.test;

import java.util.Arrays;

public class MyTest {
    public static void main(String[] args) {
        String str1 = new String("안녕");
        String str2 = new String("안녕"); 
        String str3 = "안녕";
        String str4 = "안녕";

        System.out.println(str1 == str2); // false
        System.out.println(str1 == str3); // false
        System.out.println(str3 == str4); // true
    }
}
  • str1. new를 하면 100번지에 안녕이 만들어진다.
  • str2. new를 하면 200번지에 또다른 안녕이 만들어진다.
  • str3. 리터럴은 300번지에 안녕이 만들어진다.
  • str4. 동일한 문자열 리터럴은 한 번만 만들어지기 때문에 str4도 300번지 안녕을 참조한다.

⭐️

=> new 키워드를 이용해서 객체를 생성하면 항상 힙 메모리에 새로운 객체로 만들어진다.
=> 문자열 리터럴로 생성하면 동일한 문자열을 포함하고 있는 객체가 있으면 그 객체를 공유한다.

문자열 + 문자열


결합될때마다 새로 만들어졌다가 나중에 사라지게 동작하므로 엄격히 얘기하면 비효율적인 방법이다. => StringBuffer를 사용하자

문자열 + 기본 자료형 or 기본 자료형 + 문자열

기본 자료형을 문자열로 바꿔서 결합 연산

String 클래스의 주요 메서드


package com.test;

import java.util.Arrays;

public class MyTest {
    public static void main(String[] args) {
        String s1 = new String("안녕");
        String s2 = new String("안녕");
        String s3 = "안녕";
        String s4 = "안녕";

        System.out.println(s1 == s2); // false
        System.out.println(s1 == s3); // false
        System.out.println(s3 == s4); // true

        System.out.println(s1.equals(s2)); // true
        System.out.println(s1.equals(s3)); // true
        System.out.println(s3.equals(s4)); // true

    }
}

클래스

  • 객체(object)는 사용할 수 있는 실체를 의미
  • 클래스(class)는 객체를 만들기 위한 설계도

하나의 클래스를 이용해서 여러 개의 객체를 만들 수 있음

절차 지향 VS 객체 지향 ⬇️


객체를 정의하고 연관 지으면 확장이 쉽다 ! => 객체 지향

자바에서 제공하는 객체지향 문법 요소

인터페이스 = 명세 ⭐️⭐️⭐️⭐️⭐️

  • 두 개를 연결해주는 역할! 이기종 간 쉽게 연결하는 것이 가능해진다.
  • 프로그램을 유연하게 만들어준다.

클래스

  • 실질적인 동작이 구현되는 것

추상 클래스 ⭐️⭐️⭐️⭐️⭐️

  • 동작이 100% 다 구현되지 않은 것
  • 프로그램을 유연하게 만들어준다.

클래스 구조

package ... ; // 패키지 <= 주석을 제외하고 첫 번째 줄에 위치
                         디폴트 패키지를 사용하면 생략이 가능
import ... ; // 임포트 <= 다른 패키지의 클래스를 사용할 때 사용

class 클래스명 { ... } // 외부 클래스 <= public 키워드를 사용할 수 없음

            파일명과 동일
             ~~~~~~
public class 클래스명 { 

	int a = 3; // 필드 (속성) <= 클래스의 특징(속성)을 나타내는 변수
    double abc() { ... } // 메서드 (함수) <= 클래스가 지니고 있는 기능(함수)
    클래스명() { ... } // 생성자 <= 클래스의 객체를 생성하는 역할
    class 클래스명2 { ... } // 내부 클래스 
    
}

클래스 멤버 = 필드 + 메서드 + 내부 클래스

붕어빵 : 인스턴스

객체를 생성할 때는 new 키워드를 사용

클래스명 참조변수명 = new 생성자(); // 생성자는 클래스 이름과 동일. 생성자는 곧 객체를 생성할 때 사용되는 함수 !


공통적인 내용(drive()라는 메서드)가 인스턴스 메서드 영역에 들어간다.

포인트 연산자(.) 사용

자바에서는 힙 메모리에 직접 접근을 할 수 없다.
스택 메모리에 있는 참조변수를 통해 힙 메모리에 있는 필드나 메서드에 접근할 수 있다.

참조변수명.필드명
참조변수명.메서드명()

필드 vs 지역변수

package com.test;

import java.util.Arrays;
class A {
    int m = 3; // 필드
    int n = 4;

    void work1() {
        int k = 5; // 지역변수
        System.out.println(k);
        work2(3);
    }

    void work2(int i) { // 매개변수 = 지역변수
        int j = 4; // 지역변수
        System.out.println(i + j);
    }
}

public class MyTest {
    public static void main(String[] args) {
        A a = new A();
        System.out.println(a.m);
        System.out.println(a.n);
        a.work1();
    }
}

m, n은 필드 -> 계속 값이 유지된다
k, j는 메서드 내에 있는 지역변수 -> 함수가 끝나면 사라진다.
i 는 매개변수 = 지역변수 -> 함수가 끝나면 사라진다.

필드와 지역변수의 초기값

class A {
	int m; // 필드는 힙 메모리에 들어간다 ! - 자동으로 초기화된다.
    int n;
    void work() {
    	int k; // 지역 변수는 stack 영역에 들어간다 ! - 초기화 하지 않으면 값을 가지지 않는 빈 상태로 남아있다. -> 사용할 수 없다.
        System.out.println(k);
    }
}

A a = new A();
System.out.println(a.m); // 0 강제 초기화
System.out.println(a.n); // 0 강제 초기화
a.work(); // error. k는 사용할 수 없으므로

상황: 필드와 지역변수를 초기화하지 않았다.
필드(m, n)힙 메모리에 들어간다 ! -> 자동으로 초기화된다.
지역 변수(k)stack 영역에 들어간다 ! - 초기화 하지 않으면 값을 가지지 않는 빈 상태로 남아있다. -> 사용할 수 없다.


메서드

                         매개변수
                      ~~~~~~~~~~~~
public static int sum(int a, int b) {
~~~~~~~~~~~~~ ~~~ ~~~
|             |   메서드 이름
+-- 제어자     +-- 리턴 타입
	// 메서드 본문(내용)
}

리턴 타입이 void (반환 값이 없음)이고, 매개변수도 없는 메서드

void print() {
	System.out.println("안녕");
}

리턴 타입이 있고, 매개변수가 없는 메서드

int data() {
	return 3;
}

리턴 타입과 매개변수가 모두 있는 메서드

int sum(int a, int b) {
	return a + b;
}

리턴 타입이 void일 때, 메서드 내에서 return을 사용 => 메서드를 종료한다는 의미

void printMonth(int m) {
	if (m < 0 || m > 12) {
    	System.out.println("잘못된 입력");
        return; // 메서드 종료
    }
    System.out.println(m + "월 입니다.");
}

클래스 외부에서 메서드 호출 => 참조변수를 이용해서 메서드를 호출 <= 객체를 먼저 생성

클래스 내부에서 메서드 호출 => 객체를 생성하지 않고 호출이 가능 (단, static이 붙어 있는 메서드는 static이 붙어 있는 필드 또는 메서드만 호출이 가능)

package com.test;

import java.util.Arrays;
class A {
    void printA() {
        System.out.println("A");
    }

    void printMyA() {
        System.out.println("My");
        printA(); // 같은 클래스 내부라 직접 호출 가능
    }

    void printB() {
        System.out.println("B");
    }

    static void printMyB() {
        System.out.println("My");
        printB(); // error. static이 붙어있으므로 static이 붙은 메서드만 호출 가능하다.
    }
}

public class MyTest {
    public static void main(String[] args) {
        A a = new A();
        a.printMyA(); // 다른 클래스의 메서드는 인스턴스를 new로 만들어서 호출해야 한다.

        // 인스턴스를 통해서 메서드를 호출
        a.printMyB();

        // 인스턴스를 생성하지 않고 메서드를 호출 - static메서드
        A.printMyB();
    }
}

입력 매개변수가 배열인 메서드를 호출

package com.test;

import java.util.Arrays;

public class MyTest {

    public static void printArray(int[] numbers) {
        System.out.println(Arrays.toString(numbers));
    }

    public static void main(String[] args) {
        int[] a = { 1, 2, 3 };
        printArray(a);

        printArray(new int[] { 1, 2, 3 });

        // 선언과 할당을 분리할 수 없기 때문에 못 쓴다. ⬇️
        // int[] numbers;
        // numbers = {1, 2, 3};

        // printArray({1, 2, 3}); // error.
    }
}

기본 자료형을 매개변수의 값으로 전달하는 경우, 값을 복사해서 사용한다.

참고: 관련된 보안 약점

  1. public 메서드로부터 반환된 private 배열 => getter 메서드를 잘못 구현했을 때 발생
  2. private 배열에 public 데이터 할당 => setter 메서드를 잘못 구현했을 때 발생
package com.test;

import java.util.Arrays;

class ArrayTest {
    private int[] myData = { 1, 2, 3 };

    // getter => 필드 데이터를 반환하는 메서드
    public int[] getMyData() {
        return myData;
    }

    // setter => 필드에 데이터를 설정하는 메서드
    public void setMyData(int[] data) {
        this.myData = data;
    }

    public void printMyData() {
        System.out.println(Arrays.toString((this.myData)));
    }
}

public class MyTest {
    public static void main(String[] args) {
        ArrayTest test = new ArrayTest();
        // System.out.println(Arrays.toString(test.myData)); // error. 접근 불가능하다.

        int[] outerData = test.getMyData(); // 🥵 getter에서 주소가 반환이 돼서 주소가 공유되었다.
        System.out.println(Arrays.toString(outerData)); // [1, 2, 3]

        outerData[1] = 200;
        System.out.println(Arrays.toString(outerData)); // [1, 200, 3]

        test.printMyData(); // [1, 200, 3] 🚨 private 배열이 전혀 보호가 안 된다.

        int[] outerArray = { 100, 200, 300 };
        test.setMyData(outerArray); // 🥵 setter로 넘겨준 게 outerArray의 주소를 그대로 넘겨줘서 공유를 하고 있구나
        test.printMyData(); // [100, 200, 300]

        outerArray[1] = 20000;
        test.printMyData(); // [100, 20000, 300] 🚨 외부에서 setter 메서드 안 쓰고 값을 바꿨는데 클래스 내부까지 영향을 미치는구나
    }
}

메서드 시그니처(method signature) = 메서드명 + 매개변수의 자료형 <= 메서드를 구분하는 기준

메서드 오버로딩 => 매개변수의 개수나 자료형이 다른 여러 개의 동일한 이름을 지닌 메서드를 같은 공간에 정의하는 것

println 메서드처럼 유연한 코딩이 가능하다.

가변 길이 배열 데이터를 매개변수로 받는 메서드

package com.test;

import java.util.Arrays;

public class MyTest {
    public static void method(int... numbers) {
        System.out.println("매개변수의 길이 >>> " + numbers.length);
        for (int no : numbers) {
            System.out.println(no);
        }
    }

    public static void main(String[] args) {

        method(1, 2, 3);
        method(10, 20, 30, 40, 50);

    }
}

클래스 내부 구성 요소 - 생성자

  • 객체 생성에 사용
  • 필드 초기화
  • 클래스 이름과 동일한 이름을 가지고, 리턴 타입이 없음

기본 생성자 => 생성자를 포함하지 않은 클래스에서 컴파일러가 자동으로 추가하는 생성자 (기본 생성자는 매개변수가 없고 생성자 본문이 비어 있음)

this 키워드 => 자신이 포함된 클래스의 객체를 가리키는 참조 변수

package com.test;

import java.util.Arrays;

public class MyTest {
    private static int data = 100;

    // this를 생략하여도 컴파일러가 자동으로 this를 추가
    public void print() {
        System.out.println(data); // this.data
    }

    // 매개변수의 이름과 필드의 이름이 동일한 경우
    // >> 명시적으로 this.를 사용
    public void setData(int data) {
        this.data = data;
    }

    public static void main(String[] args) {
        MyTest test = new MyTest();

        test.print(); // 100
        test.setData(200);
        test.print(); // 200

    }
}

this() 메서드

⇒ 자신이 속한 클래스 내부의 다른 생성자를 호출 ⇒ 생성자 내부에서만 사용할 수 있고, 생성자의 첫 줄에 위치해야 함

package com.test;

import java.util.Arrays;

class Sample {
    Sample() {
        System.out.println("첫번째 생성자");
    }

    // 오버로딩
    Sample(int a) {
        // 생성자 내부에서 사용할 수 있고, 반드시 첫번째 줄에 위치해야 한다.
        this(); // 첫번째 생성자를 먼저 실행
        System.out.println("두번째 생성자");
    }

    void doSomething() {
        // 메서드 내에서는 this() 메서드를 사용할 수 없음
        // this();
    }
}

public class MyTest {
    public static void main(String[] args) {
        Sample s = new Sample(1);
    }
}

중복을 줄이기 위한 용도로 this() 호출 ⬇️

package com.test;

import java.util.Arrays;

class Sample {
    String name;
    int age;
    boolean isMarried;

    Sample() {
        System.out.println("첫번째 생성자");
        this.name = "아무개";
        this.age = 0;
        this.isMarried = false;
    }

    // 중복을 방지하고 덮어쓰는 용도로도 this()를 사용할 수 있다.
    // 오버로딩
    Sample(int age) {
        this();
        this.age = age;
    }

    // 중복을 방지하고 덮어쓰는 용도로도 this(age)를 사용할 수 있다.
    // 오버로딩
    Sample(int age, boolean isMarried) {
        this(age);
        this.isMarried = isMarried;
    }

    void print() {
        System.out.println("name >>> " + this.name);
        System.out.println("age >>> " + this.age);
        System.out.println("isMarried >>> " + this.isMarried);
    }
}

public class MyTest {
    public static void main(String[] args) {
        Sample s1 = new Sample(1);
        s1.print();

        Sample s2 = new Sample(100);
        s2.print();

        Sample s3 = new Sample(200, true);
        s3.print();
    }
}

패키지(package) - 클래스 외부 구성 요소

동일한 목적으로 만들어진 클래스들을 묶어(동일한 공간(폴더)) 관리하는 것 => 클래스명의 충돌을 방지 => 클래스의 풀네임(full name) = 패키지명.클래스명

외부 클래스(external class)

public 클래스 외부에 정의한 클래스
외부 클래스는 같은 패키지 안에서만 사용 <= default 접근 지정자를 가지는 클래스

접근 지정자

  • 자바 제어자의 한 종류로, 클래스, 멤버(필드, 메서드, 이너 클래스), 생성자 앞에 위치
  • 사용 범위를 정의

멤버 및 생성자의 접근 지정자 => public, protected, default(=package), private

클래스의 접근 지정자 => public, default

default는 같은 패키지 내에서만 사용 가능


public 클래스이므로 다른 패키지에서 사용 가능


생성자가 없는 경우 자동으로 만들어준다.
다른 패키지에서 default 클래스를 import 하려고 하면 오류가 난다.


생성자가 public이어도 class 자체가 default 이므로 생성자를 불러오는 것이 불가능하다.

static 제어자

  • 클래스 멤버(필드, 메서드, 이너 클래스)에 사용하는 제어자
  • static이 붙어 있는 멤버 => 정적 멤버(static member) => 객체 생성 없이 클래스명.멤버명으로 사용이 가능
  • static이 붙어 있지 않은 멤버 => 인스턴스 멤버(instance member) => 참조변수명.멤버명
class A {
	int m = 3;
    static int n = 5; 
}

=> 정적 필드는 객체 간 공유 변수의 성격을 가진다.

package com.test;

import java.util.Arrays;

class A {
    int m = 3;
    static int n = 5;
}

public class MyTest {
    public static void main(String[] args) {
        A a1 = new A();
        A a2 = new A();

        a1.m = 5;
        a2.m = 6;
        System.out.println(a1.m); // 5
        System.out.println(a2.m); // 6

        a1.n = 7;
        a2.n = 8;
        System.out.println(a1.n); // 8 n이 8로 공유됨
        System.out.println(a2.n); // 8

        A.n = 9;
        System.out.println(a1.n); // 9 n이 9로 공유됨
        System.out.println(a2.n); // 9
    }
}

모든 필드들이 그 값을 공유한다. ⭐️⭐️⭐️⭐️⭐️
모든 곳에 동일하게 적용되어야 할 때 static을 쓴다.

인스턴스 메서드(일반 메서드)와 정적 메서드

class A {
	void abc() { // 객체를 생성한 후에 사용 가능
    	System.out.println("인스턴스 메서드");
    }
    static void bcd() { // 객체를 생성하지 않고 사용이 가능
    	System.out.println("정적 메서드");
    }
}

정적 메서드 내에서는 정적 필드 또는 정적 메서드만 사용이 가능

this 키워드를 사용할 수 없음

정적 초기화 블록 => 정적 필드를 초기화하기 위한 문법

static {
// 클래스가 메모리에 로딩될 때 실행되는 코드
}

static main() 메서드

왜 static을 붙일까? 실행하면 이 main() 메서드를 직접 호출한다.

인스턴스를 만들지 않고 직접 호출할 수 있어야 하기 때문에 static을 붙인다! ⭐️⭐️⭐️


상속 ⭐️⭐️⭐️⭐️⭐️


[UML 다이어그램]

장점 😇

  • 코드의 중복을 제거
  • 다형적 표현이 가능 => 다형성(polymorphism) : 객체를 여러 가지 모양으로 표현할 수 있는 특징⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️
Apply[] apples = { new Apple(), new Apple() };
Grape[] grapes = { new Grape(), new Grape() };
Kiwi[] kiwies = { new Kiwi(), new Kiwi() };

class Fruit { }
class Apple extends Fruit { }
class Grape extends Fruit { }
class Kiwi extends Fruit { }

Fruit fruits = { new Apple(), new Grape(), new Kiwi() };

extends 상속 받을 부모 클래스를 명시

자바의 클래스는 다중 상속이 불가능하다. => 부모가 두 개 이상 불가능하다.
class 자식클래스 extends 부모1클래스, 부모2클래스 { ... } (X)

cf) 인터페이스는 다중 상속 가능하다.

상속을 수행할 때 클래스의 내부 구성 요소 중에선 멤버(필드, 메서드, 이너 클래스)만 상속되고, 생성자는 상속되지 않는다!!!

package com.test;

import java.util.Arrays;

class A {}
class B extends A {}
class C extends B {}
class D extends B {}

public class MyTest {
    public static void main(String[] args) {
        {   // 다형적 표현이 올바르게 사용된 경우
            // 부모 변수 = 자식 인스턴스 (부모가 변수고 자식이 값이 되는 것)
            A a = new A();
            B b = new B();
            C c = new C();
            D d = new D();


            A a1 = new B();
            A a2 = new C();
            A a3 = new D();

            B b1 = new C();
            B b2 = new D();
        }
        {   // 잘못된 사용 예. 형제지간도 안 된다.
            B b1 = new A();

            C c1 = new A();
            C c2 = new B();

            D d1 = new A();
            D d2 = new B();
            D d3 = new C();
        }


    }
}

객체의 타입 변환

업캐스팅

객체에서 자식 클래스에서 부모 클래스 쪽으로 변환되는 것

  • 명시적으로 하지 않아도 컴파일러가 자동으로 수행

다운캐스팅

객체에서 부모 클래스에서 자식 클래스 쪽으로 변환되는 것

  • 개발자가 명시적으로 수행
사람     슈슈는 사람이다. => 업 캐스팅 => 항상 참
 ^      사람은 슈슈다. => 다운 캐스팅 => 항상 참이 되지 않을 수 있음
 |
슈슈

package com.test;

import java.util.Arrays;

class A {}
class B extends A {}
class C extends B {}

public class MyTest {
    public static void main(String[] args) {
        {   // 업 캐스팅은 컴파일러가 자동으로 수행해준다.
            B b1 = new B();
            A a1 = b1; // A a1 = (A) b1

            C c2 = new C();
            B b2 = c2; // B b2 = (B) c2
            A a2 = c2; // A a2 = (A) c2
        }
        {   // 다운 캐스팅 => 수동으로 타입을 변환해야 한다.
            A a1 = new A(); // 실질적인 내용이 A이므로
            // B b1 = (B) a1; // 내용 자체가 달라서런타임 예외 발생

            A a2 = new B(); // B 가 A 로 형변환 됨. a2는 실질적인 내용이 B 이므로
            B b2 = (B) a2; // 내용이 통해서 런타임 에러도 안 난다. 명시적으로 타입 변환 A를 B로 !
            // C c2 = (C) a2; // 문법적으로는 문제가 없는데 힙 메모리 상에서 C객체를 찾을 수 없기 때문에 런타임 예외 발생
        }
    }
}

package com.test;

import java.util.Arrays;

class A {
    int m = 3;
    void abc() {
        System.out.println("A");
    }
}

class B extends A {
    int n = 4;
    void bcd() {
        System.out.println("B");
    }
}

public class MyTest {
    public static void main(String[] args) {
        {
            // B의 객체를 B 타입으로 선언하면 부모 자식 모두 가져올 수 있다.
            B b = new B();
            System.out.println(b.m); // 3
            System.out.println(b.n); // 4
            b.abc(); // A
            b.bcd(); // B
        }
        {
            // B(자식)의 객체를 A(부모) 타입으로 선언하면 자식이 가지고 있는 것은 쓸 수 없다.
            A a = new B();
            System.out.println(a.m); // 3 부모가 가지고 있는 것만 쓸 수 있다.
            // System.out.println(a.n); // error. 자식이 가지고 있는 것은 쓸 수 없다.
            a.abc(); // A 부모가 가지고 있는 것만 쓸 수 있다.
            // b.bcd(); // error. 자식이 가지고 있는 것은 쓸 수 없다.
        }
    }
}

자식에게 있는 값을 쓰고 싶으면 다운 캐스팅 해야한다.

{
	A a = new B();
    System.out.println(a.m); // 3
    a.abc(); // A

	B b = (B) a; // 다운 캐스팅
	System.out.println(b.n); // 4
	b.bcd(); // B
}

캐스팅 가능 여부를 확인 ⇒ instanceof 키워드

안전한 캐스팅을 하기 위해서는 instanceof 로 캐스팅 가능 여부를 확인하고 캐스팅하자!
예외 안 나도록 !!

참조변수 instanceof 타입

⇒ true : 캐스팅 가능
⇒ false : 캐스팅 불가능

package com.test;

import java.util.Arrays;

class A {
    int m = 3;
    void abc() {
        System.out.println("A");
    }
}

class B extends A {
    int n = 4;
    void bcd() {
        System.out.println("B");
    }
}

public class MyTest {
    public static void main(String[] args) {
        A aa = new A();
        A ab = new B();

        System.out.println(aa instanceof A); // true
        System.out.println(ab instanceof A); // true

        System.out.println(aa instanceof B); // false
        System.out.println(ab instanceof B); // true ab는 A의 인스턴스이면서 B의 인스턴스이다. B로 다운캐스팅이 가능하다 !

        if (ab instanceof B) {
            B b = (B) ab; // 이렇게 안전하게 다운캐스팅 하자
        }


    }
}

ab는 A의 인스턴스이면서 B의 인스턴스이다. B로 다운캐스팅이 가능하다 !

이 모든 것은 다형성을 위해서 !!!!!!! ⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️


메서드 오버라이딩(method overriding) ⭐️⭐️⭐️⭐️⭐️

부모 클래스에서 상속받은 메서드와 동일한 이름의 메서드를 재정의하는 것

  • 부모 메서드를 자신이 만든 메서드로 덮어쓰는 개념

특징

  • 부모 클래스의 메서드와 시그니처와 리턴 타입이 동일해야 한다.
    cf) 오버로딩은 리턴 타입과 관계없었음 !
  • 부모 클래스의 메서드보다 접근 지정자의 범위가 같거나 넓어야 한다.
package com.test;

import java.util.Arrays;

class A {
    void print() {
        System.out.println("A 클래스");
    }
}

class B extends A {
    // 오버라이딩
    @Override
    void print() {
        System.out.println("B 클래스");
    }
}

public class MyTest {
    public static void main(String[] args) {
        A aa = new A();
        aa.print(); // A 클래스

        B bb = new B();
        bb.print(); // B 클래스

        A ab = new B();
        ab.print(); // B 클래스
    }
}

package com.test;

import java.util.Arrays;

abstract class Animal {
    abstract void cry(); // 부모는 이렇게 추상 메서드로 만들자!!!
}

class Bird extends Animal{
    void cry() {
        System.out.println("짹짹");
    }
}
class Cat extends Animal{
    void cry() {
        System.out.println("야옹");
    }
}
class Dog extends Animal{
    void cry() {
        System.out.println("멍멍");
    }
}

public class MyTest {
    public static void main(String[] args) {
        Animal bird = new Bird();
        Animal cat = new Cat();
        Animal dog = new Dog();

        bird.cry(); // 짹짹
        cat.cry(); // 야옹
        dog.cry(); // 멍멍
    }
}

제이이이일 중요하다 ⭐️⭐️⭐️⭐️⭐️

오버라이딩(재정의) 해놓으면 나중에 다운캐스팅하지 않아도 부모 타입의 변수에 그 메서드를 호출하면 내가 정의한 메서드가 실행된다.
부모는 구조를 정의하고 실제로 구현할 것은 오버라이딩 해서 특정 시점에서 실행되도록 만들어주면 된다.

상속 관계를 만들고
이것을 이용해서 다형성을 구현할 수 있고 유연하게 데이터를 할당하고 사용하는 것이 가능하다 !

Animal bird = new Bird();
부모 타입으로 선언하고 자식으로 받는다.

원래라면 다운캐스팅 해야하는데 다운캐스팅하지 않아도 오버라이딩 한 것은 그냥 쓸 수 있다 !!!!!

부모는 추상 메서드로 만들면 된다. cry() { System.out.println("운다.") } 가 출력될 일이 없기 때문!

abstract class Animal {
    abstract void cry(); // 부모는 이렇게 추상 메서드로 만들자!!!
}

추상 메서드보다 더 추상화된 개념이 인터페이스(명세)다 !

0개의 댓글

관련 채용 정보