一個SpringBoot裏邊函數式編程的例子

爲什麼寫

因爲看到了SpringBoot一個關於函數式編程的完美例子,以前很少用到,但是寫的真是太美了,就像美女一樣,真養眼。

先看下lambda常用的函數接口

接下來看看她長什麼樣子

public final class PropertyMapper {

	private static final Predicate<?> ALWAYS = (t) -> true;

	private static final PropertyMapper INSTANCE = new PropertyMapper(null, null);

	private final PropertyMapper parent;

	private final SourceOperator sourceOperator;

	private PropertyMapper(PropertyMapper parent, SourceOperator sourceOperator) {
		this.parent = parent;
		this.sourceOperator = sourceOperator;
	}

	/**
	 * Return a new {@link PropertyMapper} instance that applies
	 * {@link Source#whenNonNull() whenNonNull} to every source.
	 * @return a new property mapper instance
	 */
	public PropertyMapper alwaysApplyingWhenNonNull() {
		return alwaysApplying(this::whenNonNull);
	}

	private <T> Source<T> whenNonNull(Source<T> source) {
		return source.whenNonNull();
	}

	/**
	 * Return a new {@link PropertyMapper} instance that applies the given
	 * {@link SourceOperator} to every source.
	 * @param operator the source operator to apply
	 * @return a new property mapper instance
	 */
	public PropertyMapper alwaysApplying(SourceOperator operator) {
		Assert.notNull(operator, "Operator must not be null");
		return new PropertyMapper(this, operator);
	}

	/**
	 * Return a new {@link Source} from the specified value supplier that can be used to
	 * perform the mapping.
	 * @param <T> the source type
	 * @param supplier the value supplier
	 * @return a {@link Source} that can be used to complete the mapping
	 * @see #from(Object)
	 */
	public <T> Source<T> from(Supplier<T> supplier) {
		Assert.notNull(supplier, "Supplier must not be null");
		Source<T> source = getSource(supplier);
		if (this.sourceOperator != null) {
			source = this.sourceOperator.apply(source);
		}
		return source;
	}

	/**
	 * Return a new {@link Source} from the specified value that can be used to perform
	 * the mapping.
	 * @param <T> the source type
	 * @param value the value
	 * @return a {@link Source} that can be used to complete the mapping
	 */
	public <T> Source<T> from(T value) {
		return from(() -> value);
	}

	@SuppressWarnings("unchecked")
	private <T> Source<T> getSource(Supplier<T> supplier) {
		if (this.parent != null) {
			return this.parent.from(supplier);
		}
		return new Source<>(new CachingSupplier<>(supplier), (Predicate<T>) ALWAYS);
	}

	/**
	 * Return the property mapper.
	 * @return the property mapper
	 */
	public static PropertyMapper get() {
		return INSTANCE;
	}

	/**
	 * Supplier that caches the value to prevent multiple calls.
	 */
	private static class CachingSupplier<T> implements Supplier<T> {

		private final Supplier<T> supplier;

		private boolean hasResult;

		private T result;

		CachingSupplier(Supplier<T> supplier) {
			this.supplier = supplier;
		}

		@Override
		public T get() {
			if (!this.hasResult) {
				this.result = this.supplier.get();
				this.hasResult = true;
			}
			return this.result;
		}

	}

	/**
	 * An operation that can be applied to a {@link Source}.
	 */
	@FunctionalInterface
	public interface SourceOperator {

		/**
		 * Apply the operation to the given source.
		 * @param <T> the source type
		 * @param source the source to operate on
		 * @return the updated source
		 */
		<T> Source<T> apply(Source<T> source);

	}

	/**
	 * A source that is in the process of being mapped.
	 *
	 * @param <T> the source type
	 */
	public static final class Source<T> {

		private final Supplier<T> supplier;

		private final Predicate<T> predicate;

		private Source(Supplier<T> supplier, Predicate<T> predicate) {
			Assert.notNull(predicate, "Predicate must not be null");
			this.supplier = supplier;
			this.predicate = predicate;
		}

		/**
		 * Return an adapted version of the source with {@link Integer} type.
		 * @param <R> the resulting type
		 * @param adapter an adapter to convert the current value to a number.
		 * @return a new adapted source instance
		 */
		public <R extends Number> Source<Integer> asInt(Function<T, R> adapter) {
			return as(adapter).as(Number::intValue);
		}

		/**
		 * Return an adapted version of the source changed via the given adapter function.
		 * @param <R> the resulting type
		 * @param adapter the adapter to apply
		 * @return a new adapted source instance
		 */
		public <R> Source<R> as(Function<T, R> adapter) {
			Assert.notNull(adapter, "Adapter must not be null");
			Supplier<Boolean> test = () -> this.predicate.test(this.supplier.get());
			Predicate<R> predicate = (t) -> test.get();
			Supplier<R> supplier = () -> {
				if (test.get()) {
					return adapter.apply(this.supplier.get());
				}
				return null;
			};
			return new Source<>(supplier, predicate);
		}

		/**
		 * Return a filtered version of the source that won't map non-null values or
		 * suppliers that throw a {@link NullPointerException}.
		 * @return a new filtered source instance
		 */
		public Source<T> whenNonNull() {
			return new Source<>(new NullPointerExceptionSafeSupplier<>(this.supplier),
					Objects::nonNull);
		}

		/**
		 * Return a filtered version of the source that will only map values that are
		 * {@code true}.
		 * @return a new filtered source instance
		 */
		public Source<T> whenTrue() {
			return when(Boolean.TRUE::equals);
		}

		/**
		 * Return a filtered version of the source that will only map values that are
		 * {@code false}.
		 * @return a new filtered source instance
		 */
		public Source<T> whenFalse() {
			return when(Boolean.FALSE::equals);
		}

		/**
		 * Return a filtered version of the source that will only map values that have a
		 * {@code toString()} containing actual text.
		 * @return a new filtered source instance
		 */
		public Source<T> whenHasText() {
			return when((value) -> StringUtils.hasText(Objects.toString(value, null)));
		}

		/**
		 * Return a filtered version of the source that will only map values equal to the
		 * specified {@code object}.
		 * @param object the object to match
		 * @return a new filtered source instance
		 */
		public Source<T> whenEqualTo(Object object) {
			return when(object::equals);
		}

		/**
		 * Return a filtered version of the source that will only map values that are an
		 * instance of the given type.
		 * @param <R> the target type
		 * @param target the target type to match
		 * @return a new filtered source instance
		 */
		public <R extends T> Source<R> whenInstanceOf(Class<R> target) {
			return when(target::isInstance).as(target::cast);
		}

		/**
		 * Return a filtered version of the source that won't map values that match the
		 * given predicate.
		 * @param predicate the predicate used to filter values
		 * @return a new filtered source instance
		 */
		public Source<T> whenNot(Predicate<T> predicate) {
			Assert.notNull(predicate, "Predicate must not be null");
			return new Source<>(this.supplier, predicate.negate());
		}

		/**
		 * Return a filtered version of the source that won't map values that don't match
		 * the given predicate.
		 * @param predicate the predicate used to filter values
		 * @return a new filtered source instance
		 */
		public Source<T> when(Predicate<T> predicate) {
			Assert.notNull(predicate, "Predicate must not be null");
			return new Source<>(this.supplier, predicate);
		}

		/**
		 * Complete the mapping by passing any non-filtered value to the specified
		 * consumer.
		 * @param consumer the consumer that should accept the value if it's not been
		 * filtered
		 */
		public void to(Consumer<T> consumer) {
			Assert.notNull(consumer, "Consumer must not be null");
			T value = this.supplier.get();
			if (this.predicate.test(value)) {
				consumer.accept(value);
			}
		}

		/**
		 * Complete the mapping by creating a new instance from the non-filtered value.
		 * @param <R> the resulting type
		 * @param factory the factory used to create the instance
		 * @return the instance
		 * @throws NoSuchElementException if the value has been filtered
		 */
		public <R> R toInstance(Function<T, R> factory) {
			Assert.notNull(factory, "Factory must not be null");
			T value = this.supplier.get();
			if (!this.predicate.test(value)) {
				throw new NoSuchElementException("No value present");
			}
			return factory.apply(value);
		}

		/**
		 * Complete the mapping by calling the specified method when the value has not
		 * been filtered.
		 * @param runnable the method to call if the value has not been filtered
		 */
		public void toCall(Runnable runnable) {
			Assert.notNull(runnable, "Runnable must not be null");
			T value = this.supplier.get();
			if (this.predicate.test(value)) {
				runnable.run();
			}
		}

	}

	/**
	 * Supplier that will catch and ignore any {@link NullPointerException}.
	 */
	private static class NullPointerExceptionSafeSupplier<T> implements Supplier<T> {

		private final Supplier<T> supplier;

		NullPointerExceptionSafeSupplier(Supplier<T> supplier) {
			this.supplier = supplier;
		}

		@Override
		public T get() {
			try {
				return this.supplier.get();
			}
			catch (NullPointerException ex) {
				return null;
			}
		}

	}

}

如何使用

以tomcat最大工作線程數作爲例子,當然這裏做了極大的簡化,方便看,並且她好處顯而易見,極大的幫助我們省略了好多判斷過程,只需要使用裏邊的when等方法即可,這是太漂亮了。

package com.example.web;

import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.web.ServerProperties;

import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;

public class FunctionTest {
    /**
     * 默認工作線程數爲200
     * 具體設置的值通過配置文件設置
     */
    private int maxThreads = 200;

    @Test
    public void maxThreadsTest() {
        // tomcat配置文件映射類
        ServerProperties.Tomcat tomcatProperties = new ServerProperties.Tomcat();
        // 測試設置tomcatProperties
        tomcatProperties.setMaxThreads(1);
        // 條件映射器
        PropertyMapper propertyMapper = PropertyMapper.get();
        // 匹配過程有三種寫法,第三種寫法剛開始思想有點沒轉過來,
        // 就看看第一種寫法,慢慢就習慣了
        
        // 1:條件匹配並設置,最原始寫法
        propertyMapper.from(new Supplier<Integer>() {
            @Override
            public Integer get() {
                return tomcatProperties.getMaxThreads();
            }
        }).when(new Predicate<Integer>() {
            @Override
            public boolean test(Integer maxThreads) {
                return isPredicate(maxThreads);
            }
        }).to(new Consumer<Integer>() {
            @Override
            public void accept(Integer maxThreads) {
                customizeMaxThreads(maxThreads);
            }
        });
        // 2:條件匹配並設置,lambda寫法
        propertyMapper.from(() -> tomcatProperties.getMaxThreads())
                .when(maxThreads -> isPredicate(maxThreads))
                .to(maxThreads -> customizeMaxThreads(maxThreads));
        // 3:條件匹配並設置,引用函數式寫法
        propertyMapper.from(tomcatProperties::getMaxThreads)
                .when(this::isPredicate)
                .to(this::customizeMaxThreads);

        System.out.println(maxThreads);
    }

    private boolean isPredicate(Integer o) {
        return o > 10;
    }

    private void customizeMaxThreads(int maxThreads) {
        this.maxThreads = maxThreads;
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章