プログラミング

プログラミング初心者のJavaSilver修羅の道#10〜3秒で解ける問題〜

この「JavaSilver修羅の道」シリーズは私が資格取得のために始めたシリーズですが、つい先日(2022/7/16)に無事資格に受かることができました!

参考書には「紫本」を活用し、問題を解く時には「白本」「黒本」も活用して勉強し、未経験転職から4ヶ月、Java勉強期間3ヶ月の私でも合格することができました。

      

合計1500ページも色々な問題を解いてきたわけですが、後半は正直かなり傾向がわかってきました。というのも、問題文とか答えの選択肢を見ただけで何を引っ掛けようとしている問題なのか大体わかるようになってきます。「これはオーバーライド問題だな」とか「これは抽象メソッド問題だな」「これはnullの理解を聞いてるな」などです。

そこで個人的に苦手だった部分や私と同じようにプログラミング初心者の引っかかりそうなところを抽出して紹介していけたらなと考えています。

ということで、この第9回からは実際に試験を受けてみてほぼ確実に出るけどひっかかりやすいだろうなと思ったところを中心に記事にしていきます。

3秒で解ける問題

JavaSilverの問題ですが、実は中には問題文もしくは選択肢を見れば速攻で解くことができる問題があります。

本来であればしっかりと理解した上で解くのが良いのですが、正直いくつかのポイントを暗記さえしていれば考えなくとも簡単に解けてしまうのです。今回はふ〜んくらいの心づもりでこの記事を見ていただければと思います。では、実際に一つ一つ見ていきましょう!

varによる変数宣言

そもそもvarについて軽く説明しておこうと思います。

Javaでローカル変数型推論が使用可能になったことでローカル変数の宣言時に型をvarと記述して利用することができるようになりました。

詳しい説明は省きますが、記述した処理から型が予測できる場合はその型をわざわざ指定しなくてもvarを使っていいよという感じです。

そんなvarによる変数宣言の問題はほぼ高確率で出題されるので押さえておきましょう。

今回抑える大切なポイントはこちらです。

ローカル変数”の”初期化時のみ”使用可能

以下項目等では使用できません。

・メソッドの引数

・コンストラクタの引数

・メソッドの戻り値

・フィールド(メンバ変数)

・catchブロック

・型推論できない為nullの代入はできない

など…

試験問題と類似した問題をいくつか見ていきましょう。

Q1.変数宣言として正しいものは?

public class Main {
  var java = new ArrayList<String>(); //--(1)
  public static void main(String[] args){
    var var = "var"; //--(2)
    var value = null; //--(3)
    var num;  //--(4)
  }
}


A.(2)のみ

 

〜解説〜

(1)インスタンス変数(メンバ変数)にvarは使用できない

(3)代入したnullでは型推論できない

(4)初期化せずにvarを使用することはできない

フィールド宣言されているものは論外ですし、初期化されていないのも論外なので一瞬で2択まで絞れます。迷うとしたら(2)の識別子としてvarが使えるのかとnullが代入可能なのかなのですが、ここは今回せっかくなので覚えておきましょう。

varは予約語ではないため識別子として使用することができます。(3)に関しては解説の通りなので「varは識別子として使える」というのを理解しておけば3秒で解くことができる簡単な問題ですね。

「フィールド!、null!、初期化なし!」タンッ!、タンッ!、タンッ!とテンポ良く回答していきましょう(笑)

では、次の例題を見ていきましょう。

Q2.4行目に挿入するコードで正しくコンパイルできるものはどれですか?
2つ選択してください。

class Test {
  public static void main (String args[]) {
    String[] strs = new String[]{"Java", "Silver"};
    // (insert code here) ---4行目
    }
  }
}


1.for(var i = 0;i < strs.length; ++i){
2.for(var var:strs){
3.for(var i=0, i2=0; i<strs.length;i++){
4.for(var str=null:strs){

A.1,2

 

〜解説〜

(3)var型は複数同時に宣言することができない

(4)拡張for文ではそもそも変数の初期化ができない

この問題の迷うポイントは「var型で複数同時に宣言することができるのか?」だと思いますが、var型ではできないのでこれも今回覚えておくようにしましょう。

となるとそこを押さえておけば、(1)はローカル変数の初期化として使われており、0が代入してあることからint型であることは型推論できますし、(2)は配列がString[]型であることから型推論できます。

そして共通点として(2)(4)は拡張for文の記述の仕方「for(型 変数:配列orコレクション)」を理解していればやはりこの問題も瞬殺できるわけですね。

では、次は少し難易度を上げた問題も見てみましょう。

Q.4,5行目に挿入することで配列vの全要素を出力できるコードはどれか?
2つ選択してください。

class Test {
  public static void main (String[] args) {
    var v = new int[][]{{100,200}, {300,400}};
    for( /*(insert code here)*/ ) {  //4行目
      for( /*(insert code here)*/ ) {  //5行目
        System.out.println(i + ":");
      }
    }
  }
}

1.4行目:var[] n : v
  5行目:var i : n
2.4行目:var n : v
  5行目:var i : n
3.4行目:int n : v
  5行目:var i : n
4.4行目:var n : v
  5行目:int i : n


A.2,4

 

〜解説〜

(1)配列もvar型として定義できるため、var[]はコンパイルエラーとなる

(3)外側のループはint型の配列を取得するため、int[]nが適切

一見複雑そうに見える問題ですが、今まで紹介してきた問題となんら変わりはありません。

この問題のポイントは配列vが2次元配列になっているため、拡張for文を使用した際の受け取る方の型がどうなるのかという点です。

外側のループでは{100, 200}と{300, 400}という配列をそれぞれ格納したいので受け取る方の型は「int型の配列」になっていないと文法的におかしくなります。そして、内側のループでは、100,200,300,400というint型の数字を受け取るので受け取る方はint型である必要があります。

これを理解している前提でこの問題を見てみると、(1)はvar[]というものは無いのでそもそもおかしいですし、(2)は外も内もvar型で受け取ているため安全牌です。となると残り2つですが、(3)は解説の通り外側ループでint[]nとなっていない時点でOUTです。

このようにある程度暗記すべきポイントを押さえておくだけでvar問題は瞬殺できるので出たらラッキー!という感覚で挑みましょう。

たまに、ぬるっと別の知識を問うような問題にvarが登場しており、そこが原因でエラーになるみたいな問題もありますが今回の部分を押さえておけばきっと大丈夫なはずです。

StringクラスとStringBuilderクラスのメソッド

Javaでは文字列を扱うデータ型としてString型とStringBuilder型があります。

String型では、初期化されたString型の変数に別の文字列を再代入すると元の文字列はそのままで、新しい文字列が生成され、変数の参照先がそちらに切り替わります。

一方StringBuilder型では、初期化されたStringBuilder型の変数に対して文字列の追加や削除などが可能で「保持する文字列を直接操作」できます。

String型文字列を新たに生成

StringBuilder型保持する文字列を直接操作可能

JavaSilverで良く出題されるメソッドは結構限られているので下記メソッドをそれぞれ覚えておけば割と大丈夫です。

Stringクラス

charAt():引数にある文字を返す

equals():引数の文字列を同じか比較する

intern():文字列プールの中にある一意の文字列を返す

indexOf():引数の文字が最初に出現する位置を返す

length():文字の数を返す

replace():第1引数の文字を、第2引数で指定した文字に置き換え、結果を返す

substring():引数で指定した位置から最後までの部分の文字列を返す

 

StringBuilderクラス

append:引数で指定された文字列を現在の文字列に追加する

insert():引数で指定された文字列を、引数で指定された位置にある文字の前に挿入する

replace():第1引数から第2引数の位置の1つ前までの位置にある文字を、第3引数で指定された文字列に置換する

delete():第1引数から第2引数の位置の1つ前までの位置にある文字を削除する

substring():引数で指定した位置から最後までの部分の文字列を返す

これらを踏まえた上で例題を見ていきましょう。

Q.このコードをコンパイル、および実行すると、どのような結果になりますか?
1つ選択してください。

class Test {
  public static void main (String[] args) {
    String str = "Hello Java SE 11.";
    str.substring(6);
    str = str.intern();
    System.out.println(str);

1.Hello
2.Hello java SE 11.
3.true
4.false


A.2

 

〜解説〜

・substring()メソッドで6文字目以降の文字列を切り出しているが、切り出した文字列を変数で受け取っていない。

・intern()メソッドによって、変数strが参照している文字列を返すが結果的に同じ文字列「Hello Java SE 11.」を変数strに格納する。

この問題はString型のメソッドに関する初歩的な問題ですが、型がStringでかつStringクラスのメソッドの結果を変数で受け取っていなければ問答無用でスルーして大丈夫です。今回の場合はスルーした結果、変数strは何も変わっていないのでintern()の結果も変わりません。

メソッドの役割とStringクラスに関して理解しておけばここは秒殺できる問題ですね。

では、次は少し難易度を上げた問題を見てみましょう。

Q.このコードをコンパイル、および実行すると、どのような結果になりますか?
1つ選択してください。

class Message {
  public static void main (String[] args) {
    StringBuilder sb = new StringBuilder();
    String msg = "Thankyou";
    sb.append("Thank").append("you");

    if(msg == sb.toString()) {
      System.out.println("Same Object");
    }
    if(sb.equals(msg.toString())) {
      System.out.println("Same String");
      }
  }
}

1.Same Object Same String
2.Same String
3.Same Object
4.何も出力されない


A.4

・変数sbはStringBuilder型であること

・toString()メソッドによってそれぞれの文字列をString型で返していること

・==演算子とequals()メソッドでそれぞれ何を比較しているのか

ざっくりポイントでまとめましたが、順に説明していきます。

変数msg,sbに格納されている文字列はどちらも「Thankyou」です。ここまでは大丈夫かと思いますが、問題はif文です。

そもそも変数msgは、イメージでいうと「Thankyou」という文字列の1塊がオブジェクト領域を持っており、その領域を参照しています。

一方で変数sbは、イメージでいうとnewして確保された領域に対してappend()メソッドを使用して『「T」「h」「a」「n」「k」「y」「o」「u」』というふうに文字を追加しており、その領域を参照しています。

そしてその次の比較部分がこの問題の重要な点となります。

==演算子は、オブジェクトの参照情報が一致すればtrue

StringBuilderクラスのequals()メソッドも、オブジェクトの参照情報が一致すればtrue

Stringクラスのequals()メソッドは、オブジェクトが保持する文字列が一致すればtrue

となると、変数msgとsbが参照している領域は別のオブジェクトなので1つ目のif文はfalseという判断ができます。

2つ目のif文ですが少し深い説明をすると、そもそもequals()メソッドObjectクラスで定義されており、「同一性」を確認する仕様となっています(「同一性」はインスタンスそのものが同じかどうか)。

public boolean equals(Object obj) {
        return (this == obj);
    }

そして「同値性」を確認する場合はそのクラスでオーバーライドする必要がありますが、今回の問題におけるStringBuilderクラスではequals()メソッドがオーバーライドされていない為、「==演算子」と同様に「同一性」を確認しています。

ちなみにStringクラスで「同値性」が判定できるのは再定義されているからです。

つまり、問題の2つのif文はどちらも「同一性」を確認していることになるので両方ともfalseとなり、結果的に「何も表示されない」のです。

初めは少し混同するかもしれませんが、これも理解してしまえばそこまで悩むような問題でもありません。せっかくなので中身まで理解した上でこの記事を再度見返してみて下さい。これに関しては一瞬迷いが生まれそうなので早くても5秒くらいかかりそうですかね笑

Switch文

JavaSilverの問題においてはSwitch文も実は即答しやすい問題が多いのです。

大体よく問われるポイントは下記の3項目です。

①caseの値で変数が使われているかどうか

②Switch文の引数とcaseの値で型が同じかどう

③break文があるかどうか

さて、これらを踏まえた上で例題を見ていきましょう。

Q.このコードはコンパイルエラーとなります。コンパイルエラーが発生する行は
次のうちどれですか?2つ選択して下さい。

public class Main {
  public static void main(String[] args) {
    final int num = 10;
    int val = 20;
    int data = 30;
    switch(val){ //6行目
      case num:  //7行目
        System.out.println("A ");
        break;
      case 10*2:  //10行目
        System.out.println("B ");
        break;
      case data:  //13行目
        System.out.println("C ");
        break;
      case "40":  //16行目
        System.out.println("D ");
        break;
    }
  }
}

1.6行目   2.7行目   3.10行目   4.13行目  5.16行目


A.4, 5

 

〜解説〜

・numはint型の定数、val,dataはint型の変数のため、6,7行目は問題なし

・10行目は計算を行なっていますが、20という数値になるため問題なし

・13行目はcaseの値に変数を使用しているためコンパイルエラー

・16行目はcaseの値の型は文字列型であり、Switch文の引数のint型と異なるためコンパイルエラー

すでに上述したポイントの①と②が今回の問題の肝になっています。まずSwitch文の問題が来たら、Switch文の引数の「型」を意識して下さい。その次にcaseの値に変数がないかを確認して下さい。ここで引っかかるようならコード全部を見なくても即答ができるので時短できる場合が多いのです。

では、次は少し難易度を上げた例題を見てみましょう。

Q.このコードのコンパイルを行うと、どのような結果になりますか?
1つ選択して下さい。

class Main {
    public static void main(String[] args){
        final int i = 7;
        final int j = 8;
        switch(i){ //5行目
            case i | j: //6行目
                System.out.println("foo");
                break;
            case 8:
                System.out.println("bar");
                break;
            case 2 | 5: //12行目
                System.out.println("baz");
                break;
        }
    }
}

1.5行目でコンパイルエラー   2.6行目でコンパイルエラー   3.12行目でコンパイルエラー
4.fooを出力   5.barを出力   6.bazを出力
7.何も出力されない


A.6

 

〜解説〜

・5,6,12行目はそれぞれ定数かリテラルであり型もSwitch文の引数と同じため問題なし

・ビット演算子「|」に関してそれぞれのケースで当てはまるかどうかを考えていく必要がある。

この問題ではSwtch文の知識に加えて「ビット演算子」についても理解しておく必要のある問題となっています。実際の試験でも1,2問は出題される問題なのでしっかり押さえておきましょう。

ビット演算子

整数の値に対してビット単位で処理を行うために用意されている演算子。

AND演算子:「a&b」で表され、左辺と右辺の同じ位置にあるビットを比較して、両方のビットが1の場合だけ1にする

OR演算子:「a|b」で表され、左辺と右辺の同じ位置にあるビットを比較して、どちらかのビットが1であれば1にする

まずこの問題の6行目では「7 | 8」という形で比較されていますが、これをそれぞれ2進数で表すと以下のようになります。

7 →  + +  よって「0111」と表すことができます。

8 →  よって「1000」

0111 | 1000」で比較すると、「1111」となるため「15」となる

よって「case i | j」の場合ではないので何も処理されず次のcaseへと移りますが、次の「case 8」でもないのでこちらも何も処理ざれず次のcaseへと移ります。

「case 2 | 5」の場合も先ほどと同様に「OR演算子」の考え方でいくと、

「010 | 101」の比較となるので、その結果「111」となります。「111」を10進数で表すと「7」となるので「baz」が出力されることになります。

Switch文とビット演算子はセットで出題されるので必ず押さえて、確実に点数稼ぎにつなげていきましょう!

 

どうでしたでしょうか?今回の記事では瞬殺できるみたいな感じで言っていますが、もちろん小さなミスで落とすわけにはいかないので本番ではもうちょっとゆっくり解いても時間は大丈夫です。

ですが、今回紹介した問題だけでなく他のざまざまな問題でも、暗記しておけばサラッと解けるものは多いので、問題を解く時間を半分に短縮してより効率の良い勉強に繋げられます。

普段の何気ない移動時間や待ち時間に暗記勉強を行って、演習問題はサラッと解いた後、解説を読んだり理解する時間を多くとっていきましょう!きっとそれが試験合格への近道にもなりますし、より深い理解へと繋がると思っています。

次回は今回の記事で紹介できなかったものを紹介していこうと思います。

では、また👋

ABOUT ME
ヒロ
社会人4年目/25歳/食品商社で2年間営業した後、IT業界にシステムエンジニアとして転職/Java,PHP言語を扱う開発エンジニア