[Java SE] 抽象クラスと抽象インターフェイス

目次

【1】抽象クラス

【1.1】抽象クラスの概念

【1.2】抽象クラスの構文

【1.3】抽象クラスの特徴

【1.4】抽象クラスの役割

【2】インターフェース

【2.1】インターフェースの概念

【2.2】文法規則

【2.3】インターフェースの使用方法

【2.4】インターフェース特性

【2.5】複数のインターフェースの実装

【2.6】インターフェース間の継承

【2.7】インターフェース使用例

【2.8】クローン可能なインターフェースとディープコピー

【2.9】抽象クラスとインターフェースの違い

【3】オブジェクトクラス

【3.1】オブジェクト情報をtoStringメソッドに出力する

【3.2】オブジェクト比較イコールメソッド

【3.3】ハッシュコード方式


【1】抽象クラス

【1.1】抽象クラスの概念

        オブジェクト指向の概念では、すべてのオブジェクトはクラスによって記述されますが、逆に、すべてのクラスがオブジェクトの記述に使用されるわけではありません。クラスに特定のオブジェクトを記述するのに十分な情報が含まれていない場合、そのクラスは抽象クラスになります

【例えば】

        グラフィックの印刷の例では、親クラス Shape の描画メソッドには実際の作業がないことがわかりました。グラフィックの主な描画は、Shape のさまざまなサブクラスの描画メソッドによって行われます。このようなものには実際の処理はありません。 work.method を抽象メソッドとして設計することができ、その抽象メソッドを含むクラスを抽象クラスと呼びます

【1.2】抽象クラスの構文

        Javaでは、クラスをabstractで変更したものを抽象クラスと呼び、抽象クラス内でabstractで変更したメソッドを抽象メソッドと呼びますが、抽象メソッドは特定の実装体を与える必要はありません。

// 抽象类:被abstract访问修饰符修饰。
abstract class Shape {
    // 抽象方法:被abstract访问修饰符修饰,没有方法体。
    public abstract void draw();
    abstract void calArea();

    // 抽象类也是类,也可以增加普通方法和属性。
    public double getArea() {
        return _arga;
    }
   
    protected double _arga; // 字段:面积
}

[注意]抽象クラスもクラスであり、通常のメソッド、プロパティ、さらにはコンストラクターを含めることができます。

【1.3】抽象クラスの特徴

  1. 抽象クラスはオブジェクトを直接インスタンス化できません

Shape shape = new Shape();

// 编译出错
Error:(30, 23) java: Shape是抽象的; 无法实例化
  1. 抽象メソッドをプライベートにすることはできません

abstract class Shape {
	abstract private void draw();
}

// 编译出错
Error:(4, 27) java: 非法的修饰符组合: abstract和private

[注意]抽象メソッドがアクセス修飾子を追加しない場合、デフォルトは public になります。

  1. 抽象メソッドはサブクラスによってオーバーライドされる必要があるため、抽象メソッドはfinalおよびstaticによって変更できません。

public abstract class Shape {
	abstract final void methodA();
	abstract public static void methodB();
}

// 编译报错:
// Error:(20, 25) java: 非法的修饰符组合: abstract和final
// Error:(21, 33) java: 非法的修饰符组合: abstract和static
  1. 抽象クラスは継承する必要があり、継承後、サブクラスは親クラスの抽象メソッドをオーバーライドする必要があります。そうでない場合は、サブクラスも抽象クラスであるため、abstract で変更する必要があります。

// 抽象类:被abstract访问修饰符修饰。
abstract class Shape {
    // 抽象方法:被abstract访问修饰符修饰,没有方法体。
    public abstract void draw();

    protected double _area; // 字段:面积
}

// 矩形类
class Rect extends Shape {
    private double length;
    private double width;
    Rect(double length, double width){
        this.length = length;
        this.width = width;
    }
    public void draw(){
        System.out.println("矩形: length= "+length+" width= " + width);
    }
    public void calcArea(){
        _area = length * width;
    }
}

// 圆类:
class Circle extends Shape{
    private double r;
    final private static double PI = 3.14;
    public Circle(double r){
        this.r = r;
    }
    public void draw(){
        System.out.println("圆:r = "+r);
    }
    public void calcArea(){
        _area = PI * r * r;
    }
}

// 三角形类:
abstract class Triangle extends Shape {
    private double a;
    private double b;
    private double c;
    @Override
    public void draw() {
        System.out.println("三角形:a = "+a + " b = "+b+" c = "+c);
    }
    // 三角形:直角三角形、等腰三角形等,还可以继续细化
    //@Override
    //double calcArea(); // 编译失败:要么实现该抽象方法,要么将三角形设计为抽象类
}
  1. 抽象クラスには必ずしも抽象メソッドが含まれる必要はありませんが、抽象メソッドを持つクラスは抽象クラスである必要があります。

  2. 抽象クラスには、オブジェクトの作成時に親クラスのメンバー変数を初期化するためのサブクラスのコンストラクター メソッドを含めることができます。

【1.4】抽象クラスの役割

        抽象クラス自体はインスタンス化できません。抽象クラスを使用するには、抽象クラスのサブクラスを作成し、そのサブクラスで抽象クラスの抽象メソッドをオーバーライドする必要があります。

        通常のクラスも継承でき、通常のメソッドもオーバーライドできるのに、なぜ抽象クラスや抽象メソッドを使用する必要があるのか​​と言う人もいるかもしれません。

それは本当ですが、抽象クラスを使用することは、コンパイラ検証の追加レイヤーに相当します。

        抽象クラスを使用するシナリオは上記のコードのようなものです。実際の作業は親クラスではなく、サブクラスによって行われます。このとき誤って親クラスとして使用しても、次を使用してもエラーは発生しません。通常のクラスコンパイラ . ただし、親クラスが抽象クラスの場合はインスタンス化時にエラーが発生しますので、早めに問題を発見しましょう。

多くの構文の目的は「エラーを防ぐ」ことです。たとえば、私たちが使用した Final も同様です。作成された変数がユーザーによって変更されない場合、それは定数と同等ではないでしょうか? しかし、final を追加することでエラーを防ぐことができます。誤って変更されている場合は、コンパイラーが時間内に通知してくれるようにします。

コンパイラの検証を活用することは、実際の開発において非常に有意義です。

【2】インターフェース

【2.1】インターフェースの概念

        実際には、ラップトップの USB ポート、電源ソケットなど、インターフェイスの例はたくさんあります。

        コンピュータの USB ポートに接続できるもの: U ディスク、マウス、キーボードなど、USB プロトコルに準拠するすべてのデバイス 電源ソケットに接続できるもの: コンピュータ、テレビ、炊飯器など、USB プロトコルに準拠するすべての機器上記の例に仕様を組み込むことができますが、インターフェースは公的動作標準であり、誰もが実装する場合、標準に準拠している限り、普遍的に使用できます。Java では、インターフェイスは複数のクラスの共通の仕様、つまり参照データ型と考えることができます。

【2.2】文法規則

        インターフェースの定義形式は基本的にクラスと同じであり、classキーワードをinterfaceキーワードに置き換えて定義します。

interface ISharp {
    int size = 10;  // 默认是public static final
    void dorw();    // 默认是public abstract

    // 接口中的方法不能实现,如果非要实现需要加default
    public default void func01() {
        System.out.println("默认方法");
    }

    // 接口中可以有静态方法
    public static void func02() {
        System.out.println("静态方法");
    }
}

【ヒント】

  1. インターフェイスを作成する場合、インターフェイスの名前は通常、大文字の I で始まります。

  2. インターフェイスの名前は通常、「形容詞」を使用して付けられます。

  3. Alibaba コーディング標準では、コードを簡潔に保つために、インターフェイス内のメソッドとプロパティを変更記号で変更してはならないと規定しています。

【2.3】インターフェースの使用方法

インターフェイスを直接使用することはできません。インターフェイスを「実装」し、インターフェイス内のすべての抽象メソッドを実装するには、「実装クラス」が必要です。

public class 类名称 implements 接口名称{
	// ...
}	

[注意]サブクラスと親クラスの継承関係はextend、クラスとインターフェースの実装関係はimplementsです。

【インターフェース使用コード例】

/** - 定义接口
 */
interface ISharp {
    public abstract void drow();
}
/** - 继承接口
 */
class Rect implements ISharp {
    @Override
    public void drow() {
        System.out.println("矩形!");
    }
}
/** - 继承接口
 */
class Flower implements  ISharp {
    @Override
    public void drow() {
        System.out.println("花!");
    }
}

/** - 继承接口
 */
class Cyclic implements  ISharp {
    @Override
    public void drow() {
        System.out.println("圆!");
    }
}

public class Main {
    public static void drowMap(ISharp iSharp) {
        iSharp.drow();
    }
    
    public static void main(String[] args) {
        // 接口实现多态:向上转型
        drowMap(new Rect());
        drowMap(new Flower());
        drowMap(new Cyclic());
    }
}

【インターフェースサンプルコード】

/** - (基础方法)定义接口
 */
interface USB {
    public abstract void openDevice();
    public abstract void closeDevice();
}

/** - (继承接口)鼠标
 */
class Mouse implements USB {

    @Override
    public void openDevice() {
        System.out.println("打开鼠标");
    }

    @Override
    public void closeDevice() {
        System.out.println("关闭鼠标");
    }

    public void click() {
        System.out.println("点击鼠标");
    }
}

/** - (继承接口)键盘
 */
class KeyBoard implements USB {

    @Override
    public void openDevice() {
        System.out.println("打开键盘");
    }

    @Override
    public void closeDevice() {
        System.out.println("关闭键盘");
    }

    public void inPut() {
        System.out.println("键盘输入");
    }
}

/** - 电脑
 */
class Computer {
    public void powerOn() {
        System.out.println("打开电脑");
    }
    public void powerOff() {
        System.out.println("关闭电脑");
    }
    public void useDevice(USB usb) {
        usb.openDevice();
        if(usb instanceof Mouse) {
            Mouse mouse = (Mouse)usb;
            mouse.click();
         } else if (usb instanceof KeyBoard) {
            KeyBoard keyBoard = (KeyBoard)usb;
            keyBoard.inPut();
        }
        usb.closeDevice();
    }
}

public class Main {
    public static void main(String[] args) {
        Computer computer = new Computer();
        computer.powerOn();

        /* 使用设备(类里面的函数处理类型判断) */
        computer.useDevice(new Mouse());
        computer.useDevice(new KeyBoard());

        computer.powerOff();
    }
}

【2.4】インターフェース特性

  1. インターフェイスの型は参照型ですが、インターフェイスのオブジェクトを直接新しいものにすることはできません。

public class TestUSB {
	public static void main(String[] args) {
		USB usb = new USB();
	}
} 
// Error:(10, 19) java: day20210915.USB是抽象的; 无法实例化
  1. インターフェイス内のすべてのメソッドはパブリック抽象メソッドです。つまり、インターフェイス内のメソッドは暗黙的にパブリック抽象として指定されます(パブリック抽象のみにすることができ、他の修飾子はエラーを報告します)。

public interface USB {
	// Error:(4, 18) java: 此处不允许使用修饰符private
	private void openDevice();
	void closeDevice();
}
  1. インターフェイス内のメソッドはインターフェイス内に実装できず、インターフェイスを実装するクラスによってのみ実装できます。

public interface USB {
	void openDevice();
	// 编译失败:因为接口中的方式默认为抽象方法
	// Error:(5, 23) java: 接口抽象方法不能带有主体
	void closeDevice(){
		System.out.println("关闭USB设备");
	}
}
  1. インターフェイス内のメソッドをオーバーライドする場合、デフォルトのアクセス許可を使用することはできません。

public interface USB {
    void openDevice(); // 默认是public的
    void closeDevice(); // 默认是public的
}
public class Mouse implements USB {
    @Override
    void openDevice() {
        System.out.println("打开鼠标");
    }
    // ...
}
// 编译报错,重写USB中openDevice方法时,不能使用默认修饰符
// 正在尝试分配更低的访问权限; 以前为public
  1. インターフェイスには変数を含めることができますが、インターフェイス内の変数は暗黙的にpublic static Final 変数として指定されます。

public interface USB {
    double brand = 3.0; // 默认被:final public static修饰
    void openDevice();
    void closeDevice();
}

public class TestUSB {
    public static void main(String[] args) {
        System.out.println(USB.brand); // 可以直接通过接口名访问,说明是静态的
        // 编译报错:Error:(12, 12) java: 无法为最终变量brand分配值
        USB.brand = 2.0; // 说明brand具有final属性
    }
}
  1. インターフェイス内に静的なコード ブロックとコンストラクターを含めることはできません。

public interface USB {
    // 编译失败
    public USB(){
    } 
    {} // 编译失败
    void openDevice();
    void closeDevice();
}
  1. インターフェイスはクラスではありませんが、インターフェイスがコンパイルされた後のバイトコード ファイルのサフィックス形式も .class です。

  2. クラスがインターフェース内のすべての抽象メソッドを実装していない場合、クラスを抽象クラスとして設定する必要があります。

  3. JDL 1.8: インターフェースにはデフォルトのメソッドを含めることもできます。

  4. インターフェイス メソッドをオーバーライドしてクラスに抽象を追加したくない場合は、クラスを抽象クラスとして定義しますが、このクラスが他のクラスに継承されている場合は、書き直す必要があります。

【まとめ】

  • インターフェースを変更するには、インターフェースを使用します。

  • インターフェイスのメンバー メソッドは特定の実装を持つことができず、デフォルトではパブリックです。

    • 抽象メソッド: デフォルトはパブリック抽象メソッドです。

    • JDK 1.8 以降、実装できるメソッドはありますが、このメソッドはデフォルトでのみ変更できます。

    • インターフェイスには静的メソッドを含めることができます。

  • メンバー変数はデフォルトでは public static Final です。

  • インターフェイスをインスタンス化できません。

  • 実装は、クラスとインターフェイス間の関係を実装するために使用されます。

【2.5】複数のインターフェースの実装

        Java では、クラス間に単一の継承があります。クラスは親クラスを 1 つだけ持つことができます。つまり、Java では多重継承はサポートされていませんがクラスは複数のインターフェイスを実装できます以下ではクラスを使用して動物のグループを表します

/** - 基类(动物)
 */
class Animal {
    protected String _name;
    protected int _age;

    /* 构造函数 */
    public Animal(String name, int age) {
        this._name = name;
        this._age = age;
    }
}

さらに、「飛行」、「走行」、「水泳」をそれぞれ表す一連のインターフェイスを提供します。

/** - 接口(会飞)
 */
interface IFlying {
    void flyint();
}

/** - 接口(会跑)
 */
interface IRunning {
    void running();
}

/** - 接口(会游泳)
 */
interface ISwimming {
    void swimming();
}

次に、実行できる特定の動物の猫をいくつか作成します。

/** - 猫的行为
 */
class Cat extends Animal implements IRunning {
    public Cat(String name, int age) {
        super(name, age);
    }
    @Override
    public void running() {
        System.out.println("我的名字叫:" + _name + "->我会跑!");
    }
}

魚は泳ぐことができます

/** - 鱼的行为
 */
class Fish extends Animal implements ISwimming {
    public Fish(String name, int age) {
        super(name, age);
    }
    @Override
    public void swimming() {
        System.out.println("我的名字叫:" + _name + "->我会游泳!");
    }
}

アヒルは泳いだり、走ったり、飛んだりできます

/** - 鸭子行为
 */
class Duck extends Animal implements IRunning, IFlying, ISwimming {
    public Duck(String name, int age) {
        super(name, age);
    }

    @Override
    public void flyint() {
        System.out.println("我的名字叫:" + _name + "->我会飞!");
    }

    @Override
    public void running() {
        System.out.println("我的名字叫:" + _name + "->我会飞游跑!");
    }

    @Override
    public void swimming() {
        System.out.println("我的名字叫:" + _name + "->我会游泳!!");
    }
}

[注意]クラスが複数のインタフェースを実装する場合、各インタフェースの抽象メソッドを実装するか、実装しない場合は抽象クラスとして設定する必要があります。

ヒント: IDEA で Ctrl + i を使用すると、インターフェイスをすばやく実装できます。

public static void main(String[] args) {
    Cat cat = new Cat("猫", 20);
    cat.running();
    
    Fish fish = new Fish("鱼", 21);
    fish.swimming();
    
    Duck duck = new Duck("鸭子", 32);
    duck.swimming();
    duck.flyint();
    duck.running();
}

上記のコードは、Java オブジェクト指向プログラミングで最も一般的な使用法を示しています: クラスは親クラスを継承し、複数のインターフェイスを同時に実装します。継承によって表現される意味はセマンティクスであり、インターフェイスによって表現される意味は、 xxxの特徴を持っています。

  • 猫は走るのが特徴の動物です。

  • カエルも走ったり泳いだりできる動物です。

  • アヒルは、走ったり、泳いだり、飛んだりできる動物でもあります。

この設計の利点は何ですか? ポリモーフィズムの利点を常に念頭に置いて、プログラマが型を忘れるようにしてください。インターフェイスを使用すると、クラスのユーザーは特定の型に注意を払う必要はなく、特定のクラスが特定の機能を持っているかどうかだけに注意を払う必要があります。

例: 実行できる人は誰でも標準メソッドを呼び出すことができます。

// 此方法:拥有会跑的接口的对象都可以进行传参,并且会自动调用。
public static void run(IRunning runing) {
    runing.running();
}

public static void main(String[] args) {   
    // 自动调用行为,对比
    run(cat);
    // run(fish); // 鱼不会跑,所以不可以使用run方法
    run(duck);
}

【2.6】インターフェース間の継承

        Java では、クラス間に単一の継承があり、クラスは複数のインターフェイスを実装でき、インターフェイスは複数の継承を持つことができます。つまり、インターフェイスを使用すると、多重継承の目的を達成できます。

        インターフェイスはインターフェイスを継承して再利用することができ、extends キーワードを使用します。

【サンプルコード】

/** - 接口(会飞)
 */
interface IFlying {
    void flyint();
}

/** - 接口(会跑)
 */
interface IRunning {
    void running();
}

/** - 接口(会游泳)
 */
interface ISwimming {
    void swimming();
}

/** - 接口的继承
 */
interface ExtendInterface extends IFlying, IRunning, ISwimming {

}

/** - TestInter这个类需要实现所有的接口
 */
class TestInter implements ExtendInterface {

    @Override
    public void flyint() {

    }

    @Override
    public void running() {

    }

    @Override
    public void swimming() {

    }
}

        「水陸両用」を意味する、インターフェイスの継承を通じて新しいインターフェイス IAmphibious を作成します。このとき、インターフェイスを実装するために作成された Frog クラスは run メソッドを引き続き実装し、さらに swim メソッドも実装する必要があります。

        「水陸両用」を意味する、インターフェイスの継承を通じて新しいインターフェイス IAmphibious を作成します。このとき、インターフェイスを実装するために作成された Frog クラスは run メソッドを引き続き実装し、さらに swim メソッドも実装する必要があります。

        インターフェイス間の継承は、複数のインターフェイスをマージすることと同じです。

【2.7】インターフェース使用例

配列を学習するときに、配列をソートしてみました。

public static void main(String[] args) {
    int[] array = {1,3,65,7,8,9,0,3,6};
    Arrays.sort(array);
    System.out.println(Arrays.toString(array)); // 打印结果[0, 1, 3, 3, 6, 7, 8, 9, 65]
}

【オブジェクト配列をソートする方法1】

class Student {
    public String _name;
    public int _age;
    public Student(String name, int age){
        this._name = name;
        this._age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "_name='" + _name + '\'' +
                ", _age=" + _age +
                '}';
    }
}

学生オブジェクトの配列を指定して、このオブジェクト配列内の要素を (年齢順に) 並べ替えます。

public static void main(String[] args) {
    Student[] students = new Student[]{
            new Student("张三", 12),
            new Student("李四", 23),
            new Student("王五", 42),
            new Student("赵六", 13),
    };
}

これまでの理解によれば、配列には既製のソートメソッドが用意されていますが、このメソッドを直接使用することはできるのでしょうか?

Arrays.sort(students);
System.out.println(Arrays.toString(students));

// 报错:
"C:\Program Files\Microsoft\jdk-11.0.16.101-hotspot\bin\java.exe" "-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2022.3.2\lib\idea_rt.jar=53241:D:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2022.3.2\bin" -Dfile.encoding=UTF-8 -classpath E:\Code\Java\L20230720_Java\out\production\L20230720_Java Test.Test
Exception in thread "main" java.lang.ClassCastException: class Test.Student cannot be cast to class java.lang.Comparable (Test.Student is in unnamed module of loader 'app'; java.lang.Comparable is in module java.base of loader 'bootstrap')
	at java.base/java.util.ComparableTimSort.countRunAndMakeAscending(ComparableTimSort.java:320)
	at java.base/java.util.ComparableTimSort.sort(ComparableTimSort.java:188)
	at java.base/java.util.Arrays.sort(Arrays.java:1249)
	at Test.Test.main(Test.java:39)

よく考えてみると、通常の整数とは異なり、2 つの整数は直接比較でき、大小関係が明らかであることがわかりますが、2 つの生徒オブジェクトの大小関係をどのように判断するか? 追加で指定する必要があります。

Student クラスに Comparable インターフェイスを実装し、その中に CompareTo メソッドを実装します。

class Student implements Comparable<Student> {
    public String _name;
    public int _age;

    public Student(String name, int age) {
        this._name = name;
        this._age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "_name='" + _name + '\'' +
                ", _age=" + _age +
                '}';
    }

    @Override
    public int compareTo(Student s) {
        if (this._age > s._age) {
            return 1;
        } else if (this._age < s._age) {
            return -1;
        } else {
            return 0;
        }

    }
}

public static void main(String[] args) {
    Student[] students = new Student[]{
            new Student("张三", 12),
            new Student("李四", 23),
            new Student("王五", 42),
            new Student("赵六", 13),
    };
    Arrays.sort(students);
    System.out.println(Arrays.toString(students));
    // 打印结果:[Student{_name='张三', _age=12}, Student{_name='赵六', _age=13}, Student{_name='李四', _age=23}, Student{_name='王五', _age=42}]
}

CompareTo メソッドは、sort メソッド内で自動的に呼び出されます。compareTo のパラメーターは Object です。実際、渡されたオブジェクトは Student 型であり、現在のオブジェクトとパラメーター オブジェクトの大小関係が比較されます (分数として計算されます)。 )。

  • 現在のオブジェクトをパラメータ オブジェクトの前に並べ替える必要がある場合は、0 未満の数値を返します。

  • 現在のオブジェクトをパラメータ オブジェクトの後にランク付けする必要がある場合は、0 より大きい数値を返します。

  • 現在のオブジェクトとパラメータ オブジェクトの順序が決まっていない場合は、0 を返します。

プログラムを再度実行すると、期待どおりの結果が得られます。

[Student{_name='张三', _age=12}, Student{_name='赵六', _age=13}, Student{_name='李四', _age=23}, Student{_name='王五', _age=42}]

【オブジェクト配列をソートする方法2】

class Student {
    public String _name;
    public int _age;

    public Student(String name, int age) {
        this._name = name;
        this._age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "_name='" + _name + '\'' +
                ", _age=" + _age +
                '}';
    }
}

/* 类似仿函数一样的年龄排序(比较器) */
class AgeComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1._age - o2._age;
    }
}
/* 类似仿函数一样的姓名排序(比较器) */
class NameComparator implements  Comparator<Student> {

    @Override
    public int compare(Student o1, Student o2) {
        return o1._name.compareTo(o2._name);
    }
}

// 这样也是可以排序的,更灵活一些
public static void main(String[] args) {
    Student[] students = new Student[]{
            new Student("张三", 12),
            new Student("李四", 23),
            new Student("王五", 42),
            new Student("赵六", 13),
    };
    AgeComparator comparator1 = new AgeComparator();
    NameComparator comparator2 = new NameComparator();
    Arrays.sort(students, comparator2);
    System.out.println(Arrays.toString(students));
}

[注意事項]: sort メソッドの場合、渡される配列の各オブジェクトが「比較可能」である必要があり、compareTo メソッドをオーバーライドすることで、比較ルールを定義できます。

インターフェイスの理解をさらに深めるために、ソート メソッドを自分で実装して、先ほどのソート プロセスを完了してみます (バブル ソートを使用)。

public static void bubbleSort(Comparable[] students) {
    for(int i = 0; i < students.length - 1; i++) {
        for(int j = 0; j < students.length - 1 - i; j++) {
            if(students[j].compareTo(students[j + 1]) > 0) {
                // 交换
                Comparable temp = students[j];
                students[j] = students[j + 1];
                students[j + 1] = temp;
            }
        }
    }
}

public static void main(String[] args) {
    Student[] students = new Student[]{
            new Student("张三", 12),
            new Student("李四", 23),
            new Student("王五", 42),
            new Student("赵六", 13),
    };

    bubbleSort(students);
    System.out.println(Arrays.toString(students));
}

【2.8】クローン可能なインターフェースとディープコピー

        Java には便利なインターフェイスがいくつか組み込まれており、Clonable もその 1 つです。

        Object クラスには clone メソッドがあります。このメソッドを呼び出すと、オブジェクトの「コピー」を作成できます。ただし、clone メソッドを合法的に呼び出すには、まず Clonable インターフェイスを実装する必要があります。実装しないと、CloneNotSupportedException 例外がスローされます。

【クローン可能なインスタンス】

class Person implements Cloneable {
    public int _id;

    // 重写克隆方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    // 重写打印方法
    @Override
    public String toString() {
        return "Person{" +
                "_id=" + _id +
                '}';
    }
}

public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person = new Person();
        person._id = 99;

        // 克隆
        Person personClone = (Person)person.clone();
        // 查看克隆结果
        System.out.println(person);
        System.out.println(personClone);
    }
}

[浅いコピー VS 深いコピー]

Cloneable によってコピーされたオブジェクトは「浅いコピー」です。次のコードを確認してください。

class Money {
    public double _m;
}

class Person implements Cloneable {
    public int _id;
    public Money _money = new Money();

    // 重写克隆方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    // 重写打印方法

    @Override
    public String toString() {
        return "Person{" +
                "_id=" + _id +
                ", _money=" + _money +
                '}';
    }
}

public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person = new Person();
        person._money._m = 199;

        // 克隆
        Person personClone = (Person)person.clone();
        // 查看克隆结果
        System.out.println("person:" + person._money._m);
        System.out.println("personClone:" + personClone._money._m);
    }
}

// 打印结果:
person:199.0
personClone:199.0

【ビュー】

[ディープコピーの望ましい結果は、_money もコピーすることです]

class Money implements Cloneable {
    public double _m;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

class Person implements Cloneable {
    public int _id;
    public Money _money = new Money();

    // 重写克隆方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person temp = (Person)super.clone();
        temp._money = (Money)this._money.clone();
        return temp;
    }

    // 重写打印方法

    @Override
    public String toString() {
        return "Person{" +
                "_id=" + _id +
                ", _money=" + _money +
                '}';
    }
}

public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person = new Person();
        person._money._m = 199;

        // 克隆
        Person personClone = (Person)person.clone();
        personClone._money._m = 1000;
        // 查看克隆结果
        System.out.println("person:" + person._money._m);
        System.out.println("personClone:" + personClone._money._m);
    }
}

【ビュー】

【2.9】抽象クラスとインターフェースの違い

        抽象クラスとインターフェイスは、Java でポリモーフィズムを使用する一般的な方法です。両方とも習得する必要があります。同時に、この 2 つの違いを理解する必要があります (重要!!! よくある面接の質問)。

        主な違い: 抽象クラスには通常のメソッドと通常のフィールドを含めることができ、そのような通常のメソッドとフィールドはサブクラスで (書き換えることなく) 直接使用できますが、インターフェイスには通常のメソッドを含めることはできず、サブクラスはすべての抽象メソッドを書き換える必要があります。

        前に書いた Animal の例を見てみましょう。ここでの Animal には名前のような属性が含まれており、これはどのサブクラスにも存在します。したがって、ここでの Animal は抽象クラスとしてのみ使用でき、インターフェイスになるべきではありません。

class Animal {
	protected String name;
	public Animal(String name) {
		this.name = name;
	}
}

[もう一度注意してください] 抽象クラスの目的は、コンパイラがより適切に検証できるようにすることです。Animal のようなクラスは直接使用しませんが、そのサブクラスを使用します。Animal のインスタンスが誤って作成された場合、コンパイラは時間内に通知します。

【3】オブジェクトクラス

        オブジェクトは、Java がデフォルトで提供するクラスです。Object クラスを除く、Java のすべてのクラスには継承関係があります。デフォルトでは、Object 親クラスを継承します。つまり、Object のリファレンスを使用して、すべてのクラスのオブジェクトを受け取ることができます。

【例】全クラスのオブジェクトを受け取る場合はObjectを使用します。

class Person{}
class Student{}
public class Test {
    public static void main(String[] args) {
        function(new Person());
        function(new Student());
    }
    public static void function(Object obj) {
        System.out.println(obj);
    }
} 
//执行结果:
Person@1b6d3586
Student@4554617c

        したがって、開発においては、Object クラスが最も高度に統一されたパラメータのタイプとなります。ただし、Object クラスには明確に定義されたメソッドもいくつかあります。次のように:

オブジェクト クラス全体のすべてのメソッドを習得する必要があります。

このセクションでは、主に toString() メソッド、equals() メソッド、hashcode() メソッドについて説明します。

【3.1】オブジェクト情報をtoStringメソッドに出力する

オブジェクトの内容を出力したい場合は、Object クラスの toString() メソッドを直接オーバーライドできます。これについては前に説明したため、ここでは繰り返しません。

// Object类中的toString()方法实现:
public String toString() {
	return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

【3.2】オブジェクト比較イコールメソッド

Java では、== を比較する場合、 a. == の左辺と右辺が基本型変数の場合、変数内の値が同じかどうかを比較します b. == の左辺と右辺が同じであるかどうかを比較します。 = は参照型変数、比較は参照変数のアドレス、それらが同じかどうか c. オブジェクトの内容を比較したい場合は、equals メソッドは Object 内の equals メソッドを書き直す必要があります。デフォルトのアドレス:

[サンプルコードのデモ]

class Person {
    public String _name;
    public int _age;

    public Person(String name, int age) {
        this._name = name;
        this._age = age;
    }
}
class Student {
    public String _name;
    public int _age;

    public Student(String name, int age) {
        this._name = name;
        this._age = age;
    }
}

public class Test {
    public static void main(String[] args) {
        Person person1 = new Person("张三", 18);
        Person person2 = new Person("张三", 18);
        // 调用Object原本的equals()方法进行比较观察现象。
        System.out.println(person1.equals(person2));
        // 打印结果为false,为什么呢?
    }
}

equals() メソッドをオーバーライドする必要があります。

class Person {
    public String _name;
    public int _age;

    public Person(String name, int age) {
        this._name = name;
        this._age = age;
    }
	
    // 重写equals()方法
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false ;
        }

        if(this == obj) {
            return true ;
        }

        // 不是Person类对象
        if (!(obj instanceof Person)) {
            return false ;
        }

        Person per = (Person)obj;
        if(this._name.equals(per._name) && this._age == per._age) {
            return true;
        }

        return false;
    }
}
class Student {
    public String _name;
    public int _age;

    public Student(String name, int age) {
        this._name = name;
        this._age = age;
    }
}


public class Test {
    public static void main(String[] args) {
        Person person1 = new Person("张三", 18);
        Person person2 = new Person("张三", 18);
        System.out.println(person1.equals(person2));

    }
}

[概要]オブジェクトの内容が同じかどうかを比較する場合は、equals メソッドをオーバーライドする必要があります。

【3.3】ハッシュコード方式

先ほどの toString メソッドのソース コードを思い出してください。

public String toString() {
	return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

hashCode() メソッドは、データ構造に関係する特定のオブジェクトの位置        を計算するのに役立ちましたが、データ構造をまだ学習しておらず説明できないため、これは記憶であるとしか言えません。住所。次に、Integer.toHexString() メソッドを呼び出して、このアドレスを 16 進数で出力します。

【ハッシュコードメソッドのソースコード】

public native int hashCode();

        このメソッドはネイティブ メソッドであり、最下層は C/C++ コードで記述されます。私たちにはそれが見えません。同じ名前と同じ年齢を持つ 2 つのオブジェクトが同じ場所に保存されると考えられます。hashcode() メソッドをオーバーライドしない場合は、サンプル コードを確認できます。

class Person {
    public String _name;
    public int _age;

    public Person(String name, int age) {
        this._name = name;
        this._age = age;
    }
}
class Student {
    public String _name;
    public int _age;

    public Student(String name, int age) {
        this._name = name;
        this._age = age;
    }
}


public class Test {
    public static void main(String[] args) {
        Person person1 = new Person("张三", 18);
        Person person2 = new Person("张三", 18);
        System.out.println(person1.hashCode());
        System.out.println(person2.hashCode());

    }
}
// 打印结果:
2083562754
1239731077

【注意】 2つのオブジェクトのハッシュ値は異なります。

equals メソッドをオーバーライドするのと同様に、hashcode() メソッドをオーバーライドすることもできます。この点をもう一度見てみましょう。

class Person {
    public String _name;
    public int _age;

    public Person(String name, int age) {
        this._name = name;
        this._age = age;
    }
	
    // 重写hashCode()方法
    @Override
    public int hashCode() {
        return Objects.hash(_name, _age);
    }
}
class Student {
    public String _name;
    public int _age;

    public Student(String name, int age) {
        this._name = name;
        this._age = age;
    }
}


public class Test {
    public static void main(String[] args) {
        Person person1 = new Person("张三", 18);
        Person person2 = new Person("张三", 18);
        System.out.println(person1.hashCode());
        System.out.println(person2.hashCode());

    }
}
// 打印结果:
24022538
24022538

【注意】ハッシュ値は同じです。

【結論は】

  1. ハッシュコード方式は、オブジェクトがメモリ内の同じ場所に格納されているかどうかを判断するために使用されます。

  2. 実際、 hashCode() はハッシュ テーブルでのみ役立ち、他の状況では役に立ちません。ハッシュ テーブル内の hashCode() の機能は、オブジェクトのハッシュ コードを取得し、ハッシュ テーブル内のオブジェクトの位置を決定することです。

おすすめ

転載: blog.csdn.net/lx473774000/article/details/132726977
おすすめ