メヘンニミン

たくさんの言語に触れたい.走り書きで見る整理ブログです.

オブジェクト指向の『変数』 ~ なんでnewなん?

前回 オブジェクト指向は勉強すべき? - メヘンニミン


今回から理解の模索を始めていきます.基本中の基本!という話題で申し訳ないんですが,まずは「変数」から攻めてみましょう.

2種類の型

変数には種類があります.「値型」「基本型」「プリミティブ型」「参照型」「クラス型」…

包括していたり,別名だったり,そもそも言語で定義が異なることもあります.
# Javaでは,値型とプリミティブ型は同じ意味として扱われますが,.NETではそうでもないとか.
# Java - プリミティブ型って何だ? - Qiita

しかしどのオブジェクト指向言語の教材にも,大きく分けて2つだよ!!と書かれています.
本記事ではひとまず,この2つの呼び方を値型参照型に統一します.

ではこの2種類,何が違うか?

値型はその名のとおり,具体値をもつ変数です.Javaだと,こういうのですね.

boolean b = true;
int i = 1;
char c = 'x';
i += 3;


一方で参照型は,具体値への参照情報をもつ変数です.よって代入時は注意しろ!という話は非常に多~~くの教材で語られているんですが,ここでは言及しません.そんなことより…

new好きはそういう年頃でもなんでもない

今回の主題です.

参照型の変数は宣言後,値型の変数と比較してなにか呪文が必要でした.参照型である,JavaのPoint型の例を見てみましょう.

Point p = new Point(); //これ
p.X = 3;
p.Y = 5;
System.out.println(p.X); //出力

そう.何故参照型の変数はnewする必要があるのか.ここですよ!

僕もプログラミング習いたての頃は,この変数はnewをする,ひとまずnewをするという,何気なーい軽ーいふわーりの気持ちで書いていました.実はココの理解こそが,オブジェクト指向講座の軽ーいスタート地点だったのです.

早い言い訳は,この変数に直接具体値を代入できない点にあります.

Point p = 3; //これは,よくわかんない.エラー


Point型は,2つのint型変数を1つの変数の中に設定するために作られたクラスです.クラスは,メンバ変数メンバメソッドで構成されています.Pointクラスは,「X」というint型変数と,「Y」というint型変数の2つのメンバ変数をもっています.もっている,というよりは,そう定義されている,ということですね.

クラスはよく変数の定義書だと言われます.あんたはPoint型だ!と言われた変数は必ず上記の定義に従います.よって,"3"などという具体値を直接代入するという破廉恥な操作には対応しないんですね.


いやいやそもそもnew演算子って?

しかしここまでの話は,まだnew演算子を使う説明にはなっていません.こうすればいいんだから.

Point p; //宣言する
p.X = 3; //でもここでエラる
p.Y = 5;
Point p = Point(); //これもダメ?
p.X = 3; //ダメ
p.Y = 5;


new演算子については,Wikipediaさんが何かを言いたげでした.

new演算子によるインスタンスの作成は、大きく分けて、記憶域を確保することと初期化を行うことに分けられる。

New演算子 - Wikipedia

イメージしましょう.

Point p; //宣言してから
p = new Point(); //この行では何をしているのか?

参照型の変数は,具体値をもちません.変数pが欲しいのは"1"とか"x"などの簡単に表せる情報ではなく,参照情報です.参照するからには,参照先が必要です.宣言だけではその参照先がどこにあるか,そもそもそのどこかで参照先が作られることもありません.

newは,メモリの動的確保をしてください!という記述です.動的というのは,その行を読み込んだ時点でメモリをガバッと占有してください!というニュアンスです.占有して,クラスの定義に従い参照先を作成します.そして,メモリ内で占有した位置=参照情報を,変数pに代入しています.この作業をオブジェクト化=実体化といいます.

# 動的のいいところや,メモリ管理についての話は,またいつか.


newが握るオブジェクト指向の鍵

Point a, b, c;
a = new Point(); //実体化
b = new Point(); //実体化
c = new Point(); //実体化

さぁ,ここからがポイント!(Pointだけに!)

たくさんのPoint変数を宣言し,次々にnew演算子を使用する場合,メモリはどんどん確保され,占有され,実体となってオブジェクトが量産されていきます.でも,これらの変数を作るために用いたクラス(定義書)は1つだけです.オブジェクト指向がコード量を減らすための工夫だと紹介したのは前回.定義書1つでたくさんの同じ仕様を再現できるなら,これほどの面倒大削減はないでしょう.

なお,定義書から実体を生成してうんぬん~の説明は,よく車の例で例えられます.車クラスという定義書があるなら,そこから車A,車B,車Cという実体を生成し,その置き場所情報を変数として保存しておく.この具体化の概念は,オブジェクト指向の中でも非常に重要です.


以上がnew演算子を使用必須とする仕様の話でしたが,使用するメリットもちゃーんとあります.それが初期化

Point p; //宣言
p = new Point(3, 5); //実体化,初期化
Point a = new Point(2, 4); //全てを1行でやる


変数宣言と実体化+初期化は,全く別々の作業です.この2つを別々にやること,タイミングをずらせることは動的変数としてのメリットであり,new演算子の記述によりその利点を可能としています.

# 初期化についても,また別の機会で述べることにしますね.

newの省略可能性


「クラスをもつ参照型変数は,宣言のあとで実体化が必要だ.そのための記述がnewだった.」

しかしそれでも,「new演算子は省略できるんじゃないか?」との声はありそうです.
よく思い出してください.配列型やString型の変数は,参照型に分類されます.あ,じゃあnewを使うんですよねそうですよね…

int[] array = {13, 24, 7}; //配列
String str = "おはようございます"; //文字列

使っていない.使ってねえ.

とくに,String型変数は直接具体値(文字列)が代入されている?ように見えますね.はて?

値型と参照型,そしてnew演算子を絡ませる理解では,個人的には,例えば上の例のような現象が厄介のひとつだという気がしています.次の機会(いつか)では,このnew演算子を省略できる例と,ほかの参照型変数について見ていきます.


# ここまで長文化するとは,前回のぐったりしたい宣言はどこ.