| 측면 | 인터페이스 | 추상 클래스 |
|---|---|---|
| 선언 방식 | interface 키워드로 선언 | abstract class 키워드로 선언 |
| 다중 상속 | 다중 상속 지원 (extends 키워드를 통해 여러 인터페이스 상속 가능) | 단일 상속만 지원 (extends 키워드를 통해 하나의 클래스만 상속 가능) |
| 메서드 구현 | 모든 메서드는 추상 메서드로 기본적으로 선언됨 | 추상 메서드, 일반 메서드, final, private 등 다양한 접근자 지원 |
| 인스턴스화 가능 여부 | 직접적으로 인스턴스화할 수 없음 (객체 생성 불가) | 직접적으로 인스턴스화 불가하지만, 하위 클래스로 상속받아 인스턴스화 가능 |
| 구현체에서의 구현 | 모든 메서드는 구현체에서 반드시 구현되어야 함 | 추상 메서드는 반드시 하위 클래스에서 오버라이드해야 하지만, 나머지 메서드는 선택적으로 오버라이드 가능 |
| 유틸리티 기능 | 메서드 시그니처를 정의하여 구현체 간에 일관성을 제공 | 내부적인 동작 로직이나 유틸리티 기능을 제공할 수 있음 |
추상클래스는 인스턴스 변수, 즉 필드를 가질 수 있지만, 인터페이스는 abstract, static, default 메서드와 static final 형태의 상수만을 가질 수 있습니다.
래퍼 클래스 패턴은 기존 클래스를 상속받거나 변경하지 않고, 새로운 기능을 추가하기 위한 디자인 패턴입니다. 이는 인터페이스를 이용하여 기능을 추가하고, 이를 상속받는 클래스가 해당 기능을 사용하게 하는 것을 의미합니다.
역방향 호환성(Backward Compatibility):
인터페이스의 확장성(Interface Evolution):
default 메서드와 같은 기능은 이전에 만들어진 인터페이스에 새로운 메서드를 추가하면서도 이미 구현된 클래스에 영향을 미치지 않고 새로운 기능을 도입할 수 있도록 합니다.유틸리티 메서드 구성(Utility Method Composition):
static 메서드를 인터페이스에 추가할 수 있게 되면, 해당 인터페이스를 사용하여 유틸리티 메서드를 그룹화할 수 있습니다. 이렇게 하면 특정 기능을 위한 정적 메서드들을 해당 인터페이스로 묶어 관리할 수 있습니다. Java에서 인터페이스에 정의된 static 메서드는 상속되거나 오버라이드될 수 없습니다. 또한 구현 클래스에서 반드시 호출할 필요는 없습니다.
인터페이스를 import하는 것과 implement하는 것은 Java에서 서로 다른 개념입니다.
인터페이스를 import하는 것:
import 문은 Java에서 다른 패키지에 있는 클래스나 인터페이스를 현재 파일에서 사용하기 위해 선언하는 것입니다.import를 사용하여 패키지에 있는 클래스나 인터페이스의 이름을 짧게 사용할 수 있게 해줍니다. 예를 들어, import java.util.List;를 선언하면 List라고만 써도 java.util 패키지 내의 List를 사용할 수 있습니다.인터페이스를 implement(구현)하는 것:
implements 키워드를 사용합니다. 이는 해당 클래스가 특정 인터페이스의 메서드들을 구현하겠다는 것을 의미합니다.중첩 인터페이스는 하나의 인터페이스 안에 또 다른 인터페이스가 정의된 것을 말합니다. 이는 코드의 구조를 조직화하고 관련 있는 부분을 묶어서 관리할 수 있게 해줍니다.
중첩 인터페이스는 외부 인터페이스의 멤버로서 동작하며, 외부 클래스나 인터페이스의 이름을 접근할 때는 OuterInterface.InnerInterface와 같이 점으로 구분하여 사용합니다.
중첩 인터페이스는 외부 클래스의 메소드들과 마찬가지로 구현되어 사용될 수 있습니다.
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class CoffeeConfiguration {
//... 생략
RedisSerializationContext.RedisSerializationContextBuilder<String, Coffee> builder =
RedisSerializationContext.newSerializationContext(new StringRedisSerializer());
RedisSerializationContext<String, Coffee> context = builder.value(serializer).build();
//... 중략
public interface RedisSerializationContext<K, V> {
static <K, V> RedisSerializationContextBuilder<K, V> newSerializationContext() {
return new DefaultRedisSerializationContext.DefaultRedisSerializationContextBuilder();
}
//... 중략
public interface SerializationPair<T> {
static <T> SerializationPair<T> fromSerializer(RedisSerializer<T> serializer) {
Assert.notNull(serializer, "RedisSerializer must not be null");
return new RedisSerializerToSerializationPairAdapter(serializer);
}
//... 중략
public interface RedisSerializationContextBuilder<K, V> {
//... 중략
RedisSerializationContextBuilder<K, V> value(SerializationPair<V> pair);
//... 중략
RedisSerializationContext<K, V> build();
}
}
클래스 CoffeeConfiguration에서는 RedisSerializationContext 인터페이스를 구현(implement)하지 않고, 해당 인터페이스의 구성 요소 중 일부를 사용하기 위해 import를 통해 가져왔습니다. 이 클래스에서는 RedisSerializationContextBuilder를 사용하기 위해 RedisSerializationContext를 통해 접근하여 builder 변수에 연결했습니다.
newSerializationContext() 메서드는 RedisSerializationContextBuilder의 외부에 선언된 static 메서드이므로 RedisSerializationContext에서 직접 호출할 수 있습니다.
그러나 value()와 build()는 RedisSerializationContextBuilder의 내부에 선언된 abstract 메서드이므로, 점 접근법(dot notation)을 사용하여 RedisSerializationContext.RedisSerializationContextBuilder<K, V> 타입을 가진 builder를 통해 접근해야 합니다.
Class<T> 타입의 객체입니다. 클래스의 리터럴은 클래스의 메타정보를 포함하고 있어서, 시그니처에 대한 정보를 담고 있는 것으로 볼 수 있습니다. 시그니처는 해당 클래스의 구조와 관련된 정보를 포함하는데, 클래스의 리터럴을 통해 이 정보에 접근할 수 있습니다.public class ClassLiteralExample {
public static void main(String[] args) {
Class<String> stringClass = String.class;
System.out.println("stringClass: " + stringClass); // stringClass 출력
System.out.println("String.class: " + String.class); // String.class 출력
}
}
String.class의 결과로 반환되는 것은 Class<String> 객체입니다. 이것은 String 클래스의 메타정보를 담고 있는 객체이며, 해당 클래스의 구조, 메서드, 필드 등에 대한 정보를 담고 있습니다. stringClass라는 변수가 이 객체를 가리키고 있습니다.
출력 결과는 아래와 같습니다.
stringClass: class java.lang.String
String.class: class java.lang.String
클래스 리터럴을 받는 경우에는 클래스의 메타데이터를 다루거나, 타입 안전성을 보장하기 위해 사용되고, 클래스 자체를 받는 경우에는 클래스의 객체를 다루거나, 다형성을 활용하는 등의 목적으로 활용될 수 있습니다.
public class FactoryExample {
public static <T> T createInstance(Class<T> clazz) throws IllegalAccessException, InstantiationException {
return clazz.newInstance(); // 클래스로부터 인스턴스를 생성하는 간단한 팩토리 메서드
}
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
// 클래스를 매개변수로 전달하여 객체 생성
String instance = FactoryExample.createInstance(String.class);
System.out.println(instance); // 출력: null (빈 문자열)
}
}
public class TypeUtilityExample {
static <T> void printClassName(Class<T> clazz) {
System.out.println("클래스 이름: " + clazz.getName());
}
public static void main(String[] args) {
// 클래스를 매개변수로 받아서 클래스 이름을 출력하는 유틸리티 예시
TypeUtilityExample.printClassName(String.class); // 출력: "클래스 이름: java.lang.String"
}
}
// 전략(스트래티지) 인터페이스
interface Strategy {
void execute();
}
// 전략을 구현한 클래스
class StrategyA implements Strategy {
public void execute() {
System.out.println("전략 A 수행");
}
}
class StrategyB implements Strategy {
public void execute() {
System.out.println("전략 B 수행");
}
}
// 전략을 사용하는 클래스
class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public void executeStrategy() {
strategy.execute();
}
}
public class StrategyPatternExample {
public static void main(String[] args) {
// 전략을 클래스로 받아 객체 생성
Context contextA = new Context(new StrategyA());
Context contextB = new Context(new StrategyB());
contextA.executeStrategy(); // 출력: "전략 A 수행"
contextB.executeStrategy(); // 출력: "전략 B 수행"
}
}
class Animal {
void makeSound() {
System.out.println("동물 소리");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("멍멍");
}
}
public class PolymorphismExample {
static void soundOfAnimal(Animal animal) {
animal.makeSound();
}
public static void main(String[] args) {
// 상위 클래스를 매개변수로 받아 하위 클래스의 인스턴스를 다룸
Animal animal1 = new Animal();
Animal animal2 = new Dog();
soundOfAnimal(animal1); // 출력: "동물 소리"
soundOfAnimal(animal2); // 출력: "멍멍"
}
}
public class Jackson2JsonRedisSerializer<T> implements RedisSerializer<T> {
public static final Charset DEFAULT_CHARSET;
private final JavaType javaType;
private ObjectMapper mapper;
private final JacksonObjectReader reader;
private final JacksonObjectWriter writer;
public Jackson2JsonRedisSerializer(Class<T> type) {
this(new ObjectMapper(), type);
}
//...중략
public Jackson2JsonRedisSerializer(ObjectMapper mapper, Class<T> type) {
Assert.notNull(mapper, "ObjectMapper must not be null");
Assert.notNull(type, "Java type must not be null");
this.javaType = this.getJavaType(type);
this.mapper = mapper;
this.reader = JacksonObjectReader.create();
this.writer = JacksonObjectWriter.create();
}
//...생략
this 키워드는 클래스의 인스턴스를 가리키는 것이 맞습니다. 그러나 생성자에서 this()를 호출할 때, 이는 같은 클래스 내의 다른 생성자를 의미합니다.
this(new ObjectMapper(), type)은 Jackson2JsonRedisSerializer(ObjectMapper mapper, Class<T> type)라는 두 개의 매개변수를 가진 다른 생성자를 호출하려는 의도를 보인 것입니다.
이렇듯 Jackson2JsonRedisSerializer(Class<T> type) 생성자가 호출될 때, 내부적으로 Jackson2JsonRedisSerializer(ObjectMapper mapper, Class<T> type) 생성자를 호출함으로써 코드 중복을 피하도록 합니다.
public class ObjectMapper extends ObjectCodec implements Versioned, Serializable {
//... 중략
public ObjectMapper() {
this((JsonFactory)null, (DefaultSerializerProvider)null, (DefaultDeserializationContext)null);
}
public ObjectMapper(JsonFactory jf) {
this(jf, (DefaultSerializerProvider)null, (DefaultDeserializationContext)null);
}
//... 생략
위 코드에서 보이는 두 개의 생성자는 모두 JsonFactory, DefaultSerializerProvider, DefaultDeserializationContext 타입을 가지는 세 개의 매개변수를 사용하는 ObjectMapper 생성자를 this()로 호출하고 있다.
final과 staticfinal 값 할당 시점final로 선언된 변수는 선언 시점이 아니라 생성자에서 값이 할당되어야 합니다. 그렇지 않으면 컴파일 오류가 발생합니다. 일반적으로 final 필드는 선언과 동시에 또는 생성자에서 값을 한 번만 할당해야 합니다. 그렇지 않으면 컴파일러가 오류를 내거나 경고를 발생시킬 수 있습니다.
하지만 제공하신 코드에서 _recipe와 _factory는 생성자에서 초기값으로 null을 할당받고 있습니다. 이는 유효한 방법입니다. 생성자 내부에서 final 필드를 한 번만 초기화할 수 있고, 이후에는 해당 값이 변하지 않아야 합니다. 코드에서 보듯이, 생성자에서 한 번만 초기화되고 그 이후에는 변경되지 않는다면 이는 final 필드의 규칙을 지키고 있는 것입니다.
public abstract class SourceOrigin {
protected static final BaseSource<Object> BASE_UNKNOWN_SOURCE = new Brewer();
protected final SourceRecipe _recipe;
protected final SourceFactory _factory;
protected BaseSource<Object> _unknownSource;
public SourceOrigin(){
this._unknownSource = BASE_UNKNOWN_SOURCE;
this._recipe = null;
this._factory = null;
}
}
static final로 선언된 필드인 BASE_UNKNOWN_SOURCE는 해당 클래스에 속한 상수입니다. 이것은 수정될 수 없는 불변의 상태를 갖습니다.
https://www.baeldung.com/java-reflection-class-fields
네, 많은 내용을 잘 설명하셨습니다.
부모 클래스의 non-final, non-static, non-private 속성(인스턴스 속성): 부모 클래스의 인스턴스 생성자를 통해 값을 할당하고, 자식 클래스에서 상속받아 사용하거나 필요에 따라 재정의할 수 있습니다.
부모 클래스의 non-final, non-private, static 속성(스태틱 속성): 인스턴스나 자식 클래스에서는 직접 접근할 수 없고, 부모 클래스 이름을 통해서 접근하여 값을 수정할 수 있습니다.
부모 클래스의 final 속성: final 속성은 변경할 수 없는 값으로 취급되며, 자식 클래스, 부모 클래스의 인스턴스, 또는 부모 클래스 이름을 통한 접근을 통해 값을 읽을 수 있지만, 수정은 불가능합니다. final인 속성이 생성자에서 초기화되지 않았다면, 생성자에서 this를 통해 값을 할당할 수 있습니다.
부모 클래스의 private 속성: private 속성은 클래스 내부에서만 접근 가능하며, 부모 클래스의 인스턴스, 자식 클래스, 또는 부모 클래스 이름을 통한 접근을 통해 접근하거나 수정할 수 없습니다.
public class Parent {
public Parent (){
System.out.println("나는 Parent 기본 생성자");
};
}
public class Child extends Parent{
Child(){
System.out.println("나는 Child 기본 생성자");
}
}
public static void main(String[] args) {
Child child = new Child();
}
출력 결과
나는 Parent 기본 생성자
나는 Child 기본 생성자
분리된 추상 클래스를 만드는 이유 중 하나는 개념적으로 관련 있는 일부 기능을 한 곳에 모으는 것입니다. 하나의 추상 클래스가 지나치게 커지거나, 여러 목적을 위한 다양한 초기화를 처리해야 하는 경우에 이를 분리하여 관리하는 것이 가독성과 유지보수성을 높일 수 있습니다.
public abstract class DefaultSerializerProvider extends SerializerProvider implements Serializable {
private static final long serialVersionUID = 1L;
protected transient Map<Object, WritableObjectId> _seenObjectIds;
protected transient ArrayList<ObjectIdGenerator<?>> _objectIdGenerators;
protected transient JsonGenerator _generator;
protected DefaultSerializerProvider() {
}
protected DefaultSerializerProvider(SerializerProvider src, SerializationConfig config, SerializerFactory f) {
super(src, config, f);
}
추상 클래스는 인스턴스화될 수 없기 때문에 자체적인 인스턴스 생성이 불가능합니다. 하지만 추상 클래스 역시 생성자를 가질 수 있습니다.
추상 클래스의 생성자는 protected로 선언되며, 외부에서 직접 호출할 수 없고 하위 클래스 내부에서 super()를 통해 호출됩니다. 하위 클래스에서 반드시 호출해야 하는 초기화 로직이 있을 때, 이러한 목적으로 추상 클래스 내부에 생성자를 구현할 수 있습니다.
SerializerProvider 클래스의 생성자인 protected SerializerProvider(SerializerProvider src, SerializationConfig config, SerializerFactory f)는 자식 클래스에서 사용 가능하도록 protected로 선언되어 있습니다. 자식 클래스에서 부모 클래스의 특정 상태를 초기화하거나 확장할 수 있습니다.
public abstract class SerializerProvider extends DatabindContext {
//...중략
protected SerializerProvider(SerializerProvider src, SerializationConfig config, SerializerFactory f) {
this._unknownTypeSerializer = DEFAULT_UNKNOWN_SERIALIZER;
this._nullValueSerializer = NullSerializer.instance;
this._nullKeySerializer = DEFAULT_NULL_KEY_SERIALIZER;
this._serializerFactory = f;
this._config = config;
this._serializerCache = src._serializerCache;
this._unknownTypeSerializer = src._unknownTypeSerializer;
this._keySerializer = src._keySerializer;
this._nullValueSerializer = src._nullValueSerializer;
this._nullKeySerializer = src._nullKeySerializer;
this._stdNullValueSerializer = this._nullValueSerializer == DEFAULT_NULL_KEY_SERIALIZER;
this._serializationView = config.getActiveView();
this._attributes = config.getAttributes();
this._knownSerializers = this._serializerCache.getReadOnlyLookupMap();
}
//... 생략
주어진 생성자 코드에서 매개변수로 받은 SerializerProvider src는 생성자의 매개변수로 전달된 것으로, 해당 생성자 내에서 SerializerProvider 타입의 객체를 새로운 인스턴스로 생성하는 것이 아니라, 이미 생성된 src 객체의 참조를 받고 있습니다.
this는 현재 객체의 참조를 나타내며, 생성자 내부에서 this는 현재 객체를 가리킵니다. SerializerProvider src는 외부에서 전달된 다른 객체를 가리키며, 이를 생성자 내부에서 사용할 수 있게 됩니다.
따라서 this와 src는 서로 다른 객체를 가리키며, 생성자 내부에서 각각의 객체를 참조하는데 사용됩니다. this는 현재 인스턴스를 나타내고, src는 생성자를 호출할 때 전달된 다른 SerializerProvider 객체를 가리킵니다.
https://blog.naver.com/29java/70187757119
class Car {
String color; // 생상
String gearType; // 변속기 종류 - auto(자동), manual(수동)
int door; // 문의 개수
Car() {
this("white", "auto", 4);
}
Car(Car c) { // 인스턴스의 복사를 위한 생성자
color = c.color;
gearType = c.gearType;
door = c.door;
}
Car(String color, String gearType, int door) {
this.color = color;
this.gearType = gearType;
this.door = door;
}
}
class CarTest3 {
public static void main(String[] args) {
Car c1 = new Car();
Car c2 = new Car(c1); // c1의 복사본 c2를 생성한다.
// 이하 생략
DefaultSerializerProvider.Impl
public abstract class DefaultSerializerProvider extends SerializerProvider implements Serializable {
//... 중략
protected DefaultSerializerProvider() {
}
protected DefaultSerializerProvider(SerializerProvider src, SerializationConfig config, SerializerFactory f) {
super(src, config, f);
}
protected DefaultSerializerProvider(DefaultSerializerProvider src) {
super(src);
}
public abstract DefaultSerializerProvider createInstance(SerializationConfig var1, SerializerFactory var2);
public DefaultSerializerProvider copy() {
throw new IllegalStateException("DefaultSerializerProvider sub-class not overriding copy()");
}
//...생략
//...생략
public static final class Impl extends DefaultSerializerProvider {
private static final long serialVersionUID = 1L;
public Impl() {
}
public Impl(Impl src) {
super(src);
}
protected Impl(SerializerProvider src, SerializationConfig config, SerializerFactory f) {
super(src, config, f);
}
public DefaultSerializerProvider copy() {
return (DefaultSerializerProvider)(this.getClass() != Impl.class ? super.copy() : new Impl(this));
}
public Impl createInstance(SerializationConfig config, SerializerFactory jsf) {
return new Impl(this, config, jsf);
}
}
}
public abstract class DefaultDeserializationContext extends DeserializationContext implements Serializable {
private static final long serialVersionUID = 1L;
protected transient LinkedHashMap<ObjectIdGenerator.IdKey, ReadableObjectId> _objectIds;
private List<ObjectIdResolver> _objectIdResolvers;
protected DefaultDeserializationContext(DeserializerFactory df, DeserializerCache cache) {
super(df, cache);
}
protected DefaultDeserializationContext(DefaultDeserializationContext src, DeserializationConfig config, JsonParser p, InjectableValues values) {
super(src, config, p, values);
}
//... 생략
package java.io;
public interface Serializable {
}
Serializable 인터페이스는 마킹 인터페이스입니다. 어떤 클래스가 직렬화될 수 있다는 것을 나타내는데, 메서드를 가지지 않고 단순히 해당 클래스가 직렬화 가능하다는 표시를 합니다. 이를 통해 객체를 파일에 저장하거나 네트워크로 전송할 수 있도록 합니다.
package com.fasterxml.jackson.core;
public interface Versioned {
Version version();
}
Versioned 인터페이스에서 version() 메서드를 통해 Version 클래스의 버전 정보를 반환하는 것은 느슨한 결합을 유지하기 위함일 수 있습니다. 이렇게 함으로써 Version 클래스의 변경이 Versioned 인터페이스의 구현에 영향을 덜 주면서도 버전 정보를 요구하는 부분에서 해당 기능을 사용할 수 있습니다. 또한, Version 클래스를 직접 사용하지 않고 Versioned 인터페이스를 통해 버전 정보에 접근하면, 다른 버전 정보를 제공하는 클래스로 손쉽게 교체할 수 있습니다.
package com.fasterxml.jackson.core;
import java.io.Serializable;
public class Version implements Comparable<Version>, Serializable {
//... 중략
public Version(int major, int minor, int patchLevel, String snapshotInfo, String groupId, String artifactId) {
this._majorVersion = major;
this._minorVersion = minor;
this._patchLevel = patchLevel;
this._snapshotInfo = snapshotInfo;
this._groupId = groupId == null ? "" : groupId;
this._artifactId = artifactId == null ? "" : artifactId;
}
//... 중략
public int compareTo(Version other) {
if (other == this) {
return 0;
} else {
int diff = this._groupId.compareTo(other._groupId);
if (diff == 0) {
diff = this._artifactId.compareTo(other._artifactId);
if (diff == 0) {
diff = this._majorVersion - other._majorVersion;
if (diff == 0) {
diff = this._minorVersion - other._minorVersion;
if (diff == 0) {
diff = this._patchLevel - other._patchLevel;
}
}
}
}
return diff;
//... 생략
package java.lang;
import java.util.*;
public interface Comparable<T> {
public int compareTo(T o);
}