Til (60)

 

 

 

Stack

 

 

Queue

 

 

❑ 자료구조란?

 

여러 자료(데이터)를 다루는 구조

자료(데이터)란?

휴대폰 번호, 방문기록, 고객명단, 대기열 등 모든 것이 자료이다.

자료를 정리하는 방식은 자료의 형태마다 달라야한다.

만약에 규칙없이 저장하거나 하나의 구조만으로 저장하면 자료마다 활용하는 것이 불편하다.

자료를 효율적으로 정리하면 저장하고 활용하는데 있어서 훨씬 유리하게 된다.

 

자료를 효율적으로 정리하는 방법은 여러가지가 있다.

 

자료를 다루는 구조 그 자체를 뜻하고 구현하는 방식에는 제약이 없음

 

주로쓰는 자료구조 : stack, queue, graph, tree

 

❑ Stack?

 

FILO(First In Last Out) / LIFO(Last In First Out)

먼저 들어간놈이 나중에 나오고 나중에 들어간 놈이 먼저 나오는 자료 구조

출입구가 하나고 끝이 막힌 구조를 생각하면 된다.

 

 

자바에서 Stack을 배열로 구현해보기

더보기

Doit! 자료구조와 함께 배우는 알고리즘 입문(자바편)에서 참고해서 작성하였다.

 

package stack;

public class IntStack {
    private int[] stk;  // 스택용 배열
    private int capacity;   // 스택 용량
    private  int ptr;   // 스택 포인터

    // 실행시 예외: 스택이 비어있음
    public class EmptyIntStackException extends RuntimeException {
        public EmptyIntStackException(){};
    }

    // 실행시 예외: 스택이 가득 참
    public class OverflowIntStackException extends RuntimeException {
        public OverflowIntStackException(){};
    }

    // 생성자
    public IntStack(int maxlen) {
        ptr = 0;
        capacity = maxlen;
        try {
            stk = new int[capacity];
        } catch (OutOfMemoryError e) {
            capacity = 0;
        }
    }

    // 스택에 x push
    public int push(int x) throws OverflowIntStackException {
        if(ptr >= capacity) {
            throw new OverflowIntStackException();
        }
        return stk[ptr++] = x;
    }

    // 스택에서 데이터를 pop
    public int pop() throws EmptyIntStackException {
        if(ptr <= 0) {
            throw new EmptyIntStackException();
        }
        return stk[--ptr];
    }

    // 스택에서 데이터를 peek
    public int peek() throws EmptyIntStackException {
        if(ptr <= 0) {
            throw new EmptyIntStackException();
        }
        return stk[ptr - 1];
    }

    // 스택을 비움
    public void clear() {
        ptr = 0;
    }

    // 스택에서 x를 찾아 인덱스(없으면 -1)을 반환
    public int indexOf(int x) {
        for(int i = ptr - 1; i >= 0; i--) {
            if (stk[i] == x)
                return i;
        }
        return -1;
    }

    // 스택의 용량 반환
    public int getCapacity() {
        return capacity;
    }

    // 스택에 쌓여 있는 데이터 개수를 반환
    public int size() {
        return ptr;
    }

    // 스택이 비어있는가?
    public boolean isEmpty() {
        return ptr <= 0;
//        return ptr == 0; 으로도 가능
    }

    // 스택이 가득 찼는가?
    public boolean isFull() {
        return ptr >= capacity;
    }

    // 스택 안의 모든 데이터를 bottom-> top 순서로 출력
    public void dump() {
        if(ptr <= 0) {
            System.out.println("Stack is Empty!");
        }
        else {
            for(int i = 0; i < ptr; i++)
                System.out.print(stk[i]+ " ");
            System.out.println();
        }
    }
}

 

 

테스트할 코드도 구현해보았다.

package stack;

import java.util.Scanner;

public class IntStackTester {
    public static void main(String[] args) {
        Scanner stdIn = new Scanner(System.in);
        IntStack s = new IntStack(64);

        while(true) {
            System.out.println();
            System.out.printf("present data count: %d / %d\n", s.size(), s.getCapacity());
            System.out.print("(1)push (2)pop (3)peek (4)dump (5)clear (6)getCapacity (7)indexOf (8)isFull (0)exit  : ");

            int menu = stdIn.nextInt();

            if(menu == 0) break;    //exit

            int x;
            switch(menu) {
                case 1 :    // push
                    System.out.print("데이터: ");
                    x = stdIn.nextInt();
                    try {
                        s.push(x);
                    }catch (IntStack.OverflowIntStackException e) {
                        System.out.println("Stack is full!!");
                    }
                    break;
                case 2:     // pop
                    try {
                        x = s.pop();
                        System.out.println("pop data is " + x + ".");
                    } catch (IntStack.EmptyIntStackException e) {
                        System.out.println("Stack is Empty!!");
                    }
                    break;
                case 3:     // peek
                    try {
                        x = s.peek();
                        System.out.println("peek data is " + x + ".");
                    } catch (IntStack.EmptyIntStackException e) {
                        System.out.println("Stack is Empty!!");
                    }
                    break;
                case 4:     // dump
                    s.dump();
                    break;
                case 5:     // clear
                    s.clear();
                    break;
                case 6:     // getCapacity
                    System.out.println("Capacity : " + s.getCapacity());
                    break;
                case 7:     //indexOf
                    System.out.print("input you want to find : ");
                    x = stdIn.nextInt();
                    System.out.println(x + " is in " + s.indexOf(x));
                    break;
                case 8:
                    System.out.println("Stack is Full? " + s.isFull());
                    break;
            }

        }
    }
}

 

 

자바에서는 Stack 자료구조가 클래스로 이미 지원되고 있다.

여러가지 메소드들을 이용해서 효과적으로 관리할수 있다.

  • push : 데이터를 넣는다.
  • pop : 데이터를 꺼낸다.
  • peek : 스택의 꼭대기에 있는 요소를 반환한다.
  • empty : 스택이 비었는지 확인한다.
  • search : 찾고자 하는 요소가 꼭대기부터 맨 처음으로 등장하는 index를 반환한다.

 

 

 

❑ Queue?

 

FIFO / LILO

먼저 들어간 놈이 먼저 나온다

줄 서서 기다리는 것을 이미지하면 된다.

은행이나 마트에서 줄서서 기다리면 먼저 온 사람 먼저 처리해준다

인큐 : 큐안에 들어옴

디큐 : 큐안에서 나감

 

자바에서 배열로 구현한 Queue..작성중..

 

 

➤ 현실적인 예시

 

문서를 출력하면 문서가 들어간 순서대로 인쇄 된다.

컴퓨터(출력버튼)-(임시기억장치)Queue에 하나씩 들어옴 - Queue에 들어온 순서대로 문서 인쇄

인쇄할 때 CPU가 인쇄 데이터를 보내는 속도가 프린터가 인쇄하는 속도보다 빠르기 때문에 인쇄 데이터를 잠시 보관할 임시저장공간이 필요하다.

만약에 CPU가 프린터가 하나씩 인쇄 되는것을 기다렸다가 데이터를 전송하면 CPU가 그 시간동안 다른일을 처리하지 못하기 때문에 비효율적이다.

컴퓨터 장치간에 데이터를 주고받을 때 장치 사이 속도가 존재한다.

시간차이나 속도차이를 극복하기 위해 임시 기억 장치의 자료구조로 Queue를 사용

-> 버퍼(Buffer) 라고 함

 

 

 

 

 

읽어주셔서 감사합니다.😍

오개념에 대한 지적은 언제나 환영입니다.

 

 

 

 

 

❑ 람다식(Lambda)

 

 

➤ 람다식이란?

 

자바는 객체 지향 프로그래밍 언어이다.

객체 지향은 객체를 만들어야지만 함수(메서드)를 사용할 수 있지만

함수형 프로그래밍은 함수의 구현과 호출만으로 프로그램을 만들 수 있다.

자바가 처음 나온 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를 반환하도록 만들었다.

 

 

 

➤ 왜 함수형 프로그래밍을 이용할까?

 

자바에서 람다식을 이용한 함수형 프로그램 코드가 객체를 만들어서 사용하는 객체지향형 코드보다 훨씬 간결하고 직관적이다.

함수식을 통해서 용도를 한눈에 이해할 수 있게 된다.

 

그리고 재사용하기도 용이하고 확장성 있는 프로그래밍이 가능하다.

함수형 인터페이스를 한번 구현하면 구현한 객체명으로 같은 식을 계속 사용할 수 있다.

만약 중간에 식을 조금 수정해야 한다면 다시 만들 필요없이 람다식을 이용해서 만들어 놓은 객체에 함수식을 고쳐주면 된다.

 

함수가 입력받은 자료 이외에 외부 자료에 영향을 미치지 않기 때문에 여러 자료를 동시에 처리하는 병렬 처리에 적합하다.

 

 

 

➤ 메서드 레퍼런스

 

함수형 인터페이스와 비슷한 기능을 제공하는 것이 메서드 레퍼런스이다.

메서드 레퍼런스는 작성해놓은 클래스의 객체를 생성하지 않고도 클래스 내의 메서드를 이용할 수 있게 해준다.

매개변수를 작성할 필요없이 메서드로 인수를 전달한다.

 

IntBinaryOperator operator1 = (a, b) -> Math.max(a, b);
IntBinaryOperator operator2 = Math :: max;
System.out.println(operator1.applyAsInt(1, 5) + ", " + operator2.applyAsInt(1, 5));

메서드 레퍼런스는 :: 을 이용해서 클래스(또는 인터페이스)의 메서드에 접근할 수 있다.

클래스명::메소드명

IntBinaryOperator은 두 개의 int 매개값을 받아 int값을 리턴하는 함수형 인터페이스이다.

추상형 메서드 이름은 applyAsInt 이다.

 

정적 메서드 레퍼런스

클래스 :: 메서드

 

인스턴스 메서드 레퍼런스

참조 변수 :: 메서드

 

참고 사이트

https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html

 

Method References (The Java™ Tutorials > Learning the Java Language > Classes and Objects)

The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available. See Java Language Changes for a summary of updated

docs.oracle.com

 

 

➤ 매개변수의 메서드 레퍼런스

 

람다식에서 제공되는 메개변수의 메서드를 호출하여 매개변수를 매개값으로 사용하는 경우도 있다.

(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형으로 만들기

 

 

 

➤ 스트림의 자주 사용하는 메서드

 

forEach() : 내부적으로 반복문이 수행됨

stream.forEach(s -> System.out.println(s));

 

filter() : 조건을 넣고 조건이 참인 경우만 추출

stream.filter(s -> s.length() >= 5);

 

map() : 요소들을 순회하여 다른 형식으로 변환

stream.map(c -> c * 2);

 

forEach() : 요소를 하나씩 꺼내는 기능, 요소를 하나씩 소모한다.

stream.forEach(System.out.println(s));

 

count() : 요소의 개수를 셈

sum() : 요소의 합을 구함

average() : 요소의 평균값

max(), main() : 요소의 최대값, 최소값을 구함

 

sorted() : 요소들을 정렬함

stream.sorted() 	// 오름차순으로 정렬
stream.sorted(Comparator.reverseOrder());	// 내림차순으로 정렬

 

match() : 요소들이 특정한 조건을 충족하는지 검사, boolean형으로 반환

stream.allMatch(a -> a%2 == 0);	// 모든 요소들이 조건을 만족하는지 조사
stream.anyMatch(a -> a%2 == 0); // 최소 한 개 이상의 요소가 조건을 만족하는지 조사
stream.noneMatch(a -> a%2 == 0); // 모든 요소들이 조건을 만족하지 않는지 조사

 

 

 

➤ 스트림의 특징

 

  • 자료의 대상과 관계없이 동일한 연산을 수행한다.
  • 한 번 생성하고 사용한(소모한) 스트림은 재사용 할 수 없다.
  • 스트림의 연산은 기존 자료를 변경하지 않는다.
  • 스트림의 연산은 중간 연산과 최종 연산이 있다.

 

 

-Optional<T> 에 대해서 더 공부 필요

 

 

읽어주셔서 감사합니다😍

오개념에 대한 지적은 환영입니다.

 

 

 

❑ 열거형(Enum)

 

 

➤ enum이란?

 

enumeration : 열거, 목록

관련이 있는 상수들의 집합

자바에서 final로 선언하는 상수(고정된 값)들을 집합으로 묶어 놓은 것이다.

enum형 상수도 객체이다

JDK 1.5 버전부터 생김

 

 

➤ 문법

 

enum 열거형이름 { 상수명1, 상수명2, 상수명3, ... }

 

enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }

 

열거형의 원소들은 대소문자 모두 사용 가능하지만 상수는 대문자로 작성하는 관례가 있음

 

 

 

➤ 사용법

 

enum은 쉽게 말하자면 class 처럼 사용하면 된다.

class 자리에 enum을 작성하고

사용할 때 클래스의 static 변수를 참조하는 것과 같은 방법으로 사용하면 된다.

 

System.out.println(Day.MONDAY);

 

열거형의 객체를 생성해서 값을 할당할 수 있다.

 

Day today = Day.THURSDAY;
System.out.println(today);	// THURSDAY 출력

 

 

 

 

➤ 열거 객체 메소드

 

enum

name()

ordinal()

compareTo(비교값)

valueOf(String name)

values()

 

 

➤ enum을 사용하는 이유

 

상수를 하나씩 선언하는 것보다 코드가 단순, 가독성이 좋음

인스턴스 생성과 상속을 방지

키워드를 사용하기 때문에 구현의 의도가 열거임을 분명하게 나타낼 수 있음

 

 

 

➤ public static final의 문제점 ⚠️ 헷갈리는 개념

 

'public static final'을 이용해서 하나씩 상수를 선언하게 되면

-> 중복이 발생할 수 있다.

-> 이부분은 인터페이스에 상수를 작성해서 일차적으로 해결 가능하지만

인터페이스는 타입 안정성의 문제가 생긴다.

인터페이스를 사용해서 넣은 상수 값끼리 서로 비교 가능한 문제가 발생한다.

직접 비교해도 에러가 발생하지 않아서 타입안정성이 떨어지게 된다.

비교자체가 불가능하게 하려면 서로 다른 객체로 만들어야 한다.

이 부분은 공부해도 아직 설명할 정도로 이해하진 못했다. 추후에 공부필요!

객체를 따로따로 만들면 코드 안정성은 올라가지만 switch문에 사용은 못함

enum은 switch문에 사용 가능

 

 


 

 

❑ 애너테이션(Annotation)

 

주석과 비슷한 것

@ 로 나타냄

애너테이션은 주석처럼 프로그램에 영향을 미치지 않음

 

 

➤ 주석 vs 애너테이션

 

누구를 위한 정보인가?

주석: 코드를 읽는 사람

애너테이션: 프로그램

 

 

 

➤ 애너테이션의 용도

 

프로그램에 직접적으로 영향을 미치진 않으면서 컴파일러에게 정보를 제공해줄 때 사용한다.

예를 들어서 자주 쓰는 애너테이션인 @Override는 컴파일러에게 밑의 메소드가 상위 클래스의 메소드를 오버라이딩한 메소드라는 것을 알려준다.

그래서 메소드 이름의 오타같은 문제를 컴파일러가 잡아줄 수 있도록 해준다.

만약에 @Override로 컴파일러에 정보를 제공하지 않는다면 어떤 문제가 생길까?

만약에 상위 클래스에서 run()이라고 정의한 메소드가 있다고 생각하자.

그리고 하위 클래스에서 이 메소드를 재정의해서 작성하려는데 급하게 코딩하다가 rnu()이라고 오타를 냈다고 가정해보자.

이때 프로그램을 실행하게 되면 컴파일러는 이 rnu()이라는 오타난 메소드를 상위 클래스의 메소드를 재정의한 메소드가 아닌 새로 정의한  아예 새로운 메소드라고 인식한다.

이런 문제를 방지하기 위해 컴파일러에 @Override 라는 애너테이션으로 정보를 제공하고 컴파일러는 개발자가 잘못 작성한 부분을 오류로 알려주게 된다.

 

 

 

➤ 애너테이션의 종류

 

표준 애너테이션

자바에서 기본 제공하는 애너테이션

@Override : 컴파일러에게 메서드를 오버라이딩하는 것이라고 알림

@Deprecated : 앞으로 사용하지 않는다고 알림

@FunctionalInterface : 함수형 인터페이스임을 알림

@SuppressWarning : 컴파일러가 경고메세지를 나타내지 않음

 

 

메타 애너테이션

애너테이션에 붙는 애너테이션, 애너테이션을 정의하는데 사용

@Target : 애너테이션을 정의할 때 적용 대상을 지정하는데 사용

@Documented : 애너테이션 정보를 javadoc으로 작성된 문서에 포함시킴

@Inherited : 애너테이션이 하위 클래스에 상속되도록 함

@Repeatable : 애너테이션을 반복해서 적용할 수 있게 함

 

 

사용자 정의 애너테이션

사용자가 직접 정의하는 애너테이션

 

읽어주셔서 감사합니다 😍

오개념에 대한 지적은 언제나 환영입니다

 

 

 

 

 

❑ 내부 클래스(Inner Class) 란?

 

 

말 그대로 클래스 내부에 선언된 클래스

주로 클래스 내에서만 사용하고 다른 클래스에서는 사용하지 않을 때 내부 클래스로 선언함

 

class Outer {	// 외부 클래스
	class Inner {	// 내부 클래스
    		
    }
}

 

클래스를 감싸고 있는 클래스 : 외부 클래스

클래스안에 있는 클래스 : 내부 클래스

 

 

내부 클래스는 총 4가지 종류가 있음

 

내부 클래스 종류 선언 위치
인스턴스 내부 클래스 인스턴스 변수 위치
정적 내부 클래스 정적 변수 위치
지역 내부 클래스 지역 변수
익명 내부 클래스  

 

클래스내 변수(인스턴스 변수, 정적 변수, 지역 변수)의 위치와 선언 위치가 같다.

 

 

1. 인스턴스 내부 클래스( instance inner class)

 

인스턴스 변수가 선언되는 위치에 선언된다.

외부 클래스 내부에서만 생성하여 사용할 객체를 선언할 때 사용한다.

 

항상 외부 클래스가 먼저 생성된 후 인스턴스 내부 클래스가 생성된다.

따라서 외부 클래스 생성 없이는 사용 불가

 

인스턴스 내부 클래스에 static 변수, static 메소드를 선언할 수 없게 되어있어서 오류가 발생한다.

static 변수나 static 메소드는 클래스의 생성과 상관없이 사용할 수 있지만

내부 클래스는 외부 클래스가 생성되야만 생성되기 때문에 static 사용 불가

 

외부 클래스에서 private으로 선언한 것들 모두 내부 클래스에서 접근 가능

인스턴스 내부 클래스의 메소드는 외부 클래스의 메소드가 호출될 때 사용할 수 있다.

 

 

 

 

2. 정적 내부 클래스(static inner class)

 

인스턴스 내부 클래스는 정적 변수나 정적 메소드를 생성할 수 없다.

하지만 정적 내부 클래스는 정적 메소드, 정적 변수를 내부에 생성하고 사용할 수 있다.

따라서 외부 클래스의 생성과 상관없이 사용하거나 내부에 정적 변수, 정적 메소드를 사용하고 싶다면 정적 내부 클래스 사용하면 된다.

 

정적 내부 클래스는 외부 클래스의 멤버 변수와 같은 위치에 정의한다.

정적 변수와 마찬가지로 static 예약어를 사용한다.

 

예시 코드

더보기
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이 아닌 경우에 다른 클래스에서도 사용할 수 있다.

OutClass.InStaticClass.sTest();

 

 

 

 

2. 지역 내부 클래스(local inner class)

 

지역 변수와 같이 메소드 내부에 정의하는 클래스이다.

 

지역 내부 클래스 선언하기

더보기
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메모리에서 사라진다.

그런데 정적 변수는 컴파일과 동시에 클래스 정보와 함께 메모리를 할당받고 프로그램 실행중에는 계속 메모리 공간에 있어서 접근할 수 있다.

따라서 자바에서 static 변수는 내부에 선언하면 오류가 발생한다.

 

참고 사이트

https://www.geeksforgeeks.org/g-fact-47/

 

Are static local variables allowed in Java? - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

 

 

지역 내부 클래스에서 지역 변수 = 상수 ?

 

지역 내부 클래스에서 사용하는 변수는 상수로 바뀌어서 값을 변경할 수 없게 된다.

그 이유를 이해하려면 일단 지역 내부 클래스의 지역변수가 JVM에서 메모리가 어떻게 흘러가는지를 알아야한다.

항상 지역 변수는 메소드가 호출될 때 stack 메모리에 생성되고 메소드 실행이 끝나면 메모리에서 사라진다.

그런데 만약에 지역변수를 지역 내부 클래스에서 일반 변수처럼 쓸 수 있다면 어떻게 될까?

다른 클래스에서 지역 내부 클래스를 만들기 위해 메소드를 호출하고

메소드는 스택 메모리에 올라갔다가 메소드 수행이 끝나면 스택 메모리에서 사라진다.

LocalTest myPet = new LocalTest();
Runnable runnable = myPet.getMyPet(10);	// getMyPet()메소드의 실행이 끝나면 스택메모리에서 사라짐
runnable.run();		// 지역내부 클래스의 메소드를 사용하면서 getMyPet()의 지역 변수가 사용됨

그런데 메소드의 매개변수와 지역변수를 지역 내부 클래스의 객체를 이용해서 스택메모리에서 사라진 이후에도 참조할 수 있다.

이 때 값을 변경할 수 있다면(변수라면) 문제가 생기기 때문에 지역 변수와 매개변수는 모두 상수로 처리된다.

상수 예약어 'final'을 작성해야하지만 안써도 컴파일러에서 자동으로 final을 추가해준다.

 

참고 사이트

https://www.geeksforgeeks.org/local-inner-class-java/

 

Local Inner Class in Java - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

 

지역 내부 클래스에서 사용하는 메소드의 지역 변수는 모두 상수로 바뀐다!

위에 거 다 까먹어도 이것만 기억하면 지역내부 클래스를 쓸 수 있다.

 

 

 

 

4. 익명 내부 클래스 (Anonymous inner class)

 

익명 클래스가 무엇인가?

클래스 이름을 사용하지 않는 클래스를 익명 클래스라고 한다.

 

지역 내부 클래스와 쓰임새가 비슷하지만 이름이 없기 때문에 일회용이다.

클래스의 선언과 동시에 객체를 생성하기 때문에 하나의 객체만을 생성한다.

따라서 한번만 사용하는 클래스를 생성할 때 만들면 된다.

 

지역 내부 클래스에서 사용한 코드를 익명 내부 클래스로 바꾸면 이렇게 작성할 수 있다.

더보기
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)의 이벤트를 처리하는 핸들러를 구현하는데 사용? 한다고 한다.

 

읽어주셔서 감사합니다.😃  도움이 되셨길 바랍니다.

오개념에 대한 지적은 언제나 환영입니다.🙌

 

 

 

 

 

 

❑ 제네릭(Generic)

 

 

➤ 제네릭이 무엇인가?

 

  • Generic 은 “일반적인”이란 뜻
  • 클래스 내부에 사용할 타입을 외부에서 파라미터 형태로 지정
  • 데이터 타입을 일반화 ( ▶️ 범용적으로 사용할 수 있게함)
  • 자바 5부터 제네릭 추가됨

 

 

 

➤ 제네릭 프로그래밍

 

어떤 변수가 하나의 참조 자료형이 아닌 여러 참조 자료형을 사용할 수 있도록 프로그래밍하는 것

 

여러 참조 자료형이 쓰일 수 있을 때 미리 특정한 자료형을 지정하지 않고 제네릭으로 선언해서 클래스나 메소드가 사용되는 시점에 어떤 자료형 사용할지 지정

 

 

 제네릭 클래스 정의하기

 

클래스명 <T> {

}

 

ex) 

class GenericDeclaration <T> {
    private T variable;

    public T getVariable() {
        return variable;
    }

    public void setVariable(T variable) {
        this.variable = variable;
    }
}

 

여러 자료형으로 바꾸어 사용할 부분에 자료형 대신 T 작성

<> : 다이아몬드 연산자

T를 자료형 매개변수(Type parameter, 타입 매개변수)

T : 대입된 자료형

GenericDeclaration <T> : 제네릭 자료형, 매개변수화된 자료형

 

 

ArrayList<Integer> array = new ArrayList<>();

선언하는 부분의 <>에 자료형을 작성했기 때문에 생성부분의 <> 안은 생략 가능

컴파일러가 유추 가능하기 때문

 

주로 <>안에 T를 많이 사용 (Type)

E (Element), K (Keyt), N (Number), V (Value), R (Result) 등 있음

위의 예시 뿐만 아니라 A, B, C 등 사용자가 정의해서 사용해도 된다.

 

 

 

⤵️아래는 저의 견해입니다.

 

타입 매개변수는 수학의 변수 x라고 생각해주면 코드를 작성하고 읽기가 용이하다.

수학에서 변수 x는 어떤 수든 대입할 수 있다.

하지만 식에서 x에 특정 수를 대입하는 순간 식 안의 모든 x는 그 수로 정해져서 계산된다.

예를 들어서 x+1-2x 라는 식이 있다고 가정해보자.

이때 x에 1을 대입하려고 하면 x, -2x 모두 x=1을 대입하고 계산해야 한다.

x에는 x=1 을 대입하고 -2x에는 x=2를 대입하는 식으로 계산해선 안된다.

변수 x는 같은 수를 의미하므로 모든 x엔 같은 수로 대입해줘야 한다.

프로그래밍에서 제네릭을 컴파일하는 과정에서도 마찬가지이다.

컴파일러는 같은 알파벳의 타입 매개변수는 모두 같은 변수라고 생각한다.

따라서 타입 매개변수 T는 인스턴스가 만들어질 때 같은 타입이 대입된다.

 

 

 

➤ static 변수와 타입 매개변수

 

static 변수, static 메소드 내에서는 타입 매개변수 사용할 수 없음

 

🔎  이유는?

제네릭의 타입이 정해지는 것은 인스턴스가 생성되는 순간이다.

하지만 static은 클래스 생성 시 클래스 정보와 함께 메모리에 올라가는데 타입을 지정해주지 않으면 메모리를 할당할 수 없다.

따라서 타입 매개변수의 자료형이 결정되는 시점보다 static 변수나 메소드가 생성되는 시점이 더 빠르기 때문에 문제가 발생하게 된다.

 

 

 

 

➤ 왜 제네릭이 직접 형 변환하는 것보다 안정적일까?

 

Object 등의 상위 클래스 타입으로 정의하고 직접 형변환 해주면 제네릭보다 불안정함

제네릭 클래스는 컴파일러가 일단 대입된 자료형이 잘 쓰였는지 확인하고

class 파일을 생성할 때 타입 매개변수를 사용한 곳에 지정한 자료형에 따라 컴파일함

 

 

 

➤ 제네릭 메소드(Generic Method)

 

클래스 전체가 아닌 클래스 내부의 특정 메소드만 제네릭으로 선언

매개변수나 메소드 시그니처에 타입 매개변수 T를 사용한 메소드

메소드가 호출되는 시점에 제네릭 타입이 결정됨

정의되는 시점에는 어떤 타입이 입력되는지 알 수 없음

-> length()와 같은 클래스내 메소드는 정의하는 시점에 사용 불가

-> 최상위 클래스 Object의 메소드는 사용 가능

일반 메소드, static 메소드에서 모두 활용 가능

 

 

 

➤ 제네릭의 장점

 

- 여러 참조 자료형마다 코드를 작성하지 않아도 되서 코드가 간결하고 직관적

- 매번 형 변환을 할 필요가 없기 때문에 오류의 확률 줄어듬 -> 안정성

- 새로운 자료형을 사용할 땐 그 자료형만 생성하고 사용할때 <> 안에 자료형 넣기만 하면됨

 

 

 

☑️ 참고) 제네릭으로 자료형 추론하기

자바10 부터는 지역변수에 한해서 자료형을 추론 가능(var형 사용 가능)

➡️ 지역 변수 자료형 추론(local variable type inference)

var local = new ArrayList<String>();

➡️ 컴파일러가 자료형 추론 가능

 

 

 

➤ 와일드카드(wild card)

 

제한을 두지 않는 기호를 의미

제네릭에서는 ? 기호를 사용하여 와일드 카드 사용

 

  • <?> : 타입 매개변수에 모든 타입 사용
  • <? extends T> : T 타입과 T타입의 하위 클래스만 사용
  • <? super T> : T 타입과 T 타입의 상위 클래스만 사용

 

 

 

추가로 공부할 부분

 

왜 Object가 아닌 Generic을 사용하는가?

 

 

Object의 메소드

https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html

 

Object (Java Platform SE 7 )

Called by the garbage collector on an object when garbage collection determines that there are no more references to the object. A subclass overrides the finalize method to dispose of system resources or to perform other cleanup. The general contract of fi

docs.oracle.com

 

 

읽어주셔서 감사합니다 😊  도움이 되셨길 바랍니다.

오개념에 대한 지적은 언제나 환영입니다.

 

 

 

 

 

❑ 리팩토링(Refactoring)

 

컴퓨터 프로그래밍과 소프트웨어 디자인에서 code refactoring 이란?

외부 동작을 변경하지 않고 기존 컴퓨터 코드를 재구성 하는 프로세스다.

 

 

https://en.wikipedia.org/wiki/Code_refactoring

 

Code refactoring - Wikipedia

From Wikipedia, the free encyclopedia Jump to navigation Jump to search Restructuring existing computer code without changing its external behavior This article is about a behaviour-preserving change. It is not to be confused with Rewrite (programming). In

en.wikipedia.org

 

 

 

 

❑ API(application program interface)

 

컴퓨터 프로그램과 컴퓨터 사이의 연결?

프로토콜 집합, 정의를 사용하여 두 소프트웨어의 구성요소가 서로 통신할 수 있게끔 하는 메커니즘

 

 

 

❑ 문자열 배열을 정수 배열로 변환하기

 

parseInt()와 반복문 사용

Integer.parseInt(String[] args)

 

//...
int[] user_info_int(String[] info) {
	int[] userInfoInt = new int[3];
	// String array converts into integers array one by one
	// except name
	for (int i = 0; i < info.length - 1; i++) {
		// Change values except name to integer array
   	 userInfoInt[i] = Integer.parseInt(info[i + 1]);
	}
//...

 

 

 

 

 

❑ 깃허브 error

 

 

➤ 로컬 저장소에서 add 시 에러

 

‘does not have a commit checked out’

 

폴더 내에서 git이 관리하게 하는 명령어(git init)를 2번 하면 발생

터미널에서 ls -a 후 .git 파일 2개 확인

필요없는 .git 및 폴더 삭제

➡️ 해결

 

 

 

➤ push 시 에러

 

'src refspec master does not match any’

 

깃허브에 올릴 때 명령어가 꼬이면 주로 발생

새로운 폴더 만들거나 해당 폴더 내 생성된 .git 파일 삭제하고 처음부터 명령어 입력하면 됨

개인 프로젝트 : 깃 clone해서 수정한 파일 붙여넣기 하고 push 하면 됨

 

 

'does not appear to be a git repository'

 

git remote -v 로 저장소명 올바른지, 연결 잘 되있는지 확인

 

 

 

 

 

복잡한 프로젝트 코딩하기

1단계 프로그램에 필요한 기능들 생각해보기

2단계 프로그램에 필요한 메소드 정의 및 파일 단위로 분할

3단계 프로그램 메소드들의 흐름도 그려보기

 

재귀함수(recursive fuction)?

자신을 다시 부르는 함수

function() 메소드 안에 function() 을 호출

코드가 직관적이고 이해하기 쉬움

 

 

 

❑ 오늘 해결한 intelliJ 오류

 

깃허브에서 clone 한 후에 intelliJ에서 실행

디버깅시 “기본 클래스 를 찾거나 로드할 수 없습니다.”

Project와 package가 잘 연결되있는지 확인!!
코드 첫 줄에 package 구문 맞는지 확인

 

모듈이 잘 연결되있는지 확인

있을경우

오른쪽 상단에 톱니바퀴 -> Preference -> File type -> Java 선택

 

 

 

 

 

 

 

 

< 다형성(Polymorphism) >

 

 

❏ 다형성이 무엇인가?

 

polymorphism은 poly(여러개) 와 morphism(형태)의 합성어이다.

말그대로 여러개의 형태를 의미한다.

객체지향에서 하위 클래스는 상위클래스로 형변환(업캐스팅)이 가능하다.

 

 

예를 들어서 동물이라는 클래스를 개, 고양이, 도마뱀이라는 클래스가 상속하고 있다.

 

class Animal {
    void info(){
        System.out.println("나는 동물 입니다.");
    }
}

class Dog extends Animal{
    @Override
    void info() {
        System.out.println("나는 개입니다.🐕");
    }
    void woof() {
        System.out.println("멍멍멍멍");
    }
}

class Cat extends Animal {
    @Override
    void info() {
        System.out.println("나는 고양이입니다.🐱");
    }
    void meow() {
        System.out.println("야옹~야옹~");
    }
}

class Lizard extends Animal {
    String sexuality;
    @Override
    void info() {
        System.out.println("나는 도마뱀입니다.🦎");
    }
    
}

 

Animal의 info클래스를 각각의 하위 클래스에서 이름에 맞게 재정의를 해줬다.

 

public class AnimalTest {
    public static void main(String args[]) {
        // 다형성을 이용하여 같은 참조변수로 선언하기
        Animal dog = new Dog();
        Animal cat = new Cat();
        Animal lizard = new Lizard();

        // 재정의한 메소드 실행
        dog.info();
        cat.info();
        lizard.info();
    }
}

 

<출력결과>

나는 개입니다.🐕
나는 고양이입니다.🐱
나는 도마뱀입니다.🦎

 

상위 클래스인 Animal로 타입을 지정하고 new로 만든 인스턴스는 각각 하위 클래스로 지정해줄 수 있다.

이렇게 하위 클래스로 만들고 상위 클래스로 타입을 바꾸는 것을 '업캐스팅'이라고 한다.

상속 계층도에서 위로 올라가서 업캐스팅이라고 생각하자. (반대는 아래로 가니까 다운캐스팅)

 

 

업캐스팅, 다운캐스팅

 

자바에서는 업캐스팅은 별다른 키워드 없이 암묵적으로 가능하다.

이것을 '묵시적 형변환' 이라고도 한다.

하지만 반대로 암묵적으로 다운캐스팅(상위클래스 ➡️ 하위클래스)은 불가하다!!

 

다운 캐스팅 오류

상위 클래스로 인스턴스를 만든 후에 하위 클래스로 다운 캐스팅하게 되면 사진처럼 빨간 줄이 그어지고 오류가 난다.

 

 

 

 

❏ 왜 업캐스팅은 묵시적으로 되지만 다운캐스팅은 안될까?

 

하위 클래스들은 상위 클래스보다 구체적이고 메소드나 멤버변수가 같거나 많다.

하지만 상위 클래스는 더 일반적으로 만들어서 메소드나 멤버변수가 같거나 적다.

 

그래서 상위 클래스로 만든 인스턴스를 하위 클래스 타입의 메모리 공간에 집어넣으면

일부 메소드와 멤버변수를 사용할 수 없는 일이 발생한다.

실제로 클래스 내에 정의된 멤버변수와 메소드를 쓸 수 없어서 에러가 발생한다.

 

반대로 하위 클래스로 만든 인스턴스를 상위 클래스 타입의 메모리에 넣는건

인스턴스 만들 떄 있던 일부 기능이나 속성을 사용하지는 못하더라도 상위 클래스에 정의된 모든 메소드나 멤버변수는 사용할 수 있다.

내용물은 하위 클래스더라도 참조 타입은 상위 클래스이기 때문에 문제될 것은 없는 것이다.

 

참고) 상위 클래스 위의 상위 클래스들도 묵시적으로 형변환이 가능할까?

상속 계층이 여러 층일 때 여러 층 위의 상위 클래스로의 형변환도 묵시적으로 이루어진다.

 

 

 

❏ instanceof 를 통한 다운 캐스팅

 

상위 클래스로 형변환 했던 하위 클래스를 다시 원래 자료형으로 돌려놓을 수는 없을까?

하위 클래스로 직접적으로 형변환 할 수는 없지만 instanceof라는 키워드를 사용하면 원래 형태가 하위 클래스인지 확인할 수는 있다.

 

instanceof 예약어는 왼쪽에 있는 변수의 원래 인스턴스 형이 오른쪽 클래스 자료형인지 확인해준다.

➡️ 원래 자료형이 맞으면 true / 틀리면 false 를 반환한다.

주로 if 문과 함께 사용한다.

 

하위 클래스를 업 캐스팅하면 상위 클래스에 없는 해당 클래스의 고유한 메소드나 필드는 사용할 수 없게 된다.

위에 작성한 예시 코드에서 Dog와 Cat 클래스에는 상위 클래스에서 재정의한 메소드가 아닌 woof()와 meow()메소드가 있다.

그리고 Lizard에는 스트링 타입의 성별 멤버 변수가 선언되있다.

상위 클래스에서 정의 하지 않은 woof()와 meow(), sexuality를 업캐스팅한 변수에서 접근해보았다.

 

public static void main(String args[]) {
        // 다형성을 이용하여 같은 참조변수로 선언하기
        Animal dog = new Dog();
        Animal cat = new Cat();
        Animal lizard = new Lizard();
        Lizard example = new Animal();

        // 하위클래스에만 있는 메소드 실행
        dog.woof(); // cannot find symbol 오류
        cat.meow(); // cannot find symbol 오류
        // 하위 클래스에만 있는 필드 실행
        lizard.sexuality = "female";    // cannot find symbol 오류
        
}

 

실행하게 되면 'cannot find symbol' 에러가 뜨게 된다.

상위 클래스로 형변환하면서 하위 클래스에만 있던 메소드와 필드는 메모리 할당이 되지 않아 사용할 수 없게 된 것이다.

 

이럴 때 instanceof 를 사용해서 원래 자료형을 확인하고 맞으면 원래 인스턴스형강제로 다운캐스팅 해주면 된다.

원래 인스턴스형으로 변환할 때는 괄호안에 원래 자료형을 반드시 명시해야한다.

상위 클래스로 형변환 했던 객체를 다시 원래 자료형으로 형변환하는 것을 '다운캐스팅'이라고 부르기도 한다.

 

public static void main(String[] args) {
   // 상위 클래스로 형변환
   Animal dog = new Dog();
   Animal cat = new Cat();
   Animal lizard = new Lizard();

   // instanceof를 이용한 다운캐스팅
   if (dog instanceof Dog) {
       Dog dog1 = (Dog)dog;
       dog1.woof();
   }
   if (cat instanceof Cat) {
       Cat cat1 = (Cat)cat;
       cat1.meow();;
   }
   if (lizard instanceof Lizard) {
       Lizard lizard1 = (Lizard)lizard;
       lizard1.sexuality = "female";
       System.out.println(lizard1.sexuality);
   }
}

 

<출력결과>

멍멍멍멍
야옹~야옹~
female

 

 

 

 

 

 

업캐스팅과 다운캐스팅에 대한 레퍼런스

https://www.geeksforgeeks.org/upcasting-vs-downcasting-in-java/

 

Upcasting Vs Downcasting in Java - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

 

 

 

읽어주셔서 감사합니다. 도움이 되셨길 바랍니다. 🫶

오개념에 대한 지적은 언제나 환영입니다.

 

 

 

 

 

 

< 상속 >

 

 

❑ 상속이란?

 

상속은 OOP에서 아주아주 중요한 개념이다.

자바에서 상속은 말 그대로 부모 클래스가 자식 클래스에게 가지고 있는것을 물려주는 것을 말한다.

부모-자식 / 조상-자손 / parent class-child class 라고 표현하기도 한다.

하지만 상속의 의미를 생각했을 때 상위 클래스-하위 클래스라고 표현하는 것이 올바르다.

 

하위 클래스는 상위 클래스에서 속성과 기능을 모두 물려받아서 하위 클래스는 상위 클래스의 필드(field)와 메소드(method)를 쓸 수 있다.

 

 

🔎 그렇다면 상속은 언제 써야할까?

 

상속에서 상위 클래스는 하위 클래스를 포괄하는 일반적인 개념이고

하위 클래스는 상위 클래스보다 구체적인 개념이다.

상속은 이미 정의해놓은 클래스에 속성이나 기능을 추가해서 새로운 클래스를 만들려고 할 때 유용할 것이다.

이미 만들어놓은 클래스에 추가할 것만 새로 작성하면 되서 완전히 새로 작성하는 것보다 에너지가 적게 들 것이다.

그리고 상위 클래스를 통해서 아래에 여러 하위 클래스를 관리할 수 있기 때문에 유지보수하기 편할 것이다.

 

 

🌱 상속 개념 예시

포유류와 사람으로 예를 들어서 표현해보았다.

포유류는 사람, 고양이, 개보다 포괄적인 개념이고 사람, 고양이, 개는 포유류에 포함된다.

따라서 포유류가 상위 클래스이고 사람/고양이/개는 하위 클래스가 될 수 있다.

 

※ 상속을 표현할 때 화살표는 항상 하위 클래스에서 상위 클래스 쪽으로 향하게 그린다.

 

상속 예시1

 

여기서 또 사람은 프로그래머/요리사/화가보다 일반적인 개념이다.

따라서 사람 클래스는 이번엔 프로그래머, 요리사, 화가 클래스의 상위 클래스가 될 수 있다.

 

 

상속 예시2

 

이렇게 상속은 누군가의 하위 클래스이면서 동시에 누군가의 상위 클래스가 될 수 있다.

 

자바에서 상속을 구현할 때는 extends라는 예약어를 사용한다.

 

class child extends parent {
}

 

하위 클래스의 이름 옆에 'extends 상위클래스' 를 작성하면 상위-하위 클래스 관계가 된다.

 

 

🔔 참고) 다중 상속

다중 상속은 한 클래스가 여러 클래스에서 상속받는 것이다.

객체지향 언어인 C++에서는 다중 상속이 가능하지만 자바에서는 지원하지 않는다.

자바에서는 하위 클래스는 단 하나의 상위 클래스만 가질 수 있다. (extends 뒤엔 하나의 클래스만 올수 있다.)

여러 클래스에서 상속받으면 다양한 기능을 상속받을 수 있지만 모호성의 문제가 있다.

객체 지향에서 다중상속으로 인한 대표적인 문제가 다이아몬드 문제이다.

C++에서는 문법적으로 이를 해결했지만 자바는 모호함을 없애고 다중 상속을 사용하지 않는 것을 택한 것이다.

하지만 자바에서도 다형성이나 인터페이스라는 개념들로 다중 상속과 비슷한 효과를 낼 수 있다.

 

 

 

❑ 포함 관계

 

자바의 클래스들은 서로 관계적이다.

그렇다면 모든 관계가 상속일까? ❌

 

클래스간의 관계에는 상속도 있지만 '포함' 관계도 있다.

포함은 클래스 내에서 사용하기 위해서 정의한 클래스 관계이다.

public class Student {
	Subject math = new subject("math");
}

public class Subject {
	String name;
    
    Subject(String name) {
    	this.name = name;    
    }
}

 

예를 들어서 학생 클래스와 과목 클래스가 있을 때 과목 객체들은 모두 학생 클래스에 속하게 된다.

이럴 때 상속으로 만들면 어떻게 될까?

학생 클래스의 속성과 메소드는 과목 클래스에서 재사용하기 어렵다.

하지만 학생 클래스에서 과목 클래스를 만들어서 사용한다.

이럴 때는 상속보다는 포함 관계로 만드는 것이 좋다.

 

 

※ 상속은 '일반적인 개념-구체적인 개념' 다시말해

'IS-A 관계' (is a relationship) 에서 사용하는 것이 좋다.

'~(하위클래스)는 ~(상위클래스)이다' 로 표현했을 때 어색하지 않다면 상속으로 쓰면 된다.

 

- 예시 -

학생은 사람이다.

강아지는 동물이다.

카카오톡은 앱이다.

 

 

※ 포함은 'HAS-A 관계(has a relationship)' 에서 사용하는게 좋다.

'~는 ~를 가지고 있다.' 로 표현했을 때 어색하지 않다면 포함으로 쓰면 된다.

 

- 예시 -

컴퓨터는 모니터를 가지고 있다.

플레이어는 레벨을 가지고 있다.

 

 

 

❑ 메소드 오버라이딩 ( Method Overriding)

 

메서드 오버라이딩이란?

상위 클래스에서 정의된 메소드를 하위 클래스에서 재정의 하는 것이다.

상위 클래스에서 정의한 메소드를 하위 클래스마다 다르게 사용할 때가 있다.

오버라이딩 된 메소드라는 것을 컴파일러에 알려주기 위해서 재정의할 메소드 위에 '@Override'를 작성해준다.

public class Person {
	void walk() {
    	System.out.println("걷습니다.");
	}
}

class Dancer extends Person {
	@Override
	void walk() {
    	System.out.println("춤추면서 걷습니다.");
    }
}

 

 

⚠️ 오버라이딩 조건

✅ 반환형, 메소드 이름, 매개변수 개수, 매개변수 자료형이 반드시 같아야함

  접근 제어자의 범위가 상위 클래스의 메소드보다 같거나 넓어야 함

  예외는 상위 클래스의 메소드보다 많이 선언할 수 없음

➤ 조건을 지키지 않으면 자바 컴파일러가 재정의한 메소드를 기존의 메소드와 다른 메소드로 인식!

 

 

 

❑ super

 

상속에서 쓰는 super의 역할은 this 와 비슷하다.

super는 하위클래스에서 상위 클래스를 부르는 예약어다.

super 와 super()는 this처럼 역할이 다르다.

 

super

하위클래스에서 상위클래스를 부를 때 사용한다.

 

super()

하위클래스의 생성자에서 상위클래스의 생성자를 부를 때 사용한다.

생성자의 첫번째 줄에 작성해야 한다.

하위클래스 생성자에 작성하지 않아도 자동으로 호출한다.

 

 

 

❑ 최상위 클래스 Object

 

자바에서는 'Object'라는 말은 객체라는 의미이기도 하지만 최상위 클래스를 의미한다.

모든 클래스는 Object 밑에 하위 계층에 존재한다.

만약에 어떤 클래스를 만들었는데 extends를 작성하지 않으면 컴파일러가 자동으로 'extends Object'를 작성한다.

따라서 모든 클래스는 Object에 정의된 메소드들을 사용할 수 있다.

Object의 대표적인 메소드

메소드 설명
toString() 객체를 문자열로 표현하여 반환, 재정의하여 객체에 대한 설명이나 특정 멤버 변수 값을 반환함
boolean equals(Object obj) 두 인스턴스가 동일한지 여부를 반환, 재정의하여 논리적으로 동일한 인스턴스임을 정의할 수 있음
in hashCode() 객체의 해시코드 값을 반환(위치정보)
Object clone() 객체를 복제하여 동일한 멤버 변수 값을 가진 새로운 인스턴스를 생성함
Class getClass() 현재 객체가 참조하고 있는 클래스를 반환함
void finalize() 인스턴스가 힙 메모리에서 제거될 때 가비지 컬렉터에 의해 호출되는 메소드, 네트워크 연결 해제, 열려 있는 파일 스트림 해제 등을 구현
void wait() 멀티스레드 프로그램에서 사용하는 메소드, 스레드를 '기다리는 상태'로 만듦
void notify() wait() 메소드에 의해 기다리고 있는 스레드를 실행 가능한 상태로 가져옴

 

 

 

 

 

 

 

생성자 (Constructor)

 

 

“ 인스턴스가 생성될 때 인스턴스를 초기화 하는 메소드 “

 

 

클래스 구성요소 중 하나로 클래스를 생성하는 역할

생성자가 인스턴스를 만드는 명령어는 아님

new 키워드가 클래스의 인스턴스를 만들때 사용됨

 

클래스 이름(){

}

 

 

🌟 생성자도 오버로딩이 가능함

 

 

메소드와 다른점

 

1. 리턴을 하지 않는다

2. 생성자 이름은 반드시 클래스 이름과 동일해야 한다

     ➡️ 클래스명과 다르면 일반 메소드가 된다

 

 

 

생성자가 왜 필요한가?

 

➡️ 클래스를 처음 만들 때 멤버 변수나 상수를 초기화해서 여러개의 객체를 생성할 때 시간을 단축!

 

 

 

 

기본 생성자 / 매개변수가 있는 생성자

 

 

기본 생성자(default constructor)

 

➤ 모든 클래스 내부에는 생성자가 반드시 1개 이상 있어야 함

➤ 생성자가 없는 클래스는 프로그램 실행할 때 컴파일러가 자동으로 기본 생성자 생성

         → 생성자를 작성한 경우에는 디폴트 생성자 제공하지 않음

➤ 기본 생성자 직접 작성해도됨 ‘클래스() {}’

➤ 매개변수와 구현코드가 없음

 

 

매개변수가 있는 생성자

 

➤ 매개변수를 통해서 생성과 동시에 속성 값을 같이 넣어줄 수 있다.

➤ 기본 생성자로 만들고 ‘객체이름.변수’로 속성값 하나하나 지정해주는 것 보다 훨씬 편리함

 

 

 

 


 

 

 

this

 

▶️ this는 생성된 인스턴스 스스로를 가르키는 예약어

▶️ this는 쓰임새가 총 3가지 있음

 

 

1. 자신의 인스턴스 변수를 가르키는 this

 

인스턴스 변수와 지역변수 이름을 같게 하면 혼동됨

 

휴대폰을 예시로 들어서  클래스를 작성해보았다.

 

public class Phone {
	// 필드
	private String company;
   	private static int serialNum = 10000;
  	private String modelName;
   	private int modelNum;
	
	// 생성자
	Phone(String company, String modelName){
		// 잘못된 예시
		company = company;	
		modelName = modelName;
		modelNum = Phone.serialNum;
		Phone.serialNum += 1000;
	}
    
	// 메소드
	public String getCompany() {
        return company;
	}
	public String getModelName() {
        return modelName;
	}
	public int getModelNum() {
        return modelNum;
	}
}

 

Phone의 생성자를 보면 지역 변수와 인스턴스 변수 이름이 같다.

이런식으로 작성하면 컴파일러는 양쪽 다 지역변수로 생각한다.

 

main 클래스를 만들고 출력해보자

 

public class PhoneTest {
    public static void main(String[] args) {
        Phone iPhone11Max = new Phone("Apple", "iPhone11Max");

        System.out.println(iPhone11Max.getModelName() + "의 정보");
        System.out.println("제조사 : " + iPhone11Max.getCompany());
        System.out.println("모델넘버 : " + iPhone11Max.getModelNum());    }
}

 

<실행 결과>

null의 정보
제조사 : null
모델넘버 : 10000

 

실행 결과를 보면 지역 변수명과 인스턴스 변수명이 같은 변수는 인스턴스의 변수에 제대로 값이 저장이 안되서 null로 뜨는 것을 확인할 수 있다.

자바에서는 일반적으로 인스턴스 변수와 생성자의 지역변수 명을 똑같이 작성하는데

이럴때 인스턴스 변수와 지역변수를 컴파일러가 구분할 수 있게 'this.변수명' 을 사용한다.

이때 사용하는 this는 자신의 인스턴스를 가르킨다.

지역 변수가 아닌 인스턴스 내에 있는 해당 변수를 사용하는 것이다.

 

클래스의 생성자 부분을 이렇게 고치고 실행해보자

Phone(String company, String modelName) {
	this.company = company;
	this.modelName = modelName;
	this.modelNum = Phone.serialNum;
	Phone.serialNum += 1000;
}

 

<실행 결과>

iPhone11Max의 정보
제조사 : Apple
모델넘버 : 10000

this를 사용해서 구분해주면 지역변수가 인스턴스 변수에 올바르게 대입된 것을 확인할 수 있다.

 

 

 

2. 생성자에서 다른 생성자를 호출하는 this()

 

this()는 객체 내부에서 같은 객체 내부의 생성자를 호출하는 메소드이다.

반드시 생성자 내부에만 사용하고 생성자의 첫 줄에 위치해야한다.

생성자가 여러개 있으면 원하는 생성자의 매개변수에 맞춰서 괄호()안에 넣어주면 된다.

 

예시)

public class ThisExample {
    int num;
    String name;
    public ThisExample() {
        this(1, "None");
    }
    public ThisExample(int num, String name) {
        this.num = num;
        this.name = name;

    }
}

this(1, "None") 은 ThisExample(int num, String name) 생성자를 호출

num 변수에 1을, name변수에 "None"을 대입시킨다.

 

public class ThisTest {
    public static void main(String[] args) {
        ThisExample example1 = new ThisExample();
        ThisExample example2 = new ThisExample(10, "김철수");

        System.out.println(example1.name + ", " + example1.num);
        System.out.println(example2.name + ", " + example2.num);
    }
}

 

<실행 결과>

None, 1
김철수, 10

 

 

 

3. 자신의 주소를 가르키는 this

 

객체 내부에서 this 는 인스턴스의 주소를 가르키는 역할도 한다.

주소를 리턴하는 경우는 잘 사용하지 않는다.

 

 

ThisExample 클래스에 주소를 리턴하는 메소드를 작성해 보았다.

ThisExample address() {
   return this;
}

 

 

public static void main(String[] args) {
        ThisExample example3 = new ThisExample();
        System.out.println(example3.address());
        System.out.println(example3);
}

객체를 그대로 출력해도 주소값을 얻을 수 있다.

 

<실행결과>

constructor.ThisExample@6ad5c04e
constructor.ThisExample@6ad5c04e

실행해보면 인스턴스가 저장된 주소가 출력된다.

주소 앞에 constructor은 패키지 이름이고 ThisExample은 클래스 이름이다.

 

 

읽어주셔서 감사합니다 ❤️

 

 

 

 

 

 

객체지향 프로그래밍

 

OOP (Object Oriented Programming)

 

객체지향 프로그래밍 = 자바 ? (❌)

자바는 OOP의 하나일 뿐 자바 자체가 객체지향 프로그래밍은 아니다.

 

 

객체란?

 

눈에 보이는 모든 사물, 세상에 존재하는 모든 것

 

➤ 객체와 인스턴스의 차이는 무엇일까?

Object 와 instance는 같은 때 사용하는데 차이가 있을까?

여러 글들에서 객체와 인스턴스의 차이를 두지 않고 있는데 용어의 사용에서 차이가 있다고 해서 궁금해졌다.

이 부분은 정확하진 않지만 많은 글들을 참고해봤다.

객체는 객체를 선언하고 아직 메모리를 차지하기 전의 상태를 얘기하고

인스턴스는 실제로 메모리가 할당된 상태를 말한다고 한다. (인스턴스화 한다고 한다)

객체는 청사진을 토대로 이런 물건을 만들어야겠다 라고 생각하고 아직 실물은 없는 거고

인스턴스는 실제로 물건을 만들어서 실물로 만든 상태인 것이다...

왜 이 두가지에 차이를 두는지 아직은 잘 모르겠다. 🤔

공부하다보면 언젠가 깨닫게 되지 않을까..

 

※ instantiate (인스턴스화) : 클래스로부터 객체를 만드는 과정, 컴파일러가 클래스 정보를 토대로 힙 메모리에 객체를 만들 공간을 지정하고 클래스 정보를 옮겨서 인스턴스를 만드는 일련의 과정을 의미합니다. 쉽게 말해 청사진을 토대로 만드는 과정

 

 

Class

 

- 클래스명은 일반적으로 대문자로 시작

- 객체를 여러개 만들어낼 수 있는 틀, 설계도, 청사진

- 필드(속성), 메소드(기능), 생성자로 구성됨

 

 

field (멤버변수)

 

클래스 내의 속성을 나타냄

 

 

자바의 변수의 종류

 

1. 클래스 변수 (=static 변수 =정적변수)

 

static int serialNum = 1001;

 

  • 클래스가 생성될 때 함께 생성됨
  • 인스턴스 변수와 같은 위치에서 선언하지만 static이 앞에 붙음
  • 선언된 클래스에 존재
  • 인스턴스로 클래스 변수에 접근가능 ☞ 인스턴스명.변수명
  • 클래스명으로도 접근 가능 ☞ 클래스명.변수명
  • 어떻게 접근하든 같은 클래스 변수로 도착함
  • 보통 하나의 변수를 여러 객체에서 공통으로 사용할 때 클래스 변수로 만듬

 

 

2. 인스턴스 변수

 

int inst = 100;

 

  • 인스턴스가 생성될 때마다 생기는 변수
  • 인스턴스마다 다른 힙 메모리에 저장됨
  • 반드시 인스턴스를 생성해야만 접근 가능

 

 

3. 지역변수

 

  • 메소드에 선언된 변수
  • 메소드가 끝나면 스택 메모리에서 사라짐

 

 

static

 

  • 클래스의 멤버에 사용가능
  • static이 붙은 멤버는 정적멤버 (static member)
  • static이 붙은 변수(클래스 변수)는 인스턴스를 생성할 때마다 양산되지 않음
  • 클래스명과 포인터로도 접근 가능
  • 프로그램이 실행될 때 딱 한번만 스택 메모리 공간에 공간 할당됨

 

 

method

 

  •  클래스 내의 기능을 담당
  •  함수
  •  메소드는 클래스변수와 같은 공간에 저장됨(메소드 영역)  같은 클래스로 만든 인스턴스는 모두 하나의 메소드를 공유함
  •  method signature와 method body 로 나뉨

 

접근제어자 반환타입 메서드명(매개 변수) { // method signature

메소드 내용 // method body

}

 

  • 반환타입이 없으면 void ☞ return 안써줘도됨
  • 반환타입이 있으면 return 뒤에 반드시 같은 반환타입을 넣어줘야함
  • 반환타입은 기본 변수뿐 아니라 참조 변수도 가능
  • 포인트(.)를 이용해서 인스턴스 내부의 메소드 호출
  • 메소드 호출 시 괄호() 안에 넣어주는 값을 인자(argument)라고 부름

 

 

method Overloading

 

  • 클래스 안에 같은 이름의 메소드를 여러개 정의하는 것
  • overload는 사전적으로 과적하다, 부담을 지우다 라는 의미
  • 같은 이름의 메소드를 여러개 가지면서 매개변수의 유형과 개수가 다르게하는 기술
  • 자주사용하는 오버로딩으로는 println() 메소드가 있음
  • 안에 넣어주는 인자에 따라 다른 println()메소드 호출됨

 

 

1 2 3 4 5 6