C# と Java の微妙な違い

c#とJavaって基本的な構文は相当似てるんですが、やっぱり違う言語なので微妙に違います。

両方を使い比べてみて、やっぱり全く違うんだな、と感じたことをいくつか書いてみようと思います。

アクセス修飾子

説明 c# Java
クラス外部からもアクセス可能 public public
派生クラス内からアクセス可能 protected protected
クラス内からのみアクセス可能 private private
internal 該当なし EXE, DLL内でのみ使用可能*1
該当なし package-private 同じパッケージ内でアクセス可能

お互い全然違うのが,internalとprivate-packageです。アクセス範囲のコントロールをする意図は同じですが公開される範囲が全然違います。internalの方が全然広いです。

この違いで開発単位を割り当てる際にパッケージで1人とかEXE、DLLで一人のような判断に影響する場合があります。

アクセス修飾時を省略したときの挙動

Javaの場合

アクセス修飾子を省略すると、自動的に「package-private」となります。

このアクセス修飾子は予約後にありません。従って、「指定したいときは省略する」と考えてると一瞬あれっ?という感が漂います。なので、この場合、指定しないとprivate-packageだけど公開したいから他のアクセス修飾子を付けると考えたほうが自然です。

void getMessage(int no) {

ただ、他のメンバーには必ずアクセス修飾子が付いてる中で、省略してるメンバーが突然出てくると、少々気持ち悪い人も中にはいるようです。なので

@PackagePrivate
void getMessage(int no) {

とか

/** package */ void getMesage(int no) {

のように書く人も中には居るようです。

c#の場合

アクセス修飾子を省略すると「クラスはinternal」、「メンバーはprivate」になります。c#はアクセス修飾子は全て予約後になっています。自分の意図したアクセスレベルを明示的に書くことができます。人によってはばらばらで気持ち悪いと感じる人も居るようです。

class MessageTable
{
    void getMessage(int no)
    {

internal class MessageTable
{
    private void getMessage(int no)
    {

は完全に同じ意味です。

コンストラクタ呼び出しの後の中括弧

最初にc#を使っていた自分としてはこの挙動は割と衝撃を受けました。インスタンスを「new」を使って宣言した直後に中括弧を付けたときの挙動です。

Sample sample = new Sample() { // ← この事です
Javaの場合

簡単に言うと、匿名クラスがその時に宣言できます。

説明しにくいのでコードで解説しますが、例えばこんなインターフェースがあったとして

public interfaec IAreaTouched {
    void touched(final object pEventSource, final object parameter);
}

上記クラスを利用する時にわざわざ

public class ScreenTouchListner implements IAreaTouched {
    @Override
    public void touched(final object pEventSource, final object parameter) {
        // 何らかの処理の記述
    }
}

と宣言したクラスを作ってから利用するコード側で

public static void main(String args[]) {
    // 上記で宣言したクラスをインスタンス化
    IAreaTouched listener = new ScreenTouchListner();
}

なんていちいちやってると、激しくめんどくさいので、インターフェースを実装したクラスをいちいち宣言せずに

public static void main(String args[]) {
    // 上記で宣言したクラスをインスタンス化
    IAreaTouched listener = new IAreaTouched() {
        @Override
        public void touched(final object pEventSource, final object parameter) {
            // その時に必要な挙動を定義
        }
    }
}

すると、めんどくさい事しないで済むよねということになります。ただ、コード上に宣言と実装が混在してコードが汚くなりがち & 再利用できないという欠点もあります。

Java8からはクロージャが使えるのでもう少しめんどくさい感は減ります。*2

c#の場合

c#の場合、挙動をその場で定義することはできません。中括弧からは後ろは公開メンバーの初期化(オブジェクト初期化子、コレクション初期化子の利用)ができます。

public MemberName
{
    public void Name { get; set; }
    
    public IList<string> Relations { get; set; }
}

上述のクラス宣言があった時に利用者側コードで以下のように記述すると

public static void Main(string[] args)
{
    var member = new MemberName()
    {
        // オブジェクト初期化子でプロパティを初期化
        Name = "Takachan", 

        // オブジェクト初期化子 + コレクション初期化子でプロパティを初期化
        Relations = new List<string>() { "Iketani", "Tanaka", "Mochizuki" },
    };
}

となります。

*1:リンク先からは不可視です

*2:個人的な話、身の回りにJava8をバリバリ使う風潮が無いためいつまでたってもこの構文を使わざるを得ません