От проверок каждый раз, до Функторов, до Java 8 Лямбд (вроде)
Задача
Взятьэтот пример класса, который адаптирует и добавляемый в Writer :
import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
import java.io.Writer;
import java.util.Objects;
/**
<P>{@code java WriterForAppendableWChecksInFunc}</P>
**/
public class WriterForAppendableWChecksInFunc extends Writer {
private final Appendable apbl;
public WriterForAppendableWChecksInFunc(Appendable apbl) {
if(apbl == null) {
throw new NullPointerException("apbl");
}
this.apbl = apbl;
}
//Required functions, but not relevant to this post...START
public void write(char[] a_c, int i_ndexStart, int i_ndexEndX) throws IOException {
public Writer append(char c_c) throws IOException {
public Writer append(CharSequence text) throws IOException {
public Writer append(CharSequence text, int i_ndexStart, int i_ndexEndX) throws IOException {
//Required functions, but not relevant to this post...END
public void flush() throws IOException {
if(apbl instanceof Flushable) {
((Flushable)apbl).flush();
}
}
public void close() throws IOException {
flush();
if(apbl instanceof Closeable) {
((Closeable)apbl).close();
}
}
}
Не все Appendable
s Flushable
или Closeable
, но те, которые есть, также должны быть закрыты и очищены.Поэтому фактический тип объекта Appendable
должен проверяться при каждом вызове flush()
и close()
, и, когда это действительно тот тип, он приводится и вызывается функция.
По общему признанию, это не самый лучший пример, поскольку close()
вызывается только один раз для каждого экземпляра, и flush()
также не обязательно вызывается так часто.Кроме того, instanceof
, хотя и отражает, не так уж и плохо, учитывая этот конкретный пример использования.Тем не менее, концепция необходимости проверять что-то каждый раз, когда вам нужно сделать что-то еще , является реальной, и избегание этих проверок «каждый раз», когда это действительно важно, дает значительные преимущества.
Переместить все "сверхмощные" чеки в конструктор
Так с чего начать?Как избежать этих проверок, не ставя под угрозу свой код?
В нашем примере самый простой шаг - переместить все проверки instanceof
в конструктор.
public class WriterForAppendableWChecksInCnstr extends Writer {
private final Appendable apbl;
private final boolean isFlshbl;
private final boolean isClsbl;
public WriterForAppendableWChecksInCnstr(Appendable apbl) {
if(apbl == null) {
throw new NullPointerException("apbl");
}
this.apbl = apbl;
isFlshbl = (apbl instanceof Flushable);
isClsbl = (apbl instanceof Closeable);
}
//write and append functions go here...
public void flush() throws IOException {
if(isFlshbl) {
((Flushable)apbl).flush();
}
}
public void close() throws IOException {
flush();
if(isClsbl) {
((Closeable)apbl).close();
}
}
}
Теперь, когда эти «сверхмощные» проверки выполняются только один раз, flush()
и close()
должны выполнять только логические проверки.Хотя это, безусловно, улучшение, как можно полностью исключить эти функциональные проверки?
Если бы вы только могли как-то определить функцию , которая могла бы быть сохраненной по классу, а затем используется по flush()
и close()
...
public class WriterForAppendableWChecksInCnstr extends Writer {
private final Appendable apbl;
private final FlushableFunction flshblFunc; //If only!
private final CloseableFunction clsblFunc; //If only!
public WriterForAppendableWChecksInCnstr(Appendable apbl) {
if(apbl == null) {
throw new NullPointerException("apbl");
}
this.apbl = apbl;
if(apbl instanceof Flushable) {
flshblFunc = //The flushable function
} else {
flshblFunc = //A do-nothing function
}
if(apbl instanceof Closeable) {
clsblFunc = //The closeable function
} else {
clsblFunc = //A do-nothing function
}
}
//write and append functions go here...
public void flush() throws IOException {
flshblFunc(); //If only!
}
public void close() throws IOException {
flush();
clsblFunc(); //If only!
}
}
Но передача функций невозможна ... по крайней мере, до Java 8 Lambdas .Так как же это сделать в версиях Java до 8?
Функторы
С Функтором .Функтор - это, в основном, лямбда, но она обернута в объект.Хотя функции нельзя передавать в другие функции в качестве параметров, объекты могут .По сути, Functors и Lambdas - это способ передачи функций .
Так как же мы можем внедрить Functor в наш адаптер-писатель?Мы знаем, что close()
и flush()
полезны только с объектами Closeable
и Flushable
.И что некоторые Appendable
являются Flushable
, некоторые Closeable
, некоторые нет, некоторые оба.
Следовательно, мы можем хранить объект Flushable
и Closeable
вверхняя часть класса:
public class WriterForAppendable extends Writer {
private final Appendable apbl;
private final Flushable flshbl;
private final Closeable clsbl;
public WriterForAppendable(Appendable apbl) {
if(apbl == null) {
throw new NullPointerException("apbl");
}
//Avoids instanceof at every call to flush() and close()
if(apbl instanceof Flushable) {
flshbl = apbl; //This Appendable *is* a Flushable
} else {
flshbl = //?????? //But what goes here????
}
if(apbl instanceof Closeable) {
clsbl = apbl; //This Appendable *is* a Closeable
} else {
clsbl = //?????? //And here????
}
this.apbl = apbl;
}
//write and append functions go here...
public void flush() throws IOException {
flshbl.flush();
}
public void close() throws IOException {
flush();
clsbl.close();
}
}
Проверки «каждый раз» теперь исключены.Но когда Appendable
это не a Flushable
или не a Closeable
, что должно быть сохранено?
Ничего не делатьФункторы
A ничего не делать Функтор ...
class CloseableDoesNothing implements Closeable {
public void close() throws IOException {
}
}
class FlushableDoesNothing implements Flushable {
public void flush() throws IOException {
}
}
... который может быть реализован как анонимный внутренний класс:
public WriterForAppendable(Appendable apbl) {
if(apbl == null) {
throw new NullPointerException("apbl");
}
this.apbl = apbl;
//Avoids instanceof at every call to flush() and close()
flshbl = ((apbl instanceof Flushable)
? (Flushable)apbl
: new Flushable() {
public void flush() throws IOException {
}
});
clsbl = ((apbl instanceof Closeable)
? (Closeable)apbl
: new Closeable() {
public void close() throws IOException {
}
});
}
//the rest of the class goes here...
}
Для большей эффективности эти функторы, которые ничего не делают, должны быть реализованы как статические конечные объекты.И с этим, вот окончательная версия нашего класса:
package xbn.z.xmpl.lang.functor;
import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
import java.io.Writer;
public class WriterForAppendable extends Writer {
private final Appendable apbl;
private final Flushable flshbl;
private final Closeable clsbl;
//Do-nothing functors
private static final Flushable FLUSHABLE_DO_NOTHING = new Flushable() {
public void flush() throws IOException {
}
};
private static final Closeable CLOSEABLE_DO_NOTHING = new Closeable() {
public void close() throws IOException {
}
};
public WriterForAppendable(Appendable apbl) {
if(apbl == null) {
throw new NullPointerException("apbl");
}
this.apbl = apbl;
//Avoids instanceof at every call to flush() and close()
flshbl = ((apbl instanceof Flushable)
? (Flushable)apbl
: FLUSHABLE_DO_NOTHING);
clsbl = ((apbl instanceof Closeable)
? (Closeable)apbl
: CLOSEABLE_DO_NOTHING);
}
public void write(char[] a_c, int i_ndexStart, int i_ndexEndX) throws IOException {
apbl.append(String.valueOf(a_c), i_ndexStart, i_ndexEndX);
}
public Writer append(char c_c) throws IOException {
apbl.append(c_c);
return this;
}
public Writer append(CharSequence c_q) throws IOException {
apbl.append(c_q);
return this;
}
public Writer append(CharSequence c_q, int i_ndexStart, int i_ndexEndX) throws IOException {
apbl.append(c_q, i_ndexStart, i_ndexEndX);
return this;
}
public void flush() throws IOException {
flshbl.flush();
}
public void close() throws IOException {
flush();
clsbl.close();
}
}
Этот конкретный пример происходит от этот вопрос на stackoverflow .Полностью работающую и полностью документированную версию этого примера (включая функцию тестирования) можно найти внизу этого поста-вопроса (над ответом).
Реализация функторов с помощью Enum
Оставляя наш пример Writer
- Appendable
, давайте рассмотрим другой способ реализации функторов: с помощью Enum.
Например, это перечисление имеет функцию move
для каждого кардинального направления:
public enum CardinalDirection {
NORTH(new MoveNorth()),
SOUTH(new MoveSouth()),
EAST(new MoveEast()),
WEST(new MoveWest());
private final MoveInDirection dirFunc;
CardinalDirection(MoveInDirection dirFunc) {
if(dirFunc == null) {
throw new NullPointerException("dirFunc");
}
this.dirFunc = dirFunc;
}
public void move(int steps) {
dirFunc.move(steps);
}
}
Для его конструктора требуется объект MoveInDirection
(который является интерфейсом, но также может быть абстрактным классом):
interface MoveInDirection {
void move(int steps);
}
Естественно, существует четыре конкретных реализации этого интерфейса, по одной на направление. Вот тривиальная реализация для севера:
class MoveNorth implements MoveInDirection {
public void move(int steps) {
System.out.println("Moved " + steps + " steps north.");
}
}
Использование этого Функтора завершается простым вызовом:
CardinalDirection.WEST.move(3);
Который в нашем примере выводит это на консоль:
Moved 3 steps west.
А вот полный рабочий пример:
/**
<P>Demonstrates a Functor implemented as an Enum.</P>
<P>{@code java EnumFunctorXmpl}</P>
**/
public class EnumFunctorXmpl {
public static final void main(String[] ignored) {
CardinalDirection.WEST.move(3);
CardinalDirection.NORTH.move(2);
CardinalDirection.EAST.move(15);
}
}
enum CardinalDirection {
NORTH(new MoveNorth()),
SOUTH(new MoveSouth()),
EAST(new MoveEast()),
WEST(new MoveWest());
private final MoveInDirection dirFunc;
CardinalDirection(MoveInDirection dirFunc) {
if(dirFunc == null) {
throw new NullPointerException("dirFunc");
}
this.dirFunc = dirFunc;
}
public void move(int steps) {
dirFunc.move(steps);
}
}
interface MoveInDirection {
void move(int steps);
}
class MoveNorth implements MoveInDirection {
public void move(int steps) {
System.out.println("Moved " + steps + " steps north.");
}
}
class MoveSouth implements MoveInDirection {
public void move(int steps) {
System.out.println("Moved " + steps + " steps south.");
}
}
class MoveEast implements MoveInDirection {
public void move(int steps) {
System.out.println("Moved " + steps + " steps east.");
}
}
class MoveWest implements MoveInDirection {
public void move(int steps) {
System.out.println("Moved " + steps + " steps west.");
}
}
Выход:
[C:\java_code]java EnumFunctorXmpl
Moved 3 steps west.
Moved 2 steps north.
Moved 15 steps east.
Я еще не начал с Java 8, поэтому я пока не могу написать раздел Lambdas:)