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

letthem·5일 전
0

LG CNS AM CAMP 1기

목록 보기
13/16
post-thumbnail

앞쪽 내용 중요 ⭐️⭐️⭐️⭐️⭐️

오버라이딩

배열을 이용해서 하나의 변수로 관리하는 것도 가능하다

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[] animals = new Animal[] { new Bird(), new Cat(), new Dog() };
		for (Animal a : animals) {
			a.cry();
		}
	}
}

메서드 오버라이딩 시 접근 지정자는 부모 보다 범위가 같거나 넓은 접근 지정자를 사용

부모 클래스 메서드의 접근 지정자		메서드 오버라이딩 시 사용할 수 있는 접근 지정자
public 						public
protected					public, protected
default						public, protected, default
private						public, protected, default, private

접근 지정자를 잘못 설정하면 아래와 같은 문법 오류가 발생

🚨 Cannot reduce the visibility of the inherited method from Animal

class Animal {
    public void cry();
}

class Bird extends Animal{
    void cry() { // error. default. public으로 바꿔야 오류가 안 남
        System.out.println("짹짹");
    }
}

인스턴스 필드의 중복

package com.test;

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

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

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

        aa.print(); // A
        bb.print(); // B
        ab.print(); // B. 메서드: 자식이 오버라이드하면 덮어쓰여진 메서드가 호출된다.
        System.out.println(aa.m); // 3
        System.out.println(bb.m); // 4
        System.out.println(ab.m); // 3이 나온다. A 인스턴스를 B가 감싸고 실질적으로 A를 가리킴
        // 인스턴스 변수: 그대로 유지가 된다.

    }
}

static 필드의 중복

package com.test;

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

class B extends A{
    static int m = 4;
    void print() {
        System.out.println("B");
    }
}

public class MyTest {
    public static void main(String[] args) {
        // 클래스 이름을 이용해서 static 필드에 접근
        System.out.println(A.m); // 3
        System.out.println(B.m); // 4

        A aa = new A();
        B bb = new B();
        A ab = new B();

        // 인스턴스 변수를 이용해서 static 필드에 접근 => warning이 발생
        // 클래스 이름. 으로 접근 권장 O /  인스턴스 변수.으로 접근 권장 X
        System.out.println(aa.m); // 3
        System.out.println(bb.m); // 4
        System.out.println(ab.m); // 3이 나온다. A 인스턴스를 B가 감싸고 실질적으로 A를 가리킴
        // 인스턴스 변수: 그대로 유지가 된다.

    }
}

정적 메서드 ⇒ 오버라이딩되지 않음

package com.test;

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

class B extends A{
    static int m = 4;
    static void print() {
        System.out.println("B");
    }
}

public class MyTest {
    public static void main(String[] args) {
        // 클래스 이름을 이용해서 static 메서드를 실행
        A.print();
        B.print();
  
        // 인스턴스 변수를 이용해서 static 메서드를 실행 => warning이 발생
        A aa = new A();
        B bb = new B();
        A ab = new B();

        aa.print(); // A
        bb.print(); // B
        ab.print(); // A 따로 만들어지기 때문에 static은 오버라이드 되지 않는다.

    }
}

super 키워드, super() 메서드

cf) this 키워드, this() 메서드

this => 자신의 객체
this() => 자신의 생성자 : 생성자 내부에서만 사용. 생성자의 첫 번째 줄에서 사용

super => 부모 객체 => 부모 필드 또는 메서드를 호출하기 위해 사용
super() => 부모의 생성자

package com.test;

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

class B extends A{
    void print() {
        System.out.println("B");
    }
    void parentPrint() {
        super.print(); // 부모의 메서드를 부르고 싶어서 super 사용
    }
}

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

        aa.print(); // A
        bb.print(); // B
        ab.print(); // B

        bb.parentPrint(); // A
        ((B)ab).parentPrint(); // A
    }
}

super 키워드 사용 예

반복을 줄일 수 있다.

class A {
    void doSomething() {
        System.out.println("메모리 할당");
        System.out.println("UI를 설정");
        System.out.println("변수를 초기화");
        System.out.println("기타 등등 많은 작업을 수행");
    }
}

class B extends A{
    void doSomething() {
        System.out.println("메모리 할당");
        System.out.println("UI를 설정");
        System.out.println("변수를 초기화");
        System.out.println("기타 등등 많은 작업을 수행");

        System.out.println("자식 클래스에서 필요한 기능을 수행");
    }
}

class C extends A {
    void doSomething() {
        super.doSomething();
        System.out.println("자식 클래스에서 필요한 기능을 수행");
    }
}

super() 메서드

=> 생성자 내부에서만 사용이 가능하고, 반드시 첫 줄에 위치해야 함
=> this(), super()는 함께 사용할 수 없음

모든 생성자는 첫 줄에 반드시 this() 또는 super()가 있어야 함 ⇒ 아무것도 사용하지 않으면 컴파일러가 자동으로 super()를 삽입

package com.test;

class A {
    // 생성자 O
    A(int a) {
        System.out.println("A");
    }
    A() { // 3. 매개변수가 없는 생성자를 이렇게 만들자 !

    }
}

// 2. 'com. test. A'에 사용 가능한 매개변수가 없는 생성자가 없습니다
class B extends A{
    // 1. 컴파일러가 아래와 같은 기본 생성자를 자동으로 만들어준다.
    // super()에 대응하는 생성자 A 클래스에 없기 때문에 발생
    /*
    B() {
        super();
     */
}

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

    }
}

최상위 클래스 Object

자바의 모든 클래스는 Object 클래스를 상속받는다.
=> 아무런 클래스를 상속받지 않으면 컴파일러가 자동으로 extends Object 코드를 삽입해서 Object 클래스를 상속하도록 함

=> 결국 모든 클래스들은 Object를 상속받게 된다.

class A { } => class A extends Object { }
class B extends A { } // B는 Object도 상속 받게 된다.

// 자바의 모든 클래스는 어떤 객체로 만들든지 다음과 같이 Object 타입으로 선언할 수 있음
Object o1 = new A();
Object o2 = new B();

Object 클래스의 메서드

public boolean equals(Object obj)

스택 메모리의 값(주소)을 비교
== (등가 비교 연산자)와 동일한 결과

문자열에서는 equals가 주소가 아닌 값을 비교하는 것이었는데,,

package com.test;

class A {
    String name;
    A (String name) {
        this.name = name;
    }
}

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

        System.out.println(s1 == s2); // false
        System.out.println(s1.equals(s2)); // true
        
        A a1 = new A("안녕");
        A a2 = new A("안녕");

        System.out.println(a1 == a2); // false
        System.out.println(a1.equals(a2)); // false 힙 메모리의 주소를 비교하기 때문이다.

    }
}

실제 내용을 비교하려면 오버라이드해서 사용해야 한다.

package com.test;

class A {
    String name;
    A (String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) { // override 해주면 ..! ✨
        if (obj instanceof A && this.name == ((A)obj).name) {
            return true;
        }
        return false;
    }
}

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

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

        A a1 = new A("안녕");
        A a2 = new A("안녕");

        System.out.println(a1 == a2); // false
        System.out.println(a1.equals(a2)); // true 👍

    }
}

toString()

=> 객체 정보(패키지이름.클래스이름@해시코드)를 문자열로 리턴하는 메서드
=> 일반적으로 자식 클래스에서 toString() 메서드를 오버라이딩해서 사용

package com.test;

class A {
    String name;

    A (String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof A && this.name == ((A)obj).name) {
            return true;
        }
        return false;
    }
}

public class MyTest {
    public static void main(String[] args) {

        A a1 = new A("안녕");
        
        System.out.println(a1); // com.test.A@5acf9800
						   ~~
              자동으로 해당 객체의 toString() 메서드를 호출
    }
}

오버라이드 하면 ⬇️

package com.test;

class A {
    String name;

    A (String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof A && this.name == ((A)obj).name) {
            return true;
        }
        return false;
    }

    @Override
    public String toString() {
        return  "이름은 " + this.name + "입니다.";
    }
}

public class MyTest {
    public static void main(String[] args) {

        A a1 = new A("안녕"); 

        System.out.println(a1); // 이름은 안녕입니다.
    }
}

해쉬(Hash, Message Digest)

  • 단방향성 D -- o --> H(D) -- x --> D => 인증 정보를 저장, 인증 처리할 때 사용
    • 암호화는 가능하나, 복호화는 불가능하다.
  • 유일성 D <> D' → H(D) <> H(D') => 무결성 보장을 할 때 사용
    • <> : 다르다면
    • 조금이라도 다르다면 해시값도 다르다.

hashCode()

=> 객체의 위치값을 기준으로 생성한 고유값
=> Hashtable, HashMap 등의 객체에서 동등 비교를 할 때 해당 메서드를 오버라이딩해야 함

{key=value} 키는 중복되면 안 된다. 이를 체크할 때 hashCode()를 본다.

package com.test;

import java.util.HashMap;

class A {
    String name;

    A (String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof A && this.name == ((A)obj).name) {
            return true;
        }
        return false;
    }

    @Override
    public String toString() {
        return  "이름은 " + this.name + "입니다.";
    }
}

public class MyTest {
    public static void main(String[] args) {
        HashMap<Integer, String> hm1 = new HashMap<>();
        hm1.put(1, "데이터1");
        hm1.put(2, "데이터2");
        hm1.put(1, "데이터3");
        System.out.println(hm1); // {1=데이터3, 2=데이터2}
    }
}

키가 중복되면 데이터를 덮어쓴다.

package com.test;

import java.util.HashMap;

class A {
    String name;

    A (String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof A && this.name == ((A)obj).name) {
            return true;
        }
        return false;
    }

    @Override
    public String toString() {
        return this.name;
    }
}

public class MyTest {
    public static void main(String[] args) {
        HashMap<Integer, String> hm1 = new HashMap<>();
        hm1.put(1, "데이터1");
        hm1.put(2, "데이터2");
        hm1.put(1, "데이터3");
        System.out.println(hm1); // {1=데이터3, 2=데이터2}

        HashMap<A, String> hm2 = new HashMap<>();
        A a1 = new A("첫번째");
        A a2 = new A("두번째");
        A a3 = new A("첫번째");

        hm2.put(a1, "데이터1");
        hm2.put(a2, "데이터2");
        hm2.put(a3, "데이터3");
        System.out.println(hm2); // hashCode가 다 다르기 때문에 {두번째=데이터2, 첫번째=데이터3, 첫번째=데이터1} 이렇게 나타남

        System.out.println(a1.hashCode()); // 1523554304 <= 해시 코드가 모두 다르게 반환
        System.out.println(a2.hashCode()); // 1175962212
        System.out.println(a3.hashCode()); // 918221580
    }
}

hashCode가 다 다르기 때문에 {두번째=데이터2, 첫번째=데이터3, 첫번째=데이터1} 이렇게 나타난다.

hashCode() 를 오버라이드 하면 ⬇️

package com.test;

import java.util.HashMap;

class A {
    String name;

    A (String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof A && this.name == ((A)obj).name) {
            return true;
        }
        return false;
    }

    @Override
    public String toString() {
        return this.name;
    }

    @Override
    public int hashCode() { // 🩷
        return this.name.hashCode();
    }
}

public class MyTest {
    public static void main(String[] args) {
        HashMap<Integer, String> hm1 = new HashMap<>();
        hm1.put(1, "데이터1");
        hm1.put(2, "데이터2");
        hm1.put(1, "데이터3");
        System.out.println(hm1); // {1=데이터3, 2=데이터2}

        HashMap<A, String> hm2 = new HashMap<>();
        A a1 = new A("첫번째");
        A a2 = new A("두번째");
        A a3 = new A("첫번째");

        hm2.put(a1, "데이터1");
        hm2.put(a2, "데이터2");
        hm2.put(a3, "데이터3");
        System.out.println(hm2); // hashCode가 같아지므로 {첫번째=데이터3, 두번째=데이터2} 이렇게 나타남

        System.out.println(a1.hashCode()); // 51899483
        System.out.println(a2.hashCode()); // 45907648
        System.out.println(a3.hashCode()); // 51899483
    }
}

final 제어자

필드, 지역 변수, 메서드, 클래스 앞에 올 수 있음

final 변수 => final 필드, final 지역변수 => 한 번 대입된 값을 수정할 수 없음

package com.test;

import java.util.HashMap;

class A {
    int a = 3;
    final int b = 5;
    A() {
        
    }
}

class B {
    int a;
    final int b;
    B() {
        // this.a = 3; 인스턴스 필드는 자동으로 초기화되나,
        // this.b = 5; error. final 변수는 자동으로 초기화되지 않는다.
    }
}

class C {
    int a = 3;
    final int b = 5;
    C() {
        this.a = 7;
        // this.b = 10; error. final은 값이 한 번 할당되면 수정할 수 없다.
    }
}

public class MyTest {
    public static void main(String[] args) {
        
    }
}

final 변수는 선언과 동시에 초기화하든지, 생성자에서 반드시 초기화해야 한다 ‼️

final 메서드

=> 자식 클래스에서 해당 메서드를 오버라이딩할 수 없다

class A {
    void func() {
        
    }
    
    final void func2() {
        
    }
}

class B extends A {
    void func() {
        
    }

    // error. 재정의할 수 없습니다.
    void func2() {
        
    }
}

재정의를 막고자 할 때 final 메서드를 쓸 수 있다.

final 클래스

=> 상속할 수 없는 클래스

final class A {
}

// error. final 'com. test. A'으로부터 상속할 수 없습니다
class B extends A {
    
}

abstract 제어자

구체적이지 않은 것이 추상적이다.
=> 구체적인 동작은 정의하지 않은 것
just, 무엇인가 있다!
본문이 없다.

추상 메서드

abstract 리턴타입 메서드이름(매개변수);

추상 클래스

하나 이상의 추상 메서드를 포함하고 있는 클래스
객체를 직접 생성할 수 없음 => A a = new A();와 같이 생성자를 호출할 수 없다.
=> (추상 메서드를 구현한 => 추상 메서드를 오버라이딩(= 구현한다(implements)))자식 클래스를 이용해서 생성

abstract class Animal {
    abstract void cry(); ⇐ 추상 메서드를 포함하면 추상 클래스가 되어야 함 
}

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

class Dog extends Animal {
    void CRY() { // error. 추상 클래스를 상속 받으면 추상 메서드를 구현(오버라이딩)하거나 추상 클래스가 되어야 함
        System.out.println("멍멍");
    }
}
class Animal {
    void cry {
        
    }
}

추상 메서드를 포함하지 않는(=일반 메서드로만 구성된) 클래스도 추상 클래스가 될 수 있음
이렇게 사용할 수도 있지만 사용할 때 CRY(잘못된 구현. 새로운 메서드를 정의) 라고 사용해도 문법적 오류가 안 나서

abstract class Animal {
    abstract void cry();
}

이렇게 추상 클래스로 명시하면 잘못된 구현을 방지할 수 있다. 이렇게 사용하자 🩷

추상 클래스 타입의 객체를 생성하는 방법 1. 상속해서 객체를 생성

package com.test;

abstract class Animal {
    abstract void cry();
}

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

public class MyTest {
    public static void main(String[] args) {
        Animal animal = new Cat();
        Cat cat = new Cat();

        animal.cry(); // 야옹
        cat.cry(); // 야옹
    }
}

추상 클래스 타입의 객체를 생성하는 방법 2.익명 이너 클래스를 사용

=> 한 번만 만들어서 사용할 경우

package com.test;

abstract class Animal {
    abstract void cry();
}

public class MyTest {
    public static void main(String[] args) {
        Animal animal = new Animal() { // 추상클래스는 new 할 수 없는데 괄호 열어서 직접 cry 메서드를 작성해주면 가능하다
            void cry() {
                System.out.println("야옹");
            }
        };

        animal.cry(); // 야옹
    }
}

인터페이스

모든 필드가 public static final로 정의
static 메서드와 default 메서드를 제외한 모든 abstract로 정의된 객체지향 프로그래밍 요소

class 대신 interface 키워드를 사용해서 선언

필드와 메서드에 제어자(modifier)를 생략하면 컴파일러가 자동으로 제어자를 삽입

interface A {
	int a = 3; // public static final int a = 3 이렇게 컴파일러가 자동으로 붙여준다.
    void abc(); // public abstract void abc();
}

인터페이스를 상속할 때는 implements 키워드를 사용하고, 다중 상속이 가능 👍

클래스와 인터페이스를 동시에 상속받을 때는 extends 먼저하고 다음 implements 를 써야 한다.

interface A {

}

interface B {

}

// 인터페이스는 다중 상속이 가능
class C implements A, B {

}

class D implements A {

}

class E {

}

// 클래스와 인터페이스를 동시에 상속
class F extends E implements A {

}
  • 클래스는 클래스와 인터페이스를 상속 받을 수 있다.

  • 인터페이스는 인터페이스만 상속 받을 수 있다. (extends 사용)

interface A {

}

class B {

}

// 클래스는 클래스와 인터페이스를 상속 받을 수 있음
class C extends B {

}

// 인터페이스는 인터페이스만 상속(이땐 extends를 사용함) 받을 수 있음
interface E extends A {
    
}

// error. 부모는 interface여야 한다.
interface F extends B {
    
}

class D implements A {

}

Q. 인터페이스는 왜 클래스를 상속 받지 못할까?
A. 인터페이스는 타입이 정해져있는데 클래스는 일반 메서드도 갖고 있기 때문에 상속 받을 수 없다.

클래스가 인터페이스를 상속할 때

1. 자식 클래스는 반드시 추상 메서드를 구현해야 한다.

interface A {
	void abc();
}

// error. 클래스 'B'은(는) abstract로 선언되거나 'A'에서 추상 메서드 'abc()'을(를) 구현해야 합니다
class B implements A {
	
}

인터페이스의 추상 메서드를 구현하지 않으면 추상 클래스(abstract)로 정의해야 한다. ⬇️

interface A {
    void abc(); 
}

abstract class B implements A {

}

2. 자식 클래스의 구현 메서드는 public만 가능

interface A {
    void abc(); // public abstract가 생략되어 있음 
    			// => 자식 클래스의 메서드는 public으로 선언해야 한다.
}

abstract class B implements A {
    // error. A의 'abc()'와(과) 충돌합니다
    void abc() {
        System.out.println("Hello");
    }
}

public 으로 선언하기 ⬇️

interface A {
    void abc(); // public abstract가 생략되어 있음 
    			// => 자식 클래스의 메서드는 public으로 선언해야 한다.
}

abstract class B implements A {
    public void abc() {
        System.out.println("Hello");
    }
}

인터페이스 타입의 객체를 생성하는 방법

1. 인터페이스를 일반 클래스로 상속해 객체 생성

반드시 상속을 통해 구현 메서드로 구현한 것을 활용해 인스턴스로 만들어야 한다.

package com.test;

interface A {
    int a = 3;
    void abc();
}

// 여러 개의 객체를 생성해서 사용할 경우 자식 클래스를 정의한다.
class B implements A {
    public void abc() {
        System.out.println("Hello");
    }
}

public class MyTest {
    public static void main(String[] args) {
        // A a = new A(); // (X)
        A a = new B(); // 반드시 상속을 통해 구현 메서드로 구현한 것을 활용해 인스턴스로 만들어야 한다.
    }
}

2. 익명 이너 클래스 사용

package com.test;

interface A {
    int a = 3;
    void abc();
}

// 객체 하나만 생성할 때 익명 이너 클래스 사용
public class MyTest {
    public static void main(String[] args) {
        A a = new A() {
            public void abc() {
                System.out.println("Hello");
            }
        };
    }
}

인터페이스를 사용해야 하는 이유 => 프린터 예

package com.test;

// 프린터가 가져야 할 사양
interface Printer {
    public void print(String s);
    public void lineFeed();
    public void loadPaper();
}

// Samsung 프린터 구현
// 해당 제품에 맞는 기능 구현 - 구체화된 예시
class SamsungPrinter implements Printer {
    public void print(String s) { }
    public void lineFeed() { }
    public void loadPaper() { }
}

// HP 프린터 구현 - 구체화된 예시
class HpPrinter implements Printer {
    public void print(String s) { }
    public void lineFeed() { }
    public void loadPaper() { }
}

public class MyTest {
    public static void main(String[] args) {
        // 삼성 프린터를 이용하는 경우
        SamsungPrinter sp = new SamsungPrinter();
        sp.print("어떤 내용");
        sp.lineFeed();
        sp.loadPaper();
        
        // HP 프린터를 이용하는 경우
        HpPrinter hp = new HpPrinter();
        hp.print("어떤 내용");
        hp.lineFeed();
        hp.loadPaper();
    }
}

추상화가 잘된 예시

package com.test;

// 프린터가 가져야 할 사양 (명세)
interface Printer {
    public void print(String s);
    public void lineFeed();
    public void loadPaper();
}

// 해당 제품에 맞는 기능 구현 - 다른 곳에서 작성. 기능이 바뀌더라도 여기 내용만 바꾸면 된다. (드라이버)
class PrinterImpl implements Printer {
    public void print(String s) { } // <= 각 프린터를 제어하는 코드를 구현
    public void lineFeed() { }
    public void loadPaper() { }
}

public class MyTest { // 클라이언트 코드(바꾸지 않아도 된다)
    public static void main(String[] args) {
        Printer sp = new PrinterImpl(); // ⇐ 프린터가 변경되어도 해당 코드는 변경할 필요가 없음
        sp.print("어떤 내용");
        sp.lineFeed();
        sp.loadPaper();
    }
}

인터페이스는 프로그램을 추상화하는 방법이다. 아주 유연해진다!


default 메서드

=> 하위 호환성을 보장하기 위해서 추가

package com.test;

interface A {
    void abc();
    void bcd();
    void xyz(); // <= 새로운 메서드가 추가되면 자식 클래스에 override 안 돼 있어서 error.
}

class B implements A { // <= 인터페이스를 상속한 클래스는 추상 메서드를 구현해야 하는데
    @Override
    public void abc() {
        System.out.println("abc is called");
    }

    @Override
    public void bcd() {
        System.out.println("bcd is called");
    }
}

public class MyTest {
    public static void main(String[] args) {
        A aa = new B();
        aa.abc();
        aa.bcd();
    }
}

새롭게 추가한 메서드에 default 키워드를 추가하고 본문을 추가하면 해당 인터페이스를 상속한 클래스에서 해당 메서드를 구현하지 않아도 되도록 하는 문법


interface A {
    void abc();
    void bcd();
    default void xyz() { // <= default 붙여주기
    
    }; 
}

xyz()가 새로운 명세라면 기존 코드에 xyz()를 Override 하지 않아 에러가 나는데,
default를 넣어주면 기존 코드에서 Override 하지 않아도 된다!

ex) List 인터페이스에 replaceAll 메서드


정적 메서드(static method)

클래스 내부의 정적 메서드와 동일
인터페이스명.정적메서드명() 방식으로 호출해서 사용

package com.test;

interface A {
    void abc();
    void bcd();
    default void xyz() {

    };

    static void hello() { // B에 implement 하지 않아도 static을 쓰면
        System.out.println("Hello");
    }
}

class B implements A {
    @Override
    public void abc() {
        System.out.println("abc is called");
    }

    @Override
    public void bcd() {
        System.out.println("bcd is called");
    }
}

public class MyTest {
    public static void main(String[] args) {
        A aa = new B();
        aa.abc();
        aa.bcd();
        A.hello(); // 인터페이스명.정적메서드명() 으로 사용이 가능하다
    }
}

기존 코드(B 클래스)를 수정하지 않고 새롭게 추가된 인터페이스의 기능을 사용할 때 쓴다 !!


예외

개발자가 해결할 수 있는 오류 = 예외(exception)
연산 오류, 포맷 오류, ... 등

cf) 에러(error) = 자바 가상 머신 자체에서 발생하는 오류 = 개발자가 해결할 수 없는 오류

예외 처리

`
``java
try {
// 예외 발생 가능 코드
} catch (예외클래스이름 참조변수이름) {
// 예외가 발생했을 때 처리
} finally {
// 예외 발생 여부와 관계 없이 실행되는 코드
// 리소스 해제와 같은 정리 코드를 기술
}

package com.test;

public class MyTest {
    public static void main(String[] args) {
        try {
            System.out.println(3 / 0);
            int i = Integer.parseInt("십삼");
        } catch (NumberFormatException e) {
            System.out.println("숫자를 변환할 수 없습니다.");
        } catch (ArithmeticException e) {
            System.out.println("숫자는 0으로 나눌 수 없습니다.");
        } catch (Exception e) {
            System.out.println("오류 발생");
        } finally {
            System.out.println("프로그램을 종료합니다.");
        }
    }
}

예외가 발생하면 바로 해당 catch로 가고 finally로 가서 종료된다.

모든 예외는 Exception에서 걸러지므로 가장 마지막에 넣어줘야 한다.

올바른 예외 처리 ⇒ 발생 가능한 예외를 세분화해서 발생할 수 있는 순서대로 기술

리소스 자동 해제 예외 처리 => try-with-resource 구문

원래 : finally 구문을 이용해서 try 블록에서 생성한 리소스를 해제

package com.test;

import java.io.IOException;
import java.io.InputStreamReader;

public class MyTest {
    public static void main(String[] args) {
        InputStreamReader is = null;
        try {
            is = new InputStreamReader(System.in);
            System.out.println(is.read());
        } catch (IOException e) {
            
        } finally { // <= finally 구문을 이용해서 try 블록에서 생성한 리소스를 해제
            if (is != null) {
                try {
                    is.close();
                } catch (Exception e) {
                    
                }
            }
        }
    }
}

try-with-resource 구문 사용

try 괄호 안에 자원 생성 구문을 넣어주면 try 구문을 빠져나갈 때 자동으로 closed 된다.

package com.test;

import java.io.IOException;
import java.io.InputStreamReader;

public class MyTest {
    public static void main(String[] args) {
        try (InputStreamReader is = new InputStreamReader(System.in);){
            System.out.println(is.read()); // <= try 블록을 빠져나갈 때 자동으로 해제 
            							   // close() 메서드를 호출
        } catch (IOException e) {

        }
    }
}

try-with-resource 구문을 사용하기 위해서는 AutoCloseable 인터페이스를 상속받아 close 메서드를 구현해야 함 👍

class A implements AutoCloseable {
	String resource;
	A(String resource) {
		this.resource = resource;
	}
	
	@Override
	public void close() throws Exception {
		resource = null;
		System.out.println("리소스 해제");		
	}
}


public class MyTest {
	public static void main(String[] args) {
		try (A a = new A("리소스")) {
			
		} catch (Exception e) {
			
		} 
	}
}

예외 전가(throws)

⇒ 호출한 메서드가 예외를 처리하도록 전달

리턴타입 메서드이름(매개변수) throws 예외클래스명 {
// 예외 발생 코드
}

전가된 예외는 최상위 메서드인 main() 메서드까지 올라가고 main() 메서드에서도 예외를 전가하면 main() 메서드를 실행한 JVM이 직접 예외를 처리

사용자 정의 예외 클래스 작성

⇒ Exception을 상속받아서 일반 예외 클래스를 만드는 방법, RuntimeException을 상속 받아서 실행

package com.test;

class MyException extends Exception {
    MyException() {

    }

    MyException(String s) {
        super(s);
    }
}

class MyRuntimeException extends RuntimeException {
    MyRuntimeException() {

    }

    MyRuntimeException(String s) {
        super(s);
    }
}
public class MyTest {
    public static void main(String[] args) throws MyException {
        MyException me1 = new MyException(); // 예외 객체 생성
        MyException me2 = new MyException("예외 메시지");

        try {
            throw me2;
        } catch (MyException e) {
            System.out.println(e.getMessage());
            e.printStackTrace(); // 호출됐었던 클래스를 역순으로 추적해서 뿌려주기
        }
    }
}

e.printStackTrace(); // 호출됐었던 클래스를 역순으로 추적해서 뿌려주기 -> 개발 보안에서 필요하다 !

StackTrace 예시 ⬇️

JVM이 보여준다 ⬇️

package com.test;

class A {
    void abc() throws NumberFormatException {
        bcd();
    }

    void bcd() throws NumberFormatException {
        cde();
    }

    void cde() throws NumberFormatException {
        Integer.parseInt("하나둘셋");
    }
}
public class MyTest {
    public static void main(String[] args) {
        A a = new A();
        a.abc(); // 이렇게 하면 JVM이 보여주는 것이다.
    }
}

main 내부에서 보여주려면 ⬇️

public class MyTest {
    public static void main(String[] args) {
        A a = new A();
        try {
            a.abc();
        } catch(NumberFormatException e) {
            e.printStackTrace(); // 이렇게 하면 main 내부에서 보여주는 것이다.
        }
    }
}

함수의 호출 순서를 역순으로 볼 수 있다 !!!

사용자 정의 예외 클래스 사용 예 ⭐️🩷⭐️🩷⭐️🩷⭐️🩷

score 변수 => 점수를 저장 => 0~100값만 대입이 가능 => 범위 밖의 값을 입력했을 때 예외를 발생 => 음수인 경우 MinusException을, 100을 초과하는 경우 OverException을 발생

package com.test;

class MinusException extends Exception {
    MinusException() {

    }
    MinusException(String s) {
        super(s);
    }
}

class OverException extends Exception {
    OverException() {

    }
    OverException(String s) {
        super(s);
    }
}


class A {
    void checkScore(int score) throws MinusException, OverException{
        if (score < 0)
            throw new MinusException("음수값 입력");
        else if (score > 100)
            throw new OverException("100점 초과");
        else
            System.out.println("정상적인 값입니다.");
    }
}

public class MyTest {
    public static void main(String[] args) {
        A a = new A();
        try {
            a.checkScore(55);
            a.checkScore(105);
        } catch (MinusException | OverException e) {
            System.out.println(e.getMessage());
        }

        try {
            a.checkScore(55);
            a.checkScore(-100);
        } catch (MinusException | OverException e) {
            System.out.println(e.getMessage());
        }
    }
}


1-c / 2-f / 3-g / 4-e / 5-d / 6-b / 7-a

0개의 댓글