일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 주식
- 주린이
- 오버라이딩
- 다형성
- 현금흐름표
- Java
- 그리디 알고리즘
- 금리인상
- object
- 미국주식
- StringBuffer
- 기업분석
- 제태크
- 접근제어자
- 배당성장
- 금리인하
- mco
- XLF
- 프로그래머스
- 인플레이션
- 알고리즘
- S&P500
- 백준
- etf
- javascript
- FCF
- 자바
- 잉여현금흐름
- 객체지향
- 무디스
- Today
- Total
오늘의하루
[Java] 변수, 연산자, 조건문, 반복문, 배열, 객체지향, 예외처리 본문
변수(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 오버 로딩
하나의 클래스에 같은 이름의 메서드를 열개 정의하는 것을 메서드 오버 로딩이라고 부른다.
오버 로딩의 조건
선언 부만 신경 쓰면 되며 구현부는 각각의 메서드에 맞게 변경 가능하다.
- 메서드의 이름이 같아야 한다.
- 매개변수의 개수 또는 타입이 달라야 한다.
- 리턴 타입은 오버 로딩에 아무런 영향을 주지 못한다.
- 리턴 타입만 다른 것은 오버 로딩이 아니라는 말이다.
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();
- new 연산자에 의해서 heap영역에 Test클래스의 인스턴스 생성한다.
- 생성자 Test()가 호출되어 수행된다.
- 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;
}
}
- 클래스 변수 cv는 기본값 0으로 초기화된다.
- 클래스 변수 cv에 1을 대입하여 저장한다. (명시적 초기화)
- 초기화 블록에서 클래스 변수 cv에 3을 대입하여 저장한다.
- 인스턴스 변수 iv는 기본값 0으로 초기화된다.
- 인스턴스 변수 iv에 2를 대입하여 저장한다. (명시적 초기화)
- 초기화 블록에서 인스턴스 변수 iv에 4를 대입하여 저장한다.
- 생성자를 통해 인스턴스 변수 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)
부모 클래스로부터 상속받은 메서드의 구현부를 자식 클래스에 자기한테 맞게 변경하는 것을 말한다.
오버 라이딩의 조건
- 선언부가 같아야 한다. (이름, 매개변수, 리턴 타입)
- 접근 제어자가 좁은 범위로 변경될 수 없다.
- public > protected > default > private
- 부모 클래스의 메서드보다 많은 수의 예외를 선언할 수 없다.
// 오버라이딩과 오버로딩 구분하기
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이 생략되어있다.
}
디폴트 메서드가 기존의 메서드와 충돌하는 경우 아래와 같이 해결된다.
- 여러 인터페이스의 디폴트 메서드 간의 충돌
- 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버 라이딩해야 한다.
- 오버 라이딩 시 접근제어자를 좁은 범위로 변경할 수 없다.
- 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버 라이딩해야 한다.
- 디폴트 메서드와 부모 클래스의 메서드 간의 충돌
- 부모 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다.
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메서드에서 예외가 처리되었습니다.
'JAVA' 카테고리의 다른 글
[프로그래머스 1단계 - JAVA] 자릿수 더하기 (0) | 2022.09.16 |
---|---|
[프로그래머스 1단계 - JAVA] 짝수와 홀수 (0) | 2022.09.16 |
[Java] Process & Thread (프로세스와 스레드) 알아보기 (0) | 2022.09.05 |
[Java] 표준, 메타 Annotation(애너테이션) (0) | 2022.09.02 |
[Java Note] Generics(제네릭스) 선언 정의 제한 형 변환 와일드 카드 (0) | 2022.08.31 |