- 投稿日:2021-06-23T23:12:20+09:00
JsonInclude(Include.NON_NULL)のNULLカラムをObjectMapperで強制出力
本稿は JsonInclude(Include.NON_NULL) を指定したclassのオブジェクトに対して、 値がnullでもデシリアライズ時に強制的に表示する方法についてのメモである。 背景と動機 ObjectMapperではなく、class定義でメンバごとに表示制御を行う場合、 基本的にObjectMapperに関係なく、nullメンバは表示されなくなる。 オブジェクトで指定してるのだからその動作は当然ではあるのだが、 私は開発環境向けにどうしても強制的に表示したくなった。 しかし、nullメンバを表示しない方法はたくさん見つかるが、 逆は全然情報が見つからなかった。 nullメンバを非表示 オブジェクトの全メンバに対してnullの場合に表示しないようにするには 以下のようにクラスにJsonIncludeを付与すれば良い。 https://fasterxml.github.io/jackson-annotations/javadoc/2.9/com/fasterxml/jackson/annotation/JsonInclude.Include.html @JsonInclude(JsonInclude.Include.NON_NULL) @Data class MyObject { private String name; private Integer age; } このクラスのオブジェクトを値をnullにしてJSONデシリアライズすると 以下のように空のJSONになる。 {} nullメンバを強制表示 今回の方法が必要になった経緯から説明すると、 私はデバッグ画面の入力用にメンバを含むJSONを用意したかったのだ。 正攻法で考えると初期化メソッドを持つinterfaceを作り、 それをimplementsさせれば有意な値を含めたJSONを作ることができるだろう。 しかし今回は、既に動いているアプリに対する修正を最小限にしたかったため、 "{}"をシリアライズし、そこから今度はデシリアライズして、 以下のような値を作りたいと考えた。 { "name": null, "age": null } いろいろな方法を試した結果、AnnotationIntrospectorをカスタムで用意する方法で 実現できた。 public class Hoge { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper() .setAnnotationIntrospector(new CustomAnnotationIntrospector()); private static class CustomAnnotationIntrospector extends JacksonAnnotationIntrospector { @Override public JsonInclude.Value findPropertyInclusion(Annotated a) { return JsonInclude.Value.empty(); } } } 尚、上記のObjectMapperだと "{}" からのシリアライズはうまくいかないため、 私の要求を満たすにはシリアライズとデシリアライズでObjectMapperを別にする必要があった。
- 投稿日:2021-06-23T22:26:38+09:00
SerializableのserialVersionUIDが原因で発生するInvalidClassExceptionの対処方法
serialVersionUIDとは serialVersionUIDはSerializableなオブジェクトに定義するバージョン番号のようなものです。データのシリアライズ/デシリアライズする際に、互換性を確認するためにこのserialVersionUIDを使用します。 また、serialVersionUIDを定義しない場合、自動で計算されることになります。 InvalidClassException これは、Serializableなオブジェクトをデシリアライズする際、serialVersionUIDが異なる場合に発生します。 java.io.InvalidClassException com.sample.data.SampleSerializableData; Incompatible class (SUID): com.sample.data.SampleSerializableData: static final long serialVersionUID =4092797215335171425L; but expected com.sample.data.SampleSerializableData: static final long serialVersionUID =5140160798577733211L; 今回、SerializableなJavaクラスをKotlin化した際、誤ってserialVersionUIDを削除してしまったため、アプリバージョンアップの際にデータのデシリアライズができなくなってしまいました。 回避方法 今回、既にアプリのバージョンアップを何度か挟んでしまっていたため、serialVersionUIDを復活させてしまうと、今後はKotlin版のSerializableオブジェクトがデシリアライズできなくなってしまいます。そのため、以下の記事を参考にデシリアライザをカスタマイズすることで回避しました。 カスタムInputStream ObjectInputStream をextendsして readClassDescriptorをoverrideしています。戻り値の ObjectStreamClass はSerializationの情報を持つオブジェクトです。クラス名とserialVersionUIDを含んでいます。 内容はコード内のコメントに記載しました。 CustomInputStream class CustomInputStream(inputStream: InputStream) : ObjectInputStream(inputStream) { @Throws(IOException::class, ClassNotFoundException::class) override fun readClassDescriptor(): ObjectStreamClass? { // streamからクラス記述子を読み出します val resultClassDescriptor = super.readClassDescriptor() ?: return null // Class名を取得 val localClass = try { Class.forName(resultClassDescriptor.name) } catch (e: ClassNotFoundException) { return resultClassDescriptor } // クラス名を、現在のアプリバイナリの中から探します val localClassDescriptor = ObjectStreamClass.lookup(localClass) // only if class implements serializable if (localClassDescriptor != null) { // 現在のアプリバイナリと、streamの中のそれぞれのserialVersionUIDを比較します val localSUID = localClassDescriptor.serialVersionUID val streamSUID = resultClassDescriptor.serialVersionUID // serialVersionUIDが異なる場合 if (streamSUID != localSUID) { // streamのserialVersionUIDが正として、localClassDescriptorを使います return localClassDescriptor } } return resultClassDescriptor } } 利用するとき val objectInputStream = CustomInputStream(byteArrayInputStream) val data = objectInputStream.readObject() as? SampleSerializableData かなり強引な技ではあるので、どうにも回避方法がなくなってしまった場合の奥の手として使うのが良いと思います。また、利用箇所も限定的にして、他のSerializableには影響が出ないようにしましょう。
- 投稿日:2021-06-23T14:36:31+09:00
対策例:startActivityForResult(Intent,int) in Fragment has been deprecated
背景 startActivityForResult(Intent,int) での画像選択コードが deprecated と言われるので、修正前後のコード例を残しておく。 コード 修正前 public class MainActivity extends AppCompatActivity implements View.OnClickListener { private final static int RESULT_PICK_IMAGEFILE = 1000; @Override public void onActivityResult(int requestCode, int resultCode, Intent resultData) { super.onActivityResult(requestCode, resultCode, resultData); if (requestCode == RESULT_PICK_IMAGEFILE && resultCode == RESULT_OK) { if (resultData != null) { Uri uri = resultData.getData(); // Do something here. } } } private void onSelectImage() { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("image/*"); intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true); startActivityForResult(intent, RESULT_PICK_IMAGEFILE); // deprecated!! } } 修正後 public class MainActivity extends AppCompatActivity implements View.OnClickListener { ActivityResultLauncher<Intent> _launcherSelectSingleImage = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback<ActivityResult>() { @Override public void onActivityResult(ActivityResult result) { if (result.getResultCode() == RESULT_OK) { Intent resultData = result.getData(); if (resultData != null) { Uri uri = resultData.getData(); // Do something here. } } } }); private void onSelectImage() { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("image/*"); intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true); Intent chooserIntent = Intent.createChooser(intent, "単一画像の選択"); _launcherSelectSingleImage.launch(chooserIntent); } } 参考リンク android - Java solution for "startActivityForResult(Intent,int) in Fragment has been deprecated" when opening external URL? - Stack Overflow startActivityForResult()とonActivityResult()がDeprecatedになった対処法 / startActivityForResult() and onActivityResult() has Deprecated - Speaker Deck
- 投稿日:2021-06-23T11:49:21+09:00
インターフェースの多重継承
はじめに Java SE 11 Silver取得に向けて学習中に、練習問題にて出題されたのでまとめてみました。 今回は4パターン紹介します。 継承の仕様(オーバーライド、オーバーロード)を把握していると、より理解しやすいかと思います。 また、共変戻り値に関しましては今回は取り上げませんでした。 ※オーバーライド、オーバーロードを理解したい方は こちらの記事 を参照 パターン1 Interface_A.java interface Interface_A{ void test(); } Interface_B.java interface Interface_B{ void test(); } Interface_C.java interface Interface_C extends Interface_A, Interface_B{ void test();//コンパイル可 } 全てのインターフェースが同じシグニチャのメソッドを宣言している場合です。 CにA Bのtest()をオーバーライドしていることになり、 コンパイル可能です。 パターン2 Interface_A.java interface Interface_A{ void hoge(); } Interface_B.java interface Interface_B{ void test(); } Interface_C.java interface Interface_C extends Interface_A, Interface_B{ int test();//コンパイルエラー int test(int num);//コンパイル可 } 1つ目はメソッド名が継承したBと同じかつ、戻り値が違う場合です。 オーバーライドを宣言していますが、戻り値が一致していないので コンパイルエラーになります。(共変戻り値であればコンパイル可能) 2つ目は戻り値、引数が違う場合です。 メソッド名は同じですが、シグニチャが違うためオーバーロード扱いとなり コンパイル可能です。 パターン3 Interface_A.java interface Interface_A{ void test(); } Interface_B.java interface Interface_B{ void test(); } Interface_C.java interface Interface_C extends Interface_A, Interface_B{ void hoge();//コンパイル可 } 継承したインターフェースのメソッドを1つもオーバーライドしていない場合です。 インターフェース、抽象クラスに継承または実装されたインターフェースのメソッドは、 任意での実装となり、実装しなくても実行可能です。 パターン4 Interface_A.java interface Interface_A{ void hoge(); } Interface_B.java interface Interface_B{ void piyo(); } Interface_C.java interface Interface_C extends Interface_A, Interface_B{ void fuga(); } Main.java class Main implements Interface_C{ @Override public void piyo() {} @Override public void hoge() {} @Override public int fuga() {} } 多重継承されたインターフェースをクラスに実装する場合です。 実装するには、継承元全てのメソッドを実装してあげる必要があります。 パターン4ex Interface_A.java interface Interface_A{ void hoge(); } Interface_B.java interface Interface_B{ void piyo(); } Interface_C.java interface Interface_C extends Interface_A, Interface_B{ default void hoge(){} default void piyo(){} void fuga(); } Main.java class Main implements Interface_C{ @Override public int fuga() {} } 多重継承したインターフェースが親インターフェースの実装をdefaultにて行っている場合は 継承先での実装は任意となり、エラーにはなりません。 終わりに 人生で初めてブログを書いてみました。 アドバイスや質問がありましたら、よろしくお願いします! Javaの継承関連やコンスタントプール関連は暗記で網羅するより、しっかりと 仕組みを理解することで様々なパターンに対応できるようになります。 私も試験が間近なので、ニアミスを減らすべく細かな仕様を復習していきたいと思います。 参考 参考書:徹底攻略Java
- 投稿日:2021-06-23T11:29:38+09:00
[補足] org.mockito.exceptions.misusing.UnnecessaryStubbingExceptionについて
背景 以下記事は [Web/まとめ] JUnit5 で Mockitoを利用する方法で理解に若干時間のかかった org.mockito.exceptions.misusing.UnnecessaryStubbingException について、実際のサンプルコードを踏まえて、理解の足しにした追加メモである。 一体どのような時に起こるのか? (復習&まとめ)まずMockitoで何ができるのか? Mockitoを利用することで、テスト対象コードが利用する依存サービス (3rd party libraryだったり、実際の実装がわからない interface classだったり)への依存なしでテストを行うことができる。 例えば、実際の実装を使わずとも、Mockオブジェクトを定義し、そのMock オブジェクトがどのような結果を返すかなどを、開発者は各テストケースに応じて挙動を書き換える (=定義する)ことができる 利用している3rd library interfaceがExceptionをthrowした場合は、自分のテストクラスはどのような挙動をするべきか? 3rd party library interface / method の実装や中身は興味がない、とにかくintegrationが正常に行われていると仮定して、3rd party library interface の挙動をフェイク実装したい (復習&まとめ) TestにおけるStubとMockとは? 自分は、Mockitoにおいて、ここやや混同したのでまとめておく。 MockとStubの違い Mockitoの場合は StubもMockも作り方は基本的に一緒 (<=ここが自分にとってかなりややこしかった )。Mockの場合は verifyをコールして動作の検証を行う一方、Stubの場合はそれを行わない(実装が壊れないようにフェイクデータを返すことが責務だから)。 参考LINK https://stackoverflow.com/questions/463707/what-are-the-differences-between-mocks-and-stubs-on-rhino-mocks https://pazoo.hatenablog.com/entry/stub_mock_Java (わかりやすい説明 ) https://javadoc.io/static/org.mockito/mockito-core/3.11.2/org/mockito/Mockito.html#verification (Mockito公式Doc/Mockについて) https://javadoc.io/static/org.mockito/mockito-core/3.11.2/org/mockito/Mockito.html#stubbing (Mockito公式Doc/Stubについて) まとめ Mock テスト対象のクラスに対するテストの一環として、(実装は持たない架空の)interface/componentの 予測される定義を記述し、実際に期待通りコールされたかをチェックする 従って、Mockitoの場合は verify を呼ぶことが必須!!! ただ、それ以外の作成方法は一緒! (例: @Mockを利用) Stub テストコードがテストケースの意図と異なる箇所で関連のないエラーが発生しないよう、問題ないFakeデータを返すように定義するためだけに、挙動を定義する 従って、Mockitoの場合は verify はむしろ実行してはいけない! (Mockito java docではコードが冗長になるのでやめろと言っている (参考) https://javadoc.io/static/org.mockito/mockito-core/3.11.2/org/mockito/Mockito.html#stubbing ただ、それ以外の作成方法は一緒! (例: @Mockを利用) (話題の核心) UnnecessaryStubbingExceptionはどのような問題で発生するのか? ここまで前提知識が詰まったところでようやく核心に入れる。。 このExceptionは、Mockについてではなく Stubについて述べている。 上記で述べたとおり、テストコードを無事テスト意図通りに動かすためだけのFake Dataを返せば良いというタイプのオブジェクトであるが、注意しなければならないのが、、 やみくもに Stub Codeをテスト対象コード、テスト意図をきちんと理解しないまま、テストを動かすだけに適当につけていってしまうと、テストコードが読みにくくなり、テストが汚くなってしまうということなのだ! この問題に対してアラートを検知してくれる仕組みがこのExceprtionなのだ! 実際には、Mocktito JavaDocでわかりやすい例を表示してくれていた (https://javadoc.io/static/org.mockito/mockito-core/3.11.2/org/mockito/Mockito.html) 例: 以下テストでは before() メソッドにより、毎テストケース実行時に when(foo.foo()).thenReturn("ok"); が定義されている。 これは、test1 & test2 では問題なく動作するが、 test3では foo に対する interactionが発生していないため、when(foo.foo()).thenReturn("ok");というスタブ定義が冗長になってしまい、 UnnecessaryStubbingExceptionが発生してしまう。 public class SomeTest { @Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(STRICT_STUBS); @Mock Foo foo; @Mock Bar bar; @Before public void before() { when(foo.foo()).thenReturn("ok"); // it is better to configure the stubbing to be lenient: // lenient().when(foo.foo()).thenReturn("ok"); // or the entire mock to be lenient: // foo = mock(Foo.class, withSettings().lenient()); } @Test public void test1() { foo.foo(); } @Test public void test2() { foo.foo(); } @Test public void test3() { bar.bar(); } } lenient 利用のススメ このtest errorは mock objectに対して lenient モードを利用することによって消去することができる。 上記 公式Mockito documentationでは、別にそうして lenient モードを利用することは必ずしも悪いことではないと言っている (エラーはデフォルトで出しておきながら、、 ) 理由としては、、 上記のように、@Beforeメソッド時に、繰り返し的にStubの共通挙動を定義することで、コードの冗長化が減る なので、上記例 (test3) のように、一部やむを得ない例外事項が発生してしまったとしても、重複コードの冗長化 (=>@Beforeメソッドの代わりに、各テストメソッドに同じ処理を重複して書き込む)よりは、 lenient モードを使い、(stubの冗長化をトレードオフし)@Beforeメソッドで初期化処理をまとめた方が綺麗である ただ、いずれにしてもケースバイケースなので、開発者自身の best judgement に委ねる 私の会社のプロジェクトの場合は、、、 まさに lenient 利用のススメ と同じようなトレードオフ現象に遭遇したわけだが、私のテストの場合は、とりあえず@Beforeメソッドから共通処理を外し、各テストメソッドごとに特有の初期化処理を実装するようにした。 どうしてそのような判断をしたかというと、、、 テストケースごとに、このオブジェクトはStub初期化処理して、これはしない、というケースが複雑だったから 場合によっては、テストケース時に、Before メソッドで定義したStubを resetする必要があった ちなみに、resetを使うこともmockitoのベストプラクティスとしてはお勧めされていない https://javadoc.io/static/org.mockito/mockito-core/3.11.2/org/mockito/Mockito.html#17 このままの状態で letientモードを使い共通化を優先してしまうと、後ほど別の開発者(いや私ですら)がテストコードを修正したり、デバッグしたり、となった時、非常に煩雑で読みにくい実装になるだろうと懸念されるから また、各Stub初期化処理をメソッドにまとめる (例: setUpXXXServiceStub みたいにdescriptiveなメソッド名で)と、別に各テストコード内に記載しても、それなりに読みやすくキープできる 同時に、各テストケース/テスト意図ごとに、オブジェクトがどのようにスタブされるべきであるかが、クリアにわかり良い判断だとは思う
- 投稿日:2021-06-23T08:45:57+09:00
SpringBootポートフォリオ作成_画面
SpringBootポートフォリオ作成 要件 Spring Bootで一通りアプリを書く ・Todo項目追加、一覧取得、完了済みマークメソッド ・lombok ログ validation jQuery JUnitテストケースで単体テスト作成 Git GitHUb Docker(-compose)を使用 AWSでデプロイ 作業手順 ・アプリケーションで使用するデータベースを作成する。 ・STS で新規プロジェクトを作成する。 ・テーブル定義を作成する。 ・URL 設計を作成する。 ・マイグレーションファイルを作成する。 ・機能ごとに... ・マッパーやドメインを作成する。 ・コントローラーを作成する。 ・テンプレートを作成する。 新規プロジェクト作成 名前:springtodo Javaバージョン:11 グループ:dev.itboot 成果物:springtodo バージョン:1.0.0説明省略 パッケージ:dev.itboot.mb 依存関係で以下を選択して SpringBootDevTools(開発ツール) Lombok(開発ツール) Validation=検証(I/O) MyBatisFramework(SQL) H2Database(SQL) PostgresSQL Driver Thymeleaf(テンプレート・エンジン) SpringWeb(Web) SpringDataJPA テーブル定義・URL設計参考 application.properties mybatis.configuration.mapunderscoretocamelcase=true spring.datasource.url=jdbc:h2:mem:spring_demo server.port=8080 ローカルではH2を使うことにする。 http://localhost:8080/h2-console ←新規で立ち上げることをお勧めします。 lombokの設定 1.サイトからlombok.jarをダウンロードする。 2./Applications/SpringToolSuite4.app/Contents/Eclipse/へlombok.jarを配置する。 3./Applications/SpringToolSuite4.app/Contents/Eclipse/SpringToolSuite4.iniへ下記を追記する。 -javaagent:/Applications/SpringToolSuite4.app/Contents/Eclipse/lombok.jar ログについて ログ設定 lombok 設定必要 @Slf4j → アノテーション必要。 ログのレベルを変えたい。→ application.properties に logging.level.<パッケージ名>や<クラス名>を追加 logging.level.com.example.demo=trace パスの指定 logging.file.path=./log ファイル名の指定 logging.file.name=./log/application.log バリデーション その1 参考アドレス https://itsakura.com/java-springboot-validate 参考プログラム抜粋 package dev.itboot.mb.domains; import java.util.Date; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat.ISO; import lombok.Data; @Data public class TaskForm { private int id; private boolean status; // @NotBlank(message = "必須項目です") @NotBlank @Size(max = 60) // @Size(min = 3, max = 6, message = "3文字から6文字で入力して下さい") private String title; // @NotNull(message = "必須項目です") @NotNull @DateTimeFormat(iso = ISO.DATE) private Date lmt; } @PostMapping("/add") public String create(@Validated TaskForm taskForm, BindingResult bindingResult, @RequestParam Map<String, String> requestParams) throws Exception { if (bindingResult.hasErrors()) { return "add"; } else { String title = requestParams.get("title"); // System.out.println(title); String lmt = requestParams.get("lmt"); // System.out.println(lmt); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); int id = 0; Task t = new Task(id, false, title, dateFormat.parse(lmt)); taskMapper.add(t); log.info("登録遷移処理"); return "redirect:/"; } } 手順 resourcesの直下に ValidationMessages.propertiesを設定 注意 UTF-8にすること 例: javax.validation.constraints.NotBlank.message = 必須入力です。 javax.validation.constraints.NotNull.message = 必須入力です。 javax.validation.constraints.Size.message ={min}から{max}の範囲内でなければなりません。 今の時点でのつまずいたところ・・・ @DateTimeFormat(iso = ISO.DATE)をつけ忘れ・・・ 参考エラー Failed to convert property value of type java.lang.String to required type java.util.Date for property lmt; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.util.Date] for value 2021-06-18; nested exception is java.lang.IllegalArgumentException jQuery 手順 POMに以下を追加 <dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.5.1</version> </dependency> HTML下部に指定 <script src="webjars/jquery/3.5.1/jquery.min.js"></script> <script type="text/javascript" th:src="@{/script.js}"></script> $(function(){ $("#title").on('keydown keyup change', function() { var count = $(this).val().length; $("#count").text(count); if(count > 60) { $("#count").css({color: 'red', fontWeight: 'bold'}); }else{ $("#count").css({color: '#333', fontWeight: 'normal'}); } }); }); <div class="todo-container"> <h1 class="h3 mb-4">タスクを追加する</h1> <div class="card"> <div class="card-body"> <form action="/add" th:object="${taskForm}" method="post"> <div class="form-group"> <label for="title">タイトル</label> <input class="form-control" th:field="*{title}" /> <span class="text-danger" th:if="${#fields.hasErrors('title')}" th:errors="*{title}"></span> </div> <div class="form-group"> <label for="limit">期限</label> <input class="form-control" type="date" th:field="*{lmt}"/> <span class="text-danger" th:if="${#fields.hasErrors('lmt')}" th:errors="*{lmt}"></span> </div> <div class="d-flex justify-content-between align-items-center"> <a href="/" class="btn btn-outline-primary">戻る</a> <button class="btn btn-primary" type="submit">追加する</button> </div> <div class="form-group"> <span id="count"></span>/60 </div> </form> </div> </div> </div> テスト作成 クラスを指定して新規→テストケース作成 @SpringBootTest Docker(-compose)を使用をして確認を行った。 特記事項 postgres環境で試したことで色々な問題が出た。 SQLは手動で流した。試行錯誤の結果 CREATE SEQUENCE tasks_seq; CREATE TABLE tasks ( id integer NOT NULL DEFAULT nextval('tasks_seq'), status BOOLEAN NOT NULL, title VARCHAR(100) NOT NULL, lmt DATE NOT NULL ); ALTER SEQUENCE tasks_seq OWNED BY tasks.id INSERT INTO tasks(id, status, title, lmt) values(nextval('tasks_seq'), 'f', 'ドリブル練習', CURRENT_DATE); INSERT INTO tasks(id, status, title, lmt) values(nextval('tasks_seq'), 'f', 'シュート練習', CURRENT_DATE); AWS上にリリースを行なった。 つまずいた点 SQLがうまくいかなかった。 postgresのバージョンが違うのだろうか? 参考 http://springmybatis-env.eba-fb6zgdsj.ap-northeast-1.elasticbeanstalk.com/ https://github.com/noikedan/springtodo
- 投稿日:2021-06-23T06:58:11+09:00
文字列の長さで意図的に例外処理を発生させる
例外処理を使って坂道メンバーの文字数チェックをする。 //例外処理を使って坂道メンバーの文字数チェックをする。 // 新規作成 2021/6/23 // Author 乃木坂好きのITエンジニア import java.util.Scanner; public class Sakamichi { public static void main(String[] args) throws Exception { Scanner sc = new Scanner(System.in); System.out.println("好きな坂道メンバーを入力してください"); String line = sc.nextLine(); x(line); System.out.println("4文字以上のメンバーを入力しました "); } static void x(String name) throws Exception{ int len = name.length(); //メンバーの文字数が4字未満なら例外処理を実行する if(len < 4) { throw new Exception(); } System.out.println(name); } }