If you don't know how to use the Consumer interface, come to the company and I'll tell you face to face

Posted by drkstr on Tue, 14 Dec 2021 04:43:05 +0100

background

Yes, I'm still working on the XXXX project and interfacing with a third party. The difference is that this time I deal with my own business logic.

During the development process, I encountered such a problem:

Table structure: A main table A and an associated table B. table A stores the record status of table B.

Scenario: Step 1: create main table data and insert table A; The second step calls the third-party interface to insert table B and update the status of table A. At this time, we should all think that we need to do A good job in the idempotency of data in the second step. In this case, the following situations will exist:

1, There is no data associated with table A in table B. at this time, you need to call the third-party interface to insert table B and update the status of table A;

2, There is data associated with table A in table B;

  1. The status in table A is in process: the word in process is returned directly;
  2. The status in table A is processing success: the word success is returned directly;
  3. The status in table A is processing failure: at this time, it is necessary to call the third-party interface to update table B and update the status of table A;

code implementation

First, I write pseudo code like this

B b = this.baseMapper.selectOne(queryWrapper);
if (b != null) {
	String status = b.getStatus();
	if (Objects.equals(Constants.STATUS_ING, status)){
		return "Processing";
	} else if (Objects.equals(Constants.STATUS_SUCCESS, status)){
		return "Processing succeeded";
	}
	//Failed operation
	//Request the third-party interface and parse the response results
	......
	if (ReturnInfoEnum.SUCCESS.getCode().equals(parse.getCode())) {
        ......
        //Update table B operation
		bb.setStatus(Constants.STATUS_ING);
		mapper.updateById(bb);

		//Update the status of table A
		a.setStatus(Constants.STATUS_ING);
		aMapper.updateById(a);
	}
	
} else {
	//Request the third-party interface and parse the response results
	......
	if (ReturnInfoEnum.SUCCESS.getCode().equals(parse.getCode())) {
        ......
        //Insert table B operation
		bb.setStatus(Constants.STATUS_ING);
		mapper.insert(bb);

		//Update the status of table A
		a.setStatus(Constants.STATUS_ING);
		aMapper.updateById(a);
	}
}

I don't know whether the careful little partner finds that the B table record with the status of "failed" and the B table do not exist. Except for the operation of inserting the B table or updating the B table, the other operations are the same.

If we want to extract the public parts and find them scattered, it's better not to extract them, but there are a lot of duplicate codes without extracting the code, which is not in line with my style. So I put my hand to the Consumer interface.

Pseudo code after change

B b = this.baseMapper.selectOne(queryWrapper);
if (b != null) {
	String status = b.getStatus();
	if (Objects.equals(Constants.STATUS_ING, status)){
		return "Processing";
	} else if (Objects.equals(Constants.STATUS_SUCCESS, status)){
		return "Processing succeeded";
	}
	//Failed operation
	getResponse(dto, response, s -> mapper.updateById(s));
} else {
	getResponse(dto, response, s -> mapper.updateById(s));
}

public void getResponse(DTO dto, Response response, Consumer<B> consumer){
	//Request the third-party interface and parse the response results
	......
	if (ReturnInfoEnum.SUCCESS.getCode().equals(parse.getCode())) {
        ......
		bb.setStatus(Constants.STATUS_ING);
	
		consumer.accept(bb);

		//Update the status of table A
		a.setStatus(Constants.STATUS_ING);
		aMapper.updateById(a);
	}
}

Seeing this, if everyone has understood it, Congratulations, indicating that you have mastered the use of Consumer. If you still have some doubts, let's move on. We'll introduce four common functional interfaces.

Functional interface

What is a functional interface? A functional interface is an interface that has only one abstract method (except Object methods), but can have multiple non abstract methods. It represents a single function in logic.

@FunctionalInterface

@The functional interface annotation is used to indicate that the interface is functional. It helps early detection of inappropriate method declarations in functional interfaces or interface inheritance.

If the interface is annotated with this annotation, but it is not a functional interface, an error will be reported during compilation.

Consumer

We generally call it "Consumer", which means an operation that accepts a single input parameter but does not return a result. Unlike other functional interfaces, Consumer expects to operate through side effects.

What are the side effects? Let's talk about the side effects I understand. The side effects are actually whether a function will modify resources outside its scope. If there are side effects, it is called there are side effects. On the contrary, there are no side effects. For example, modify global variables and objects referenced by input parameters.

@FunctionalInterface
public interface Consumer<T> {

    /**
     *  Perform this operation on the given parameter.
     */
    void accept(T t);

    /**
     * 
     *  Return a combined Consumer, perform this operation in turn, and then perform the after operation.
     *  If any operation throws an exception, it will be forwarded to the caller of the composite operation.
     *  If this operation throws an exception, the after operation will not be performed.
     */
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

As in the scenario in our case, we only need to pass the logical method to be executed into getResponse() as a parameter, and then execute the accept() method in this method for consumption. If we don't understand it, we can convert it to the calling method of anonymous inner class.

 getResponse(dto, response, new Consumer<B>() {
    @Override
    public void accept(B bb) {
      mapper.insert(bb);
    }
});

When the accept() method is called, the method of the anonymous inner class will be called, that is, the logical method we passed in getResponse().

Supplier

We generally call it "producer". It has no parameter input, but can return results. It is the provider of results.

@FunctionalInterface
public interface Supplier<T> {

    /**
     *  Get a result
     */
    T get();
}

Take a simple example:

Optional<Double> optional = Optional.empty();
optional.orElseGet(()->Math.random() );

//orElseGet method source code, which uses the get method
public T orElseGet(Supplier<? extends T> other) {   
    return value != null ? value : other.get();
}

Function

I call it "translator", which means a function that receives a parameter and returns a result after processing.

@FunctionalInterface
public interface Function<T, R> {

    /**
     *  Pass in the parameter of type T, and return the result of type R after the calculation of the function expression
     */
    R apply(T t);

    /**
     * Returns a composite function. First apply the parameter to the before function, and then apply the result to the current function to return the final result.
     * If the evaluation of any function throws an exception, it is forwarded to the caller of the composite function.
     */
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    /**
     * Returns a composite function. First apply the parameter to the current function, and then apply the result to the after function to return the final result.
     * If the evaluation of any function throws an exception, it is forwarded to the caller of the composite function. 
     */
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    /**
     *  Returns a function that always returns its input parameters.
     */
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

We have many applications in lambda expressions, so let's briefly demonstrate:

@Data
@AllArgsConstructor
public class Teacher {
    private String name;
    private int age;
}

public class TeacherTest {
    public static void main(String[] args) {
       List<Teacher> list = Arrays.asList(
            new Teacher("Zhang San",25),
            new Teacher("Li Si",28),
            new Teacher("Wang Wu",18));
      List<String> collect = list.stream().map(item -> item.getName()).collect(Collectors.toList());
      System.out.println(collect);
    }
}

The parameter received by map is the Function type, item is the incoming parameter, and item Getname() is the result of the return processing, and the final output result is

[Zhang San, Li Si, Wang Wu]

Predicate

We call it the "judge", which returns the boolean result by receiving the parameter T.

@FunctionalInterface
public interface Predicate<T> {

    /**
     *  Receive a parameter to judge whether the parameter matches a rule. If the matching succeeds, it returns true. If the matching fails, it returns false
     */
    boolean test(T t);

    /**
     *  Receive a Predicate type parameter, use the logic of the current function and other function to judge whether the parameter t matches the rule, and return true for success and false for failure 
     *  If the current function returns false, the other function does not evaluate
     * Any exceptions thrown during the evaluation of Predicate are forwarded to the caller
     */
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    /**
     *  Returns the Predicate after the current Predicate inversion operation
     */
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    /**
     *  Receive a Predicate type parameter, use the current function and other function logic or judge whether the parameter t matches the rule, and return true if successful and false if failed 
     *  If the current function returns true, the other function does not evaluate
     * Any exceptions thrown during the evaluation of Predicate are forwarded to the caller
     */
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    /**
     *  Static method: pass in a parameter to generate a Predicate. When calling the test() method, call object - > targetref Equals (object) function
     *
     */
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

I believe you will often encounter this functional interface in the coding process. Let's take an example:

public static void main(String[] args) {
    List<Teacher> list = Arrays.asList(
        new Teacher("Zhang San",25),
        new Teacher("Li Si",28),
        new Teacher("Wang Wu",18));

    list = list.stream().filter(item -> item.getAge()>25).collect(Collectors.toList());
    list.stream().forEach(item->System.out.println(item.getName()));
}

The parameter of filter() is of type predict, and the returned result is Li Si

See here, we have introduced the four common functional interfaces. To be honest, I've seen functional interfaces several times, especially consumers and suppliers. At that time, I just learned it in my mind and didn't apply it to specific projects. When I meet it again next time, I still look confused. I don't know if you have this feeling.

Therefore, we need to sum up experiences and lessons. We must understand the principle of the code, and then knock it several times, so as to apply it to our own projects and improve our coding ability.

That's all for today. If you have different opinions or better idea s, please contact ah Q. add ah q and join the technical exchange group to participate in the discussion!

Topics: Java optional