面向对象基础--中

116 阅读21分钟

Day8-第六章 面向对象基础--中

6.1 封装

6.1.1 封装概述

1、为什么需要封装?

  • 我要用洗衣机,只需要按一下开关和洗涤模式就可以了。有必要了解洗衣机内部的结构吗?有必要碰电动机吗?
  • 我们使用的电脑,内部有CPU、硬盘、键盘、鼠标等等,每一个部件通过某种连接方式一起工作,但是各个部件之间又是独立的。
  • 现实生活中,每一个个体与个体之间是有边界的,每一个团体与团体之间是有边界的,而同一个个体、团体内部的信息是互通的,只是对外有所隐瞒。

面向对象编程语言是对客观世界的模拟,客观世界里每一个事物的内部信息都是隐藏在对象内部的,外界无法直接操作和修改,只能通过指定的方式进行访问和修改。封装可以被认为是一个保护屏障,防止该类的代码和数据被其他类随意访问。适当的封装可以让代码更容易理解与维护,也加强了代码的安全性。

随着我们系统越来越复杂,类会越来越多,那么类之间的访问边界必须把握好,面向对象的开发原则要遵循“高内聚、低耦合”,而“高内聚,低耦合”的体现之一:

  • 高内聚:类的内部数据操作细节自己完成,不允许外部干涉;
  • 低耦合:仅对外暴露少量的方法用于使用

隐藏对象内部的复杂性,只对外公开简单和可控的访问方式,从而提高系统的可扩展性、可维护性。通俗的讲,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。

package com.atguigu.oop.encapsulation;
​
public class Circle {
    private double radius;
​
    public void setRadius(double radius) {
        if(radius < 0){
            return;
        }
        this.radius = radius;
    }
​
    public double getRadius() {
        return radius;
    }
}
​
package com.atguigu.oop.encapsulation;
​
public class TestCircle {
    public static void main(String[] args) {
        double r1 = -1.5;
        double r2 = 2.5;
​
        Circle c1 = new Circle();
        Circle c2 = new Circle();
//        c1.radius = r1;
//        c2.radius = r2;
​
        /*
        if(r1>0){
            c1.radius = r1;
        }
        if(r2 > 0){
            c2.radius = r2;
        }*/
​
        c1.setRadius(r1);
        c2.setRadius(r2);
​
        System.out.println(c1.getRadius());
        System.out.println(c2.getRadius());
    }
}
​

2、如何实现封装呢?

实现封装就是指控制类或成员的可见性范围?这就需要依赖访问控制修饰符,也称为权限修饰符来控制。

权限修饰符:public,protected,缺省,private

修饰符本类本包其他包子类其他包非子类
private×××
缺省××
protected×
public

外部类:public和缺省

成员变量、成员方法、构造器、成员内部类:public,protected,缺省,private

6.1.2 成员变量/属性私有化问题

成员变量(field)私有化之后,提供标准的get/set方法,我们把这种成员变量也称为属性(property)。

或者可以说只要能通过get/set操作的就是事物的属性,哪怕它没有对应的成员变量。

1、成员变量封装的目的

  • 隐藏类的实现细节
  • 让使用者只能通过事先预定的方法来访问数据,从而可以在该方法里面加入控制逻辑,限制对成员变量的不合理访问。还可以进行数据检查,从而有利于保证对象信息的完整性。
  • 便于修改,提高代码的可维护性。主要说的是隐藏的部分,在内部修改了,如果其对外可以的访问方式不变的话,外部根本感觉不到它的修改。例如:Java8->Java9,String从char[]转为byte[]内部实现,而对外的方法不变,我们使用者根本感觉不到它内部的修改。

2、实现步骤

  1. 使用 private 修饰成员变量
private 数据类型 变量名 ;

代码如下:

public class Person {
    private String name;
    private int age;
    private boolean marry;
}
  1. 提供 getXxx方法 / setXxx 方法,可以访问成员变量,代码如下:
public class Person {
    private String name;
    private int age;
    private boolean marry;
​
    public void setName(String name) {
        this.name = name;
    }
​
    public String getName() {
        return name;
    }
​
    public void setAge(int age) {
        this.age = age;
    }
​
    public int getAge() {
        return age;
    }
    
    public void setMarry(boolean marry){
        this.marry = marry;
    }
    
    public boolean isMarry(){
        return marry;
    }
}

3、测试


​
public class TestPerson {
    public static void main(String[] args) {
        Person p = new Person();
​
        //实例变量私有化,跨类是无法直接使用的
/*        p.name = "张三";
        p.age = 23;
        p.marry = true;*/
​
        p.setName("张三");
        System.out.println("p.name = " + p.getName());
​
        p.setAge(23);
        System.out.println("p.age = " + p.getAge());
​
        p.setMarry(true);
        System.out.println("p.marry = " + p.isMarry());
    }
}

3、IDEA自动生成get/set方法模板

  • 大部分键盘模式按Alt + Insert键。
  • 部分键盘模式需要按Alt + Insert + Fn键。
  • Mac电脑快捷键需要单独设置

image.png

image.png

6.2 构造器

我们发现我们new完对象时,所有成员变量都是默认值,如果我们需要赋别的值,需要挨个为它们再赋值,太麻烦了。我们能不能在new对象时,直接为当前对象的某个或所有成员变量直接赋值呢。

可以,Java给我们提供了构造器(Constructor)。

6.2.1 构造器的作用

new对象,并在new对象的时候为实例变量赋值。

6.2.2 声明构造器

构造器又称为构造方法,那是因为它长的很像方法。但是和方法还是有所区别的。

1、构造器语法格式

【修饰符】 class 类名{
    【修饰符】 构造器名(){
        // 实例初始化代码
    }
    【修饰符】 构造器名(参数列表){
        // 实例初始化代码
    }
}

注意事项:

  1. 构造器名必须与它所在的类名必须相同。
  2. 它没有返回值,所以不需要返回值类型,甚至不需要void
  3. 如果你不提供构造器,系统会给出无参数构造器,并且该构造器的修饰符默认与类的修饰符相同
  4. 如果你提供了构造器,系统将不再提供无参数构造器,除非你自己定义。
  5. 构造器是可以重载的,既可以定义参数,也可以不定义参数。
  6. 构造器的修饰符只能是权限修饰符,不能被其他任何修饰

代码如下:

public class Student {
    private String name;
    private int age;
​
    // 无参构造
    public Student() {}
​
    // 有参构造
    public Student(String name,int age) {
        this.name = name;
        this.age = age;
    }
​
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
​
    public String getInfo(){
        return "姓名:" + name +",年龄:" + age;
    }
}
​

2、生成构造器代码:Alt + Insert

image.png

image.png

6.2.3 使用构造器创建对象

1、使用new调用构造器


public class TestStudent {
    public static void main(String[] args) {
        //调用无参构造创建学生对象
        Student s1 = new Student();
​
        //调用有参构造创建学生对象
        Student s2 = new Student("张三",23);
​
        System.out.println(s1.getInfo());
        System.out.println(s2.getInfo());
    }
}

2、IDEA查看构造器和方法形参列表快捷键:Ctrl + P

image.png

6.2.4 同一个类中的构造器互相调用

  • this():调用本类的无参构造
  • this(实参列表):调用本类的有参构造
  • this()和this(实参列表)只能出现在构造器首行
  • 不能出现递归调用
public class Student {
    private String name;
    private int age;
​
    // 无参构造
    public Student() {
//        this("",18);//调用本类有参构造
    }
​
    // 有参构造
    public Student(String name,int age) {
        this();//调用本类无参构造
        this.name = name;
        this.age = age;
    }
​
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
​
    public String getInfo(){
        return "姓名:" + name +",年龄:" + age;
    }
}

6.2.5 标准JavaBean

JavaBean 是 Java语言编写类的一种标准规范。符合JavaBean 的类,要求:

(1)类必须是具体的和公共的,

(2)并且具有无参数的构造方法,

(3)成员变量私有化,并提供用来操作成员变量的setget 方法。

(4)重写toString方法(请看下一个小节)

public class ClassName{
  //成员变量
    
  //构造方法
    //无参构造方法【必须】
    //有参构造方法【建议】
    
  //getXxx()
  //setXxx()
  //其他成员方法
}

编写符合JavaBean 规范的类,以学生类为例,标准代码如下:

public class Student {
    // 成员变量
    private String name;
    private int age;
​
    // 构造方法
    public Student() {
    }
​
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
​
    // get/set成员方法
    public void setName(String name) {
        this.name = name;
    }
​
    public String getName() {
        return name;
    }
​
    public void setAge(int age) {
        this.age = age;
    }
​
    public int getAge() {
        return age;
    }
    
    //其他成员方法列表
    public String getInfo(){
        return "姓名:" + name + ",年龄:" + age;
    }
}

测试类,代码如下:

public class TestStudent {
    public static void main(String[] args) {
        // 无参构造使用
        Student s = new Student();
        s.setName("柳岩");
        s.setAge(18);
        System.out.println(s.getName() + "---" + s.getAge());
        System.out.println(s.getInfo());
​
        // 带参构造使用
        Student s2 = new Student("赵丽颖", 18);
        System.out.println(s2.getName() + "---" + s2.getAge());
        System.out.println(s2.getInfo());
    }
}

6.3 继承

6.3.1 继承的概述

生活中的继承

  • 财产:富二代

  • 样貌:如图所示:

继承3.jpg

  • 才华:如图所示:

继承4.jpg

继承有延续(下一代延续上一代的基因、财富)、扩展(下一代和上一代又有所不同)的意思。

社会的进步就是源于知识、财富、经验得以继承,又可以不断的翻新。

Java中的继承

如图所示:

继承4.jpg

多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类中无需再定义这些属性和行为,只需要和抽取出来的类构成某种关系。如图所示:

猫狗继承2.jpg

其中,多个类可以称为子类,也叫派生类;多个类抽取出来的这个类称为父类超类(superclass) 或者基类

继承描述的是事物之间的所属关系,这种关系是:is-a 的关系。例如,图中猫属于动物,狗也属于动物。可见,父类更通用或更一般,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系。

继承的好处

  • 提高代码的复用性
  • 提高代码的扩展性
  • 表示类与类之间的is-a关系

6.3.2 继承的语法格式

通过 extends 关键字,可以声明一个子类继承另外一个父类,定义格式如下:

【修饰符】 class 父类 {
    ...
}
​
【修饰符】 class 子类 extends 父类 {
    ...
}
​

1、父类

/*
 * 定义动物类Animal,做为父类
 */
public class Animal {
    // 定义name属性
    String name;
    // 定义age属性
    int age;
​
    // 定义动物的吃东西方法
    public void eat() {
        System.out.println(age + "岁的"
                + name + "在吃东西");
    }
}
​

2、子类

/*
 * 定义猫类Cat 继承 动物类Animal
 */
public class Cat extends Animal {
    int count;//记录每只猫抓的老鼠数量
​
    // 定义一个猫抓老鼠的方法catchMouse
    public void catchMouse() {
        count++;
        System.out.println("抓老鼠,已经抓了"
                + count + "只老鼠");
    }
}

3、测试类

public class TestCat {
    public static void main(String[] args) {
        // 创建一个猫类对象
        Cat cat = new Cat();
        // 为该猫类对象的name属性进行赋值
        cat.name = "Tom";
        // 为该猫类对象的age属性进行赋值
        cat.age = 2;
        // 调用该猫继承来的eat()方法
        cat.eat();
        // 调用该猫的catchMouse()方法
        cat.catchMouse();
        cat.catchMouse();
        cat.catchMouse();
        
        //调用该猫的eat()方法
        cat.eat();
    }
}

6.3.3 IDEA中如何查看继承关系

1.子类和父类是一种相对的概念

例如:B类对于A来说是子类,但是对于C类来说是父类

2.查看继承关系快捷键

例如:选择A类名,按Ctrl + H就会显示A类的继承树。

:A类的父类和子类

image.png image-20211230090719430():A类的父类

image.png image-20211230090732532():A类的所有子类

image.png 例如:在类继承目录树中选中某个类,比如C类,按Ctrl+ Alt+U就会用图形化方式显示C类的继承祖宗

image.png

6.3.4 继承的特点

1.每一个类有一个默认的父类java.lang.Object类,它也是所有类的根父类

image.png

2.子类会继承父类所有的实例变量和实例方法

从类的定义来看,类是一类具有相同特性的事物的抽象描述。父类是所有子类共同特征的抽象描述。而实例变量和实例方法就是事物的特征,那么父类中声明的实例变量和实例方法代表子类事物也有这个特征。

  • 当子类对象被创建时,在堆中给对象申请内存时,就要看子类和父类都声明了什么实例变量,这些实例变量都要分配内存。
  • 当子类对象调用方法时,编译器会先在子类模板中看该类是否有这个方法,如果没找到,会看它的父类甚至父类的父类是否声明了这个方法,遵循从下往上找的顺序,找到了就停止,一直到根父类都没有找到,就会报编译错误。

3.Java只支持单继承,不支持多重继承

public class A{}
class B extends A{}
​
//一个类只能有一个父类,不可以有多个直接父类。
class C extends B{}     //ok
class C extends A,B...  //error

4.Java支持多层继承(继承体系)

class A{}
class B extends A{}
class C extends B{}

顶层父类是Object类。所有的类默认继承Object,作为父类。

5.一个父类可以同时拥有多个子类

class A{}
class B extends A{}
class D extends A{}
class E extends A{}

6.3.5 方法重写(Override)

我们说父类的所有方法子类都会继承,但是当某个方法被继承到子类之后,子类觉得父类原来的实现不适合于子类,该怎么办呢?我们可以进行方法重写 (Override)

1、方法重写

public class Person {
    private String name;
    private int age;
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
​
    public int getAge() {
        return age;
    }
​
    public void setAge(int age) {
        this.age = age;
    }
​
    public String getInfo(){
        return "姓名:" + name + ",年龄:" + age;
    }
}
​
public class Student extends Person {
    private int score;
​
    public int getScore() {
        return score;
    }
​
    public void setScore(int score) {
        this.score = score;
    }
​
    //方法的重写
    public String getInfo(){
//        return "姓名:" + name + ",年龄:" + age;
        //在子类中不能直接使用父类私有的name和age
        return "姓名:" + getName() + ",年龄:" + getAge() + ",成绩:" + score;
    }
}
​
public class TestStudent {
    public static void main(String[] args) {
        Student student = new Student();
​
        student.setName("张三");
        student.setAge(23);
        student.setScore(89);
​
        System.out.println(student.getInfo());
    }
}
​

2、在子类中如何调用父类被重写的方法

在子类中可以通过super关键字调用父类被重写的方法

super.被重写方法(【实参列表】)

示例代码:

public class Student extends Person {
    private int score;
​
    public int getScore() {
        return score;
    }
​
    public void setScore(int score) {
        this.score = score;
    }
​
    //方法的重写
    public String getInfo(){
//        return "姓名:" + name + ",年龄:" + age;
        //在子类中不能直接使用父类私有的name和age
//        return "姓名:" + getName() + ",年龄:" + getAge() + ",成绩:" + score;
        return super.getInfo() + ",成绩:" + score;
    }
}
​

3、IDEA重写方法快捷键:Ctrl + O

image.png


public class Student extends Person {
    private int score;
​
    public int getScore() {
        return score;
    }
​
    public void setScore(int score) {
        this.score = score;
    }
​
    @Override
    public String getInfo() {
        return super.getInfo() +",成绩:" + score;
    }
}

@Override:写在方法上面,用来检测是不是满足重写方法的要求。这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。建议保留,这样编译器可以帮助我们检查格式,另外也可以让阅读源代码的程序员清晰的知道这是一个重写的方法。

4、重写方法的要求

1.必须保证父子类之间重写方法的名称相同。

2.必须保证父子类之间重写方法的参数列表也完全相同。

2.子类方法的返回值类型必须【小于等于】父类方法的返回值类型(小于其实就是是它的子类,例如:Student < Person)。

注意:如果返回值类型是基本数据类型和void,那么必须是相同

3.子类方法的权限必须【大于等于】父类方法的权限修饰符。

注意:public > protected > 缺省 > private

父类私有方法不能重写

跨包的父类缺省的方法也不能重写

5、方法的重载和方法的重写

方法的重载:方法名相同,形参列表不同。不看返回值类型。

方法的重写:见上面。

(1)同一个类中

public class TestOverload {
    public int max(int a, int b){
        return a > b ? a : b;
    }
    public double max(double a, double b){
        return a > b ? a : b;
    }
    public int max(int a, int b,int c){
        return max(max(a,b),c);
    }
}

(2)父子类中

package com.atguigu.inherited.method;
​
public class TestOverloadOverride {
    public static void main(String[] args) {
        Son s = new Son();
        s.method(1);//只有一个形式的method方法
​
        Daughter d = new Daughter();
        d.method(1);
        d.method(1,2);//有两个形式的method方法
    }
}
​
class Father{
    public void method(int i){
        System.out.println("Father.method");
    }
}
class Son extends Father{
    public void method(int i){//重写
        System.out.println("Son.method");
    }
}
class Daughter extends Father{
    public void method(int i,int j){//重载
        System.out.println("Daughter.method");
    }
}

6.3.6 Object根父类

1、Object根父类

API(Application Programming Interface) ,应用程序编程接口。Java API是一本程序员的字典 ,是JDK中提供给我们使用的类的说明文档。所以我们可以通过查询API的方式,来学习Java提供的类,并得知如何使用它们。在API文档中是无法得知这些类具体是如何实现的,如果要查看具体实现代码,那么我们需要查看src源码

java.lang.Object是类层次结构的根类,即所有类的父类。每个类都使用 Object 作为超类。所有对象(包括数组)都实现这个类的方法。

  • 如果一个类没有特别指定父类,那么默认则继承自Object类。例如:
public class MyClass /*extends Object*/ {
    // ...
}
  • 所有对象(包括数组)都实现这个类的方法。而且很多方法子类都会重写,通过子类对象调用方法后执行的是重写后的方法。

2、重写toString

java.lang.Object根父类中有一个public String toString()方法,在Java中,当我们使用字符串与一个对象进行“+”时,或者使用System.out.println或print方法输出一个对象时,都会自动调用对象的toString方法。

Object中实现的toString方法,默认返回” 对象的类型 @ 对象hashCode值的十六进制形式“的信息表达式,而这个信息表达式对于绝大部分场景来说是没有用的,建议子类重写toString方法,结果应该时一个简明但易于读懂的信息表达式。

如何重写toString方法呢?

(1)如果某个类的直接父类是Object,或它的直接父类不是Object,但是父类并没有重写过toString,那么建议使用快捷键:Alt + Insert,然后选择toString

image.png

image.png

image.png

public class Person {
    private String name;
    private int age;
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
​
    public int getAge() {
        return age;
    }
​
    public void setAge(int age) {
        this.age = age;
    }
​
    public String getInfo(){
        return "姓名:" + name + ",年龄:" + age;
    }
​
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + ''' +
                ", age=" + age +
                '}';
    }
}
​

image.png

(2)如果父类已经重写过toString,子类想要基于父类的toString方法进一步重写,那么可以继续使用快捷键Ctrl + O

public class Student extends Person {
    private int score;
​
    public int getScore() {
        return score;
    }
​
    public void setScore(int score) {
        this.score = score;
    }
​
    @Override
    public String getInfo() {
        return super.getInfo() +",成绩:" + score;
    }
​
    @Override
    public String toString() {
        return super.toString() + ",成绩" + score;
    }
}
​
public class TestStudent {
    public static void main(String[] args) {
        Student student = new Student();
​
        student.setName("张三");
        student.setAge(23);
        student.setScore(89);
​
        System.out.println(student.getInfo());
        System.out.println(student);//打印对象时,会自动调用对象的toString方法
    }
}

6.3.7 权限修饰符限制问题

权限修饰符:public,protected,缺省,private

修饰符本类本包(包含子类和非子类)其他包子类其他包非子类
private×××
缺省√(本包子类非子类都可见)××
protected√(本包子类非子类都可见)√(其他包仅限于子类中可见,直接使用方式)×
public

外部类:public和缺省

成员变量、成员方法等:public,protected,缺省,private

1、外部类要跨包使用必须是public,否则仅限于本包使用

(1)外部类的权限修饰符如果缺省,本包使用没问题

(2)外部类的权限修饰符如果缺省,跨包使用有问题

2、父类成员变量私有化(private)

子类虽会继承父类私有(private)的成员变量,但子类不能对继承的私有成员变量直接进行访问,可通过继承的get/set方法进行访问。如图所示:

继承私有成员1.jpg

父类代码:

public class Person {
    private String name;
    private int age;
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
​
    public int getAge() {
        return age;
    }
​
    public void setAge(int age) {
        this.age = age;
    }
​
    public String getInfo(){
        return "姓名:" + name + ",年龄:" + age;
    }
}
​

子类代码:


public class Student extends Person {
    private int score;
​
    public int getScore() {
        return score;
    }
​
    public void setScore(int score) {
        this.score = score;
    }
​
    public String getInfo(){
//        return "姓名:" + name + ",年龄:" + age;
        //在子类中不能直接使用父类私有的name和age
        return "姓名:" + getName() + ",年龄:" + getAge();
    }
}
​

测试类代码:

public class TestStudent {
    public static void main(String[] args) {
        Student student = new Student();
​
        student.setName("张三");
        student.setAge(23);
        student.setScore(89);
​
        System.out.println(student.getInfo());
    }
}

IDEA在Debug模式下查看学生对象信息:

3、成员的权限修饰符问题

(1)本包下使用:成员的权限修饰符可以是public、protected、缺省

(2)跨包使用时,如果类的权限修饰符缺省,成员权限修饰符>类的权限修饰符也没有意义

(3)跨包下使用:要求严格

注意:跨包时父类中protected修饰的成员,仅限于在子类中访问,且是子类对象自己访问。

6.3.8 继承时构造器问题

子类继承父类时,不会继承父类的构造器。必须通过super()或super(实参列表)的方式调用父类的构造器。

  • super();:子类构造器中一定会调用父类的构造器,默认调用父类的无参构造,super();可以省略。
  • super(实参列表);:如果父类没有无参构造或者有无参构造但是子类就是想要调用父类的有参构造,则必须使用super(实参列表);的语句。
  • super()和super(实参列表)都只能出现在子类构造器的首行。
public class Employee {
    private String name;
    private int age;
    private double salary;
​
    public Employee() {
        System.out.println("父类Employee无参构造");
    }
​
    public Employee(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
        System.out.println("父类Employee有参构造");
    }
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
​
    public int getAge() {
        return age;
    }
​
    public void setAge(int age) {
        this.age = age;
    }
​
    public double getSalary() {
        return salary;
    }
​
    public void setSalary(double salary) {
        this.salary = salary;
    }
​
    public String getInfo(){
        return "姓名:" + name + ",年龄:" + age +",薪资:" + salary;
    }
}
​
public class Manager extends Employee{
    private double bonusRate;
​
    public Manager() {
        super();//可以省略
    }
​
    public Manager(String name, int age, double salary, double bonusRate) {
        super(name, age, salary);//调用父类的有参构造
        this.bonusRate = bonusRate;
    }
​
    public double getBonusRate() {
        return bonusRate;
    }
​
    public void setBonusRate(double bonusRate) {
        this.bonusRate = bonusRate;
    }
​
    @Override
    public String getInfo() {
        return super.getInfo() +",奖金比例:" + bonusRate;
    }
}
​

image.png

package com.atguigu.constructor;
​
public class TestEmployee {
    public static void main(String[] args) {
        Manager m1 = new Manager();
        System.out.println(m1.getInfo());
​
        Manager m2 = new Manager("张三",23,20000,0.1);
        System.out.println(m2.getInfo());
    }
}
​

形式一:

class A{
​
}
class B extends A{
​
}
​
class Test{
    public static void main(String[] args){
        B b = new B();
        //A类和B类都是默认有一个无参构造,B类的默认无参构造中还会默认调用A类的默认无参构造
        //但是因为都是默认的,没有打印语句,看不出来
    }
}

形式二:

class A{
    A(){
        System.out.println("A类无参构造器");
    }
}
class B extends A{
​
}
class Test{
    public static void main(String[] args){
        B b = new B();
        //A类显示声明一个无参构造,
        //B类默认有一个无参构造,
        //B类的默认无参构造中会默认调用A类的无参构造
        //可以看到会输出“A类无参构造器"
    }
}

形式三:

class A{
    A(){
        System.out.println("A类无参构造器");
    }
}
class B extends A{
    B(){
        System.out.println("B类无参构造器");
    }
}
class Test{
    public static void main(String[] args){
        B b = new B();
        //A类显示声明一个无参构造,
        //B类显示声明一个无参构造,        
        //B类的无参构造中虽然没有写super(),但是仍然会默认调用A类的无参构造
        //可以看到会输出“A类无参构造器"和"B类无参构造器")
    }
}

形式四:

class A{
    A(){
        System.out.println("A类无参构造器");
    }
}
class B extends A{
    B(){
        super();
        System.out.println("B类无参构造器");
    }
}
class Test{
    public static void main(String[] args){
        B b = new B();
        //A类显示声明一个无参构造,
        //B类显示声明一个无参构造,        
        //B类的无参构造中明确写了super(),表示调用A类的无参构造
        //可以看到会输出“A类无参构造器"和"B类无参构造器")
    }
}

形式五:

class A{
    A(int a){
        System.out.println("A类有参构造器");
    }
}
class B extends A{
    B(){
        System.out.println("B类无参构造器");
    }
}
class Test05{
    public static void main(String[] args){
        B b = new B();
        //A类显示声明一个有参构造,没有写无参构造,那么A类就没有无参构造了
        //B类显示声明一个无参构造,        
        //B类的无参构造没有写super(...),表示默认调用A类的无参构造
        //编译报错,因为A类没有无参构造
    }
}

形式六:

class A{
    A(int a){
        System.out.println("A类有参构造器");
    }
}
class B extends A{
    B(){
        super();
        System.out.println("B类无参构造器");
    }
}
class Test06{
    public static void main(String[] args){
        B b = new B();
        //A类显示声明一个有参构造,没有写无参构造,那么A类就没有无参构造了
        //B类显示声明一个无参构造,        
        //B类的无参构造明确写super(),表示调用A类的无参构造
        //编译报错,因为A类没有无参构造
    }
}

形式七:

class A{
    A(int a){
        System.out.println("A类有参构造器");
    }
}
class B extends A{
    B(int a){
        super(a);
        System.out.println("B类有参构造器");
    }
}
class Test07{
    public static void main(String[] args){
        B b = new B(10);
        //A类显示声明一个有参构造,没有写无参构造,那么A类就没有无参构造了
        //B类显示声明一个有参构造,        
        //B类的有参构造明确写super(a),表示调用A类的有参构造
        //会打印“A类有参构造器"和"B类有参构造器"
    }
}

形式八:

class A{
    A(){
        System.out.println("A类无参构造器");
    }
    A(int a){
        System.out.println("A类有参构造器");
    }
}
class B extends A{
    B(){
        super();//可以省略,调用父类的无参构造
        System.out.println("B类无参构造器");
    }
    B(int a){
        super(a);//调用父类有参构造
        System.out.println("B类有参构造器");
    }
}
class Test8{
    public static void main(String[] args){
        B b1 = new B();
        B b2 = new B(10);
    }
}