简体   繁体   中英

Throwing custom exceptions in a functional interface

I am writing a program that consumes some data from a RabbitMQ API, via the java.function.Consumer functional interface. In my subclass that implements this interface, I have 3 potential exceptions:

public interface MyMessageHandler extends Consumer<MyMessage> {}
public class SpecificMessageHandler implements MyMessageHandler {


@Override
public void accept(IncomingMessage incomingMessage) {
  if(incomingMessage.getTime() < 1000) {
    throw new InvalidTimeException("message"); //Custom exception extends RuntimeException
  }
  if(incomingMessageAlreadyExists) {
    throw new DuplicateMessageException("message"); //Custom exception extends RuntimeException
  }
  try {
    ObjectMapper.reader.readValue(incomingMessage.getJson()) // Throws IOEXception
  } catch(IOException e) {
      throw new CustomIOException("message"); //Custom exceptin extends RuntimeException
    }
    // If all is well, carry on with rest of function
  }
}

I am having to take this route because you can't seem to throw regular exceptions in a functional interface, it has to be a runtime exception.

I am throwing the exceptions at this level of the stack, as I want to implement the actual handling behaviour higher up the stack, due to the fact I will have many message handlers that will be handled in the same way, therefore it's easier to implement that handling behaviour once higher up, rather than in every single handler class.

This functionally works, however feels like bad design. Is there a more elegant way to implement this? Note, I can't switch from a functional interface to something else, as i'm working with legacy code (not ideal, but that's how the world is some times!)

I am having to take this route because you can't seem to throw regular exceptions in a functional interface, it has to be a runtime exception.

This is incorrect. You can't throw checked exceptions in a java.function.Consumer specifically, because its signature

void accept(T t);

doesn't declare any.

The following functional interface is perfectly acceptable, and can throw certain checked exceptions.

public interface MyMessageHandler {
    void accept(IncomingMessage incomingMessage) throws IOException;
}

MyMessageHandler handler = (msg) -> {
    throw new IOException(); // Valid
};

If you wanted something more generic, you could declare something like

public interface ThrowableConsumer<T> {
    void accept(T t) throws Exception;
}

interface MyMessageHandler extends ThrowableConsumer<MyMessage> {}

I am throwing the exceptions at this level of the stack, as I want to implement the actual handling behaviour higher up the stack

You could use Lombok's @SneakyThrows to effectively convert a checked exception into an unchecked one. A hack, but it works.

class SpecificMessageHandler implements MyMessageHandler {
    @Override
    @SneakyThrows
    public void accept(IncomingMessage incomingMessage) {
        // Doesn't matter that it's checked and j.f.Consumer doesn't declare it
        throw new IOException(); 
    }
}

However I highly doubt that your employer will allow you to do hacks like this if you're not permitted to change an interface.

This functionally works, however feels like bad design

Why? There's a huge class of people who believe that checked exceptions were a mistake to begin with. There's nothing wrong with runtime exceptions.

Note, I can't switch from a functional interface to something else, as i'm working with legacy code

People throw this word around a lot. Java 8 came out 8 years ago. Your code can't be that old. Usually when people say " I can't change X ", what they mean is that they don't feel comfortable changing it, for whatever reason. If you're living in fear of your software, find a way to change that.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM