11-1 / java.lang 패키지

java.lang은 자바 프로그램의 기본적인 클래스를 담고 있는 패키지. 이 패키지는 가장 기본이기에 이 패키지의 클래스와 인터페이스는 import 없이 사용가능하다.

 

이 패키지에 속하는 주요 클래스와 간략 용도

클래스 용도
Object - 자바 클래스의 최상위 클래스로 사용
System - 표준 입력 장치(키보드)로부터 데이터를 입력받을 때 사용
- 표준 출력 장치(모니터)로 출력하기 위해 사용
- 자바 가상 기계를 종료할 때 사용
- 쓰레기 수집기를 실행 요청할 때 사용
Class - 클래스를 메모리로 로딩할 때 사용
String - 문자열을 저장하고 여러가지 정보를 얻을 때 사용
Wrapper Byte, Short, Character,
Integer, Float, Double, Boolean, Long
- 기본 타입의 데이터를 갖는 객체를 만들 때 사용
- 문자열을 기본 타입으로 변환할 때 사용
- 입력값 검사에 사용
Math - 수학 함수를 이용할 때 사용

 

 


자바 API 도큐먼트

API는 라이브러리라고 부르기도 하는데, 프로그램 개발에 자주 사용되는 클래스 및 인터페이스의 모음을 말한다. 

 

방대한 자바 표준 API 중 원하는 API를 찾기위해 공식 문서를 이용할 수 있다.

 

Java Platform, Standard Edition Documentation - Releases

Java Platform, Standard Edition documentation, current and previous releases

docs.oracle.com

 

문서보는 법

상단부
하단 상세부

 

 


object 클래스

클래스 선언시 extends 키워드를 사용하지 않더라도 암시적으로 java.lang.Object를 상속하게 된다.

내가 바로 작성하는 클래스도 포함 자바의 모든 클래스들은 Object 클래스의 하위 클래스에 해당한다.

object는 필드가 없고, 생성자와 메소드로만 구성되어 있다.

 

  • 객체비교( equals() ) 

        - 두 객체가 동일한 데이터를 가지고 있는지 판별하는 메소드.

        - 객체가 동일한가를 판별하는 것과는 다르다.

        - equals() 메소드의 매개타입은 Object에 해당하며 이는 즉 모든 객체가 매개값이 될 수 있다는 이야기이다. 

          가장 상위클래스이기 때문에 자동변환이 가능하기 때문이다. 

        - 반환 값은 boolean을 반환한다.

 

  • 객체 해시코드( hashCode() )

        - 객체를 식별하는 하나의 정수값을 리턴한다.

          객체의 메모리 번지를 이용해서 해시코드를 만들기 때문에 유일한 값을 가진다.

        - 컬렉션 프레임워크 중 HashMap, HashSet, Hashtable에서 두 객체가 동등한지 비교할 때 hashCode()를 사용한다.

          ( hashCode() 리턴값이 다르면 다른 객체, 같으면 equals() 리턴값을 판단 false면 다른 객체, true면 동등객체 )

 

  • 객체 문자 정보 ( toString() ) 

        - 객체의 문자 정보(객체를 문자열로 표현한 값)를 리턴한다.

        - 형식 : 클래스이름@16진수해시코드

        - 그대로 사용하기 보다는 이 메소드를 오버라이딩하여 유익한 정보를 리턴하는 방법으로 사용되고 있다.

        - System.out.println() 메소드에서 기본타입 경우 값 그대로 출력하나,

          객체 즉 참조타입 경우에는 객체의 toString() 메소드를 호출해 리턴값을 받아 출력한다.

 

 

 


System 클래스

자바의 프로그램은 운영체제에서 바로 실행되는 것이 아니라 JVM위에서 실행되기 때문에 직접 운영체제 기능을 사용할 수 없어 System 클래스를 통해 운영체제의 일부 기능을 사용한다. System 클래스의 모든 필드와 메소드는 정적필드와 정적 메소드로 구성되어 있다. 

 

  • 프로그램 종료 ( exit() )

        - JVM을 강제로 종료

        - 매개값으로 int를 지정하게 되어있는데 이 값을 종료 상태값이라 한다. 일반적으로 정상종료는 0에 해당한다.

System.exit(0);

 

  • 현재 시간 읽기 ( currentTimeMillis(), nanoTime() )

        - 컴퓨터의 시계로부터 현재 시간을 읽어 밀리세컨드 (1/1000초)와 나노세컨드 (1/10⁹초) 단위의 long값을 리턴한다.

        - 주로 프로그램의 실행 소요 시간 측정에 사용된다.

 

 

 


Class 클래스

클래스와 인터페이스의 메타 데이터를 이 클래스로 관리한다.

여기서 메타 데이터란 클래스 이름, 생성자 성보, 필드 정보, 메소드 정보를 말한다.

 

  • Class 객체 얻기 ( getClass(), forName() )

프로그램에서 Class 객체를 얻는 3가지 방법이 있으며, 이렇게 Class객체를 얻어, Class의 메소드를 이용해 클래스 정보를 얻을 수 있다.

 

① 객체없이 클래스 이름만 가지고 Class 객체 얻는 방법 1

Class clazz= String.class;

 

② 객체없이 클래스 이름만 가지고 Class 객체 얻는 방법 2

Class clazz = Class.forName("패키지...클래스이름");

 

③ 클래스로부터 객체가 이미 생성되어 있어 객체로 부터 얻는 방법

Class clazz = 참조변수.getClass();

 

 

 

 

  • 클래스 경로를 활용해서 리소스 절대 경로 얻기

Class 객체는 해당 클래스의 파일 경로 정보도 가지고 있다.

이를 활용해 다른 리소스 파일의 절대 경로도 얻을 수 있어 UI프로그램에서 많이 활용된다.

Class clazz = 클래스이름.class;
String path = clazz.getResource("images/photho.jpg").getPath();

 

 


String 클래스

  • String 생성자

소스상 문자열 리터럴이 자동 String 개체를 생성하지만, String 클래스의 생성자로도 String객체를 생성할 수 있다. 

// 배열 전체를 String 객체로 생성
String str = new String(byte[] bytes);

// 지정한 문자셋으로 디코딩
String str = new String(byte[] bytes, String charsetName);

// 배열의 offset 인덱스 위치부터 length만큼 String 객체로 생성
String str = new String(byte[] bytes, int offset, int length);

// 지정한 문자셋으로 디코딩
String str = new String(byte[] bytes, int offset, int length, String charsetName);

 

  •  String 메소드
리턴타입 메소드이름(매개변수) 설명
char charAt(int index) 특정 위치의 문자를 리턴
boolean equals(Object anObject) - 두 문자열을 비교
- String경우 == 로 문자열 비교가 불가능하므로 이 메소드를 이용
byte[] getBytes() - byte[]로 리턴
- 네트워크로 문자열 전송 때 바이트로 변환 사용
- 문자열을 암호화 할 때 바이트로 변환 사용
byte[] getBytes(Charset charset) 주어진 문자셋으로 인코딩한 byte[]로 리턴
int indexOf(String str) - 문자열 내에서 주어진 문자열의 위치 리턴
- 주어진 문자열이 포함되있지 않으면 -1 반환
int length() - 총 문자의 수(문자열 길이)  리턴
- 공백을 포함
String replace(CharSequence target,
             CharSequence replacement)
target 부분을 replacement로 대치한 새로운 문자열 리턴
String substring(int beginIndex) beginIndex 위치부터 끝까지 부분을 잘라 문자열로 리턴
String substring(int beginIndex,
                int endIndex)
beginIndex 위치부터 endIndex "전"까지 부분을 잘라 문자열로 리턴
String  toLowerCase() 전부 알파벳 소문자로 변환한 문자열 리턴
String toUpperCase() 전부 알파벳 대문자로 변환한 문자열 리턴
String trim() 앞뒤 공백 제거한 문자열 리턴
String valueOf(int i)
valueOf(double d)
기본 타입 값을 문자열로 리턴

 

 


Wrapper(포장) 클래스

자바는 기본 타입의 값을 갖는 객체를 생성할 수 있다.

이렇게 기본 타입 값을 내부에 두고 포장하는 객체를 포장(Wrapper) 객체라고 한다.

포장 객체의 특징은 외부에서 포장하고 있는 내부 기본 타입 값을 변경할 수 없다는 점이다. 내부 값을 변경하려면 새롭게 포장 객체를 만들어야 한다.

 

  • 박싱과 언박싱

기본 타입 값을 객체로 만드는 걸 박싱이라고 하며, 포장 객체에서 기본 타입 값을 가져오는 걸 언박싱이라고 한다.

기본타입 포장클래스 박싱(Boxing) 언박싱(Unboxing)
byte Byte 기본타입 : Byte obj = new Byte(10);
문자열 : Byte obj = new Byte("10");
num = obj.byteValue();
char Character 기본타입 : Character obj = new Character('가');
문자열 : 불가
ch = obj.charValue();
short Short 기본타입 : Short obj = new Short(100);
문자열 : Short obj = new Short("100");
num = obj.shortValue();
int Integer 기본타입 : Integer obj = new Integer(1000);
문자열 : Integer obj = new Integer("1000");
num = obj.intValue();
long Long 기본타입 : Long obj = new Long(1000);
문자열 : Long obj = new Long("1000");
num = obj.longValue();
float Float 기본타입 : Float obj = new Float(2.5F);
문자열 : Float obj = new Float("2.5F");
num = obj.floatValue();
double Double 기본타입 : Double obj = new Double(3.5);
문자열 : Double obj = new Double("3.5");
num = obj.doubleValue();
boolean Boolean 기본타입 : Boolean obj = new Boolean(true);
문자열 : Boolean obj = new Boolean("true");
bool = obj.booleanValue();
생성자를 이용하지 않아고 각 포장 클래스마다 가지고 있는 valueOf 메소드를 통해 사용도 가능하다.

Integer obj = Integer.valueOf(100);
Integer obj = Integer.valueOf("100");

 

  • 자동 박싱과 자동 언박싱

기본타입을 직접 박싱, 언박싱하지 않아도 자동적으로 일어나는 경우가 있다.

- 자동 박싱 : 포장 클래스 타입에 기본값을 바로 대입하는 경우, 바로 Heap영역에 객체가 생성된다

// 직접하는 박싱
Integer obj = new Integer(100);

// 자동 박싱
Integer obj = 100;

 

- 자동 언박싱 : 기본 타입에 포장 객체가 대입되는 경우와 연산에서 자동으로 언박싱이 된다.

Integer obj = 100;
int value1 = obj; 	// 자동 언박싱
int value2 = obj * 3;	// 자동 언박싱

 

  • 문자열을 기본 타입 값으로 변환

포장 클래스는 기본타입 값을 박싱해서 포장 객체로도 만들지만 문자열을 기본 타입 값으로 변환할 때에도 많이 사용된다.

대부분 포장 클래스에 parse+기본타입 형식으로 되어 있는 정적 메소드를 이용한다. 

 

*parse : 파싱한다할 때 그 파스, 구문 분석이라는 뜻을 가지고 있다

 

기본 타입의 값을 이용
byte num = Byte.parseByte("10");
short num = Short.parseShort("100");
int num = Integer.parseInteger("1000");
long num = Long.parseLong("10000");
float num = Float.parseFloat("2.5F");
double num = Double.parseDouble("3.5");
boolean num = Boolean.parseBoolean("true");

 

포장 값 비교

포장 객체 내부의 값은 == 와 != 연산자로 비교하지 않는 것이 좋다. 이 연산자는 내부 값이 아닌 포장 객체의 참조를 비교하기 때문이다. 

 

자바 언어 명세를 보면 박싱된 값이 다음 표에 나와있는 범위면 == 와 != 연산자로 내부 값 바로 비교 가능하다고 적혀있지만 이외에는 언박싱한 값을 얻어야 비교가 가능하다고 한다.

타입 값의 범위
boolean true, false
char \u0000 ~ \u007f
byte, short, int -128 ~ 127

 

 


Math 클래스

수학 계산에 사용할 수 있는 메소드를 제공하는 클래스. 모두 정적 메소드이므로 바로 사용이 가능하다.

메소드 설명 예제코드 리턴값
int abs(int a)
double abs(double a)
절대값 int v1 = Math.abs(-5);
double v2 = Math.abs(-3.14);
v1 = 5
v2 = 3.14
double ceil(double a) 올림값 double v3 = Math.ceil(5.3);
double v4 = Math.ceil(-5.3);
v3 = 6.0
v4 = -5.0
double floor(double a) 버림값 double v5 = Math.floor(5.3);
double v6 = Math.floor(-5.3);
v5 = 5.0
v6 = -6.0
int max(int a, int b)
double max(double a, double b)
최대값 int v7 = Math.max(5, 9);
double v8 =Math.max(5.3, 2.5);
v7 = 9
v8 = 5.3
int min(int a, int b)
double min(double a, double b)
최소값 int v9 = Math.min(5, 9);
double v10 = Math.min(5.3, 2.5); 
v9 = 5
v10 = 2.5
double random() 랜덤값
(0.0<= 랜덤값 <  1.0)
double v11 = Math.random(); 0.0 <= v11 < 1.0
double rint(double a) 가까운 정수의 실수값 double v12 = Math.rint(5.3);
double v13 = Math.rint(5.7);
v12 = 5.0
v13 = 6.0
long round(double a) 반올림 값
(소수 첫째 자리에서)
long v14 = Math.round(5.3);
long v15 = Math.round(5.7);
v14 = 5
v15 = 6

 

 


11-2 / java.util 패키지

프로그램 개발에서 자주 사용되는 자료구조 뿐만아니라 여러 유용한 API를 포함하고 있다. 

자바 표준 API의 날짜 정보와 관련된 두가지만 간단히 소개해본다.

 


Date 클래스

날짜를 표현하는 클래스 

Date는 객체 간에 날짜 정보를 주고 받을 때 매개 변수나 리턴 타입으로 주로 사용

 

  • Date객체 생성
Date date = new Date();

 

  • 원하는 날짜 형식으로 문자열을 얻는 방법

Date객체 toString() 메소는 영문으로 된 날짜를 리턴하기 때문에 원하는 날짜 형식으로 얻고 싶다면 java.text 패키지의 SipleDateFormat 클래스와 함께 사용하는 것이 좋다.

Date now = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy년 MM월 dd일 hh시 mm분 ss초");
String strNow = sdf.format(now);

 

 


Calendar 클래스

달력을 표현한 클래스.

Calendar 클래스는 추상 클래스이므로 new 연산자를 사용해서 인스턴스를 생성할 수 없다.

Calendar 클래스의 정적 메소드인 getInstance() 메소드를 이용해 현재 운영체제에 설정되어 있는 시간대를 기준으로 Calendar 하위 객체를 얻을 수 있다. 

// Calendar 하위 객체 생성
Calendar now = Calendar.getInstance();

// get() 메소드를 이용해 날짜와 시간에 대한 정보를 읽을 수 있다
int year 	= now.get(Calendar.Year);		// 연도를 리턴
int month 	= now.get(Calendar.Month) + 1;		// 월을 리턴
int date 	= now.get(Calendar.DAY_OF_MONTH);	// 일을 리턴
String week = now.get(Calendar.DAY_OF_WEEK);		// 요일을 리턴
String amPm	= now.get(Calendar.AM_PM);		// 오전/오후를 리턴
int hour	= now.get(Calendar.HOUR);		// 시를 리턴
int minute 	= now.get(Calendar.MINUTE);		// 분을 리턴
int second	= now.get(Calendar.SECOND);		// 초를 리턴

'Programming > Java' 카테고리의 다른 글

혼공자바] 12-2 스레드 제어  (0) 2022.12.19
혼공자바] 12-1 멀티 스레드  (1) 2022.12.15
혼공자바] 10-2 예외처리  (0) 2022.12.13
제네릭(Generic)  (0) 2022.12.13
혼공자바] 10-1 예외 클래스  (0) 2022.12.12

 

10-2 / 예외처리

예외처리란 프로그램에서 예외 발생시 프로그램이 갑자기 종료하는 것을 막고 정상 실행을 유지할 수 있도록 처리하는 것.

 

컴파일 때 체크하여 강제적으로 예외처리 코드 작성하도록 요구하는 일반 예외와 달리 실행 예외는 컴파일러가 체크해주지 않기 때문에 개발자의 경험으로 처리코드를 작성한다.

 

 


예외 처리 코드

try-catch-finally 블록은 생성자 내부와 메소드 내부에서 작성되어 일반 예외와 실행 예외가 발생할 경우 예외처리를 할 수 있도로고 돕는다.

           try {
                                 
                                  예외 발생 가능 코드 ①

                 } catch (예외클래스 e) {
                                 
                                  예외처리 ②

                 } finally {

                                  항상 실행 ③

                 }

- try블록인 ①이 정상 실행되면 ②는 실행되지 않고 ③을 실행한다.

- try블록인 ①에서 예외가 발생하면 발생 즉시 ②가 실행되고 ② 종료후 ③이 실행된다. 

- finally 블록은 생략이 가능하며, finally문을 사용하게 되면 try와 catch에 return문이 있어도 실행된다.

 

public static void main(String[] args){

String data1 = "5";
String data2 = null;


// ArrayIndexOutOfBoundsException 처리
try {
    data1 = args[0];
    data2 = args[1];
} catch (ArrayIndexOutOfBoundsException e){
    System.out.println("실행 매개값 수 부족");
    return;
}
// 실행 매개값 수 부족


// NumberFormatException
try{
    int value1 = Integer.parseInt(data1);
    int value2 = Integer.parseInt(data2);
    int result = value1 + value2;
} catch (NumberFormatException e) {
    System.out.println("숫자로 변환 불가");
} finally {
    System.out.println("다시 실행하세요");
}
// 숫자로 변환 불가
// 다시 실행하세요

 

 

 


예외 종류에 따른 처리 코드

  • 다중 catch

try블록내의 다양한 예외를 처리하기 위해 사용

           try {
                                 
                                  예외 발생 가능 코드 

                 } catch (예외클래스 e) {
                                 
                                  예외처리1 

                 } catch (예외클래스 e) {

                                  예외처리2 

                 }

블록이 여러개지만 먼저 만나는 예외에 따라 단하나의 catch블록이 실행된다. 

 

  • catch 순서

다중 catch블록 작성시 주의할 점은 상위 예외 클래스가 하위 예외 클래스 보다 아래쪽에 위치해야 한다.

catch문이 위에서 부터 차례로 체크되는데 하위 예외가 상위 예외를 상속하고 있어 상위 예외 클래스가 위에 위치하게 되면 상위 예외에도 적용되버려 정확한 예외처리가 불가능 하기 때문이다.

 

 


예외 떠넘기기

try-catch블록으로 예외를 처리하는 것이 기본이나 경우 따라서 throws키워드를 이용하여 메소드를 호출한 곳으로 예외를 떠넘길 수도 있다.

 

throws 키워드 메소드 선언부 끝에 작성되어 메소드에서 처리하지 않은 예외를 호출한 곳으로 떠넘긴다.

리턴타입 메소드이름(매개변수, ...) throws 예외클래스1, 예외클래스2, ... {
}

// ----------------------- 모든 예외를 넘길시 가장 최상위 예외클래스인 Exception 사용

리턴타입 메소드 이름(매개변수, ...) throws Exception {
}

 

throws 키워드가 붙어 있는 메소드는 반드시 try블록 내에서 호출하여 catch 블록에서 떠넘겨진 예외를 처리해야한다. 

public class ThrowsExample {
    try {
    	findClass();
    } catch(ClassNotFoundException e) {
		Sytem.out.println("클래스가 존재하지 않습니다.");
	}
}

public static void findClass() throws ClassNotFoundException {
	Class clazz = Class.forName("java.lang.String2");
}

 

main()메소드에서도 throws 키워드를 사용해서 예외를 떠넘길 수 있는데, 결국 JVM이 최동적으로 예외 처리를 하게 됩니다. JVM은 예외의 내용을 콘솔에 출력하는 것으로 예외 처리를 합니다. (하지만 main()메소드에 바로 붙이는건 좋은 방법은 아니다.)

'Programming > Java' 카테고리의 다른 글

혼공자바] 12-1 멀티 스레드  (1) 2022.12.15
혼공자바] 11 기본 API 클래스  (0) 2022.12.14
제네릭(Generic)  (0) 2022.12.13
혼공자바] 10-1 예외 클래스  (0) 2022.12.12
혼공자바] 09-2 익명 객체  (0) 2022.12.10

 

제네릭(Generic)

뜻 자체는 일반적인.

제네릭(Generic)은 클래스 내부에서 지정하는 것이 아닌 외부에서 사용자에 의해 지정되는 것을 의미한다. 즉, 클래스 정의 시 특정(Specific) 타입을 미리 지정해주는 것이 아니라 인스턴스를 생성할 때 지정할 수 있도록 하는 일반(Generic) 타입이라는 것이다.

 

출처: 생활코딩

 

  • 제네릭의 타입
타입 설명
<T> Type
<E> Element
<K> Key
<V> Value
<N> Number
<R> Result

사실 <>안의 알파벳은 한글자일 필요도 없고 설명과 맞춰 사용할 필요도 없으나 위와 같은 형태로 관례적으로 사용되고 있다.  

 

  • 제네릭을 왜 사용할까?

- 제네릭을 사용하면 잘못된 타입이 들어올 수 있는 것을 컴파일 단계에서 방지할 수 있다.

- 클래스 외부에서 타입을 지정해주기 때문에 따로 타입을 체크하고 변환해줄 필요가 없다. 즉, 관리하기가 편하다.

- 비슷한 기능을 지원하는 경우 코드의 재사용성이 높아진다.

 

// 런타임시 에러
ArrayList list = new ArrayList(); 		//제네릭을 사용하지 않을경우
list.add("test");
String temp = (String) list.get(0); 		//타입변환이 필요함

// 컴파일시 에러
ArrayList<String> list2 = new ArrayList(); 	//제네릭을 사용할 경우
list2.add("test");
temp = list2.get(0); 				//타입변환이 필요없음

 

 

  • 클래스 또는 인터페이스 선언 방법
public class 클래스명<T> {...}
public interface 인터페이스명<T> {...}

T타입은 해당 실행 블록안에서만 유효하며 타입 파라미터로 명시할 수 있는 것은 참조 타입 뿐이다. (기본 타입은 불가)

 

 

  • 제네릭 클래스
class GenericExample<T> {
    private T t;

    public void setT(T t) {
        this.t = t;
    }
			
    public T getT() {
        return t;
    }
}

 

  • 제네릭 인터페이스
//인터페이스
interface InterfaceGenericExample<T> {
    T example();
}

//구현 클래스
class GenericExample implements InterfaceGenericExample<String> {

    @Override
    public String example() {
        return null;
    }
}

 

  • 제네릭 멀티 타입 파라미터 사용

2개 이상의 멀티 타입으로 사용한다. 

class MultiGenericExample<K, V> {

    private K key;
    private V value;

    @Override
    public K getKey() {
        return this.key;
    }

    @Override
    public V getValue() {
        return this.value;
    }

    @Override
    public V setValue(V value) {
        this.value = value;
        return value;
    }
}

 

이 방법을 사용하는 대표적인 예가 HashMap이다

public class HashMap <K, V> {...}

//----------------------------

HashMap<Integer,String> map = new HashMap<>();
map.put(1,"사과"); //값 추가
map.put(2,"바나나");
map.put(3,"포도");

 

  • 제네릭 메소드

매개 타입과 리턴 타입으로 타입 파라미터를 갖는 메소드

// 제네릭 메소드 선언
// 맨 앞에 사용 타입파라미터 선언, 리턴 타입: Box<T>, 매개 변수 타입: T 
public <T> Box<T> boxing(T t) {
    Box<T> box = new Box<T>();
    box.set(t);
    return box;
}

// 호출
public class BoxingMethodExample {
	public static void main(String[] args) {
		Box<Integer> box1 = <Integer> boxing(100);
		int intValue = box1.get();
		
		Box<String> box2 = boxing("홍길동");
		String strValue = box2.get();
	}
}

 

 

  • 와일드 카드

모든 타입을 대신할 수 있는 타입을  와일드카드라고 하고 ? (물음표)로 표시하며 총 3가지 형태로 이용한다.

 

1) <?> : 모든 클래스나 인터페이스 타입이 올 수 있다.

public static void registerCourse(Course<?> course) {
	System.out.println(course.getName() + " 수강생: " + Arrays.toString(course.getStudents()));
}

 

2) <? extends 상위타입> : 상한 경계 와일드 카드라 하며 와일드카드 타입의 최상위 타입을 정의하여 한계를 정한다.

아래 코드 같은 경우는 최상위 타입이 Student라 정해졌기 때문에 이와 동일하거나 그 이하의 타입만 ?에 들어올 수 있다.

public static void registerCourseStudent(Course<? extends Student> course) {
	System.out.println(course.getName() + " 수강생: " + Arrays.toString(course.getStudents()) );
}

 

3) <? super 하위 타입> : 하한 경계 와일드 카드라 하며 와일드카드 타입의 최하위 타입을 정의하여 한계를 정한다.

아래 코드 경우 최하위 타입이 Worket라고 정해졌기 때문에 이와 동일하거나 그 이상인 타입만 ?에 들어올 수 있다. 

public static void registerCourseWorker(Course<? super Worker> course) {
	System.out.println(course.getName() + " 수강생: " + Arrays.toString(course.getStudents()));
}

 

 

  • 제네릭 타입의 상속과 구현

제네릭 타입도 다른 타입과 마찬가지로 부모가 될 수 있으며 자식 클래스는 추가로 타입 파라미터를 가질 수 있다. 

// 제네릭 타입의 상속
public class ChildProduct<T, M, C> extends Product<T, M> {...}

 

 

또한 인터페이스도 될 수 있으며 이를 구현한 클래스도 제네릭 타입이 된다.

// 제네릭 인터페이스
public interface Storage<T> {
	//추상메소드
	public void add(T item, int index);
}
// 제네릭 구현 클래스
public class StorageImpl<T> implements Storage<T> {
	private T[] array;
	
	public StorageImpl(int capacity) {
		this.array = (T[]) (new Object[capacity]);
	}
	
	@Override
	public void add(T item, int index) {
		array[index] = item;
	}
}

 

 

 

 

 

 

 

자바 [JAVA] - 제네릭(Generic)의 이해

정적언어(C, C++, C#, Java)을 다뤄보신 분이라면 제네릭(Generic)에 대해 잘 알지는 못하더라도 한 번쯤은 들어봤을 것이다. 특히 자료구조 같이 구조체를 직접 만들어 사용할 때 많이 쓰이기도 하고

st-lab.tistory.com

 

[Java] 제네릭과 와일드카드 타입에 대해 쉽고 완벽하게 이해하기(공변과 불공변, 상한 타입과 하

이번에는 공변과 불공변에 대해서 먼저 알아보고, 이후에 제네릭과 와일드카드에 대해 알아보도록 하겠습니다. 많은 분들이 어려워하는 제네릭인 만큼 쉽게 풀어서 설명하려고 노력했는데, 많

mangkyu.tistory.com

 

[Java]STEP2 - 2) 제네릭 메소드(Generic Method), 와일드카드, 제네릭 타입의 상속과 구현

본 게시글은 도서 "이것이 자바다" 동영상 강의를 듣고 기록하는 TIL(Today I Learned) 입니다. 모든 저작권은 출판사 한빛미디어와 신용권님께 있음을 알립니다.👍 이번 게시물은 저번 게시물에 이

kyleyj.tistory.com

 

제네릭 - 생활코딩

제네릭이란? 제네릭(Generic)은 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법을 의미한다. 말이 어렵다. 아래 그림을 보자. 위의 그림은 아래의 코드를 간략화한 것이다. package or

opentutorials.org

 

 

10-1 예외 클래스

자바에서는 컴퓨터의 하드웨어 오동작 또는 공장으로 응용프로그램 실행 오류가 발생하는 것을 에러(error)라고 하며 이 이외의 프로그램 자체에서 발생하는 오류는 예외(exception)이라고 부른다.

 예외가 발생되면 프로그램은 곧바로 종료된다는 점에서 에러와 비슷하나 예외 처리(exception handling)을 통해 프로그램을 종료하지 않고 정상 실행 상태가 유지되도록 할수 있다.

또한 자바는 예외가 발생할 가능성이 높은 코드는 컴파일시 예외처리 유무를 확인하며 처리가 없다면 컴파일 하지않는데 이런 확인을 모든 예외에 다 하는 것은 아니기에 종류를 알아야한다.

 

 

 


예외와 예외 클래스

 

자바는 예외를 클래스로 관리한다. (모두 java.lang.Exception 클래스를 상속)

JVM은 프로그램 실행하는 도중 예외 발생시 해당 예외 클래스로 객체를 생성하며, 이를 예외 처리 코드에서 이용할 수 있게 한다. 

 

예외를 구별하는 기준은 RuntimeException 클래스를 기준으로 구별해 이 클래스의 하위 클래스가 아니면 일반 예외 클래스에 해당한다.

 

  • 일반 예외(Exception) / 컴파일러 체크 예외

컴파일하는 과정에서 해당 예외 처리 코드가 있는지 확인하고 검사한다. 만약 없다면 컴파일 오류가 뜬다.

 

  • 실행 예외(Runtime Exception) / 컴파일러 넌 체크 예외

실행 시 예측할 수 없이 갑자기 발생하기 때문에 컴파일 하는 과정에서 검사하지 않는다.

 

 

 


실행 예외

실행 예외는 컴파일러가 체크하지 않기에 오로지 개발자의 경험에 의해서 예외처리 코드를 작성해야 한다.

이를 처리해 주지 않으면 예외가 발생하게 되고 그러면 프로그램은 곧바로 종료된다.

 

  • NullPointerException

가장 빈번하게 발생하는 실행 예외.

객체 참조가 없는 상태, 즉 null 값을 갖는 참조 변수로 객체 접근 연산자인 도트(.) 사용했을 때 발생.

 

  • ArrayIndexOutOfBoundsException

배열에서 인덱스 번위를 초과할 경우 발생하는 실행 예외.

사용하기전에 미리 배열 길이를 조사하면 해당 예외 발생을 방지할 수 있다. 

 

  • NumberFormatException

문자열로 되어 있는 데이터를 숫자로 변경하는 경우 자주 발생.

숫자로 변환될 수 없는 문자가 포함되어 있다면 발생한다.

 

  • ClassCastException

타입 변환을 할 수 없는 경우 발생하는 실행 예외.

타입 변환은 가능한 케이스는 상위클래스와 하위클래스 간, 인터페이스와 구현클래스 간이며, 다만 처음부터 상위클래스나 인터페이스로 객체가 생성된 경우에도 하위클래스로 구현클래스로 변환하려고 할시 이 예외가 발생한다. 

instaceof 연산자를 사용하여 타입변환 전에 변환이 가능한지 확인한다면 이를 방지할 수 있다. 

 

 

09-2 익명 객체

일반적으로 클래스를 선언하고 이를 여러 곳에서 클래스 이름을 호출하며 객체를 만들어 사용한다. 그러나 클래스 이름이 없는 객체가 있다.  바로 그것이 익명(anonymous) 객체. 

익명 객체는 클래스를 상속하거나 인터페이스를 구현해야만 만들 수 있다.

 

일반적인 객체 생성 경우

// 상속
Class 클래스이름1 extends 부모클래스 {...}
부모클래스 변수 = new 클래스이름1();

// 구현
Class 클래스이름2 implements 인터페이스 {...}
인터페이스 변수 = new 클래스이름2();

 

익명 객체 생성 경우

// 상속
부모클래스 변수 = new 부모클래스() {...}

// 구현
인터페이스 변수 = new 인터페이스() {...}

와 같이 클래스 이름 없이 선언하며

부모클래스 경우는 이름이 없는 자식 객체를 참조하고, 인터페이스 변수는 이름이 없는 구현 객체를 참조한다.

 

 


익명 자식 객체 생성

자식 클래스를 명시적으로 선언하는 이유는 어디서건 이미 선언된 자식 클래스로 간단히 객체를 생성해 사용 할 수 있기 때문이다. 그러나 자식  클래스가 재사용 되지 않고, 오로지 특정 위치에서만 사용된다면 다음에 불일 일이 없는 이름없는 익명 자식 객체를 생성해서 사용하여 것이 좋은 방법이다. 익명 객체를 사용함으로써 클래스를 별도로 만들 필요 없이 코드에서 익명객체를 생성/정의해 단 한번 쓰고 소멸하는 것이 유지보수에 이점이 되기도 하지만 대신 이 경우 확장성은 줄어든다. 

부모클래스 [필드|변수] = new 부모클래스(매개값, ...) {
  // 필드
  // 메소드
};

먼저 부모클래스를 선언하여 생성자를 호출하고 실행 블록 내부에서는 필드나 메소드를 선언하거나 부모클래스의 메소드를 재정의하는 내용을 작성한다. 일반 클래스와 달리 생성자는 선언할 수 없다.

 

 

  • 필드 선언 초기값으로 익명 자식 객체 대입
// 익명 객체 생성해 필드에 대입
class A {
  Parent field =  new Parent(){
       int childField;
       void childMethod() { }
     
       @Override
       void parentMethod() { }
  };
}

// 익명 객체 필드 사용
A a = new A();
A.field.parentMethod();

 

  • 로컬 변수 선언 초기값으로 익명 자식 객체 대입
Class A {
  void method(){
    Parent localVar = new Parent(){
         int childField;
         void childMethod() { }
     
         @Override
         void ParentMethod() { }
    };
  }
}

// 익명 객체 로컬 변수 사용
A a = new A();
A.method();

 

  • 메소드 매개 변수가 부모 타입 경우, 메소드 호출 시 익명 자식 객체를 매개값으로 대입
class A {
  void method(Person person){   // 메소드의 매개 변수를 부모타입으로 주면
  	person.wake();
  }  			
}
  
  
class B {
  A a = new A();	

  a.method(		   	
    new Person(){	// 메소드를 호출하는 코드에서 익명 자식 객체를 생성 
         void work() { 
            System.out.println("공부해");
         }

         @Override	// 부모클래스인 Person으로 부터 상속 받은 메소드 재정의
         void wake() { 		
            System.out.println("일어났으면");
            work();
         }  
     }
  );
}

// 일어났으면
// 공부해

 

 

익명의 자식 객체에 새롭게 정의된 필드와 메소드는 익명 객체 내부에서만 사용되고, 외부에서는 접근 불가.

왜냐하면 익명 자식 객체는 부모 타입 변수에 대입되므로 부모 타입에 선언된 것만 사용할 수 있어, 자식 객체에 정의한건 사용할 수 없기 때문이다.

 

 

 


익명 구현 객체 생성

구현 클래스를 명시적으로 선언하는 이유 또한 자식 클래스 처럼 어디서건 이미 선언된 자식 클래스로 간단히 객체를 생성해 사용 할 수 있기 때문이다. 그러나 이 역시 재사용 되지 않고, 오로지 특정 위치에서만 사용된다면 다음에 불일 일이 없는 이름없는 익명 구현 객체를 생성해서 사용하여 것이 좋은 방법이다.

 

인터페이스 [필드|변수] = new 인터페이스 (){
  // 인터페이스에 선언된 추상 메소드의 실체 메소드 선언
  // 필드
  // 메소드
};

위의 선언문은 인터페이스를 구현해 { }와 같이 클래스 선언하라는 뜻이며 이렇게 구현된 클래스를 new연산자가 객체로 생성한다. 

 

+ { }안의 추상메소드는 모두 재정의 되어야하며 재정의되지 않으면 컴파일 에러가 발생한다.

+ { }안의 필드와 메소드를 선언할 수 있지만, 실체 메소드만 외부에서 사용가능하며 { } 안 필드와 메소드는 익명 구현 객체 안에서만 사용될 수 있다.

 

  • 필드 선언 초기값으로 익명 구현 객체를 생성하고 대입
Class A {
  RemoteControl field = new RemoteControl(){
    @Override
    void turnOn(){}
  };
}

 

  • 로컬 변수 선언 초기값으로 익명 구현 객체를 생성하고 대입
Class A {
    void method() {
        RemoteControl field = new RemoteControl(){
            @Override
            void turnOn(){}
        };    
    }
}

 

  • 메소드 매개 변수가 인터페이스 타입 경우, 메소드 호출 시 익명 구현 객체를 매개값으로 대입
class A {
  void method(RemoteControl rc){   // 메소드의 매개 변수를 인터페이스 타입으로 주면
  	rc.turnOff();
  }  			
}
  
class B {
  A a = new A();	

  a.method(		   	
    new RemoteControl(){	// 메소드를 호출하는 코드에서 익명 자식 객체를 생성 
         void turnOn() { 
            System.out.println("어두워 불을 키자");
         }

         @Override	// 부모클래스인 Person으로 부터 상속 받은 메소드 재정의
         void turnOff() { 		
            System.out.println("불끄자");
            turnOn();
         }  
     }
  );
}

// 불끄자
// 어두워 불을 키자

 

 


UI프로그램에서 중첩 인터페이스와 익명 객체를 사용한 예제

 

UI클래스 Button.java

public class Button{
    // --------- 필드
    OnClickListener listener;
    
    
    // --------- 메서드 START
    // 다양한 구현 객체를 받기위해 인터페이스를 매개 변수로
    void setOnClickListener (OnClickListener listener){ 
		this.listener = listener;
    }
    
    void touch(){
    	listener.onClick(); // 구현 객체의 onClick(); 호출
    }
    // --------- 메소드 END
    
    
    // --------- 중첩 인터페이스
    static interface OnClickListener{	
        void onClick();					// 추상 메소드
    }
}

UI클래스 Window.java

public class Window {
    // --------- 필드 START
    Button button1 = new Button();
    Button button2 = new Button();
    
    // 필드 초기값으로 익명 객체를 대입
    Button.OnClickListener listener = new Button.OnClickListener(){
    	@Override
        public void onClick(){
        	System.out.println("전화를 겁니다.");
        }
    };
    
    // --------- 필드 END
    
    // --------- 생성자 START
    
    Window() {
        button1.setOnClickListener(listener);
    	button2.setOnClickListener(new Button.OnClickListener() {
            @Override
            public void onClick(){
                System.out.println("메세지 보냅니다.");
            }
        };
    }
    
    // --------- 생성자 END 
}

실행클래스 Main.java

public class Main{
    public static void main(String[] args){
        Window w = new Window();
        w.button1.touch();
        w.button2.touch();
    }
}

// 전화를 겁니다.
// 메세지를 보냅니다.

 


익명 객체의 로컬 변수 사용

 

익명 객체는 메소드가 종료되면 사라지는게 일반적이지만, 종료되어도 실행 상태로 존재할 수 있다. 

중첩 클래스 편에서 로컬 클래스의 스레드 객체처럼 익명 스레드 객체도 또한 그렇다.

자바는 이런 문제를 해결하기 위해 컴파일 시 익명 객체 내부에서 사용하는 매개 변수나 로컬 번수의 값을 익명 객체 내부에 복사해두고 사용한다.

그러나 매개 변수나 로컬 변수가 수정되면 복사해둔 값과 달라지는 문제가 생겨 자바 8 이후 부터는 매개 변수나 로컬 변수를 익명 객체 내부에서 사용할 때 자동으로 매개 변수나 로컬 변수에 final 속성을 부여하게 된다. 

 

 

+ Recent posts