web-dev-qa-db-ja.com

Javaの関数オブジェクトまたは関数の理解を助ける

誰かがファンクタとは何かを説明し、簡単な例を提供できますか?

26
jlss4e

関数オブジェクトはそれだけです。オブジェクトと関数の両方である何か。

余談:関数オブジェクトを「ファンクター」と呼ぶことは、この用語の深刻な乱用です。異なる種類の「ファンクター」は、数学の中心的な概念であり、コンピューターサイエンスで直接的な役割を果たすものです(「Haskellファンクター」を参照)。この用語はMLでも少し異なる方法で使用されているため、これらの概念のいずれかをJava(これは可能です!)で実装しているのでない限り、この用語の使用を中止してください。単純なことは複雑になります。 。

答えに戻る:Javaには「ファーストクラス関数」がありません。つまり、関数を引数として関数に渡すことはできません。これは、構文的に、バイトコード表現。型システムには「関数コンストラクタ」がありません

つまり、次のようなものは作成できません。

 public static void tentimes(Function f){
    for(int i = 0; i < 10; i++)
       f();
 }
 ...
 public static void main{
    ...
    tentimes(System.out.println("hello"));
    ...
 }

ボタンをクリックすることで「コールバック」関数を関連付けることができるグラフィカルユーザーインターフェイスライブラリのようなものを実行できるようにしたいので、これは本当に迷惑です。

どうしようか?

さて、一般的な解決策(他のポスターで説明)は、呼び出すことができる単一のメソッドでインターフェースを定義することです。たとえば、Javaは、これらの種類のものに対して常にRunnableというインターフェースを使用します。次のようになります。

public interface Runnable{
    public void run();
}

これで、上記の例を書き直すことができます。

public static void tentimes(Runnable r){
    for(int i = 0; i < 10; i++)
       r.run();
}
...
public class PrintHello implements Runnable{
    public void run{
       System.out.println("hello")
    }
}
---
public static void main{
    ...
    tentimes(new PrintHello());
    ...
 }

明らかに、この例は不自然なものです。匿名の内部クラスを使用してこのコードをもう少し良くすることができますが、これは一般的な考え方を理解します。

ここでこれが機能しなくなります。Runnableは、引数を取らず、何も返さない関数でのみ使用できるため、ジョブごとに新しいインターフェースを定義することになります。たとえば、モハマドファイサルの回答のインターフェイスComparatorです。より一般的なアプローチを提供し、構文を採用することは、Java 8(Javaの次のバージョン))の主要な目標であり、Java 7.これは、ラムダ計算の関数抽象化メカニズムにちなんで「ラムダ」と呼ばれます。ラムダ計算は(おそらく)最も古いプログラミング言語であり、コンピュータサイエンスの多くの理論的基礎です。AlonzoChurch(1コンピュータサイエンスの主な創設者の1人)が発明しました。彼は関数にギリシャ文字のラムダを使用したため、名前が付けられました。

関数型言語(LISP、ML、Haskell、Erlangなど)、ほとんどの主要な動的言語(Python、Ruby、JavaScriptなど)およびその他のアプリケーション言語(C#、Scala、Go、Dなど)を含むその他の言語「ラムダリテラル」の何らかの形式をサポートします。 C++にも自動メモリ管理がなく、スタックフレームが保存されないため、C++にも現在は(C++ 11以降)多少複雑です。

39
Philip JF

ファンクタは、関数であるオブジェクトです。

関数はJavaの第一級オブジェクトではないため、Javaにはそれらがありません。

しかし、コマンドオブジェクトのようなインターフェイスでそれらを近似することができます。

public interface Command {
    void execute(Object [] parameters); 
}

2017年3月18日に更新:

私が最初にこれを書いてから、JDK 8はラムダを追加しました。 Java.util.function パッケージには、いくつかの便利なインターフェースがあります。

14
duffymo

毎回のチェックからファンクター、Java 8ラムダ(一種))まで

問題

adapts an AppendableWriter に変換するこの例のクラスを見てみましょう。

_   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();
      }
   }
}
_

すべてのAppendablesがFlushableまたはCloseableであるとは限りませんが、それらも閉じてフラッシュする必要があります。したがって、Appendableオブジェクトの実際のタイプは、flush()およびclose()へのすべての呼び出しでチェックする必要があり、実際にそのタイプである場合、キャストされて関数と呼ばれます。

確かに、これは最高の例ではありません。close()はインスタンスごとに1回だけ呼び出され、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();
      }
   }
}
_

これらの「ヘビーデューティー」チェックは1回だけ行われるようになったので、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 になるまでは、そうではありません。したがって、8以前ではどのように行うのですか? Javaのバージョン?

ファンクタ

Functor を使用します。ファンクターは基本的にはラムダですが、オブジェクトにラップされています。関数をパラメーターとして他の関数に渡すことはできませんが、 オブジェクトは。本質的に、ファンクターとラムダは 関数を渡す

では、ライターアダプタにFunctorを実装するにはどうすればよいでしょうか。私たちが知っているのは、close()およびflush()は、CloseableおよびFlushableオブジェクトでのみ有用であることです。そして、一部のAppendablesは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が ないFlushableまたは ないCloseable、何を保存する必要がありますか?

何もしない

何もしないFunctor ...

_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 からのものです。この例の完全に機能し、完全に文書化されたバージョン(テスト関数を含む)は、質問の下部(回答の上)にあります。

列挙型でファンクタを実装する

Writer-Appendableの例を残して、Enumを使用してFunctorsを実装する別の方法を見てみましょう。

例として、この列挙型には各基本方向に対して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オブジェクトが必要です(これはインターフェースですが、抽象クラスの場合もあります):

インターフェースMoveInDirection {
 void move(int steps); 
}

このインターフェースには、方向ごとに1つずつ、4つの具体的な実装があります。以下は北の簡単な実装です:

クラスMoveNorthはMoveInDirection {
を実装しますpublic public move(int steps){
 System.out.println( "Moved" + steps + "steps north。"); 
} 
}

このFunctorの使用は、次の単純な呼び出しで行われます。

CardinalDirection.WEST.move(3);

これは、この例では、これをコンソールに出力します。

3ステップ西に移動しました。

そして、これが完全に機能する例です:

_/**
   <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 
西に3ステップ移動しました。
北に2ステップ移動しました。
東に15ステップ移動しました。

Java 8でまだ始めていないので、まだLambdasセクションを書くことはできません:)

6
aliteralmind

関数適用の概念を取る

f.apply(x)

x.map(f)

x aを呼び出すfunctor

interface Functor<T> {
    Functor<R> map(Function<T, R> f);
}
1
user2418306