Java 이론 정리(기초)

김범수·2023년 7월 12일
1

문법

목록 보기
2/3

시작하며

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 이전에 객체지향 언어로 가장 범용적인 언어)

  • 1983년 C++
  • 1995년 Java (1996년 1월 JDK 1.0 발표)
    미국 Sun Microsystems사에서 개발(Oracle 인수)

특징

  • 플랫폼 독립적 (JVM을 통해 어느 OS에서든 Compile 가능하여 어느 OS에서 개발하더라도 실행 가능)
  • 객체지향 프로그래밍
  • Garbage Collector로 사용되지 않는 메모리 자동적 정리
  • 재활용성과 직관성이 강함

개발환경 구축

JDK 설치

JDK -> Java Development Kit
Oracle 사이트에서 JDK Download 가능

Java SE -> JDK 다운로드 가능

  1. JDK (Java Development Kit)
    • JDK는 Java 언어로 애플리케이션을 개발하기 위한 도구 모음
    • JDK는 Java 컴파일러 (javac)를 비롯하여 개발에 필요한 다양한 도구와 라이브러리를 포함
  2. Server JRE (Server Java Runtime Environment)
    • Server JRE는 JRE의 기능을 포함하면서 서버 환경에서의 성능 및 관리 향상을 위한 추가 기능을 제공하는 패키지
  3. JRE (Java Runtime Environment)
    • JRE는 일반 사용자가 Java 애플리케이션을 실행하는 데 필요한 최소한의 런타임 환경을 제공

PATH 설정

시스템 - 환경 변수 - 시스템 변수에 설정

  • 변수 이름: JAVA_HOME

  • 변수 경로: jdk 경로

  • 변수 이름: CLASSPATH

  • 변수 경로: .;%JAVA_HOME%\lib\tools.jar

  • 변수 이름: Path

  • 변수 경로: %JAVA_HOME%\bin;

위 3개의 환경 변수 지정

Eclipse Tool

  • 필자는 Intellij를 더 자주 사용
    Eclipse IDE for Java EE Developers -> Web Development 지원

개발 순서

  1. 코드 작성
  2. 코드 컴파일
  3. JVM으로 실행
  • FileName.java
  • > javac FileName.java
  • FileName.class 생성
  • > java FileName.class
  • FileName Java 실행

Compiler는 작성한 파일을 실행 가능한 코드로 바꾸는 역할을 하고 이러한 과정을 컴파일이라함.
Code -> Compile 하는 과정에서 문제가 발생하면 Compile Error

주석문

// 행 단위 주석
//

// 블럭단위 주석
/**/

// 문서화 주석
/**
 * 
 * @author Kimbumsoo99
 * @param args
 */

상수

한 번 저장한 데이터 값을 변경할 수 없는 것
상수는 대문자의 명명규칙을 사용한다.
대문자 선언

final int J;
J = 10; // 이렇게 한 번 선언하면 수정할 수 없음

기본형 데이터 타입

  1. 논리리터럴
    • boolean 1byte
  2. 문자리터럴
    • char 2byte
  3. 정수리터럴
    • byte, short, char, int, long
    • 1, 2, 2, 4, 8 byte
    • long l = 214783648L;
  4. 실수리터럴
    • float, double
    • 4, 8 byte
    • 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;

연산자

  • 연산자(Operations): +, -, *, /, %, =, ... 등
  • 피연산자(Operand): 리터럴, 변수 등
  • 연산식(Expressions): 연산자 + 피연산자를 통한 연산 과정

연산자 우선순위

연산자예시 기호
최우선 연산자. [] ()
단항 연산자++ -- ! ~ +/- : 부정, bit변환>부호>증감
산술 연산자* / % > + - > shift/시프트 연산자 >> << >>>
비교 연산자> < >= <= == !=
비트 연산자&
논리 연산자&& >
삼항 연산자조건식 ? 참 : 거짓
대입 연산자= *= /= %= += -=

논리연산자

A, B가 피연산자인 경우

AB-----A&&BA||B!AA^B
TRUETRUETRUETRUEFALSEFALSE
TRUEFALSEFALSETRUEFALSETRUE
FALSETRUEFALSETRUETRUETRUE
FALSEFALSEFALSEFALSETRUEFALSE

조건문

자바에서 제공하는 문법

  • if, else if, else
  • switch

삼항연산자

int b = (5 > 4) ? 50 : 40;

switch

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

for each

int[] iarr = {10,20,30,40,50};
for(int value:iarr){
    System.out.println(value);
}

배열

1차원 배열

int[] arr1 = new int[100];
int[] arr2 = new int[]{1,2,3,4};
int[] arr3 = {1,2,3,4];

2차원 배열

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 키워드를 통해 객체를 메모리에 생성하면, 이러한 객체를 인스턴스라고 한다.
변수로 나타내면 메모리에 올라간 인스턴스를 가리키는 변수가 된다.

문자열 String

자바에서 가장 많이 사용되는 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를 사용하지 않고 사용할 수 있다. (메모리 소모 절약)

Method

String에는 많은 method가 존재한다.

Stirng str = "hello";
System.out.println(str.substring(3)); // 출력: lo (idx 3부터 출력)
System.out.println(str); // 출력: Hello (값 변화 X)

위에서 동작하는 substring()에 경우
값을 바꾸는게 아니라 새로운 String을 반환한다는 것을 알아야 한다.

Field [Class 구성요소(변수)]

Class내에서 변수처럼 사용되는 것들을 Field라고 한다.
class People에서 name, age 등이 field다.

객체가 가지는 속성 -> Field

public class Car{
    String name;
    int number;
}

Method

필드가 물체의 상태라면, 물체의 행동에 해당하는게 메소드이다.

  • 인자( Argument ) 는 어떤 함수를 호출시에 전달되는 값을 의미
  • 매개 변수( Parameter ) 는 그 전달된 인자를 받아들이는 변수를 의미
int x = 3, y = 4;
add(x, y); // Argument
int add(int a, int b); // Parameter

변수 scope와 static

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하게 선언된 변수는 값을 저장할 수 있는 공간이 하나만 생성되어, 인스턴스가 여러개 생성되도 static한 변수는 하나다.
보통 사용은 ClassName.staticValue로 사용한다.

열거형(enum)

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;
    }
}

위 예시 코드와 같은 이유로 특정 값만 지녀야한다면 열거형을 사용하는 것이 좋다.

생성자

모든 클래스는 인스턴스화 될때 생성자를 사용한다.
생성자는 보통 객체가 될 때 필드를 초기화 하는 역할을 수행

특징

  1. 생성자는 리턴타입이 없다.
  2. 생성자를 프로그래머가 만들지 않으면 매개변수가 없는 생성자가 컴파일할 때 자동으로 만들어진다.
  3. 매개변수가 없는 생성자를 기본생성자라고 한다.
  4. 생성자를 하나라도 프로그래머가 만들었다면 기본생성자는 자동으로 만들어지지 않는다.

예시

public class Car{
	String name;
	int number;

	public Car(String n){
		name = n;
	}
}

this

this는 현재 객체, 자기 자신을 나타낸다.
생성자 예시처럼 변수명을 다르게 선언할 수 있지만, 변수명은 공통된 것을 사용해야 의미를 파악하기 쉽다. 위 코드에 경우 매개변수인 n의 스코프가 global scope에 있는 name보다 우선순위가 더 높아 이름을 다르게 선언해야했는데 this를 사용하면 그렇지 않아도 된다.

public Car(String name){
	this.name = name;
}

변수 이름뿐아니라 Method를 사용할 때에도 this.methodName();으로 호출할 수 있다.

Overloading

오버로딩이란 매개변수의 유형과 개수가 다르게 하여 같은 이름의 메소드를 여러 개 가질 수 있게하는 기술이다.

메소드 오버로딩

  • 이름은 같지만 매개변수가 다른 메소드
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

패키지(package)란 서로 관련이 있는 클래스 또는 인터페이스들을 묶어 놓은 묶음이다. 패키지를 사용함으로써 클래스들이 필요할 때만 사용될 수 있도록 하고, 클래스를 패키지 이름과 함께 계층적인 형태로 사용함으로써 다른 그룹에 속한 클래스와 발생할 수 있는 클래스 이름간의 충돌을 막아줌으로 클래스의 관리를 편하게 해준다.

패키지 이름 정의

  • package 이름은 보통 도메인 이름을 거꾸로 적은 후, 프로젝트 이름을 붙인다.
  • 폴더명 구분은 .을 통해서 하고, 숫자로 시작할 수 없다.
  • 예를 들어 도메인 이름이 8curz.com이고, 프로젝트 이름이 javastudy라면 다음과 같이 패키지를 지정한다. 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("날다.");
	}
}
  1. 추상 클래스: 미완성의 추상 메소드를 포함할 수 있다. 하나라도 추상메소드가 있다면 추상 클래스가 된다.
  2. 추상 메소드: 내용이 없는 메소드, 구현을 하지 않았음.

구현

public class Duck extends Bird{
	@Override
	public void sing() {
		System.out.println("꽥꽥!!");
	}
}

추상 클래스가 갖고 있는 추상 메소드를 구현해야한다. 추상 클래스를 상속 받았는데 이를 구현하지 않는다면, 해당 클래스도 추상 클래스가 된다.
추상 클래스는 객체를 가질 수 없다.
Bird b = new Bird(); -> Compile Error

super

super는 this와 비슷하다. 일반적으로 모든 클래스는 생성자를 가지고 있는다. 사용자가 지정하지 않았다면 기본생성자라도 가지고 있게 된다.
this는 현재 객체를 나타냈는데, super는 부모 객체를 나타내는 것을 의미한다.
상속받고 있는 객체를 가리키는 키워드이며, 사용은 this와 비슷하게 사용된다.

super()

만약 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

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라는 키워드를 사용한다.
    또한 상속과는 달리 인터페이스는 여러개를 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();
    }
}

위 코드 예시처럼 클래스 형변환이 가능하다.

Interface method Type

기존 인터페이스는 추상메소드만 가질 수 있었는데, Java8부터는 두 가지가 생겼다.
1. default method
2. static method

default 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도 존재한다. 인터페이스에서 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;
  }
}

위와 같이 사용할 수 있다.

static 클래스

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();
	}
}

해당 익명 클래스를 사용하는 이유는, 해당클래스에서만 사용되고 다른 클래스에서 사용되지 않는 경우에 주로 사용한다.

Exception (예외)

프로그램을 실행하다보면 예기치 못한 사건을 예외라고한다. 예외 상황을 미리 예측하고 처리할 수 있는데, 이것을 바로 예외 처리라 한다.

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블록에서 모든 오류를 처리할 수 있다.

Throws

예외 처리를 위해 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는 다음과 같이 사용한다.

  • 예시 1
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;
}
  • 예시 2
public class ExceptionExam {
    public int get50thItem (int []array) throws ArrayIndexOutOfBoundsException{
        return  array[49];
    }
}

예외 발생 throw

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이 발생한다는 의미이다.

사용자 정의 Exception

public class ClassName extends Exception{
}

이런식으로 사용자가 직접 Exception을 만들 수 있다. 이렇게 굳이 사용자가 직접 예외클래스를 만드는 이유는 가독성 때문이다. 에러가 발생했을 때 무슨 에러가 나타났는지 클래스이름만 가지고 한 눈에 알아볼 수 있기 때문이다.

Exception은 두 가지로 나뉘게 된다.
1. Exception 클래스를 상속 받아 정의한 checkedException
2. RuntimeException 클래스를 상속 받아 정의한 unCheckedException

CheckedException

위에서 말한 것처럼 Exception 클래스를 상속 받은 예외이다.
해당 경우는 예외가 발생했을 때 예외 처리를 하지 않는다면, 반드시 컴파일 오류를 발생 시킨다.

public class MyCheckedException extends Exception{
}

Unchecked 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)

profile
새싹

0개의 댓글