측면 | 인터페이스 | 추상 클래스 |
---|---|---|
선언 방식 | 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
과 static
final
값 할당 시점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);
}