
배열 요소 중 3개를 뽑아 곱했을 때 나올 수 있는 최대값을 구하라.
public int largestProductOfThree(int[] arr) { // 배열 요소 중 3개를 뽑아 곱했을 때 나올 수 있는 최대값을 구하라
int[] arr2 = arr.clone();
int[] arr3 = arr.clone();
int result = -1;
for(int j=0; j<arr3.length; j++) { // 절대값 큰 순서대로 정렬
int max = 0;
int index = 0;
for (int i = 0; i < arr2.length; i++) {
if (Math.abs(arr2[i]) > max) {
index = i;
max = Math.abs(arr2[i]);
}
}
arr3[j] = arr2[index];
arr2[index]=0;
}
boolean containPositive = false;
for(int i = 0; i < arr3.length; i++){
if(arr3[i]>0) {
containPositive = true;
break;
}
}
if(!containPositive) { //arr3가 전부 음수면
result = arr3[arr3.length-1]*arr3[arr3.length-2]*arr3[arr3.length-3];
return result;
}
for(int j=0; j<=arr3.length-3; j++){
result = arr3[0]*arr3[1]*arr3[2+j];
if(result>0) break;
}
if(result<0){
for(int k=2; k<=arr3.length-2; k++){
result = arr3[0]*arr3[1+k]*arr3[2];
if(result>0) break;
}
}
if(result<0){
for(int i=3; i<=arr3.length-2; i++){
result = arr3[0+i]*arr3[1]*arr3[2];
if(result>0) break;
}
}
return result;
}
모든테스트 통과
public int largestProductOfThree2(int[] arr) { // 간단하게 압축 가능
//배열을 오름차순으로 정리합니다.
Arrays.sort(arr);
int arrLength = arr.length;
//가장 큰 양수 3가지를 곱한 값
int candidate1 = arr[arrLength - 1] * arr[arrLength - 2] * arr[arrLength - 3];
//가장 작은 음수 2가지와, 가장 큰 양수를 곱한 값
int candidate2 = arr[arrLength - 1] * arr[0] * arr[1];
return Math.max(candidate1, candidate2);
}
이렇게 훨씬 간단하게도 가능하다.
엔터프라이즈 애플리케이션 아키텍처 패턴의 하나
- 요청 데이터를 하나의 객체로 전달 받는 역할을 해줌
@RestController
@RequestMapping("/v1/members")
public class MemberController {
@PostMapping
public ResponseEntity postMember(@RequestParam("email") String email,
@RequestParam("name") String name,
@RequestParam("phone") String phone) {
Map<String, String> map = new HashMap<>();
map.put("email", email);
map.put("name", name);
map.put("phone", phone);
return new ResponseEntity<Map>(map, HttpStatus.CREATED);
}
...
...
}
기존에 사용하던 방식은 postMember()에 파라미터로 추가되는 @RequestParam의 개수가 계속해서 늘어난다.
@RestController
@RequestMapping("/v1/members")
public class MemberController {
@PostMapping
public ResponseEntity postMember(MemberDto memberDto) {
return new ResponseEntity<MemberDto>(memberDto, HttpStatus.CREATED);
}
...
...
}
DTO 클래스를 적용하면 위와 같이 간단하게 가능
마찬가지로 필요한 데이터를 전달 받기 위해 데이터를 검증하는 "유효성(Validation)검증" 도 MemberDto 클래스에 작성해 기능분리가 가능해진다.
public class MemberDto {
@Email // email양식에 맞는지 검사해주는 애너테이션
private String email;
private String name;
private String phone;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
@RequestParam 쪽 코드를 DTO 클래스의 객체로 수정@RequestBody
@ResponseBody
Spring MVC에서는 핸들러 메서드에 @ResponseBody 애너테이션이 붙거나 핸들러 메서드의 리턴 값이 ResponseEntity일 경우, 내부적으로 HttpMessageConverter가 동작하게 되어 응답 객체(여기서는 DTO 클래스의 객체)를 JSON 형식으로 바꿔준다.
역직렬화(Deserialization)
요청 받은 JSON 형식의 데이터를 DTO 같은 Java의 객체로 변환하는 것
직렬화(Serialization)
응답데이터를 전송하기 위해 DTO 같은 Java의 객체를 JSON 형식으로 변환하는 것
@RequestBody 애너테이션을 붙여야 한다.1차적으로 프론트엔드 쪽에서 유효성 검사를 진행하지만 자바스크립트로 전송되는 데이터는 브라우저의 개발자 도구를 사용해서 브레이크포인트(breakpoint)를 추가한 뒤에 얼마든지 그 값을 조작할 수 있기 때문에 서버 쪽에서 한번 더 유효성 검사를 진행해야 된다.
DTO 클래스에 유효성 검증을 적용하기 위해서는 Spring Boot에서 지원하는 Starter가 필요하다.
build.gradle 파일의 dependencies 항목에 'org.springframework.boot:spring-boot-starter-validation’을 추가해야 한다.
@NotBlank(message = "~~~")
@NotBlank만 쓰면 유효성 검증에 실패했을 때 에러 메시지가 콘솔에 출력@Email
@Pattern(regexp = "^010-\\d{3,4}-\\d{4}$", message = "~~~")
@Valid
@RequestBody앞)에 사용@RestController
@RequestMapping("/v1/members")
public class MemberController {
@PostMapping
public ResponseEntity postMember(@Valid @RequestBody MemberPostDto memberDto) {
return new ResponseEntity<>(memberDto, HttpStatus.CREATED);
}
...
...
}
@Pattern()
@Pattern(regexp = "^\\S+(\\s?\\S+)*$", message = "~~~")
정규 표현식(Reqular Experssion)
@Min(1)
@Validated
@RequestMapping뒤)에 사용DTO 클래스의 유효성 검증을 위해서 사용한 위 기능들은 Jakarta Bean Validation이라는 유효성 검증을 위한 표준 스펙에서 지원하는 내장 애너테이션들이다.
Jakarta Bean Validation
- 라이브러리처럼 사용할 수 있는 API가 아닌 스펙(사양, Specification) 자체다. ( 일종의 기능 명세를 의미 )
- Java Bean 스펙을 준수하는 Java 클래스라면 Jakarta Bean Validation의 애너테이션을 사용해서 유효성 검증을 할 수 있다.
Jakarta Bean Validation에 내장된(Built-in) 애너테이션 외에도 필요한 기능이 있다면 직접 애너테이션을 정의해 사용할 수 있다.
Custom Validator를 구현하기 위한 절차
NotSpace 인터페이스
- 공백을 허용하지 않는 Custom Annotation
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {NotSpaceValidator.class}) // (1)
public @interface NotSpace {
String message() default "공백이 아니어야 합니다"; // (2)
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@Target, @Retention는 Section1내용 참고
@Target = 애너테이션을 적용할 “대상"을 지정하는 데 사용
@Retention = 특정 애너테이션의 지속 시간을 결정하는 데 사용
@Constraint (제약조건)
@NotSpace 애너테이션이 멤버 변수에 추가되었을 때, 동작 할 Custom Validator를 연결해주는 용도로 사용했다고 생각하면 된다.NotSpaceValidator 클래스
- Custom Validator 구현
import org.springframework.util.StringUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class NotSpaceValidator implements ConstraintValidator<NotSpace, String> {
@Override
public void initialize(NotSpace constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return value == null || StringUtils.hasText(value);
}
}
<NotSpace, String> 제네릭에서 NotSpace는 CustomValidator와 매핑된 Custom Annotation(@NotSpace)을 의미하며, String은 Custom Annotation으로 검증할 대상 멤버 변수의 타입을 의미
CoffeeController 클래스
import com.codestates.coffee.CoffeePatchDto;
import com.codestates.coffee.CoffeePostDto;
import org.springframework.http.HttpStatus;
//import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
//import javax.validation.constraints.Min;
import javax.validation.constraints.Positive;
//import java.util.HashMap;
//import java.util.Map;
//import java.util.Objects;
@RestController
@RequestMapping("/v1/coffees")
@Validated // @Min() 등을 처리해주는 것
public class CoffeeController {
@PostMapping
public ResponseEntity postCoffee(@Valid @RequestBody CoffeePostDto coffeePostDto){
return new ResponseEntity<>(coffeePostDto, HttpStatus.CREATED);
}
@PatchMapping("/{coffee-id}")
public ResponseEntity patchCoffee(@PathVariable("coffee-id") @Positive long coffeeId,
@Valid @RequestBody CoffeePatchDto coffeePatchDto){
coffeePatchDto.setCoffeeId(coffeeId);
return new ResponseEntity(coffeePatchDto, HttpStatus.OK);
}
@GetMapping("/{coffee-id}") // 클라이언트가 서버에 리소스를 조회할 때 사용하는 애너테이션
public ResponseEntity getCoffee(@PathVariable("coffee-id")long coffeeId){ // 특정 회원의 정보를 클라이언트 쪽에 제공하는 핸들러 메서드
System.out.println("# coffeeId: " + coffeeId);
return new ResponseEntity<>(HttpStatus.OK);
}
@GetMapping
public ResponseEntity getMembers() { // 회원 목록을 클라이언트에게 제공하는 핸들러 메서드
System.out.println("# get coffees");
return new ResponseEntity<>(HttpStatus.OK);
}
}
@Valid와 @Validated의 차이점에 대해 의문이 생겨 추가로 블로깅했다.
CoffeePostDto 클래스
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
public class CoffeePostDto {
@NotBlank
private String korName;
@NotBlank
@Pattern(regexp = "^([a-zA-Z]+\\s?[a-zA-Z])+$") // 영어만 허용
// [A]* : A가 0개 이상이다.
// [A]+ : A가 1개 이상이다.
// \\s? : 공백이 0개 or 1개다.
// 즉, "^([a-zA-Z]+\\s?[a-zA-Z])*$"는 알파벳(몇개인지 상관x) + 공백 + 알파벳(몇개인지 상관x)이다.
// n\d* : n 뒤에 숫자가 0개 이상이라는 의미. “n”, “n1”, “n123” 에 모두 매치된다.
private String engName;
@Range(min = 100, max= 50000)
private Integer price;
public String getKorName() {
return korName;
}
public void setKorName(String korName) {
this.korName = korName;
}
public String getEngName() {
return engName;
}
public void setEngName(String engName) {
this.engName = engName;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
CoffeePostDto 클래스에서 영어만 허용하도록 @Pattern을 사용하는 과정에서 정규표현식 때문에 고생 좀 했다.
CoffeePatchDto 클래스
package com.codestates.coffee;
import com.codestates.member.NotSpace;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.Pattern;
public class CoffeePatchDto {
private long coffeeId;
@NotSpace // null가능하게 해줘야 함
// @Pattern(regexp ="^\\S+(\\s?\\S+)*$") // << 이거로 대신 써도 됨
private String korName;
@Pattern(regexp = "^([a-zA-Z]+\\s?[a-zA-Z])*$")
private String engName;
@Range(min = 100, max= 50000)
private Integer price; // int로 하면 null이 안들어간다. 기본값인 0이 들어가기 때문에 range에 걸려서 선택적으로 사용 못함
public long getCoffeeId() {
return coffeeId;
}
public void setCoffeeId(long coffeeId) {
this.coffeeId = coffeeId;
}
public String getKorName() {
return korName;
}
public void setKorName(String korName) {
this.korName = korName;
}
public String getEngName() {
return engName;
}
public void setEngName(String engName) {
this.engName = engName;
}
public Integer getPrice() {
return price;
}
public void setPrice(Integer price) {
this.price = price;
}
}
price변수 유효성 검사에서 null을 수용할 수 잇게 int 대신 Integer를 써야 했다.
| ^ | 문자열의 시작 |
| $ | 문자열의 끝 |
| . | 임의의 한 문자 |
| * | 문자가 0번 이상 발생 |
| + | 문자가 1번 이상 발생 |
| ? | 문자가 0번 혹은 1번 발생 |
| [ ] | 문자의 집합 범위를 나타냄 [0-9] : 숫자 (0부터 9) [a-z] : 알파벳 (a부터 z) 앞에 ^가 나타나면 not을 의미 |
| { } | 횟수 또는 범위를 의미 |
| ( ) | 소괄호 안의 문자를 하나의 문자로 인식 |
| | | or 조건 |
| \ | 확장 문자의 시작 |
| \b | 단어의 경계 |
| \B | 단어가 아닌 것의 경계 |
| \A | 입력의 시작부분 |
| \G | 이전 매치의 끝 |
| \Z | 입력의 끝이지만 종결자가 있는 경우 |
| \z | 입력의 끝 |
| \s | 공백문자 |
| \S | 공백문자가 아닌 나머지 문자 |
| \w | 알파벳이나 숫자 |
| \W | 알파벳이나 숫자를 제외한 문자 |
| \d | [0-9]와 동일 |
| \D | 숫자를 제외한 모든 문자 |
| Anotation | 제약조건 |
| @NotNull | Null 불가 |
| @Null | Null만 입력 가능 |
| @NotEmpty | Null, 빈 문자열 불가 |
| @NotBlank | Null, 빈 문자열, 스페이스만 있는 문자열 불가 |
| @Size(min=,max=) | 문자열, 배열등의 크기가 만족하는가? |
| @Pattern(regex=) | 정규식을 만족하는가? |
| @Max(숫자) | 지정 값 이하인가? |
| @Min(숫자) | 지정 값 이상인가 |
| @Future | 현재 보다 미래인가? |
| @Past | 현재 보다 과거인가? |
| @Positive | 양수만 가능 |
| @PositiveOrZero | 양수와 0만 가능 |
| @Negative | 음수만 가능 |
| @NegativeOrZero | 음수와 0만 가능 |
| 이메일 형식만 가능 | |
| @Digits(integer=, fraction = ) | 대상 수가 지정된 정수와 소수 자리 수 보다 작은가? |
| @DecimalMax(value=) | 지정된 값(실수) 이하인가? |
| @DecimalMin(value=) | 지정된 값(실수) 이상인가? |
| @AssertFalse | false 인가? |
| @AssertTrue | true 인가? |