And Brain said,

Java, 객체지향의 화신 본문

IT/Java & Kotlin & Spring boot

Java, 객체지향의 화신

The Man 2023. 4. 12. 14:24
반응형

목차
1. 클래스와 객체
2. 인터페이스와 추상 클래스
3. 메소드와 매개변수
4. 제네릭 (Generics)
5. 배열과 컬렉션
6. 객체지향 프로그래밍
7. 예외 처리
8. Java I/O
9. Java의 동시성(Concurrency)
10. Java의 네트워킹
11. Java의 리플렉션(Reflection)
12. Java의 디자인 패턴
13. 함수형 프로그래밍

 
오늘은 객체지향 언어의 대표주자 Java의 이론에 대해 간단하게 알아보자.
 


1. 클래스와 객체

 
 
Java는 순수 객체지향 언어는 아니지만 처음부터 객체지향을 위해 개발된 언어로, 당연히 객체라는 개념이 아주 중요하다.
 
객체(Object)는 객체 지향 프로그래밍(OOP)에서 프로그램의 기본 구성 요소로, 소프트웨어 세계에서 현실 세계의 개체를 모델링하여 표현한 것이다.
 
객체는 클래스를 기반으로 생성되며, 클래스는 객체의 설계도 또는 템플릿으로 볼 수 있고, 객체는 속성(멤버 변수)과 행동(메소드)을 갖고 있다.

속성은 객체의 상태를 나타내는 정보를 저장하고, 행동은 객체가 수행할 수 있는 작업을 정의한다.
 

public class Cat {
    private String name;
    private int age;

    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void meow() {
        System.out.println("Meow! My name is " + name + " and I'm " + age + " years old.");
    }
}

 
이 예제에서 Cat 클래스는 두 개의 private 변수인 name과 age를 가지고 있고, 이 변수들은 생성자를 통해 초기화된다.
 
meow() 메소드는 고양이의 울음소리와 함께 이름과 나이를 출력하게 된다.

이제 Cat 클래스를 기반으로 객체를 생성해보자.
 

public class Main {
    public static void main(String[] args) {
        Cat myCat = new Cat("Horang2", 1);
        myCat.meow();
    }
}

 
new Cat("Horang2", 1)를 통해 Cat 클래스의 객체를 생성하고, myCat 변수에 할당한다.
 
그 후, meow() 메소드를 호출하여 고양이의 울음소리와 함께 이름과 나이를 출력합니다.

이처럼 Java에서 클래스와 객체를 사용하면 코드를 더욱 모듈화하여 효율적으로 작성할 수 있다. 
 


2. 인터페이스와 추상 클래스

 
 
인터페이스와 추상 클래스는 Java에서 추상화를 구현하는 두 가지 방법으로, 추상화는 객체지향 프로그래밍에서 공통 특성이나 기능을 추출하여 일반화하는 과정을 말한다.
 
먼저, 인터페이스는 메소드의 시그니처와 상수만을 가진 추상화된 구조로, 구현되지 않은 메소드를 정의하고 이를 구현하는 클래스에서 해당 메소드를 구현해야 한다.

public interface Meowable {
    void meow();
}

 
이제, Cat 클래스가 Meowable 인터페이스를 구현하도록 해보자.
 

public class Cat implements Meowable {
    private String name;
    private int age;

    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public void meow() {
        System.out.println("Meow! My name is " + name + " and I'm " + age + " years old.");
    }
}


다음으로, 추상 클래스는 구현되지 않은 추상 메소드와 구현된 일반 메소드를 가질 수 있는 클래스로, abstract 키워드를 사용하여 정의하며, 추상 메소드에도 abstract 키워드를 붙여야 한다.

public abstract class AbstractAnimal {
    protected String name;
    protected int age;

    public AbstractAnimal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public abstract void makeSound();
}

 
이제 Cat 클래스가 AbstractAnimal 추상 클래스를 상속하도록 해보자.

public class Cat extends AbstractAnimal implements Meowable {
    public Cat(String name, int age) {
        super(name, age);
    }

    @Override
    public void meow() {
        makeSound();
    }

    @Override
    public void makeSound() {
        System.out.println("Meow! My name is " + name + " and I'm " + age + " years old.");
    }
}

 


3. 메소드와 매개변수

 
 
메소드는 클래스의 동작을 정의하는 코드 블록으로, 클래스 내에 선언되며, 객체가 수행할 수 있는 특정 작업을 정의한다.
 
메소드는 매개변수를 통해 외부로부터 데이터를 전달받을 수 있으며, 반환 타입, 메소드 이름, 매개변수 목록, 예외 목록 등으로 구성된다.
 
cat클래스에 greet 메소드를 추가해보자.

public class Cat extends AbstractAnimal implements Meowable {
    public Cat(String name, int age) {
        super(name, age);
    }

    @Override
    public void meow() {
        makeSound();
    }

    @Override
    public void makeSound() {
        System.out.println("Meow! My name is " + name + " and I'm " + age + " years old.");
    }

    public void greet(String ownerName) {
        System.out.println("Hello, " + ownerName + "! My name is " + name + ".");
    }
}

 
매개변수는 메소드가 호출될 때 외부로부터 전달받는 값을 저장하는 변수로, 메소드 선언에서 괄호 안에 정의되며, 쉼표로 구분된다.

Cat cat = new Cat("Horang2", 1);
cat.greet("Sanghyun"); // 출력: "Hello, Sanghyun! My name is Horang2."

 


4. 제네릭 (Generics)

 
 
제네릭은 타입을 매개변수화하여 클래스, 인터페이스, 메소드를 정의할 수 있게 하는 기능으로, 제네릭을 사용하면 다양한 타입의 객체를 처리할 수 있고, 타입 안정성을 보장할 수 있다.
 
제네릭 클래스는 클래스 선언에서 타입 매개변수를 사용하여 정의타입 매개변수는 꺽쇠 괄호(<, >)로 둘러싸인 대문자 알파벳으로 표현한다.

public class Animal<T> {
    private T animalType;

    public Animal(T animalType) {
        this.animalType = animalType;
    }

    public T getAnimalType() {
        return animalType;
    }

    public void setAnimalType(T animalType) {
        this.animalType = animalType;
    }
}

 
제네릭 클래스의 인스턴스를 생성할 때는 타입 매개변수에 구체적인 타입을 지정해야 한다.
 

Animal<Cat> catAnimal = new Animal<>(new Cat("Horang2", 1));
Animal<Dog> dogAnimal = new Animal<>(new Dog("Ming9", 3));

 
제네릭 메소드는 메소드 선언에서 타입 매개변수를 사용하여 정의되며, 클래스와 독립적으로 타입 매개변수를 가질 수 있다.

public class Animal<T> {
    private T animalType;

    public Animal(T animalType) {
        this.animalType = animalType;
    }

    public T getAnimalType() {
        return animalType;
    }

    public void setAnimalType(T animalType) {
        this.animalType = animalType;
    }
    
    public <U> void printAnimalType(U animalType) {
        System.out.println("Animal type: " + animalType.getClass().getSimpleName());
    }
}

 
제네릭 메소드를 사용하면 다양한 타입의 객체를 처리하는 동시에 타입 안정성을 보장할 수 있는 코드를 작성할 수 있다.
https://theworldaswillandidea.tistory.com/137

 

Generic, 프로그래밍 언어의 대명사(Pronouns)

제네릭(Generic)은 프로그래밍 언어에서 타입의 일반화를 가능하게 하는 고급 기능 중 하나로, 코드의 재사용성을 높이고 타입 안전성을 유지하기 위해 사용된다. 제네릭은 타입 매개 변수를 사용

theworldaswillandidea.tistory.com

 


5. 배열과 컬렉션

 
 
배열과 컬렉션은 자료구조 중 하나로, 여러 개의 동일한 타입의 데이터를 저장하고 관리하기 위한 구조다.
 
배열은 동일한 타입의 데이터를 연속적인 메모리에 저장할 수 있는 자료구조로, 배열의 크기는 고정되어 있으며, 크기를 변경할 수 없다.
 

int[] numbers = new int[5];
numbers[0] = 1;
numbers[1] = 2;
numbers[2] = 3;
numbers[3] = 4;
numbers[4] = 5;

Cat[] cats = new Cat[3];
cats[0] = new Cat("Horang2", 1);
cats[1] = new Cat("Horang3", 2);
cats[2] = new Cat("Horang4", 3);

 
컬렉션은 동적으로 크기를 변경할 수 있는 자료구조로, 자바에서 컬렉션은 java.util 패키지에서 제공하는 다양한 컬렉션 클래스를 사용할 수 있다.
 
대표적인 컬렉션 클래스로는 ArrayList, LinkedList, HashSet, LinkedHashSet, TreeSet, HashMap, LinkedHashMap, TreeMap 등이 있다.
 

import java.util.ArrayList;
import java.util.List;

List<Cat> catList = new ArrayList<>();
catList.add(new Cat("Horang2", 1));
catList.add(new Cat("Horang3", 2));
catList.add(new Cat("Horang4", 3));

for (Cat cat : catList) {
    System.out.println(cat.getName());
}

 


6. 객체지향 프로그래밍

 
이 섹션에서는 객체지향 프로그래밍의 핵심 개념인 캡슐화, 상속, 다형성에 대해서 알아보자.
 
먼저, 캡슐화는 객체의 상태(state)와 행동(behavior)을 하나로 묶고, 외부에서 접근할 수 있는 인터페이스만을 제공하는 것으로, 이를 통해 객체의 내부 데이터를 보호하고, 사용자가 객체를 쉽게 사용할 수 있도록 한다.

public class Cat {
    private String name;
    private int age;

    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

 
다음으로, 상속은 기존 클래스의 속성과 메소드를 새로운 클래스에 물려주는 것으로, 이를 통해 코드의 재사용성이 향상되고, 중복된 코드를 줄일 수 있다.

public class Animal {
    private String name;
    private int age;

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 기타 메소드
}

public class Cat extends Animal {
    public Cat(String name, int age) {
        super(name, age);
    }

    // 고양이 클래스의 특성과 메소드
}

 
마지막으로, 다형성은 하나의 인터페이스를 사용하여 다양한 타입의 객체를 다루는 것으로, 이를 통해 코드의 유연성이 높아지고, 쉽게 확장할 수 있다.

public class Animal {
    public void makeSound() {
        System.out.println("The animal makes a sound");
    }
}

public class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("The cat meows");
    }
}

public class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("The dog barks");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal myAnimal = new Cat();
        myAnimal.makeSound(); // Output: The cat meows

        myAnimal = new Dog();
        myAnimal.makeSound(); // Output: The dog barks
    }
}

 


7. 예외 처리

 
 
예외 처리는 프로그램 실행 중 발생할 수 있는 예외 상황에 대비하여, 프로그램이 적절하게 대응하도록 하는 기능으로, Java에서는 try-catch-finally 구문을 사용하여 예외 처리를 할 수 있다.

public class ExceptionExample {
    public static void main(String[] args) {
        try {
            int result = divide(10, 0);
            System.out.println("Result: " + result);
        } catch (ArithmeticException e) {
            System.out.println("An error occurred: " + e.getMessage());
        } finally {
            System.out.println("Finally block executed");
        }
    }

    public static int divide(int a, int b) {
        if (b == 0) {
            throw new ArithmeticException("Cannot divide by zero");
        }
        return a / b;
    }
}

 
이 예제에서는 divide() 메소드에서 0으로 나누려는 경우, ArithmeticException을 발생시키고 있고, 이 예외는 main() 메소드에서 try-catch 구문을 사용하여 처리하고 있다.

Java에서는 다양한 종류의 예외 클래스가 제공되며, 필요에 따라 사용자 정의 예외 클래스를 만들어 사용할 수도 있다.
 


8. Java I/O

 
 
Java I/O(Input/Output)는 파일, 네트워크, 콘솔 등 다양한 입출력 방식을 처리할 수 있는 기능을 제공하는데, Java의 java.io 패키지와 java.nio 패키지에는 입출력 관련 클래스들이 포함되어 있다.

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class FileReadingExample {
    public static void main(String[] args) {
        try {
            readFile("example.txt");
        } catch (IOException e) {
            System.err.println("Error reading file: " + e.getMessage());
        }
    }

    public static void readFile(String fileName) throws IOException {
        try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        }
    }
}

 
이 예제에서는 BufferedReader와 FileReader 클래스를 사용하여 파일의 내용을 한 줄씩 읽어들인다.
 
Java 7부터 도입된 try-with-resources 구문을 사용하면, 자원을 자동으로 해제할 수 있어 코드가 더 간결해진다.

Java I/O는 다양한 입출력 방식과 다양한 데이터 형식을 처리할 수 있는 풍부한 클래스와 인터페이스를 제공하며, 이를 활용하여 입출력 작업을 쉽게 처리할 수 있다.
 


9. Java의 동시성(Concurrency)

 
 
Java의 동시성은 멀티 스레딩을 사용하여 여러 작업을 동시에 실행하는 것을 의미하는데, 멀티 스레딩을 활용하면, 프로그램의 성능을 향상시키고 자원을 효율적으로 사용할 수 있다.
 
Java에서는 java.lang.Thread 클래스와 java.util.concurrent 패키지를 사용하여 동시성을 구현할 수 있다.
 

class MyThread extends Thread {
    private int id;

    public MyThread(int id) {
        this.id = id;
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("Thread " + id + ": " + i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class MultiThreadingExample {
    public static void main(String[] args) {
        MyThread thread1 = new MyThread(1);
        MyThread thread2 = new MyThread(2);

        thread1.start();
        thread2.start();
    }
}

 
이 예제에서는 MyThread라는 사용자 정의 스레드 클래스를 만들고, run() 메소드를 오버라이드하여 스레드에서 실행할 작업을 정의한다. main() 메소드에서 두 개의 스레드 객체를 생성하고, start() 메소드를 호출하여 스레드를 실행한다.

Java의 동시성 기능을 사용하면, 프로그램의 병렬 처리 성능을 향상시키고, 자원을 효율적으로 사용할 수 있으며, 또한, java.util.concurrent 패키지를 사용하면 더 고급 동시성 기능을 쉽게 구현할 수 있다.
 


10. Java의 네트워킹

 
 
Java는 네트워크 프로그래밍을 지원하기 위해 다양한 라이브러리와 클래스를 제공하는데, Java의 java.net 패키지를 사용하면, 소켓 프로그래밍, URL 처리, HTTP 통신 등의 네트워크 작업을 수행할 수 있다.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class SocketClientExample {
    public static void main(String[] args) {
        String host = "localhost";
        int port = 8000;

        try (Socket socket = new Socket(host, port);
             PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
             BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {

            out.println("Hello, server!");
            String response = in.readLine();
            System.out.println("Server response: " + response);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 
이 예제에서는 Socket 클래스를 사용하여 서버에 연결하고, PrintWriter와 BufferedReader를 사용하여 서버와 데이터를 주고받는다.

Java의 네트워킹 기능을 활용하면, 클라이언트-서버 구조의 애플리케이션을 구현하고, 인터넷을 통한 데이터 전송 및 통신을 수행할 수 있다.
 
이외에도 java.net 패키지는 다양한 네트워킹 관련 클래스를 제공하므로, 웹 서비스, FTP, 이메일 등의 프로토콜을 구현할 수 있다.
 


11. Java의 리플렉션(Reflection)

 
 
리플렉션은 Java 프로그램이 실행 중에 자기 자신의 구조를 검사하고, 조작할 수 있는 기능을 제공하는데, java.lang.reflect 패키지를 사용하여 클래스, 메소드, 필드, 생성자 등에 대한 메타데이터를 얻거나, 동적으로 객체를 생성하고 메소드를 호출할 수 있다.

import java.lang.reflect.Method;

public class ReflectionExample {
    public static void main(String[] args) {
        try {
            // 클래스 정보 얻기
            Class<?> catClass = Class.forName("com.example.Cat");

            // 인스턴스 생성
            Object catInstance = catClass.getDeclaredConstructor().newInstance();

            // 메소드 정보 얻기
            Method meowMethod = catClass.getDeclaredMethod("meow");

            // 메소드 호출
            meowMethod.invoke(catInstance);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 
이 예제에서는 Class.forName() 메소드를 사용하여 Cat 클래스의 정보를 얻는다. getDeclaredConstructor()와 newInstance() 메소드를 사용하여 Cat 인스턴스를 동적으로 생성하고, getDeclaredMethod() 메소드를 사용하여 meow 메소드의 정보를 얻는다. 마지막으로 invoke() 메소드를 사용하여 meow 메소드를 호출한다.

리플렉션은 유연한 코드 작성을 가능하게 하지만, 성능상의 이슈와 보안 문제 등의 단점이 있으므로 주의해서 사용해야 한다. 그럼에도 불구하고, 리플렉션은 프레임워크나 라이브러리 개발, 플러그인 시스템 구현 등의 다양한 상황에서 유용하게 사용된다.
 


12. Java의 디자인패턴

 
 
디자인 패턴은 소프트웨어 설계에서 자주 발생하는 문제들을 해결하기 위한 일종의 설계 템플릿으로, 디자인 패턴을 사용하면 코드의 유지 보수와 확장성이 향상되며, 개발 과정에서 발생할 수 있는 다양한 문제들을 미리 방지할 수 있게 된다.
 
Java에서도 많은 디자인 패턴이 사용되고 있는데, 여기서는 대표적인 디자인 패턴 Singleton, Factory, Observer 패턴 세가지만 알아보겠다.
 
먼저, Singleton 패턴부터 살펴보자.

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

 
클래스의 인스턴스를 단 하나만 생성하여 전역적으로 사용하기 위한 패턴으로, 주로 공유 자원을 관리하거나 서비스 제공 등의 목적으로 사용된다.
 
다음은, Factory 패턴을 살펴보자.

public interface Animal {
    void speak();
}

public class Dog implements Animal {
    public void speak() {
        System.out.println("Woof!");
    }
}

public class Cat implements Animal {
    public void speak() {
        System.out.println("Meow!");
    }
}

public class AnimalFactory {
    public static Animal createAnimal(String type) {
        if ("Dog".equals(type)) {
            return new Dog();
        } else if ("Cat".equals(type)) {
            return new Cat();
        } else {
            throw new IllegalArgumentException("Invalid animal type");
        }
    }
}

 
객체 생성 로직을 별도의 클래스로 분리하여, 객체 생성에 대한 책임을 분리하는 패턴으로, 클라이언트는 생성할 객체의 타입을 지정하고, 팩토리 클래스가 실제 객체 생성을 처리한다.
 
마지막으로, Observer 패턴을 살펴보자.

public interface Observer {
    void update(String message);
}

public class Subject {
    private List<Observer> observers = new ArrayList<>();

    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    public void notifyObservers(String message) {
        for (Observer observer : observers) {
            observer.update(message);
        }
    }
}

 
한 객체의 상태가 변경되었을 때, 그 객체와 연관된 다른 객체들에게 자동으로 알려주는 패턴으로, 이를 통해 객체 간의 결합도를 낮추고, 확장설을 높일 수 있다.
 
이 외에도 Strategy, Decorator, Facade 등 다양한 디자인 패턴이 있으니, 프로젝트의 요구 사항과 설계에 따라 적절한 디자인 패턴을 선택하여 사용하도록 하자.
 
 


13. 함수형 프로그래밍

 
 
Java는 1.8부터 함수형 프로그래밍 또한, 지원하기 시작하였는데, 함수형 프로그래밍은 순수 함수(pure functions)를 사용하여 프로그램의 상태 변경이나 가변 데이터를 피하는 프로그래밍 패러다임으로 최근 각광받고 있는 패러다임이다.
 
Java에서는 람다 표현식, 메소드 참조, 스트림 API, Optional 클래스 등을 통해 함수형 프로그래밍을 구현할 수 있다.
 
먼저, 람다 표현식 (Lambda Expression)은 익명 함수(Anonymous function)를 뜻하며, 간결한 방식으로 메소드를 정의하고 사용할 수 있다.

// 기존의 익명 클래스 방식
Runnable runnable1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("이리오너라!");
    }
};

// 람다 표현식
Runnable runnable2 = () -> System.out.println("이리오너라!");

 
다음으로, 메소드 참조 (Method Reference)란, 기존에 정의된 메소드를 람다 표현식처럼 간단하게 참조할 수 있는 방식으로, 정적 메소드 참조, 인스턴스 메소드 참조, 생성자 참조 등이 있다.

// 정적 메소드 참조
Function<String, Integer> parseInt = Integer::parseInt;

// 인스턴스 메소드 참조
String str = "까꿍";
Supplier<Integer> lengthSupplier = str::length;

// 생성자 참조
Supplier<List<String>> listSupplier = ArrayList<String>::new;

 
스트림 API (Stream API)는 데이터 소스를 처리하는데 사용되는 고수준 추상화된 API로, 데이터의 필터링, 변환, 집계 등의 연산을 지원한다.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 필터링
List<Integer> evenNumbers = numbers.stream()
    .filter(number -> number % 2 == 0)
    .collect(Collectors.toList());

// 매핑
List<Integer> squaredNumbers = numbers.stream()
    .map(number -> number * number)
    .collect(Collectors.toList());

// 집계
int sum = numbers.stream()
    .reduce(0, Integer::sum);

 
마지막으로, Optional 클래스는 값이 존재할 수도, 없을 수도 있는 상황을 표현하는 클래스로, Optional 클래스를 사용하면 null 포인터 예외를 효과적으로 방지할 수 있다.

// 값이 존재할 수도 있는 경우
Optional<String> optionalString = Optional.ofNullable("까꿍");

// 값이 있을 경우 실행
optionalString.ifPresent(System.out::println);

// 값이 없을 경우 기본값 사용
String value = optionalString.orElse("default value");

// 값이 없을 경우, Supplier로부터 값을 얻어옴
String valueFromSupplier = optionalString.orElseGet(() -> "default value from supplier");

// 값이 없을 경우 예외 발생
String valueOrException = optionalString.orElseThrow(() -> new RuntimeException("Value not found"));

 


끝으로,

 
여기까지 저와 함께 여러분들은 Java 프로그래밍에 대한 다양한 주제를 살펴보았습니다. Java를 이해하고 효과적으로 활용하기 위해서는 이러한 기본적인 개념들이 중요합니다. 앞서 다룬 주제들을 숙지하고 이를 실제 프로젝트에 적용해보며 Java 프로그래밍 능력을 향상시킬 수 있습니다.

다양한 주제를 다루며 기초부터 심화까지 살펴봤지만, 항상 더 배울 것이 많습니다. 지금까지 배운 내용을 바탕으로 추가적인 학습을 계속 진행하고, 다양한 프로젝트에서 Java를 활용하여 개발 능력을 키워나가시기를 바랍니다.

이 글이 여러분들의 Java 프로그래밍에 대한 이해를 높이는 데 도움이 되었기를 기대하며,

Thanks for watching, Have a nice day.

 

References

http://www.tcpschool.com/java/java_intro_basic
 

반응형
Comments