白猫のメモ帳

C#とかJavaとかJavaScriptとかHTMLとか機械学習とか。

Java使いがC#を勉強する その⑥ 拡張メソッド

こんばんは。

何やら今日は妙に温かかったですね。
そして明日は寒いらしいですね。体調に気をつけましょう。

さて、C#には拡張メソッドというデンジャラスな機能があります。
今回はこれを見ていきます。

拡張ってどういうことだ


通常、あるクラスにメソッドを追加したい場合には、
そのクラスに直接メソッドを書きます。

public class Hoge {}

public class Hoge
{
  public void Piyo()
  {
    Console.WriteLine("ぴよ");
  }
}

しかし、そのクラスが自分の持ち物ではない場合、
むやみやたらにクラスに変更を加えるわけにはいきません。

そのため、対象のクラスを継承した派生クラスを作ります。
そうすると派生クラスでは親クラスのメソッドと自クラスのメソッドの両方が使えます。

public class HogeEx : Hoge
{
  public void Piyo()
  {
    Console.WriteLine("ぴよ");
  }
}

が、元のクラスに変更を加えたくない(再コンパイルしたくない)けれど、
メソッドを追加したいという場合に拡張メソッドという機能を使うことができます。

public class ExtensionMethods
{
  public void Piyo(this Hoge hoge)
  {
     Console.WriteLine("ぴよ");

  }
}

つまり、クラスにメソッドを外から「拡張」するわけですね。

 

使うべきじゃない?

 

なかなかに便利そうな拡張メソッドですが、いいことばかりではありません。

①Objectクラスにメソッドを拡張した場合、すべての派生クラスにメソッドが追加される
②拡張メソッドの定義はどこで行ってもよいため、メソッドの定義が分散する
③クラスのメソッドとシグネチャが一致した場合、拡張メソッドは優先されない
④拡張メソッドの定義が複数読み込まれた場合、どちらも利用できない

などなど、様々な問題があります。

③では言語仕様の変更やライブラリのアップデートに伴ってメソッドが追加され、
既存のコードの挙動が変わる可能性があります。

④でも同じ拡張メソッドが追加された場合、コードが正常に動作しません。


一般的に有用に利用されるのは以下の2つの場面が多いようです。

インタフェースへのメソッド追加

前回の記事でも触れましたが、基本的にインタフェースにメソッドの実装を書くことはできません。
そのため、多重継承のないC#ではMix-Inができないわけです。

しかし、拡張メソッドを使うとインタフェースにもメソッドを追加することができます。
前回の記事のコードそのままです。

public interface IPiyo {}

public static class Util
{
  static string Piyo(this IPiyo piyo) {
    return "ぴよ";
  }
}

便利ですけど、そのインタフェースに書きたくないですか?
はて。

enumへのメソッド追加

C#では列挙型にメソッドを実装することができません。
それどころか値を持たせることすらできません。

enum Direction { North, East, South, West }

static class DirectionExtension {

    static string Name(this Direction direction) {
        string name = "";
        switch (direction) {
            case Direction.North:
                name = "北";
                break;
            case Direction.East:
                name = "東";
                break;
            case Direction.South:
                name = "南";
                break;
            case Direction.West:
                name = "西";
                break;
        }
        return name;
    }
}

一応、拡張メソッドを使うとメソッドが作れます。
値は渡せませんが。
うわー死んでしまう。

なんかこれなら自分でタイプセーフEnum作りたい。

sealed class Direction {

    public string Name { get; private set; }

    private Direction(string name) {
        this.Name = name;
    }

    public static readonly Direction North = new Direction("北");
    public static readonly Direction East = new Direction("東");
    public static readonly Direction South = new Direction("南");
    public static readonly Direction West = new Direction("西");
}


ご利用は計画的に


個人で作るアプリでは標準ライブラリのクラスなどに
好き勝手に拡張メソッドを作ると楽しいのかもしれません。

が、チームで開発するときにはあまりお勧めできなそうです。
用法用量を守って正しく使いましょう。

Java使いがC#を勉強する その⑤ 継承とインタフェース

こんばんは。

今回はクラスとインタフェースについて比較します。
オブジェクト指向なので、だいたいクラスでしょといってしまえばおしまいなのですが、
それはそれということで。

命名規則


クラスはJavaC#パスカル記法(キャメルケースの先頭も大文字)を使います。

public class Piyo {}


インタフェースもパスカル記法ですが、C#では先頭に「I」を付けます。
個人的にはこれはあまり好きじゃないですね。

public interface IPiyo {}

継承


Javaではクラスの継承とインタフェースの実装は区別されますが、

public class Hoge extends Fuga implements Piyo {}

C#では特に区別はありません。

public class Hoge : Fuga, IPiyo {}


継承に関する修飾子


abstract

abstract修飾子はクラス、メソッドなどが抽象的なものであることを表します。
abstractメソッドはabstractクラスにしか定義できません。
文法としてはJavaC#も同じです。

abstract class ParentClass
{
  abstract void Method();
}

abstractクラスは自身をインスタンス化できません。
インスタンス化するためにはこれを継承したクラスを作る必要があります。

継承したクラスがabstractでない場合、
親クラスのすべてのabstractメソッドを実装しなければなりません。

class ChildClass : ParentClass
{
  override void Method()
  {
    Console.WriteLine("ほげ");
  }
}

virtual

virtual修飾子はメソッドなどが派生クラスでオーバーライド可能であることを示します。
Javaではfinalを付けなければオーバーライド可能なので、
基本的にvirtual修飾子がついているようなイメージですね。

class ParentClass
{
  virtual void Method()
  {
    Console.WriteLine("ほげ");
  }
}

class ChildClass : ParentClass
{
  override void Method()
  {
    Console.WriteLine("ふが");
  }
}

abstractは実装なしで派生クラスに実装を促し、
virtualは実装ありで派生クラスで上書きできるということです。

sealed

sealed修飾子はクラスの継承を禁止します。
Javaではfinalのついたクラスに相当します。

sealed class ParentClass {}
class ChildClass : ParentClass{} // エラー

メソッドはもともとvirtual(かabstract)がないとオーバーライドできませんが、
派生クラスでオーバーライドしたうえで、さらにその派生クラスでのオーバーライドを防ぐ目的で、
メソッドにもsealed修飾子を付けることができます。

class ParentClass
{
  public virtual void Method() {}
}

class ChildClass : ParentClass
{
  public sealed override void Method() {}
}

class GrandchildClass : ChildClass 
{
  // エラー
  public override void Method() {}
}

new

new修飾子は継承されたメソッドなどを明示的に隠蔽できます。
また、new修飾子を付けなくても隠蔽は可能ですが、コンパイルの警告が出ます。

class ParentClass
{
  public void Method() {}
}

class ChildClass : ParentClass
{
  public new void Method() {}
}

一見するとoverrideと同じように見えますが、
拡張ではなく隠蔽なので、少し挙動が異なります。

class A 
{
  public virtual void Method() 
  {
    Console.WriteLine("A");
  }
}

class B : A 
{
  public override sealed void Method() 
  {
    Console.WriteLine("B");
  }
}

class C : B 
{
  public new void Method() 
  {
    Console.WriteLine("C");
  }
}

↑こんな風にすると、こうなる。↓

A a = new A();
A b = new B();
A c = new C();

a.Method(); // A
b.Method(); // B
c.Method(); // B

つまり、overrideはインスタンスの型で呼び出されるメソッドが変わり、
newは変数の型で呼び出されるメソッドが変わるわけですね。

ちなみにsealedを付けても隠蔽はできます。

多重継承的ななにか


JavaにもC#にも多重継承はないですが、似たようなことができる機能があります。

Javaの場合にはインタフェースのデフォルト実装を利用します。

public interface Piyo {
  default String piyo() {
    return "ぴよ";
  }
}


一方でC#では拡張メソッドを利用します。

public interface IPiyo {}

public class Util
{
  static string Piyo(this IPiyo piyo) {
    return "ぴよ";
  }
}

拡張メソッドは既存のクラス、インタフェースなどに好きにメソッドが追加できて便利ですが、
実装したいインタフェース自体に定義が書けないのがなんだか混乱しそうです。そうでもない?

拡張メソッドについては次回もう少し詳しく見ていこうかとおもいます。

 

なりますまし許すまじ


アクセス修飾子はまたの機会に。
volatileとかtransientとかstrictpfはまぁいいかな。
staticは・・・まぁいいかな。

個人的にはオーバーライドするときに上のクラスの変更とかしたくないので、
断りがなければ継承できるJavaスタイルの方が好きなのですけれど。むぅ。

new修飾子がなんだかとても難しいです。
これはポリモーフィズムを破壊したりしないの?
うーん。うーん。

お前もFunctionalInterfaceにしてやろうか

こんばんは。

タイトルの元ネタ、誰のセリフか知らなかったのですが、デーモン閣下だったのですね。
あと、藁人形だと思ってたけど蝋人形でした。ごめんなさい。

しばらくC#ネタを書いていましたが、たまにはJavaネタを。
安心感。

で、Java8で追加された機能にインタフェースのデフォルト実装があるのですが、
これのちょっとした小技を見ていきます。

関数型インタフェース


Java8でラムダ式が導入されたことに伴い、
単一のメソッドのみを持つインタフェースを
関数型インタフェース(FunctionalInterface)として扱うことができるようになりました。

通常、関数型インタフェースには「FunctionalInterface」アノテーションを付けますが、
ついていないからと言ってラムダを使えないというわけではありません。
Javaの注釈ってそういうもんです。Overrideとかね。)

例えば、引数を取らずに結果だけを返す関数として「Supplier」クラスがありますが、
こんな定義になっています。

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

で、こんな風にラムダ式が使えるわけです。

// ただ「あいうえお」という文字列を返すだけの関数
Supplier<String> supplier = () -> "あいうえお";


もしくはメソッド参照を使って、こんな感じです。

// ただ「かきくけこ」という文字列を返すだけの関数
private String k() {
    return "かきくけこ";
}

private void hoge() {
    Supplier<String> supplier = this::k;
}


ところで同じ関数型の「Consumer」クラスを見てみると、こんなことになっています。

@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

あれ、なんかいますね。

冒頭で「単一のメソッドのみを持つインタフェース」と書きましたが、実はこれは正確ではなくて、
「単一の抽象メソッドのみを持つインタフェース」が正しいのです。

「andThen」は「accept」を呼び出しているので、
ひとつの関数定義を渡すことによって複数のメソッドが利用できるようになるわけですね。

public static void main(String[] args) {

    Consumer<PrintStream> consumer1 = ps -> ps.println("ABC");
    Consumer<PrintStream> consumer2 = ps -> ps.println("DEF");

    consumer1.andThen(consumer2).accept(System.out);
}

 

誰だって関数型インタフェースになれるよ


さて、関数型インタフェースにはdefaultメソッドを作ってもいいことがわかりました。

ということはですよ、複数の抽象メソッドを持つ関数型インタフェースで、
1メソッド以外をサブインタフェースでデフォルト実装したら、
これはもう関数型インタフェースになるんじゃないのっていう気がしてきます。

試してみましょう。

public interface Greeter {
    String greeting();
    String customizedGreeting();
}

@FunctionalInterface
public interface PiyoGreeter extends Greeter {
    @Override
    default String customizedGreeting() {
        return greeting() + "ぴよ";
    }
}

こんな風にdefaultでオーバーライドして、

private static String greet(PiyoGreeter greeter) {
    return greeter.customizedGreeting();
}

public static void main(String[] args) {
    System.out.println(greet(() -> "おはよう"));    // おはようぴよ
    System.out.println(greet(() -> "こんばんは"));  // こんばんはぴよ
}

おぉ、できた!

できた・・・けど、どんな時に使うんでしょうね。
うーん。