gradle eclipse を叩いたらどうなる?

今までgradleプロジェクトをeclipseにインポートするときに

apply plugin: 'eclipse' 

とbuild gradleにプラグインを追加したあと、
何の気なしに叩いていた

gradle eclipse  

の背景にある意味をメモ。

🚩eclipseプラグインする意味とは

gradleプロジェクトをターミナルで作る際、
.classpass
.settings
.project

などのeclipseに必要な設定ファイルは作られていない。
そのためbuild gradle上でeclipseプラグインし、gradleプロジェクトをeclipseで読み込む準備を行う。

🚩gradle eclipseを叩く意味とは

gradleプロジェクトをeclipseで読み込む準備ができたので、実際に読み込んでいきたい。そこで必要になるのが

gradle eclipse  

である。これを叩くことで、eclipseでプロジェクトを読み込むのに必要な設定ファイルを作ってくれる。
そのとき、追加したjarファイルなどのクラスパスをeclipseに通してくれているのだ。

Java環境設定用語について

先日パソコンの調子が悪くなり、環境設定を1からし直す機会があった。
その時に分からなかったことが多く、このままではマズイ!!と思ったのでメモ。ついでに色々復習。

🚩JVM (Java Virtual Machine)

javaプログラムはjavaの仮想環境で動いている。イメージとしてはOSの上に乗っかっている感じ。
このJVMjavaプログラムを実行する時にOSにも分かるように変換してくれている。

🚩JRE (Java Runtime Environment)

javaを実行するために必要な環境を整えてくれる。
JREAPIはここに内包されている。ちなみにAPIはクラスライブラリに入っている。

🚩JDK (Java Development Environment)

javaで開発を行うためのツール。JREコンパイラが入っている。
ちなみにEclipseは自身のコンパイラを持っているらしい。

🚩環境変数

環境変数とはOSやログインユーザーごとに設定され、アプリケーションがその値を参照したり設定したり、アプリケーション間で共有するためのものである。

例えば、コマンドプロンプトjavaコンパイルするには以下の方法がある。
> C:/Program Files/Java/jdkのバージョン/bin/javac クラス名.java
しかし毎回毎回このjavac.exeファイルを記述するのは面倒だし、パソコン自身も山ほどあるディレクトリから探し出してくるのは大変そう、、、
そこで環境変数Pathの出番である。

pathに「C:/Program Files/Java/jdkのバージョン/bin/」と設定しておくと、「javac」と打つだけで指定のディレクトリに参照してくれる。
またパソコンもpathに設定してあるパスを優先的に探すため速い動作が望める。

環境変数にはPath以外にも様々な種類があり、その中にClasspathというものがある。
Classpathには、JVMによってコンパイルや実行が行われるとき、対象となるjavaのクラスファイルが入っているディレクトリを指定することができる。

🚩コンパイル

人間が書いたソースコードをコンピュータが理解できる言語に変換すること

🚩リンク

たいていのプログラムは、複数のクラスファイルと関係してできている。
リンクは、コンパイルされたソースファイルの関係を繋げ、一つの実行可能な形態にまとめることを指す。
またこのときライブラリから必要なファイルを取り出し結びつけることもしてくれる。(例えばprintlnなどはクラスライブラリにある)

🚩ビルド

プロジェクト全体のクラスファイルをコンパイルしてリンクを行い実行可能なプログラムを作ること。

🚩ビルドパス

ビルドを行うときに対象となるクラスファイルのクラスパスのこと

🚩Webアプリケーションのディレクトリ構造

Webアプリケーションは以下のようなディレクトリで構成されている。

f:id:mmmnn1257:20170504003406p:plain

このディレクトリ構造をwarファイルとして一つのファイルとして保管しておくことができる。
Tomcatにデプロイする時は、Webappsディレクトリ以下にwarファイルをおく。

🚩Eclipse Javaソースフォルダ

Webプロジェクトに追加されるときにコンパイルされ、自動的にWeb-INFのClassesディレクトリに追加される

🚩Eclipse Javaライブラリフォルダ

Webアプリケーションが参照するサポートライブラリファイル。このフォルダにはlibフォルダの内容が反映されている。しかし物理的には存在しない仮想jarファイルであるため、ここにjarファイルを置くだけではTomcatは反応してくれない。

Java Silver への道 配列の作成と使用編

配列

配列とは複数の値の集合をまとめて扱うための「インスタンス」である。
配列を使用するときはnewキーワードを使って配列のインスタンスを生成しなければいけない。またこのときは整数値(小数点は不可)で要素数を指定する必要があり、要素名は変えることができない。

~配列インスタンスの生成~

new int[3];

配列は変数に格納した参照を用いて扱う

~配列型変数の宣言~

int[] array;

配列型変数には配列インスタンスへの参照(リンク先)を代入する。
変数内に要素が直接入るわけではない。
また配列型変数には参照を入れるだけであるため、要素数を指定することはできない。

int array[];

配列を表す大かっこ「[]」は変数名の後ろに記述することもできる

~サンプルコード~

   public static void main(String[] args) {

        // 要素数3の配列インスタンスを生成し、整数のint型の配列型変数arrayにインスタンスの居場所を参照
        int[] array = new int[3];

        // 配列インスタンスの0番目の箱に1を代入
        array[0] = 1;

        // 要素数3の配列インスタンスを生成し、オブジェクト型の配列型変数scoresに参照
        Object[] scores = new Object[3];

        // 変数arrayの参照先インスタンスにはStringクラスがないため、ObjectクラスのtoStringメソッドが呼び出されて、ハッシュコードが出る
        System.out.println(array);
        // 要素の値を出したいときは参照先の要素数まで指定
        System.out.println(array[0]);
        // 整数型のデフォルト値は0
        System.out.println(array[1]);
        // オブジェクト型のデフォルト値はnull
        System.out.println(scores[0]);
    }
}

結果

[I@15db9742
1
0
null

初期演算子

配列インスタンスの生成と同時に要素の値を入れたい場合に用いる

int[] array = {10,20,30};
int[] array = new int[]{10,20,30};

どちらも同じ意味を持つが、後者の場合要素数を指定するとコンパイルエラーが起きる。
なぜなら初期演算子は自動的に要素数を計算してくれるからだ。

多次元配列

配列の特徴には以下の2点が挙げられる

  • 配列は参照を通して扱う
  • 複数の参照をひとまとめに扱うことができる

このことから、参照先の配列がさらに複数の参照を持っていることを「多次元配列」という。

~2次元配列~

int[][] array = new int[2][3];
  1. 素数2を持った1次元目の配列インスタンスが生成される
  2. 素数3を持った2次元目の配列インスタンスが2つ生成される
  3. 1次元目の要素として2次元目の配列インスタンスへの参照が代入される

このとき、変数とインスタンスの間で次元数を一致させる必要がある  

f:id:mmmnn1257:20170406121628p:plain

~nullが含まれている多次元配列~

要素の値にnullが入ると何も参照しないという意味になる。
またnullにはlengthは存在しない。

サンプルコード(nullがない場合)

public class Main {

    public static void main(String[] args) {

        String[][] array = { { "A", "B" }, { "C", "D", "E" } };
        int total = 0;
        for (String[] tmp : array) {
            total += tmp.length;
        }
        System.out.println(total);
    }
}

結果

5

String[][] array = { { "A", "B" }, { "C", "D", "E" } };

arrayの1つ目の要素には"A"“B"の参照、2つ目の要素には"C”“D”“E"の参照が代入されている

for (String[] tmp : array) {

arrayの要素を1つずつtmpに代入、要素数は2なので2回ループ。
このときtmpはString型なので参照先の"A"“B"は一つずつtmpの要素に入る

total += tmp.length;

1回目はtmpに"A"“B"2つの要素数が入るのでlengthは2:total = 0 + 2
2回目は"C”“D”“E"3つが入るのでlengthは3:total = 2 + 3

サンプルコード(nullがある場合)

public class Main {

    public static void main(String[] args) {

        String[][] array = { { "A", "B" }, null,{ "C", "D", "E" } };

        int total = 0;

        for (String[] tmp : array) {
            total += tmp.length;
        }
        System.out.println(total);
    }
}
String[][] array = { { "A", "B" }, null,{ "C", "D", "E" } };

2つ目の要素にnullが入る、nullが入った要素は何も参照しない

for (String[] tmp : array) {
            total += tmp.length;
        }

処理を実行中、nullにはlengthがないため、ここでNullPointerExceptionが発生する

配列と継承関係

クラスが継承関係にあるとき、スーパークラス型の配列変数で、サブクラスのインスタンスの集合を扱える

Object[] obj = {"A","B","C"};

Object型しか扱わない配列型変数objとString型しか扱わない配列インスタンスは、扱う型が異なるが、StringクラスはObjectクラスを継承しているためコンパイル・実行ができる。
つまり全ての配列型変数は、Object型に暗黙的に型変換できるということだ。

~サンプルコード~

public interface A {}
public abstract class B implements A {}
public class C extends B {}
public class D extends C {}

このコードは以下のような仕組みを表している

f:id:mmmnn1257:20170406142641p:plain

public class Main {

    public static void main(String[] args) {

        A[] arrray = {new C(),null,new D()};
        Object[] objArray = array;
    }
}

CクラスとDクラスはAインターフェースを実装しているBクラスを継承している。そのため変数型配列AはC型とD型の配列インスタンスを扱える。

また全ての配列型変数はObject型を継承しているため、型を暗黙的に変換してくれる。

ここで、nullが入っているarrayやobjarrayを使う処理があれば、NullpointerExpectionが生じる。

配列のコピー

~cloneメソッド~
cloneメソッドを使うと、同じ値を持った配列インスタンスが複製される。

   public static void main(String[] args) {

        int[] arrayA = { 1, 2, 3, 4 };  

//arrayAと同じ値を持った配列インスタンスを作成
        int[] arrayB = arrayA.clone();

//値は複製だがインスタンスは異なるため結果はfalseに
        System.out.println(arrayA == arrayB);

//arrayAの値がコピーされているので出力される
        for (int i : arrayB) {
            System.out.println(i);
        }
    }
}

結果

false
1
2
3
4

~arraycopyメソッド~
Systemクラスのarraycopyメソッドは、配列の一部をコピーしたいときに使用する

引数 説明
第1引数 コピー元となる配列
第2引数 コピー元のどの位置からコピーを開始するか(0始まり)
第3引数 コピー先の配列
第4引数 コピー先のどの位置からコピーを開始するか(0始まり)
第5引数 第2引数を含んでいくつの要素をコピーするか

サンプルコード

System.out.println(ArrayA,1,arrayB,0,4);

「arrayAの1番目から合計4つの要素をarrayBの0番目にコピーする」という意味である

Java Silver への道 演算子と判定構造の使用編

代入演算子

値を変数に代入するための演算子のこと

public class Sample {

    public static void main(String[] args) {

        int a = 5;
        int b = a += 5;
        // 演算によってaに代入された値は受け継がれる
        int c = a -= 3;
        int d = a *= 5;
        int e = a /= 5;

        System.out.println(b);
        System.out.println(c);
        System.out.println(d);
        System.out.println(e);
    }
}  

結果

10
7
35
7

*=(イコール)を記述しないと、aは初期値の「5」として演算される

数値演算のときの型について

数値を演算するとき、演算子の両側のオペランドは同じ型でなければいけない。
もしオペランドの型が異なる場合、小さいほうの型は大きいほうの型に自動変換される。

public class Sample {

    public static void main(String[] args) {

        int a = 10;
        short b = 10;
        //bはint型に変換される
        //a+bはint型のため、short型で記述するとコンパイルエラーが生じる
        int c = a + b;
    }
}

インクリメント

インクリメント演算子「++」とデクリメント演算子「–」は、変数の値に1 を加算したり減算したりするための演算子である。

~前置インクリメント「++a」と後置インクリメント「a++」~
前置インクリメントは、先に加算し、その値をaに代入する。
後置インクリメントは、aは前の値を引き継ぎ、その後加算し、aに代入する。

public class Sample {

    public static void main(String[] args) {

        int a = 10;
        //()のタイミングでインクリメントされ、代入された値は引き継がれる
        //    10(11)+(12)12+(13)13
        int b = a++ + ++a + ++a;

        System.out.println(b);
    }
}

結果

35

同一性と同値性

同一性:同じインスタンスを参照している
同値性:違うインスタンスではあるけれども、同じ値である

同一性は「==」演算子で表す。戻り値はbooleanであるため、左右のオペランドインスタンスが合致していた場合「true」が返ってくる。

同値性を確認するためのメソッドはequalsである。ただし、Objectクラスに定義されているメソッドは同一性を確認する実装になっている。そのため、設計者が自ら、どの値の一致を確認するのかequalsメソッドをオーバーライドしなければならない。

public class Sample {
    // フィールドの設定
    private int num;
    private String name;

    // コンストラクタの設定
    public Sample(int num, String name) {

        this.num = num;
        this.name = name;

    }

    public boolean equals(Object obj) {

        // 同一性の確認
        if (obj == null) {
            return false;
        }

        // オーバーライドして同値性の確認
        if (obj instanceof Sample) {
            Sample sample = (Sample) obj;
            return sample.num == this.num;
        }

        return false;
    }
}
public class Main{
    
    public static void main(String[] args) {
        
        //Sample型のインスタンスを作成
        Sample a = new Sample(10, "a");
        //Sample型のインスタンスを作成
        Sample b = new Sample(10, "b");
        
        //Sample型aのequalsメソッドの引数にsample型のbを入れる
        System.out.println(a.equals(b));
        
    }
    

処理の流れ

public class Sample {

    private int num;
    private String name;

    public Sample(int num, String name) {
        this.num = num;
        this.name = name;
    }
    //暗黙的にSample型はObject型に変換される(Object型の方が大きいため)
    public boolean equals(Object obj) {

        // 同一性の確認
        if (obj == null) {
            return false;
        }

        //objに入っているbがSampleと同じクラスかインスタンスであることが条件
        if (obj instanceof Sample) {
            //Object型であるobjをSample型にキャストして変数sampleに代入(numはObjectではなくSample型に定義されたフィールドであるため)
            Sample sample = (Sample) obj;
            //sampleのnumとaに入っているnumの値を確認
            return sample.num == this.num;
        }

        return false;
    }
}

結果

true

オーバーロード

オーバーライドと似ている言葉にオーバーロードがある。
オーバーロードとは、同じメソッドでも異なる引数を定義することで、違う結果を導くというものだ。

public class Main {

    public static void main(String[] args) {

        Sample a = new Sample(10, "a");
        Sample b = new Sample(10, "b");
       //bはSample型である
        System.out.println(a.equals(b));

    }
}  
public class Sample {

    private int num;
    private String name;

    public Sample(int num, String name) {
        this.num = num;
        this.name = name;
    }

    public boolean equals(Object obj) {

        if (obj == null) {
            return false;
        }
        if (obj instanceof Sample) {

            Sample sample = (Sample) obj;

            return sample.num == this.num;
        }

        return false;

    }
   //同じメソッドだが、引数をSample型にしてオーバーロードしている
    public boolean equals(Sample s) {

        return s.name == this.name;

    }
}  

結果

false

コンスタントプール

文字列リテラルは、インスタンスの生成が使いまわしされる。そのため、同じ参照となる。

public class Main {

    public static void main(String[] args) {

        String a = "sample";
        // 使いまわしされる
        String b = "sample";

        System.out.println(a == b);
    }
}

結果

true

*ただし、new演算子を使って明示的に新しいインスタンスを生成した場合は異なる参照となる

if文・if-else文・if-else if文

  • if文中の{}は省略可能で、その場合は次の1文だけが条件に合致した場合の処理として実行される
public class Main {

    public static void main(String[] args) {

        if (false)
            System.out.println("A");
//この部分はif文に含まれていない処理となる
        System.out.println("B");
    }
}

結果

B  

  • if-else文
if (条件式){
            //条件に合致した時の処理
        }else{
            //条件に合致しなかったときの処理
        }
public class Main {

    public static void main(String[] args) {

        int num = 10;
        if (num < 10) {
            // 条件に合致した時の処理
            System.out.println("A");
        } else {
            // 条件に合致しなかった時の処理
            System.out.println("B");
        }
        //2つ目のif文も実行される
        if (num == 10) {
            // 条件に合致した時の処理
            System.out.println("C");
        }
    }
}

結果

B
C

  • if-else if文は、複数の分岐条件を一度に記述することができる。elseとifの間で改行することができず、2つ目のif文はelse文の中にある分岐として解釈される。
public class Main {

    public static void main(String[] args) {

        int num = 10;
        if (num == 100) 
            // 条件Aに合致した時の処理
            System.out.println("A");
         else if (10 < num)
            // 条件Bに合致しなかった時の処理
            System.out.println("B");
         else //上記の条件に合致しなかったとき
         //else内で分岐     
         if (num == 10)
            // 条件に合致した時の処理
             System.out.println("C");
         else//上記の条件に合致しなかったとき
         //すでに条件合致しているため以降の処理は実行されない
         if (num == 10) {
            System.out.println("D");
        }
    }
}

結果

C

三項演算子

条件に合致するかどうかで、戻す値を変更する演算子である。

真偽値 ? trueの場合に評価する式 : falseの場合に評価する式

public class Main {

    public static void main(String[] args) {

        String a = "A";
        String b = "B";
        //AとBの同値性を確かめtrueであればno、falseであればyes
        String c = a.equals(b) ? "no" : "yes";

        System.out.println(c);
    }
}

結果

yes

switch文

条件によって分岐するif文に対してswitch文は値によって処理を分岐する。
case値にマッチする処理が実行され、処理が終わればbrakeを使ってswitch文を抜けるようにする。一致するものがなかったときはdefault文以降が実行される。

        switch (条件文) {
        case 値: 処理
            break;
        case 値: 処理
            break;
        default:処理
            break;
        }

~条件式のルール~
条件式が戻せる値の型には制限があり、以下のような種類がある

種類 値名
int型以下の整数型とそのラッパークラス int/Integer/short/Short/byte/Byte
文字と文字列 char/Character/String
列挙型 enum

~case値のルール~

  • 条件式が戻す型と同じ型か互換性があること
  • 定数であるか、コンパイル時に値を決めることができること
  • nullでないこと

「定数であること」…final宣言された変数かもしくはリテラルを表す

~breakを記述しなかった場合~
条件に合致したあと以降に現れるすべてのcase式の処理がbreakが現れるまで実行される。
そのときdefault式も対象となる。

   public static void main(String[] args) {

        int num = 10;

        switch (num) {
        case 9:
            System.out.println("yeah");
        case 10:// 条件に合致したため処理を実行
            System.out.println("A");
            // breakが記述されていないため以降の処理がすべて実行される
        case 11:
            System.out.println("B");
        case 12:
            System.out.println("C");
     default:// default式も対象となる
            System.out.println("D");

        }
    }
}

結果

A
B
C
D

Java Silver への道 データ型(参照型)編

参照型

参照型は簡単に言えば基本データ型以外の型のことである。
具体的に参照型には、オブジェクト型、列挙型、配列型がある。
String,Date,Integer,ArrayListなど

参照型を使用する際には基本的にインスタンスを生成する。
(static修飾されているメソッドや変数を使用するときはインスタンスの生成は不要)

*参照型の中でもString型は特別で、基本データと同じような宣言が可能。

参照型の変数

参照型の変数は、オブジェクトへの参照*1を保持しているか、保持していないかのどちらかしか表現できない。
参照を保持していないことを表現するためのリテラルが「null」である。

public class Main {

    public static void main(String[] args) {

        Object obj = null;

        System.out.println(obj);
    }
}

シグニチャ

シグニチャとは、メソッド名と引数のセットのことを指す。
javaにはオーバーロードという仕組みがあるため、メソッドを見分けるためには引数も加える必要がある。

// helloメソッドと引数"pochi"のシグニチャ
        s.hello("pochi");

ガーベッジコレクション

ガーベッジコレクションとは、不要なインスタンスを探し、破棄することを指す。ガーベッジコレクションが起こるタイミングはプログラマーが制御することはできず、JVMが決める。

ガーベッジコレクションの対象となるのは、「どこからも参照されなくなったインスタンス」である。代表的なタイミングとしては、nullを代入したときである。

*1:生成したインスタンスの格納場所を表す値

JavaSilver取得への道 基本編メモ

パッケージ

パッケージを使用する目的は以下のようにまとめられる。

  1. 名前空間を提供し、名前の衝突を避ける
  2. アクセス修飾子と組み合わせてアクセス制御機能を提供する
  3. クラスの分類を可能にする

1.名前空間を提供し、名前の衝突を避ける
例えば、パッケージ名を"tsujimari"とする。すると、tsujimariパッケージ内で作られたクラスは"tsujimari.クラス名"という完全修飾クラス名で表すことができる。
このようにしてクラス名が重複しても区別することができる。
ちなみに、パッケージ名にはドメイン名を逆にしたものを使用するのが慣習である。

2.アクセス修飾子と組み合わせてアクセス制御機能を提供する

アクセス修飾子 内容
private 同じクラス内からしか呼び出せない
省略 同じパッケージ内からしか呼び出せない
protected 同じパッケージ内か、そのサブクラスから呼び出せる
public どこからでも呼び出せる

アクセル修飾子を使用することで、パッケージ内のクラスを”公開するクラス”と”非公開にするクラス”に分けることができる。

3.クラスの分類を可能にする
パッケージはディレクトリ機能とマッピングされるため、管理が簡単になる。

パッケージのインポート宣言

①明示的にパッケージ宣言したクラスから無名パッケージに属するクラスにアクセスしようとするとコンパイルエラーが起きる

//無名パッケージに属するSampleクラス
public class Sample {
 public static int num = 10;
}
package example;
//exampleという明示的に宣言したパッケージに属するSampleImpleクラス
//無名パッケージに属するSampleクラスは同じ無名パッケージに属するクラスからしかアクセスできないため、Sample/numでエラーがでる。
public class SampleImple extends Sample{
    public static void main(String[] args) {
        System.out.println(num);
    }
}

この場合、Sampleクラスをexampleパッケージ内に作成すれば解決する。

②異なるパッケージのクラスにアクセスするときはインポートを宣言する

package sample2;
//sample2という明示的に宣言したパッケージに属するSampleクラス
public class Sample {
    public static int num = 10;
}
package example;
//sample2パッケージのSampleクラスをインポートする宣言
import sample2.Sample;

public class SampleImple extends Sample{

    public static void main(String[] args) {
        System.out.println(num);
    }
}

インポート宣言は、省略表記のために用いるだけであり、インポートしたフィールドやメソッドがコピーされる訳ではない。よって以下のような書き方でも可。

package example;
//完全修飾クラス名で表記
public class SampleImple extends sample2.Sample{
    public static void main(String[] args) {
        System.out.println(num);
    }
    
}

sample2に属するクラス全てをインポートしたい場合はアスタリスクを使用する

import sample2.*;

staticインポート

staticとは
staticとは、クラスに属するものであり、インスタンスを生成しなくても実行することができる。 そのため、staticメソッドから非staticなメンバ(インスタンス変数)にアクセスすることはできない。
なぜなら、インスタンスの有無に関係ないstaticメソッドから呼び出しても、どのインスタンス変数にアクセスしていいのか分からないからである。

staticインポートの書き方
staticなフィールドはメソッドはクラスに属しているため、”クラス名.フィールド名”や”クラス名.メソッド名”とどのクラスに定義されているものか明示しなければいけない。

//sampleクラスのstaticなprintメソッドをインポート
import static sample.print

メソッドがオーバーロードされたメソッドが複数あった場合は引数によって呼び出されるメソッドが決まるため、インポート宣言で引数を指定する必要はない

エントリーポイント

複数あるメソッドの中でも、最初に処理を始めるメソッドのことで、mainメソッドを指す。
エントリーポイントの定義は以下のように決められている。

public static void main(String[] args) {
        //}

変更可能なのは引数名である”args”の部分だけである。
ちなみに引数にはString配列型だけではなく、可変長引数のString型を受け取ることもできる。

Javaコマンド

javaコマンドで、mainメソッドに渡す引数のことを”起動パラメータ”や”コマンドライン引数”という。
javaの構文は以下のようになる。

java 完全修飾クラス名 引数 引数(起動パラメータ・コマンドライン引数)

参考文献:「java se8 silver 問題集」

セッション

セッションとは

ブラウザとサーバーの一連のやりとりのこと。
例えばあるwebサイトにアクセスして、そのサイトから出て行くかブラウザを閉じるまでが1セッションとなる。
セッション管理を用いると、ショッピングサイトなどで異なるページを開いても同一クライアントの情報を管理することができる。
多くの場合ページの閲覧時間が何分か開くと新しいセッションとしてカウントされる。サイトでよく見かける”セッションタイムアウトになりました”というのがこれを意味する。
HttpSessionインタフェースを使用するとサーブレットコンテナがセッション管理を容易に行ってくれる。

セッション管理サンプルコード

package example;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@WebServlet("/count")
public class CountServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        HttpSession session = req.getSession();
        Integer count = (Integer) session.getAttribute("count");
        if (count == null) {
            count = 0;
        }
        count++;
        session.setAttribute("count", count);

        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter out = resp.getWriter();
        out.println("<html>");
        out.println("<head><title>Count</title></head>");
        out.println("<body>");
        out.println("<h1>Count: " + count + "</h1>");
        out.println("<a href=\"count\" >count</a>");
        out.println("</body>");
        out.println("</html>");
    }
}
HttpSession session = req.getSession();

doGetメソッドでは、HttpServletRequestオブジェクトに対してgetSessionメソッドを呼び出し、HttpSessionオブジェクトを取得している。
この時引数を指定していないので、初回アクセス時などセッションが存在していない場合は新しいセッションが作成される。

Integer count = (Integer) session.getAttribute("count");

HttpSessionオブジェクトのgetAttributeメソッドを呼び出し、"count"という名前で保存されたデータの値を取得している。この時データがなければnullで返却される。
また、getAttributeはオブジェクト型で返却され、基本データ型は扱えない。そのため今回はIntegerにキャストして変数countをint型として扱っている。

if (count == null) {
        count = 0;
}
count++;
session.setAttribute("count", count);

変数countがnullでないことを確認し、countをカウントアップしている。そしてその後HttpsSessionオブジェクトに対しsetAttributeメソッドを呼び出し、"count"という名前をつけcountオブジェクトをセッションに保存している。