MyClass myObject = new MyClass();와 같이 사용한다.public class Car {
private String color;
public Car(String color) {
this.color = color;
}
public String getColor() {
return color;
}
}
public class Main {
public static void main(String[] args) {
Car myCar = new Car("Red");
System.out.println("My car's color is " + myCar.getColor());
}
}
이 코드에서 Car 클래스는 인스턴스 클래스이다. Main 클래스의 main 메서드에서 Car의 인스턴스를 생성하고, 그 인스턴스를 통해 색상을 얻는다.
클래스이름.메서드() 또는 클래스이름.변수 형태로 직접 접근할 수 있다.Math.sqrt(2);public class MathUtility {
public static double PI = 3.14159;
public static double square(double number) {
return number * number;
}
}
public class Main {
public static void main(String[] args) {
double radius = 5;
double area = MathUtility.PI * MathUtility.square(radius);
System.out.println("Area of the circle: " + area);
}
}
여기서 MathUtility 클래스는 정적 변수 PI와 정적 메서드 square를 가지고 있다. 이들은 인스턴스를 생성하지 않고도 MathUtility.PI와 MathUtility.square로 직접 접근할 수 있다.
public class Main {
public void displayMessage() {
// 로컬 클래스 Greeter 정의
class Greeter {
public void greet() {
System.out.println("Hello, this is a local class!");
}
}
// 로컬 클래스의 인스턴스 생성 및 메서드 호출
Greeter greeter = new Greeter();
greeter.greet();
}
public static void main(String[] args) {
Main main = new Main();
main.displayMessage(); // 메서드 호출
}
}
이 코드에서 Greeter 클래스는 displayMessage 메서드 내부에 정의되어 있으며, 이 클래스는 displayMessage 메서드의 지역적인 범위 내에서만 존재한다. 따라서 Greeter 클래스는 displayMessage 메서드 외부에서는 알려지지 않으며, 사용될 수 없다. main 메서드에서 Main 클래스의 인스턴스를 생성한 후 displayMessage 메서드를 호출하면, 이 메서드 내부에서 Greeter 클래스의 인스턴스가 생성되고, 그 인스턴스의 greet 메서드가 호출되어 메시지를 출력한다.
public class LocalClass {
int a = 100; // LocalClass의 인스턴스 변수
void print() {
System.out.println("a : " + a); // 인스턴스 변수 'a'의 값을 출력하는 메서드
}
void innerTest(int k) {
int b = 200; // innerTest 메서드의 지역변수
int c = k; // 메서드 파라미터 'k'의 값을 지역변수 'c'에 할당
// innerTest 메서드 내에 정의된 로컬 클래스
class inner {
void printData() {
System.out.println("외부 int a : " + a); // 외부 클래스의 인스턴스 변수 'a'에 접근
System.out.println("메소드 int b : " + b); // innerTest 메서드의 지역변수 'b'에 접근
System.out.println("메소드 int c : " + c); // 지역변수 'c'에 접근
System.out.println("메소드 파라미터 int k : " + k); // 메서드 파라미터 'k'에 접근
}
}
inner local = new inner(); // 로컬 클래스의 인스턴스 생성
local.printData(); // 로컬 클래스 인스턴스의 메서드 호출
}
}
public class LocalClassTest {
public static void main(String[] args) {
LocalClass localClass = new LocalClass(); // LocalClass의 인스턴스 생성
localClass.innerTest(900); // innerTest 메서드 호출, 인자로 900을 전달
}
}
외부 int a : 100
메소드 int b : 200
메소드 int c : 900
메소드 파라미터 int k : 900
LocalClass 클래스int a = 100;: LocalClass 클래스의 필드로, 클래스의 모든 메서드에서 접근 가능하다.print(): 이 메서드는 a 필드의 값을 출력한다.innerTest(int k): 이 메서드는 로컬 클래스 inner를 포함하고 있으며, 정수 인자 k를 받는다.innerTest 메서드 내의 로컬 클래스 inner:int b = 200;: innerTest 메서드의 지역 변수이다. 로컬 클래스 내부에서 접근 가능하다.int c = k;: 메서드 파라미터 k의 값을 지역 변수 c에 할당한다. 이 변수도 로컬 클래스 내부에서 접근 가능하다.inner: 이 로컬 클래스는 innerTest 메서드 내에서만 정의되고 사용된다.printData(): inner 클래스의 메서드로, LocalClass의 필드 a와 innerTest의 지역 변수 b, c, 그리고 파라미터 k의 값을 출력한다.LocalClassTest 클래스와 main 메서드LocalClass의 인스턴스를 생성한다.innerTest(900)을 호출한다. 이때 k 값으로 900이 전달된다.public class StaticClassTest {
int a = 10; // 인스턴스 변수, 정적 컨텍스트에서는 사용할 수 없음
private int b = 100; // private 인스턴스 변수, 마찬가지로 정적 컨텍스트에서 사용 불가
static int c = 200; // 정적 변수, 클래스 전체에서 공유됨
static int sum = 0; // 정적 변수, 메서드에서 계산된 합을 저장하기 위해 사용
static int sNum1 = 20; // 정적 변수
static int sNum2 = 30; // 정적 변수
// 정적 메서드, 정적 변수만을 이용하여 합을 계산
static void sum() {
sum = sNum1 + Inner.d;
}
// 정적 내부 클래스
static class Inner {
static int d = 1000; // 정적 변수, Inner 클래스와 StaticClassTest 클래스에서 접근 가능
int e = 2000; // 인스턴스 변수, Inner 클래스의 인스턴스에서만 사용 가능
// 인스턴스 메서드, 정적 변수와 인스턴스 변수에 접근
void printData() {
// 인스턴스 변수 a와 b는 정적 컨텍스트에서 사용할 수 없으므로 주석 처리됨
//System.out.println("외부 int a : " + a); // 정적 컨텍스트에서는 사용할 수 없음
//System.out.println("외부 int b : " + b); // 정적 컨텍스트에서는 사용할 수 없음
System.out.println("외부 static int c : " + c); // 정적 변수 c에 접근
System.out.println("내부 static int d : " + d); // 정적 변수 d에 접근
System.out.println("내부 int e : " + e); // 인스턴스 변수 e에 접근
}
}
public static void main(String[] args) {
int num = StaticClassTest.c; // StaticClassTest의 정적 변수 c에 접근
// StaticClassTest의 정적 내부 클래스 Inner의 정적 변수 d에 접근
int innerNum = StaticClassTest.Inner.d;
// StaticClassTest의 정적 내부 클래스 Inner의 인스턴스 생성
StaticClassTest.Inner inner = new StaticClassTest.Inner();
inner.printData(); // Inner 클래스의 인스턴스 메서드 호출
}
}
외부 static int c : 200
내부 static int d : 1000
내부 int e : 2000
이 코드에서 주요 포인트는 정적 내부 클래스 Inner와 그 정적 및 인스턴스 멤버들의 사용 방식이다. 정적 내부 클래스는 외부 클래스의 인스턴스와 독립적으로 존재하며, 그 정적 멤버들은 클래스 이름을 통해 직접 접근할 수 있다. 인스턴스 멤버들에 접근하기 위해서는 클래스의 인스턴스를 생성해야 한다. main 메서드에서 이러한 접근 방식을 보여주고 있다.
public class InstanceClassTest {
int a = 100; // 인스턴스 변수, 각 객체마다 고유한 값을 가짐
private int b = 10; // private 인스턴스 변수, 클래스 내부에서만 접근 가능
static int c = 200; // 정적 변수, 모든 객체가 공유
int sum = 0; // 또 다른 인스턴스 변수, sum() 메서드에서 사용될 예정
// 인스턴스 메서드, a와 b의 합을 계산하여 sum에 저장
void sum() {
sum = a + b;
}
// 인스턴스 내부 클래스
class Inner {
int in = 100; // Inner 클래스의 인스턴스 변수
// Inner 클래스의 인스턴스 메서드
void printData() {
System.out.println("Inner int in : " + in); // Inner 클래스의 인스턴스 변수 출력
// 외부 클래스(InstanceClassTest)의 인스턴스 변수에 접근
System.out.println("int a : " + a);
System.out.println("private int b : " + b);
System.out.println("static int c : " + c); // 외부 클래스의 정적 변수에도 접근 가능
sum(); // 외부 클래스의 인스턴스 메서드 호출
System.out.println("int sum : " + sum); // 외부 클래스의 인스턴스 변수에 접근
}
}
public static void main(String[] args) {
// InstanceClassTest의 인스턴스 생성
InstanceClassTest outer = new InstanceClassTest();
System.out.println("필드변수 a : " + outer.a);
System.out.println("--- 인스턴스 클래스 사용 ---");
// Inner 클래스(인스턴스 클래스)의 객체 생성
InstanceClassTest.Inner inner = new InstanceClassTest().new Inner();
inner.printData(); // Inner 클래스의 메서드 호출
}
}
필드변수 a : 100
--- 인스턴스 클래스 사용 ---
Inner int in : 100
int a : 100
private int b : 10
static int c : 200
int sum : 110
이 코드의 핵심은 InstanceClassTest 클래스 안에 정의된 Inner 클래스이다. Inner 클래스는 InstanceClassTest의 인스턴스 멤버들(a, b, sum, sum())에 직접 접근할 수 있다. 이는 인스턴스 내부 클래스의 특징 중 하나다. 또한, main 메서드에서 InstanceClassTest의 인스턴스를 생성하고, 이를 통해 Inner 클래스의 인스턴스를 생성하며, printData 메서드를 호출하여 Inner 클래스와 InstanceClassTest 클래스의 멤버들을 사용하는 과정을 보여준다.
// 인터페이스 선언
interface TestInter {
int DATA = 100; // 인터페이스의 상수
void printData(); // 추상 메서드
}
// TestInter 인터페이스를 구현하는 클래스
class TestInterImpl implements TestInter {
@Override
public void printData() {
System.out.println(">>> 구현한 메소드");
}
}
public class AnonymousClassTest {
public static void main(String[] args) {
// TestInterImpl 클래스의 인스턴스 생성 및 메서드 호출
TestInterImpl imp = new TestInterImpl();
imp.printData();
// 또 다른 TestInterImpl 인스턴스 생성 및 메서드 호출
TestInterImpl imp2 = new TestInterImpl();
imp2.printData();
// 익명 클래스를 사용하여 TestInter 인터페이스 구현
// 이 클래스는 이름이 없으며, 한 번만 사용될 목적으로 생성됨
TestInter test = new TestInter() {
@Override
public void printData() {
System.out.println("DATA : " + DATA); // 인터페이스의 상수에 접근
}
};
test.printData();
// 바로 호출하는 익명 클래스의 예시
// 익명 클래스를 생성하고 printData 메서드를 호출
(new TestInter() {
@Override
public void printData() {
System.out.println("DATA2 : " + DATA); // 인터페이스의 상수에 접근
}
}).printData();
// 다른 방식으로 구현된 익명 클래스
TestInter test2 = new TestInter() {
@Override
public void printData() {
System.out.println(">> 나는 익명클래스(이름없음)");
}
};
test2.printData(); // 메서드 호출
}
}
>>> 구현한 메소드
>>> 구현한 메소드
DATA : 100
DATA2 : 100
>> 나는 익명클래스(이름없음)
이 코드는 익명 클래스를 사용하여 인터페이스 TestInter를 다양한 방식으로 구현하는 것을 보여준다. 익명 클래스는 클래스를 별도로 선언하지 않고 바로 구현체를 제공할 수 있어, 일회성의 간단한 구현에 매우 유용하다. 예를 들어, 이벤트 핸들러, 스레드 생성 등의 상황에서 많이 사용된다. 이 코드에서는 TestInter 인터페이스의 printData 메서드를 여러 익명 클래스에서 각각 다른 방식으로 구현하여 사용하고 있다.
여러 종류의 객체를 저장할 수 있는 상자(Box) 클래스를 만들고 싶다고 해보자. 제네릭을 사용하지 않으면, 이 상자는 특정 타입의 객체만 저장할 수 있거나, Object 타입을 사용해야 한다. 하지만 Object 타입을 사용하면 타입 안정성이 떨어지고, 캐스팅(casting)이 필요하다. 제네릭을 사용하면 이런 문제를 해결할 수 있다.
제네릭을 사용하지 않은 경우
class Box {
private Object object;
public void set(Object object) { this.object = object; }
public Object get() { return object; }
}
// 사용 예시
Box box = new Box();
box.set("Hello World");
String str = (String) box.get(); // 캐스팅 필요
제네릭을 사용한 경우
class Box<T> {
private T object;
public void set(T object) { this.object = object; }
public T get() { return object; }
}
// 사용 예시
Box<String> stringBox = new Box<>();
stringBox.set("Hello World");
String str = stringBox.get(); // 캐스팅 불필요
제네릭을 사용한 Box<T> 클래스에서 T는 타입 파라미터다. 이를 통해 Box 클래스가 다양한 타입의 객체를 저장할 수 있게 된다.
사용할 때는 Box<String>처럼 구체적인 타입을 지정해준다. 그 결과, stringBox 객체는 String 타입의 객체만 저장하고 반환할 수 있게 되어, 타입 안정성이 높아지고, 코드도 더 깔끔해진다.
제네릭을 사용하면 이렇게 코드의 유연성과 안정성을 동시에 높일 수 있다.
타입 안정성(Type Safety): 컴파일 시점에 타입 체크를 할 수 있어서 실행 시간(runtime)에 발생할 수 있는 ClassCastException을 예방할 수 있다.
코드 재사용성(Reusable Code): 같은 코드를 다양한 타입에 대해 사용할 수 있어 코드 중복을 줄일 수 있다.
제네릭 클래스는 타입 파라미터를 가지는 클래스다.
class Box<T> {
private T t; // T 타입의 멤버 변수
public void set(T t) {
this.t = t; // T 타입의 값을 설정
}
public T get() {
return t; // T 타입의 값을 반환
}
}
여기서는 T를 타입 파라미터로 사용한 Box 클래스를 예로 들었다.
제네릭 메소드는 메소드 선언에 타입 파라미터를 포함하는 메소드다.
class Util {
// 제네릭 메소드
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element); // 배열의 각 요소 출력
}
}
}
// 사용 예시
String[] stringArray = {"Hello", "World"};
Util.<String>printArray(stringArray); // String 타입의 배열을 출력
여기서는 T를 타입 파라미터로 사용한 printArray 메소드를 예로 들었다.
제네릭 인터페이스는 인터페이스 선언에 타입 파라미터를 사용하는 인터페이스다.
interface Container<T> {
void set(T t); // T 타입의 값을 설정
T get(); // T 타입의 값을 반환
}
class StringContainer implements Container<String> {
private String string;
@Override
public void set(String string) {
this.string = string;
}
@Override
public String get() {
return string;
}
}
이 예는 T를 타입 파라미터로 사용한 Container 인터페이스로 예를 든 것이다..
와일드카드는 ? 기호를 사용하여 제네릭 타입의 범위를 지정해주는 데 사용한다. List<?>는 모든 타입의 List를 의미한다.
public static void printList(List<?> list) {
for (Object elem : list) {
System.out.println(elem); // 리스트의 각 요소 출력
}
}
// 사용 예시
List<String> stringList = Arrays.asList("Hello", "World");
printList(stringList); // 와일드카드를 사용한 메소드 호출
타입 파라미터는 제네릭 클래스나 메소드를 정의할 때 사용되는 '변수' 같은 것이다. 이를 통해 구체적인 타입을 지정하지 않고도 클래스나 메소드를 정의할 수 있다. 타입 파라미터는 보통 대문자 한 글자 (예: T, E, K, V 등)를 사용해 표현하며, 이는 각각 Type, Element, Key, Value를 의미한다.
제네릭 클래스나 메소드를 사용할 때, 이 타입 파라미터에 구체적인 타입을 지정해줌으로써, 다양한 타입에 대해 유연하게 코드를 사용할 수 있다.
예를 들어, Box<T> 클래스가 있다면, T는 타입 파라미터이고, Box<String>이나 Box<Integer>처럼 사용할 때 String, Integer가 구체적인 타입 인자(Type Argument)가 된다.
class Box<T> {
private T content;
public void set(T content) {
this.content = content;
}
public T get() {
return content;
}
}
// 사용 예시
Box<String> stringBox = new Box<>();
stringBox.set("Hello");
String value = stringBox.get(); // 캐스팅 없이 사용 가능
여기서 T는 타입 파라미터이며, Box<String>에서 String은 타입 인자라고 할 수 있다. 이런 방식으로 제네릭을 사용하면 하나의 코드로 다양한 타입을 처리할 수 있어 타입 안전성과 코드 재사용성을 높일 수 있다.
class BoxT<T> {
T obj; // T 타입의 객체를 저장
T getObj() {
return obj; // T 타입의 객체를 반환
}
public void setObj(T obj) {
this.obj = obj; // T 타입의 객체를 설정
}
}
public class GenericWildcardExam {
public static void main(String[] args) {
// String 타입을 사용하는 BoxT 인스턴스 생성
BoxT<String> box1 = new BoxT<String>();
box1.setObj("문자열");
// Integer 타입을 사용하는 BoxT 인스턴스 생성
BoxT<Integer> box2 = new BoxT<Integer>();
box2.setObj(100);
System.out.println("-----------------");
// 와일드카드 <?> 사용: 모든 타입의 BoxT를 참조할 수 있음
BoxT<?> box3 = new BoxT<String>();
box3 = new BoxT<Integer>();
box3 = new BoxT<StringBuilder>();
// 제한된 와일드카드 <? extends Number> 사용: Number 타입 또는 그 하위 타입의 BoxT를 참조
BoxT<? extends Number> box10 = null;
box10 = new BoxT<Number>();
box10 = new BoxT<Integer>();
box10 = new BoxT<Double>();
// box10 = new BoxT<Character>(); // 컴파일 오류: Character는 Number의 하위 클래스가 아님
// 상위 타입 와일드카드 <? super Number> 사용: Number 타입 또는 그 상위 타입의 BoxT를 참조
BoxT<? super Number> box20 = null;
box20 = new BoxT<Number>();
box20 = new BoxT<Object>();
// box20 = new BoxT<Integer>(); // 컴파일 오류: Integer는 Number의 상위 클래스가 아님
}
}
package com.mystudy.generic;
// 제네릭을 사용하지 않는 Box 클래스
class Box {
Object obj; // Object 타입으로 모든 객체를 저장할 수 있음
Object getObj() {
return obj; // 객체 반환
}
public void setObj(Object obj) {
this.obj = obj; // 객체 저장
}
}
// 제네릭을 사용하는 BoxG 클래스
class BoxG<T> {
T obj; // 제네릭 타입 T로 선언된 객체
T getObj() {
return obj; // T 타입의 객체 반환
}
public void setObj(T obj) {
this.obj = obj; // T 타입의 객체 저장
}
}
public class GenericExam {
public static void main(String[] args) {
// 제네릭을 사용하지 않을 때
Box box = new Box();
box.setObj("문자열 String"); // String 객체 저장
box.setObj(100); // Integer 객체 저장
box.setObj(new Box()); // Box 객체 저장
box.setObj(new Integer(1000)); // Integer 객체 저장
System.out.println(box.getObj());
Integer integer = (Integer)box.getObj(); // 캐스팅 필요
System.out.println(integer.MAX_VALUE);
System.out.println("===================");
// 제네릭 사용 예시
BoxG<String> boxg = new BoxG<String>(); // String 타입으로 제네릭 설정
boxg.setObj("문자열 String"); // String 객체 저장
boxg.setObj("Hello Java"); // String 객체 저장
boxg.setObj("100000"); // String 객체 저장
// boxg.setObj(333); // 컴파일 오류: int는 String 타입이 아니므로 저장할 수 없음
System.out.println(boxg.getObj());
String obj = boxg.getObj(); // 캐스팅 필요 없음
}
}