20210510のJavaに関する記事は6件です。

継承と抽象クラス、インターフェースについて

■ 継承とは あるクラスで定義したフィールドやメソッドを受け継ぎ、それに差分として独自のフィールドやメソッドを加えて定義し、クラスを作成することができる仕組み extends クラス名 だけで親クラス(スーパークラス)を継承でき、子クラス(サブクラス)には差分を記述するだけでよく、追加・修正、また内容の把握や管理が楽になる サブクラスになればなるほど具体化(特化)し、スーパークラスになればなるほど抽象化(汎化)していく( Humanクラス⇒ Athleteクラス⇒ SoccerPlayerクラス⇒ JapanTeamクラス / BasketballPlayer⇒ Lakersクラスみたいな) 特化していく毎に持っている属性情報も特化していく、例えば、Humanクラスは最低限名前、年齢、性別は持っており、Athleteクラスは身長、体重や体力などを持って、SoccerPlayerクラスではポジションや各スキル値を持ち、JapanTeamクラスではその他ウイイレで何かありそうな設定を持っている(やらないので分からない) ■ 継承のルール ・サブクラスが複数のスーパークラスを継承することはできない(多重継承の禁止) ⇒1つのスーパークラスから複数のサブクラスを定義することや、その下の孫やひ孫にあたるクラスも定義できる ・修飾子 final がついているクラスは継承できない(フィールドやメソッドについている場合はオーバーライドできない) ・「サブクラス is a 親クラス」という is-a の原則が成り立たない継承はすべきではない(できるにはできるが、オブジェクト指向と乖離し多態性が利用できなくなる) ・サブクラスをインスタンス化する場合でも、実際の処理ではまずスーパークラスのコンストラクタが呼び出される仕組みになっている そのため、スーパークラスで引数ありのコンストラクタのみがある場合、サブクラスではエラーが発生する(引数なしのコンストラクタは使う予定がなくても用意したほうがよいというのはこういう点にもある) ■ 継承のやり方 先述した Human クラスと Athlete クラスを例に 【スーパークラス: Human クラス】 public class Human { private String name; private int age; public Human() { } public Human(String name, int age) { this.name = name; this.age = age; } public void greet() { System.out.println(name + "と申します。" + age + "歳です。"); } 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; } } 【サブクラス: Athlete クラス】 public class Athlete extends Human { private int height; private int weight; public Athlete() { } public Athlete(String name, int age) { super.setName(name); super.setAge(age); this.height = 0; this.weight = 0; } public void introduction() { System.out.println("身長は" + height + "cm、体重は" + weight + "kgです。"); } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } public int getWeight() { return this.weight; } public void setWeight(int weight) { this.weight = weight; } } extends で Human クラスを継承し、差分の身長、体重フィールドを追加 コンストラクタでは、Superクラスの setter を呼び出す場合、 super. を頭につけて指定する super.setName(name) で setter を呼び出し、引数で受ける name を設定する 追加した height、weight は初期値0としているが、特に初期値設定が必要なければ引数に指定するだけでよい 【main メソッドのクラス】 public static void main(String[] args) { Athlete a1 = new Athlete("レブロン・ジェームズ", 37); a1.setHeight(206); a1.setWeight(113); a1.greet(); a1.introduction(); } main メソッドのクラスで setter を呼び出し値を設定する場合には特に必要ないが、 Athlete a1 = new Athlete("レブロン", 37, 206, 113); 上記のように main メソッドのクラスで引数指定のみ行う場合、Athlete クラスのコンストラクタに変数を持たせ、this.height = height、this.weight = weight で受けた変数を private にしたフィールドに代入する必要がある ■ オーバーライド 先述した Human クラスの greet メソッドを Athlete クラスで使用する場合に限って内容を変更したい、というときには、Athlete クラス内で再定義(オーバーライド)することによって可能になる ※先述したが、final 修飾子のついたフィールドやメソッドはオーバーライドできない Human クラスを継承し Athlete クラスになったため、スポーツの種類についても自己紹介に加えるように変更 sports フィールドを追加し public class Athlete extends Human { private int height; private int weight; private String sports; public Athlete() { } public Athlete(String name, int age, int height, int weight, String sports) { super.setName(name); super.setAge(age); this.height = height; this.weight = weight; this.sports = sports; } public void greet() { System.out.println(sports + "選手の" + super.getName() + "、" + super.getAge() + "歳です。"); } public void introduction() { System.out.println("身長は" + height + "cm、体重は" + weight + "kgです。"); } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } public int getWeight() { return this.weight; } public void setWeight(int weight) { this.weight = weight; } public String getSports() { return sports; } public void setSports(String sports) { this.sports = sports; } } public static void main(String[] args) { Athlete a1 = new Athlete("レブロン・ジェームズ", 37, 206, 113, "バスケットボール"); a1.greet(); a1.introduction(); } バスケットボール選手のレブロン・ジェームズ、37歳です。 となる またオーバーライドの際には 1.他の人がコードを読む際にわかりやすい 2.Eclipse が記述ミスを検知する という点から、@Override のアノテーションを記述したほうがよい ■ 抽象クラス / インターフェース 継承するどのサブクラスでも共通して記述する必要があると考えられるものは、基本的にスーパークラスに記述する(そのクラスが現実的に持つ属性情報は、その時点で持たせるべきという考え方) しかし共通の属性情報であっても、個々のインスタンスによって値が異なる場合は、詳細な内容について記述できない ならば記述しない、若しくはとりあえず内容のないメソッドなどを記述しておけばよいのではないかと考えられるが、以下 1.サブクラス作成側が定義し忘れる、若しくはオーバーライドし忘れる可能性がある 2.現時点で内容が確定できないメソッドなのか、そのまま何もしないメソッドとして定義されているのかが不明瞭 3.サブクラスのために作成した、いわば未完成の「継承専用メソッド」であるスーパークラスがインスタンス化される可能性が考えられる(サブクラス作成側が new、entends どちらもできてしまう) これらの問題はすべて抽象クラスを利用することによって解決できる ■ 抽象クラス / 抽象メソッドとは abstract 修飾子をクラスやメソッドに指定することで、そのクラスに対して以下の機能を付与することができる 1.内容のない(処理内容未定の)メソッドを記述できる 2.new によるインスタンス化が不可能になる 3.抽象メソッドはサブクラス、またはその下の孫クラス以下でオーバーライドしなければならない(サブクラスでエラーが出る) 【スーパークラス】 public abstract class Human { private String name; private int age; public Human() { } public Human(String name, int age) { this.name = name; this.age = age; } public abstract void greet(); //抽象メソッド 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; } } 【サブクラス】 public class Athlete extends Human { private int height; private int weight; private String sports; public Athlete() { } public Athlete(String name, int age, int height, int weight, String sports) { super.setName(name); super.setAge(age); this.height = height; this.weight = weight; this.sports = sports; } public void greet() { System.out.println(sports + "選手の" + super.getName() + "、" + super.getAge() + "歳です。"); } public void introduction() { System.out.println("身長は" + height + "cm、体重は" + weight + "kgです。"); } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } public int getWeight() { return this.weight; } public void setWeight(int weight) { this.weight = weight; } public String getSports() { return sports; } public void setSports(String sports) { this.sports = sports; } } 抽象クラスの宣言は public abstract class Human { で class の前に abstract をつけるだけ 抽象メソッドの場合も public abstract void greet(); でvoidの前につけるだけ、処理内容を記述しないため { } もつけない ■ インターフェース 抽象クラスがより階層を昇り曖昧なものになっていく(汎化していく)と、次第にクラス内のフィールドやメソッドが減っていき、抽象メソッドだけになっていく フィールドを持たず、抽象メソッドのみを持つクラスをインターフェースとして扱い、抽象クラスを作成する際と記述内容が異なる インターフェースを利用することで、継承する他クラスに対して 1.共通のメソッド群を実装するように強制できる 2.インターフェースを実装したクラスが、指定のメソッドを持っていることが保証される という効果がある 抽象クラスが通常のクラスに対して is-a の関係であるのに対し、インターフェースは has-a の関係であるといえる(例で記述するコードはこれまで通り Human だが) 【 Human インターフェース】 public interface Human { void greet(); インターフェースの場合は修飾子に interface を指定する またインターフェースとして扱われることの条件にすべてのメソッドが抽象メソッドであること、加えてインターフェースがそもそも継承されるために作成される public なものであることから、わざわざ public abstract void greet(); と public abstract を記述しなくてよい またフィールドを持たないこともインターフェースとして扱われる条件であるが、 final 修飾子のついたフィールドであれば宣言が許可される インターフェースではそのままフィールドを宣言することで、自動的に public static final がつくようになっているため、例えば円周率をインターフェースで定義すると double PI = 3.14; という記述で public static final double PI = 3.14 という意味になる インターフェースを継承するクラスでは 【 Athlete クラス】 public class Athlete implements Human { というように implements (実装)で継承する 通常のスーパークラス、サブクラスの関係では多重継承(サブクラスが複数のスーパークラスを持つ)ことは禁止されているが、インターフェースであれば多重継承が許可されている ここが抽象クラスとの1番大きな違い ※そもそも多重継承が禁止されている理由は、複数のスーパークラスで同じメソッドが定義されている場合、サブクラスでそのメソッドを呼び出した場合どちらのスーパークラスのメソッドを呼び出せばよいかわからない、という懸念に対するものであり、インターフェースはその問題に対して、もとよりすべて抽象メソッドであり内容があるものを持っていないため、禁止する理由がなくなる そのため public class Athlete implements Human, SportMan, Creature { のように複数指定して継承が可能 またインターフェース同士の継承は extends を記述する クラス ← クラス:extends インターフェース ← クラス:implements インターフェース ← インターフェース:extends これら extends と implement は同時に行うことも可能 public class Athlete extends Human implements SportMan, Creature { ※また、Java8 からは " default " キーワードによる処理を持った抽象メソッドの定義が可能となっている 継承先でオーバーライドされなかった場合に、自動的にデフォルトで指定した処理内容でオーバーライドされたものとみなす機能 便利だが、先述した多重継承を禁止する理由に触れてしまう可能性も生まれることになる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】URL文字列からドメインを取得する

TL;DR URI.getRawAuthorityが良さげに見えました。 やり方 やり方はいくつか考えられますが、ここではjava.net.URI.getRawAuthorityを使ったやり方を紹介します。 サンプルコード import java.net.URI; import java.util.stream.Stream; Stream.of( new URI("url"), new URI("url.com"), new URI("./url/foo"), new URI("http://url"), new URI("http://url.com/"), new URI("http://aa.bb.cc.com/foo/bar"), new URI("https://あ.い") ).forEach(it -> System.out.println(it.getRawAuthority())); 実行結果 null null null url url.com aa.bb.cc.com あ.い 解説 サンプルコードからは以下のことが確認できます。 入力フォーマットは、http(s)://を頭につければ後ろがFQDN形式になっていなくてもOK 相対パスを渡した際はnullになる 日本語(多バイト文字)が入っていても動作する ドメイン末尾の/やパスは削除される 変なものを入れるとnullを返されてしまったり、URISyntaxExceptionを投げられることが有る点には注意が必要です。 また、getAuthorityではなくgetRawAuthorityを用いている理由は、エスケープされた入力が意図せずデコードされるようなことを回避するためです。 URIからドメインを取得する方法は他にも有りますが、自分の要件ではこのやり方で十分動作したため、ここでは解説しません。 より詳細な解説は下記のJavadocを参照ください。 また、URLクラスはあまり良くないそうなので、必ずURIクラスを用いることをお勧めします。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【paiza Aランクレベルアップメニュー】 陣取りの手間 Java編

paiza Aランクレベルアップメニュー https://paiza.jp/works/mondai/a_rank_level_up_problems/problem_index?language_uid=java Javaで挑戦中。22問目で詰まっている。 https://paiza.jp/works/mondai/a_rank_level_up_problems/a_rank_camp_step5 うまく?マークが記載されないのだが、毎回打ち直してエネルギー使ってるのが最高に非効率だと気が付く。 「さあここから処理を考えるぞ」というタイミングで疲れ切っていてヒントを呑み込めないのはどうかと…… ・毎度コードを書き直すのが大変 ・せっかくアカウント持ってるんだからQiita使え ・問題集は解答を外部に記載しても良い  ということなので、まずは21問目の回答を備忘録として置いておく。 https://paiza.jp/works/mondai/a_rank_level_up_problems/a_rank_camp_step4 Main.java import java.util.*; import java.util.Queue; import java.util.ArrayDeque; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int h = sc.nextInt(); int w = sc.nextInt(); String map[][] = new String[h][w]; //陣 Queue<XY> q = new ArrayDeque<>(); //キュー for(int i=0; i<h; i++){ //初期化 String temp = sc.next(); for(int j=0; j<w; j++){ map[i][j] = String.valueOf(temp.charAt(j)); if(map[i][j].equals("*")){ map[i][j] = "0"; //最初は0に書き換える q.add(new XY(i, j, 1)); //スタート地点(初期値のy, x, 1が入っている) } } } while(q.size() > 0){ XY xy = q.poll(); int y = xy.y; int x = xy.x; int d = xy.d; if(y > 0 && map[y-1][x].equals(".")){ //上 map[y-1][x] = String.valueOf(d); q.add(new XY(y-1, x, d+1)); } if(y < h-1 && map[y+1][x].equals(".")){ //下 map[y+1][x] = String.valueOf(d); q.add(new XY(y+1, x, d+1)); } if(x < w-1 && map[y][x+1].equals(".")){ //右 map[y][x+1] = String.valueOf(d); q.add(new XY(y, x+1, d+1)); } if(x > 0 && map[y][x-1].equals(".")){ //上 map[y][x-1] = String.valueOf(d); q.add(new XY(y, x-1, d+1)); } } for(int i=0; i<h; i++){ //出力 for(int j=0; j<w; j++){ System.out.print(map[i][j]); } System.out.println(""); } } } class XY{ int y; int x; int d; XY(int y, int x, int d){ this.y = y; this.x = x; this.d = d; } } 参考: https://qiita.com/aja_min/items/fed640d6835eb343eedf https://jpliterature.hatenablog.com/entry/java-breadth-first-search-1 「あ、そうか。クラスを作れば三次配列めいたことができるのか……」 「お前は大学やオンラインスクールで何をやっとったんじゃ」  使わないと忘れるということですね。いやはや。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Mybatisで複数の引数を識別させる方法

はじめに 検索結果を取得する機能で、「入力フォームから取得した検索条件」と「事前に準備した非表示のリスト」という2つの引数で絞り込みを行いたい場面に遭遇した。そこで、2つの引数をMybatisに渡し、絞り込みを行なった結果を取得しようとした際に躓いた点についてまとめてみた。 実現したいこと Mybatisで二つ以上の引数を使用してSQLを実行する。 調査 Mybatisでは複数の引数をただ渡すだけでは識別してくれない。@Paramアノテーションを用いることで、各引数に名前づけを行いMybatis側でその引数名が使用できる様子。 実装 @Mapper public interface SearchMapper { public List<ACCOUNT> searchAccount(@Param("searchForm") SearchForm searchForm, @Param("hideList") List<Integer> hideList); } <select id="searchAccount" resultType="com.example.matching.entity.ACCOUNT"> SELECT * FROM ACCOUNT WHERE ID NOT IN <foreach item="item" index="index" collection="hideList" open="(" separator="," close=")"> #{item} </foreach> <if test="searchForm.AGE_LOWER != null and searchForm.AGE_UPPER != null"> AND AGE BETWEEN CAST(#{searchForm.AGE_LOWER} as INTEGER) and CAST(#{searchForm.AGE_UPPER} as INTEGER) </if> ORDER BY CREATED DESC </select> まとめ @Paramを付与しない場合、第一引数はparam1、第二引数はparam2のように「param+1からのインデックス」という変数になります。 以下、参考記事によると、@Paramを用いることなくMybatisで複数の引数を使用することは可能なようだが、基本的には@Paramの使用が推奨されている。 参考記事
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

奇数と偶数に分けてからソートを行うアルゴリズム

奇遇転置ソートを見ているときに、突然頭から湧きました。 その時の状況: 「奇数番同士の値と偶数番同士の値を比較して、ソートを行っているんだ~」   ↓ 「奇数と偶数だけでソートできそうだな~。あっできそうだから作ってみよ!」 こんな感じで作っていました。 仕組み ソート対象データから、処理スタックに格納する 処理スタックから、奇数グループと偶数グループに分ける 奇数なら奇数グループ、偶数なら偶数グループに格納する 奇数グループと偶数グループからデータを比較し、以下のように統合する 昇順なら最小値を選択する 降順なら最大値を選択する 奇数グループと偶数グループから全て取り出すまで3.を繰り返す 今回は処理スタックとグループを配列で格納し、統合する前に前処理としてソートを行ってから統合していきます。 開発環境 Java(OpenJDK 15.0.2) ソースコード OddEventMergeSort.java public class OddEvenMergeSort { public static void sort(int[] inputs) { int stackPoint = inputs.length - 1; int getData = 0; int[] oddData = new int[inputs.length]; int oddIndex = 0; int oddEndIndex = 0; int[] evenData = new int[inputs.length]; int evenEndIndex = 0; int evenIndex = 0; for (; stackPoint >= 0; stackPoint--) if (inputs[stackPoint] % 2 == 1) { oddData[oddIndex] = inputs[stackPoint]; oddIndex++; } else { evenData[evenIndex] = inputs[stackPoint]; evenIndex++; } oddEndIndex = oddIndex - 1; evenEndIndex = evenIndex - 1; preSort(oddData,oddEndIndex); preSort(evenData,evenEndIndex); oddEndIndex++; evenEndIndex++; oddData[oddEndIndex] = Integer.MAX_VALUE; evenData[evenEndIndex] = Integer.MAX_VALUE; stackPoint = 0; oddIndex = 0; evenIndex = 0; while (oddIndex <= oddEndIndex - 1 || evenIndex <= evenEndIndex - 1) { getData = Math.min(oddData[oddIndex], evenData[evenIndex]); inputs[stackPoint] = getData; stackPoint++; if (getData % 2 == 1) oddIndex++; else evenIndex++; } } /* * 奇数、偶数に分けたデータをソートする */ private static void preSort(int[] datas, int endIndex) { for (int i = 0, temp = 0; i <= endIndex; i++) { for (int j = 0; j <= endIndex - 1; j++) { if (datas[j + 1] <= datas[j]) { temp = datas[j]; datas[j] = datas[j + 1]; datas[j + 1] = temp; } } } } } 動作確認及び時間計測 ソートが終わるまでの時間を調べるため、詳細に調べるためナノ秒で計測しました。 Main.java public class Main { public static void main(String[] args){ int[] datas = {9,5,2,3,1}; // 計測開始 long startTime = System.nanoTime(); OddEvenMergeSort.sort(datas); long endTime = System.nanoTime(); // 計測終了 for(int i=0;i < datas.length;i++){ System.out.println(i + ":" + datas[i]); } System.out.println(endTime-startTime); } } 結果 0:1 1:2 2:3 3:5 4:8 843500 0.8435mミリ秒となりました。 しかし、遅いのかどうか分からないためバブルソートと比較していきます。 バブルソートの場合 Main.java public class Main { public static void main(String[] args){ int[] datas = {8,5,2,3,1}; // 計測開始 long startTime = System.nanoTime(); for (int i = 0, temp = 0; i <= datas.length; i++) { for (int j = 0; j <= datas.length - 2; j++) { if (datas[j + 1] <= datas[j]) { temp = datas[j]; datas[j] = datas[j + 1]; datas[j + 1] = temp; } } } long endTime = System.nanoTime(); // 計測終了 for(int i=0;i < datas.length;i++){ System.out.println(i + ":" + datas[i]); } System.out.println(endTime-startTime); } } 出力結果 0:1 1:2 2:3 3:5 4:8 1900 1900ナノ秒 ミリ秒に直すと、0.0019ミリ秒になりました。 まとめ バブルソートはすごい。 簡単なコードなのに、圧倒的に速く終わるバブルソートの時間に驚きました。 圧倒的に遅いですが、速度よりもロマン重視で作っていましたので最初に計測したときは「まあ遅いだろうね」というのが本音でした(笑)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java 】サイコロを作成せよ

class Saicoro { public static void main(String[] args) { double rand = Math.random() * 6 + 1; System.out.println("サイコロの目は" + (int)rand + "です"); } } コンパイル&実行コマンド↓ % 【java saicoro.java】
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む