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

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

新規登録して質問してみよう
ただいま回答率
85.48%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Q&A

解決済

2回答

2889閲覧

「オーバーライドしません」というコンパイルエラーが発生する

programing13579

総合スコア1

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

0グッド

1クリップ

投稿2023/01/20 16:07

前提

Javaのオーバーライドやオーバーロードについて勉強しています。
勉強のためのサンプルコードを書くと、以下のエラーメッセージが発生しました。

実現したいこと

なぜ、このエラーが発生するのか、知りたいです。

ネットで調べたところ、このコンパイルエラーは、コンパイル前はオーバーロードとなるが、
コンパイル後はオーバーライドとなり、矛盾が生じるために発生するという認識です。

なぜ、このコードだとコンパイル前はオーバーロードとなるのか、
コンパイル後はオーバーライドとなるのか、知りたいです。

発生している問題・エラーメッセージ

名前の競合: 型 B のメソッド test(Set<String>) は型 A の test(Set<CharSequence>) と同じ erasure を持っていますが、オーバーライドしません

該当のソースコード

Java

1//A.java 2 3import java.util.List; 4import java.util.Set; 5 6public class A { 7 public List<Number> test (Set<CharSequence> s) { 8  return null; 9 } 10}

Java

1//B.java 2 3import java.util.ArrayList; 4import java.util.Set; 5 6public class B extends A { 7 public ArrayList<Integer> test(Set<String> s) { 8  return null; 9 } 10}

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

型イレージャ

jimbeさんが指摘されているように、この問題は型イレージャが原因で起こります。JVMはジェネリクスを知りません。

Javaの世界

Javaコンパイラはtestの引数の型Set<CharSequence>とSet<String>は別の型とみなします(非変)。testメソッドのシグネチャ(メソッド名と引数の型で決まる)はそれぞれ異なるので、メソッドがオーバーロードされています。

  • Aのメソッド ... List<Number> test(Set<CharSequence>)
  • Bのメソッド ... ArrayList<Integer> test(Set<String>)

JVMの世界

バイトコードが生成される時、型イレージャによってジェネリクスの型が消され、要素の型はraw型(Object)になります。ただし、それぞれのメソッド内部で、必要なキャスト(checkcast)はコンパイラが補います。このようにtestメソッドがオーバーライドされたことになります。

  • Aのメソッド ... List test(Set)
  • Bのメソッド ... ArrayList test(Set)

さて、これがコンパイルエラーにならず、仮にオーバーライドできたとすると、Aの型を持つ変数xにBのインスタンスを代入して x.test()を呼ぶとポリモーフィズムが働き、Bのtest()が呼ばれます。Aのtestの引数のSetにはStringBuilderを入れることができますが、Bのtest()は、処理の途中で、Setの要素をStringにキャストするのでエラーになります。

Java

1A x = new B(); 2Set<CharSequence> s = new HashSet<>(List.of(new StringBuilder())); 3List<Number> l = x.test(s);

JavaとJVMの解釈に不整合が生じ実行時エラーがおきるため、コンパイルエラーにしているのだと思います。

【メソッドのオーバーライドについて(追記)】

メソッドがオーバーライド可能な条件
・メソッドのシグネチャが同一(メソッド名が同じ,メソッドの引数が同じ型で並びが同一)
・メソッドの戻り値の型が同一型か共変型(サブクラス、またはインターフェイスを実装するクラス)
・メソッドのアクセスレベルが同じか緩い

まずtestの戻り値が同一型か共変(covariant)型かです。
ArrayListはListを実装するので共変型

ところがジェネリクスを適用、コレクションが共変でも型パラメータが異なると非変(異なる型)になります。
ArrayList<Integer>とList<Number>は非変(nonvariant)
Set<Number>とSet<Integer>は非変

従って、Javaの世界ではメソッドの引数の型が異なるのでオーバーロード
しかし、JVMの世界ではオーバーライド(ArrayListはListを実装するので共変型)

【自然な解決案(追記)】

Aで定義するNumber , CharSequenceは抽象クラスなので、Aも抽象クラスとするのが自然です。メソッドがオーバーロードと解釈されないようにクラスに型パラメータを定義します。以下のBのtest()は、A<String, Integer>の定義に従って、AのメソッドList<Integer> test(Set<String> s)をオーバーライドします。

Java

1// A 2import java.util.List; 3import java.util.Set; 4 5public abstract class A<T extends CharSequence, U extends Number> { 6 public abstract List<U> test(Set<T> s); 7} 8 9// B 10import java.util.ArrayList; 11import java.util.Set; 12import java.util.stream.Collectors; 13 14public class B extends A<String, Integer> { 15 @Override 16 public ArrayList<Integer> test(Set<String> s) { 17 return s.stream().map(x -> Integer.valueOf(x)).collect(Collectors.toCollection(ArrayList::new)); 18 } 19}

以下は参考とします

参考

testメソッドの目的は、Set<T> -> List<U>に変換することです。本質はコレクションに関係なく、TをUに変換することですから、継承によって T -> U を解決します。

Java

1import java.util.List; 2import java.util.Set; 3import java.util.stream.Collectors; 4 5public abstract class A<T extends CharSequence, U extends Number> { 6 public final List<U> test(Set<T> s) { 7 return s.stream().map(x -> convert(x)).collect(Collectors.toList()); 8 } 9 public abstract U convert(T t); 10} 11 12public class B extends A<String, Integer> { 13 public Integer convert(String a) { 14 return Integer.valueOf(a); 15 } 16}

この解決法をTemplate Methodパターンと呼びます。関数型インターフェースが登場してからTemplate Methodパターンは使わず、変換用の関数 T -> U を与えるようになりました。

Java

1public class A<T extends CharSequence, U extends Number> { 2 public List<U> test(Set<T> s, Function<T,U> f) { 3 return s.stream().map(x -> f.apply(x)).collect(Collectors.toList()); 4 } 5} 6public class B extends A<String, Integer> {}

もっと良い解決案があるかもしれません。
継承は使えないので、型パラメータを使ってコレクションの共通処理を記述するのが良いでしょう。

投稿2023/01/21 21:27

編集2023/01/23 10:35
xebme

総合スコア1083

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

xebme

2023/01/22 22:08

説明が飛躍している箇所があります。不変、共変と、オーバーライドされたメソッドの引数と戻り値について、解説が必要です。
xebme

2023/01/22 22:58

Bのtest()の戻り値がArrayListだったので修正しました。 メソッドのオーバーライドの条件と自然な解決案を追記しました。
xebme

2023/01/29 20:30 編集

不変(invariant)は正しくは非変(nonvariant)でした。 共変はvariantではなくcovariantです。 https://ja.wikipedia.org/wiki/%E5%85%B1%E5%A4%89%E6%80%A7%E3%81%A8%E5%8F%8D%E5%A4%89%E6%80%A7_(%E8%A8%88%E7%AE%97%E6%A9%9F%E7%A7%91%E5%AD%A6) Javaでは、共変、非変の理解は必須です。(参考:リスコフの置換原則に従うと、サブクラスでオーバライドするメソッドの戻り値はスーパークラスのメソッドの戻り値の共変、引数は反変になります。) 不変(invariant)はメソッドの不変条件を意味しジェネリクスとは別の概念。
xebme

2023/01/26 20:50

【結論】 コレクション<型パラメータ> - コレクションが共変または同じで、型パラメータが同じなら、共変 - コレクションが共変または同じで、(型パラメータに継承関係があっても)型パラメータが異なるなら、非変 質問の例は非変なのでサブクラスでオーバーライドすることはできません。解答は技巧の遊びにすぎません。実務では参考コードの方が役立つでしょう。もちろんクラスBは定義しません。Stream.map()に渡す関数を変えるだけです。
guest

0

java のジェネリクスの型情報はバイトコードに入りません。
ですので test メソッドのパラメータ s は、ソースコード上は違う型でもバイトコード上は同じ型となります。

投稿2023/01/20 18:39

編集2023/01/20 18:40
jimbe

総合スコア12648

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問