ネーミング |
---|
汎用型 |
総称型 |
generic type |
ジェネリクス |
generics |
型パラメータ |
type parameter |
総称クラス |
ジェネリッククラス |
generic class |
汎用型導入前 | 汎用型導入後 | 機能 |
---|---|---|
boolean add(Object e) | boolean add(E e) | 要素を追加する |
Object get(int index) | E get(int index) | 指定された位置の要素を取得する |
List list = new ArrayList();
list.add(new Integer(3));// コンパイル警告
list.add(new Double(3.14));// コンパイル警告
Integer i0 = (Integer) list.get(0);// 正常
Integer i1 = (Integer) list.get(1);// 実行時エラー
List<Integer> list = new ArrayList<Integer>();
list.add(new Integer(3));
list.add(new Double(3.14));// コンパイルエラー
Integer i0 = list.get(0);// 正常
Double d1 = (Double) list.get(1);// コンパイルエラー
生産効率を維持する | エラーを製造時に検出する |
プログラムを再利用する | 型をパラメータにしてプログラムをテンプレート化し、それを利用したプログラムを製造する時に型を割り当てる |
List<Integer> list = new ArrayList<Integer>();
List list2 = list;
list2.add(new Double(3.14));// コンパイル警告
Integer i0 = list.get(0);// 実行時エラー
上記の例ではDoubleをIntegerにキャストしようとしてClassCastExceptionが発生します。
メソッド | 機能 |
---|---|
V put(K key, V value) | キーと値の組を関連付ける |
V get(Object key) | 指定されたキーに関連付けられた値を返す |
Map<String, List<String>> listMap = new HashMap<String, List<String>>();
listMap.put("key0", new ArrayList<String>());
List<String> list = listMap.get("key0");
視点\表記 | ? extends E | ? super E |
---|---|---|
単語 | 上限境界 | 下限境界 |
型の集合 | Eの子である型の集まり | Eの親である型の集まり |
変数(引数、返却値) | 型Eの変数に設定することができる型の集まり | 型Eの値を設定することができる変数の型の集まり |
メソッド | 機能 |
---|---|
boolean addAll(Collection<? extends E> c) | 指定されたコレクションの各要素をこのオブジェクトに追加する |
List<SQLException> list0 = new ArrayList<SQLException>();
List<SQLWarning> list1 = new ArrayList<SQLWarning>();
list0.addAll(list1);// 正常
list1.addAll(list0);// コンパイルエラー
java.sql.SQLWarningはjava.sql.SQLExceptionを継承したクラスです。
SQLExceptionではあるがSQLWarningでないインスタンスが格納されているかもしれないlist0をlist1に追加することができないことがコンパイラでチェックされています。
メソッド | 機能 |
---|---|
static <T> boolean addAll(Collection<? super T> c, T... elements) | 指定されたコレクションに指定された全ての要素を追加する |
List<SQLException> list0 = new ArrayList<SQLException>();
Collections.addAll(list0, new SQLWarning());// 正常
Collections.addAll(list0, new Exception());// コンパイルエラー
Integer[] array0 = new Integer[3];
Number[] array1 = array0;// 正常
array1[0] = 3.14;// 実行時エラー
List<Integer> list0 = new ArrayList<Integer>();
List<? extends Number> list1 = list0;// 正常
list1.add(Double.valueOf(3.14));// コンパイルエラー
List<Number> list2 = list0;// コンパイルエラー
List<? super Integer> list3 = list0;// 正常
list3.add(Integer.valueOf(3));// 正常
配列では配列代入時にエラーにならずに値代入時に実行エラーになります。
汎用型ではリスト代入時にエラーを出すことにより値代入の整合性を確保します。
共変のように変数を扱うには境界ワイルドカードを使用する必要があります。
Javaでは配列と汎用型の動作が異なるため、配列の汎用型の組み合わせに以下の例のような制限があります。
List<String[]> arrayList = null;
arrayList = new ArrayList<String[]>();// 正常
List<?>[] listArray0 = null;// 正常
listArray0 = new List<?>[2];// 正常
List<String>[] listArray1 = null;// 正常
listArray1 = new List<String>[2];// コンパイルエラー
public interface Fn<C, R, E extends Exception> {
R exec(C c) throws E;
}
以下の意味があります。
機能 | 型パラメータ | 意味 |
---|---|---|
引数、入力 | C | 型Cの値を受け取る関数であり |
返却値、出力 | R | 型Rの値を返す関数であり |
例外の出力 | E | 型Eの例外を発生させる可能性がある関数である |
機能 | 型パラメータ | 意味 |
---|---|---|
引数 | ? super C | 型Cの値を渡すことができる委譲先であり |
返却値 | ? extends R | 型Rの変数に格納することができる型を返してくれる委譲先であり |
例外 | ? extends E | 型Eの例外として投げることができる例外だけを発生させる可能性がある委譲先である |
public class JoinFn<A, B, C, E extends Exception> implements Fn<A, C, E> {
private Fn<? super A, ? extends B, ? extends E> firstFn;
private Fn<? super B, ? extends C, ? extends E> secondFn;
public C exec(final A a) throws E {
return secondFn.exec(firstFn.exec(a));
}
// コンストラクタとアクセサは省略
}
なぜなら以下の各値はそれぞれ次の意味を持つからです。
記号 | 説明 |
---|---|
Fn<? super A, | 型Aの値を受け取ることができ |
? extends B, | 型Bの変数に設定することができ |
? extends E> | 型Eの例外として投げることができる関数と |
Fn<? super B, | 型Bの値を受け取ることができ |
? extends C, | 型Cの変数に設定することができ |
? extends E> | 型Eの例外として投げることができる関数を合成すると |
Fn<A, | 型Aの値を受け取り |
C, | 型Cの値を返し |
E> | 型Eの例外を発生させる可能性がある関数になるから |
public class EchoFn<C, E extends Exception> implements Fn<C, C, E> {
public C exec(final C c) {
return c;
}
}
public class MaxLengthChecker<E extends Exception> implements Fn<String, Boolean, E> {
private int value;
public Boolean exec(final String c) {
return c.length() <= value;
}
// コンストラクタ、アクセサは省略
}
public interface Fn<C, R, E extends Exception> {
R exec(C c) throws E;
}
メソッド | 機能 |
---|---|
int compareTo(T o) | このオブジェクトと指定されたオブジェクトの順序を比較する |
public class MaxChecker<C extends Comparable<C>, E extends Exception> implements Fn<C, Boolean, E> {
private C value;
public Boolean exec(final C c) {
return value.compareTo(c) >= 0;
}
// コンストラクタ、アクセサは省略
}
Comparableが実装されている型であれば型に依存せずに最大値を検証することができます。
インタフェース | 機能 | プロパティ |
---|---|---|
NodeContext | DOMノードの変数を保持するコンテキスト | org.w3c.dom.Node node |
IdContext | 識別子である変数を保持するコンテキスト | String id |
public class IFrameInserter<C extends NodeContext & IdContext, E extends Exception> implements Fn<C, Void, E> {
public Void exec(final C c) throws E {
// 省略
}
// コンストラクタ、変数、アクセサは省略
}
型パラメータCの実装に依存せずに複数の性質をもつ型パラメータに対して実装することができます。
「&」の右にはインタフェースだけを宣言することができます。
class StringList extends ArrayList<String>{}// 悪い例
完全なtypedefを実現するわけではないので、StringListとList<String>には以下の代入関係があります。
List<String> list0 = new StringList();// 正常
StringList list1 = new ArrayList<String>();// コンパイルエラー
StringListを受け取る関数を定義してしまった場合、その関数にはもはやList<String>のインスタンスを渡すことができません。
public void doSomething(StringList list){
// ...
}
複数の性質を持つ型パラメータを定義する際は、受け皿となるクラスあるいはインタフェースを用意する代わりに「&」を用いて宣言することにより再利用性を維持することができます。次のインタフェース宣言は悪い例です。
public interface MyIdNodeContext extends IdContext, NodeContext {}// 悪い例
public final class FnUtils {
public static <C> Fn<C, C, RuntimeException> echo() {
return new EchoFn<C, RuntimeException>();
}
// ...
}
インスタンスの生成を以下の様に略記することができます。
Fn<String, String, RuntimeException> fn0 = new EchoFn<String, RuntimeException>();// before
Fn<String, String, RuntimeException> fn1 = FnUtils.echo();// after
さらにスタティックインポートを使うと以下の様に略記することができます。
import static woolpack.fn.FnUtils.*;
// ...
Fn<String, String, RuntimeException> fn1 = echo();
用語 | 意味 |
---|---|
関数インタフェース | 関数を抽象化したインタフェース(再掲)
public interface Fn<C, R, E extends Exception> { |
関数クラス | 関数インタフェースの実装 |
関数ファクトリ | 関数クラスを生成する型推論のためのスタティックメソッド |
関数オブジェクト | 関数クラスのインスタンスを組み立てたオブジェクト |
関数ファクトリ | 生成される関数オブジェクトの性質 |
---|---|
FnUtils#fix() | 固定値を返す |
FnUtils#echo() | 引数をそのまま返す |
Fn<Integer, Integer, RuntimeException> fn0 = FnUtils.echo();
Fn<String, String, RuntimeException> fn1 = FnUtils.echo();
生産効率を維持する | |
プログラムを再利用する | 各プログラムの責務(Responsibility)を適切に割り当てる |
各プログラムを部品化して組み立てる | プログラム同士の接続を実装に依存しないようにする |
プログラム同士の接続を形式(インタフェース)に依存するようにする |
public class RegExpConverter<E extends Exception> implements Fn<String, String, E> {
private Pattern pattern;
private String replacement;
public String exec(final String c) {
return pattern.matcher(c).replaceAll(replacement);
}
}
final Fn<String, String, RuntimeException> fn = new RegExpConverter<RuntimeException>(Pattern.compile("##(.+)##"), "$1");
処理部品に対し依存性が自然に注入されます(依存性の注入)。
またオブジェクトを生成する処理が処理部品の組み立てるフェーズに集中することにより
処理部品と組み立て処理の間で依存性が自然に逆転します(制御の逆転)。
既存のDI/IoCコンテナの代替として使用することができます。
final String result = fn.exec("mamo##hona##mimo");
assertEquals("mamohonamimo", result);
RegExpConverterクラスやPatternクラス、
変換元の正規表現や変換後の置き換えパターン「"$1"」に依存せずに、
Fnインタフェースだけに依存する処理ブロックやクラスを設計することができます。
それによって疎結合を維持することができます。
![]() |
layer | 層 | アプローチ | 説明 |
---|---|---|---|
part | 処理部品を定義する層 | ボトムアップ | 処理の詳細を部品化する |
composition | 処理部品を組み立てる層 | トップダウン | 部品化された処理を組み立てる |
execution | 処理を実行する層 | トップダウン | 組み立てられた処理を実行する |
public class Delegator<C, R, E extends Exception> implements Fn<C, R, E> {
private Fn<? super C, ? extends R, ? extends E> fn;
public R exec(final C c) throws E {
return fn.exec(c);
}
// コンストラクタ、アクセサは省略
}
このクラスは次の目的を持っています。
public class CloneFormatFactory<E extends Exception> implements Fn<Object, Format, E> {
private Format format;
public Format exec(final Object c) {
return (Format) format.clone();
}
// コンストラクタ、アクセサは省略
}
Formatのクローンを生成するファクトリとみなせます。
public class FormatConverter<E extends Exception> implements Fn<Object, String, E> {
private Fn<?, ? extends Format, ? extends E> formatFactory;
public String exec(final Object c) throws E {
return formatFactory.exec(null).format(c);
}
// コンストラクタ、アクセサは省略
}
フォーマットのファクトリが取り替えられるようになっています。
例えばスレッドローカルから取得したロケールによって、生成するFormatを分岐するファクトリを設定することもできます。
「取り替えられる」という性質がStrategyの特徴です。
カリー化 | 文 | 意味 |
---|---|---|
前 | String#length() <= maxLength | 文字列と閾値を引数にして真偽値を返す文 |
後 | BoolUtils#checkMaxLength() | 閾値を引数にして「文字列を引数にして真偽値を返す関数クラス」を生成する関数ファクトリ |
public final class FnUtils {
public static <C, R> FixFn<C, R, RuntimeException> fix(final R value) {
return new FixFn<C, R, RuntimeException>(value);
}
// ...
}
型を確定するタイミングを分散させることができるので「型のカリー化」とみなせます。
public class ToArrayConverter extends Delegator<ConvertContext, Void, RuntimeException> {
@Override
public Void exec(final ConvertContext c) {
if (!c.getToType().isArray()) {
super.exec(c);
return null;
}
// convert to array
return null;
}
// コンストラクタは省略
}
public class BuildableArrayList<V> extends ArrayList<V> {
public BuildableArrayList<V> list(final V v) {
add(v);
return this;
}
// ...
}
次のクラスは型推論を適用するためにスタティックなファクトリメソッドです。
public final class Utils {
public static <V> BuildableArrayList<V> list(final V v) {
return new BuildableArrayList<V>().list(v);
}
// ...
}
次のようにして使います。
List<String> list = Utils.list("key0").list("key1");
java.util.Arraysクラスには配列を使用したListファクトリメソッドがあります。
List<String> list = Arrays.asList("key0", "key1");
Javaでは汎用型付きの配列を宣言することはできないので、汎用型が付けられたコレクションを生成するときにBuilderパターンを使用します。
List<Number> list0 = Utils.list((Number) 3).list(3.14);
List<Number> list1 = Utils.<Number>list(3).list(3.14);
public class ExecFn<C, R, E extends Exception> implements Fn<C, R, E> {
private Fn<? super C, ? extends Fn<? super C, ? extends R, ? extends E>, ? extends E> fn;
public R exec(final C c) throws E {
return fn.exec(c).exec(c);
}
// コンストラクタ、アクセサは省略
}
委譲先の型宣言
「Fn<? super C, ? extends Fn<? super C, ? extends R, ? extends E>, ? extends E>」
で、ふたつの層の関数の引数が同一(? super C)になっているところが特徴的です。
ExecFnとSwitchFn(Map#get()の関数クラスによるアダプタ)を利用して
オブジェクトに対するswitch句を定義することができます。
![]() |
final Fn<String, String, RuntimeException> fn = FnUtils.join(
ConvertUtils.convertRegExp(Pattern.compile("#@@([^@]+)@#"), "?$1?"),
ConvertUtils.convertRegExp(Pattern.compile("#@([^@]+)@#"), "!$1!")
);
関数型言語の普通の関数の合成は、Context役がImmutable(不変)であるInterpreterとみなせます。
final Fn<NodeContext, Void, RuntimeException> fn = XmlUtils.findNode(JXPUtils.list("//*[@id=\"dummy\" or @name=\"dummy\"]"), XmlUtils.REMOVE_THIS);
NodeContext型の引数に対して意図的に副作用を発生させています。
関数型言語の副作用がある関数の合成は、Context役がMonad(Haskellの場合)であり、
かつContext役にPrototypeの性質がないInterpreterとみなせます。
概念 | メタファ | 例の図との対応 |
---|---|---|
インタープリタ デザインパターン | 加工対象を加工するための組立式の生産ライン/流れ作業/コンベア | 例のクラス全体 |
Expression 役 | コンベア部品の仕様 | CheckExpression インタフェース |
Context 役 | コンベアに乗って流れてくる、加工対象を入れるトレーに注文書を付加したもの | ValuesContext クラス |
Expression 役の interpret メソッド | コンベア同士をつなぐための接続点/ジョイント(受動側) | CheckExpression インタフェースの interpret メソッド |
Expression 役の各実装 | 加工対象を加工する機能を付加したコンベア | SerialCheckExpression / RequiredCheckExpression / MinCheckExpression / MaxCheckExpression の各クラス |
Expression 役の各実装に定義された委譲先の Expression 役の属性値 | コンベア同士をつなぐための接続点/ジョイント(能動側) | SerialExpression クラスの list 属性 |
Expression 役の各実装に定義された Expression 役以外のオブジェクト属性値 | 機能を付加したコンベアの操作レバー | MinCheckExpression / MaxCheckExpression クラスの value 属性 |
![]() |
![]() |
概念 | メタファ | 例の図との対応 |
---|---|---|
Woolpack API | オーダーメードパソコンを製造するラインを組み立てるキット | Woolpack API |
Woolpack を使用したWebアプリケーション | オーダーメードパソコンを製造する工場とその営業所 | SampleServlet クラス |
Servlet コンテナ | 営業所 | ServletContainer |
web.xml | 営業所と工場を行き来するトラックの経路情報 | web.xml |
コンストラクタツリー | パソコンの製造するための組み立て完了した工場 | serial 変数を根とするツリー |
HTTP リクエストの URL | 注文書に記載されたパソコンの製品番号 | 変換値が EEContext クラスの id 属性に設定される |
HTTP リクエストのパラメータ | 注文書に記載されたオプション品の明細 | 変換したオブジェクトが EEContext クラスの inputMap 属性に設定される |
Context 役のコンテナ定義に記載されたコンポーネント定義 | パソコンの各製品の基本仕様 | model 変数 |
HTML ファイル | パソコンの筐体 | id0.html, id1.html |
HTTP レスポンス | 組み立て完了したパソコン | EEContext クラスの node 属性 |
![]() |
public class NotDelegator<C, E extends Exception> extends Delegator<C, Boolean, E> {
@Override
public Boolean exec(final C c) throws E {
return !super.exec(c);
}
// コンストラクタ、アクセサは省略
}
汎用型と関数型を組み合わせているので、型のバリエーションに対してひとつの関数クラスで対処することができます。
public class MapCache<C, K, E extends Exception> implements Fn<C, Object, E> {
private Fn<? super C, ? extends Map<? super K, Object>, ? extends E> mapGetter;
private Fn<? super C, ? extends K, ? extends E> keyGetter;
private Fn<? super C, ?, ? extends E> maker;
public Object exec(final C c) throws E {
final Map<? super K, Object> map = mapGetter.exec(c);
final K key = keyGetter.exec(c);
Object value = map.get(key);
if (value == null) {
value = maker.exec(c);
map.put(key, value);
return value;
}
return value;
}
// コンストラクタ、アクセサは省略
}
汎用型と関数型を組み合わせているので、型のバリエーションに対してひとつの関数クラスで対処することができます。
ネーミング |
---|
依存性の注入 |
Dependency injection |
DI |
制御の逆転 |
Inversion of Control |
IoC |
性質 | DI/AOPコンテナ | 汎用型+デザインパターン+関数型 |
---|---|---|
DI/AOPを宣言する場所 | 規約による抽象化、XML、初期化処理を行う場所(コンストラクタなど) | 関数ファクトリ、初期化処理を行う場所(コンストラクタなど) |
DI/AOPを行うタイミング | プログラム実行直後の初期化フェーズ | |
DIの実現方式 | DIコンテナによる隠蔽、バイトコード加工技術、リフレクション、ビルダーパターン | デザインパターン(主にInterpreter、Strategy、Currying) |
処理のインターセプト | 主にバイトコード加工技術によるAOP | Proxy、Decorator、Abstract Factory |
メソッド | 機能 |
---|---|
boolean hasNext() | 次の要素が存在する場合だけtrueを返す |
E next() | 次の要素を返す |
void remove() | 現在の位置の要素を削除する |
メソッド | 機能 |
---|---|
Iterator<T> iterator() | このオブジェクトの反復子を生成する |
List<Integer> list = new ArrayList<Integer>();
for(Integer i : list) {
System.out.println(i);
}
配列もIterableが実装されています。
ラムダ計算 | 処理定義の階層化 |
---|---|
ラムダ抽象 | 処理部品を組み立てる層 |
関数適用 | 処理を実行する層 |
ラムダ計算の用語 | 意味 | オブジェクト指向による説明 |
---|---|---|
アルファ変換 | 束縛変数の名前は重要ではない | 言語のカプセル化機構で表現されている |
ベータ簡約 | 関数の適用 | 関数インタフェースの実行メソッド(applyまたはexec)の実行するとこと対応する |
イータ変換 | 関数の外延性 | 関数の引数が副作用を許容する場合は局所的には成立しない |
public final class FactoryUtils {
public static final Fn<Object, Map<String, Object>, RuntimeException> LINKED_MAP_FACTORY = new Fn<Object, Map<String, Object>, RuntimeException>() {
public Map<String, Object> exec(final Object c) {
return new LinkedHashMap<String, Object>();
}
};
// ...
}
public final class JXPUtils {
public static Fn<Object, Node, RuntimeException> one(final String expression) {
JXPathContext.compile(expression);
return new Fn<Object, Node, RuntimeException>() {
public Node exec(final Object c) throws RuntimeException {
return (Node) JXPathContext.newContext(c).selectSingleNode(expression);
}
};
}
// ...
}
XPath文であるexpression変数を使用して匿名の関数クラスが定義されています。
Javaでは匿名クラスからそれが続するメソッドの変数を参照する場合、finalが宣言されている必要があります。
public class SingleInput<C, R> implements Fn<C, R, Exception> {
private DataSource dataSource;
private Fn<? super C, ? extends String, ? extends Exception> queryFactory;
private Fn<? super PreparedStatement, ? extends R, ? extends Exception> converter;
private Fn<? super SQLException, ?, ? extends Exception> errorFn;
public R exec(final C c) throws Exception {
SQLException e0 = null;
try {
final Connection connection = dataSource.getConnection();
try {
final PreparedStatement statement = connection.prepareStatement(queryFactory.exec(c));
try {
statement.setObject(1, c);
statement.execute();
return converter.exec(statement);
} catch (final SQLException e1) {
errorFn.exec(e1);
e0 = e1;
} finally {
statement.close();
}
} catch (final SQLException e1) {
errorFn.exec(e1);
if (e0 == null) {
e0 = e1;
}
} finally {
connection.close();
}
} catch (final SQLException e1) {
errorFn.exec(e1);
if (e0 == null) {
e0 = e1;
}
}
if (e0 != null) {
throw e0;
} else {
return null;
}
}
}
PreparedStatementを受け取った委譲先(converter)は、
ConnectionやPreparedStatementの後始末処理を委譲元に任せて
ResultSetをフェッチしたり更新件数を抽出したりできます。
また、SingleInputクラスが委譲先(converter)の返却値の型パラメータ「R」をそのまま呼出し元に伝達しているので、
委譲先(converter)の型のバリエーションに対してひとつのクラスで対応することができます。
public class ConvertContext {
private Object value;
private Class toType;
private String propertyName;
private Fn<ConvertContext, Void, RuntimeException> fn;
// アクセサは省略
}
次のクラスは自分自身をコンテキストに登録している関数クラスです。
public class SettingFnConverter extends Delegator<ConvertContext, Void, RuntimeException> {
@Override
public Void exec(final ConvertContext c) {
c.setFn(this);
super.exec(c);
return null;
}
// コンストラクタは省略
}
次のクラスはコンテキストに設定された関数オブジェクトを取り出して委譲する関数クラスの例(配列への変換器)です。
public class ToArrayConverter extends Delegator<ConvertContext, Void, RuntimeException> {
@Override
public Void exec(final ConvertContext c) {
// ...
final Collection<?> collection = Utils.toCollection(c.getValue());
// ...
for (final Object before : collection) {
c.setValue(before);
c.getFn().exec(c);
// ...
}
// ...
return null;
}
// コンストラクタは省略
}
Common Lisp ではlabelsを使用して再帰を表現する方法もあるため関数オブジェクトへの参照手段は異なりますが、
Double Dispatchは関数型言語における再帰呼び出しとみなせます。