オブジェクト指向プログラミング言語にとって、クラスは間違いなく最も重要な基盤です。抽象化、カプセル化、継承、ポリモーフィズムの 4 つの主要な機能はクラスと切り離すことができません。クラスの存在だけがオブジェクト指向プログラミングの特徴を反映します。今日は、クラスと継承に関する知識を学びます。まず、クラスの初期化に関連したことについて説明し、その後、継承の優れた機能をいくつかの側面から説明します。この記事の目次概要は以下の通りです:
1.授業は理解していますか?
2. 相続について理解していますか?
3. 筆記面接でよくある質問
1. カテゴリを理解していますか?
Java では、クラス ファイルは拡張子 .java が付いたコード ファイルです。各クラス ファイルに含めることができるパブリック クラスは 1 つだけです。パブリック クラスがある場合、クラス ファイルの名前はそのクラス ファイルと同じである必要があります。 public クラスの名前。 public が存在しない場合、クラス ファイルの名前は任意の名前にすることができます (もちろん、数字で始まる名前は許可されません)。
クラス内で、メンバー変数について、定義時に明示的な代入の初期化がない場合、Java はクラスの各メンバー変数が適切に初期化されていることを確認します:
1) char、short、byte、int、long、float の場合、double およびその他の基本データ型はデフォルトで 0 に初期化されます (ブール変数はデフォルトで false に初期化されます)
2) 参照型変数の場合、デフォルトで null に初期化されます。
コンストラクターが明示的に定義されていない場合、コンパイラーは引数のないコンストラクターを自動的に作成しますが、コンストラクターが明示的に定義されている場合、コンパイラーは自動的にコンストラクターを追加しないことに注意してください。デフォルトではすべてのコンストラクターが静的であることに注意してください。
初期化シーケンスに注目してみましょう:
プログラムが実行されるとき、Java 実行エンジンはまず、そのクラスがロードされているかどうかを確認します。クラスをロードしてからオブジェクトを生成します。ロードされている場合は、オブジェクトが直接生成されます。
クラスのロード処理中に、クラスの静的メンバー変数が初期化されます。また、クラス内に静的ステートメントブロックがある場合、静的ステートメントブロックが実行されます。静的メンバー変数と静的ステートメント ブロックの実行順序は、コード内の順序と一致します。 Java では、クラスはオンデマンドでロードされ、必要なときに 1 回だけロードされることに注意してください。次の例を見て理解してください:
public class Test { public static void main(String[] args) throws ClassNotFoundException { Bread bread1 = new Bread(); Bread bread2 = new Bread(); } } class Bread { static{ System.out.println("Bread is loaded"); } public Bread() { System.out.println("bread"); } }
このコードを実行すると、「Bread isloaded」が 1 回だけ出力されることがわかります。
オブジェクトを生成する過程では、まずオブジェクトのメンバー変数が初期化され、その後コンストラクターが実行されます。これは、変数がメソッド定義の間に散在している場合でも、クラス内の変数はメソッド (コンストラクターを含む) が呼び出される前に初期化されることを意味します。
public class Test { public static void main(String[] args) { new Meal(); } } class Meal { public Meal() { System.out.println("meal"); } Bread bread = new Bread(); } class Bread { public Bread() { System.out.println("bread"); } }
出力結果は、
bread
meal
2. 継承を理解していますか?
継承はすべての OOP 言語に不可欠な部分であり、Java では継承関係を表現するために extends キーワードが使用されます。クラスを作成すると、継承するクラスが明示的に指定されていない場合は、常にルート クラスの Object から暗黙的に継承されます。たとえば、次のコード:
class Person { public Person() { } } class Man extends Person { public Man() { } }
Man が person クラスから継承するクラス この場合、 person クラスは親クラス (基本クラス) と呼ばれ、Man クラスはサブクラス (派生クラス) と呼ばれます。 2 つのクラス間に継承関係がある場合、サブクラスは親クラスのメソッドと変数を自動的に継承し、親クラスのメソッドと変数をサブクラスで呼び出すことができます。 Java では、単一継承のみが許可されます。つまり、クラスは最大 1 つの親クラスからのみ明示的に継承できます。ただし、クラスは複数のクラスに継承できます。つまり、クラスは複数のサブクラスを持つことができます。
1. サブクラスは親クラスのメンバ変数を継承します
サブクラスがあるクラスを継承する場合、親クラスのメンバ変数を利用することができますが、親クラスのメンバ変数をすべて完全に継承するわけではありません。具体的な原則は次のとおりです:
1) 親クラスの public および protected メンバー変数は継承できますが、親クラスの private メンバー変数は継承できません。親クラス、子クラスと親クラスが同じパッケージの下にある場合、サブクラスは継承できます。それ以外の場合、サブクラスは継承できません。
3) サブクラスが継承できるメンバー変数の場合、同じ名前がサブクラスに出現すると、隠蔽が発生します。つまり、サブクラスのメンバー変数は、同じ名前を持つ親クラスのメンバー変数をブロックします。親クラスの同名のメンバ変数にサブクラスでアクセスしたい場合は、super キーワードを使用して参照する必要があります。
2. サブクラスは親クラスのメソッドを継承します
同様に、サブクラスも親クラスのメソッドをすべて完全に継承するわけではありません。
1) 親クラスのパブリックおよび保護されたメンバーメソッドを継承できますが、親クラスのプライベートメンバーメソッドは継承できません。同じパッケージ内にある場合、サブクラスは継承できます。それ以外の場合、サブクラスは継承できません。
3)对于子类可以继承的父类成员方法,如果在子类中出现了同名称的成员方法,则称为覆盖,即子类的成员方法会覆盖掉父类的同名成员方法。如果要在子类中访问父类中同名成员方法,需要使用super关键字来进行引用。
注意:隐藏和覆盖是不同的。隐藏是针对成员变量和静态方法的,而覆盖是针对普通方法的。(后面会讲到)
3.构造器
子类是不能够继承父类的构造器,但是要注意的是,如果父类的构造器都是带有参数的,则必须在子类的构造器中显示地通过super关键字调用父类的构造器并配以适当的参数列表。如果父类有无参构造器,则在子类的构造器中用super关键字调用父类构造器不是必须的,如果没有使用super关键字,系统会自动调用父类的无参构造器。看下面这个例子就清楚了:
class Shape { protected String name; public Shape(){ name = "shape"; } public Shape(String name) { this.name = name; } } class Circle extends Shape { private double radius; public Circle() { radius = 0; } public Circle(double radius) { this.radius = radius; } public Circle(double radius,String name) { this.radius = radius; this.name = name; } }
这样的代码是没有问题的,如果把父类的无参构造器去掉,则下面的代码必然会出错:
改成下面这样就行了:
4.super
super主要有两种用法:
1)super.成员变量/super.成员方法;
2)super(parameter1,parameter2....)
第一种用法主要用来在子类中调用父类的同名成员变量或者方法;第二种主要用在子类的构造器中显示地调用父类的构造器,要注意的是,如果是用在子类构造器中,则必须是子类构造器的第一个语句。
三.常见的面试笔试题
1.下面这段代码的输出结果是什么?
public class Test { public static void main(String[] args) { new Circle(); } } class Draw { public Draw(String type) { System.out.println(type+" draw constructor"); } } class Shape { private Draw draw = new Draw("shape"); public Shape(){ System.out.println("shape constructor"); } } class Circle extends Shape { private Draw draw = new Draw("circle"); public Circle() { System.out.println("circle constructor"); } }
shape draw constructor
shape constructor
circle draw constructor
circle constructor
这道题目主要考察的是类继承时构造器的调用顺序和初始化顺序。要记住一点:父类的构造器调用以及初始化过程一定在子类的前面。由于Circle类的父类是Shape类,所以Shape类先进行初始化,然后再执行Shape类的构造器。接着才是对子类Circle进行初始化,最后执行Circle的构造器。
2.下面这段代码的输出结果是什么?
public class Test { public static void main(String[] args) { Shape shape = new Circle(); System.out.println(shape.name); shape.printType(); shape.printName(); } } class Shape { public String name = "shape"; public Shape(){ System.out.println("shape constructor"); } public void printType() { System.out.println("this is shape"); } public static void printName() { System.out.println("shape"); } } class Circle extends Shape { public String name = "circle"; public Circle() { System.out.println("circle constructor"); } public void printType() { System.out.println("this is circle"); } public static void printName() { System.out.println("circle"); } }
shape constructor
circle constructor
shapethis is circle
shape
这道题主要考察了隐藏和覆盖的区别(当然也和多态相关,在后续博文中会继续讲到)。
覆盖只针对非静态方法(终态方法不能被继承,所以就存在覆盖一说了),而隐藏是针对成员变量和静态方法的。这2者之间的区别是:覆盖受RTTI(Runtime type identification)约束的,而隐藏却不受该约束。也就是说只有覆盖方法才会进行动态绑定,而隐藏是不会发生动态绑定的。在Java中,除了static方法和final方法,其他所有的方法都是动态绑定。因此,就会出现上面的输出结果。
更多Java: クラスと継承相关文章请关注PHP中文网!