public class IntQueue {
private int[] que; // 큐용 배열
private int capacity; // 큐의 용량
private int front; // 맨 앞의 요소 커서
private int rear; // 맨 뒤의 요소 커서
private int num; // 현재 데이터 개수
// 실행 시 예외 : 큐가 비어 있음
public class EmptyIntQueueException extends RuntimeException {
public EmptyIntQueueException() {}
}
// 실행 시 예외 : 큐가 꽉 차있음
public class OverflowIntQueueException extends RuntimeException {
public OverflowIntQueueException() {}
}
// 생성자
public IntQueue(int maxlen) {
num = front = rear = 0;
capacity = maxlen;
try {
que = new int[capacity]; // 큐 본체용 배열을 생성
} catch (OutOfMemoryError e) { // 생성할 수 없을 경우
capacity = 0;
}
}
// 큐에 데이터 인큐
public int enque(int x) throws OverflowIntQueueException {
if(num >= capacity) {
throw new OverflowIntQueueException(); // 큐가 가득참
}
que[rear++] = x;
num++;
if(rear == capacity)
rear = 0;
return x;
}
// 큐에 데이터를 디큐
public int deque() throws EmptyIntQueueException {
if(num <= 0)
throw new EmptyIntQueueException();
int x = que[front++];
num--;
if(front == capacity)
front = 0;
return x;
}
// 큐에서 데이터를 피크(front 데이터를 들여다봄)
public int peek() throws EmptyIntQueueException {
if(num <= 0)
throw new EmptyIntQueueException(); // 큐가 비어있음
return que[front];
}
// 큐를 비움
public void clear() {
num = front = rear = 0;
}
// 큐에서 x를 검색하여 인덱스(찾지 못하면 -1)를 반환
public int indexOf(int x) {
for(int i = 0; i < num; i++) {
int idx = (i + front) % capacity;
if(que[idx] == x) // 검색 성공
return idx;
}
return -1; // 검색 실패
}
// 큐의 용량을 반환
public int getCapacity() {
return capacity;
}
// 큐에 쌓여 있는 데이터 개수를 반환
public int size() {
return num;
}
// 큐가 비어있나요?
public boolean isEmpty() {
return num <=0;
}
// 큐가 가득 찼나요?
public boolean isFull() {
return num >= capacity;
}
// 큐에서 임의의 데이터를 검색
public int search(int x) {
for(int i = 0; i < num; i++) {
if(que[(i + front) % capacity] == x)
return i + 1;
}
return 0;
}
// 큐 안의 모든 데이터를 front -> rear 순으로 출력
public void dump() {
if(num <= 0)
System.out.println("큐가 비어있습니다.");
else {
for(int i = 0; i < num; i++) {
System.out.println(que[(i + front) % capacity] + " ");
}
System.out.println();
}
}
}
public class IntQueue {
private int[] que; // 큐용 배열
private int capacity; // 큐의 용량
private int front; // 맨 앞의 요소 커서
private int rear; // 맨 뒤의 요소 커서
private int num; // 현재 데이터 개수
// 실행 시 예외 : 큐가 비어 있음
public class EmptyIntQueueException extends RuntimeException {
public EmptyIntQueueException() {}
}
// 실행 시 예외 : 큐가 꽉 차있음
public class OverflowIntQueueException extends RuntimeException {
public OverflowIntQueueException() {}
}
// 생성자
public IntQueue(int maxlen) {
num = front = rear = 0;
capacity = maxlen;
try {
que = new int[capacity]; // 큐 본체용 배열을 생성
} catch (OutOfMemoryError e) { // 생성할 수 없을 경우
capacity = 0;
}
}
// 큐에 데이터 인큐
public int enque(int x) throws OverflowIntQueueException {
if(num >= capacity) {
throw new OverflowIntQueueException(); // 큐가 가득참
}
que[rear++] = x;
num++;
if(rear == capacity)
rear = 0;
return x;
}
// 큐에 데이터를 디큐
public int deque() throws EmptyIntQueueException {
if(num <= 0)
throw new EmptyIntQueueException();
int x = que[front++];
num--;
if(front == capacity)
front = 0;
return x;
}
// 큐에서 데이터를 피크(front 데이터를 들여다봄)
public int peek() throws EmptyIntQueueException {
if(num <= 0)
throw new EmptyIntQueueException(); // 큐가 비어있음
return que[front];
}
// 큐를 비움
public void clear() {
num = front = rear = 0;
}
// 큐에서 x를 검색하여 인덱스(찾지 못하면 -1)를 반환
public int indexOf(int x) {
for(int i = 0; i < num; i++) {
int idx = (i + front) % capacity;
if(que[idx] == x) // 검색 성공
return idx;
}
return -1; // 검색 실패
}
// 큐의 용량을 반환
public int getCapacity() {
return capacity;
}
// 큐에 쌓여 있는 데이터 개수를 반환
public int size() {
return num;
}
// 큐가 비어있나요?
public boolean isEmpty() {
return num <=0;
}
// 큐가 가득 찼나요?
public boolean isFull() {
return num >= capacity;
}
// 큐에서 임의의 데이터를 검색
public int search(int x) {
for(int i = 0; i < num; i++) {
if(que[(i + front) % capacity] == x)
return i + 1;
}
return 0;
}
// 큐 안의 모든 데이터를 front -> rear 순으로 출력
public void dump() {
if(num <= 0)
System.out.println("큐가 비어있습니다.");
else {
for(int i = 0; i < num; i++) {
System.out.println(que[(i + front) % capacity] + " ");
}
System.out.println();
}
}
}
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int x = sc.nextInt();
int y = sc.nextInt();
int w = sc.nextInt();
int h = sc.nextInt();
int horizon, length;
if((double)x < (double)w / 2) {
horizon = x;
}
else horizon = w - x;
if((double)y < (double)h / 2) {
length = y;
}
else length = h - y;
System.out.print(Math.min(horizon, length));
}
}
🔎 백준의 유용한 기능
백준과 깃헙 연동하기(백준허브 확장자)
크롬 확장자 중에 백준허브를 추가(Add to Chrome)
깃헙 아이디와 Authenticate
Repository 추가 또는 기존 Repository 주소 입력해서 연동
백준에서 문제를 풀고 제출하면 자동으로 내 깃헙 repository에 commit이 됩니다!⭐️
백준 티어 보기
백준에는 티어가 존재하는줄 몰랐는데
브론즈, 실버, 골드,... 등 티어가 존재한다.
solved를 이용하면 백준에서 문제의 티어를 볼 수 있다.
이외에도 백준 티어를 깃헙 프로필에 보이게 할 수도 있다. ㅋㅋ
근데 아직 백준 티어가 브론즈 바닥이라서 나중에 당당하게 티어를 보여줄 만큼 올라갔을 때 깃허브 프로필에 올려야 겠다..
자바가 처음 나온 90년대에는 객체 지향 언어가 각광받았지만 지금은 함수형 프로그래밍이 다시 각광받고 있다.
이런 패러다임에 맞춰서 자바 8버전부터 함수형 프로그래밍을 할 수 있도록 '람다식(Lambda Expressions)'을 지원한다.
자바에서 제공하는 함수형 프로그래밍 방식을 '람다식'이라고 한다.
람다식을 사용하면 간결하게 함수를 만들고 사용할 수 있다.
람다식은 먼저 인터페이스를 작성하고 사용하려는 인터페이스를 익명으로 구현하는 방법이다.
➤ 람다식의 작성
(매개변수) -> {실행코드}
람다식 문법 ⬇️
public class LambdaTest {
public static void main (String[] args) {
x -> {System.out.println(x);} // 매개변수가 한 개인 경우 괄호 생략 가능 ⭕️
x, y -> {System.out.println(x * y);} // 매개변수가 두 개 이상인 경우 괄호 생략 ❌
(x, y) -> System.out.println(x * y); // 실행문이 한 개인 경우 괄호 생략 가능 ⭕️
(x, y) -> {return x + y;} // 하지만 실행문이 한 개여도 return문인 경우 괄호 생략 ❌
(x, y) -> x + y; // 실행문이 한 개이고 return문인 경우 return과 중괄호 모두 생략 ⭕️
}
}
<작성규칙>
매개변수가 하나일 경우는 괄호를 생략 가능하지만 2개 이상인 경우엔 생략할 수 없다.
실행문이 하나인 경우에도 중괄호를 생략할 수 있지만 하나인 실행문이 return문인 경우에는 생략할 수 없다.
실행문에 return문만 있는 경우는 return과 중괄호 모두 생략하는 것이 올바르다.
➤ 함수형 인터페이스
자바에서 메소드를 사용하려면 클래스를 만들고 객체롤 메소드를 호출해야 한다.
인터페이스는 클래스마다 공통적으로 사용하는 기능을 메소드마다 다르게 정의할 수 있게 해준다.
하지만 함수를 만들어서 사용할 때는 함수형 인터페이스를 만들면 같은 식에 다른 값을 넣어서 재사용할 수 있어 편리하다.
🌱 함수형 인터페이스를 작성하기
package lambdaPractice;
@FunctionalInterface
public interface BigNum {
int getMax(int num1, int num2); // 추상 메서드 선언
// int add(int num1, int num2); // 함수형 인터페이스는 한 개의 추상 메서드만 선언할 수 있음!⚠️
}
위에는 두개의 숫자를 받아서 더 큰 숫자를 반환하는 함수를 포함한 함수형 인터페이스이다.
위에를 보면 작성하는 규칙이 있다.
1. '@FunctionalInterface' 애너테이션을 사용해서 컴파일러에 함수형 인터페이스임을 알릴 수 있다.
2. 한 개의 추상 메소드만 하나만 정의할 수 있다.
🌱 함수형 인터페이스 구현하기!
package lambdaPractice;
public class BigNumTest {
public static void main(String[] args) {
BigNum big = (x, y) -> (x >= y) ? x : y;
System.out.println(big.getMax(10, 12));
System.out.println(big.getMax(5,1));
}
}
작성한 함수형 인터페이스형인 BigNum형 big 함수를 선언해주었다.
매개변수로 x와 y를 받아서 'x >= y' 가 true이면 x, false이면 y를 반환하도록 만들었다.
➤ 왜 함수형 프로그래밍을 이용할까?
자바에서 람다식을 이용한 함수형 프로그램 코드가 객체를 만들어서 사용하는 객체지향형 코드보다 훨씬 간결하고 직관적이다.
함수식을 통해서 용도를 한눈에 이해할 수 있게 된다.
그리고 재사용하기도 용이하고 확장성 있는 프로그래밍이 가능하다.
함수형 인터페이스를 한번 구현하면 구현한 객체명으로 같은 식을 계속 사용할 수 있다.
만약 중간에 식을 조금 수정해야 한다면 다시 만들 필요없이 람다식을 이용해서 만들어 놓은 객체에 함수식을 고쳐주면 된다.
함수가 입력받은 자료 이외에 외부 자료에 영향을 미치지 않기 때문에 여러 자료를 동시에 처리하는 병렬 처리에 적합하다.
➤ 메서드 레퍼런스
함수형 인터페이스와 비슷한 기능을 제공하는 것이 메서드 레퍼런스이다.
메서드 레퍼런스는 작성해놓은 클래스의 객체를 생성하지 않고도 클래스 내의 메서드를 이용할 수 있게 해준다.
람다식에서 제공되는 메개변수의 메서드를 호출하여 매개변수를 매개값으로 사용하는 경우도 있다.
(a,b) -> {a.instanceMethod(b);};
클래스 :: instanceMethod
정적 메서드 레퍼런스와 작성방법은 동일하지만 a의 인스턴스 메서드가 참조된다는 점에서 다른 코드이다.
➤ 생성자 참조
생성자를 참조하는 람다식 표현도 작성 가능하다.
(a, b) -> {return new Object(a, b);};
위의 코드를 생성자 참조 표현으로 간단하게 표현할 수 있다.
클래스 :: new
❑ 스트림(Stream)
➤ 스트림이란?
스트림은 배열, 컬렉션의 요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 반복자 이다.
정렬이나 특정 값을 제외하고 출력하는 등 여러 자료 처리에 대한 기능을 구현해 놓은 클래스이다.
스트림을 사용하면 List, Set, Map, 배열 등 다양한 데이터 소스를 스트림형으로 변환하고 다양한 메서드를 제공한다.
처리해야하는 자료에 상관없이 같은 방식으로 메서드를 호출할 수 있기 때문에 자료를 추상화 했다고 표현한다.
➤ 스트림 생성하기
배열을 스트림으로 생성하기 : Arrays.stream(arr)
Collection 인터페이스에는 Stream<E> stream() 메서드가 정의되어 있기 때문에
Collection을 구현한 클래스에서는 모두 클래스명.stream()으로 스트림 클래스를 만들 수 있다.
int[] arr = new int[];
Arrays.stream(arr); // 배열을 stream형으로 만들기
List<String> list = new ArrayList<String>();
Stream<String> stream = list.stream(); // ArrayList를 stream형으로 만들기
stream.allMatch(a -> a%2 == 0); // 모든 요소들이 조건을 만족하는지 조사
stream.anyMatch(a -> a%2 == 0); // 최소 한 개 이상의 요소가 조건을 만족하는지 조사
stream.noneMatch(a -> a%2 == 0); // 모든 요소들이 조건을 만족하지 않는지 조사
public class OutClass { // 외부 클래스
private int num = 1; // private으로 선언한 인스턴스 변수
private static int sNum = 2; // private으로 선언한 정적 변수
static class InStaticClass { // 정적 내부 클래스
int inNum = 4; // 정적 내부 클래스의 인스턴스 변수
static int sInNum = 5; // 정적 내부 클래스의 정적 변수
void inTest() {
// num += 10; // 오류⚠️ (정적 내부 클래스에서는 외부 클래스의 인스턴스 변수 사용 불가)
System.out.println("InStaticClass inNum = " + inNum + "(내부 클래스의 인스턴스 변수 사용)");
System.out.println("InStaticClass sInNum = " + sInNum + "(내부 클래스의 정적 변수 사용)");
System.out.println("OutClass sNum = " + sNum + "(외부 클래스의 정적 변수 사용)");
}
static void sTest() {
// num += 10; // 오류⚠️ (정적 내부 클래스에서는 외부 클래스의 인스턴스 변수 사용 불가)
// inNum += 10; // 오류⚠️ (정적 메소드에서는 인스턴스 변수 사용 불가)
sInNum += 100; // 내부 클래스의 정적 변수 사용 가능
System.out.println("OuterClass sNum = " + sNum + "(외부 클래스의 정적 변수 사용)");
System.out.println("InStaticClass sInNum = " + sInNum + "(내부 클래스의 정적 변수 사용)");
}
}
}
class main {
public static void main(String[] args) {
OutClass.InStaticClass sInClass = new OutClass.InStaticClass();
System.out.println("정적 내부 클래스 일반 메소드 호출");
sInClass.inTest();
System.out.println();
System.out.println("정적 내부 클래스의 정적 메소드 호출");
OutClass.InStaticClass.sTest();
}
}
<출력 결과>
정적 내부 클래스 일반 메소드 호출
InStaticClass inNum = 4(내부 클래스의 인스턴스 변수 사용)
InStaticClass sInNum = 5(내부 클래스의 정적 변수 사용)
OutClass sNum = 2(외부 클래스의 정적 변수 사용)
정적 내부 클래스의 정적 메소드 호출
OuterClass sNum = 2(외부 클래스의 정적 변수 사용)
InStaticClass sInNum = 105(내부 클래스의 정적 변수 사용)
정적 내부 클래스 내의 일반 메소드와 정적 메소드는 변수 유형에 따라 사용가능 여부가 다르다.
표로 정리하면 아래와 같다.
정적 내부 클래스 메소드
변수 유형
사용 가능 여부
일반 메서드 void Method()
외부 클래스의 인스턴스 변수
x
외부 클래스의 정적 변수
o
정적 내부 클래스의 인스턴스 변수
o
정적 내부 클래스의 정적 변수
o
정적 메서드 static void sMethod()
외부 클래스의 인스턴스 변수
x
외부 클래스의 정적 변수
o
정적 내부 클래스의 인스턴스 변수
x
정적 내부 클래스의 정적 변수
o
정적 내부 클래스의 정적 메소드에서는 외부 클래스나 내부 클래스에서 정적 변수만 사용 할 수 있다.
➤ 다른 클래스에서 정적 내부 클래스 사용하기
정적 내부 클래스는 외부 클래스를 생성하지 않고도 내부 클래스 자료형으로 바로 선언하여 생성할 수 있다.
OutClass.InStaticClass sInClass = new OutClass.InStaticClass();
또한 정적 내부 클래스에 선언한 메소드나 변수는 private이 아닌 경우에 다른 클래스에서도 사용할 수 있다.
class LocalTest { // 외부 클래스
int num = 10;
static int sNum = 100;
Runnable getMyPet(int pNum){ // 메소드
int mNum = 20;
// static int insNum = 200; // 메소드내 정적 변수 선언 불가
class MyPet implements Runnable{ // 🔥지역내부 클래스
int inNum = 30; // 컴파일러가 자동으로 final 추가
// num += 10; // 외부 클래스의 멤버변수 변경 불가
// mNum += 10; // 지역변수는 상수로 바뀌므로 변경하면 오류 발생
// pNum += 10; // 메소드의 지역변수는 상수로 바뀌므로 변경하면 오류 발생
public void run() {
System.out.println("외부 클래스의 멤버 변수 num : " + num);
System.out.println("외부 클래스의 정적 변수 sNum : " + sNum);
System.out.println("외부클래스의 메소드내 지역변수 mNum : " + mNum);
System.out.println("지역 내부 클래스의 멤버 변수 inNum : " + inNum);
System.out.println("지역 내부 클래스를 감싸고 있는 메소드의 매개변수 pNum : " + pNum);
}
}
return new MyPet();
}
}
만든 지역 내부 클래스는 이렇게 사용된다.
public class LocalInnerPractice {
public static void main(String[] args) {
LocalTest myPet = new LocalTest();
Runnable runnable = myPet.getMyPet(10);
runnable.run();
}
}
<출력 결과>
외부 클래스의 멤버 변수 num : 10
외부 클래스의 정적 변수 sNum : 100
외부클래스의 메소드내 지역변수 mNum : 20
지역 내부 클래스의 멤버 변수 inNum : 30
지역 내부 클래스를 감싸고 있는 메소드의 매개변수 pNum : 10
지역내부 클래스를 생성하는건 생소하기도 하고 복잡하다.
일단은 외부 클래스를 반드시 만들어 줘야 하고
메소드를 실행해서 return으로 지역 내부 클래스 객체를 생성해서 반환하도록 설계해주면 된다.
※ 참고) 메소드 내의 지역 변수는 static 변수로 선언할 수 없다.
자바에서는 static local variable을 지원하지 않는다.
static local variables are not allowed!
static 변수는 field 에만 정의되는 변수이다.
메소드는 실행될 때만 stack 메모리에 올라갔다가 실행이 끝나면 stack메모리에서 사라진다.
그런데 정적 변수는 컴파일과 동시에 클래스 정보와 함께 메모리를 할당받고 프로그램 실행중에는 계속 메모리 공간에 있어서 접근할 수 있다.
class Person {
Runnable getRunnable(int i) {
int num = 100;
return new Runnable() { // 익명 내부 클래스, Runnable 인터페이스 생성
@Override
public void run() {
// num += 10; // 오류 발생
// i += 20; // 오류 발생
System.out.println("매개변수 i : " + i);
System.out.println("지역 변수 num : " + num);
}
}; // 끝나는 중괄호 뒤에 ; 붙임
}
Runnable runnable = new Runnable() { // 익명 내부 클래스를 변수에 대입
@Override
public void run() {
System.out.println("Runnable이 구현된 익명 클래스 변수");
}
};
}
생성한 익명 내부 클래스를 사용해보자
public class AnnoymousInnerTest {
public static void main(String[] args) {
Person p = new Person();
Runnable runnable = p.getRunnable(10);
runnable.run();
p.runnable.run();
}
}
<출력 결과>
매개변수 i : 10
지역 변수 num1 : 100
Runnable이 구현된 익명 클래스 변수
익명 클래스는 끝나는 부분에 세미콜론(;)을 작성해서 컴파일러에 익명 클래스가 끝났다고 알려줘야 한다.
익명 내부 클래스는 단 하나의 인터페이스나 단 하나의 추상 클래스를 바로 생성할 수 있다.
인터페이스는 인스턴스를 생성할 수 없는데..???
인터페이스의 인스턴스를 만들려면 구현을 다하지 않은 메소드를 작성해줘야 한다.
익명 내부 클래스를 호출할 때는 지역 내부 클래스 처럼
인스턴스를 생성해서 호출해도 되고
예시 코드의 'p.runnable.run()' 처럼 클래스내의 인터페이스 이름으로 바로 호출할 수도 있다.
익명 내부 클래스는 아직은 이해하기 어려운 개념이라는 생각이 든다. 🙁
익명 내부 클래스의 용도는 주로 안드로이드에서 버튼이나 텍스트 상자 같은 위젯(widget)의 이벤트를 처리하는 핸들러를 구현하는데 사용? 한다고 한다.