이번 포스트에서는 자바의 핵심 구성 요소와 간단한 입출력, 그리고 상속을 다루는 예제 세 가지를 살펴봅니다. 각 예제를 실제 코드와 함께 설명하고, 자바 개발 환경에서 자주 쓰이는 단축키나 메소드 구조도 짚어봅니다.
들어가기 전에 주의할 점으로, Java 언어는 명령어의 대소문자를 구분하지 않으면 실행되지 않습니다. 반드시 확인하고 넘어갑시다.
1. 자바의 특징과 명령어 구성
다음 Hello.java 예제는 콘솔 기반 입출력의 기초를 보여줍니다.
각 클래스와 메소드의 역할을 주석으로 설명하며, System.out, System.err를 이용한 다양한 출력 방법을 다룹니다.
public class Hello {
public static void main(String[] args) {
// 단축기능 설명
/*
* System : 클래스명
* out : 객체명
* println : 메소드명 (print + newline)
* print : 출력만 (줄 바꿈 없음)
* printf : 포맷에 맞춰 출력
*/
// CUI or TUI (Console/Text User Interface)
System.out.print("안녕하세요");
System.out.println("반갑습니다");
System.out.println("안녕하십니다");
System.out.println("프린트라인 / syso + Ctrl+Space");
System.err.println("에러 내용 출력 / syser + Ctrl+Space");
// printf 사용 예제
String name = "홍길동"; // %s
int age = 30; // %d
double height = 180.5; // %f (%.1f 소수점 첫째 자리)
System.out.printf(
"이름 : %s\n나이 : %d(살)\n키 : %.1f\n",
name, age, height
);
// 원시적인 출력 (문자 코드)
System.out.write(75); // ASCII 코드 75 -> 'K'
System.out.flush(); // 버퍼 강제 비움
}
}
각 주요 메소드에 대해 정리하면 다음과 같습니다.
- System.out.print(...) : 출력 후 줄 바꿈 없이 이어서 작성
- System.out.println(...): 출력 후 자동 줄 바꿈
- System.err.println(...): 표준 에러 스트림으로 출력
- System.out.printf(...) : 포맷 문자열에 맞춰 출력
- System.out.write(int), flush() : 바이트 단위 직접 출력
2. Scanner를 이용한 입력 처리
Scanner 클래스는 콘솔에서 키보드 입력을 받을 때 가장 많이 사용하는 도구입니다.
이 예제에서는 한 글자를 읽어 화면에 다시 출력하고, Scanner를 닫는 방법을 보여줍니다.
package mymain;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner kbd = new Scanner(System.in); // 입력용 Scanner 생성
char xC = kbd.next().charAt(0); // 문자열 첫 글자를 추출
System.out.println(xC); // 읽어온 글자 출력
kbd.close(); // 리소스 해제
}
}
- new Scanner(System.in) : 키보드 입력 스트림 연결
- next() : 공백으로 구분된 다음 토큰(문자열) 읽기
- charAt(0) : 읽은 문자열의 첫 번째 문자 반환
- close() : Scanner와 연결된 스트림 닫기
3. Superclass와 Subclass의 상속 구조
아래 예제는 상속(extends)과 생성자 체이닝(super)을 이용해 부모 클래스를 확장하는 방법을 보여줍니다.
toString() 메소드를 오버라이드하여 객체 정보를 문자열로 표현하고, 다형성을 활용해 출력합니다.
package mymain;
public class SubClass extends SuperClass {
int age;
public SubClass(String name, int age) {
super(name); // 부모 생성자 호출
this.age = age;
}
@Override
public String toString() {
return name + ", " + age;
}
public static void main(String[] args) {
SuperClass s1 = new SubClass("이순신", 20);
SuperClass s2 = new SuperClass("홍길동");
System.out.println(s1 + " : " + s2);
}
}
class SuperClass {
String name;
public SuperClass(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
3번 예제 실행 결과
이순신, 20 : 홍길동
3.1. 객체 생성과 상속 관계
- SuperClass 타입의 참조 변수에 SubClass 인스턴스를 할당합니다.
- SubClass는 SuperClass를 상속받았기 때문에 가능
- SuperClass 변수로 SubClass 객체를 가리키는 다형성(polymorphism)이 적용됨
- SuperClass s1 = new SubClass("이순신", 20);
- SuperClass 인스턴스 생성
- SuperClass s2 = new SuperClass("홍길동");
3.2. 생성자 호출 흐름
- new SubClass("이순신", 20) 실행 시
- SubClass 생성자 진입
- super(name) 호출로 SuperClass의 생성자에서 this.name = name
- SubClass 생성자 내부에서 this.age = age
- 결과적으로 s1 객체에는
- name 필드에 "이순신"
- age 필드에 20
값이 저장됩니다.
3.3. toString() 오버라이드 및 동적 바인딩
- SuperClass 정의
public class SuperClass { String name; public SuperClass(String name) { this.name = name; } @Override public String toString() { return name; } }
- SubClass 정의
public class SubClass extends SuperClass { int age; public SubClass(String name, int age) { super(name); this.age = age; } @Override public String toString() { return name + ", " + age; } }
- 다형성에 따른 메소드 선택
- s1은 SuperClass 타입이지만 실제 객체는 SubClass입니다.
- toString() 호출 시 컴파일 타임이 아니라 런타임에 실제 클래스(SubClass)의 오버라이드된 메소드가 실행됩니다.
- 이를 동적 바인딩(dynamic binding) 혹은 런타임 다형성이라 부릅니다.
3.4. 문자열 연결 시 toString() 호출
System.out.println(s1 + " : " + s2);
- 자바에서 String + 객체 연산은 내부적으로 해당 객체의 toString()을 호출합니다.
- 위 문장은 다음과 동일하게 동작합니다.
String result = s1.toString() + " : " + s2.toString(); System.out.println(result);
- 따라서 값은 아래와 같은 순서로 출력됩니다.
- s1.toString() → "이순신, 20"
- s2.toString() → "홍길동"
- 최종 문자열 → "이순신, 20 : 홍길동"
오버라이드에 대해서는 추후 더 자세히 다룰 예정입니다.