質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.35%

javaの型安全と多態性について

解決済

回答 4

投稿 編集

  • 評価
  • クリップ 2
  • VIEW 2,424
退会済みユーザー

退会済みユーザー

javaの型安全と多態性についての質問です。
勉強していて、相反する(あるいは相反し得る)考えだと思ったのですが、皆様はどのようにお考えなのかと思い、質問させていただきます。
多態性は変数やインスタンスを曖昧に捉えることで、メリットを得ようとする考え方です。
例えば、コレクションの中身を表示するようなメソッドがあったとして、

public void prints(List<String>) {};

public void prints(ArrayList<String>) {};

上ではArrayListもLinkedListでも同様に表示できますが、下ではArrayListしか表示させることができません。
また、コレクションを宣言するときは
List<String> names = new ArrayList<String>();
のように書くことが推奨されています。

一方、型安全はこれとは逆に変数には可能な限り厳密な型を指定しようという考え方です。

public void prints(String a, int b){};

のようなメソッドを

public void prints(Object a, Object b){};

のように書いてしまうと、
prints("ああ",3);
と呼ばなければならないところを
prints(3,"ああ");
と書いてしまった時にコンパイルエラーが出ず、実行する時にエラーが出るようになり、望ましくありません。

上記の例を見ていると、多態性は曖昧に捉えることでメリットを得ているのに対し、型安全では厳密に捉えた方が良いとということがわかります。
この二つって相容れないものだと思うのですが、実際どうなんでしょうか。

これに対する皆さんの考えを教えてください。

補足
なんだか意図がうまく伝わっていない気がしたので、補足しておきます。
型安全は型によって、安全を担保しようという考え方で、enumやジェネリクスもこれに当たります。
enumによって、ある型に入れることのできる候補というのは制限することができますし、ジェネリクスも同様で、ArrayList<String>だとString型しか入りません。
一方、多態性はアップキャストすることによるメリットを享受しようという考えに基づいています。
まとめると、多態性は曖昧に捉えており、型安全は厳密に範囲を狭めているということになるわけですが、これらのコンセプトって相容れないものだと思うのです。
しかしながら、現実のプログラミングにおいて、この二つの考えって共存しているとは思うのですが、一つの部分(ステートよりもさらに小さな単位で、例えば評価式)に着目してみると、どちらかしか使ってないと思うのです。
つまり、多態性と型安全の両方のコンセプトを一つ部分に取り込むことはできないと思うのですが、その使い分けについて、教えてくださいというのが私の質問だったのです。
書き方が悪かったですね。
申し訳ないです。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • argius

    2016/08/07 23:55

    ArrayList<String> names = new ArrayList<String>();と宣言してもprints(List<String>)は呼べるので、「したがって」の例として適切でないように思います。

    キャンセル

  • 退会済みユーザー

    退会済みユーザー

    2016/08/08 08:53

    確かに、適当ではないですね。
    ご指摘ありがとうございます。

    キャンセル

  • iwamoto_takaaki

    2016/08/08 10:24

    > そうすると、「型を絞り込むことにより得られるメリット」と「型を曖昧にすることによりメリット」の両方が存在すると思うのですが、それらをどのように区別しているのか、というのが私の質問になります。

    この点は、質問に追記すべきだと思います。ほかの方が検索するかもしれないからです。また、それにより、追加の回答が得られます。

    キャンセル

回答 4

checkベストアンサー

+6

相反する(あるいは相反し得る)考えだと思ったのですが、皆様はどのようにお考えなのかと思い、質問させていただきます。
まとめると、多態性は曖昧に捉えており、型安全は厳密に範囲を狭めているということになるわけですが、これらのコンセプトって相容れないものだと思うのです。

「型安全性」と「多態性」は同じ評価軸上にある対立概念ではなく、それぞれが独立した概念であり、むしろ相補的なものと解釈しています。

多態性は変数やインスタンスを曖昧に捉えることで、メリットを得ようとする考え方です。
一方、多態性はアップキャストすることによるメリットを享受しようという考えに基づいています。

私とは解釈が異なります。Javaの文脈で多態性(Polymorphism)という場合、クラス継承やインターフェイス実装によるサブタイピングを意味するかと思います。ここでは、変数やインスタンスを「曖昧に捉える」ことが本質ではなく、変数やインスタンス間での「共通仕様を型(親クラス/インターフェイス)として表現する」ことです。共通の仕様を“型”によって表現することで、(コンパイル時に)無関係な型を渡せない安全なAPI仕様を設計できます。このように、型安全性と多態性は両立しえます。

逆に、(コンパイル時の)型安全性が無い多態性も実現可能です。Java言語ではリフレクション(Reflection)を使えば、任意の型に対して特定のメソッド呼び出しを試行できますが、コンパイル時には一切の型システムからのサポート(型検査)を受けられません。もちろん実行時には例外が送出さるのですがが、ここではコンパイル時の型安全性を損ねた多態性が実現されています。

一方、型安全はこれとは逆に変数には可能な限り厳密な型を指定しようという考え方です。

こちらも解釈が異なります。型安全(type safe)という単語はその文脈によって解釈の幅が広いようですが、型システムの原義では、型安全=「型システムによりプログラムが異常状態に陥らないことを保証する」という意味合いのようです。ここで言う“異常状態”は、対象とするプログラミング言語やシステムにより異なっており、Java言語それ自体では「未定義動作により任意のメモリ領域をアクセスしないところ」までを保証します。一方で、実行時に型に関する例外(例:ClassCastException, NullPointerException)が発生しないという型安全性はJava言語それ自身では達成できず、ライブラリのインターフェイス(API)設計の範疇となります。

型安全は型によって、安全を担保しようという考え方で、enumやジェネリクスもこれに当たります。
enumによって、ある型に入れることのできる候補というのは制限することができますし、ジェネリクスも同様で、ArrayList<String>だとString型しか入りません。

ご指摘の通りenumやジェネリクスは、型に関する例外送出やプログラム不整合を避けるために、コンパイル時の型検査が可能な仕組みとして導入されました。

  • enumは相関の無い列挙値を、型として区別できるようにする仕組みです。型安全性が向上します。(多態性には直接寄与しませんが、関数オーバーロードによるアドホック多相と組み合わせることはできます。)
  • ジェネリクスにより、共通の振る舞いを持つが型の一部が異なるという仕様を、コンパイル時に型検査できるようになります。ここでも多態性(正確にはパラメトリック多相)と型安全性が両立しています。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/08/08 11:41

    なるほど、目からウロコですね。
    多態性の本質は「共通仕様を型として表現する」ことであり、そのようにしておけば無関係なインスタンスや変数が入り込む余地はなく、型安全も両立している。
    という解釈でよろしいでしょうか。

    一方、「型安全」という言葉に対する解釈ですが、yohhoyさんは「型に関して、プログラムが異常な動作をしないようにする」という意味合いなので、私のいう「型を厳密に絞り込むこと」では意味が狭すぎるということでしょうか?

    キャンセル

  • 2016/08/08 11:47 編集

    > 多態性の本質は「共通仕様を型として表現する」ことであり、そのようにしておけば無関係なインスタンスや変数が入り込む余地はなく、型安全も両立している。

    おおむね「はい」。多態性にはいくつか構造的な種類が存在しており、サブタイピング多態(多相)の説明としては上記の通りです。

    > 「型を厳密に絞り込むこと」では意味が狭すぎる

    狭い/広いという程度問題ではなく、互いの論点が少しずれていると思います。billさんの主張は「型安全のためには型を具象化せよ」ですが、私の回答では「型システムを使って変なことを防ぐ=型安全」と言っているだけです。

    追記:今回の回答にあたり http://togetter.com/li/376541 こちらの議論がためになりました。型安全性は、狭義には「型に"関する"安全性」を指しますが、型システムとして表現可能な概念であれば、必ずしも型自身の安全性に限る必要はありません。例えば、Rustというプログラミング言語では、型にデータの有効期間という追加情報が埋め込まれており、これにより「データ競合が生じないという安全性」を型システムを使って保証します。(私は詳しくないのですが)Haskell言語も強力な(≒表現力の高い)型システムにより、型に関すること以上の安全性を保証できる能力があります。

    キャンセル

+2

型安全とは簡単にいうとキャストに失敗する恐れがないことです。

多態の時には基底クラス方向(objectクラス)にむかってキャストします。この方向であれば、コンパイラが型の安全性をチェックしてくれます。つまり型安全です。

型安全にならないのは、派生クラスにキャストする場合です。型安全にするにはこれ 避けましょうという話です。

ちなみにそれぞれ、基底クラス方向へのキャストをアップキャスト、派生クラス方向へのキャストをダウンキャストと呼びます。


補足を受けて追記します。

yohhoyさんの意見に全面的に賛成します。かなり詳しいです。
型安全と多態は別の概念だし、共存可能で、ぶつかるのは例外的だとおもいます。

型安全の話ついでに多態について。
アップキャストやジェネリックは方安全、つまり使ってもなんら問題は起こらないのでどんどん使うことをお勧めします。

型情報を厳密にすると、ほかの型でも同じようなメソッドを組むことになり、調査負荷の増大、メンテ漏れ、結果バグの発生といった問題(技術的負債)を抱えることになります。書いた時の必要とされていたかというのは気にせず、メソッドとして必要最小限な適切な型で受けるようにすることをお勧めします。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/08/08 18:56

    実用的なアドバイスありがとうございます。
    「必要最小限な適切な型」を引数に設定しておけば、多態性のメリットも得られるし、型の安全性も担保されるわけですね。

    キャンセル

  • 2016/08/09 00:13

    そうです。

    したがって、public void prints(Object a, Object b){}; の例は、内部でダウンキャストが必要なので不適切ということになります。

    public void prints(List<String>) {}; の例では、問題なければこちらの方が良いということになります。

    せっかく例があったのに言及を漏らしてました。

    キャンセル

+1

CS学んだわけではないので、
もっと詳しい方マサカリ投げてください。


意図を外した回答

> 多態性は曖昧に捉えることでメリットを得ている

ArrayListはListですが、
ObjectStringではありません。

Listを要求していて、内部でListとして扱うなら
ArrayListを渡しても問題無いですね。

Objectを要求していて、内部でStringとして扱うなら
String以外渡すとエラーなのにString以外を渡せるので問題ですね。

Listを要求していて、内部でArrayListとして扱うなら
ArrayList以外のListを渡すとエラーなのにArrayList以外のListを渡せるので問題ですね。

Objectを要求していて、内部でObjectとして扱うなら
Stringを渡しても問題無いですね。

ArrayListはListとしての振る舞いを持ちますね、
ArrayListをListとして扱えるのは多態の例です。

ObjectStringとしての振る舞いを持ちませんね、
ObjectStringとして扱えるのは多態の例ではないです。

型安全について不安になったのでちょっと調べました。

言葉の定義の問題なので、質問の意図とは外れますが、

型安全はこれとは逆に変数には可能な限り厳密な型を指定しようという考え方です。

型安全をこう捉えるなら、多態性とは確かに相反しますが、
そもそも「型安全」は
「型に対する不正な操作を実行時に検出して異常終了させる」も安全側に含みます。
つまり、実行時にすらエラーを吐かないものを「型が安全でない」というらしいです。

静的型付けか動的型付けかは型安全性と関係ないです。


長ったらしいので簡潔に纏めると
あなたの型安全(と多態)の認識がズレてませんか?

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/08/08 09:29

    わかりました。
    うーん、他の言語を知らないので、あまりよくわからないのですが、変数を宣言するのが静的型付けで、変数を宣言しないのが動的型付けだと認識していたのですが、それは適当ではないということですよね?


    そうすると、「型を絞り込むことにより得られるメリット」と「型を曖昧にすることによりメリット」の両方が存在すると思うのですが、それらをどのように区別しているのか、というのが私の質問になります。

    定義が間違っていたことに関しては私の勉強不足でした。
    ご指摘ありがとうございました。

    キャンセル

  • 2016/08/08 11:47

    「型を曖昧」と言われても、ArrayListはListなんだから曖昧でもなんでもないと思うんですよ。
    Listで充分だからListを要求しているところにListであるArrayListを渡すのと、
    Stringが必要なところをObjectとして要求してStringではないInterger渡すのでは全然状況が違いますよね。

    キャンセル

  • 2016/08/08 18:58

    おっしゃる通りです。
    回答ありがとうございました。

    キャンセル

+1

  • ArrayListクラス
    ensureCapacity()
    removeRange()
    trimToSize()

あたりのメソッドはListでは宣言されていません。
なのでこのメソッドが使えないというデメリットがあります。
ArrayList<E>
こういったメソッド一覧を用いてプログラムを書こうとしたときに、これらのメソッド呼び出しが失敗して困る原因になりかねません。

APIとして他人に提供するならListのほうがいいかもしれませんが、自分で作ったプログラムならArrayを消すだけで修正できるので最初からArrayListで宣言、利用してしまったほうがミスが少ないでしょう。

ただし、それぞれListのメソッド、Objectのメソッドしか利用しない場合はそのクラスの型として宣言したほうが多くを受け入れることができます。
メソッドの利用目的に合わせて、その中でできるだけ広い範囲をカバー(スーパークラスの型で受け取り)したほうがいいということでしょう。

  • まとめると
    今回しか利用しないメソッド→その状況以外使用しないので厳密に
    多くのクラスから利用するメソッド→広い範囲での利用を想定して曖昧?に
    必ずスーパークラスのメソッドしか利用しないメソッド→スーパークラスの型を指定
    スーパークラス以外のメソッドを利用する可能性があるメソッド→サブクラスの型を指定

蛇足ですが、
public void prints(String a, int b){};
public void prints(Object a, Object b){};
この二つだと型推論で上は(String, int)、下は(String, Integer)となって、プリミティブ型のintとラッパークラスのオブジェクト型のIntegerで型が厳密には違ってくるんじゃないかと。
例として挙げるには少し不適切かもしれません。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/08/07 10:16

    使い方が決まっているメソッドに関しては、型安全の考え方を採用し、使い方に幅がある、もしくはわからない場合は多態性の考えを取り入れるわけですね。
    なるほど、合理的ですね。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.35%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る