오늘의하루

[Java] 변수, 연산자, 조건문, 반복문, 배열, 객체지향, 예외처리 본문

JAVA

[Java] 변수, 연산자, 조건문, 반복문, 배열, 객체지향, 예외처리

오늘의하루_master 2022. 9. 13. 12:12

변수(Variable)

하나의 값을 저장할 수 있는 기억공간

변수의 타입(Data type)

변수의 기본형 타입(Primitive type)

  • 논리 = boolean
  • 문자 = char
  • 숫자
    • 정수 = byte, short, int, long
    • 실수 = float, double
구분 1byte 2byte 4byte 8byte
논리형 boolean      
문자형   char('')    
정수형 byte short int long(L)
실수형     float(f) double

변수의 참조형 타입(Reference type)

기본형을 제외한 나머지(예를 들어 String, System 등)

  • 객체의 주소를 저장한다. (4byte)

변수 선언 시 용어 정리

  • 변수(variable) = 하나의 값을 저장하기 위한 공간
  • 상수(constant) = 한 번만 값을 저장할 수 있는 공간 ( 변경 불가 )
  • 리터럴(literal) = 그 자체로 값을 의미하는 것
int score = 100; // score는 변수, 100은 리터럴, 타입 int
final int MAX = 200; // MAX는 상수, 200은 리터럴, 타입 int
String str = "ABC"; // str은 변수, "ABC"는 (객체)리터럴, 타입 String

변수의 기본값과 초기화

변수에 처음으로 값을 저장하는 것을 초기화라고 부른다.

  • 지역 변수의 경우 사용되기 전에 반드시 초기화해야 한다.
자료형 기본값
boolean false
char '\u0000' = 공백
byte 0
short 0
int 0
long 0L
float 0.0f
double 0.0d 또는 0.0
참조형 변수 null

문자와 문자열

어떤 타입이든 문자열(String) 타입과 "+" 연산을 하게 되면 문자열이 된다.

  • 문자(char)는 한 문자만 저장이 가능하다.
char ch1 = 'A';
Stirng s1 = "BB";
System.out.println(ch1 + s1); // "ABB"

char ch2 = ''; // Error! 빈문자 리터럴을 줄 수 없다.
char ch2 = ' '; // 공백을 줘야한다. == char ch2 = '\u0000';

기본형 타입의 형 변환(Casting)

값의 타입을 다른 타입으로 변환시키는 것을 말한다.

  • boolean을 제외한 7개의 기본형 타입은 서로 형 변환이 가능하다.
int i = 65;
char ch = (char)i;
System.out.println(ch); // A

char ch2 = 'A';
int i2 = (int)ch2;
System.out.println(i2); // 65

float f = 1.6f;
int i3 = (int)f;
System.out.println(i3); // 1

int i4 = 10;
float f2 = (float)i4;
System.out.println(f2); // 10.0f
1byte 2byte 4byte 8byte 4byte 8byte
byte short int long float double
    char      

왼쪽에서 오른쪽으로 형 변환할 때는 변환할 타입을 생략할 수 있지만 반대의 경우 꼭 작성해줘야 한다.

int i = 10;
long l = i; // (long) 생략가능
float f = l; // (float) 생략가능

float f2 = 10.0f;
long l2 = (long)f2; // 생략불가능
int i2 = (int)l2; // 생략불가능

연산자(Operator)

연산자는 어떠한 기능을 수행하는 기호(+, -, *, /, % 등등)

  • 피 연산자(Operand)는 연산자의 작업 대상 (변수, 상수, 리터럴 수식)
종류 연산방향 연산자 우선순위
단항 연산자 ++, --, +, -, ~, ! (타입) 높음
산술 연산자 *, /, %  
+, -  
<<, >>, >>>  
비교 연산자 <, >, <=. >=, instanceof  
==, !=  
논리 연산자 &  
^  
|  
&&  
||  
삼항 연산자 ? :  
대입 연산자 =, +=, /=, *=, -=, <<=
>>=, >>>=, &=, ^=, |=
낮음

조건문

자바에서는 if문과 switch문 두 개만 조건문으로 사용할 수 있으며 if문이 많은 경우에 switch문을 사용하는 것을 고려해보는 것이 좋다.

if(num == 1){
    System.out.println(1);
}else if (num == 2){
    System.out.println(2);
}else if (num == 3){
    System.out.println(3);
}else{
    System.out.println("Unknown");
}
// 아래와 위 두개는 같은 의미입니다.
swith(num){
    case 1:
        System.out.println(1);
        break;
    case 2:
        System.out.println(2);
        break;
    case 3:
        System.out.println(3);
        break;
    default:
        System.out.println("Unknown");
}

Math.random( )

Math클래스에 정의된 난수 발생 함수를 사용할 수 있다.

  • 0.0 <= Math.random() < 1.0
  • Math.random()의 기본 타입은 double이다.
// 0.0이상 1.0미만 실수를 출력하기
double d = Math.random();
// 결과 예시 : 0.861731779164997

// 0이상 10미만 정수를 출력하기
int i = (int)(Math.random()*10);
// 결과 예시 : 6

// 5이상 13미만 정수를 출력하기
int i = (int)(Math.random()*8)+5;
// 결과 예시 : 11

반복문

문장들을 반복해서 수행할 때 사용하면 되며 반복 횟수가 중요한 경우는 for문을 사용하고 그 외에는 while문을 사용되며 for문으로 만든 것은 while문으로 만들 수 있고 while문으로 만든 것도 for문으로 만들 수 있다.

for문

초기화, 조건식, 증감식 그리고 수행할 블록 또는 문장으로 구성되어있다.

for(초기화; 조건식; 증감식){
    // 조건식이 true일때 수행될 문장
}

// 1에서 10까지 정수 더하기
int sum = 0;
for(int i = 1; i < 11; i++){
    sum = sum + i;
}
// 결과 : 55

while문

조건식과 수행할 블록 또는 문자로 구성되어있다.

while(조건식){
    // 조건식이 true일 때 수행될 문장
}

// 1~10까지 정수 더하기
int sum = 0;
int i = 1;
while(i < 11){
    sum = sum + i;
    i++;
}
// 결과 : 55

break문

자신이 포함된 하나의 반복문 또는 switch문을 빠져나온다.

  • 주로 if문과 함께 사용해서 특정 조건일 경우 반복문을 벗어난다.
// 1부터 x까지 합한 결과가 100보다 클때 가장 작은 x를 구하시오.
int sum = 0;
int i = 1;

while(true){
    sum = sum + i;
    if(sum > 100){
        break; // while문을 벗어난다.
    }
    i++;
}
// 결과 : 14

continue문

자신이 포함된 반복문의 끝으로 이동해서 반복문을 실행한다.

  • continue문 이후 문장들은 수행되지 않는다.
// 1부터 10까지의 숫자중 짝수만 출력하세요.
for (int i = 1; i < 11; i++){
    if(!(i % 2 == 0)){
        continue;
    }
    System.out.println(i);
}
// 결과 : 2 4 6 8 10

이름 붙은 break문, continue문

반복문 앞에 이름을 붙이고 그 이름을 break. continue와 함께 사용하면 중첩된 반복문을 한 번에 벗어나거나 건너뛸 수 있습니다.

Loop1:
for(int i = 2; i <= 9; i++){
    for(int j = 1; j <= 9; j++){
        if(j == 5){
            break Loop1;
        }
        System.out.println(i + "*" + j + "=" + i*j);
    }
    System.out.println();
}
// 결과
// 2*1=2
// 2*2=4
// 2*3=6
// 2*4=8

Loop2:
for(int i = 2; i <= 9; i++){
    for(int j = 1; j <= 9; j++){
        if(j == 3){
            continue Loop2;
        }
        System.out.println(i + "*" + j + "=" + i*j);
    }
    System.out.println();
}
// 결과
// 2*1=2
// 2*2=4
// 3*1=3
// 3*2=6
// ... 9*2=18

배열(Array)

같은 타입의 여러 변수를 하나의 묶음으로 다루는 것으로 많은 양의 데이터의 다룰 때 유용하다.

  • 배열의 각 요소는 서로 연속적(순서 O)이다.
int[] score = new int[5];
// 5개의 int 값을 저장할 수 있는 배열을 생성한다.
score   score[0] score[1] score[2] score[3] score[4]
0x100 0 0 0 0 0
주소 저장   0x100        
  • 배열의 각 요소는 기본값으로 저장된다.

배열의 초기화

생성된 배열에 처음으로 값을 저장한다.

int[] score = new int[3];
score[0] = 1;
score[1] = 2;
score[2] = 3;
// 각각 하나씩 초기화하기

int[] score = {1,2,3}; // 생성과 초기화 동시에 1
int[] score = new int[]{1,2,3}; // 생성과 초기화 동시에 2

int[] score;
score = {1,2,3}; // Error!!!!
score = new int[]{1,2,3}; // OK!!!!

배열의 활용

score[2] = 100; // score배열의 3번째 요소에 100을 저장한다.
int value = score[2]; // score배열의 3번째 요소의 값을 value에 저장한다.

int[] score = {1,2,3,4,5};
System.out.println(score.length); // 배열의 크기를 알려준다.
// 결과 : 5

배열을 이용한 예제 살펴보기

// 배열을 섞어보자
int[] ball = new int[45]; // 45개의 정수값을 저장하기 위한 배열 생성
        
for (int i = 0; i < ball.length; i++){
    ball[i] = i+1; // ball[0]에는 1이 저장된다.
}
int tmp = 0;
int j = 0;
        
for (int i = 0; i < 100; i++){
    j = (int)(Math.random() * 45); // 0 ~ 44 정수의 임의의 값
    // ball[0]과 ball[j]의 값을 서로 바꾼다.
    tmp = ball[0];
    ball[0] = ball[j];
    ball[j] = tmp;
}
        
for(int i = 0; i < ball.length; i++){
    System.out.print(ball[i] + " ");
}

// 문자배열을 new String()을하면 문자열이 된다.
char[] hex = {'C','A','F','E'};
String[] binary = {"0000","0001","0010","0011",
                   "0100","0101","0110","0111",
                   "1000","1001","1010","1011",
                   "1100","1101","1110","1111"};
        
String result = "";
        
for (int i = 0; i < hex.length; i++){
    if(hex[i] >= '0' && hex[i] <= '9'){
        result = result + binary[hex[i]-'0'] + " "; // '8'-'0' = 8
    }else{
        result = result + binary[hex[i]-'A'+10] + " "; // 'C'-'A' = 2
    }
}
System.out.println("hex = " + new String(hex)); // "CAFE"
System.out.println("binary = " + result); // 1100 1010 1111 1110

다차원 배열의 선언과 생성

"[ ]"의 개수가 차원의 수를 의미한다.

선언 방법 선언 예시
타입[ ][ ] 변수이름; int[ ][ ] score;
타입 변수이름[ ][ ]; int score[ ][ ];
타입[ ] 변수이름[ ]; int[ ] score[ ];
  • 위에 선언 방법은 다 같은 결과를 만든다.
int[][] score = new int[4][3];
// 4행 3열의 2차원 배열을 생성한다.

int[][] score = { // 4행 3열 2차원 배열 초기화
                  {100,100,100},
                  {20,20,20},
                  {30,30,30},
                  {40,40,40},
                };
  • 2차원 배열은 1차원 배열의 요소에 배열을 추가한 것이다.
score   주소 0X100   주소 0X200    
0X100 0X200 100 100 100
    score[0]   score[0][0] score[0][1] score[0][2]
        주소 0X300    
    0X300 20 20 20
    score[1]   score[1][0] score[1][1] score[1][2]
        주소 0X400    
    0X400 30 30 30
    score[2]   score[2][0] score[2][1] score[2][2]
        주소 0X500    
    0X500 40 40 40
    score[3]   score[3][0] score[3][1] score[3][2]

가변 배열

다차원 배열에서 마지막 차수의 크기를 지정하지 않고 각각 다르게 생성할 수 있다.

int[][] score = new int[4][]; // 가변 배열 생성
score[0] = new int[3];
score[1] = new int[2];
score[2] = new int[1];
score[3] = new int[2];

int[][] score2 = {
                    {100,100,100},
                    {20,20},
                    {30},
                    {40,40},
                 };
score   주소 0X100   주소 0X200    
0X100 0X200 100 100 100
    score[0]   score[0][0] score[0][1] score[0][2]
        주소 0X300    
    0X300 20 20  
    score[1]   score[1][0] score[1][1]  
        주소 0X400    
    0X400 30    
    score[2]   score[2][0]    
        주소 0X500    
    0X500 40 40  
    score[3]   score[3][0] score[3][1]  

System.arraycopy()로 배열 요소 복사

System.arraycopy(arr1, 1, arr2, 2, 2);
  • arr1, 0 = arr1 [0]에서
  • arr2, 1 = arr1 [1]으로
  • 2 = 2개의 테이블을 복사한다.
char[] arr1 = {'0','1','2','3','4'};
char[] arr2 = {'A','B','C','D','E'};

System.arraycopy(arr1, 1, arr2, 2, 2);
arr1   arr1[0] arr1[1] arr1[2] arr1[3] arr1[4]
0x100 '0' '1' '2' '3' '4'
    주소 0x100        
arr2   arr2[0] arr2[1] arr2[2] arr2[3] arr2[4]
0x200 'A' 'B' 'C' 'D' 'F'
    주소 0x200        
  • 배열 복사 후
arr1   arr1[0] arr1[1] arr1[2] arr1[3] arr1[4]
0x100 '0' '1' '2' '3' '4'
    주소 0x100        
arr2   arr2[0] arr2[1] arr2[2] arr2[3] arr2[4]
0x200 'A' 'B' '1' '2' 'F'
    주소 0x200        

사용자 입력받기 - 입력창

Swing패키지의 JOptionPane.showInputDialog()를 사용할 수 있다.

  • 입력 및 확인, 취소가 가능한 팝업창이 생성된다.
import javax.swing.*;

public class Myclass{
    public static void main(String[] args){
        int answer = (int)(Math.random()* 100)+1; // 1~100사이의 임의 정수값
        int input = 0;
        String temp = "";
        int count = 0;
        
        while(true){
            count++;
            temp = JOptionPane.showInputDialog("아무숫자나 입력하세요."); // 팝업창
            if(temp == null || temp.equals("-1")){
                break;
            }
            System.out.println("입력받은 값은 " + temp + "입니다.");
            input = Integer.parseInt(temp);
            if(answer > input){
                System.out.println(input + "보다 작은 수입니다.");
            }else if(answer < input){
                System.out.println(input + "보다 큰 수 입니다.");
            }else if(answer == input){
                System.out.println("정답입니다.");
                break;
            }
        }
    }
}

객체지향

객체지향 언어의 특징

  • 코드의 재사용성이 높다.
  • 코드의 관리가 쉽다.
  • 신뢰성 높은 프로그램 개발이 가능하다.

클래스와 객체

구분 특징
클래스 정의 객체를 정의해 놓은 것
객체의 정의 속성과 기능을 가진 것

객체는 인스턴스를 포함하는 의미이며 클래스로부터 인스턴스를 생성하는 것을 인스턴스화라고 부른다.

인스턴스화 : 클래스 → 인스턴스(객체)

객체의 구성요소 - 속성과 기능

객체는 속성(변수)과 기능(메서드)으로 이루어져 있다.

  • 속성과 기능을 객체의 멤버라고 부른다.
class Tv{
    // 속성(변수)
    int channel;
    String color;
    boolean power;
    
    // 기능(메서드)
    void power(){power = !power;}
    void channelUp(){channel++;}
    void channelDown(){channel--;}
}

인스턴스의 생성과 사용

  • 인스턴스 생성 방법
class Tv{
    int channel;
    void channelUp(){channel++;}
}

Tv t1;
// 클래스명 참조변수명; = 객체를 다루기 위한 참조변수를 선언한다.
t1 = new Tv();
// 참조변수명 = new 클래스명(); = new 연산자로 객체를 생성하고 그 주소를 참조변수에 저장한다.
// 한줄로 표현하면 Tv t1 = new Tv();
  • 인스턴스 사용방법
class Tv{
    int channel;
    void channelUp(){channel++;}
}

Tv t1 = new Tv(); // 인스턴스 생성 후 참조변수 t1에 주소 저장
Tv t2 = new Tv();
t1.channel = 7; // 변수 초기화
t1.channelUp(); // 메서드 사용
System.out.println(t1.channel); // 8

t2 = t1; // Tv타입의 참조변수 t2에 t1의 값(주소)을 저장한다.
System.out.println(t2.channel); // 8
  • 한 개의 인스턴스를 여러 개의 참조 변수가 가리키는 것은 가능하다.
  • But 여러 개의 인스턴스를 하나의 참조 변수가 가리키는 것은 불가능하다.

클래스

클래스란 서로 관련된 값을 묶어서 하나의 타입으로 정의하는 것을 말한다.

  • 클래스는 구조체와 함수를 결합한 것이다.
  • 구조체 = 타입에 관계없이 서로 관련된 데이터를 저장할 수 있는 공간
// 별도로 변수들을 가지고 있다.
int hour;
int minute;
int second;

// 클래스로 하나의 묶음으로 만든다.
class Time{
    int hour;
    int minute;
    int second;
}

클래스는 배열로 만들 수 있다.

class Time{
    int hour;
    int minute;
    int second;
}

Time[] t = new Time[3];
t[0] = new Time();
t[1] = new Time();
t[2] = new Time();
t   t[0]   hour minute second
0x100 0x200 0 0 0
        주소 0x200    
    t[1]   hour minute second
    0x300 0 0 0
        주소 0x300    
    t[2]   hour minute second
    0x400 0 0 0
        주소 0x400    
  • 변수의 타입이 int이고 초기화되지 않았기 때문에 기본값인 0이 들어간다.

변수

변수의 선언 위치가 변수의 종류와 범위(scope)를 결정한다.

변수의 종류 선언위치 생성시기
클래스 변수 클래스 영역 클래스가 메모리에 로딩될때
인스턴스 변수 인스턴스 생성시
지역 변수 메서드 영역 메서드 호출시
class Variable{
    int iv; // 인스턴스 변수
    static int cv; // 클래스 변수
    
    void method(){
       int lv = 0; // 지역 변수
    }
}

클래스 변수

  • 인스턴스 생성 없이 "클래스명. 클래스 변수명"으로 접근 가능
  • 같은 클래스의 모든 인스턴스들이 공유할 수 있는 변수

인스턴스 변수

  • 인스턴스 생성 후 "참조 변수명. 인스턴스 변수명"으로 접근 가능
  • 가리키는 참조 변수가 없는 경우 가비지 컬렉터에 의해 자동으로 소멸된다.

지역변수

  • 메서드가 종료되면 함께 소멸된다.
  • 지역변수의 경우 사용 전 초기화를 꼭 해줘야 한다.

메서드

어떤 값을 입력받아서 처리하고 그 결과를 반환하는 역할을 한다.

  • 하나의 메서드는 하나의 기능만 수행하는 것이 좋다.
리턴타입 메서드이름 (타입 변수명1, 타입 변수명2 ...){ // >> 선언부
    // 메서드 호출 시 수행될 작업 >> 구현부
}
  • 리턴 타입이 void가 아닌 경우 return 문을 필수로 작성해주어야 한다.
  • 리턴 타입이 void인 경우 return 문을 작성하지 않아도 된다.

return문

현재 실행 중인 메서드를 종료하고 호출한 곳으로 되돌아간다.

  • 메서드의 리턴 타입과 return문의 결괏값의 타입이 일치해야 한다.
int add(int a, int b){
    int result = a + b;
    return result;
}

// add의 리턴타입 int == result의 타입 int

메서드의 호출 방법

메서드의 호출 방법은 변수의 접근 방법과 같다.

클래스 메서드

  • 인스턴스 생성 없이 "클래스명. 메서드명(매개변수)"로 호출이 가능하다.
  • 클래스 메서드 내에서 인스턴스 멤버를 사용할 수 없고 클래스 변수는 멤버는 사용할 수 있다.

인스턴스 메서드

  • 인스턴스 생성 후 "참조 변수명. 메서드명(매개변수)"로 호출이 가능하다.
  • 인스턴스 메서드에는 인스턴스 멤버, 클래스 멤버를 사용할 수 있다.
class Test{
    int iv; // 인스턴스 변수
    static int cv; // 클래스 변수
    
    void instanceMethod1(){}
    void instanceMethod2(){
        System.out.println(iv); // 인스턴스 변수 사용 가능
        System.out.println(cv); // 클래스 변수 사용 가능
        instanceMethod1(); // 다른 인스턴스 메서드 호출 가능
        staticMethod1(); // 클래스 메서드 호출 가능
    }
    static void staticMethod1(){}
    static void staticMethod2(){
        System.out.println(cv); // 클래스 변수 사용 가능
        System.out.println(iv); // Error! 인스턴스 변수 사용 불가
        instanceMethod1(); // Error! 인스턴스 메서드 호출 불가
        staticMethod1(); // 클래스 메서드 호출 가능
    }
}

JVM의 메모리 구조

  • 자바 프로그램을 실행하면 JVM은 OS로부터 메모리를 할당받는다.
  • 컴파일러(javac)가 소스코드(. java)를 바이트코드(. class)로 컴파일한다.
  • 바이트코드(. class)는 Class Loader를 통해 JVM Runtime Data Area에 로딩된다.
  • Runtime Data Area에 로딩된 바이트코드(. class)는 Execution Engine을 통해 해석된다.

Runtime Data Area

Method Area

  • 클래스 멤버, 클래스 정보 등등 저장된다.

Call Stack (Last In First Out 구조)

  • 메서드가 호출되면 메서드 수행에 필요한 메모리 공간을 할당받고 종료되면 메모리를 반환한다.
  • 스레드 별로 독자적으로 가진다.

Heap

  • 인스턴스가 생성되는 공간으로 new 연산자에 의해서 생성되는 배열과 객체가 생성된다.
  • 동적으로 생성된 오브젝트와 배열이 저장되는 곳이며 GC(가비지 컬렉터)의 대상이 되는 영역이다.

매개변수 (기본형, 참조형)

  • 기본형 매개변수는 변수의 값을 읽기만 할 수 있습니다.
  • 참조형 매개변수는 변수의 값을 읽기 및 수정할 수 있습니다.
public class Test{
    public static void main(String[] args){
        Data d = new Data();
        d.x = 10;
        System.out.println("main() : x = " + d.x); // 10
        change1(d.x);
        System.out.println("After change1(d.x) : x = " + d.x); // 10
        change2(d); // Data c = d는 참조변수 c에 d의 값(주소)을 저장한다.
        // 하나의 인스턴스를 두개의 참조변수가 가리키는 셈이다.
        System.out.println("After change2(d.x) : x = " + d.x); // 1000
    }
    static void change1(int x){
        x = 1000;
        System.out.println("change1() : x = " + x); // 1000
    }
    
    static void change2(Data c){
        c.x = 1000;
        System.out.println("change2() : x = " + c.x); // 1000
    }
}

class Data{
    int x;
}

Method 오버 로딩

하나의 클래스에 같은 이름의 메서드를 열개 정의하는 것을 메서드 오버 로딩이라고 부른다.

오버 로딩의 조건

선언 부만 신경 쓰면 되며 구현부는 각각의 메서드에 맞게 변경 가능하다.

  1. 메서드의 이름이 같아야 한다.
  2. 매개변수의 개수 또는 타입이 달라야 한다.
  3. 리턴 타입은 오버 로딩에 아무런 영향을 주지 못한다.
    • 리턴 타입만 다른 것은 오버 로딩이 아니라는 말이다.
void println(){System.out.println("초기 메서드");}
String println(){return "Error!";} // 리턴타입만 다른건 오버로딩이 아니다.
void println(boolean x){System.out.println("OK");} // OK
long println(long x) {return x;} // OK

오버 로딩을 올바르게 사용하는 방법

매개변수는 다르지만 같은 의미의 기능을 수행하게 만드는 것이다.

생성자(constructor)

모든 클래스는 반드시 하나 이상의 생성자를 가지고 있어야 한다.

  • 인스턴스가 생성될 때마다 호출되는 "인스턴스 초기화 메서드"이다.
Test t = new Test();
  1. new 연산자에 의해서 heap영역에 Test클래스의 인스턴스 생성한다.
  2. 생성자 Test()가 호출되어 수행된다.
  3. new 연산자 결과로 Test인스턴스의 주소를 참조 변수 t에 저장한다.

생성자의 조건

생성자의 이름은 클래스의 이름과 같아야 하며 리턴 값이 없다.

  • 리턴 값이 없지만 void를 쓰지 않는다.
class Test{
    int x;
    int y;
    
    Test(){} // 매개변수가 없는 기본 생성자
    Test(int a, int b){ // 매개변수가 있는 생성자
        x = a; 
        y = b;
    }
}

기본 생성자

매개변수가 없는 생성자를 의미합니다.

  • 만약 클래스에 생성자가 없다면 컴파일러(javac)가 기본 생성자를 만들어 줍니다.
class Test{
    // Test(){} = 생성자가 없기 때문에 javac가 자동으로 추가해준다.
}

this( ) 생성자

같은 클래스의 다른 생성자를 호출할 때 사용하며 생성자의 첫 줄에서만 가능하다.

class Test{
    int age;
    String name;
    
    Test(){
        this(30, "jang"); // this()생성자 사용
        // Test(int x, String str)을 호출한다.
    }
    Test(int x, String str){
        age = x;
        str = name;
    }
}

참조 변수 this

호출한 자신을 가리키는 참조 변수이다.

class Test{
    int age;
    String name;
    Test(){
        this(30, "jang"); // this() 생성자
    }
    Test(int age, String name){
        this.age = age; // 참조변수 this로 매개변수와 분리해주었다.
        this.name = name; // 참조변수 this로 매개변수와 분리해주었다.
    }
}

생성자를 통해 인스턴스 복사

인스턴스 간의 차이는 변수의 값뿐이므로 복사해서 독립적이지만 똑같은 인스턴스를 만들 수 있다.

class Car{
    String color;
    int door;
    
    Car(){this("red",4);}
    
    Car(Car x){
        this(x.color, x.door);
    }
    
    Car(String color, int door){
        this.color = color;
        this.door = door;
    }
}

Car c1 = new Car("green", 10);
Car c2 = new Car(c1);
System.out.println(c2.color); // green
System.out.println(c2.door); // 10

변수의 초기화

변수를 선언하고 처음으로 값을 저장하는 것을 말한다.

  • 멤버 변수와 배열은 각 타입의 기본값으로 자동 초기화되므로 생략 가능하다.
  • 지역 변수는 꼭 사용 전 초기화를 해줘야 한다.
  • 클래스 변수는 처음 메모리에 로딩될 때 딱 한 번만 초기화된다.

초기화의 방법

명시적 초기화

class Test{
    int x = 4; // 기본형 변수의 명시적 초기화
    Engine e = new Engine(); // 참조형 변수의 명식적 초기화
}

생성자 초기화

class Test{
    int x;
    
    Test(int x){
        this.x = x;
    }
}

초기화 블록

  • 클래스 초기화 블록
    • 복잡한 초기화에 사용되며 클래스가 로딩될 때 실행된다.
  • 인스턴스 초기화 블록
    • 생성자에서 공통적으로 수행되는 작업에 사용된다.
    • 인스턴스가 생성될 때 생성자보다 먼저 초기화 블록이 실행된다.
class Test{
    int x;
    static int cv;
    {x = 1;} // 인스턴스 초기화 블럭
    static{cv = 2;} // 클래스 초기화 블럭
}

초기화 시기와 순서

class Test{
    static int cv = 1;
    int iv = 2
    
    static{cv = 3}
    {iv = 4;}
    
    Test(){
        iv = 5;
    }
}
  1. 클래스 변수 cv는 기본값 0으로 초기화된다.
  2. 클래스 변수 cv에 1을 대입하여 저장한다. (명시적 초기화)
  3. 초기화 블록에서 클래스 변수 cv에 3을 대입하여 저장한다.
  4. 인스턴스 변수 iv는 기본값 0으로 초기화된다.
  5. 인스턴스 변수 iv에 2를 대입하여 저장한다. (명시적 초기화)
  6. 초기화 블록에서 인스턴스 변수 iv에 4를 대입하여 저장한다.
  7. 생성자를 통해 인스턴스 변수 iv에 5를 대입하여 저장한다.

예제

class product{
    static int count = 0;
    int No;
    
    { // 인스턴스 변수 초기화 블럭
        ++count;
        No = count;
    }
    product(){}
}

product p1 = new product();
product p2 = new product();
product p3 = new product();
        
System.out.println("p1 No = " + p1.No); // 1
System.out.println("p2 No = " + p2.No); // 2
System.out.println("p3 No = " + p3.No); // 3
        
System.out.println("total = " + product.count); // 3

상속(inheritance)

기존의 클래스를 재사용해서 새로운 클래스를 작성하는 행위이다.

  • 이렇게 생성된 클래스와 기존 클래스는 부모와 자식의 관계를 맺는다.
  • 자식 클래스는 부모 클래스의 생성자, 초기화 블록을 제외한 모든 멤버를 상속받는다.
  • 부모의 변경은 자식에게 영향을 주며 반대의 경우는 아무런 영향을 주지 못한다.
  • 자바에서는 단일 상속을 원칙으로 한다.
  • 상속은 extends로 관계를 맺는다.
  • 상속관계를 사용하는 경우는 "~은 ~이다."라고 말이 되면 된다.
  • 부모가 없는 클래스는 자동적으로 Object 클래스를 상속받게 된다.
  • 상속계층도 최상위는 무조건 Object클래스이다.
  • 그러므로 모든 클래스는 Object클래스에 정의된 11개의 메서드를 상속받는다.
    • toString(), equals(Object obj), hashCode() 등등
class Shape{
    int x;
    int y;
}
// Circle은 Shape이다. (O)
// Circle 클래스는 Shape 클래스의 자식 클래스가 된다.
class Circle extends Shape{
    int z;
}

포함(composite)

클래스 멤버 변수로 다른 클래스를 선언하는 것을 말한다.

  • 두 개의 클래스를 상속받고 싶다면 하나는 상속 하나는 포함을 한다.
  • 포함 관계를 사용하는 경우는 "~은 ~을 가지고 있다."라고 말이 되면 된다.
class Point{
    int x;
    int y;
}
// Circle은 Point를 가지고 있다. (O)
class Circle{
    Point p = new Point(); // 포함
    int r;
}

예제

class Shape{
    String color = "Blue";
    void draw(){System.out.println("도형을 그린다.");}
}

class Point{
    int x;
    int y;
    
    Point(){this(0,0);}
    Point(int x, int y){
        this.x = x;
        this.y = y;
    }
}

class Circle extends Shape{
    Point center;
    int r;
    
    Circle(){
        this(new Point(0,0), 100);
    }
    Circle(Point center, int r){
        this.center = center;
        this.r = r;
    }
}

class Triangle extends Shape{
    Point[] p;
    
    Triangle(Point[] p){
        this.p = p;
    }
    
    Triangle(Point p1, Point p2, Point p3){
        p = new Point[]{p1,p2,p3};
    }
}

// ================================================

Circle c1 = new Circle();
// x에 접근하려면 c1.center.x;
Circle c2 = new Circle(new Point(150,150), 500);

Point[] p = {
                new Point(100,100),
                new Point(140,50),
                new Point(200,100),
            };

Triangle t1 = new Triangle(p);
// 첫번째 요소의 x에 접근하려면 t1.p[0].x

오버 라이딩(overrideing)

부모 클래스로부터 상속받은 메서드의 구현부를 자식 클래스에 자기한테 맞게 변경하는 것을 말한다.

오버 라이딩의 조건

  1. 선언부가 같아야 한다. (이름, 매개변수, 리턴 타입)
  2. 접근 제어자가 좁은 범위로 변경될 수 없다.
    • public > protected > default > private
  3. 부모 클래스의 메서드보다 많은 수의 예외를 선언할 수 없다.
// 오버라이딩과 오버로딩 구분하기
class parent{
    void parentMethod(){System.out.println("hello");}
}

class Child extends parent{
    void parentMethod(){System.out.println("1");} // 오버라이딩
    void parentMethod(int i){System.out.println(i);} // 오버로딩
    
    void ChildMethod(){System.out.println("hi");}
    void ChildMethod(int i){System.out.println(i);} // 오버로딩
    void ChildMethod(){} // Error! 중복정의
}

참조 변수 super

참조 변수 super는 조상의 멤버와 자신의 멤버를 구분하기 위해 사용된다.

class Parent{
    int x = 10;
    
    String getLocation(){
        return "x = " + x;
    }
}

class Child extends Parent{
    int x = 20;
    int y = 30;
    void method(){
        System.out.println("x = " + x); // 가장 가까운 x에 접근 (20)
        System.out.println("this.x = " + this.x); // 자신의 x에 접근 (20)
        System.out.println("super.x = " + super.x); // 부모의 x에 접근 (10)
    }
    
    String getLocation(){
        // return "x = " + x + " y = " + y; // 오버라이딩으로 만들기
        return super.getLocation() + " y = " + y; // 조상 메서드 호출해서 만들기
    }
}

super( ) 부모 생성자

자식 클래스의 인스턴스를 생성하면 부모의 자식의 멤버가 합쳐진 인스턴스가 생성된다.

  • 자식의 멤버는 자식 클래스의 생성자로 인해 초기화된다.
  • 부모의 멤버는 자식 클래스에서 초기화될 수 없다.
    • 이런 이유로 자식 클래스의 생성자의 첫 문장에서 부모의 생성자를 호출해야 한다.
  • 생성자 첫 줄에는 this() 혹은 super()를 호출해야 한다.
    • 그렇지 않으면 컴파일러가 자동적으로 super()를 삽입한다.
  • 부모의 멤버는 부모 클래스에서 초기화하고 자식의 멤버는 자식 클래스에서 초기화는 것이 좋다.
class Point(){ // extends Object
    int x;
    int y;
    
    Point(){
        this(0,0);
    }
    Point(int x, int y){
         // super(); >> Object();
         this.x = x;
         this.y = y;
    }
    
    String getLocation(){
        return "x = " + x + " y = " + y;
    }
}

class Point3D extends Point{
    int z;
    
    Point3D(int x, int y, int z){
        super(x,y); // 부모 클래스의 Point(x,y) 생성자 호출 OK
        this.z = z;
    }
    /* 만약 여기서 super생성자를 사용하지 않을 경우?
    Point3D(int x, int y, int z){
        // super(); >> 컴파일러가 생성한다.
        // 부모 클래스에는 Point()라는 기본생성자가 없으므로 Error! 발생
        this.x = x;
        this.y = y;
        this.z = z;
    }
    */
    String getLocation(){ // 오버라이딩
        return "x = " + x + " y = " + y + " z = " + z;
    }
}

패키지(package)

패키지는 서로 관련된 클래스와 인터페이스의 묶음입니다.

  • 패키지의 선언은 소스파일의 첫 번째 문장으로 단 한번 선언합니다.
  • 하나의 소스코드에 두 개 이상의 클래스가 포함된 경우 모두 같은 패키지에 속한다.
  • 패키지 선언이 없을 경우는 unnamed 패키지에 속한다.

import문

사용할 클래스가 속한 패키지를 지정하는 데 사용한다.

  • import문을 사용하면 클래스를 사용할 때 패키 지명을 생략할 수 있다.
  • 패키지 문과 클래스 선언의 사이에 선언한다.
  • 선언 방법
    • import 패키 지명. 클래스명; = 해당 클래스
    • import 패키 지명.*; = 패키지에 있는 모든 클래스
class Test{
    java.util.Date today = new java.util.Date();
}

import java.util.*;
class Test{
    Date today = new Date();
}
  • java.lang패키지의 클래스는 import하지 않아도 생략 가능하다.
class Test{
    java.lang.System.out.println("hello");
}

class Test{
    System.out.println("hello");
}

만약 이름이 같은 클래스가 속한 두 패키지를 import 할 때는 클래스 앞에 패키지 명을 붙여주는 것이 좋다.

import java.sql.*; // java.sql.Date
import java.util.*; // java.util.Date

class Test{
    java.util.Date today = new java.util.Date();
}

제어자(modifiers)

클래스, 변수, 메서드의 선언부에 사용되어 부가적인 의미를 부여한다.

  • 제어자는 크게 접근 제어자와 그 외 제어자로 나뉜다.
    • 접근 제어자 : public, protected, default, private
    • 그 외 제어자 : static, final, abstract, native, volatile, synchronized 등등

그 외 제어자에 대해 알아보기

static = 공통적인

static이 사용되는 곳은 멤버 변수, 메서드, 초기화 블록이다.

대상 의미
멤버변수 모든 인스턴스에 공통적으로 사용되는 클래스 변수가 된다.
클래스 변수는 인스턴스를 생성하지 않고도 사용 가능하다.
클래스가 메모리에 로드될 때 생성된다.
메서드 인스턴스를 생성하지 않고도 호출 가능하며 static 메서드라고 부른다.
static 메서드 내에서는 인스턴스 멤버를 사용할 수 없다.
class Test1{
    static int x = 20;
    static int y = 10;
    
    static{x = 30;}
    
    static int max(int a, int b){
        return a > b ? a : b;
    }
}

class Test2{
    int width = Test1.x; // OK (30)
    int height = Test1.y; // OK (10)
}

final = 마지막의

final이 사용되는 곳은 클래스, 메서드, 멤버 변수, 지역변수이다.

대상 의미
클래스 확장 될 수 없는 클래스이다.
다른 클래스의 부모가 될 수 없다.
메서드 오버라이딩을 통해 재정의 할 수 없는 메서드이다.
멤버변수 변수 앞에 final이 붙으면 값을 변경할 수 없는 상수가 된다.
지역변수
final class Test{
    final int MAX = 10; // 멤버변수 (상수)
    
    final void getMaxSize(){ // 메서드 (overriding 불가능)
        final LV = MAX; // 지역변수 (상수)
        return MAX;
    }
}
class Test2 extends Test{
    void getMaxSize(){ // Error! 오버라이딩 불가능
        System.out.println("Error!");
    }
}

final 멤버 변수는 선언과 동시에 초기화를 하는 게 좋지만 인스턴스 변수의 경우 생성자를 통해 초기화할 수 있다.

class Test{
     final int NUMBER; // 상수지만 초기화 x
     
     Test(int num){
         NUMBER = num; // 상수 초기화
     }
}

Test t1 = new Test(5);
t1.NUMBER = 10; // Error! 상수이므로 변경 불가능

abstract = 추상의

abstract가 사용되는 곳은 클래스, 메서드이다.

대상 의미
클래스 클래스 내에 추상 메서드가 선언되어 있음을 의미한다.
메서드 선언부만 작성되고 구현부는 작성되지 않은 추상 메서드임을 알린다.
abstract class Test{ // 추상 클래스
    abstract void move(); // 추상 메서드
}

접근 제어자(access modifier)

멤버 또는 클래스에 사용되어 외부로부터 접근을 제한한다.

  • 외부로부터 데이터를 보호하기 위함이다.
대상 의미
private 같은 클래스 내에서만 접근이 가능하다.
사용 가능한 곳 : 멤버
default 같은 패키지 내에서만 접근이 가능하다.
사용 가능한 곳 : 클래스, 멤버
protected 같은 패키지 내에서, 다른 패키지의 자식 클래스에서 접근 가능하다.
사용 가능한 곳 : 멤버
public 접근 제한이 없다.
사용 가능한 곳 : 클래스, 멤버
class Time{
    private int hour;
    
    Time(int hour){
        setHour(hour);
    }
    
    public int getHour(){return hour;}
    public void setHour(int hour){
        if(hour < 0 || hour > 23){
            return;
        }
        this.hour = hour;
    }
}

Time t = new Time(12);
t.hour = 13; // Error! private이므로 외부에서 접근이 불가능하다.
t.setHour(t.getHour() + 1); // 13

생성자의 접근 제어자

일반적으로 생성자의 접근 제어자는 클래스의 접근 제어자와 일치한다.

  • 생성자에 접근 제어자를 사용함으로써 인스턴스 생성을 제한할 수 있다.
class Test{
    int x;
    int y;
    private static Test s = null;
    
    private Test(int x, int y){
        this.x = x;
        this.y = y;
    }
    
    public static Test getInstance(int a, int b){
        if(s == null){
            s = new Test(a,b);
        }
        return s;
    }
}

Test t1 = Test.getInstance(1,2);
System.out.println(t1.x); // 1

다형성(polymorphism)

부모의 참조 변수로 자식 타입의 객체를 다룰 수 있는 행위를 의미한다.

  • 반대의 경우는 성립되지 않는다.
class Tv{
    int channel;
    void channelUp(){channel++;}
}

class SmartTv extends Tv{
    String text;
    void show(){System.out.println(text);}
}

Tv t1 = new SmartTv();
SmartTv t2 = new Tv(); // Error!

다형성을 통해 멤버에 접근하기

// 위 코드에 이어서 작성됩니다.

Tv t1 = new SmartTv();
t1.channel = 7; // OK
t1.channelUp(); // OK
t1.text = "SmartTv"; // Error!

실제 생성된 인스턴스의 타입은 자식 타입이지만 참조 변수가 부모 타입이기 때문에 부모의 멤버에만 접근이 가능하다.

참조 변수의 형 변환

서로 상속관계에 있는 타입 간의 형 변환이 가능하다.

class Car{
    int door;
    String color;
    
    void drive(){System.out.println("drive Brrrr");}
}

class FireEngine extends Car{
    void water(){System.out.println("water");}
}

Car c = null;
FireEngine fe1 = new FireEngine();
FireEngine fe2 = null;

fe1.water(); // OK
c = (Car)fe1; // OK >> c = fe1 (부모를 자식으로 형변환)
c.water(); // Error!
fe2 = (FireEngine)c; // OK >> 자식을 부모로 형변환
fe2.water(); // OK

instanceof 연산자

참조 변수가 참조한 인스턴스의 실제 타입을 체크하는 데 사용된다.

  • 이항 연산자이며 연산 결과는 true, false로 반환된다.
  • A instanceof B = A가 참조한 인스턴스의 실제 타입이 B 또는 B의 자식 타입인지 확인한다.
class Car{
    int door;
}

class FireEngine extends Car{
    int water;
}

FireEngine fe = new FireEngine();

if(fe instanceof FireEngine){
    System.out.println("OK");
}
if(fe instanceof Car){
    System.out.println("OK");
}
if(fe instanceof Object){
    System.out.println("OK");
}

참조 변수와 인스턴스 변수의 연결

부모와 자식 클래스 간 변수 또는 메서드가 중복된 경우 사용한다.

멤버 변수 중복 (참조 변수 타입에 영향을 받는다.)

  • 참조 변수의 타입에 따라 변수의 값이 달라진다.

메서드 중복 (참조 변수 타입에 영향을 받지 않는다.)

  • 참조 변수와 상관없이 실제 생성된 인스턴스의 타입에 메서드가 호출된다.
class parent{
    int x = 10;
    
    void method(){System.out.println("parent Method");}
}

class child extends parent{
    int x = 20;
    
    void method(){Sysetem.out.println("child Method");}
}

parent p = new child(); // 다형성
child c = new child();

p.x; // 10 >> 참조변수에 영향을 받는다.
p.method(); // child Method >> 참조변수에 영향을 받지 않는다.
c.x; // 20 >> 참조변수에 영향을 받는다.
c.method(); // child Method >> 참조변수에 영향을 받지 않는다.

매개변수의 다형성

참조형 매개변수는 메서드 호출 시 자신 또는 자신의 자식 타입의 인스턴스를 넘겨줄 수 있다.

class Product{
    int price;
}

class Tv extends Product{}
class Audio extends Product{}

class Buyer{
    int money = 100;
    
    void Buy(Product p){ // 매개변수에 다형성을 사용
        money = money - p.price;
    }
    
    // 매개변수에 다형성을 사용하지 않았을 경우
    // 하나하나 다 만들어줘야 한다.
    void Buy(Tv t){
        money = money - t.price;
    }
    void Buy(Audio a){
        money = money - a.price;
    }
}

Buyer user = new Buyer();
Product p1 = new Tv(); // 다형성
Product p2 = new Audio(); // 다형성

user.Buy(p1);
user.Buy(p2);

여러 종류의 객체를 하나의 배열로 다루기

부모 타입의 배열에 자식들의 객체를 담을 수 있다.

class Product{
    int price;
}
class Tv extneds Product{}
class Audio extends Product{}

Product[] parr = new Product[2];
parr[0] = new Tv();
parr[1] = new Audio();

추상 클래스(abstract class)

추상 메서드를 포함하고 있는 클래스이다.

  • 추상 클래스도 생성자가 있어야 한다.
  • 추상 클래스의 일반 메서드는 추상 메서드를 호출할 수 있다.
  • 추상 클래스는 그 자체로 인스턴스를 생성할 수 없다.
  • 추상 클래스를 상속받은 클래스가 추상 메서드를 모두 구현해서 사용 가능하다.
abstract class Test{
    int currentPos;
    
    Test(){ // 생성자
        currentPos = 1;
    }
    
    abstract void start(int pos); // 추상메서드
    abstract void stop(); // 추상메서드
    
    void get(){
        start(currentPos); // 추상메서드 호출 가능
    }
}

추상 메서드(abstract method)

선언 부만 있고 구현부는 없는 메서드이다.

  • 공통적으로 사용되지만 클래스마다 다르게 구현되는 경우 사용한다.
  • 추상 클래스를 상속받은 자식 클래스는 추상 메서드를 모두 구현해야 사용 가능하다.
abstract class Test{
    abstract void get(); // 추상 메서드
}
Test t1 = new Test(); // Error!

class Test2 extends Test{
    void get(){System.out.println("OK");} // 추상 메서드 구현 완료
}
Test2 t2 = new Test2(); // OK

인터페이스(interface)

실제 구현된 것이 없는 기본 설계도라고 생각하면 쉽다.

  • 추상 메서드, 상수, default 메서드만 멤버로 가질 수 있다.
    • 상수의 경우 "인터페이스명. 변수명"으로 바로 접근 가능하다.
  • 인스턴스를 생성할 수 없으며 클래스 작성에 도움을 주기 위한 목적이다.
  • 표준을 제시하는 데 사용된다.
  • 서로 관계없는 클래스들에게 관계를 맺어줄 수 있다.
interface interfaceName{
    public static final int x = 10;
    // 모든 변수는 public static final이므로 생략 가능하다.
    // static이므로 바로 사용가능하다.
    
    public abstract void MethodName();
    // 모든 메서드는 public abstract이므로 생략 가능하다.
    
    int y = 1;
    void get();
}

인터페이스의 상속

인터페이스끼리는 다중 상속이 가능하다.

  • 인터페이스는 Object 같은 최상위 조상이 없다.

 

interface Test1{
    void get();
}
interface Test2{
    void start();
}

interface Test3 extends Test1, Test2{}

인터페이스의 구현

클래스에서 인터페이스를 상속하여 모든 추상 메서드 구현하면 사용이 가능하다.

  • 이때 사용하는 건 "implemets"이다.
  • 클래스 상속과 인터페이스 구현은 동시에 가능하다.
interface interT{
    void show();
}

class Test{
    int x;
    int y;
    
    void get(){System.out.println("get");}
}

class Test2 extends Test implements interT{
    public void show(){System.out.println("Hi");}
}

class Test3 implements interT{
    public void show(){System.out.println("hello");}
}

인터페이스를 이용한 다형성

인터페이스 타입의 변수로 인터페이스를 구현한 클래스의 인터페이스를 참조할 수 있다.

  • 인터페이스를 메서드의 매개변수로 지정할 수 있다.
  • 인터페이스를 리턴 타입으로 지정할 수 있다.
interface Fightable{
    void move(int x, int y);
    void attack(Fightable f);
}
class Unit{
    int x, y;
}

class Fighter extends Unit implements Fightable{
    int z = 10;
    public void move(int x, int y){System.out.printf("x = %d y= %d\n",x,y);}
    public void attack(Fightable f){ // 매개변수로 인터페이스 사용
        if(f instanceof Test){ // instanceof 연산자로 인스턴스 실제 타입 체크
            Test t = (Test)f;
            System.out.println(t.z); // 20
        }else if (f instanceof Fighter){
            Fighter fi = (Fighter)f;
            System.out.println(fi.z); // 10
        }
    }
    Fightable method(){return new Fighter();} // 리턴타입이 인터페이스인 경우
}

class Test implements Fightable{
    int z = 20;
    int x = 1;
    int y = 2;
    public void move(int x, int y){System.out.printf("x = %d y= %d\n",x,y);}
    public void attack(Fightable f){System.out.println(z);}
}

Fighter fe = new Fighter();
Test t1 = new Test();
fe.attack(t1); // Test도 Fightable을 구현한 클래스이기 때문에 매개변수로 줄 수 있다.
// 결과 : 20

관계없는 클래스 묶어보기

인터페이스를 사용해서 관계없는 클래스에게 관계를 맺어줄 수 있다.

interface Repairable{}

class Unit {
    int power;
    final int MAX_HP;
    Unit(int hp){
        MAX_HP = hp;
    }
}
class GroundUnit extends Unit{
    GroundUnit(int hp){
        super(hp);
    }
}

class Marine extends GroundUnit{
    Marine(){
        super(40);
        power = MAX_HP;
    }
}
class Tank extends GroundUnit implements Repairable{
    Tank(){
        super(150);
        power = MAX_HP;
    }
}
class AirUnit extends Unit implements Repairable{
    AirUnit(){
        super(100);
        power = MAX_HP;
    }
}
class Scv extends GroundUnit implements Repairable{
    Scv(){
        super(60);
        power = MAX_HP;
    }
    void repair(Repairable r){ // 매개변수로 인터테이스 타입 지정
        if (r instanceof Unit){ // r의 인스턴스가 Unit 또는 Unit의 자식타입인가?
            Unit u = (Unit)r; // 맞다면 형변환을 한다.
            while(u.power != MAX_HP){
                u.power++;
            }
        }
    }
}

Marine m1 = new Marine();
Tank t1 = new Tank();
AirUnit a1 = new AirUnit();
Scv s1 = new Scv();

s1.repair(m1); // Error!
s1.repair(t1); // OK
s1.repair(a1); // OK

디폴트 메서드(default method)

JDK 1.8부터 인터페이스에 디폴트 메서드를 추가할 수 있게 되었다.

  • 디폴트 메서드는 인터페이스에서 사용 가능한 일반 메서드이다.
  • 접근제어자 default랑 디폴트 메서드의 default는 아무 상관없는 거다.
interface Test{
    int i = 10; // 상수
    void method(); // 추상 메서드
    default void show(){}; // 디폴트 메서드
    // 디폴트 메서드는 앞에 public이 생략되어있다.
}

디폴트 메서드가 기존의 메서드와 충돌하는 경우 아래와 같이 해결된다.

  1. 여러 인터페이스의 디폴트 메서드 간의 충돌
    • 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버 라이딩해야 한다.
      • 오버 라이딩 시 접근제어자를 좁은 범위로 변경할 수 없다.
  2. 디폴트 메서드와 부모 클래스의 메서드 간의 충돌
    • 부모 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다.
interface Test1{
    default void show(){System.out.println("interface Test1");}
}
interface Test2{
    default void show(){System.out.println("interface Test2");}
}
class Test implements Test1, Test2{
    public void show(){System.out.println("interface Q");}
    // 인터페이스 간 디폴트 메서드 충돌일 경우 오버라이딩 해준다.
}

class parent{
    public void show(){System.out.println("parent method");}

}
class child extends parent implements Test1{}
// 인터페이스의 디폴트 메서드와 부모 클래스의 메서드가 충돌하게 될때
// 인터페이스의 디폴트 메서드는 무시된다.

내부 클래스

클래스 안에 선언된 클래스를 말한다.

  • 특정 클래스 내에서만 주로 사용되는 클래스는 내부 클래스로 선언한다.

내부 클래스의 종류와 특징

내부 클래스의 종류는 변수의 선언 위치에 따른 종류와 동일하다.

  • 범위(scope) 또한 유사하다.
내부 클래스 특징
인스턴스 내부 클래스 멤버변수 선언위치에 선언한다.
인스턴스 멤버처럼 다루어진다.
스태틱 내부 클래스 클래스 멤버변수 선언위치에 선언한다.
클래스 멤버처럼 다루어진다.
지역 내부 클래스 메서드나 초기화 블럭 안에 선언한다.
선언된 영역 내부에서만 사용된다.
익명 클래스 클래스의 선언과 객체의 생성을 동시에 한다.
1회용으로 사용된다.
class Outer{
    class InstanceInner{} // 인스턴스 내부 클래스
    static class StaticInner{} // 스태틱 내부 클래스
    
    void Method(){
        class LocalInner{} // 지역 내부 클래스
    }
}

내부 클래스의 제어자와 접근성

내부 클래스의 접근제어자는 변수에 사용할 수 있는 접근제어자와 동일한다.

  • static 클래스에서만 static 멤버를 정의할 수 있다. (JDK 1.8 기준)
    • JDK 17부터는 인스턴스, 지역 내부 클래스에서도 static 멤버를 정의할 수 있다.
public class MyClass {
    public static void main(String args[]) {
        Outer o = new Outer(1,2,3);
        Outer.II instance = o.new II(50);
        instance.method();
        o.method();
        System.out.println(Outer.SI.cv);
    }
}

class Outer{
    private int Outeriv;
    static int Outercv;
    final int OUTERIV = 10;
    int value;
    Outer(int x, int y, int z){
        Outeriv = x;
        Outercv = y;
        value = z;
    }
    public class II{
        int iv1;
        int iv2 = Outeriv; // private 변수도 바로 접근이 가능하다.
        int iv3 = Outercv;
        int iv4 = OUTERIV;
        int value;
        II(int x){
            value = x;
        }
        void method(){
            System.out.println("value = " + value); // 가까운 value
            System.out.println("this.value = " + this.value); // 자기 자신의 value
            System.out.println("Outer.this.value = " + Outer.this.value); // Outer의 value
        }
    }
    static class SI{
        // 스태틱 클래스 내부에서는 외부 인스턴스 멤버에 접근 할 수없다.
        static int cv = 10000;
    }
    
    void method(){
        // 지역 변수, 지역 변수 내부 클래스는 접근제어자를 붙일수 없다.
        int lv = 500;
        final int LV = 600;
        class LI{
            int iv1 = Outeriv;
            int iv2 = Outercv; //Outer.Outercv
            int iv3 = OUTERIV;
            int iv4 = lv;
            int iv5 = LV;
            int xx;
            LI(int x ){xx = x;}
            
            Outer.II i1 = new Outer.II(10);
            int sum = i1.iv1 + i1.iv2 + this.iv4 + this.iv5;
            void get(){
                System.out.println("o.method "+iv5);
            }
            void total(){
                System.out.println(iv1);
                System.out.println(iv2);
                System.out.println(iv3);
                System.out.println(iv4);
                System.out.println(iv5);
                System.out.println(xx);
            }
        }
        LI l1 = new LI(10);
        l1.get();
        l1.total();
    }
}

익명 클래스 (anonymous class)

이름이 없는 일회용 클래스로서 단 하나의 객체만을 생성할 수 있습니다.

new 클래스 이름(){
    // 멤버 선언
}

new 구현 인터페이스 이름(){
    // 멤버 선언
}

class Test{
    Object iv = new Object(){void method(){}}; // 익명 클래스
    static Object cv = new Object(){void method(){}}; // 익명 클래스
    
    void myMethod(){
        Object lv = new Object(){void method(){}}; // 익명 클래스
    }
}

예외처리(Exception handling)

프로그램의 비정상 종료를 막고 정상적인 실행상태를 유지하는 것을 말한다.

구분 특징
컴파일 에러 컴파일 할 때 발생하는 에러
(고치기 전까지 실행 불가)
런타임 에러 실행할 때 발생하는 에러
(실행 중 에러 발생 시 프로그램 종료)
  • 컴파일러가 하는 일 = 구문 체크, 번역, 최적화, 생략된 코드 추가

java의 런타임 에러

구분 특징
에러(error) 프로그램 코드에 의해서 수습될 수 심각한 오류
예외(exception) 프로그램 코드에 의해서 수습될 수 있는 미약한 오류
  • 에러는 어쩔 수 없지만 예외는 처리해야 한다.

예외 처리 구문 (try - catch)

예외를 처리하기 위해서는 try - catch문을 사용해야 한다.

try{
    // 예외가 발생할 가능성이 있는 코드 작성
}catch(Exception1 e){
    // Exception1이 발생했을 경우 이를 처리 하기 위한 코드 작성
}catch(Exception2 e){
    // Exception2이 발생했을 경우 이를 처리 하기 위한 코드 작성
}
  • try 블록에서 예외가 발생한 경우
    • 발생한 예외와 일치하는 catch블록이 있는지 확인
    • catch 블록을 찾으면 해당 catch블록 코드 수행 후 try-catch문 빠져나온다.
      • catch 블록이 없는 경우 런타임 에러 발생으로 프로그램 종료
  • try 블록에서 예외가 발생하지 않은 경우
    • try-catch문을 빠져나온다.

예외 발생시키기

강제로 예외를 발생시킬 수 있다.

Exception e = new Exception("고의로 예외 발생시켰다");

throw e; // 예외를 발생시킨다.

class Test{
    public static void main(String[] args){
        try{
            Exception e = new Exception("고의로 예외 발생!"); // 예외 선언
            throw e; // 예외 강제 발생
            // throw new Exception("고의로 예외 발생!"); // 한줄로 작성 가능
        }catch(Exception e){
            System.out.println("에러 메시지 : " + e.getMessage());
            e.printStackTrace();
        }
        Sysetem.out.println("프로그래림이 정상 종료 되었습니다.");
    }
}

// 실행결과
// 에러 메시지 : 고의로 예외 발생!
// java.lang.Exception : 고의로 예외 발생!at Test.main(Test.java:8)
// 프로그램이 정상 종료 되었습니다.

예외 클래스 계층 구조

예외 클래스는 크게 두 그룹으로 나뉜다.

구분 특징
RuntimeException 클래스와
자식 클래스
프로그래머의 실수로 발생하는 예외
(예외처리 필수)
Exception 클래스와
자식 클래스
사용자의 실수와 같은 외적인 요인으로 인한 예외
(예외처리 선택)
  • Exception (모든 예외의 최고 조상) - 모든 예외 처리 가능
    • IOException (입출력 예외)
    • ClassNotFoundException (클래스가 존재하지 않는다.) - ex) *. class
    • ...
    • RuntimeException
      • ArithmeticException (산술 계산 예외) - ex) 5/0
      • ClassCastException (형 변환 예외)
      • NullPointException (널 포인트) - ex) String str = null; str.length;
      • ...
      • ArrayIndexOutOfBoundsException (배열 범위 벗어남)

finally 블록

예외 발생 여부와 관계없이 무조건 실행되어야 하는 코드이며 선택적으로 사용할 수 있다.

  • 예외 발생 o = try > catch > finally
  • 예외 발생 x = try > finally
try{
    // 예외 발생할 가능성 있는 코드
}catch(Exception e){
    // 예외 처리를 위한 코드
}finally{
    // 예외와 관계없이 실행할 코드
    // 무조건 try-catch문의 맨 마지막에 작성해야한다.
}

메서드에 예외 선언하기 (throws)

예외를 처리하는 것이 아닌 호출한 메서드로 전달해주는 것을 말한다.

  • 메서드를 호출한 곳에서 예외처리를 해야 한다.
void method() throws Exception1, Exception2 ... ExceptionN{
    // 메서드 내용
}

예외 되던지기(re-throwing)

예외를 처리한 후에 다시 예외를 생성해서 호출한 메서드로 전달한다.

class Test{
    public static void main(String[] args){
        try{
            method1();
        }catch(Exception e){
            System.out.println("main메서드에서 예외가 처리되었습니다.");
        }
    }
    static void method1() throws Exception{
        try{
            throw new Exception("고의로 예외 발생!");
        }catch(Exception e){
            System.out.println("method1메서드에서 예외가 처리 되었습니다.");
            throw e; // 다시 예외 발생(re-throwing)
        }
    }
}

// 실행 결과
// method1메서드에서 예외가 처리 되었습니다.
// main메서드에서 예외가 처리되었습니다.

Comments