기존에는 HTML을 사용하여 웹페이지를 개발했다. 그러나 HTML만 사용할 경우 사용자 변경이나, 시간에따라 데이터를 바꿀 수 없다. 즉 데이터가 고정되어있어 정적인 웹페이지밖에 만들 수 없다.
사용자가 글을 등록하거나 삭제할 경우 동적으로 데이터를 보여주고 싶다면 동적인 웹페이지를 작성해야 하는데 JAVA 진영에서 동적인 웹페이지를 개발하기위한 기술중 하나가 서블릿이다.
톰캣이란?
서블릿을 실행하는 역할을 한다. 사용자의 요청을받아 사용자 요청에 해당하는 Path를 추출하고 Path에 해당하는 서블릿을 찾아 그 서블릿에 요청을 하고 서블릿이 작업 처리가 끝나고 응답을 하면 클라이언트에게 데이터를 전달하는 역할을 한다. 이런 톰캣서버를 총칭해서 Web Application Server라고 하고 줄여서 WAS라고 한다.
서블릿 컨테이너란?
서블릿 객체를 만들어 보관하고 서블릿을 관리하고 서비스하는 프로그램이다. 대표적인 서블릿 컨테이너로 톰캣이 있다.
람다식은 객체지향 언어보다는 함수지향 언어에 가깝다. 객체지향 언어인 자바가 람다식을 수용한 이유는 코드가 매우 간결해지고 컬렉션의 요소를 필터링하거나 매핑해서 원하는 결과를 쉽게 집계할 수 있기 때문이라고 한다.
람다식은 "(매개변수) -> { 실행코드 }" 형태로 작성되는데, 마치 함수 정의 형태를 띠고 있지만 런타임 시에 인터페이스의 익명 구현 객체로 생성된다.
람다식의 기본 문법
람다식을 작성하는 방법은 다음과 같다.
(타입, 매개변수) -> { 실행문 }
예를 들어 int 매개 변수 a의 값을 콘솔에 출력하기 위해 다음과 같이 람다식을 작성할 수 있다.
(int a) -> { System.out.println(a); }
매개 변수의 타입은 대입되는 값에 따라 자동으로 인식될 수 있기 때문에 생략해도 무방하다.
하나의 매개변수만 있다면 괄호를 생략 할 수 있고, 하나의 실행문만 있다면 중괄호도 생략이 가능하다.
a -> System.out.println(a)
만약 매개변수가 없다면 빈 괄호를 사용하여 작성한다.
만약 어떤 작업을 수행후 리턴해야 하는 값이 있으면 return을 적어주고 만약 return만 있다면 return 생략 하여 작성할 수 있다.
(x, y) -> x + y
람다식은 인터페이스 변수에 대입된다. 인터페이스는 직접 객체화 할 수 없기때문에 구현 클래스가 필요한데, 람다식은 익명 구현 클래스를 생성하고 객체화한다. 람다식은 대입될 인터페이스의 종류에 따라 작성 방법이 달라지기 때문에 람다식이 대입될 인터페이스를 람다식의 타켓 타입이라고 한다.
람다식의 실행 블록에는 클래스의 멤버(필드와 메서드) 및 로컬 변수를 사용할 수 있다. 클래스의 멤버는 제약 사항 없이 사용 가능하지만, 로컬 변수는 제약 사항이 따른다.
자바 8부터는 빈번하게 사용되는 함수적 인터페이스는 표준 API 패키지로 제공한다. 크게 Consumer, Supplier, Function, Operator, Predicate로 구분된다.
종류
특징
Consumer
매개값은 있고 리턴값은 없음
Supplier
매개값은 없고, 리턴값은 있음
Function
매개값도 있고 리턴값도 있음 주로 매개값을 리턴값으로 매핑(타입 변환)
Operator
매개값도 있고, 리턴값도 있음 주로 매개값을 연산하고 결과를 리턴
Predicate
매개값은 있고, 리턴값은 boolean 매개값을 조사해서 true/false를 리턴
메소드 참조
메소드를 참조해서 매개 변수의 정보 및 리턴 타입을 알아내어, 람다식에 불필요한 매개 변수를 제거하는 것이 목적이다.
예를들어 두개의 값을 받아 큰 수를 리턴하는 Math 클래스의 max() 정적 메소드를 호출하는 람다식은 다음과 같다.
(left, right) -> Math.max(left, right);
이 경우에는 다음과 같이 메소드 참조를 이용하면 매우 깔끔하게 처리할 수 있다.
Math::max
정적(static)메서드를 참조할 경우에는 클래스 이름 뒤에 :: 기호를 붙이고 정적 메소드 이름을 적으면된다.
클래스 :: 메소드
인스턴스 메서드일 경우에는 먼저 객체를 생성한 다음 참조 변수 뒤에 :: 기호를 붙이고 인스턴스 메서드 이름을 적으면된다.
컬렉션의 저장요소를 하나씩 참조해서 람다식(함수적스타일)로 처리할 수 있도록 해주는 반복자이다.
List<String> list = Arrays.asList("홍길동", "신용권", "김자바");
Stream<String> stream = list.stream();
stream.forEach( name -> System.out.println(name); }
//forEach 메소드는 다음과 같이 Consumer 함수적 인터페이스 타입의 매개값을 가지므로
// 컬렉션의 요소를 소비할 코드를 람다식으로 기술할 수 있다.
void forEach(Consumer<T> action)
스트림의 연산은 기존 자료를 변경하지 않는다. 자료에 대한 스트림을 생성하면 스트림이 사용하는 메모리 공간은 별도로 생성되므로 연산이 수행되도 기존 자료에 대한 변경은 발생하지 않는다.
스트림은 중간 처리와 최종 처리를 할 수 있다.
스트림은 컬렉션의 요소에 대해 중간 처리와 최종 처리를 수행할 수 있는데, 중간 처리에서는 매핑, 필터링, 정렬을 수행하고 최종 처리에서는 반복, 카운팅, 평균, 총합 등의 집계 처리를 수행한다.
스트림에 대해 중간 연산은 여러 개의 연산이 적용될 수 있지만 최종 연산은 마지막에 한 번만 적용된다.
그러므로 중간 연산에 대한 결과를 연산 중에 알수 없다.
최종처리가 시작되기 전까지 중간 처리는 지연(lazy)된다.
스트림 생성하고 사용하기
정수 배열에 스트림 생성하여 연산을 수행하기
public class StreamTest {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
for (int i : arr) {
System.out.println("i = " + i);
}
// forEach : 배열의 인자들을 하나씩 꺼냄
Arrays.stream(arr).forEach(number -> System.out.println("number = " + number));
// 스트림은 사용하고 나면 항상 재생성해야한다.
int sum = Arrays.stream(arr).sum();
long count = Arrays.stream(arr).count();
System.out.println("sum = " + sum);
System.out.println("count = " + count);
}
}
클래스는 일반 클래스와 추상 클래스로 나뉘는데 추상 클래스는 클래스 구현부 내부에 추상 메서드가 하나 이상 포함되거나 클래스가 abstract로 정의된 경우를 말합니다. 추상 메서드가 있다면 이를 상속한 자식 클래스에서 부모의 추상 메서드를 반드시 완성해야 합니다. 상속을 위한 클래스이기 때문에 따로 인스턴스화 할 수 없습니다.
일반 메서드를 선언하고 구현하면 이를 상속한 자손 클래스에서 따로 재정의할 필요없이 일반 메서드를 가져다 쓸 수 있습니다.
abstract class 클래스이름 {
...
public abstract void 메서드이름();
}
인터페이스
인터페이스는 추상클래스처럼 다른 클래스를 작성하는데 도움을 주는 목적으로 작성하고 클래스와 다르게 다중상속(구현)이 가능합니다.
interface 인터페이스 이름{
public static final 상수이름 = 값;
public void 메서드이름();
}
추상클래스와 인터페이스의 공통점과 차이점
공통점
둘다 추상 메서드를 가지고 있어 상속 or 구현 받아 재정의 해야합니다.
차이점
추상클래스 : IS - A 관계일때 사용 -> ~는 ~이다.
인터페이스 : HAS - A 관계일때 사용 -> ~는 ~를 할 수 있다.
만약 모든 클래스가 인터페이스를 사용해서 기본 틀을 구성한다면 공통으로 필요한 기능들도 모든 클래스에서 재정의해야하는 번거로움이 있습니다. 이렇게 공통된 기능이 필요하다면 추상 클래스를 이용하여 일반 메서드를 작성하여 자식 클래스에서 사용할 수 있도록 하면 됩니다. 그러나 자바는 하나의 클래스만 상속이 가능하기때문에 만약 각각 다른 추상클래스를 상속하고 있지만 공통된 기능이 필요하다면 해당 기능을 인터페이스로 작성해서 구현해야 합니다.