static
public static void main(String[] args) {
}
public은 접근 제한자이고 void는 반환 자료형입니다. 그러면 static이 무엇일까요?

메서드 영역에는 클래스 변수와 클래스 메서드 같은 클래스 정보가 저장됩니다. 이는 프로그램이 시작될 때 메서드 영역을 할당받고 프로그램이 종료될 때까지 유지됩니다.
클래스 변수와 클래스 메서드는 앞에 static 키워드가 붙습니다. 즉, 이 키워드는 해당 클래스 자체에 속함을 의미합니다.
main() 메서드는 왜 static을 붙일까요?
main() 메서드는 프로그램의 시작점입니다. 인스턴스 메서드는 객체가 있어야 호출할 수 있습니다. 그러나 프로그램 실행 직후에는 생성된 객체가 없습니다. 그리하여 객체 없이도 호출할 수 있도록 main() 메서드에 항상 static을 붙입니다.
public static void main(String[] args) 에서 String[] args 는 main() 메서드를 호출할 때 전달하는 인자를 받는 매개변수입니다. 문자열 배열을 받는다는 뜻입니다. 변수명은 args 대신 다른 이름을 써도 되지만, 통상적으로 args를 사용합니다.
접근 제한자
객체지향 프로그래밍 언어는 캡슐화라는 특징이 존재합니다. 그리하여 클래스에서는 특정 메서드를 통해서만 접근하도록 형식을 구현할 수 있습니다. 이는 접근 제한자를 사용하여 조절합니다.
접근 제한자는 클래스와 클래스 구성 요소에 접근할 수 있는 범위나 권한을 나타냅니다.
- public : 같은 패키지든 다른 패키지든 상관없이 어디서든 접근할 수 있습니다. 클래스, 필드, 메서드, 생성자에 모두 사용할 수 있습니다.
- protected : 같은 패키지의 클래스나 다른 패키지의 상속받은 클래스에서 접근할 수 있습니다. 클래스에는 사용할 수 없고, 필드, 메서드, 생성자에 사용할 수 있습니다.
- private : 같은 클래스에서만 접근할 수 있습니다. 클래스에는 사용할 수 없고, 필드, 메서드, 생성자에 사용할 수 있습니다.
- default : 접근 제한자를 따로 명시하지 않으면 자동으로 default 권한이 적용됩니다. 같은 패키지의 클래스에서만 접근할 수 있고, 클래스, 필드, 메서드, 생성자 모두 가능합니다.
접근 허용 강도는 public > protected > default > private 순입니다.

접근 제한자를 설정할 때 public 접근 제한자로 설정하면 클래스명과 파일명이 동일해야 합니다.
게터 메서드와 세터 메서드
자바에서는 게터와 세터 메서드를 제공해 접근이 제한된 필드인 private에 우회해 접근할 수 있는 방법을 제공합니다.
게터 메서드
- private 접근 제한자가 붙은 필드를 외부에서 읽을 수 있게 하는 메서드입니다.
- 게터 메서드는 외부에서 접근할 수 있도록 public으로 선언합니다. 반환형은 접근하려는 필드의 자료형과 같습니다. 그리고 접두사 get을 결합하여 메서드명을 작성합니다.
- 게터 메서드는 매개변수가 없고 읽어온 필드 값을 return 키워드로 반환하는 기능만 있습니다.
public class Car {
private int maxSpeed; // 최대 속도
// 생성자
public Car(int maxSpeed) {
this.maxSpeed = maxSpeed;
countOfCars++;
}
// 게터 메서드
public int getMaxSpeed() {
return maxSpeed;
}
}
public class Main {
public static void main(String[] args) {
Car newCar = new Car(250); // 객체 생성
System.out.println("newCar의 최대 속도: " + newCar.getMaxSpeed());
}
}
세터 메서드
- 세터 메서드는 private 으로 선언한 필드의 값을 외부에서 설정하거나 변경할 수 있는 메서드입니다.
- 외부에서 접근할 수 있도록 public으로 선언합니다. 값을 반환하지 않기 때문에 반환형은 void입니다. 그리고 접두사 set과 필드명을 조합하여 메서드명을 짓습니다.
public class Car {
private int maxSpeed; // 최대 속도
// 생성자
public Car(int maxSpeed) {
this.maxSpeed = maxSpeed;
}
// 게터 메서드
public int getMaxSpeed() {
return maxSpeed;
}
// 세터 메서드
public void setMaxSpeed(int maxSpeed) {
this.maxSpeed = maxSpeed;
}
}
public class Main {
public static void main(String[] args) {
Car newCar = new Car(250); // 객체 생성
System.out.println("newCar의 최대 속도: " + newCar.getMaxSpeed());
newCar.setMaxSpeed(300);
System.out.println("변경 후 newCar의 최대 속도: " + newCar.getMaxSpeed());
}
}
상속
상속(inheritance)은 한 클래스가 다른 클래스의 필드와 메서드를 이어받아 사용하고 기능을 확장할 수 있는 개념입니다. 상속하는 클래스는 부모 클래스이고 상속받는 클래스는 자식 클래스입니다.
상속받은 자식 클래스는 부모 클래스에 작성된 내용을 물려받아 재사용할 수 있으므로 중복되는 필드나 메서드를 줄일 수 있는 장점이 있습니다.
상속받는 자식 클래스 뒤에 extends 키워드로 부모 클래스를 연결하면 됩니다.
class 자식_클래스명 extends 부모_클래스명 {
// 클래스 본문
}
class Animal{}
class Dog extends Animal {}
class Cat extends Animal {}
클래스 상속하기
package ch08;
public class Dog {
// 필드
String breed; // 견종
String name; // 이름
int age; // 나이
// 메서드
public void eat() {
System.out.println(name + "가 밥을 먹습니다.");
}
public void roll() {
System.out.println(name + "가 바닥을 구릅니다.");
}
}
package ch08;
public class Cat {
String name;
int age;
public void eat() {
System.out.println(name + "가 밥을 먹습니다.");
}
public void rub() {
System.out.println(name + "가 몸을 비빕니다.");
}
}
위 코드를 보면 Dog와 Cat 클래스에는 name, age 필드와 eat() 메서드가 공통으로 들어 있습니다. 그래서 부모 클래스로 만들어서 상속하게 할 수 있습니다. 부모 클래스인 Animal 클래스를 생성합니다.
package ch08;
public class Animal {
// 필드
String name;
int age;
// 메서드
public void eat() {
System.out.println(name + "가 밥을 먹습니다.");
}
}
Dog, Cat 클래스에서 extends 키워드로 Animal 클래스를 상속받습니다. 이후 공통 부분을 삭제합니다.
package ch08;
public class Dog extends Animal {
String breed; // 견종_Dog 클래스에만 있는 필드
// Dog 클래스에만 있는 메서드
public void roll() {
System.out.println(name + "가 바닥을 구릅니다.");
}
}
package ch08;
public class Cat extends Animal { // Animal 클래스 상속
// Cat 클래스에만 있는 메서드
public void rub() {
System.out.println(name + "가 몸을 비빕니다.");
}
}

자식 클래스에서 부모 클래스에 있는 필드와 메서드에 접근하는 방법에 대해 확인해봅시다.
1. Main 클래스를 생성하고 내부에 main() 메서드를 선언합니다.
package ch08;
public class Main {
public static void main(String[] args) {
}
}
2. main() 메서드에서 Dog와 Cat 클래스의 객체를 생성합니다.
상속 관계가 있으면 객체를 생성할 때 상속한 클래스의 생성자가 먼저 호출됩니다. 즉, 부모 클래스인 Animal 클래스의 생성자가 호출되고 이후 Dog와 Cat 클래스의 생성자가 호출됩니다.
package ch08;
public class Main {
public static void main(String[] args) {
Dog myDog = new Dog(); // 강아지 객체 생성
Cat myCat = new Cat(); // 고양이 객체 생성
}
}
3. Dog와 Cat 클래스의 객체 주소를 저장한 참조 변수 myDog와 myCat으로 객체의 필드에 접근합니다.
접근 제한자는 default입니다.
package ch08;
public class Main {
public static void main(String[] args) {
Dog myDog = new Dog(); // 강아지 객체 생성
Cat myCat = new Cat(); // 고양이 객체 생성
myDog.breed = "진돗개";
myDog.name = "바둑이";
myDog.age = 3;
System.out.println(myDog.name + "는 " + myDog.breed + "이고, " + myDog.age + "살입니다.");
myCat.name = "나비";
myCat.age = 2;
System.out.println(myCat.name + "는 " + myCat.age + "살입니다.");
}
}
4. Animal 클래스에 정의한 메서드에도 접근해 봅시다.
package ch08;
public class Main {
public static void main(String[] args) {
Dog myDog = new Dog(); // 강아지 객체 생성
Cat myCat = new Cat(); // 고양이 객체 생성
myDog.breed = "진돗개";
myDog.name = "바둑이";
myDog.age = 3;
System.out.println(myDog.name + "는 " + myDog.breed + "이고, " + myDog.age + "살입니다.");
myCat.name = "나비";
myCat.age = 2;
System.out.println(myCat.name + "는 " + myCat.age + "살입니다.");
myDog.eat();
myDog.roll();
myCat.eat();
myCat.rub();
}
}
메서드 오버라이딩
자식 클래스가 부모 클래스를 상속받으면 메서드를 그대로 사용합니다. 그러나 자식 클래스에서 다른 동작을 수행하도록 메서드를 변형해서 사용할 수 있습니다.
상속받은 메서드를 재정의 할 수 있으며, 이를 메서드 오버라이딩이라고 합니다.
단, 오버라이딩한 메서드는 부모 클래스의 메서드와 이름, 반환형, 매개변수의 개수와 순서, 자료형이 모두 같아야 합니다.
package ch08;
public class Cat extends Animal { // Animal 클래스 상속
// Cat 클래스에만 있는 메서드
public void rub() {
System.out.println(name + "가 몸을 비빕니다.");
}
@Override // 오버라이딩 애너테이션
public void eat() { // 메서드 오버라이딩
System.out.println(name + "가 닭고기를 먹습니다.");
}
}
자식 클래스에서 부모 클래스의 메서드를 재정의하면 자식 클래스의 메서드에 우선순위가 있습니다. 즉, 자식 클래스의 메서드가 호출됩니다.
자바는 메서드 오버라이딩을 통해 같은 매서드를 객체마다 다르게 동작하게 함으로써 다형성을 구현할 수 있습니다.
다형성은 하나의 객체나 메서드가 여러 형태로 사용될 수 있는 특성입니다.
애너테이션 : 자바에서 메타데이터를 클래스, 메서드, 변수 등에 추가하는 방법으로 주석이라고 생각하면 됩니다. 이는 어떤 메서드가 부모 클래스에서 오버라이딩되었는지 확인할 수 있고 코드의 가독성을 높이고, 유지 보수를 용이하게 합니다.
메서드 오버로딩
메서드 오버로딩은 메서드명은 같지만 매개변수의 개수와 순서, 자료형이 다른 메서드를 같은 클래스 안에 여러 개 정의하는 것입니다.
package ch08;
public class Dog extends Animal {
String breed; // 견종_Dog 클래스에만 있는 필드
// Dog 클래스에만 있는 메서드
public void roll() {
System.out.println(name + "가 바닥을 구릅니다.");
}
public void roll(int times) {
System.out.println(name + "가 바닥을 " + times + "번 구릅니다.");
}
}
package ch08;
public class Main {
public static void main(String[] args) {
(중략)
myDog.eat();
myDog.roll(2);
myCat.eat();
myCat.rub();
}
}
roll() 메서드를 호출할 때 인자를 넣어 전달하면 Dog 클래스에서 인자 개수와 매개변수 개수가 일치하는 메서드를 찾아 실행합니다. 즉, 기존 roll() 메서드가 아닌 매개변수가 1개인 roll() 메서드가 실행됩니다.
오버라이딩은 상속 관계에서 일어나고 오버로딩은 한 클래스 안에서 일어납니다. 또한, 오버라이딩은 메서드 시그니처가 같지만, 오버로딩은 매개변수의 개수나 순서, 자료형이 다릅니다.
업캐스팅 (형변환)
업캐스팅(upcasting)은 자식 클래스의 객체를 부모 클래스형으로 변환하는 것을 의미합니다.
Dog myDog = new Dog(); // Dog 클래스의 참조 변수에 할당
Cat myCat = new Cat(); // Cat 클래스의 참조 변수에 할당
상속 관계에서는 아래와 같이 업캐스팅으로 객체를 생성할 수 있습니다.
Animal myAnimal1 = new Dog(); // Dog 클래스 객체를 Animal 클래스형의 참조 변수에 할당
Animal myAnimal2 = new Cat(); // Cat 클래스 객체를 Animal 클래스형의 참조 변수에 할당
컴파일러에 의해 자식 클래스의 객체가 부모 클래스형으로 자동 형변환됩니다.
이때 업캐스팅된 참조 변수로는 부모 클래스에 선언된 필드와 메서드만 접근할 수 있습니다. 자식 클래스에 정의한 필드와 메서드에는 접근할 수 없습니다.
다만, 자식 클래스에 오버라이딩된 메서드가 있다면 해당 메서드에는 접근할 수 있습니다.
public class Main {
public static void main(String[] args) {
Animal myAnimal = new Dog();
myAnimal.name = "바둑이";
myAnimal.eat();
myAnimal = new Cat();
myAnimal.name = "나비";
myAnimal.eat();
}
}
public class Main {
public static void main(String[] args) {
Animal myAnimal = new Dog();
myAnimal.name = "바둑이";
myAnimal.eat(); // Animal 클래스의 eat() 메서드 호출
myAnimal.roll();
myAnimal = new Cat();
myAnimal.name = "나비";
myAnimal.eat(); // Cat 클래스의 eat() 메서드 호출
myAnimal.rub();
}
}
roll() 과 rub() 메서드는 모두 자식 클래스에 정의된 메서드로 오버라이딩된 메서드가 아닙니다. 즉, myAnimal.roll(); 과 myAnimal.rub(); 코드에 의해 오류가 발생합니다.
업캐스팅을 하는 이유는 부모 클래스 하나로 여러 자식 클래스의 객체를 다룰 수 있기 때문입니다.
다운캐스팅
다운캐스팅은 부모 클래스형 객체를 자식 클래스형으로 형변환하는 것을 의미합니다.
public class Main {
public static void main(String[] args) {
Animal myAnimal = new Dog();
myAnimal.name = "바둑이";
myAnimal.eat(); // Animal 클래스의 eat() 메서드 호출
Dog myDog = (Dog)myAnimal;
myDog.roll();
myAnimal = new Cat();
myAnimal.name = "나비";
myAnimal.eat(); // Cat 클래스의 eat() 메서드 호출
Cat myCat = (Cat)myAnimal;
myCat.rub();
}
}
자바에서 메서드를 호출할 때 참조 변수를 선언한 클래스 기준으로 메서드를 찾습니다.
myAnimal로 호출할 수 있는 메서드는 Animal 클래스에 정의된 메서드뿐입니다. 즉, Dog나 Cat 클래스에만 정의된 메서드 roll()과 rub() 메서드는 Animal형 참조 변수로는 직접 호출할 수 없습니다. 그러나 다운캐스팅하면 해당 객체에 정의된 메서드를 호출할 수 있습니다.
super
this는 객체를 참조할 때 사용하면 super는 부모 클래스를 참조하는 데 사용합니다.
1. super 키워드는 자식 클래스에서 부모 클래스의 메서드를 호출할 때 사용합니다.
Cat 클래스의 eat() 메서드를 호출할 때, Animal 클래스의 eat() 메서드도 함께 호출하고자 할 때 super 키워드를 사용할 수 있습니다. 즉, super.eat() 으로 부모 클래스의 eat() 메서드를 호출합니다.
package ch08;
public class Cat extends Animal { // Animal 클래스 상속
// Cat 클래스에만 있는 메서드
public void rub() {
System.out.println(name + "가 몸을 비빕니다.");
}
@Override // 오버라이딩 애너테이션
public void eat() { // 메서드 오버라이딩
super.eat(); // 부모 클래스의 eat() 메서드 호출
System.out.println(name + "가 닭고기를 먹습니다.");
}
}
그 후, main() 메서드를 실행하면 cat.eat() 으로 Cat 클래스의 eat() 메서드가 실행됩니다. 이때 eat() 메서드에서 호출한 super.eat() 때문에 Animal 클래스의 eat() 메서드가 먼저 실행되어 출력되고 Cat 클래스에 오버라이딩된 eat() 메서드만의 동작이 실행되어 출력됩니다.
package ch08;
public class Main {
public static void main(String[] args) {
Dog myDog = new Dog(); // 강아지 객체 생성
Cat myCat = new Cat(); // 고양이 객체 생성
myDog.breed = "진돗개";
myDog.name = "바둑이";
myDog.age = 3;
System.out.println(myDog.name + "는 " + myDog.breed + "이고, " + myDog.age + "살입니다.");
myCat.name = "나비";
myCat.age = 2;
System.out.println(myCat.name + "는 " + myCat.age + "살입니다.");
myDog.eat();
myDog.roll(2);
myCat.eat();
myCat.rub();
}
}

2. super 키워드는 자식 클래스의 생성자에서 부모 클래스의 생성자를 호출할 때 사용합니다.
자식 클래스의 생성자에서 부모 클래스의 생성자를 호출할 때 부모 클래스에는 명시적 생성자가 있어야 합니다. 생성자에는 매개변수로 값을 받아 공통 필드를 초기화합니다. 이때 this 키워드로 매개변수와 필드를 구분합니다.
package ch08;
public class Animal {
// 필드
String name;
int age;
// Animal 클래스의 생성자
public Animal(String name, int age) {
this.name = name;
this.age = age;
System.out.println("이름: " + this.name + "\n나이: " + this.age);
}
// 메서드
public void eat() {
System.out.println(name + "가 밥을 먹습니다.");
}
}
Dog 클래스에도 생성자를 정의합니다. 이때 공통 필드인 name, age는 super 키워드로 Animal 클래스의 생성자를 호출해 초기화합니다. 그리고 고유 필드인 breed만 Dog 클래스의 생성자에서 this 키워드로 초기화합니다.
package ch08;
public class Dog extends Animal {
String breed; // 견종_Dog 클래스에만 있는 필드
public Dog(String name, int age, String breed) { // Dog 클래스의 생성자
super(name, age); // 부모 클래스의 생성자 호출
this.breed = breed;
System.out.println("품종: " + this.breed);
}
// Dog 클래스에만 있는 메서드
public void roll() {
System.out.println(name + "가 바닥을 구릅니다.");
}
public void roll(int times) {
System.out.println(name + "가 바닥을 " + times + "번 구릅니다.");
}
}
Cat 클래스에도 생성자를 정의합니다.
package ch08;
public class Cat extends Animal { // Animal 클래스 상속
public Cat(String name, int age) { // Cat 클래스의 생성자
super(name, age);
}
// Cat 클래스에만 있는 메서드
public void rub() {
System.out.println(name + "가 몸을 비빕니다.");
}
@Override // 오버라이딩 애너테이션
public void eat() { // 메서드 오버라이딩
super.eat(); // 부모 클래스의 eat() 메서드 호출
System.out.println(name + "가 닭고기를 먹습니다.");
}
}
package ch08;
public class Main {
public static void main(String[] args) {
Dog myDog = new Dog("바둑이", 3, "진돗개"); // 강아지 객체 생성
Cat myCat = new Cat("나비", 2); // 고양이 객체 생성
myDog.eat();
myDog.roll(2);
myCat.eat();
myCat.rub();
}
}
3. super 키워드는 부모 클래스의 필드에 접근할 때 사용합니다.
Animal 클래스에는 name 이라는 필드가 있으며, 동물로 초기화되어 있습니다.
public class Animal {
// 부모 클래스의 필드
String name = "동물";
public void printName() {
System.out.println("부모 클래스의 name: " + name);
}
}
Dog 클래스에서 자식 클래스의 필드는 this.name으로, 부모 클래스의 필드는 super.name으로 접근합니다.
package ch08;
public class Dog extends Animal {
// 자식 클래스의 필드(부모 클래스와 동일한 이름)
String name = "강아지";
public void displayNames() {
// 자식 클래스의 name 필드 출력
System.out.println("자식 클래스의 name: " + this.name);
// 부모 클래스의 name 필드 출력
System.out.println("부모 클래스의 name: " + super.name);
}
}
super 키워드를 사용해 부모 클래스의 필드에 접근할 수 있습니다. 또한, 부모 클래스와 자식 클래스의 필드가 이름이 같을 때 super 키워드로 둘을 구분할 수 있습니다.
public class Main {
public static void main(String[] args) {
Dog myDog = new Dog();
// 자식 클래스와 부모 클래스의 필드 출력
myDog.displayNames();
// 부모 클래스의 메서드 호출(부모 클래스의 name 필드 사용)
myDog.printName();
}
}
추상 클래스
추상(abstraction)은 여러가지 사물이나 개념에서 공통되는 특성이나 속성 따위를 추출해 파악하는 작용
추상적(abstract)은 어떤 사물이 일정한 형태와 성질을 갖추고 있지 않은 것
즉, 추상 클래스(abstract class)는 일반 클래스의 공통 부분을 추출해서 만든 완전하지 않은 클래스입니다.
추상 클래스의 특징
- 추상 클래스 앞에 abstract 키워드를 붙여 선언합니다.
- 추상 메서드를 포함합니다.
- 추상 메서드는 메서드의 시그니처만 선언하고, 어떤 동작을 하는지 구체적인 내용을 정의하지 않은 메서드입니다. 그래서 추상 클래스를 상속받은 클래스는 반드시 추상 메서드를 오버라이딩해서 동작을 구현해야 합니다.
- 추상 메서드 앞에 abstract 키워드가 붙습니다.
- 추상 클래스는 new 키워드를 사용해 직접 객체를 생성할 수 없습니다.
- 객체는 상속받는 자식 클래스에서만 생성할 수 있습니다.
- 추상 클래스는 추상 메서드는 물론 동작까지 정의된 일반 메서드를 가질 수 있습니다.
[접근_제한자] abstract class 클래스명 {
// 필드
// 일반 메서드
abstract 반환형 메서드명(매개변수1, 매개변수2...); // 추상 메서드
}
추상 클래스를 사용하는 이유는 코드의 가독성을 높이고 효율성과 생산성을 향상시키기 위해서입니다.
선언(declaration)은 변수, 메서드, 클래스 등의 이름과 타입을 정의하는 것입니다.
구현(implementation)은 선언한 메서드, 클래스가 실제 수행하는 기능을 정의하는 것입니다.
제가 추상 클래스를 공부해보았지만 해당 부분이 잘 이해가 되지 않아 실습 이후에 한번 더 공부해보아야 겠다고 생각하였습니다.
인터페이스
자바에서는 여러 클래스로부터 상속받는 다중 상속을 허용하지 않습니다. 왜냐하면 자식 클래스는 어떤 클래스의 메서드를 사용해야 하는지에 대해 헷갈리기 때문입니다.
자바에서는 다중 상속을 지원하지 않는 대신 유사한 기능을 제공하는 인터페이스를 도입했습니다. 인터페이스(interface)는 클래스에서 구현할 메서드들이 선언된 집합입니다.
인터페이스 특징
1. 인터페이스는 앞에 interface 키워드를 붙여 선언합니다.
2. 인터페이스에는 메서드 선언만 있고 구현은 없는 추상 메서드를 포함합니다. 인터페이스에 선언한 메서드는 abstract를 붙이지 않아도 자동으로 추상 메서드로 인식됩니다.
3. 인터페이스는 디폴트 메서드와 정적 메서드를 포함할 수 있습니다. 디폴트 메서드는 이름 앞에 default 키워드가, 정적 메서드는 static 키워드가 붙습니다. 둘 다 메서드 동작이 구현돼 있지만, 정적 메서드는 클래스에서 오버라이딩 할 수 없습니다.
4. 인터페이스는 상수만 포함할 수 있으며, 선언하면 자동으로 public, static, final 키워드가 붙습니다. 상수에 접근할 때는 인터페이스명을 사용합니다.
6. 인터페이스는 접근 제한자로 public과 default만 사용할 수 있습니다. 인터페이스에 선언한 모든 메서드는 자동으로 public으로 설정됩니다.
[접근_제한자] interface 인터페이스명 {
자료형 변수명 = 값; // 상수
반환형 메서드명(매개변수1, 매개변수2..._ {}; // 추상 메서드
default 반환형 메서드명(매개변수1, 매개변수2...) {}; // 디폴트 메서드
static 반환형 메서드명(매개변수1, 매개변수2...) {}; // 정적 메서드
}
7. 구현 클래스에는 implements 키워드로 구현할 인터페이스를 명시합니다. 다른 클래스의 자식 클래스라면 implements 키워드와 인터페이스 이름을 부모 클래스 뒤에 추가합니다. 구현할 인터페이스가 여럿일 때는 쉼표로 구분합니다.
class 클래스명 implements 인터페이스명 {}
class 자식_클래스명 extends 부모_클래스명 implements 인터페이스1, 인터페이스2 {}
8. 인터페이스는 인터페이스끼리 상속할 수 있습니다. 단, implemets 대신 extends 키워드를 사용합니다.
interface 자식_인터페이스명 extends 부모_인터페이스 {}

오류(error)
오류는 프로그램에서 발생할 수 있는 심각한 문제로, 제어할 수 없고 정상적인 실행도 불가능한 상황을 의미합니다.
오류는 크게 3가지로 나눌 수 있습니다.
- 컴파일 타입 오류 : 컴파일 과정에서 컴파일러가 이해할 수 없는 코드가 있을 때 발생하는 오류입니다. 문법 오류(syntax error), 타입 오류(type error), 참조 오류(reference error) 등이 해당합니다.
- 런타임 오류 : 프로그램이 실행 중일 때 발생하는 오류입니다. 프로그램이 실행되는 중에 처리할 수 없는 연산을 만나 비정상적으로 종료되는 경우입니다.
- 논리 오류 : 컴파일과 실행은 정상적으로 되지만, 결과가 의도한 대로 나오지 않아서 발생하는 오류입니다. 직접 오류를 찾아야 해서 해결하기가 어렵습니다.
버그(bug)는 프로그램이 의도한 대로 작동하지 않거나 갑자기 실행이 중단되며 오작동하는 것을 의미합니다.
디버깅(debugging)은 이러한 버그의 원인을 찾아 수정하는 작업입니다.
예외(exception)
예외는 프로그램을 컴파일하고 실행하는 도중에 문제가 발생하는 경우 예방 조치를 취할 수 있음을 의미합니다.
예외 처리는 프로그램을 실행할 때 발생할 수 있는 예외에 대비한 코드를 작성해 프로그램의 비정상 종료를 막고 프로그램이 계속 실행될 수 있게 하는 작업을 의미합니다.
예외는 컴파일러가 예외 처리를 확인하는지 안 하는지에 따라 다음과 같이 나누어집니다.
- 확인 예외 : 컴파일러가 자바 소스 코드를 컴파일하는 과정에서 예외를 확인하고 예외 처리를 하는지 검사합니다. 예외 처리를 하지 않으면 컴파일 오류가 발생합니다. 그래서 컴파일 예외라고도 합니다.
- 미확인 예외 : 컴파일 과정에서 예외 처리를 확인하지 않으며, 명시적으로 예외 처리를 하도록 강제하지도 않습니다. 런타임 예외라고도 합니다.
try-catch-finally 문으로 예외 처리하기
자바에서 예외 처리하는 방법 중 하나입니다.
try {
// 예외가 발생할 수 있는 코드
} catch (예외_클래스 변수) {
// 해당 예외가 발생했을 때 수행할 동작
} finally {
// 예외와 상관없이 항상 수행할 동작
}
try 블록 안에는 예외가 발생할 수 있는 코드를 작성합니다. try 블록 안에서 예외가 발생하면 이후 코드는 수행하지 않고 바로 catch 블록으로 넘어갑니다.
catch 블록은 try 블록에서 예외가 발생했을 때 실행되는 코드를 작성합니다. catch 블록은 else if처럼 여러 개를 작성해 다양한 예외 처리를 할 수 있습니다. try 블록에서 작성한 예외와 일치하는 catch 블록이 없으면 예외를 처리하지 못하고 프로그램이 종료됩니다.
finally 블록은 필수 요소는 아닙니다. 예외와 상관없이 무조건 실행해야 하는 코드가 있을 때 finally 블록 안에 작성합니다. try 블록에서 예외가 발생하지 않으면 try 블록을 수행한 후 finally 블록으로 넘어갑니다.
import java.util.Scanner;
public class TryCatchExample {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
System.out.println("첫 번째 정수를 입력하세요. --> ");
int a = scan.nextInt();
System.out.println("두 번째 정수를 입력하세요. --> ");
int b = scan.nextInt();
int result = a / b;
System.out.println("결과: " + result);
}
}
위 코드에서 대부분의 정수를 입력하였을 때는 아무 문제가 없이 출력됩니다. 그러나 두 번째 정수에 0을 입력하면 예외가 발생합니다. 메시지는 RuntimeException 중 하나인 ArithmeticException 예외가 발생했다고 나옵니다. 즉, 이는 0으로 나누기를 해서 발생한 예외입니다. 이러한 경우에 try-catch 문을 사용하여 예외 처리를 할 수 있습니다.
import java.util.Scanner;
public class TryCatchExample {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
System.out.println("첫 번째 정수를 입력하세요. --> ");
int a = scan.nextInt();
System.out.println("두 번째 정수를 입력하세요. --> ");
int b = scan.nextInt();
try {
int result = a / b;
System.out.println("결과: " + result);
} catch (ArithmeticException e) {
System.out.println(e);
} finally {
System.out.println("프로그램을 종료합니다.");
}
}
}
위 코드도 예외가 발생합니다. try 블록에서 예외가 발생하면 발생한 예외 정보를 e 객체에 저장합니다. 예외 객체는 catch 블록으로 보냅니다. 그 후, catch 블록 안에 있는 내용을 수행합니다. 마지막으로 finally 블록에 있는 문장을 실행하고 프로그램을 종료합니다.
throw로 예외 발생시키기
자바에서 예외 처리하는 방법 중 하나입니다.
throw new 예외_클래스([예외_메시지]);
코드에 의도적으로 예외를 발생시킬 수도 있습니다. 이 때, throw 키워드를 사용합니다. throw 키워드는 주로 RuntimeException 예외를 처리할 때 사용합니다.
import java.util.Scanner;
public class TryCatchExample {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
System.out.println("첫 번째 정수를 입력하세요. --> ");
int a = scan.nextInt();
System.out.println("두 번째 정수를 입력하세요. --> ");
int b = scan.nextInt();
try {
if (b == 3) {
throw new ArithmeticException("3으로 나눌 수 없습니다!");
}
int result = a / b;
System.out.println("결과: " + result);
} catch (ArithmeticException e) {
System.out.println(e.getMessage());
} finally {
System.out.println("프로그램을 종료합니다.");
}
}
}
throws로 예외 처리 넘기기
자바에서 예외 처리하는 방법 중 하나입니다.
throws 키워드를 사용하면 해당 메서드에서 예외를 처리하지 않고 호출한 곳에서 처리하도록 예외 처리를 넘길 수 있습니다. throws 뒤에 예외 클래스를 필요한 만큼 넣을 수 있습니다.
반환형 메서드명(매개변수) throws 예외_클래스 {
// 수행할 내용
}
'Study > 코딩 자율학습단' 카테고리의 다른 글
[코딩 자율학습단 13기] 자바 입문 [4주차] (0) | 2025.03.19 |
---|---|
[코딩 자율학습단 13기] 자바 입문 [2주차] (0) | 2025.03.01 |
[코딩 자율학습단 13기] 자바 입문 [1주차] (0) | 2025.02.23 |