トップ画像
Privateの仕様を勘違いしてました

執筆者: オキリョウ

最終更新: 11/5/2021

プログラミングできる人からしたら何言ってんだという内容かもしれませんが、結構ショックを受けたので残しておきます

先日、Kotlinのコードを読んでいたら、こんな感じのコードに出会いました

class Foo(private var value: String) {
    operator fun plus(other: Foo): Foo {
        return Foo(this.value + other.value)
    }
}


パッと見普通のコードなのですが、このコードのために頭を抱えさせられました
というのも、別のインスタンスのprivateプロパティにアクセスするコードが書かれているからです


class Foo(private var value: String) {
operator fun plus(other: Foo): Foo {
return Foo(this.value + other.value)
}
}


例えば以下のようなコードが書けてしまいます

fun main() {
    val foo1 = Foo("Hello")
    val foo2 = Foo("World")

    println(foo1 + foo2)
}


結果

Foo: { value : HelloWorld }


このコードでは、foo1とfoo2は、もともとのクラスは同じですが、全く別のインスタンスです。
しかし、このように書くことで、別のインスタンスのprivateなプロパティにもアクセスできるようになっています

そこで調べてみたところ、以下の記述がありました(参照先

private はそのクラス内(そのすべてのメンバーを含む)でのみ見える


よく見るとインスタンスではなく、クラス内のみアクセス可能と書いていました
つまり同一のクラスからできたインスタンスだとアクセス可能らしいです
だから最初のコードでもコンパイル&実行できるということらしいです

しかし、ここでいくつかの疑問が出てきます。そこでそこらへんも調べてみました

Privateなメソッド(関数)でも同じなのか


同じように動くようです(参考先

サブクラスだとどうなるのか


動きません
仮に、サブクラスも参照可能にするprotectedを利用しても不可能でした

open class Foo(protected var value: String)

class Bar(private val name: String): Foo(name){
    
    // 引数のクラスの参照はNG
    fun ng(other: Foo) {
        println(other.value)
    }
    
    // 自身の親の参照はOK
    fun ok() {
        println(value)
    }
}


拡張関数だとどうなるか


無理です

open class Foo(private var value: String)

// NG
fun Foo.print(){
    println(this.value)
}


これはJavaにコンパイルされたときのコードを見ればわかります

public static final void print(@NotNull Foo $this$print) {
  // Something...
}


Javaには拡張関数は存在しないため、引数としてクラスを渡して、それで処理しています
ですので、privateプロパティにはアクセスできないので、動きません

自身のインスタンスだけアクセスできるようにする方法はないのか


少なくとも公式からは出されていない感じがします(privateが一番厳しい制約とのこと)

Kotlinの場合、DelegateとかReflectionを使えば書ける気もしないこともないですが、書いたところでコンパイルエラーになりません

したがってほとんど意味を成しません

ですので、コメントやアノテーションなどで、分かるようにするのが精いっぱいだと思います

他の言語だとどうか


C#


using System;
					
public class Program
{
	public static void Main()
	{
		Foo foo1 = new Foo("Hello");
		Foo foo2 = new Foo("World");
		
		Console.WriteLine(foo1.getValue(foo2));
	}
}

public class Foo
{
	String value;
	
	public Foo(String value)
	{
		this.value = value;
	}
	
	public String getValue(Foo other)
	{
		return other.value;
	}
}


結果

World


動いています

PHP


<?php

$foo1 = new Foo("Hello");
$foo2 = new Foo("World");

print $foo1->getValue($foo2);

class Foo
{
    private string $value;
    
    function __construct(string $value)
    {
        $this->value = $value;
    }
    
    function getValue(Foo $other): string
    {
        return $other->value;
    }
}


結果

World


動いています


ですので、Java、C#、PHPで動くため、おそらくほぼすべてのプログラミング言語はこの仕様だと考えられます

まとめ


最初見たときは本当に驚きましたが、冷静になって考えるとこちらの方が理にかなっていますね

同一インスタンスじゃないとアクセスできない!としないといけない場面てほとんどないでしょうし

むしろこちらの方が、クラスの役割をきっちりかけていい感じがします

今までずっと勘違いしてきたことを今更知って本当に恥ずかしいです・・・

取得に失敗しました

2020年度 入部

GitHub