すごい久しぶりに自分でクラスを書く
RubyやFunctional Jλvλのこととかをぼんやり考えていたら書きたくなってしまった。とくに使い道はない。
package com.m12i.java.range; import java.util.ArrayList; import java.util.List; /** * 始点と終点の2値により決定されるある範囲をあらわすクラス.<br> * 始点と終点は同じクラスである必要がある。 このクラスはイミュータブルなクラスである。 * * @param <T> * Comparableの実装クラス */ public class Range<T extends Comparable<T>> { /** 範囲の始点 */ private final T start; /** 範囲の終点 */ private final T end; /** * 始点を取得する. * * @return 始点 */ public final T getStart() { return this.start; } /** * 終点を取得する. * * @return 終点 */ public final T getEnd() { return this.end; } /** * 2値から新しい範囲オブジェクトを生成する.<br> * 2値のうちいずれかでもnullである場合や、一方のクラスと他方のクラスが異なる場合、 * {@link IllegalArgumentException}をスローする。<br> * * @param value1 * 範囲の始点(もしくは終点) * @param value2 * 範囲の終点(もしくは始点) */ public Range(final T value1, final T value2) { Range.checkNullArguments(value1, value2); if (!value1.getClass().equals(value2.getClass())) { throw new IllegalArgumentException(); } if (value1.compareTo(value2) <= 0) { this.start = value1; this.end = value2; } else { this.start = value2; this.end = value1; } } /** * 2値から新しい範囲オブジェクトを生成する.<br> * 2値のうちいずれかでもnullである場合{@link IllegalArgumentException}をスローする。 * * @param value1 * 範囲の始点(もしくは終点) * @param value2 * 範囲の終点(もしくは始点) * @return 新しい範囲オブジェクト */ public static <T extends Comparable<T>> Range<T> between(final T value1, final T value2) { return new Range<T>(value1, value2); } /** * 2つの範囲の積(共通部分)の範囲オブジェクトを求める.<br> * 2つの範囲が共通部分を持たない場合、{@link Range.NotOverlapException}がスローされる。 * * @param range1 * 範囲1 * @param range2 * 範囲2 * @return 共通部分をあらわす範囲オブジェクト * @throws NotOverlapException * 2つの範囲オブジェクトに重なる部分がない */ public static <T extends Comparable<T>> Range<T> productOf( final Range<T> range1, final Range<T> range2) { Range.checkNullArguments(range1, range2); if (!range1.overlaps(range2)) { throw new NotOverlapException(); } final T start = range1.getStart().compareTo(range2.getStart()) >= 0 ? range1 .getStart() : range2.getStart(); final T end = range1.getEnd().compareTo(range2.getEnd()) <= 0 ? range1 .getEnd() : range2.getEnd(); return Range.between(start, end); } /** * 2つの範囲の和(共通部分で連結したもの)の範囲オブジェクトを求める.<br> * 2つの範囲が共通部分を持たない場合、{@link Range.NotOverlapException}がスローされる。 * * @param range1 * 範囲1 * @param range2 * 範囲2 * @return 連結された範囲オブジェクト * @throws NotOverlapException * 2つの範囲オブジェクトに重なる部分がない */ public static <T extends Comparable<T>> Range<T> sumOf( final Range<T> range1, final Range<T> range2) { Range.checkNullArguments(range1, range2); if (!range1.overlaps(range2)) { throw new NotOverlapException(); } final T start = range1.getStart().compareTo(range2.getStart()) <= 0 ? range1 .getStart() : range2.getStart(); final T end = range1.getEnd().compareTo(range2.getEnd()) >= 0 ? range1 .getEnd() : range2.getEnd(); return Range.between(start, end); } /** * 2つの範囲の積(共通部分)の範囲オブジェクトを求める.<br> * * @param other * 対象の範囲 * @return 共通部分をあらわす範囲オブジェクト * @see Range#productOf(Range, Range) */ public final Range<T> and(Range<T> other) { return Range.productOf(this, other); } /** * 2つの範囲の和(共通部分で連結したもの)の範囲オブジェクトを求める. * * @param other * 対象の範囲 * @return 連結された範囲オブジェクト * @see Range#sumOf(Range, Range) */ public final Range<T> or(Range<T> other) { return Range.sumOf(this, other); } /** * 2つの範囲オブジェクトに重なるところがあるかチェックする.<br> * 「重なっている」と見なせるケースは以下の通り。 * <ol> * <li>thisとotherが等しい</li> * <li>thisがotherを内包している</li> * <li>thisがotherに内包されている</li> * <li>thisの始点がotherの始点以上かつ終点以下</li> * <li>thisの終点がotherの始点以上かつ終点以下</li> * </ol> * * @param other * チェック対象の範囲オブジェクト * @return 重なる(true)/重ならない(false) */ public final boolean overlaps(final Range<T> other) { Range.checkNullArguments(other); return this.equals(other) || this.include(other) || other.include(this) || (this.getStart().compareTo(other.getStart()) >= 0 && this .getStart().compareTo(other.getEnd()) <= 0) || (this.getEnd().compareTo(other.getStart()) >= 0 && this .getEnd().compareTo(other.getEnd()) <= 0); } /** * 引数で与えられた値が範囲に含まれるかどうかチェックする. * * @param value * チェック対象の値 * @return 含まれる(true)/含まれない(false) */ public final boolean include(final T value) { Range.checkNullArguments(value); return this.getStart().compareTo(value) <= 0 && value.compareTo(this.getEnd()) <= 0; } /** * 引数で与えられた範囲がレシーバ側に含まれるものであるかどうかチェックする. * * @param other * チェック対象の値 * @return 含まれる(true)/含まれない(false) */ public final boolean include(final Range<T> other) { Range.checkNullArguments(other); return this.getStart().compareTo(other.getStart()) <= 0 && other.getEnd().compareTo(this.getEnd()) <= 0; } /** * 2つの範囲が意味的・機能的にまったく等しいかどうかをチェックする.<br> * 2つの範囲の始点と終点がそれぞれ等しい(equalsメソッドにより判定される)場合、範囲オブジェクトもまた等しいと判定される。 * * @param other * チェック対象の値 * @return 等しい(true)/等しくない(false) */ public final boolean equals(final Object other) { if(other != null && other instanceof Range){ final Range<?> otherRange = (Range<?>) other; return this.getStart().equals(otherRange.getStart()) && this.getEnd().equals(otherRange.getEnd()); }else{ return false; } } /** * 引数で与えられたリストのうち、範囲に適合する要素のみからなる新しいリストを生成する. * * @param target * もとのリスト * @return 新しいリスト */ public final List<T> selectFrom(final Iterable<T> target) { final List<T> result = new ArrayList<T>(); for (final T element : target) { if (this.include(element)) { result.add(element); } } return result; } /** * 引数で与えられたリストのうち、範囲に適合しない要素のみからなる新しいリストを生成する. * * @param target * もとのリスト * @return 新しいリスト */ public final List<T> rejectFrom(final Iterable<T> target) { final List<T> result = new ArrayList<T>(); for (final T element : target) { if (!this.include(element)) { result.add(element); } } return result; } @Override public final String toString() { return this.getStart().toString() + ".." + this.getEnd().toString(); } private static final void checkNullArguments(Object... args) { for (final Object obj : args) { if (obj == null) { throw new IllegalArgumentException(); } } } /** * 2つの範囲オブジェクトに、重なる部分のないことをあらわす例外 */ private static final class NotOverlapException extends RuntimeException { private static final long serialVersionUID = -7712951059270375953L; } }