クラスとインタフェース
クラス
実装例
public class Outer {
class Inner {
// do something
}
}
- クラス内に定義したクラスのことを「ネストしたクラス」と呼ぶ。
- ネストしたクラスを囲む外側のクラスのことを「エンクロージングクラス」or 「トップレベルクラス」という。
- staticで修飾したインナークラスを「staticインナークラス」。
- 「ローカルクラス」と「匿名クラス」はどちらもメソッド内で定義したクラス。
public | protected | private | abstract | final | static | |
---|---|---|---|---|---|---|
インナークラス | ○ | ○ | ○ | ○ | ○ | × |
staticインナークラス | ○ | ○ | ○ | ○ | ○ | ○ |
ローカルクラス | × | × | × | ○ | ○ | × |
匿名クラス | × | × | × | × | × | × |
インナークラスのインスタンス化
- インナークラスのインスタンスを生成するには、先にエンクロージングクラスのインスタンスを生成し、そのインスタンスへの参照とnew演算子を使う。
実装例①
public class Outer {
class Inner {
public void test() {
System.out.println("test");
}
}
public static void main(String[] args) {
new Outer().new Inner();
}
}
実装例②
public class Outer {
public static void main(String[] args) {
var inner = new Inner();
}
class Inner {
String name;
Inner(String name) {
this.name = name;
}
}
}
実行結果
①Innerクラスの宣言をstaticで修飾する。
②Innerクラスの宣言自体を、インナークラスではなく別のクラスとして定義する。
staticインナークラスのインスタンス化
- エンクロージングクラスのインスタンスなしでインスタンス化可能。
実装例
public class Outer {
public static class Inner {
Inner inner = new Inner();
}
}
インナークラス
- staticではないインナークラスには、staticなフィールドやメソッドを定義できない。
- staticなインナークラスには、staticなものとそうでないものを両方を定義できる。
実装例
public class Outer {
void test() {
Inner.message = "Hello";
}
class Inner {
private static String message = "Hello";
public void test() {
System.out.println(message);
}
}
}
実行結果
private static String message = "Hello";でコンパイルエラー
- staticなインナークラスからエンクロージングクラスの非saticなフィールドやメソッドにはアクセスができないため、下記はコンパイルエラーが起きる。
フィールドmessageをstaticで修飾するとコンパイルエラーは解消します。
実装例
public class Outer {
private String message = "Hello";
static class Inner {
public void test() {
System.out.println(message);
}
}
}
実行結果
private String message = "Hello";でコンパイルエラー
匿名クラス
名前を持たないクラスのことを指す。
匿名クラスは、インターフェースや抽象クラスを実装・拡張するために使われ、クラスの定義と同時にインスタンス化される。
実装例
// Runnableインタフェースを実現した匿名クラス
public class ThreadSample {
public static voidmain(String[] args) {
Runnable run = new Runnable() {
@Override
public void run() {
System.out.println ("hello");
}
};
// do something
}
}
- 匿名クラスにはコンストラクタを定義できない。
実装例
public class Sample {
public Sample() {
System.out.println("A");
}
}
public class Main {
public static void main(String[] args) {
Sample sample = new Sample() {
public Sample() {
System.out.println("B");
}
};
}
}
実行結果
コンパイルエラー
実装例
public class Sample {
private int num;
public Sample(int num) {
this.num = num;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
}
public class Main {
public static void main(String[] args) {
var sample = new Sample(10) {
void modify(int num) {
setNum(num);
}
};
sample.modify(100);
System.out.println(sample.getNum());
}
}
実行結果
100
ローカルクラス
- 外部クラスのメソッド内でクラス定義するクラスをローカルクラスという。
- 一般的にはメソッドの中で局所的に定義されたクラスのことをいう。
- ローカルクラスは内部クラスの一種なので、staticなフィールド変数やメソッドを定義することはできない。
実装例
public class Factory {
public static Test hello() {
class Hello implements Test {
@Override
public void execute() {
System.out.println ("hello");
}
}
return new Hello();
}
}
public class FactoryTest {
public static void main(String[] args) {
Factory.hello().execute(); // helloと表示される
}
}
- ローカルクラス内から参照するローカル変数はローカルクラスよりもあとにローカル変数を宣言するとコンパイルエラーになる。
- ローカルクラス内から参照するローカル変数は実質的finalでなければならない。
実装例
public class Factory {
public void test() {
String name = "sample";
class Sample {
public void hello() {
System.out.println (name);
}
}
name = "test"; // コンパイルエラー
return new Sample().hello();
}
}
インタフェースクラス
実装例
public interface A {
static void test() {
System.out.println ("A");
}
}
public class Main implements A {
public static void main(String[] args) {
A.test(); // Aと表示される
}
}
- インタフェースのstaticメソッドは、継承したサブインタフェースや実現したクラスの名前を使って呼び出すことができない。
実装例
public interface A {
static void test() {
System.out.println ("A");
}
}
public interface B extends A {}
public interface C implements A {}
public class D implements A {
public static void main(String[] args) {
A.test(); // Aと表示される
B.test(); // コンパイルエラー
C.test(); // コンパイルエラー
test(); // コンパイルエラー
}
}
- インタフェースに定義したstaticメソッドは、そのインタフェースを実現したクラスでオーバーライドできない。(クラスに定義したstaticメソッドもオーバーライドできない。)
実装例
public interface A {
static void test() {
System.out.println ("A");
}
}
public class Main implements A {
@Override
public static void test() {
System.out.println ("B");
}
public static void main(String[] args) {
A.test();
}
}
実行結果
@Overrideをつけるとコンパイルエラー
@Overrideをつけないと「A」が表示される
- インタフェースのデフォルトメソッドをオーバーライドしたとき、オーバーライドした側のメソッドからデフォルトメソッドを呼び出すことができる。
- 2つ以上の階層にあるスーパーインタフェースが持っているデフォルトメソッドを呼び出すことはできない。
- 「インタフェース名.super.メソッド名」でデフォルトメソッドを呼び出させる。
- オーバーライドしたメソッドからデフォルトメソッドを呼び出すとき、呼び出せるのは直接の関係があるインナークラスか、そのインタフェースが継承している1つ上の階層にあたるスーパーインタフェースだけ。
実装例
public interface A {
default void test() {
System.out.println ("A");
}
}
public interface B {
default void test() {
System.out.println ("B");
}
}
public interface C extends B {}
public interface D extends C {}
public class Sample implements A, D {
@Override
public void test() {
A.super.test();
// D.super.test();// コンパイル可能
}
}
実行結果
A
- クラスが定義するメソッドと、インタフェースのデフォルトメソッドが重複した場合、スーパークラスメソッドが優先的に呼び出される。
実装例
public interface A {
default void test() {
System.out.println ("A");
}
}
public class B {
public void test() {
System.out.println ("B");
}
}
public class C extends B implements A {}
public class Main {
public static void main(String[] args) {
A a = new C();
a.test();
}
}
実行結果
B
- スーパークラスのメソッドがpublicではないため、Bクラスから引き継いだtestメソッドが、Aインタフェースに定義されているtestメソッドと互換性がないためコンパイルエラー。
public interface A {
public default void test() {
System.out.println ("A");
}
}
public abstract class B {
protected void test() {
System.out.println ("B");
}
}
public class Sample extends B implements A {
public static void main(String[] args) {
new Sample().test();
}
}
実行結果
コンパイルエラー(class Bのtestメソッドが修飾子がprotectedのため。)
- インターフェースにprivate修飾子で修飾した「privateメソッド」を追加できる。デフォルトメソッド内で使用する想定でprivateメソッドを追加する。
実装例
public interface Hoge {
private void hoge() {
System.out.println("Hoge");
}
default void print() {
hoge();
}
}
public class Main implements Hoge {
public static void main(String[] args) {
new Main().print();
}
@Override
public void print() {
Hoge.super.print();
}
}
実行結果
Hoge
- 2つ以上の階層にあるスーパーインタフェースが持っているデフォルトメソッドを呼び出すことはできない。
Enum
定数をまとめて定義するもの。
実装例
public enum Sports {
SOCCER,
BASEBALL,
BASKETBALL,
}
public class Main {
public static void main(String[] args) {
Sports favorite = Sports.SOCCER; // サッカーが好きです。と表示
switch(favorite) {
case SOCCER:
System.out.println("サッカーが好きです。");
break;
case BASEBALL:
System.out.println("野球が好きです。");
break;
case BASKETBALL:
System.out.println("バスケが好きです。");
break;
}
}
}
Enumが持つメソッド
valueOfメソッド
valueOfは名称の文字列に対応するEnumを戻します。
先程の例だとvalueOfの引数に"BASKETBALL"を定義するとEnumの"BASKETBALL"が戻ります。
実装例
System.out.println(Sports.valueOf("BASKETBALL"));// BASKETBALLと表示
values
valuesは全てのEnumの配列を返すstaticメソッド。
実装例
System.out.println(Test.values()[0]);
実装例
...省略
for(Sports favorite : Sports.values()) {
System.out.prontln(favorite); // SOCCER、BASEBALL、BASKETBALLと表示
}
- System.outのprintlnメソッドは、引数で渡されたオブジェクトのtoStringメソッドを内部的に呼び出し、その結果をコンソールに表示。
- 必ずアクセス修飾子をprivateにしなければならない。privateでない別のアクセス修飾子にした場合、コンパイルエラーになる。
- Enumが使用されるとき、列挙子が1つずつインスタンス化される。
実装例
public enum Sample {
A("hello"), B("hello"), C("hello");
private final String value;
private Sample(String value) {
System.out.println(value);
this.value = value;
}
@Override
public String toString() {
return this.value;
}
}
public class Main {
public static void main(String[] args) {
System.out.println(Sample.A); // Sample.Aでenumクラスのコンストラクタが呼び出される
}
}
実行結果
helloが4回表示される
- switch文で列挙子を使用することで、読みやすさ(可読性)が向上することなどがEnumを使うメリット。
Typeクラス
public enum Type {
A(1), B(2), C(3);
int val;
private Type(int val) {
this.val = val;
}
}
Sampleクラス
public class Sample {
public static void main(String[] args) {
Type type = Type.A;
switch(type) {
case A:
System.out.println("A");
break;
case B:
System.out.println("B");
break;
case C:
System.out.println("C");
break;
}
}
}