LoginSignup
0
0

Javaの型・継承・ジェネリクス・関数型インタフェースなど

Last updated at Posted at 2023-10-08

はじめに

Javaの記事は普段書かないのですが、会社のメンバー育成用に簡単にまとめていたので、せっかくなので記事として共有させていただこうと思います。
あくまで概略的な説明です。

プリミティブ型

値を保持するのみの型
メソッドはないです
メモリ上に直接その値が保持されます(ByValue:値渡し)
継承はできません

int i = 10;
i.toString(); というものはない    

オブジェクト型

java.lang.Objectを継承したクラス
メモリ上に直接その値が保持されるのではなく、値を保持したメモリアドレスへの参照値が格納されます(ByReference:参照渡し)
ただ他の言語とちょっと違いJavaは参照渡しではなく参照の値渡しと呼ばれるややこしい動作をします
そのためあまり複雑に考えると訳が分からなくなるので注意

/**
 * intはプリミティブ型ですが
 * Stringはオブジェクト型です
 * ここをしっかりと分かっていれば大丈夫!!
 */
public class StringSamples {

    /**
     * プリミティブ型について
     */
    @Test
    public void primitiveTypeSample(){
        // valueAには10、valueBには20という値が保持されます
        int valueA = 10;
        int valueB = 20;

        System.out.println("valueA:=" + valueA + ", valueB:=" + valueB);    // valueA:=10, valueB:=20
        System.out.println(valueA==valueB);                                 // false

        // valueAにvalueBを代入すると
        valueA = valueB;
        System.out.println("valueA:=" + valueA + ", valueB:=" + valueB);    // valueA:=20, valueB:=20
        System.out.println(valueA==valueB);                                 // true

        // valueBを30にしたらどうなる?
        valueB = 30;
        System.out.println("valueA:=" + valueA + ", valueB:=" + valueB);    // valueA:=20, valueB:=30
        System.out.println(valueA==valueB);                                 // false

        // valueAを30にしたらどうなる?
        valueA = 30;
        System.out.println("valueA:=" + valueA + ", valueB:=" + valueB);    // valueA:=30, valueB:=30
        System.out.println(valueA==valueB);                                 // true
    }


    /**
     * プリミティブ型と同じことをString型で行ったどうなる?
     */
    @Test
    public void stringTypeSample(){
        // valueAには10、valueBには20という値が保持されます
        String valueA = "10";
        String valueB = "20";

        System.out.println("valueA:=" + valueA + ", valueB:=" + valueB);    // valueA:=10, valueB:=20
        System.out.println(valueA==valueB);                                 // false

        // valueAにvalueBを代入すると
        valueA = valueB;
        System.out.println("valueA:=" + valueA + ", valueB:=" + valueB);    // valueA:=20, valueB:=20
        System.out.println(valueA==valueB);                                 // true

        // valueBを30にしたらどうなる?
        valueB = "30";
        System.out.println("valueA:=" + valueA + ", valueB:=" + valueB);    // valueA:=20, valueB:=30
        System.out.println(valueA==valueB);                                 // false

        // valueAを30にしたらどうなる?
        valueA = "30";
        System.out.println("valueA:=" + valueA + ", valueB:=" + valueB);    // valueA:=30, valueB:=30
        System.out.println(valueA==valueB);                                 // true

        // valueBをnew String("30")とした場合は
        valueB = new String("30");
        System.out.println("valueA:=" + valueA + ", valueB:=" + valueB);    // valueA:=30, valueB:=30
        System.out.println(valueA==valueB);                                 // false  <------おなじ30なのにfalseになる
        // 参照値ではなく中身の値を比較する場合はequalsを使用する
        System.out.println("equals:" + valueA.equals(valueB));
    }


    /**
     * 参照渡し(ほとんどこの使い方はやりません)
     *  参照値と呼び出しメソッドの参照値が同じ
     */
    @Test
    public void arrayByRefSample() {
        String[] array = {"おはようございます", "こんにちは", "こんばんは"};
        System.out.println("1. " + array[0]);       // おはようございます
        changeArrayByRef(array);
        System.out.println("3. " + array[0]);       // Hello

        // 参照値は以下の通り
        System.out.println("arrayの参照値:=" + array);
    }

    private void changeArrayByRef(String[] arr){
        arr[0] = "Hello";
        System.out.println("2. " + arr[0]);       // Hello

        // 参照値は以下の通り
        System.out.println("arrの参照値:=" + arr);
    }


    /**
     * 参照の値渡し(これがオブジェクト型の基本的な使い方)
     */
    @Test
    public void arrayByRefWithByValSample(){
        String[] array = {"おはようございます", "こんにちは", "こんばんは"};
        System.out.println("1. " + array[0]);       // おはようございます
        changeArrayByRefWithByVal(array);
        System.out.println("3. " + array[0]);       // おはようございます
        // 参照値は以下の通り
        System.out.println("arrayの参照値:=" + array);
    }

    private void changeArrayByRefWithByVal(String[] arr){
        arr = new String[3];  // ここでarrayを新しく初期化したインスタンスに入れ替えた!
        arr[0] = "Good Morning";
        System.out.println("2. " + arr[0]);       // Good Morning

        // 参照値は以下の通り
        System.out.println("arrの参照値:=" + arr);
    }

    /**
     * Stringも参照の値渡しです
     * 参照値を渡しているのではなく、参照値が持っている値だけを渡している
     */
    @Test
    public void stringByRefWithByValSample(){
        String message = "おはよう";
        System.out.println("1. " + message);        // おはよう
        changeStringByRefWithByVal(message);        // 参照渡ししているようだが実際は String.valueOf(message) を渡している感じ
        System.out.println("3. " + message);        // おはよう
    }

    private void changeStringByRefWithByVal(String value){
        value = "Good Morning";                   // ここでStringは作り直しされたことになる
        System.out.println("2. " + value);        // Good Morning
    }

}


ところで継承ってなんでしょう?

継承

以下のようにextendsで基底クラスのメソッドやフィールドを引き継ぎ新しいクラスを作成すること
基底クラスをスーパークラス、継承先をサブクラスと呼びます

/**
 * BaseClassがスーパークラス
 * ClassSampleがサブクラス
 */
public class ClassSample extends BaseClass {
    
}

プリミティブ以外はこのObjectクラスを継承しています
継承したサブクラスはObjectクラスに実装されているメソッドを使用することができる

// 基底クラス
public class Object { 
    // コンストラクタ
    public Object(){}
    
    // メソッド
    boolean equals(Object obj){}
    String toString(){}
    //....etc
}

サブクラスの例: Integerクラス

Integerクラスとは
java.lang.Object -> java.lang.Number -> java.lang.Integer と継承されています
ObjectのサブクラスがNumber, さらにサブクラスがInteger
IntegerのスーパークラスがNUmber, さらにそのスーパークラスがObject
という関係になります。
img.png
ですので、以下のようなことになります

  • プリミティブ型: int.toString() はない
  • オブジェクト型を継承: Integer.toString() はある
public class ExtendSample {

    /**
     * 直感的に分かる例
     */
    @Test
    public void StringSample(){
        // 変数の設定
        String japanese = "日本語";
        String english = "英語";
        // 出力
        System.out.println(japanese);   // 結果は日本語
        System.out.println(english);    // 結果は英語
        //
        japanese = english;
        System.out.println(japanese);   // 結果は? 英語ですね
    }

    /**
     * AnimalBaseClassについて
     * cat, dog それぞれに初期値の名前を設定している
     * catとdogは同じAnimalBaseClassなのでお互いに代入可能
     */
    @Test
    public void animalBaseClassSample(){
        // FoodBaseClass型の変数の設定
        AnimalBaseClass cat = new AnimalBaseClass("猫");
        AnimalBaseClass dog = new AnimalBaseClass("犬");

        System.out.println("cat is " + cat.getName());
        System.out.println("dog is " + dog.getName());

        cat = dog;
        System.out.println("catは" + cat.getName());
    }

    /**
     * 動物基底クラスから魚クラスに拡張します
     */
    private class FishClass extends AnimalBaseClass {

        /**
         * 基底クラスのデフォルトコンストラクタをsuperで呼び出しています
         * @param name
         */
        public FishClass(String name) {
            // superより上には何もコードは書けない
            super(name);
        }

        /**
         * 泳ぐというメソッドを独自実装する
         */
        public void swim(){
            System.out.println("泳ぎます");
        }
    }

    /**
     * 動物基底クラスから鳥クラスに拡張します
     */
    private class BirdClass extends AnimalBaseClass {

        public BirdClass(String name) {
            super(name);
        }

        /**
         * 飛ぶというメソッドを独自実装する
         */
        public void fly(){
            System.out.println("飛びます");
        }
    }


    @Test
    public void extendsSample(){

        FishClass fishClass = new FishClass("サバ");
        fishClass.swim();

        BirdClass birdClass = new BirdClass("つばめ");
        birdClass.fly();

        // 基底クラス(スーパークラス)の変数に拡張クラス(サブクラス)を代入する
        AnimalBaseClass fish = new FishClass("サカナ");
        System.out.println(fish.getName());
        // fish.Swim();  // これはできない

        AnimalBaseClass bird = new BirdClass("トリ");
        System.out.println(bird.getName());

        // コンパイルエラー
        // 理由:superクラスにはサブクラスの実装(swim)がないので、実装内容を渡すことができない
//        FishClass errFish = new AnimalBaseClass("さかな");
        // new AnimalBaseClass("さかな")では以下のコードを実行することができないということ
//        errFish.Swim();
    }
}

ジェネリクスとは

一般的には以下のようにクラスを利用してメソッドを呼び出します

public class SomeClass {
    public void method(Integer value){
        // .... 何かしらの処理をする
    }
}

public class Sample {
    public void UseSomeClassMethod() {
        // クラスを変数に代入
        SomeClass someClass = new SomeClass();
        
        // methodを呼び出し
        someClass.method("100");
    }
}

ところが、このメソッドはInteger型しか使えません。
もっと汎用性のあるものにしたいというときはどうすればいいでしょうか?
つまり、IntegerやLong, Doubleも使いたいとしたら?
ひとつの方法はObjectを引数にするということです

public class SomeClass {
    // 引数をObjectにしてみた
    public void method(Object value){
    }
}

public class Sample {
    public void UseSomeClassMethod() {
        // クラスを変数に代入
        SomeClass someClass = new SomeClass();

        // methodを呼び出し
        someClass.method(100);      // int
        someClass.method(30000L);   // long
        someClass.method(3.14F);    // float
        someClass.method(1.2345D);  // double
        // これもできてしまう
        someClass.method(new int[2]);
        someClass.method("あいうえお");
    }
}

ところがこれだと、文字列でも他のクラスでも配列でもなんでも引数として使えることになります。
これは良くないので、クラスを生成する際に何かしらの型を指定して使えるようにできないか?
ということで、できたのが汎用型です (TではなくてもEでもRでも文字はなんでも可)
これをジェネリクス型と呼びます
ジェネリクス型というのは定義するときには型は決まっていないけれども、実際にコードを実行する際に型を決定する手法ということになります。

/**
 * ジェネリクス型
 * @param <T> 
 */
public class SomeClass<T> {
    public void method(T value) {
    }


    public void UseSomeClassMethod() {
        // Integerを使用するクラスとして生成する
        SomeClass<Integer> someClassInt = new SomeClass<>();
        someClassInt.method(100);
        
        // Doubleを使用するクラスとして生成する
        SomeClass<Double> someClassDouble = new SomeClass<>();
        someClassDouble.method(1.2345D);
    }
}

さらにこのジェネリック型を使用し、特定のサブクラスやスーパークラスに限定するようにしたのが上限付きワイルドカード、下限付きワイルドカードです
上限付きワイルドカード extends T>
下限付きワイルドカード super T>

public class WildCardsSample {

    /* ------------------ジェネリック型-------------------------- */
    /**
     * 基底クラス(スーパークラス)
     * @param <T>
     */
    public class GenericClass<T> {
        public void viewData(T value){
            System.out.println(value.toString());
        }
    }

    @Test
    public void UseSomeClassMethod() {
        GenericClass<Double> doubleGenericClass = new GenericClass<>();
        doubleGenericClass.viewData(1.2345D);

        GenericClass<String> stringGenericClass = new GenericClass<>();
        stringGenericClass.viewData("123456");
    }
    /* -------------------------------------------- */


    /* --------------------上限付きワイルドカード------------------------ */
    /**
     * Upper Bounded Wild Cards
     * 上限付きワイルドカード
     * <? extends T>
     */

    private double result;

    // 上限付きワイルドカードを使用したメソッド
    public void sum(List<? extends Number> numbers){
        result = 0d;
        for (Number n : numbers) {
            result += n.doubleValue();
        }
        System.out.println("合計は" + result);
    }

    @Test
    public void upperBoundedWildCardsTest(){
        List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
        sum(integerList);

        List<Double> doubleList = Arrays.asList(1.1, 2.2, 3.3, 4.4, 5.5);
        sum(doubleList);

        // ObjectはNumberよりも上の型なのでコンパイルエラーとなります
//        List<Object> objectList = Arrays.asList(1, 2, 3, 4, 5);
//        sum(objectList);
    }
    /* -------------------------------------------- */


    /* -----------------------下限付きワイルドカード--------------------- */
    /**
     * Lower Bounded Wild Cards
     * 下限付きワイルドカード
     * <? super T>
     */

    private class FishClass extends AnimalBaseClass {
        public FishClass(String name) {
            super(name);
        }
    }

    private class BirdClass extends AnimalBaseClass {
        public BirdClass(String name) {
            super(name);
        }
    }

    private class SeaFishClass extends FishClass{
        public SeaFishClass(String name) {
            super(name);
        }
    }

    // 下限付きワイルドカードを使用したメソッド
    public void addFish(List<? super FishClass> fishList, String name) {
        fishList.add(new FishClass(name));
        System.out.println(name + "が追加されたよ");
    }

    @Test
    public void lowerBoundedWildCardsTest(){
        List<AnimalBaseClass> animals = new ArrayList<>();
        List<FishClass> fishes = new ArrayList<>();
        List<BirdClass> birds = new ArrayList<>();
        List<SeaFishClass> seaFishes = new ArrayList<>();

        addFish(animals, "動物");
        addFish(fishes, "メダカ");

        // これはAnimalBaseClassを継承していてもFishClassの継承ツリーにはないのでコンパイルエラーとなります
        // addFish(birds, "ハト");

        // FishClassよりもサブクラスなので、これはコンパイルエラーとなります
//        addFish(seaFishes, "いわし");
    }
    /* -------------------------------------------- */
}

匿名クラス

ジェネリック型のさらなる利用方法として匿名クラスがあります
これはインタフェースに1つだけのメソッドを作成し、それを継承したクラスを作成せずに使えるようにしたものです
ちなみに1つだけのメソッドを持つインタフェースを関数型インタフェースと言います
そうであることを明記するためにFunctionalInterfaceアノテーションをつけることもできます

public class GenericSample {

    /**
     * メソッドをひとつだけ宣言しているのが特徴です
     */
    @FunctionalInterface   // なくても問題ないが、これをつけておくと複数のメソッドを記入したときにコンパイルエラーとすることができる
    interface Sample {
        void method();
    }

    /**
     * ジェネリック型 (匿名クラス)
     */
    public class GenericExercise {
        public void exercise() {
            // ジェネリック型の実装
            //   本来はclass SampleImpl implements Sampleというインタフェースを継承したクラスが必要だがそれを短縮してる
            Sample sample = new Sample() {
                @Override
                public void method() {
                    System.out.println("Overrideしたメソッドだよ");
                }
            };

            sample.method();
        }
    }

    @Test
     void SampleTest(){
        GenericExercise exercise = new GenericExercise();
        exercise.exercise();
    }


    /**
     * ラムダにしたもの
     * このほうが直感的な感じになります
     */
    public class GenericLambdaExercise {
        public void exercise() {
            // ジェネリック型のラムダでの実装
            Sample sample = () -> System.out.println("ラムダでOverrideしたメソッドだよ");
            sample.method();
        }
    }

    @Test
    void SampleLambdaTest(){
        GenericLambdaExercise exercise = new GenericLambdaExercise();
        exercise.exercise();
    }
}

既存の関数型インタフェース

    Consumer<T>
        accept(T value)
    Supplier<T>
        T get()
    Predicate<T>
        boolean test(T value)
    FUnction<T, R>    
        R apply(T value)
    ....など

これらは既存の様々なメソッドで使用されていますが、たとえばsystem.out.println()はConsumerを呼び出しています
Streamの中間処理でもそれらが利用されていたりします

public FunctiolanInterfaceClass {
    /**
     * Consumer<T>
     *     T型の引数を受けてメソッドを実行する
     */
    @Test
    void consumerTest() {
        Consumer<String> consumer = x -> System.out.println(x + "です");

//        ラムダではない書き方だと以下のようになります(ラムダが使いやすいのがよく分かりますね)
//        Consumer<String> consumer = new Consumer<String>() {
//            @Override
//            public void accept(String s) {
//                System.out.println(x + "です");
//            }
//        };

        consumer.accept("コンシューマ");
    }

    /**
     * Predicate<T>
     *     T型の引数を受けてbooleanを返します
     */
    @Test
    void PredicateTest() {
        Predicate<String> predicate = x -> x.equals("あ");

//        ラムダではない書き方だと以下のようになります(ラムダが使いやすいのがよく分かりますね)
//        Predicate<String> predicate = new Predicate<String>() {
//            @Override
//            public boolean test(String s) {
//                return s.equals("あ");
//            }
//        };

        System.out.println(predicate.test("あ"));    // true
        System.out.println(predicate.test("い"));    // false
    }
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0