プログラミングに興味のある方ならば、オブジェクト指向という言葉は聞いたことがあると思います。よく使われているオブジェクト指向プログラミング言語にC++があります。C++はオブジェクト指向プログラミングができるようにC言語を拡張したものですが、度重なる機能追加により複雑な言語仕様になってしまいました。このため、初心者がオブジェクト指向を学ぶには適していないと言われています。
Java のオブジェクト指向はC++よりも簡単だといわれていますが、C++と同じようにバージョンアップするたびに新しい機能が追加されるので、Java のオブジェクト指向機能もかなり複雑になりつつあります。初心者 (M.Hiroi を含む) からみると、どちらのオブジェクト指向も大変難しい、と思われている方が多いのではないでしょうか。オブジェクト指向を学ぶには Ruby のようなスクリプト言語のほうが適しているのかもしれません。
困ったことに、Java はオブジェクト指向プログラミング言語なので、オブジェクト指向を避けて通るわけにはいきません。そこで、簡単なプログラムを作りながら、少しずつステップアップしていきましょう。まずは最初に、一般的なオブジェクト指向について簡単に説明します。
プログラムを作る場合、全体を小さな処理に分割して、一つ一つの処理を作成し、それらを組み合わせて全体のプログラムを完成させます。このとき、基本的な部品となるのが関数です。つまり、処理を関数単位で分割して、それらを組み合わせてプログラムを作るわけです。もともと関数の役割は、入力されたデータを処理してその結果を返すことです。つまり、関数は機能を表しているのです。このため、全体を小さな処理に分割するにしても、機能単位で行われることが普通です。
オブジェクト指向プログラミングでは、関数ではなく「オブジェクト (object)」を部品として扱います。たとえば、えんぴつを考えてみましょう。えんぴつには、色、長さ、固さ、などいろいろな性質があります。そして、えんぴつを使って文字を書いたり、絵を描いたりすることができます。プログラムでは、このような性質をデータで表し、機能を関数で表すことになります。オブジェクトとは、このデータと関数を結び付けたものと考えてください。
いままでのプログラミング言語では、データと関数を別々に定義するため、それを一つのオブジェクトとして表すことができません。えんぴつで文字を書くにも、えんぴつの種類をチェックして文字を書くようにプログラムしなければいけません。ところが、オブジェクトはデータと関数を結び付けたものなので、自分がなにをしたらよいかわかっています。えんぴつオブジェクトに文字を書けと命じれば、それが赤えんぴつのオブジェクトであれば文字は赤に、黒えんぴつのオブジェクトであれば黒い文字になるのです。
このように、オブジェクトはデータと関数を一つにまとめたものです。従来のプログラミングが全体を機能単位で分割するのに対し、オブジェクト指向プログラミングでは全体をオブジェクト単位に分割して、それを組み合わせることでプログラムを作成します。
ところで、データと関数を結び付けることは、従来のプログラミング言語でも可能です。オブジェクト指向はプログラミングの考え方の一つであり、C++のようなオブジェクト指向言語を使わなくても、たとえばC言語でもその考え方にしたがってプログラムを作成すれば、オブジェクト指向プログラミングになります。
実際、オブジェクト指向には様々な考え方があり、いろいろなオブジェクト指向プログラミング言語が存在します。ですが、データと関数を一つにまとめたものをオブジェクトとして扱うという基本的な考え方は、オブジェクト指向言語の元祖と言われる Smalltalk でも、C++ や Java でも同じです。
次は、一般的なオブジェクト指向機能について簡単に説明します。
「クラス (class)」はオブジェクトの振る舞いを定義したものです。ここでデータを格納するための変数や、それを操作する関数が定義されます。Java はこの変数を「フィールド変数」といいます。他の言語ではメンバ変数とかインスタンス変数と呼ぶことがあります。そして、クラスの中で定義された関数を「メソッド (method)」といいます。メソッドはあとで説明します。
クラスはオブジェクトの設計図にあたるもので、オブジェクトの「雛形」と呼ぶこともあります。クラスはオブジェクトの振る舞いを定義するだけで、アクセスできる実体はなにも生み出していない、ということに注意してください。ただし、プログラミング言語によってはクラスに実体を持たせていることもあります。Java のフィールド変数やメソッドを static 宣言すると、クラスに直接アクセスすることができます。
このクラスから実体として作り出されるのが「インスタンス (instance)」です。このインスタンスを「オブジェクト」と考えてください。インスタンスを生成する方法は、当然ですがプログラミング言語によって違います。たとえば C++や Java は new を使います。図 1 を見てください。
┌─ class Foo ─┐ ┌─ instance ─┐ │ │ │ │ │ 設計図 │─ インスタンスの生成 →│ 実体 │ │ │ │ │ └────────┘ └───────┘ │ │ │ ┌─ instance ─┐ │ │ │ └───── インスタンスの生成 →│ 実体 │ │ │ └───────┘ 図 1 : クラスとインスタンスの関係
クラスはオブジェクトの定義を表すものですから、Foo というクラスは一つしかありません。これに対し、インスタンスはクラスから生み出されるオブジェクトです。たとえば、クラス Foo に new を適用することで、いくつでもインスタンスを生み出すことができるのです。クラスは設計図であり、それに従って作られるオブジェクトがインスタンスと考えるとわかりやすいでしょう。
メソッドはオブジェクトと結びついた関数です。オブジェクト指向プログラミングでは、ほかの関数から直接オブジェクトを操作することはせず、メソッドを呼び出すことで行います。メソッドは、クラスが異なっていれば同じ名前のメソッドを定義することができます。たとえば、クラス Foo1 にメソッド bar() が定義されていても、クラス Foo2 に同名のメソッド bar() を定義することができます。
そして、ここからが重要なのですが、あるオブジェクトに対してメソッド bar() を呼び出した場合、それが Foo1 から作られたオブジェクトであれば、Foo1 で定義された bar() が実行され、Foo2 から作られたオブジェクトであれば、Foo2 で定義された bar() が実行されるのです。このように、オブジェクトが属するクラスによって、実行されるメソッドが異なるのです。この機能を「ポリモーフィズム(polymorphism)」と呼びます。これにより、オブジェクトは自分が行うべき適切な処理を実行できるわけです。
┌─ class Foo1 ─┐ ┌─ instance ─┐ │ │ │ │ │ 設計図 │─── 生成 ───→│ 実体 │ │ │ │ │ │ │ └───────┘ │┌─ method ─┐│ ↑ ││ ││ │ ││ bar()←─┼┼─── アクセス ─────┘ ││ ││ │└──────┘│ └────────┘ 図 2 : クラス、インスタンス、メソッドの関係
クラス、インスタンス、メソッドの関係は図 2 のようになります。クラスという設計図が中心にあり、そこからインスタンスが生み出され、メソッドを使ってインスタンスを操作する、という関係になります。
さて、一般的な話はここまでにして、Java のオブジェクト指向機能に目を向けてみましょう。Java は class 文でクラスを定義します。class 文の構文を図 3 に示します。
class ClassName extends SuperClassName { ... } 図 3 : class 文の構文
class の次にクラス名を指定し、その次の extends で他のクラスを指定すると、そのクラスの機能を引き継ぐことができます。この機能を「継承 (inheritance)」といいます。継承は次回以降で詳しく説明します。extends を省略すると暗黙のうちに Object というクラスが継承されます。Java のすべてのクラスは直接的もしくは間接的に Object を継承しています。そのあとのブロック { } の中でフィールド変数やメソッドを定義します。
class の前には public を付けることができます。public 宣言されたクラスは他のどのクラスからでも使用することができます。無指定の場合は、同じファイル内のクラスからしか使用することができません。
一番簡単なクラス定義を示しましょう。JShell で次のように入力してください。
jshell> class Foo {} | 次を作成しました: クラス Foo jshell> Foo a = new Foo() a ==> Foo@2b71fc7e jshell> var b = new Foo() b ==> Foo@3eb07fd3
一般に、クラス名は英大文字から始めることが多いので、名前は Foo としました。Foo はクラス名しかありませんが、これでも立派なクラスなのです。Java は new を付けて Foo() と呼び出すと、そのクラスのインスタンスを生成して返します。Foo() を「コンストラクタ」といって、呼び出すときに引数を指定することができます。これはあとで説明します。なお、変数の宣言に var を使ってもかまいません。
Java はクラス内で宣言された変数をフィールド変数として扱います。他の言語ではインスタンス変数とかメンバ変数といいます。フィールド変数の実体はインスタンスに割り当てられます。static を付けるとクラス変数として扱われるため、インスタンスには割り当てられません。クラス変数についてはあとで説明します。なお、フィールド変数とクラス変数は var で宣言することはできません。ご注意くださいませ。
Java のフィールド変数は public, protected, private という 3 通りのアクセス制御を行うことができます。public はどのクラスからでもアクセスすることできます。protected は同じクラスとそれを継承したクラス (サブクラス) からアクセスすることができ、private は同じクラスからしかアクセスすることができません。指定を省略した場合、同じファイル内で定義されたフィールド変数であれば、異なるクラスであってもアクセスすることができます。
フィールド変数のアクセスは次の形式で行います。
object.variable
インスタンス object の後ろにドット ( . ) を付けて、その後ろにフィールド変数名を指定します。同じクラス内であれば、object を省略してフィールド変数名だけでアクセスすることができます。簡単な例を示しましょう。
jshell> class Foo { int x = 1; } | 次を作成しました: クラス Foo jshell> var a = new Foo() a ==> Foo@2b71fc7e jshell> var b = new Foo() b ==> Foo@25f38edc jshell> a.x $4 ==> 1 jshell> b.x $5 ==> 1 jshell> a.x = 10 $6 ==> 10 jshell> b.x = 100 $7 ==> 100 jshell> a.x $8 ==> 10 jshell> b.x $9 ==> 100
クラス Foo のインスタンスを生成して変数 a と b にセットします。インスタンス a, b にあるフィールド変数 x は、それぞれ a.x, b.x でアクセスすることができます。a.x = 10 とすると a の x に 10 が代入され、b.x = 100 とすると b の x に 100 が代入されます。
ただし、オブジェクト指向でプログラムを作る場合、フィールド変数に直接アクセスすることはあまり行われません。フィールド変数の値を参照するメソッド (リーダー: reader) と値を更新するメソッド (ライター: writer) を用意して、それらのメソッドを経由してアクセスするのが一般的で、お行儀の良いプログラミングスタイルとされています。
Java はクラス内で定義された関数をメソッドとして扱います。static を付けるとクラスメソッドになります。インスタンスを操作するメソッドを定義する場合は static を付けないよう注意してください。Java のメソッドは public, protected, private という 3 通りのアクセス制御を行うことができます。これはフィールド変数のアクセス制御と同じです。指定を省略した場合のアクセス制御もフィールド変数と同じです。
簡単な例として、クラス Foo にフィールド変数 x のリーダーメソッドとライダーメソッドを定義しましょう。次の例を見てください。
jshell> class Foo { ...> int x = 1; ...> int getX() { return x; } ...> void setX(int n) { x = n; } ...> } | 次を作成しました: クラス Foo jshell> var a = new Foo() a ==> Foo@2b71fc7e jshell> var b = new Foo() b ==> Foo@25f38edc jshell> a.getX() $4 ==> 1 jshell> b.getX() $5 ==> 1 jshell> a.setX(20) jshell> b.setX(200) jshell> a.getX() $8 ==> 20 jshell> b.getX() $9 ==> 200
メソッド getX() がリーダーで、setX() がライターです。Java の場合、リーダーメソッドには接頭辞に get を、ライターメソッドには set を付けることが一般的なようです。これらのメソッドは static を付けていないので、インスタンスを操作する「インスタンスメソッド」になります。インスタンスメソッドは次の形式で呼び出します。
object.method(args, ...)
インスタンス object の後ろにドット ( . ) を付けて、メソッド名と引数を続けて書きます。同じクラス内であれば、object を省略してメソッド名だけで呼び出すことができます。
インスタンスを生成するとき、フィールド変数に初期値を代入できると便利です。Java はクラスと同じ名前のメソッドが定義されていると、インスタンスを生成するときにそのメソッドを呼び出して、インスタンスの初期化処理を行うことができます。このメソッドを「コンストラクタ (constructor)」といいます。
コンストラクタの構文を次に示します。
アクセス制御 クラス名(データ型 引数, ...) { 処理; ... }
コンストラクタは値を返さないので、返り値のデータ型を指定する必要はありません。あとは通常のメソッドと同じです。もちろん、多重定義も可能です。次の例を見てください。
jshell> class Foo { ...> int x; ...> Foo() { x = 1; } ...> Foo(int x) { this.x = x; } ...> int getX() { return x; } ...> void setX(int x) { this.x = x; } ...> } | 次を作成しました: クラス Foo jshell> var a = new Foo() a ==> Foo@2b71fc7e jshell> var b = new Foo(2) b ==> Foo@25f38edc jshell> a.getX() $4 ==> 1 jshell> b.getX() $5 ==> 2 jshell> a.setX(10) jshell> b.setX(20) jshell> a.getX() $8 ==> 10 jshell> b.getX() $9 ==> 20
引数なしのコンストラクタと引数が一つのコンストラクタを定義します。引数なしのコンストラクタでは、フィールド変数 x を 1 に初期化します。引数が一つのコンストラクタでは、引数 x の値でフィールド変数 x を初期化します。
メソッドの局所変数とフィールド変数名が同じ場合、Java は局所変数のアクセスを優先します。フィールド変数にアクセスする場合は this を付けてください。this はそのメソッドを呼び出したインスタンスを表す特別な変数です。コンストラクタ Foo(int x) とメソッド setX(int x) の仮引数は x なので、その値をフィールド変数 x に代入するには this.x = x とします。
変数 a のインスタンスは、コンストラクタ Foo() で生成されるので x の値は 1 になります。変数 b のインスタンスは Foo(2) で生成されるので x の値は引数で渡された 2 になります。
コンストラクタが未定義の場合、引数なしで何も処理しないコンストラクタが自動的に生成されます。これをデフォルトコンストラクタといいます。コンストラクタが一つでも定義されていると、デフォルトコンストラクタは自動的に生成されません。たとえば、コンストラクタ Foo(int n) { ... } を一つだけを定義すると、引数なしのコンストラクタは未定義になるので、Foo() の呼び出しはコンパイルエラーになります。ご注意くださいませ。
ところで、コンストラクタ、リーダーメソッド、ライターメソッドをまとめて「アクセスメソッド」といいます。フィールド変数に直接アクセスせず、アクセスメソッドを経由してデータの参照や代入を行うことを「データ抽象」とか「カプセル化」といいます。わざわざアクセスメソッドを用意するのは面倒なようですが、そのことによりクラスの詳細な内容をアクセスメソッドで包み隠すことができます。それだけプログラムも読みやすくなり、修正にも強いプログラムを作ることができます。
簡単な例として、点を表すクラスを作ってみましょう。名前は Point にしました。x 座標をインスタンス変数 x に、y 座標を変数 y に格納します。次のリストを見てください。
リスト : Point クラス (Point0.java) class Point { double x = 0.0, y = 0.0; // コンストラクタ Point() { } Point(double x, double y) { this.x = x; this.y = y; } // 距離を求める double distance(Point p) { double dx = x - p.x; double dy = y - p.y; return Math.sqrt(dx * dx + dy * dy); } } public class Point0 { public static void main(String[] args) { Point p1 = new Point(); Point p2 = new Point(10.0, 10.0); System.out.println(p1.distance(p2)); } }
メソッド distance() は Point クラスのインスタンス p を受け取り、その距離を計算します。sqrt() は平方根を求めるメソッドで、パッケージ java.lang のクラス Math に定義されています。このほかにも Math には便利な数学関数が多数用意されています。
あとは Point のインスタンスを生成して、変数 p1, p2 にセットして p1.distance(p2) で p1 と p2 の距離を計算します。実行結果は次のようになります。
$ javac Point0.java $ java Point0 14.142135623730951
ここで、インスタンスメソッドの呼び出しは、インスタンスによって適切なメソッドが選択されることに注意してください。たとえば、3 次元の座標を表す Point3D クラスを考えてみましょう。次のリストを見てください。
リスト : Point3D クラス (Point1.java) class Point { double x = 0.0, y = 0.0; // コンストラクタ Point() { } Point(double x, double y) { this.x = x; this.y = y; } // 距離を求める double distance(Point p) { double dx = x - p.x; double dy = y - p.y; return Math.sqrt(dx * dx + dy * dy); } } class Point3D { double x = 0.0, y = 0.0, z = 0.0; // コンストラクタ Point3D() { } Point3D(double x, double y, double z) { this.x = x; this.y = y; this.z = z; } // 距離を求める double distance(Point3D p) { double dx = x - p.x; double dy = y - p.y; double dz = z - p.z; return Math.sqrt(dx * dx + dy * dy + dz * dz); } } public class Point1 { public static void main(String[] args) { Point p1 = new Point(); Point p2 = new Point(10.0, 10.0); Point3D p3 = new Point3D(); Point3D p4 = new Point3D(10.0, 10.0, 10.0); System.out.println(p1.distance(p2)); System.out.println(p3.distance(p4)); } }
クラス Point3D は Point を 3 次元に拡張しただけです。Point でも Point3D でも距離を計算するメソッド distance() が定義されていることに注目してください。それでは、メソッド distance() を呼び出してみましょう。
$ javac Point1.java $ java Point1 14.142135623730951 17.320508075688775
ドットの左側のインスタンス p1, p3 によって適切なメソッドが呼び出され、ポリモーフィズムが働いているようにみえます。Perl, Python, Ruby などのように、変数を使用するときデータ型の宣言が不要なプログラミング言語を「動的型付け言語」といい、C/C++や Java などのように変数を使用するときデータ型の宣言が必要なプログラミング言語を「静的型付け言語」といいます。
たとえば Ruby の場合、p1.distance(p3) で呼び出されるメソッド distance() は、プログラムを実行するとき変数 p1 に格納されているオブジェクトの型 (クラス) によって決定されます。p1 が Point のインスタンスであれば、Point で定義されたメソッド distance() が呼び出されます。つまり、動的型付け言語のメソッド呼び出しはポリモーフィズムが働いている、と考えることができます。
これに対し、C/C++や Java などの静的型付け言語は、コンパイルの時点で呼び出すメソッドを可能な限り決定します。p1.distance(p2) の呼び出しは Point クラスのメソッドで、p3.distance(p4) は Point3D クラスのメソッドと決めることが可能です。この場合、動的型付け言語のようなポリモーフィズムは働いていませんが、オブジェクトのクラスによって呼び出すメソッドが決定される、ということにかわりはありません。
Java でプログラムの実行時にポリモーフィズムを働かせるには「継承」もしくは「インターフェース」という機能を使います。これは次回以降で説明します。
インスタンス変数は個々のインスタンス(オブジェクト)に割り当てられる変数です。その値はインスタンスによって変わります。クラスで共通の変数や定数を使いたい場合は、class 文の中で static 変数を定義します。これを「クラス変数」といいます。簡単な例を示しましょう。
jshell> class Foo { ...> int x = 1; ...> static int y = 2; ...> } | 次を作成しました: クラス Foo jshell> var a = new Foo() a ==> Foo@2b71fc7e jshell> var b = new Foo() b ==> Foo@25f38edc jshell> b.x = 2 $4 ==> 2 jshell> a.x $5 ==> 1 jshell> b.x $6 ==> 2 jshell> a.y $7 ==> 2 jshell> b.y $8 ==> 2 jshell> a.y = 10 $9 ==> 10 jshell> b.y $10 ==> 10 jshell> Foo.y $11 ==> 10 jshell> Foo.y = 20 $12 ==> 20 jshell> a.y $13 ==> 20 jshell> b.y $14 ==> 20
変数 y は static 宣言されているのでクラス変数になります。クラス変数は次のようにアクセスすることができます。
class.variable object.variable
class はクラス名、object はインスタンス、vaiable はクラス変数名を表します。クラス変数はインスタンスからでもアクセスすることができます。インスタンスを生成して変数 a, b にセットします。a.y と b.y とすると、クラス変数 y にアクセスします。a.y = 10 とすると、b.y と Foo.y の値も 10 になります。Foo.y = 20 とすると、a.y と b.y の値も 20 になります。
フィールド変数に final 修飾子を付けると、その変数は初期化されたあと値を書き換えることはできなくなります。つまり、定数として扱うことができます。簡単な例を示しましょう。
jshell> class Foo { ...> static final int x = 100; ...> } | 次を作成しました: クラス Foo jshell> Foo.x $2 ==> 100 jshell> Foo.x = 200 | エラー: | final変数xに値を代入することはできません | Foo.x = 200 | ^---^
クラス変数 x は final 宣言されているので定数として扱われます。クラス変数 x の値を参照することはできますが、値を代入するコードはコンパイルエラーになります。
メソッドは個々のインスタンスを操作する関数です。一般に、ユーザが定義するメソッドは引数のインスタンスを操作対象とし、クラスの動作にかかわることはありません。インスタンスを操作するメソッドを「インスタンスメソッド」といいます。これに対し、クラスの動作にかかわるメソッドを考えることができます。これを「クラスメソッド」といいます。Java はメソッドを static 宣言するとクラスメソッドになります。
たとえば、クラス Foo のクラス変数 y を操作するクラスメソッド getY(), setY() を作りましょう。次のリストを見てください。
リスト : クラスメソッド (sample56.java) class Foo { int x = 1; static int y = 2; // クラスメソッド static int getY() { return y; } static void setY(int y) { Foo.y = y; } // this は参照できない // コンストラクタ Foo() { } Foo(int x) { this.x = x; } // アクセスメソッド int getX() { return x; } void setX(int x) { this.x = x; } } public class sample56 { public static void main(String[] args) { Foo a = new Foo(); Foo b = new Foo(10); System.out.println(Foo.getY()); System.out.println(a.getY()); System.out.println(b.getY()); Foo.setY(20); System.out.println(Foo.getY()); System.out.println(a.getY()); System.out.println(b.getY()); a.setY(30); System.out.println(Foo.getY()); System.out.println(a.getY()); System.out.println(b.getY()); } }
$ javac sample56.java $ java sample56 2 2 2 20 20 20 30 30 30
クラスメソッドの呼び出しは次のように行います。
class.method(args, ...) object.method(args, ...)
class はクラス名、object はインスタンス、method() はクラスメソッドを表します。クラスメソッドはインスタンスからでも呼び出すことができます。インスタンスを生成して変数 a, b にセットします。Foo.getY(), a.getY(), b.getY() のどれでもクラスメソッド getY() を呼び出すことができます。
なお、クラスメソッド内で this を参照することはできません。局所変数とクラス変数が同じ名前の場合は、クラス名を指定してクラス変数にアクセスしてください。
Java のパッケージ java.math には任意精度の整数 (多倍長整数) を扱うクラス BigInteger が用意されています。次に示す関数をBigInteger を使って定義してください。
組み合わせの数 \({}_n \mathrm{C}_r\) を求める場合、次の公式を使うと簡単です。
(1) と (2) の公式を使うと簡単に (高速に) 答えを求めることができます。ただし、(3) の公式をそのままプログラムすると二重再帰になるので、大きな値を求めると時間がかかってしまいます。ご注意くださいませ。
パッケージ内に定義されているクラスやメソッドなどの名前は、次の方法でアクセスすることができます。
パッケージ名.名前
パッケージ名が長いとプログラムを記述するのが大変です。パッケージ名を省略して名前だけでアクセスできると便利ですね。このために用意されている構文が import です。
2 は「オンデマンドのインポート宣言」と呼ばれる方法で、そのパッケージからプログラムで必要となる名前をインポートします。import では、2 の方法よりも個別に名前を書く 1 の方法が推奨されているようです。
ちなみに、JShell では以下のパッケージがあらかじめインポートされています。
jshell> /imports | import java.io.* | import java.math.* | import java.net.* | import java.nio.file.* | import java.util.* | import java.util.concurrent.* | import java.util.function.* | import java.util.prefs.* | import java.util.regex.* | import java.util.stream.*
/imports は JShell がインポートしたパッケージを調べるコマンドです。
jshell> BigInteger fact(int n) { ...> if (n == 0) { ...> return BigInteger.ONE; ...> } else { ...> return BigInteger.valueOf(n).multiply(fact(n - 1)); ...> } ...> } | 次を作成しました: メソッド fact(int) jshell> fact(10) $4 ==> 3628800 jshell> fact(30) $5 ==> 265252859812191058636308480000000 jshell> fact(50) $6 ==> 30414093201713378043612608166064768844377641568960512000000000000 jshell> BigInteger fibo(int n) { ...> var a = BigInteger.ZERO; ...> var b = BigInteger.ONE; ...> while (n-- > 0) { ...> var c = a; ...> a = b; ...> b = b.add(c); ...> } ...> return a; ...> } | 次を作成しました: メソッド fibo(int) jshell> fibo(10) $8 ==> 55 jshell> fibo(40) $9 ==> 102334155 jshell> fibo(50) $10 ==> 12586269025 jshell> fibo(100) $11 ==> 354224848179261915075 jshell> fibo(200) $12 ==> 280571172992510140037611932413038677189525 jshell> BigInteger comb(int n, int r) { ...> if (n == r || r == 0) { ...> return BigInteger.ONE; ...> } else { ...> return comb(n, r - 1).multiply(BigInteger.valueOf(n - r + 1)).divide(BigInteger.valueOf(r)); ...> } ...> } | 次を作成しました: メソッド comb(int,int) jshell> comb(28,14) $14 ==> 40116600 jshell> comb(100,50) $15 ==> 100891344545564193334812497256