20211201のJavaに関する記事は7件です。

[JavaSilver受験対策] 誰も解けないラムダ式を解説

この記事は、シアトルコンサルティング株式会社 Advent Calendar 2021の9日目の記事です。 こんにちは、シアトルコンサルティングの村田です。 はじめに ラムダ式ってなに Javaシルバーの勉強中でラムダ式についての問題が分からない Javaのラムダ式に関して上記の方を対象にしています。 後半ではJavaシルバーの例題を用いて得点するポイントを解説しています。 1. ラムダ式は実行を代入できる  通常インタフェースはメソッドを実現したクラスを定義しなければなりません。 しかしラムダ式を用いることで、メソッドが1つだけのインタフェース型変数(関数型インタフェース)に実行を代入できます。 Runnable r = () -> {System.out.println("sample");}; ■ラムダ式の構文  引数の変数宣言+処理ブロックをアロー演算子「->」でつなぎ、以下のように記述します。  (引数) -> {処理}; 関数型インタフェース型の変数に代入するとそのインタフェース型のインスタンスとして動作します。  関数型インタフェースの型 変数名 = (引数) -> {処理}; 2. ラムダ式を活用してコードを単純化  ■ラムダ式を使用しない場合 public static void main(String[] args) { List<String> list = List.of("a","b","c","d"); Consumer<String> logic = new Consumer<String>() { @Override public void accept(String str) { System.out.println(str); } }; list.forEach(logic); }  ■ラムダ式を利用した場合 public static void main(String[] args) { List<String> list = List.of("a","b","c","d"); list.forEach((str) -> System.out.println(str)); }  関数型インタフェースを使用する場合はラムダ式を活用することで、定義から実装までを1行で記述できます。 上記のような簡易的なコードであれば、かなり簡略化できます。 3. JavaSilverでの出題形式と解法のポイント  ここからはJavaSilver問題に向けた解き方のコツを説明します。 <例題①> コンパイルの実行結果として正しいものはどれか。 public void Sample() { int i = 0; Supplier<Integer> x = () -> i; i++; System.out.print(x.get()); } <選択肢> A.コンパイルできるが、何も起きない B.例外がスローされる C.0が出力される D.1が出力される E.コンパイルエラー → 正解 <解説> ラムダ式を含むブロックの中では、宣言された変数をラムダ式で使う場合、実質的にはfinalである必要があります。 変数「i」が宣言されたあと、ラムダ式で使用されているにもかかわらずインクリメント(i++)されているためコンパイルエラーになります。 <例題②> コンパイルの実行結果として正しいものはどれか。 public static void main(String[] args) { final String hoge = "hello"; Consumer<Integer> x = (hoge) -> System.out.print(hoge); x.accept("thanks"); } <選択肢> A.「hello」が出力される B.「thanks」が出力される C.何も出力されない D.コンパイルエラー → 正解 E.例外がスローされる <解説> このプログラムでの注目ポイントは宣言している変数名です。 ラムダ式内の「(hoge) -> 」の部分は正確には「(String hoge) ->」と引数宣言のデータ型を省略した形になっています。 2行目にfinalで宣言しているhogeとラムダ式内で宣言しているhogeが重複しているためコンパイルエラーになります。 さいごに  Javaシルバー学習のお供として分からなかったことをまとめました。 Javaシルバーで得点するためにはコンパイルエラーになるケースを覚えることが近道になります。 選択肢を絞るための一助としていただければと思います。 参考 (1) 志賀澄人 徹底攻略Java SE 11 Silver 問題集[1Z0-815]対応 株式会社インプレス 2019
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

テーブル更新時エラー 未完成

コンソールアプリケーション:社員情報管理アプリ DBController.java //更新機能 public static void Javadoc_update(String empId ,String empName_up, String gender_up, String birthday_up, String deptId_up) throws ClassNotFoundException, SQLException { Connection connection = null; PreparedStatement preparedStatement = null; try { //DBに接続 connection = DBManager.getConnection(); preparedStatement = connection.prepareStatement(ConstantSQL.SQL_UPDATE1); preparedStatement.setString(1, empName_up); preparedStatement.setString(2, gender_up); preparedStatement.setString(3, birthday_up); preparedStatement.setString(4, deptId_up); preparedStatement.setString(5, empId); //SQLの実行 preparedStatement.executeUpdate(); System.out.println("社員情報を更新しました"); } finally { DBManager.close(preparedStatement); DBManager.close(connection); } ConstantSQL.java public class ConstantSQL { //全てCASE文で記述した場合 public static String SQL_UPDATE1 = "UPDATE employee e SET \r\n" + "e.emp_name = (CASE WHEN e.emp_name IS NULL THEN e.emp_name WHEN e.emp_name IS NOT NULL THEN ? END),\r\n" + "e.gender = (CASE WHEN e.gender IS NULL THEN e.gender WHEN e.gender IS NOT NULL THEN ? END),\r\n" + "e.birthday = (CASE WHEN e.birthday IS NULL THEN e.birthday WHEN e.birthday IS NOT NULL THEN TO_DATE(?, 'YYYY-MM-DD') END),\r\n" + "dept_id = (CASE WHEN dept_id IS NULL THEN dept_id WHEN dept_id IS NOT NULL THEN ? END)\r\n" + "WHERE emp_id = ?"; //emp_nameのみCASE文で記述した場合 public static String SQL_UPDATE1 = "UPDATE employee e SET \r\n" + "e.emp_name = (CASE WHEN e.emp_name IS NULL THEN e.emp_name WHEN e.emp_name IS NOT NULL THEN ? END),\r\n" + "e.gender = ?, \r\n" + "e.birthday = ?, \r\n" + "dept_id = ?\r\n" + "WHERE emp_id = ?"; } CASE文の説明 --CASEで条件をわけてSELECTする SELECT CASE WHEN 条件1 THEN 条件1の結果 WHEN 条件2 THEN 条件2の結果 ELSE 条件1,2以外の結果 END FROM table1; 全てCASE文で入力したとき エラー文 java.sql.SQLSyntaxErrorException: ORA-00932: データ型が一致しません: NUMBERが予想されましたがCHARです。 値を入力して更新しても入力しないで更新しても上記のエラーになる。 emp_nameのみCASE文で記述した場合 エラー文 java.sql.SQLException: ORA-01407: ("CONSOLE_CRUD_USER"."EMPLOYEE"."EMP_NAME")をNULLには更新できません。 値を入力しないで更新しないと上記のエラーになる。 ただしemp_nameに値を入れて更新するとエラーは発生せず更新できる。 別のパターン 同じようにbirthdayのみ、genderのみ、dept_idのみCASE文で記述した場合 エラー文 java.sql.SQLSyntaxErrorException: ORA-00932: データ型が一致しません: NUMBERが予想されましたがCHARです。 いずれの場合も値を入力しないと上記のエラーになる。 または値を入力した場合にも同様のエラーになる。 参考記事
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[JavaSilver受験対策] 非検査例外と検査例外の違い 

この記事は、シアトルコンサルティング株式会社 Advent Calendar 2021の8日目の記事です。 こんにちは、シアトルコンサルティングの村田です。 はじめに Javaの例外クラスに関して非検査例外と検査例外の内容と違いを解説しており、下記の方を対象としています。 非検査例外と検査例外ってなに Javaシルバーの勉強中で例外についての問題が分からない 1. 例外は2種類に分けられる  Javaには例外処理をしなくてもコンパイルエラーにならないクラスがあります。 これを非検査例外と呼び(コンパイラが検査しないから「非検査」) try-catchやthrowsで例外ハンドリングをする必要がありません。 反対に例外ハンドリングが必要なクラスが検査例外です。 検査例外はハンドリングしなければコンパイルエラーになります。  例外クラスは大きくError、Exception、RuntimeExceptionに分けられ、順にエラー、検査例外、非検査例外を表します。 (補足:RuntimeExceptionはExceptionクラスのサブクラスです) 2. 非検査例外について  非検査例外(RuntimeException)には以下のようなものがあります。 NullPointerException ArrayIndexOutOfBoundsException IllegalArgumentException ...etc  これらはわざわざ例外処理をする必要がない程度のエラーです。 nullチェック等は例外処理するまでもなくプログラムで回避できるでしょ?という理由だからです。  例えば以下のサンプルコードでは、非検査例外であるRuntimeExceptionをthrowしているためコンパイルエラーにはなりません。 実装する際はこのうようにコンパイラがスルーしてしまうため、受け取る変数の中身や型を拾って対応しましょう。 public class Sample { public static void main(String[] args) { sample(); } private static void sample() { throw new RuntimeException(); } } 3. 検査例外について  検査例外(Exception)には以下のようなものがあります。 IOException SQLException InterruptedException ...etc    これらはコンパイル時に検査を行うためハンドリングしなければなりません。 読み込むファイルが壊れていたり、DBが壊れているという事象はプログラムでは回避できません。 そこでコンパイラによりハンドリングを強制されています。  以下のサンプルコードでは、検査例外であるExceptionをthrowしている箇所で「処理されない例外の型 Exception」と、コンパイルエラーになります。 public class Sample { public static void main(String[] args) { sample(); } private static void sample() { throw new Exception(); } } 4. コンパイルエラーの解消 コンパイルエラーを解消するには以下の2つの方法があります。  ・throwsを用いる  ・try-catchで例外ハンドリングする ■throwsを用いる  対象メソッドと呼び出し元の両方で、throwsを用いてスローする可能性を宣言します。 public class Sample_1 { public static void main(String[] args) throws Exception{ sample(); } private static void sample() throws Exception{ throw new Exception(); } } ■try-catchで例外ハンドリングする  対象メソッドでthrows宣言および呼び出し元でのtry-catchによるハンドリングをします。 public class Sample_2 { public static void main(String[] args) { try { sample(); } catch (Exception e) { System.out.println("error"); } } private static void sample() throws Exception{ throw new Exception(); } } さいごに  Javaシルバー学習のお供として分からなかったことをまとめました。 選択肢を絞るための一助としていただければと思います。 参考 (1) 志賀澄人 徹底攻略Java SE 11 Silver 問題集[1Z0-815]対応 株式会社インプレス 2019 (2) 25際で独立したフリーランスエンジニアじゃけえのあれこれ 【Java】検査例外と非検査例外の違いを図でまとめてみた https://freelance-jak.com/technology/java/1157/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JDBC基礎

DBManager.java package jp.co.sss.jdbc.chapter01; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class DBManager { /** ドライバクラス名 */ private static final String DRIVER = "oracle.jdbc.driver.OracleDriver"; /** 接続する DB の URL */ private static final String URL = "jdbc:oracle:thin:@localhost:1521:XE"; /** DB 接続するためのユーザ名 */ private static final String USER_NAME = "jdbc_user"; /** DB 接続するためのパスワード */ private static final String PASSWORD = "systemsss"; /** * DB と接続する * * @return DB コネクション * @throws ClassNotFoundException * ドライバクラスが見つからなかった場合 * @throws SQLException * DB 接続に失敗した場合 */ public static Connection getConnection() throws ClassNotFoundException,SQLException { //JDBC ドライバクラスを JVM に登録 Class.forName(DRIVER); //DB に接続 Connection conn = DriverManager.getConnection(URL, USER_NAME,PASSWORD); System.out.println("DB に接続しました"); return conn; } /** * DB との接続を切断する * * @param connection * DB との接続情報 */ public static void close(Connection connection) { if (connection != null) { try { connection.close(); System.out.println("DB と切断しました"); } catch (SQLException e) { e.printStackTrace(); } } } /** * PreparedStatement をクローズする * * @param preparedStatement * ステートメント情報 */ public static void close(PreparedStatement preparedStatement) { if (preparedStatement != null) { try { preparedStatement.close(); } catch (SQLException e) { e.printStackTrace(); } } } /** * ResultSet をクローズする * * @param resultSet * SQL 検索結果 */ public static void close(ResultSet resultSet) { if (resultSet != null) { try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); } } } } 各種定数を定義 private static final String DRIVER = "oracle.jdbc.driver.OracleDriver"; /** 接続する DB の URL / private static final String URL = "jdbc:oracle:thin:@localhost:1521:XE"; /* DB 接続するためのユーザ名 / private static final String USER_NAME = "jdbc_user"; /* DB 接続するためのパスワード */ private static final String PASSWORD = "systemsss"; getConnectionメソッド Connection型のgetConnectionを定義  throwsでエラーの時に返すエラー処理を記述 Class.forName(DRIVER);でオラクルとのやり取りを出来るようにする ドライバは「ojdbc7.jar」 DriverManager.getConnectionでDBに接続する処理を書く。 引数のURL,USER,PASSをもとにDB 接続に成功すると、このメソッドは Connection 型のオブジェクトが戻り値として返される。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JAVA 継承

Constant2301.java package lesson23; public class Constant2301 { public static final String POLICE = "警察官"; public static final String TEACHER = "教師"; public static final String CHEF = "料理人"; } Chef2301.java package lesson23; public class Chef2301 extends Worker2301 { public Chef2301(String name, int age) { super(Constant2301.CHEF, name, age); } public void doWork() { System.out.println("食事を作ります。"); } } Display2301..java package lesson23; public class Display2301 { public static void displayWorkers(Worker2301[] workers) { for(Worker2301 worker : workers) { worker.showIntroduction(); worker.doWork(); } } } Police2301.java package lesson23; public class Police2301 extends Worker2301 { public Police2301(String name, int age) { super(Constant2301.POLICE, name, age); } public void doWork() { System.out.println("地域や人々の安全を守ります。"); } } Sample2301.java package lesson23; public class Sample2301 { public static void main(String[] args) { Worker2301[] workers = new Worker2301[3]; // サブクラスのオブジェクトを配列に追加 // 警察官クラスのオブジェクトを生成して、コンストラクタを呼び出す workers[0] = new Police2301("田中", 28); // 教師クラスのオブジェクトを生成して、コンストラクタを呼び出す workers[1] = new Teacher2301("佐藤", 53); // 料理人クラスのオブジェクトを生成して、コンストラクタを呼び出す workers[2] = new Chef2301("鈴木", 31); // 出力用クラスに各オブジェクト情報を渡す Display2301.displayWorkers(workers); } } Teacher.java package lesson23; public class Teacher2301 extends Worker2301 { public Teacher2301(String name, int age) { super(Constant2301.TEACHER, name, age); } public void doWork() { System.out.println("知識を教えます。"); } } Worker2301.java package lesson23; public abstract class Worker2301 { protected String job; protected String name; protected int age; Worker2301(String job, String name, int age){ this.job = job; this.name = name; this.age = age; } public void showIntroduction() { System.out.println("名前は" + name + "、年齢は" + age + "歳、職業は" + job + "です。"); } public abstract void doWork(); public String getJob() { return job; } public void setJob(String job) { this.job = job; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } 使われた用語
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Java]Android Fragment間のデータ受け渡し Bundle vs ViewModel

目的 Android, Javaで開発するにあたり、Fragment間のデータ受け渡しにBundleとViewModelどちらを使うべきか指標を明示する。 ※ただしデータの値は変更しない前提です。 結論 Bundle:Fragment間で受け渡すデータ数が少ない場合 ViewModel:Fragment間で受け渡すデータ数が多い場合 のように使い分けるのが良いと思います。 はじめに 初めてAndroid開発をしたときに、Fragmentから別Fragmentにデータを受け渡すとき、メジャーな方法としてBundleとViewModelがあると知りました。 両方使ってみて、それぞれの利点・欠点が少し分かったので、まとめてみます。 ちなみに筆者は最初はBundleを使いまくっていたのですが、以下に記載する欠点により、ViewModelをメインで使うようになりました。 Bundle vs ViewModel BundleとViewModelの利点・欠点をまとめます。 Bundle ViewModel 利点 データの定義が不要データのスコープが狭い データ名/型の管理がシンプルFragmentへの渡し不要 欠点 データ名/型の管理が煩雑Fragmentへの渡し必要 データの定義が必要データのスコープが広い 以下、それぞれ説明していきます。 Bundle 利点 データの定義が不要 BundleはputStringやputIntなどでデータ名を指定し値設定、getStringやgetIntなどでデータ名を指定し値取得ができます。 FirstFlagment.java public class FirstFlagment extends Fragment { ... private void moveFragment() { Bundle bundle = getArguents(); bundle.putInt("Apple", 100);// ★値設定 SecondFragment secondFragment = new SecondFragment(); SecondFragment.setArguments(bundle); // フラグメント遷移 getChildFragmentManager() .beginTransaction() .add(R.id.first_layout, secondFragment) .commit() } ... } SecondFlagment.java public class SecondFlagment extends Fragment { ... @Override public void onViewCreated(View view, Bundle bundle) { Bundle bundle = getArguents(); int value = bundle.getInt("Apple");// ★値取得 ... } ... } 上記のように、データ名を指定して簡単に値を設定し渡すことができます。 データのスコープが狭い 上記の例のように、Bundleを渡したフラグメントのみ、該当データを取得できるため、データのスコープは狭く制限できていると言えます。 欠点 データ名/型の管理が煩雑 データの種類が増えてくると、当然データ名が複数必要になります。 また、各データの型が違う場合は、どのデータがどの型か、の管理も必要になります。 要は、データの種類が増えると、取り出すときにどんなデータ名と型で登録したっけ?と分かりづらくなっちゃうってことです。 以下は、データの設定と取得でデータ名/型が一致していないため、NGとなるケースです。 データの設定 bundle.putInt("Apple", 30); bundle.putString("Orange", "30"); bundle.putInt("Lemon", 60); データの取得 int apple = bundle.getInt("Apple");// ← OK int orange = bundle.getInt("Orange");// ← NG: getStringで取得していない int lemon = bundle.getInt("Lemo");// ← NG: データ名誤り Fragmentへの渡し必要 FragmentのsetArgumentsでBundleを渡す必要があります。 これを忘れると、次のFragmentでBundleを使えません。 データの取得 Bundle bundle = getArguents(); bundle.putInt("Apple", 100); SecondFragment secondFragment = new SecondFragment(); SecondFragment.setArguments(bundle);// ← これを忘れるとデータを渡せない Fragment 利点 データ名/型の管理がシンプル ViewModelの属性としてデータを定義するため、データ名(変数名)と型をシンプルに管理できます。これで、データ名と型はこのクラスを見れば間違いなく分かります。 MyViewModel.java public class MyViewModel extends ViewModel { public int apple; public String orange; public int lemon; ... データの設定/取得は、ViewModelProviderでViewModelを取得して実行できます。 FirstFlagment.java public class FirstFlagment extends Fragment { ... private void moveFragment() { MyViewModel viewModel = new ViewModelProvider(this).get(MyViewModel.class); viewModel.apple = 100;// ★値設定 SecondFragment secondFragment = new SecondFragment(); // フラグメント遷移 getChildFragmentManager() .beginTransaction() .add(R.id.first_layout, secondFragment) .commit() } ... } SecondFlagment.java public class SecondFlagment extends Fragment { ... @Override public void onViewCreated(View view, Bundle bundle) { MyViewModel viewModel = new ViewModelProvider(this).get(MyViewModel.class); int value = viewModel.apple;// ★値取得 ... } ... } Fragmentへの渡し不要 上記コードの通り、ViewModelのデータ取得/設定はViewModelProvider経由で行うので、bundleのように次フラグメントへの渡し忘れは起きません。 欠点 データの定義が必要 上記のコード例の通り、ViewModel継承したクラスを定義し、受け渡すデータをすべて属性として定義する必要があります。 そのためBundleと比べると、ひと手間が必要で、1,2個だけデータを受け渡したいときはわざわざViewModelを作るのは面倒に感じます。 データのスコープが広い ViewModelは、ViewModelProviderを用いてどのフラグメントからも取得可能です。 よって、データのスコープが広くなっていると言えます。 チームで開発をしている場合、ViewModelの値がどこかで書き換えられているかもしれませんので、注意して使う必要があります。 私の場合、アプリ全体を一人で開発したため、ViewModelは読み取り専用と決めて使ったため、このデメリットは問題になりませんでした。 ViewModelProvider経由でどこからでもデータ取得可能 MyViewModel viewModel = new ViewModelProvider(this).get(MyViewModel.class); まとめ Bundle  データの定義が不要でスコープも限定できるが、  データ名/型の管理が煩雑でFragmentへの渡し必要。  ⇒Fragment間で受け渡すデータ数が少ない場合 に使い易い。 ViewModel  データ名/型の管理がシンプルでFragmentへの渡し不要だが、  データの定義が必要でデータのスコープが広い。  ⇒Fragment間で受け渡すデータ数が多い場合 に使い易い。 と結論づけました。 最後に 筆者はAndroid開発に携わって1年ほどです。 まだまだ知見不足の点があるかと思いますので、お気づきの点はご指摘くださると助かります。 参考 https://developer.android.com/topic/libraries/architecture/viewmodel?hl=JA#sharing https://qiita.com/m-coder/items/3a8e66d49f2830b09bf4
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Java] 匿名クラスとインスタンスイニシャライザを使って初期化したデータのシリアライズに失敗した話

以前セッション管理の方法をメモリからDBに変更した際に、シリアライズ周りでエラーが起きたのでそれの原因と対策についてまとめる。 前提 Javaではデータの初期化に下記のような方法がある。 List<String> animals = new ArrayList<>() { { add("いぬ"); add("ねこ"); add("たぬき"); } }; これは匿名クラスとインスタンスイニシャライザを使って初期化を行うという方法である。 通常Listの初期化にはList.of(), Arrays.asList()などを使うが、これはimmutableなListを作成するため、後から要素の追加などが出来ない。 しかし、匿名クラスとインスタンスイニシャライザを使えばArrayListのサブクラスとしてインスタンスを作成するため、その後の要素の追加が可能になる。 起きたこと Serializableを実装したSchoolオブジェクトをシリアライズしようとしたエラーが発生した。 School.java // シリアライズ可能なクラス public class School implements Serializable { private List<String> studentNames; public List<String> getStudentNames() { return studentNames; } public void setStudentNames(final List<String> studentNames) { this.studentNames = studentNames; } } Service.java public class Service { public void process() { // 匿名クラス + インスタンスイニシャライザを使ってリストを初期化 final List<String> studentNames = new ArrayList<>() { { add("ひろし"); add("みか"); add("たくろう"); add("たけし"); } }; final School school = new School(); school.setStudentNames(studentNames); try (FileOutputStream fos = new FileOutputStream("school.obj"); ObjectOutputStream oos = new ObjectOutputStream(fos)) { // Schoolのシリアライズ oos.writeObject(school); } catch (final IOException e) { e.printStackTrace(); } } } 実行クラス(Main.java) public class Main { public static void main(final String[] args) { final Service service = new Service(); service.process(); } } エラー内容 java.io.NotSerializableException: Service at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1185) at java.base/java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1553) at java.base/java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1510) at java.base/java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1433) at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1179) at java.base/java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1553) at java.base/java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1510) at java.base/java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1433) at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1179) at java.base/java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:349) at Service.process(Service.java:22) at Main.main(Main.java:4) 原因 NotSerializableExceptionはシリアライズしようとしたオブジェクトがSerializableを実装(implements)していないときに投げられる例外で、今回の場合だとServiceクラスがSerializableを実装していないためエラーとなった。 そもそもシリアライズしようとしているオブジェクトはSchoolなのになぜServiceが出てくるのか? これが今回のポイントで、インスタンスメソッド内で匿名クラスを使うとその匿名クラスはコンパイル時に親クラスの情報を持つようになる。 実際にコンパイル後のクラスの情報を見てみる。 $ cd bin $ ls Main.class School.class 'Service$1.class' Service.class コンパイルすると匿名クラスも一つのclassファイルとして生成されていて、今回の場合だとService$1.classがそれにあたる。 javapコマンドで逆アセンブルすることでclassファイルのフィールドやメソッドを確認することができる。 $ javap -p 'Service$1.class' Compiled from "Service.java" class Service$1 extends java.util.ArrayList<java.lang.String> { final Service this$0; Service$1(Service); } すると匿名クラスのService$1.classはフィールドにServiceを持っていることがわかる。 これでSchoolオブジェクトをシリアライズしようとしたときにServiceオブジェクトが登場することがわかった。 対策 匿名クラスを使ったデータの初期化は行わない 今回の例でもただインスタンスの作成とデータの初期化を同時やりたかっただけなので、普通にaddやaddAllなどをすればよいし、List.of(), Arrays.asList()を引数に渡してインスタンス生成するやり方でも良い。 final List<String> studentNames = new ArrayList<>(); studentNames.add("ひろし"); studentNames.add("みか"); studentNames.add("たくろう"); studentNames.add("たけし"); final List<String> studentNames = new ArrayList<>(List.of("ひろし", "みか", "たくろう", "たけし")); 自分の書いているコードの意味をしっかりと理解する ArrayListの初期化の方法を検索すると、今回の匿名クラスとインスタンスイニシャライザを使って初期化する方法を紹介している記事は多数見つかる。 そのコードを理解しないままに使ってしまうと、思わぬところでエラーとなってしまうため、この構文が何をしているものなのか、それを使った上で問題はないのかをしっかり理解した上で使うようにする。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む