장점
단점
소스코드를 작성하고 컴파일하면 각 클래스는 .class라는 바이트코드 파일로 변환됨
런타임 때 어떤 클래스의 멤버를 사용해야 한다면, 클래스 로더는 해당 클래스의 바이트코드를 찾음
검증과 초기화 과정을 거쳐 JVM의 Runtime Data Area에 바이트코드 적재
바이트코드들은 프로그램 실행 초기에 모두 적재되는 것이 아니라 런타임 때 해당 클래스가 필요한 경우 동적으로 적재
final로 선언된 상수를 사용할 때는 클래스로더가 해당 클래스를 로딩하지 않음
public class Test {
piblic static final int ZERO = 0;
} // 클래스 로더가 로딩하지 않음
public class Test {
piblic static int ZERO = 0;
} // 클래스 로더가 로딩함
public static final String USERNAME = "unknonw";
final String input = console.leadLine();
final double random = Math.random();
Abstract Class
추상 클래스는 하나 이상의 추상 메소드를 포함하는 클래스이다. 추상 메소드란 메소드 선언만 있고 구현이 없는 메소드로, 구현은 상속받은 클래스에서 진행한다. 추상 클래스는 직접적으로 인스턴스화할 수 없고 상속을 통해서만 사용한다. 추상 클래스는 추상 메소드 외에도 일반적인 메소드와 인스턴스 변수도 포함시킬 수 있다.
추상 메소드 vs 일반 메소드
추상메소드
일반 메소드
Example
추상 클래스 구성
public abstract class Animal {
public abstract void makeSound();
}
이후 Animal을 상속받은 강아지, 고양이 클래스에서 해당 메소드를 구체적으로 구현한다.
public class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("야옹야옹");
}
}
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("멍멍");
}
}
인터페이스는 추상 메소드, 상수 및 디폴트 메소드로 이루어진 참조 타입이다. 추상 클래스와 다르게 추상 메소드만 가지고 있으며 일반 메소드의 구현은 포함하지 않는다. 인터페이스는 어떤 골격을 정의하는 것이다.
의존 관계를 맺을 때 변화하기 쉬운 것 또는 자주 변화하는 것보다는 변화하기 어려운 것, 거의 변화가 없는 것에 의존해야 한다. 한마디로 구체적인 클래스보다 인터페이스나 추상 클래스와 관계를 맺어야 한다.
하나의 인터페이스는 여러 개의 인터페이스를 extends
키워드를 통해 상속받을 수 있음
이때 상속받은 인터페이스의 메소드들을 선택적으로 구현 가능
Example
Interface 작성
public interface Shape {
public double calculateArea();
}
이후 Shape을 상속받은 클래스 Circle에서 해당 메소드를 구체화
public class Circle implements Shape{
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double calculateArea() {
return (radius * radius * 3.14);
}
}
이를 확장시켜서 둘레를 구하는 calculaterPerimeter
함수도 인터페이스에 정의한 뒤, Circle과 Rectangel 클래스를 나누어서 작성
public interface Shape {
public double calculateArea();
public double calculaterPerimeter();
}
이후 Circle과 Rectangle 클래스에서 구현해 준다.
public class Circle implements Shape{
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double calculateArea() {
return (radius * radius * 3.14);
}
@Override
public double calculaterPerimeter() {
return (2 * radius * 3.14);
}
}
public class Rectangle implements Shape {
private double height;
private double width;
public Rectangle(double height, double width) {
this.height = height;
this.width = width;
}
@Override
public double calculateArea() {
return (height * width);
}
@Override
public double calculaterPerimeter() {
return (2 * (height + width));
}
}
추상 클래스와 인터페이스 공통점
추상 클래스와 인터페이스 차이점
Is-a 관계는 객체 지향 프로그래밍에서 사용되는 상속 관계를 의미한다. 이 관계는 클래스 간의 계층 구조를 형성하며, 한 클래스가 다른 클래스의 하위 클래스인 경우를 나타낸다.
Is-a 관계는 "A는 B이다"라는 문장으로 표현될 수 있다. 예를 들어, "사람은 동물이다"라는 문장에서 사람 클래스는 동물 클래스의 하위 클래스로서 Is-a 관계를 가지고 있다.
Is-a 관계는 상속을 통해 구현됩니다. 하위 클래스는 상위 클래스의 특성과 동작을 상속받아 사용할 수 있습니다. 이를 통해 코드의 재사용성을 높일 수 있고, 객체 지향의 다형성 개념을 활용할 수 있습니다.
Has-a 관계는 객체 지향 프로그래밍에서 사용되는 연관 관계를 의미한다. 이 관계는 한 클래스가 다른 클래스를 포함하고 있는 경우를 나타낸다.
Has-a 관계는 "A는 B를 가지고 있다"라는 문장으로 표현될 수 있다. 예를 들어, "자동차는 엔진을 가지고 있다"라는 문장에서 자동차 클래스는 엔진 클래스를 포함하고 있어 Has-a 관계를 가지고 있다.
Has-a 관계는 클래스 간의 구성(composition) 또는 집합(aggregation) 관계로 구현될 수 있다. 구성 관계는 한 클래스가 다른 클래스의 부분으로서 완전히 종속되는 관계를 나타내며, 부분 객체의 생명 주기와 전체 객체의 생명 주기가 밀접하게 연관된다. 집합 관계는 한 클래스가 다른 클래스의 일부를 포함하지만, 포함된 객체의 생명 주기와 전체 객체의 생명 주기가 독립적이다.
정적 바인딩 (Static Binding) | 동적 바인딩 (Dynamic Binding) |
---|---|
컴파일 시간에 결정 | 실행 시간에 결정 |
프로그램이 실행되어도 변하지 않음 | 늦은 바인딩 |
오버로딩 | 오버라이딩 |
private, final, static 붙은 메서드 | Java에서 다형성, 상속이 가능한 이유 |
Error
OutOfMemoryError
, ThreadDeath
, StackOverflowError
Exception
UncheckedException
CheckedException
예외 복구
예외가 발생해도 앱이 정상적으로 작동하도록 함
static int MAX_RETRY = 10;
int retry = MAX_RETRY;
while (retry > 0) {
retry -= 1;
try{
} catch(SomeException e) {
} finally {
}
}
// MAX_RETRY 초과 시 예외 Throw
throw new MAXTRYEXCEPTION();
예외 처리 회피
예외가 발생하면 throws를 통해 호출한 쪽으로 던지고 자신의 책임 제거
// 예시 1
public void add() throws SQLException {
}
// 예시 2
public void add() throws SQLException {
try {
} catch(SQLException e) {
throw e;
}
}
예외 전환
호출한 쪽에 더욱 명확하게 인지할 수 있도록 던진다
public void add(User user) throws DuplicateUserIdException, SQLException {
try {
} catch(SQLException e) {
if(e.getErrorCode() == MysqlErrorNumbers.ER_DUP_ENTRY) {
throw DuplicateUserIdException();
}
else throw e;
}
}
멀티 쓰레드 프로세스에서는 다른 쓰레드의 작업에 영향을 미칠 수 있음
진행 중인 작업이 다른 쓰레드에게 간섭받지 않게 하려면 동기화가 필요
동기화를 위해서는 간섭받지 않아야 하는 문장을 임계 영역을 설정
임계 영역은 Lock을 얻은 단 하나의 쓰레드만 출입 가능
메서드 전체 영역을 임계 영역으로 지정
public synchronized void withdraw(int money){
if(balance >= money){
try{
Thread.sleep(1000);
}catch(Exception e){}
balance -= money;
}
}
특정한 영역을 임계 영역으로 지정: synchronized(객체의 참조 변수) {}
public synchronized void withdraw(int money){
synchronized(this) {
if(balance >= money){
try{
Thread.sleep(1000);
}catch(Exception e){}
balance -= money;
}
}
}
thread마다 독립적인 변수를 가질 수 있게 해 주는 것
스레드 단위로 로컬 변수를 사용할 수 있기 때문에 마치 전역변수처럼 여러 메서드에서 활용할 수 있음
아래와 같이 활용
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
String[] arr = new String[]{"a", "b", "c"};
Stream<String> stream = Arrays.stream(arr);
List<String> list = Arrays.asList("a","b","c");
Stream<String> stream = list.stream();
Stream<String> builderStream = Stream.<String>builder()
.add("a").add("b").add("c")
.build();
Stream<String> generatedStream = Stream.generate(()->"a").limit(3);
// 생성할 때 스트림의 크기가 정해져있지 않기(무한하기)때문에 최대 크기를 제한해줘야 한다.
Stream<Integer> iteratedStream = Stream.iterate(0, n->n+2).limit(5); //0,2,4,6,8
IntStream intStream = IntStream.range(1, 5); // [1, 2, 3, 4]
Stream<String> parallelStream = list.parallelStream();
Calculating: ****기본형 타입을 사용하는 경우 스트림 내 요소들로 최소, 최대, 합, 평균 등을 구하는 연산을 수행
IntStream stream = list.stream()
.count() //스트림 요소 개수 반환
.sum() //스트림 요소의 합 반환
.min() //스트림의 최소값 반환
.max() //스트림의 최대값 반환
.average() //스트림의 평균값 반환
Reduction: 스트림의 요소를 하나씩 줄여가며 누적 연산을 수행
IntStream stream = IntStream.range(1,5);
.reduce(10, (total, num)-> total + num);
//reduce(초기값, (누적 변수,요소)->수행문)
// 10 + 1+2+3+4+5 = 25
Collecting: 스트림의 요소를 원하는 자료형으로 변환
//예시 리스트
List<Person> members = Arrays.asList(new Person("lee",26),
new Person("kim", 23),
new Person("park", 23));
// toList() - 리스트로 반환
members.stream()
.map(Person::getLastName)
.collect(Collectors.toList());
// [lee, kim, park]
// joining() - 작업 결과를 하나의 스트링으로 이어 붙이기
members.stream()
.map(Person::getLastName)
.collect(Collectors.joining(delimiter = "+" , prefix = "<", suffix = ">");
// <lee+kim+park>
//groupingBy() - 그룹지어서 Map으로 반환
members.stream()
.collect(Collectors.groupingBy(Person::getAge));
// {26 = [Person{lastName="lee",age=26}],
// 23 = [Person{lastName="kim",age=23},Person{lastName="park",age=23}]}
//collectingAndThen() - collecting 이후 추가 작업 수행
members.stream()
.collect(Collectors.collectingAndThen (Collectors.toSet(),
Collections::unmodifiableSet));
//Set으로 collect한 후 수정불가한 set으로 변환하는 작업 실행
Matching 특정 조건을 만족하는 요소가 있는지 체크한 결과를 반환
anyMatch (하나라도 만족하는 요소가 있는지)
allMatch( 모두 만족하는지)
noneMatch (모두 만족하지 않는지)
List<String> members = Arrays.asList("Lee", "Park", "Hwang");
boolean matchResult = members.stream()
.anyMatch(members->members.contains("w")); //w를 포함하는 요소가 있는지, True
boolean matchResult = members.stream()
.allMatch(members->members.length() >= 4); //모든 요소의 길이가 4 이상인지, False
boolean matchResult = members.stream()
.noneMatch(members->members.endsWith("t")); //t로 끝나는 요소가 하나도 없는지, True
Iterating: forEach로 스트림을 돌면서 실행되는 작업
members.stream()
.map(Person::getName)
.forEach(System.out::println);
//결과를 출력 (peek는 중간, forEach는 최종)
Finding: 스트림에서 하나의 요소를 반환
Person person = members.stream()
.findAny() //먼저 찾은 요소 하나 반환, 병렬 스트림의 경우 첫번째 요소가 보장되지 않음
.findFirst() //첫번째 요소 반환