JAVA에 대한 기본 개념은 이미 알고 있지만, 혹시 놓치는 부분이 있을까 싶어 Programmers에서 제공하는 기초, 중급 강의를 듣고 정리해 볼 예정이다.
(강의 링크: https://school.programmers.co.kr/learn/courses/5/5-%EB%AC%B4%EB%A3%8C-%EC%9E%90%EB%B0%94-%EC%9E%85%EB%AC%B8)
헷갈리는 부분이나 자주 사용하지 않았던 부분, 새롭게 알게된 부분 등 작성자에 기준에 맞게 적을 예정이다.
객체지향언어 (Object Oriented Language)이며, C++ 문법을 기본으로 개발했다.
(C++은 C언어에 객체지향 특성을 확장한 Java 이전에 객체지향 언어로 가장 범용적인 언어)
JDK -> Java Development Kit
Oracle 사이트에서 JDK Download 가능
Java SE -> JDK 다운로드 가능
시스템 - 환경 변수 - 시스템 변수에 설정
변수 이름: JAVA_HOME
변수 경로: jdk 경로
변수 이름: CLASSPATH
변수 경로: .;%JAVA_HOME%\lib\tools.jar
변수 이름: Path
변수 경로: %JAVA_HOME%\bin;
위 3개의 환경 변수 지정
> javac FileName.java
> java FileName.class
Compiler는 작성한 파일을 실행 가능한 코드로 바꾸는 역할을 하고 이러한 과정을 컴파일이라함.
Code -> Compile 하는 과정에서 문제가 발생하면 Compile Error
// 행 단위 주석
//
// 블럭단위 주석
/**/
// 문서화 주석
/**
*
* @author Kimbumsoo99
* @param args
*/
한 번 저장한 데이터 값을 변경할 수 없는 것
상수는 대문자의 명명규칙을 사용한다.
대문자 선언
final int J;
J = 10; // 이렇게 한 번 선언하면 수정할 수 없음
long l = 214783648L;
float f = 32.4f;
byte < short, char < int < long < float < double
크기가 더 큰 타입으로 바꾸는 경우
int x = 30000;
long y = x;
크기가 더 작은 타입으로 바꾸는 경우
long x = 50000;
// int y = x; (Compile Error)
int y = (int) x;
연산자 | 예시 기호 |
---|---|
최우선 연산자 | . [] () |
단항 연산자 | ++ -- ! ~ +/- : 부정, bit변환>부호>증감 |
산술 연산자 | * / % > + - > shift/시프트 연산자 >> << >>> |
비교 연산자 | > < >= <= == != |
비트 연산자 | & |
논리 연산자 | && > |
삼항 연산자 | 조건식 ? 참 : 거짓 |
대입 연산자 | = *= /= %= += -= |
A
, B
가 피연산자인 경우
A | B | ----- | A&&B | A||B | !A | A^B |
---|---|---|---|---|---|---|
TRUE | TRUE | TRUE | TRUE | FALSE | FALSE | |
TRUE | FALSE | FALSE | TRUE | FALSE | TRUE | |
FALSE | TRUE | FALSE | TRUE | TRUE | TRUE | |
FALSE | FALSE | FALSE | FALSE | TRUE | FALSE |
자바에서 제공하는 문법
if
, else if
, else
switch
int b = (5 > 4) ? 50 : 40;
int value = 1;
switch(value){
case 1:
System.out.println("1");
break;
case 2:
System.out.println("2");
break;
case 3:
System.out.println("3");
default:
System.out.println("default");
자바에서 제공하는 문법
while
, do-while
for
int[] iarr = {10,20,30,40,50};
for(int value:iarr){
System.out.println(value);
}
int[] arr1 = new int[100];
int[] arr2 = new int[]{1,2,3,4};
int[] arr3 = {1,2,3,4];
int[][] arr1 = new int[3][4];
/*
0 0 0 0 -> arr1[0]
0 0 0 0 -> arr1[1]
0 0 0 0 -> arr1[2]
*/
int[][] arr2 = new int[3][]; // 가능하나 값을 지정하지 못함.
// arr2[0][0] = 10; -> 이렇게 바로 진행하면 Compile Error
arr2[0] = new int[1];
arr2[0][0] = 10;
자바는 객체지향 언어이다.
객체지향 언어: 프로그램을 구성하는 요소는 객체이며 이것이 상호작용 하도록 프로그래밍 한다.
객체를 만들기 위해서는 반드시 클래스를 만들어야함.
클래스 -> 객체를 만들기 위한 틀
public class ClassName{
// 구성 목록
public static void main(String[] args){
ClassName c = new ClassName();
}
}
위에 기본형 타입과 이어지는 느낌이다.
기본형 타입은 논리형, 문자형, 정수형, 실수형 4개가 존재했다.
이 기본형 타입은 Class가 아니다.
참조형 타입이란 기본형 타입을 제외한 모든 타입
String str = new String("Hello");
이 참조형 데이터는 값을 저장할 때 객체의 주소값을 저장(참조)한다.
배열도 참조형, 클래스도 모두 참조 타입이다.
new
키워드를 통해 객체를 메모리에 생성하면, 이러한 객체를 인스턴스라고 한다.
변수로 나타내면 메모리에 올라간 인스턴스를 가리키는 변수가 된다.
자바에서 가장 많이 사용되는 Class는 String
이라고 할 수 있다.
String str1 = "hello"; // String은 특별해서 이런식으로 생성 가능
String str2 = "hello";
String str3 = new String("Hello");
String str4 = new String("Hello");
// 위 str1, str2 -> 메모리에 상수 영역에 저장된다.
// "hello"가 상수 영역에 메모리가 저장되고, 해당 메모리를 str1과 str2는 같은 인스턴스를 참조
// str3, str4 -> 각각 다른 인스턴스 (Heap 영역에 생성)
System.out.print(str1 == str2); // 출력: 참 (같은 레퍼런스)
System.out.print(str1 == str3); // 출력: 거짓 (다른 레퍼런스)
System.out.print(str3 == str4); // 출력: 거짓 (다른 레퍼런스)
String str = new String("hello");
System.out.println(str.concat(" world")); //출력결과는 hello world
System.out.println(str); //출력결과는 hello
str = str.concat(" world");
System.out.println(str.substring(1, 3)); //출력결과 el
System.out.println(str.substring(2)); //출력결과 llo world
String에 차별화된 특징
1. 한 번 생성된 Class는 변하지 않는다.
2. new를 사용하지 않고 사용할 수 있다. (메모리 소모 절약)
String에는 많은 method가 존재한다.
Stirng str = "hello";
System.out.println(str.substring(3)); // 출력: lo (idx 3부터 출력)
System.out.println(str); // 출력: Hello (값 변화 X)
위에서 동작하는 substring()에 경우
값을 바꾸는게 아니라 새로운 String을 반환한다는 것을 알아야 한다.
Class내에서 변수처럼 사용되는 것들을 Field라고 한다.
class People
에서 name
, age
등이 field다.
객체가 가지는 속성 -> Field
public class Car{
String name;
int number;
}
필드가 물체의 상태라면, 물체의 행동에 해당하는게 메소드이다.
int x = 3, y = 4;
add(x, y); // Argument
int add(int a, int b); // Parameter
public class ValableScopeExam{
int globalScope = 10; // 인스턴스 변수
public void scopeTest(int value){
int localScope = 10;
System.out.println(globalScope);
System.out.println(localScpe);
System.out.println(value);
}
public static void main(String[] args) {
System.out.println(globalScope); // 오류
System.out.println(localScope); // 오류
System.out.println(value); // 오류
}
}
변수를 선언한 위치에 따라 사용가능한 Scope가 달라지게 된다.
이때 GlobalScope이더라도, main에서는 오류가 나타나는데 이유는 static한 메소드이기 때문이다.
static하다는 것은 클래스를 인스턴스화 하지 않고 사용할 수 있게하는 것인데, main함수는 static한 함수이기 때문에 이런 경우 static한 변수만 사용할 수 있다.
static하게 선언된 변수는 값을 저장할 수 있는 공간이 하나만 생성되어, 인스턴스가 여러개 생성되도 static한 변수는 하나다.
보통 사용은 ClassName.staticValue
로 사용한다.
JDK5에서 추가된 열거형이다. 이전에는 상수를 열거형 대신 사용했다.
상수를 사용했을때의 문제점 때문이다.
1. String으로 선언된 gender1 에는 MALE,FEMALE 둘 중 한가지 값을 갖기 원하는데, gender1의 type이 String 이기 때문에 gender1 = "소년"; 이렇게 수행 되어도 전혀 문제가 되지 않는다.
2. 실행할때 원했던 값인 MALE,FEMALE 이 아닌 다른 값이 들어오게 되므로 문제를 발생시킬 수 있다.
public class EnumExam {
public static final String MALE = "MALE";
public static final String FEMALE = "FEMALE";
public static void main(String[] args) {
String gender1;
gender1 = EnumExam.MALE;
gender1 = EnumExam.FEMALE;
gender1 = "boy"; // 오류가 없음
Gender gender2;
gender2 = Gender.MALE;
gender2 = Gender.FEMALE;
// gender2 = "boy"; // Compile Error
}
enum Gender{
MALE, FEMALE;
}
}
위 예시 코드와 같은 이유로 특정 값만 지녀야한다면 열거형을 사용하는 것이 좋다.
모든 클래스는 인스턴스화 될때 생성자를 사용한다.
생성자는 보통 객체가 될 때 필드를 초기화 하는 역할을 수행
public class Car{
String name;
int number;
public Car(String n){
name = n;
}
}
this는 현재 객체, 자기 자신을 나타낸다.
생성자 예시처럼 변수명을 다르게 선언할 수 있지만, 변수명은 공통된 것을 사용해야 의미를 파악하기 쉽다. 위 코드에 경우 매개변수인 n의 스코프가 global scope에 있는 name보다 우선순위가 더 높아 이름을 다르게 선언해야했는데 this
를 사용하면 그렇지 않아도 된다.
public Car(String name){
this.name = name;
}
변수 이름뿐아니라 Method를 사용할 때에도 this.methodName();
으로 호출할 수 있다.
오버로딩이란 매개변수의 유형과 개수가 다르게 하여 같은 이름의 메소드를 여러 개 가질 수 있게하는 기술이다.
class MyClass2{
public int plus(int x, int y){
return x+y;
}
public int plus(int x, int y, int z){
return x + y + z;
}
public String plus(String x, String y){
return x + y;
}
}
만약 변수명은 다르고 매개변수 타입과 개수가 동일하다면 정의할 수 없다.
위 메소드 오버로딩과 동일하다.
생성자도 결국 메소드이기 때문에 오버로딩을 통해 생성자를 여러개 생성할 수 있다.
public class Car{
String name;
int number;
public Car(){
}
public Car(String name){
this.name = name;
}
public Car(String name, int number){
this.name = name;
this.number = number;
}
}
이렇게 생성자를 여러개 사용할 수 있으며, this 키워드를 통해 생성자를 호출할 수도 있다.
public Car(){
this("이름없음", 0);
}
위 예시처럼 this()
를 사용하여 자신의 생성자를 호출할 수 있다.
이로써 얻는 장점으로는 비슷한 코드가 중복되서 나오는 것을 방지할 수 있다.
패키지(package)란 서로 관련이 있는 클래스 또는 인터페이스들을 묶어 놓은 묶음이다. 패키지를 사용함으로써 클래스들이 필요할 때만 사용될 수 있도록 하고, 클래스를 패키지 이름과 함께 계층적인 형태로 사용함으로써 다른 그룹에 속한 클래스와 발생할 수 있는 클래스 이름간의 충돌을 막아줌으로 클래스의 관리를 편하게 해준다.
.
을 통해서 하고, 숫자로 시작할 수 없다.com.eightcruz.javastudy.Hello
도메인 이름이 숫자로 시작되는 경우 8 -> eight
으로 바꾼 것 처럼 적절하게 수정하고, 도메인을 사용하는 이유는 패키지가 중복되는 것을 방지하기 위함이므로, 반드시 존재하는 도메인일 필요는 없다.
Java에서 다른 패키지에 있는 class를 사용해야 할 때가 존재하는데 다음과 같이 선언해줘야 한다.
import com.eightcruz.javastudy.Hello;
import java.util.*;
만약 import를 하기 싫다면 다음과 같이도 사용할 수 있다.
com.eightcruz.javastudy.Hello hello = new com.eightcruz.javastudy.Hello();
상속이란? 부모가 가진것을 자식에게 물려주는것을 의미한다.
is a
또는 kind of
관계라고 표현한다.
가구 -> 침대
음료수 -> 콜라
자동차 -> 소방차
이런 관계들을 상속 관계라 한다.
public class Car{
}
public class Bus extends Car{
}
이런식으로 extends
키워드를 통해 부모 클래스를 상속받을 수 있고, 이렇게 상속하게 되면 부모가 가지고 있는 method나 필드(field 변수 등)를 사용할 수 있다.
클래스는 필드와 메소드를 가지고 있다.
접근 제한자란 클래스 내에서 멤버의 접근을 제한하는 역할을 하게된다.
4개의 접근제한자가 존재한다.
1. public: 어떤 클래스든 접근할 수 있다.
2. protected: 자기 자신, 같은 패키지, 서로 다른 패키지다 하더라도 상속받은 자식 클래스에서는 접근 가능
3. default: 아무것도 접근제한자를 적지 않은 경우, 자기자신과 같은 패키지만 접근 가능 하다.
4. private: 자기 자신만 접근할 수 있다는 것을 의미한다.
접근제한자는 class에 필드, 메소드에도 사용할 수 있지만 class 자체에도 지정할 수 있다.
추상 클래스란 구체적이지 않은 클래스를 의미한다.
새 -> 독수리, 타조, 앵무새 등 여러 가지를 떠올릴 수 있다.
이렇게 구체적이지 않은 클래스를 나타내는 것이고, 이를 구현한 클래스가 추상 클래스다.
abstract
키워드를 이용한다.
public abstract class Bird{
public abstract void sing(); // 추상 메소드
public void fly(){
System.out.println("날다.");
}
}
public class Duck extends Bird{
@Override
public void sing() {
System.out.println("꽥꽥!!");
}
}
추상 클래스가 갖고 있는 추상 메소드를 구현해야한다. 추상 클래스를 상속 받았는데 이를 구현하지 않는다면, 해당 클래스도 추상 클래스가 된다.
추상 클래스는 객체를 가질 수 없다.
Bird b = new Bird();
-> Compile Error
super는 this와 비슷하다. 일반적으로 모든 클래스는 생성자를 가지고 있는다. 사용자가 지정하지 않았다면 기본생성자라도 가지고 있게 된다.
this는 현재 객체를 나타냈는데, super는 부모 객체를 나타내는 것을 의미한다.
상속받고 있는 객체를 가리키는 키워드이며, 사용은 this와 비슷하게 사용된다.
만약 class가 상속받고 있다면, class가 인스턴스화 될 때 생성자가 실행되면서 객체 초기화를 하는데, 이때 자신의 생성자뿐 아니라 부모의 생성자super()
도 실행하게 된다.
public class Car{
public Car(){
System.out.println("Car의 기본생성자입니다.");
}
}
public class Bus extends Car{
public Bus(){
System.out.println("Bus의 기본생성자입니다.");
}
}
이런식으로 상속관계가 형성되어 있고 생성자가 있다면 내부적으로는 다음과 같은 동작을 한다.
public Bus(){
super(); // 이 키워드가 항상 생략돼서 넣어져 있음.
System.out.println("Bus의 기본생성자입니다.");
}
super()
키워드는 기본적으로 생략되어 있지만, 원한다면 다른 생성자를 호출할 수도 있다.
super("String");
이런식으로 호출하면 부모 생성자 중 String을 받는 생성자가 대신하여 호출된다.
Overriding이란 부모가 가지고 있는 메소드와 똑같은 모양의 메소드를 자식이 가지고 있는 것
즉, 오버라이딩이란 메소드를 재정의하는 것이다.
부모클래스가 가지고 있는 것을 사용하고 싶지만, 조금 다른 형태로 사용하고 싶을 때 자식에서 같은 메소드를 재정의하여 다르게 사용할 수 있다.
public class Car{
public void run(){
System.out.println("Car의 run메소드");
}
}
Car 를 상속받는 Bus 클래스
public class Bus extends Car{
@Override
public void run(){
// super.run();
System.out.println("Bus의 run메소드");
}
}
public class BusExam{
public static void main(String args[]){
Bus bus = new Bus();
bus.run(); // Bus의 run메소드가 실행된다.
}
}
메소드를 오버라이드 하면, 항상 자식클래스에서 정의된 메소드가 호출된다.
오버라이딩 한다고 해서 부모의 메소드가 사라지는 것은 아니다.
물론 super 키워드를 배웠던 것 처럼 부모의 키워드를 사용할 수도 있다.
상속과계란 is a
관계라고 했다. 'Bus는 Car다' 라는 관계가 성립되는 것
그렇기 때문에 부모 타입으로 자식객체를 참조할 수 있다.
public class Car {
void run(){
System.out.println("차 출바알~");
}
}
public class Bus extends Car{
@Override
void run() {
System.out.println("버스 출바알~");
}
}
public class Main {
public static void main(String[] args) {
Bus bus = new Bus();
Car carBus = new Bus();
// Bus busCar = new Car(); Compile 에러
Car car = new Car();
Car c = new Bus();
Bus bus1 = (Bus) c; // 이렇게 사용할 수도 있다.
}
}
위 코드 예시처럼 Bus는 Bus로도 Car로도 받을 수 있지만, Car는 Bus를 못받는다.
(부모는 자식을 담을 수 있지만, 자식은 부모를 담지 못한다.)
여튼 이런식으로 부모는 자식을 참조할 수 있다.
이렇게 해야하는 이유는 나중에 객체지향 설계에서 다형성과 어울러지는 특성때문이 강한데 해당 부분은 나중에 정리하도록 하겠다.
public class GasStation{
public static void main(String[]args){
GasStation gasStation = new GasStation(); //gasStation인스턴스 생성
Suv suv = new Suv(); //suv 인스턴스 생성
Truck truck = new Truck(); //truck 인스턴스 생성
Bus bus = new Bus(); //bus 인스턴스 생성
/* 아래 처럼 선언 가능
Car suv = new Suv(); //suv 인스턴스 생성
Car truck = new Truck(); //truck 인스턴스 생성
Car bus = new Bus(); //bus 인스턴스 생성
*/
//gasStation에서 suv에 기름을 넣습니다.
gasStation.fill(suv);
//gasStation에서 truck에 기름을 넣습니다.
gasStation.fill(truck);
//gasStation에서 bus에 기름을 넣습니다.
gasStation.fill(bus);
}
public void fill(Car car){
//참고. car.getClass().getName()은 car오브젝트가 실제로 어떤 클래스인지를 알려줍니다.
System.out.println(car.getClass().getName()+"에 기름을 넣습니다.");
car.gas += 10;
System.out.println("기름이 "+car.gas+"리터 들어있습니다.");
}
}
인터페이스: 서로 관계가 없는 물체들이 상호 작용을 하기 위해서 사용하는 장치나 시스템
정의가 좀 어려운데, 결국 내가 클래스를 만들 때 어떤 기능을 만들지에 대한 설계서다.
TV를 만든다면, 전원을 키고 끄는 기능, 볼륨 조절, 채널 변경 등에 대한 기능을 만들 것이라고 명시하는 것
public interface TV{
// Field
public int MAX_VOLUME = 100;
public int MIN_VOLUME = 0;
// Method
public void turnOn();
public void turnOff();
public void changeVolume(int volume);
public void changeChannel(int channel);
}
인터페이스는 추상 메소드와 상수를 정의할 수 있다.
변수를 위처럼 선언하면 컴파일시 자동으로 상수로 변한다.
public static final int MAX_VOLUME = 100;
public static final int MIN_VOLUME = 0;
이외에도 정의된 메소드는 모두 추상 메소드이다. 컴파일 단계에서는 모든 메소드가 추상 메소드로 자동으로 변경된다.
인터페이스를 생성한 뒤 이를 클래스에서는 구현해야한다.
extends
implements
implements
를 할 수 있으며, 상속과 같이 사용할 수 있다.인터페이스도 다형성을 통한 클래스 형변환이 가능하다.
public class LedTV implements TV{
public void on(){
System.out.println("켜다");
}
public void off(){
System.out.println("끄다");
}
public void volume(int value){
System.out.println(value + "로 볼륨조정하다.");
}
public void channel(int number){
System.out.println(number + "로 채널조정하다.");
}
}
public class LedTVExam{
public static void main(String args[]){
TV tv = new LedTV(); // 클래스 형변환
tv.on();
tv.volume(50);
tv.channel(6);
tv.off();
}
}
위 코드 예시처럼 클래스 형변환이 가능하다.
기존 인터페이스는 추상메소드만 가질 수 있었는데, Java8부터는 두 가지가 생겼다.
1. default method
2. static method
인터페이스가 default 키워드로 선언되면 메소드가 구현될 수 있다. 이를 구현하는 클래스는 default method를 오버라이딩 할 수 있다.
public interface Calculator {
public int plus(int i, int j);
public int multiple(int i, int j);
default int exec(int i, int j){ //default로 선언함으로 메소드를 구현할 수 있다.
return i + j;
}
}
위 코드처럼 메소드를 인터페이스에 구현할 수 있다.
인터페이스가 변경됐을 때, 인터페이스를 구현하는 모든 클래스들이 해당 메소드를 구현해야 하는 문제가 있었는데, 이런 문제를 해결하기 위해 인터페이스에 메소드를 구현할 수 있도록 했다.
static method도 존재한다. 인터페이스에서 static method를 선언하면 간단한 기능을 가지는 유틸리티성 인터페이스도 만들 수 있다.
public interface Calculator {
public int plus(int i, int j);
public int multiple(int i, int j);
default int exec(int i, int j){
return i + j;
}
public static int exec2(int i, int j){ //static 메소드
return i * j;
}
// 인터페이스에서 정의한 static method는 반드시 InterfaceName.methodName(); 형식으로 호출
}
클래스 안에 선언된 클래스다.
어느 위치에 선언하느냐에 따라서 4가지 형태가 있을 수 있다.
1. 중첩클래스(인스턴스 클래스) -> 필드를 선언하는 위치에 선언
2. 정적 중첩 클래스(static 클래스) -> static으로 정의
3. 지역 중첩 클래스(지역 클래스) -> Method안에 클래스를 선언
4. 익명 클래스
그냥 단순히 클래스안에 클래스를 선언
public class Main{
class Inner{
int value = 2;
}
public static void main(String[] args){
Main m = new Main();
Main.Inner in = m.new Inner();
in.value;
}
}
위와 같이 사용할 수 있다.
public class Main2{
static class Car{
int value = 0;
}
public static void main(String[] args){
Car car = new Car();
// Main2.Car car = new Main2.Car();
car.value;
}
}
public class Main3{
public void exec(){
class Car{
int value = 0;
public void plus(){
value++;
}
}
Car car = new Car();
car.plus();
System.out.println(car.value);
}
public static void main(String[] args){
Main3 m = new Main2();
m.exec();
}
}
지역 클래스는 좀 특이하다 Method 내에서 선언하며, Method 내에서만 사용할 수 있는 클래스다.
익명 중첩 클래스는 익명 클래스라고 보통 말하며, 내부 클래스이기도 하다.
일반적으로 추상 클래스를 상속받았을 때 Override를 통해 구현을 할 것이다.
그러면 구현한 클래스를 통해서 Overriding한 Method를 호출할 텐데, 익명 클래스는 new 키워드를 통해 클래스를 만들면서 새롭게 Override하거나 할 때 사용할 수 있다.
상속받는 클래스를 굳이 만들어야하지 않을 때, 익명 클래스를 주로 사용한다.
// 일반적인 추상클래스
public abstract class Action{
public abstract void exec();
}
// 추상클래스 상속 및 메소드 구현
public class MyAction extends Action{
public void exec(){
System.out.println("exec");
}
}
// 추상클래스를 구현한 클래스를 인스턴스화 시켜서 메소드 호출
public class ActionExam{
public static void main(String args[]){
Action action = new MyAction();
action.exec();
}
}
// 추상클래스를 상속받지 않고 익명 클래스를 통해 직접 메소드 구현
public class ActionExam{
public static void main(String args[]){
Action action = new Action(){
public void exec(){
System.out.println("exec");
}
};
action.exec();
}
}
해당 익명 클래스를 사용하는 이유는, 해당클래스에서만 사용되고 다른 클래스에서 사용되지 않는 경우에 주로 사용한다.
프로그램을 실행하다보면 예기치 못한 사건을 예외라고한다. 예외 상황을 미리 예측하고 처리할 수 있는데, 이것을 바로 예외 처리라 한다.
int i = 10;
int j = 0;
int k = i / j; // Error 발생
이런식으로 예외가 발생하게 된다.
예외처리하는 문법은 try-catch-finally
를 통해 한다.
에러가 발생할 가능성이 있는 문장을 try로 묶고, 예외 발생시에는 catch로 묶고, finally는 예외가 발생하더라도 반드시 실행해야하는 경우 사용
int i = 10;
int j = 0;
try{
int k = i/j;
} catch (ArithmeticException e) {
System.out.println("0으로 나눌 수 없습니다." + e.toString());
} finally {
System.out.println("반드시 실행합니다.");
}
이렇게 사용할 수 있습니다. 일반적으로 예외가 발생하면 프로그램이 종료되지만, 이렇게 사용하면 원치 않은 종료를 막을 수 있다.
Exception클래스들은 모두 Exception클래스를 상속받으므로, 예외클래스에 Exception을 두게 되면 어떤 오류가 발생하든지 간에 하나의 catch블록에서 모든 오류를 처리할 수 있다.
예외 처리를 위해 try-catch를 진행했었다. 예외 처리는 다른 방법으로도 가능한데 그 중 하나가 throws
다.
int i = 10;
int j = 0;
int k = div(i, j);
static int div(int i, int j){
return i / j;
}
해당 코드처럼 Method를 사용할 때도 예외가 발생한다.
이런 경우 try-catch를 통해 해결할 수 있지만, throws를 통해 div()를 호출한 객체로 예외를 넘기는 동작을 할 수 있다.
즉, 정리하자면 다음과 같다.
메소드 호출 -> 메소드 내부 예외 발생 -> 프로그램 종료 x, 호출한 객체에게 예외 전달 -> 예외 발생 처리
위 프로세스로 동작하게 되며 throws는 다음과 같이 사용한다.
try{
int k = div(i, j);
}catch(ArithmenticException e){
System.out.println(e.toString());
}
// 메소드
static int div(int i, int j) throws ArithmenticException{
return i / j;
}
public class ExceptionExam {
public int get50thItem (int []array) throws ArrayIndexOutOfBoundsException{
return array[49];
}
}
throw는 오류를 떠넘기는 throws와 보통 같이 사용되는데, 강제로 오류를 발생시키는 것이다.
int k = 10 / 0;
과 같이 0으로 나누는 것 자체가 문제가 생기는 포인트이다.
그렇다면 div(int, int)
Method에서 두 번째 파라미터가 0이 된다면 당연히 Error가 발생하는게 맞기 때문에 이런 경우 예외를 발생시키는 등의 동작을 할 수 있다.
try{
int k = div(i, j);
}catch(Exception e){
System.out.println(e.toString());
}
// 메소드
static int div(int i, int j) throws IllegalArgumentException{
if (j == 0) throw new IllegalArgumentException("0으로 나눌 수 없습니다.");
// if (j == 0) return throw new Exception(); 과 같이 return과 함께 사용 X
return i / j;
}
이런식으로 에러를 강제로 발생 시킬 수 있다.
참고로 IllegalArgumentException은 적합하지 않거나(illegal) 적절하지 못한(inappropriate) 인자를 메소드에 넘겨주었을 때 Exception이 발생한다는 의미이다.
public class ClassName extends Exception{
}
이런식으로 사용자가 직접 Exception을 만들 수 있다. 이렇게 굳이 사용자가 직접 예외클래스를 만드는 이유는 가독성 때문이다. 에러가 발생했을 때 무슨 에러가 나타났는지 클래스이름만 가지고 한 눈에 알아볼 수 있기 때문이다.
Exception은 두 가지로 나뉘게 된다.
1. Exception 클래스를 상속 받아 정의한 checkedException
2. RuntimeException 클래스를 상속 받아 정의한 unCheckedException
위에서 말한 것처럼 Exception 클래스를 상속 받은 예외이다.
해당 경우는 예외가 발생했을 때 예외 처리를 하지 않는다면, 반드시 컴파일 오류를 발생 시킨다.
public class MyCheckedException extends Exception{
}
public class BizException extends RuntimeException {
public BizException(String msg){
super(msg);
}
public BizException(Exception ex){
super(ex);
}
}
public class BizService {
public void bizMethod(int i)throws BizException{
System.out.println("비지니스 로직이 시작합니다.");
if(i < 0){
throw new BizException("매개변수 i는 0이상이어야 합니다.");
}
System.out.println("비지니스 로직이 종료됩니다.");
}
}
public class BizExam {
public static void main(String[] args) {
BizService biz = new BizService();
biz.bizMethod(5);
try{
biz.bizMethod(-3);
}catch(Exception ex){
ex.printStackTrace();
}
}
}
비지니스 로직이 시작합니다.
비지니스 로직이 종료됩니다.
비지니스 로직이 시작합니다.
javaStudy.BizException: 매개변수 i는 0이상이어야 합니다.
at javaStudy.BizService.bizMethod(BizService.java:7)
at javaStudy.BizExam.main(BizExam.java:9)