앞쪽 내용 중요 ⭐️⭐️⭐️⭐️⭐️
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를 가리킴
// 인스턴스 변수: 그대로 유지가 된다.
}
}
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은 오버라이드 되지 않는다.
}
}
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
}
}
반복을 줄일 수 있다.
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("자식 클래스에서 필요한 기능을 수행");
}
}
=> 생성자 내부에서만 사용이 가능하고, 반드시 첫 줄에 위치해야 함
=> 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 클래스를 상속받는다.
=> 아무런 클래스를 상속받지 않으면 컴파일러가 자동으로 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();
스택 메모리의 값(주소)을 비교
== (등가 비교 연산자)와 동일한 결과
문자열에서는 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() 메서드를 오버라이딩해서 사용
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); // 이름은 안녕입니다.
}
}
=> 객체의 위치값을 기준으로 생성한 고유값
=> 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} 이렇게 나타난다.
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 지역변수 => 한 번 대입된 값을 수정할 수 없음
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 변수는 선언과 동시에 초기화하든지, 생성자에서 반드시 초기화해야 한다 ‼️
=> 자식 클래스에서 해당 메서드를 오버라이딩할 수 없다
class A {
void func() {
}
final void func2() {
}
}
class B extends A {
void func() {
}
// error. 재정의할 수 없습니다.
void func2() {
}
}
재정의를 막고자 할 때 final 메서드를 쓸 수 있다.
=> 상속할 수 없는 클래스
final class A {
}
// error. final 'com. test. A'으로부터 상속할 수 없습니다
class B extends A {
}
구체적이지 않은 것이 추상적이다.
=> 구체적인 동작은 정의하지 않은 것
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();
}
이렇게 추상 클래스로 명시하면 잘못된 구현을 방지할 수 있다. 이렇게 사용하자 🩷
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(); // 야옹
}
}
=> 한 번만 만들어서 사용할 경우
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. 인터페이스는 타입이 정해져있는데 클래스는 일반 메서드도 갖고 있기 때문에 상속 받을 수 없다.
interface A {
void abc();
}
// error. 클래스 'B'은(는) abstract로 선언되거나 'A'에서 추상 메서드 'abc()'을(를) 구현해야 합니다
class B implements A {
}
인터페이스의 추상 메서드를 구현하지 않으면 추상 클래스(abstract)로 정의해야 한다. ⬇️
interface A {
void abc();
}
abstract class B implements A {
}
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");
}
}
반드시 상속을 통해 구현 메서드로 구현한 것을 활용해 인스턴스로 만들어야 한다.
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(); // 반드시 상속을 통해 구현 메서드로 구현한 것을 활용해 인스턴스로 만들어야 한다.
}
}
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();
}
}
인터페이스는 프로그램을 추상화하는 방법이다. 아주 유연해진다!
=> 하위 호환성을 보장하기 위해서 추가
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 메서드
클래스 내부의 정적 메서드와 동일
인터페이스명.정적메서드명()
방식으로 호출해서 사용
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에서 걸러지므로 가장 마지막에 넣어줘야 한다.
올바른 예외 처리 ⇒ 발생 가능한 예외를 세분화해서 발생할 수 있는 순서대로 기술
원래 : 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 괄호 안에 자원 생성 구문을 넣어주면 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) {
}
}
}
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 예외클래스명 {
// 예외 발생 코드
}
전가된 예외는 최상위 메서드인 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();
// 호출됐었던 클래스를 역순으로 추적해서 뿌려주기 -> 개발 보안에서 필요하다 !
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