0

I have this interface

public interface CopyData {
    <T extends SpecificRecordBase> void copy(T data);
}

I have class implementing this interface

public class CopyDataImpl implements CopyData {
    private CopyDataProvider copyDataProvider;

    @Override
    public <T extends SpecificRecordBase> void copy(T data) {
        try {
            Copier copier = copyDataProvider.copyFor(data);
            if(copier == null) {
                return;
            }
            ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
            Encoder encoder = EncoderFactory.get().jsonEncoder(data.getSchema(), byteStream);
            data.customEncode(encoder);
            encoder.flush();
            copier.copy(data);
        } catch (Exception e) {
            log.error("Error occurred storing the data {}", e.getMessage());
        }
    }
}

I am using Spring and loading their beans respectively.

@Configuration
public class CopyDataDynamicConfig {

    @Bean
    public CopyDataProvider copyDataProvider() {
        return new CopyDataProviderImpl();
    }

    @Bean
    public CopyData copyData(CopyDataProvider copyDataProvider) {
        return new CopyDataImpl(copyDataProvider);
    }

}

I want to make my interface a functional interface and use it. Is it feasible or an overkill in this case?

Can someone please share how I can achieve that?

EDIT 1

I am aware that I can make my interface functional interface easily as it has only method and can add @FunctionalInterface annotation.

My question was I want to use functional interface directly and not using dedicated implementation class.

SRJ
  • 2,092
  • 3
  • 17
  • 36
  • 4
    Given it has a single method, it already is a functional interface (you can annotate it with `@FunctionalInterface` to have the compiler return an error when you add another method). What exactly is your question? – Mark Rotteveel Aug 26 '23 at 14:29
  • 3
    If you did that, the body of `CopyDataImpl` would become the body of a lambda in `CopyDataDynamicConfig#copyData`. This is still seemingly only a single use case for this interface, however. I'm struggling to find the benefit of this abstraction. You could just return a `Consumer` and be rid of the `CopyData` interface. – Rogue Aug 26 '23 at 14:29
  • 1
    You must declare as `public interface CopyData { void copy(T data); }`, otherwise you cannot use a lambda expression – knittl Aug 26 '23 at 14:33
  • 4
    Why isn't the signature just `void copy(SpecificRecordBase data);`? – Olivier Aug 26 '23 at 14:39
  • @Mark Rotteveel Thanks Yes I am aware I can mark it as functional interface but i am strugging to use it in my Configuration declaration :) – SRJ Aug 26 '23 at 14:41
  • @Rogue Yes eventually I will use Consumer interface as that will serve the purpose. – SRJ Aug 26 '23 at 14:42
  • Your question makes no sense. Your interface is already a functional interface by virtue of the fact that it has a single abstract method. You vaguely say you want to use it "seamlessly" - what exactly does this mean? In your comments you say you want to use it with your "Configuration declaration" - what does this refer to, as there is no mention of it in your question or code? Can you rephrase your question so it's clear what you're actually asking. – jon hanson Aug 26 '23 at 14:46
  • @knittl Thanks that helped, how can I use instance variable `copyDataProvider` then in lambda declaration ? – SRJ Aug 26 '23 at 14:46
  • @jonhanson I want to use functional interface directly without creating an implementation class. I would like to see how can I achieve the same. – SRJ Aug 26 '23 at 14:48
  • 1
    Functional interfaces are for short, quick functions that don't *need* a full class. You have a class that carries some state (`copyDataProvider`) whose method is 13 lines long and does some custom error handling. I would definitely *not* use the lambda syntax for that. It's far too complicated, *especially* the stateful part. – Silvio Mayolo Aug 26 '23 at 14:52
  • Thank you Slaw and Silvio Mayolo for understanding my intent of the question. It really helped. – SRJ Aug 26 '23 at 14:54
  • 2
    "_I am aware that I can make my interface functional interface easily as it has only method and can add @FunctionalInterface annotation_" – Just want to make sure this is clear. An interface can be a functional interface **even if that annotation is not present**. The only requirement is that the type is an interface, and it has exactly one abstract method (i.e., a SAM [Single Abstract Method] type). The `@FunctionalInterface` annotation only serves as documentation of intent and forcers the compiler to validate the type is a functional interface. It's similar to `@Override` in that regard. – Slaw Aug 26 '23 at 15:06
  • 3
    @Slaw to be more precise, an interface that has exactly one abstract method that doesn’t match a `public` method in `java.lang.Object`. – Holger Aug 28 '23 at 08:32
  • @SilvioMayolo Yes I realised that it is best practice to use expressions over statements when employing functional programming and my method does mutation along with multiple statements. Thanks. – SRJ Aug 30 '23 at 09:24

1 Answers1

3

Let me try to rephrase the question:

My question was I want to use functional interface directly and not using dedicated implementation class.

wants to ask: "how can I use a lambda expression instead of implementing the interface with a class?".

So basically, how to turn this:

public class CopyDataImpl implements CopyData {
    private CopyDataProvider copyDataProvider;

    @Override
    public <T extends SpecificRecordBase> void copy(T data) {
        try {
            Copier copier = copyDataProvider.copyFor(data);
            if(copier == null) {
                return;
            }
            ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
            Encoder encoder = EncoderFactory.get().jsonEncoder(data.getSchema(), byteStream);
            data.customEncode(encoder);
            encoder.flush();
            copier.copy(data);
        } catch (Exception e) {
            log.error("Error occurred storing the data {}", e.getMessage());
        }
    }
}

@Configuration
public class CopyDataDynamicConfig {
    @Bean
    public CopyDataProvider copyDataProvider() {
        return new CopyDataProviderImpl();
    }

    @Bean
    public CopyData copyData(CopyDataProvider copyDataProvider) {
        return new CopyDataImpl(copyDataProvider);
    }
}

into that:

@Configuration
public class CopyDataDynamicConfig {
    @Bean
    public CopyDataProvider copyDataProvider() {
        return new CopyDataProviderImpl();
    }

    @Bean
    public <T> CopyData<T> copyData(CopyDataProvider copyDataProvider) {
        return data -> {
            try {
                Copier copier = copyDataProvider.copyFor(data);
                if(copier == null) {
                    return;
                }
                ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
                Encoder encoder = EncoderFactory.get().jsonEncoder(data.getSchema(), byteStream);
                data.customEncode(encoder);
                encoder.flush();
                copier.copy(data);
            } catch (Exception e) {
                log.error("Error occurred storing the data {}", e.getMessage());
            }
        };
    }
}

And the answer is that you must make your method non-generic (the interface can still be generic):

public interface CopyData<T extends SpecificRecordBase> {
    void copy(T data);
}

This is equivalent to java.util.function.Consumer:

public interface Consumer<T> {
  void accept(T t);
}

In your case, it's probably sufficient to define interface CopyData { void copy(SpecificRecordBase data); } without any generics.

Note that returning a lambda as a bean could break in weird ways. IIRC I ran into this problem once.

Be aware that you don't need to create a named class; you can easily create an anonymous class:

@FunctionalInterface
public interface CopyData {
    void copy(SpecificRecordBase data);
}

@Configuration
public class CopyDataDynamicConfig {
    @Bean
    public CopyDataProvider copyDataProvider() {
        return new CopyDataProviderImpl();
    }

    @Bean
    public CopyData copyData(CopyDataProvider copyDataProvider) {
        return new CopyData() {
            @Override
            void copy(SpecificRecordBase data) {
                try {
                    Copier copier = copyDataProvider.copyFor(data);
                    if(copier == null) {
                        return;
                    }
                    ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
                    Encoder encoder = EncoderFactory.get().jsonEncoder(data.getSchema(), byteStream);
                    data.customEncode(encoder);
                    encoder.flush();
                    copier.copy(data);
                } catch (Exception e) {
                    log.error("Error occurred storing the data {}", e.getMessage());
                }
            }
        };
    }
}
knittl
  • 246,190
  • 53
  • 318
  • 364
  • 2
    For the general pattern, see also [Generic Interface vs Generic Methods, and when to use one](https://stackoverflow.com/a/30575685/2711488) – Holger Aug 28 '23 at 08:28