Y's note

Web技術・プロダクトマネジメント・そして経営について

本ブログの更新を停止しており、今後は下記Noteに記載していきます。
https://note.com/yutakikuchi/

Javaを最速でマスターするための予備知識7項目

Java: The Good Parts

Java: The Good Parts

index

  • 1.Compile
    • javac
    • Making Jar
  • 2.Class
    • Relations
    • Basic Class
    • Nested Class
    • Static Nested Class
    • Inner Class
    • Local Class
    • Anonymous Class
  • 3.NameSpace
  • 4.Annotation
  • 5.Generics
  • 6.Extension for
  • 7.Variable Arguments
  • Links

1.Compile

Javac

JavaのCompileにはjavacを利用します。javacコマンドの実行により.javaなどのjavaソースファイルを.classファイルにコンパイルします。javacの使用についてはhelpが参考になるので内容を見てみます。

$ java -version
java version "1.7.0_01"
Java(TM) SE Runtime Environment (build 1.7.0_01-b08)
Java HotSpot(TM) 64-Bit Server VM (build 21.1-b02, mixed mode)

$ javac -help
使用方法: javac <options> <source files>
使用可能なオプションには次のものがあります。
  -g                         すべてのデバッグ情報を生成する
  -g:none                    デバッグ情報を生成しない
  -g:{lines,vars,source}     いくつかのデバッグ情報のみを生成する
  -nowarn                    警告を発生させない
  -verbose                   コンパイラの動作についてメッセージを出力する
  -deprecation               推奨されないAPIが使用されているソースの位置を出力する
  -classpath <path>          ユーザー・クラス・ファイルおよび注釈プロセッサを検索する位置を指定する
  -cp <path>                 ユーザー・クラス・ファイルおよび注釈プロセッサを検索する位置を指定する
  -sourcepath <path>         入力ソース・ファイルを検索する位置を指定する
  -bootclasspath <path>      ブートストラップ・クラス・パスの位置をオーバーライドする
  -extdirs <dirs>            インストール済み拡張機能の位置をオーバーライドする
  -endorseddirs <dirs>       推奨規格パスの位置をオーバーライドする
  -proc:{none,only}          注釈処理やコンパイルを実行するかどうかを制御します。
  -processor <class1>[,<class2>,<class3>...] 実行する注釈プロセッサの名前。デフォルトの検出処理をバイパス
  -processorpath <path>      注釈プロセッサを検索する位置を指定する
  -d <directory>             生成されたクラス・ファイルを格納する位置を指定する
  -s <directory>             生成されたソース・ファイルを格納する場所を指定する
  -implicit:{none,class}     暗黙的に参照されるファイルについてクラス・ファイルを生成するかどうかを指定する
  -encoding <encoding>       ソース・ファイルが使用する文字エンコーディングを指定する
  -source <release>          指定されたリリースとソースの互換性を保つ
  -target <release>          特定のVMバージョン用のクラス・ファイルを生成する
  -version                   バージョン情報
  -help                      標準オプションの概要を出力する
  -Akey[=value]              注釈プロセッサに渡されるオプション
  -X                         非標準オプションの概要を出力する
  -J<flag>                   <flag>を実行システムに直接渡す
  -Werror                    警告が発生した場合にコンパイルを終了する
  @<filename>                ファイルからの読取りオプションおよびファイル名

よく使いそうなオプションは-verbose、-deprecation、-classpath、-d、-s、-encodingでしょうか。以下ではverboseとencoding(UTF-8で指定)についてサンプルを載せておきます。

$ javac -verbose Request.java 
[RegularFileObject[Request.java]を構文解析開始]
[17ミリ秒で構文解析完了]
[ソース・ファイルの検索パス: .]
(略)
[ZipFileIndexFileObject[/usr/java/jdk1.7.0_01/lib/ct.sym(META-INF/sym/rt.jar/java/io/InputStream.class)]を読込み中]
[ZipFileIndexFileObject[/usr/java/jdk1.7.0_01/lib/ct.sym(META-INF/sym/rt.jar/java/lang/Object.class)]を読込み中]
[ZipFileIndexFileObject[/usr/java/jdk1.7.0_01/lib/ct.sym(META-INF/sym/rt.jar/java/lang/String.class)]を読込み中] 
(略)
[RegularFileObject[Request.class]を書込み完了]
[合計991ミリ秒]

$ javac -encoding utf-8 Request.java

特定のディレクトリ以下で複数javaファイルを一度にCompileしたい時は直列でファイルを複数指定するか、まとめてアスタリスク(*)を使う事も出来ます。

$ javac file1.java file2.java
$ javac *.java
Making jar

jarはJava classファイルのアーカイブ(Java Archive)で、ファイルを一つずつ管理する手間が省けます。jarファイルを作成するにはjarコマンドを使います。一般的な圧縮ファイルに用いられるUnixコマンドのtarと同じような形式で実行できます。下はJarのhelpコマンドです。

$ jar   
使用方法: jar {ctxui}[vfm0Me] [jar-file] [manifest-file] [entry-point] [-C dir] files ...
オプション:
\  -c アーカイブを新規作成する
\  -t アーカイブの内容を一覧表示する
\  -x 指定の(またはすべての)ファイルをアーカイブから抽出する
\  -u 既存アーカイブを更新する
\  -v 標準出力に詳細な出力を生成する
\  -f アーカイブ・ファイル名を指定する
\  -m 指定のマニフェスト・ファイルからマニフェスト情報を取り込む
\  -e 実行可能jarファイルにバンドルされたスタンドアロン・アプリケーションの
\     エントリ・ポイントを指定する
\  -0 格納のみ。ZIP圧縮を使用しない
\  -M エントリのマニフェスト・ファイルを作成しない
\  -i 指定のjarファイルの索引情報を生成する
\  -C 指定のディレクトリに変更し、以下のファイルを取り込む
ファイルがディレクトリの場合は再帰的に処理されます。
マニフェスト・ファイル名、アーカイブ・ファイル名およびエントリ・ポイント名は、
フラグ'm'、'f'、'e'の指定と同じ順番で指定する必要があります。

例1: 2つのクラス・ファイルをアーカイブclasses.jarに保存する:
\    jar cvf classes.jar Foo.class Bar.class
例2: 既存のマニフェスト・ファイル'mymanifest'を使用し、foo/ディレクトリの
\   全ファイルを'classes.jar'にアーカイブする:
\    jar cvfm classes.jar mymanifest -C foo/ 

下はjarのサンプルClassです。SampleJar.javaとして保存します。保存したファイルをコンパイルするとSampleJar.class SampleJar2.classというclassファイルが2つできるのでそれをjarコマンドでまとめます。まとめたファイルに対してjava -cpコマンドで実行します。また実行できるようになったJarファイルの中身を見てみます。jar xvfコマンドでjarファイルを解凍できます。

class SampleJar {
    public static void main( String[] args ) {
        System.out.println( "Hello! SampleJar" );
    }
}

class SampleJar2 {
    public static void main( String[] args ) {
        System.out.println( "Hello! SampleJar2" );
    }
}
$ javac SampleJar.java

$ jar cvf SampleJarTest.jar SampleJar.class SampleJar2.class
マニフェストが追加されました
SampleJar.classを追加中です(入=428)(出=290)(32%収縮されました)
SampleJar2.classを追加中です(入=430)(出=291)(32%収縮されました)

$ java -cp SampleJarTest.jar SampleJar           
Hello! SampleJar

$ ava -cp SampleJarTest.jar SampleJar2
Hello! SampleJar2

$ jar xvf SampleJarTest.jar 
META-INF/が作成されました
\META-INF/MANIFEST.MFが展開されました
\SampleJar.classが展開されました
\SampleJar2.classが展開されました

$ tree
.
|-- META-INF
|   `-- MANIFEST.MF
|-- SampleJar.class
|-- SampleJar2.class
`-- SampleJarTest.jar

META-INF/MANIFEST.MFにMain-Class: SampleJarと加えるとmainで呼び出せるクラスを指定できます。書き直したサンプルを以下に記します。またjarコマンドでmanifestファイルを指定し、作成されたjarファイルを実行してみます。

$ diff -u MANIFEST.MF MANIFEST.MF.bak
--- MANIFEST.MF	2012-04-24 02:05:12.000000000 +0900
+++ MANIFEST.MF.bak	2012-04-24 02:11:10.000000000 +0900
@@ -1,3 +1,2 @@
 Manifest-Version: 1.0
 Created-By: 1.7.0_01 (Oracle Corporation)
-Main-Class: SampleJar

$ jar cvfm SampleJarTest.jar META-INF/MANIFEST.MF *.class
マニフェストが追加されました
SampleJar.classを追加中です(入=428)(出=290)(32%収縮されました)
SampleJar2.classを追加中です(入=430)(出=291)(32%収縮されました)

$ java -jar SampleJarTest.jar
Hello! SampleJar

$ java -cp SampleJarTest.jar SampleJar 
Hello! SampleJar

$ java -cp SampleJarTest.jar SampleJar2 
Hello! SampleJar2

2.Class

Relations

Basic、Inner、Static Nested、Local、AnonymousといったClassの関係性を以下に示します。Inner ClassとStatic Nested Classは同じNested Classに属しますが、それぞれ異なった役割をもちます。またLocal ClassとAnonymous ClassはInner Classに属します。

  • Basic Class
  • Nested Class
    • Static Nested Class
    • Inner Class
      • Local Class
      • Anonymous Class
Basic Class

JavaはClassを定義しないと何も始まりません。main関数でさえClass内部に記述します。main関数はpublic static void main( String[] args )として定義します。通常は1Javaファイルに対して1Classを書きます。後で詳細を載せようと思いますが、public staticとするとインスタンス化しなくてもコンパイルだけで実行可能になります。classを定義する時に他の言語と同様にコンストラクタが定義可能ですが、Javaにはデストラクタが存在しないようです。代わりにガーベージコレクションが発生したタイミングで呼び出されるfinalizerというものがあるようですが、常に呼び出される訳ではないので定義する時は注意が必要です。

// SampleClassを利用するClient
class SampleClient {
    // staticを付けないとクラス内から呼び出せない
    private static boolean flag = false;
    public static void main( String[] args ) {
        SampleClass sc = new SampleClass( "John", 5 );
        System.out.println( sc.getName() );
        System.out.println( sc.getNumber() );
        setFlag();
        System.out.println( getFlag() );
        System.gc();
    }

    // staticを付けないとクラス内から呼び出せない
    private static void setFlag() {
        flag = true;
    }
    
    // staticを付けないとクラス内から呼び出せない
    public static boolean getFlag() {
        return flag;
    }
}

class SampleClass {

    // メンバ変数
    private String Name;
    private int Number;
    
    // コンストラクタ
    SampleClass( String name, int number ) {
        System.out.println( "called constructor" );
        this.Name = name;
        this.Number = number;
    }

    // デストラクタはJavaでは不要 代わりとしてfinalizerが存在する
    public void finalize() {
        System.out.println( "called finalaizer" );
    }

    // Stringを返すメソッド
    public String getName() {
        return this.Name;
    }

    // intを返すメソッド
    public int getNumber() {
        return this.Number;
    }

    // stringを設定するメソッド
    public void setName( String name ) {
        this.Name = name;
    }

    // intを設定するメソッド
    public void setNumber( int number ) {
        this.Number = number;
    }
}

実行結果

$ java SampleClient 
called constructor
John
5
true
Nested Class

JavaPythonと同様にClassの入れ子階層を作る事ができます。入れ子階層の外側のクラスをOuter Class、内側のクラスをNested Classと呼びます。Nested Classの種類はInner Class、Static Nested Classの2つがあります。Inner Classは更にLocal、Anonymous Classに分類されます。

Static Nested Class

Static Nested ClassはOuter Classと関係性が無い非Inner Classと言われています。Static Nested ClassはOuter Classの静的メンバクラスでしかないのでInstanceを生成しなくてもアクセスが可能です。

// Outer Class
class OuterClass {
    // Static Nested Class
    static class StaticNestedClass {
        // staticで定義する
        static String name = "John";
        static int number = 5;
        public static String getName() {
            return name;
        }
        public static int getNumber() {
            return number;
        }
    }
}

// Classを呼び出すClient
class ClassClient {
    public static void main( String[] args ) {
        System.out.println( OuterClass.StaticNestedClass.getName() );
        System.out.println( OuterClass.StaticNestedClass.getNumber() );
    }
}
Inner Class

Classの中に更なる非StaticなClassを定義する事が可能です。Inner Classを必要とするケースですが外部向けに新たにClassを作るよりはOuter Classのデータを享受しながら拡張できるInnerClassを定義する場合等に応用できると思います。

public class OuterClass {
   public class InnerClass { 
        public class Inner2Class { 
            // Inner2Class method
            public void echo() {
                System.out.println( "Inner2Class Hello!" );
            }
        }
        // InnerClass method
        public void echo() {
            System.out.println( "InnerClass Hello!" );
        } 
    }
    
    // OuterClass method
    public void echo() {
        System.out.println( "OuterClass Hello!" );
    }
    
    // OuterClass method
    public static void main( String[] args ) {
        OuterClass outer = new OuterClass();
        OuterClass.InnerClass inner = outer.new InnerClass();
        OuterClass.InnerClass.Inner2Class i2 = inner.new Inner2Class();
        i2.echo();
    }
}

実行結果

$ java OuterClass 
Inner2Class Hello!
Local Class

Inner ClassのうちClassに名前をつけるのがLocal Classです。Local ClassとAnonymous Classに共通して言える事はstaticメンバを持つ事ができません。上のInner Classを次のようにstaticなメンバ変数を追加するとCompileエラーが出ます。

public class OuterClass {
    public OuterClass() {
        System.out.println( "call OuterClass constructor" );
        //メソッド内の内部クラス
        class InnerClass {
            // コンパイルエラー
            static String name = "John"; 
            public String returnString() {
                return "InnerClass returnString";
            }
        }
        System.out.println( new InnerClass().returnString() );
    }

    // main
    public static void main(String[] args) {
        new OuterClass();
        //匿名クラス
        System.out.println((new Object() {
            public String echoString() {
                return "AnonymousClass echoString";
            }
        }).echoString()); 
    }
}

Compile実行結果

$javac OuterClass.java
OuterClass.java:6: エラー: 内部クラスInnerClassの静的宣言が不正です
            static String name = "John"; 
                          ^
  修飾子'static'は定数および変数の宣言でのみ使用できます
エラー1個
Anonymous Class

Inner Classは名前をつける必要がありません。特定のメソッド内にてnew Object() { 定義メソッド }.定義メソッド()と呼び出せばClassの名前定義を省けます。

public class OuterClass {
    public OuterClass() {
        System.out.println( "call OuterClass constructor" );
        //メソッド内の内部クラス
        class InnerClass {
            public String returnString() {
                return "InnerClass returnString";
            }
        }
        System.out.println( new InnerClass().returnString() );
    }

    // main
    public static void main(String[] args) {
        new OuterClass();
        //匿名クラス
        System.out.println((new Object() {
            public String echoString() {
                return "AnonymousClass echoString";
            }
        }).echoString()); 
    }
}

実行結果

call AnonymousClass constructor
InnerClass returnString
AnonymousClass echoString

3.NameSpace

Classファイルの名前空間を指定するにはpackageを使います。これにより自分で作成したClass名がimportしたpackageのClass名と重なっても問題を引き起こしません。packageの宣言は慣習的にjp.co.sitename;と言ったようにトップレベルドメインから記載します。package名はディレクトリの構成に依存し、jp.co.sitenameの場合javaファイルをjp/co/sitename/以下に設置します。packageの呼び出し元javaファイルはclasspath設定のディレクトリに配置します。classpath設定のディレクトリで呼び出し元javaファイルをjavacでコンパイルすると、呼び出し側、package側の両方でclassファイルが作成されます。
PackageClass.class

package jp.co.sitename;

public class PackageClass {
    public String returnString() {
        return "PackageClass retrunString";
    }
}

PackageClient.java

import jp.co.sitename.PackageClass;

class PackageClient {
    public static void main( String[] args ) {
        PackageClass pc = new PackageClass();
        System.out.println( pc.returnString() );
    }
}

Compile&実行

$ tree
.
|-- PackageClient.java   //呼び出し側
|-- jp
   `-- co
       `-- sitename
           `-- PackageClass.java  // packageとして定義したjava

$ javac PackageClient.java
$ tree
.
|-- PackageClient.class
|-- PackageClient.java
|-- jp
   `-- co
       `-- sitename
           |-- PackageClass.class
           `-- PackageClass.java

$ java PackageClient 
PackageClass retrunString

4.Annotation

Java SE 5から登場したAnnotationという機能によりソースコードに注釈を加える事ができます。注釈と言ってもただの説明文ではなく、Annotationの記述よってプログラムの動作を変更したり、コンパイル時のエラー出力を制御できたりします。Annotationを積極的に利用する事でコードに統一感を持たせる事が出来るので、メンテナンス運用の助けとなると思います。以下標準のAnnotationの種類です。Annotaionはjava.lang.Annotationの継承なので、それを更に継承してAnnotationを自作する事も可能なようです。ここでは例として

Annotation 説明
@Deprecated Classやメソッドが非推奨
@Override SuperClassのメソッドをOverrideしている事を示す
@SuppressWarnings 引数で指定した特定の警告メッセージを無視
@Target 定義Annotationの適用箇所を差す
@Retention Annotationの配置方法設定
// import
import java.lang.annotation.Annotation;

// 親クラス
class SuperClass {
    protected String name;

    public SuperClass() {
        this.name = "Super Class";
    }

    public void echoName() {
        System.out.println( this.name );
    }
}

// 継承クラス
class ChildClass extends SuperClass {
    public ChildClass() {
        this.name = "Child Class";
    }

    @Override
    public void echoName() {
        System.out.println( "Child Class method call " );
        super.echoName();
    }
}

// client
class ClassClient {
    public static void main( String[] args ) {
        ChildClass cc = new ChildClass();
        cc.echoName();
    }
}

実行結果

$ java ClassClient    
Child Class method call 
Child Class

5.Generics

GenericsJava SDK1.5から導入された文法でC++のtemplateと同じようなもので、Classやmethodの定義はデータ型を汎用化し、そのClass利用側で型の制約をつける事ができる機能です。Class/methodの定義はそれぞれの定義はclass class名<型パラメータリスト>や public T method名(仮型引数)といった記述を行います。パラメータリストや仮型引数などTやEなどの大文字一文字を用いるのが慣習のようです。下の例では様々なデータ型を定義できる汎用的なクラスを定義しておいて、利用者側でといった実型引数をつけて型を明確化します。これにより型の定義が分かりやすくなります。

// 型パラメータリストTで定義
class GenericsClass<T> {
    private T val;
    
    //  仮型引数Tで定義
    public void setValue( T val ) {
        this.val = val;
    }

    //  型パラメータリストTで定義
    public T getValue() {
        return this.val;
    }
}

// ClientClass
class GenericsClient {
    public static void main( String[] args ) {
        // Stringで定義
        GenericsClass<String> gcs = new GenericsClass<String>();
        gcs.setValue( "String value" );
        System.out.println( gcs.getValue() );
        
        // Integerで定義
        GenericsClass<Integer> gci = new GenericsClass<Integer>();
        gci.setValue( 10 );
        System.out.println( gci.getValue() );
    }
}

実行結果

String value
10

6.Extension for

JDK1.5前のjava連想配列のkey,valueの値をforで取得する場合にはiteratorを使わなければなりませんでした。JDK1.5以降は拡張for文というものが使え、幾分簡単にkey,valueが取得できます。以下にMapで定義されたkey、valueに対する操作プログラムを記述します。

import java.util.*;

class ExtensionFor {
    public static void main( String[] args ) {
        Map<String, String> map = new HashMap<String, String>();
        map.put( "1", "value1" );
        map.put( "2", "value2" );
        map.put( "3", "value3" );
        
        System.out.println( "Before JDK1.5" );
        
        // JDK1.5前
        for( Iterator<String> i = map.keySet().iterator(); i.hasNext(); ) {
            Object key = i.next();
            System.out.println( key + ":" + map.get( key ) );
        }

        System.out.println( "After JDK1.5" );

        // JDK1.5後
        for(Map.Entry<String, String> e : map.entrySet() ) {
            System.out.println( e.getKey() + ":" + e.getValue() );
        }   
    }
}

実行結果

Before JDK1.5
3:value3
2:value2
1:value1
After JDK1.5
3:value3
2:value2
1:value1

7.Variables

JDK1.5前は関数の引数が固定で定義しなければなりませんでしたが、1.5以降は可変長で変数定義できます。型名 ピリオド3つ(...) 変数名と定義することで変数の個数の制限がなくなります。これにより関数利用者側へのIF提供が柔軟になります。

class VariableClass {
    // 可変長引数定義 ピリオド3つ
    public void printVariables( String ... args ) {
        for( String s : args ) {
            System.out.println( s );
        }
    }
}

class ClassClient {
    public static void main( String[] args ) {
        VariableClass vc = new VariableClass();
        //可変長引数設定
        vc.printVariables( "a", "b", "c" );
        vc.printVariables( "d", "f" );
        vc.printVariables( "g" );
        vc.printVariables( );
    }
}