一、对象数组

1、对象类型数组概念

对象数组是指对象类型的元素组成的数组,与基本类型数组不同的是对象数组可以存储对象的引用

对象数组开辟了三块内存空间,比基本数据类型数组多开辟了一块。

对象数组

/**
 * @document: 对象数组
 * @Author:SmallG
 * @CreateTime:2023/7/26+9:27
 */

public class Demo01 {
    public static void main(String[] args) {
        //创建学生对象的数组
        Student[] students = new Student[3];
        students[0] = new Student("SmallG", 20, '男', 100);
        students[1] = new Student("SmallY", 22, '女', 100);
        students[2] = new Student("SmallZ", 21, '男', 100);
        for (int i = 0; i < students.length; i++) {
            Student student = students[i];
            System.out.println("姓名:" + student.name);
            System.out.println("年龄:" + student.age);
            System.out.println("性别:" + student.gender);
            System.out.println("成绩:" + student.score);
            System.out.println();
        }
    }
}

2、对象初始化数组

数组中的默认值是null,在初始化数组的时候,每个元素开辟了两块内存,栈内存存储对象在堆内存中的首地址,未初始化之前堆内存中存储全是null

3、静态初始化

可以直接对每个对象赋值

Student[] students = {new Student("Alice"), new Student("Bob"), new Student("Carol")};

二、继承

1、利用泛化设计父类

泛化是面向对象编程中的一种关系,它描述了一般性和特殊性之间的关系,通过泛化可以识别出多个类之间共享的属性和方法,并将他们提取到一个公共的父类当中,好处是提高了代码的复用性。

泛化的步骤:

  • 识别共享的属性和方法,找出共有的属性和方法
  • 设计父类:将共享的属性和方法定义在父类中,父类应该抽象和通用化,不涉及具体的业务逻辑
  • 子类继承:可以继承父类中的属性和方法

父类:

/**
 * @document: teacher和student的父类
 * @Author:SmallG
 * @CreateTime:2023/7/26+10:10
 */

public class Person {
    String name;
    int age;
    char gender;

    public Person() {
    }

    public Person(String name, int age, char gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }
}

子类:

/**
 * @document: 学生类
 * @Author:SmallG
 * @CreateTime:2023/7/26+9:28
 */

public class Student extends Person{

    double score;

    //无参构造器 默认构造器
    public Student() {
    }

    //包含所有属性的构造器
    public Student(String name, int age, char gender, double score) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.score = score;
    }
}


/**
 * @document: 老师类
 * @Author:SmallG
 * @CreateTime:2023/7/26+10:07
 */

public class Teacher extends Person {

    double salary;

    public Teacher() {
    }

    public Teacher(String name, int age, char gender, double salary) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.salary = salary;
    }
}

继承的特性:单继承:java不支持多继承,一个类只能有一个父类,一个父类可以有多个子类。

2、继承的传递性

/**
 * @document: 继承的传递性
 * @Author:SmallG
 * @CreateTime:2023/7/26+10:28
 */

public class Demo03 {
    public static void main(String[] args) {
        //体现传递性
        Coo coo = new Coo();
        System.out.println(coo.c);
        System.out.println(coo.b);
        System.out.println(coo.a);
    }
}

class Aoo {
    int a;
}

class Boo extends Aoo {//继承自Aoo 拥有两个属性a和b
    int b;
}

class Coo extends Boo {//继承自Boo 拥有三个属性 a b c
    int c;
}

3、继承中的构造器

子类可以重用构造器,构造器不能继承。

子类用supper调用父类的构造器,super()是默认调用父类的默认构造器。(super必须写在第一行)

子类先调用自己的构造器,构造器的第一行默认调用执行父类的构造器,然后执行自己的构造器。

super用于构造器中或在继承中的方法重写中

父类:

/**
 * @document: teacher和student的父类
 * @Author:SmallG
 * @CreateTime:2023/7/26+10:10
 */

public class Person {
    String name;
    int age;
    char gender;

    public Person() {
    }

    public Person(String name, int age, char gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }
}

子类:

/**
 * @document: 学生类
 * @Author:SmallG
 * @CreateTime:2023/7/26+9:28
 */

public class Student extends Person{

    double score;

    //无参构造器 默认构造器
    public Student() {
    }

    //包含所有属性的构造器
    public Student(String name, int age, char gender, double score) {
        super(name,age,gender); //用super调用父类的构造器
        this.score = score;
    }
}


/**
 * @document: 老师类
 * @Author:SmallG
 * @CreateTime:2023/7/26+10:07
 */

public class Teacher extends Person {

    double salary;

    public Teacher() {
    }

    public Teacher(String name, int age, char gender, double salary) {
        super(name,age,gender); //用super调用父类的构造器
        this.salary = salary;
    }
}

4、方法的继承

父类:

/**
 * @document: teacher和student的父类
 * @Author:SmallG
 * @CreateTime:2023/7/26+10:10
 */

public class Person {
    String name;
    int age;
    char gender;

    public Person() {
        System.out.println("Person类的构造器");
    }

    public Person(String name, int age, char gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    //自我介绍
    public void printInfo() {
        System.out.println("姓名: " + name + " 年龄: " + age + " 性别: " + gender);
    }
}

子类:

/**
 * @document: 学生类
 * @Author:SmallG
 * @CreateTime:2023/7/26+9:28
 */

public class Student extends Person{

    double score;

    //无参构造器 默认构造器
    public Student() {
        System.out.println("Student 的构造器");
    }

    //包含所有属性的构造器
    public Student(String name, int age, char gender, double score) {
        super(name,age,gender);
        this.score = score;
    }

    //复用父类中的方法

    @Override
    public void printInfo() {
        super.printInfo(); //继承父类的方法
    }
}


/**
 * @document: 老师类
 * @Author:SmallG
 * @CreateTime:2023/7/26+10:07
 */

public class Teacher extends Person {

    double salary;

    public Teacher() {
    }

    public Teacher(String name, int age, char gender, double salary) {
        super(name, age, gender); //用super调用父类的构造器
        this.salary = salary;
    }

    @Override
    public void printInfo() {
        super.printInfo(); //继承父类的方法
    }
}

5、方法的重写

父类的方法不能满足子类的需求,子类可以对方法进行重写,重写时方法名与父类方法名保持一致。

父类:

/**
 * @document: teacher和student的父类
 * @Author:SmallG
 * @CreateTime:2023/7/26+10:10
 */

public class Person {
    String name;
    int age;
    char gender;

    public Person() {
    }

    public Person(String name, int age, char gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }
    public void eat() {
        System.out.println("吃饭");
    }
}

子类:

/**
 * @document: 学生类
 * @Author:SmallG
 * @CreateTime:2023/7/26+9:28
 */

public class Student extends Person{

    double score;

    //无参构造器 默认构造器
    public Student() {
    }

    //包含所有属性的构造器
    public Student(String name, int age, char gender, double score) {
        super(name,age,gender);
        this.score = score;
    }

    //重写父类的eat()方法
    public void eat(){
        super.eat(); //调用父类的eat方法,实现在父类的方法的基础之上扩展 而不是完全覆盖
        System.out.println("学生在学生餐厅吃饭");
    }
}


/**
 * @document: 老师类
 * @Author:SmallG
 * @CreateTime:2023/7/26+10:07
 */

public class Teacher extends Person {

    double salary;

    public Teacher() {
    }

    public Teacher(String name, int age, char gender, double salary) {
        super(name, age, gender); //用super调用父类的构造器
        this.salary = salary;
    }

    //重写父类的方法
    public void eat(){
        super.eat(); //调用父类的eat方法,实现在父类的方法的基础之上扩展 而不是完全覆盖
        System.out.println("教师去教师餐厅吃饭");
    }
}

6、**经典面试题:重写与重载的区别

重写(Override)和重载(Overload)是面向对象编程中的两个重要概念,常常在面试中被问及。它们有以下几个区别:

  • 定义:重写是指在子类中重新定义父类的方法,方法名、参数列表和返回类型都相同。重载是在同一个类中定义多个方法,方法名相同但参数列表不同。
  • 关联:重写涉及继承关系,即子类继承父类的方法并对其进行重新定义。重载是同一个类中的方法之间的关系,通过方法的参数列表的差异来区分。
  • 方法签名:重写要求子类方法与父类方法具有相同的方法签名,包括方法名、参数列表和返回类型。重载要求方法名相同,但参数列表必须不同(个数、类型、顺序)。
  • 功能:重写用于在子类中重新定义父类的方法,可以根据子类的需要实现不同的功能。重载用于处理同一个类中不同的输入情况,通过参数的差异来选择不同的方法。
  • 编译时决定:重写是在运行时动态绑定的,即根据对象的实际类型来确定调用的方法。重载是在编译时静态绑定的,根据传入的参数类型和个数来选择合适的方法。

总结起来,重写和重载的区别在于定义位置、关联性、方法签名、功能和编译时决定。重写用于子类对父类方法的重新定义,重载用于同一个类中多个方法的差异处理。

三、访问控制

1、包

一旦使用package指定了包名,则类的全称应该是“包名.类名”,一般在起名时将网址倒过来命名。

语法要点:

  • 必须在java源文件的第一行使用package声明包,package语句只能有一行
  • package可以不写,不写表示使用默认包。
  • package相同的类放置在同一个包中
  • 包名要符合Java命名规范
  • 一个包中可以定义多个类,但是类名不能同名
  • 包编译以后变成对应的文件夹

2、访问控制修饰符

4个访问修饰词

  • public:共有的,修饰类、修饰属性、修饰方法、修饰构造器
  • protected:受保护的,修饰成员变量:属性、方法、构造器
  • 默认的:不写任何修饰词
  • private:私有的

访问控制修饰符

public与private:

package day02.demo04;

/**
 * @document:
 * @Author:SmallG
 * @CreateTime:2023/7/26+16:24
 */

public class Goo {
    private int a = 5;
    public int b = 6;

    //类的内部都可以访问
    public int add() {
        //属性a是私有的,在类的内部是可以访问的
        return a + b;
    }
}


package day02.demo05;

import day02.demo04.Goo;

/**
 * @document:
 * @Author:SmallG
 * @CreateTime:2023/7/26+16:27
 */

public class Demo {
    public static void main(String[] args) {
        //创建Goo对象
        Goo goo = new Goo();
        //访问Goo的属性
        //System.out.println(goo.a);  //报错:a是私有的 不能访问 类的外部不可见

        System.out.println(goo.b); //公有属性 任何位置都可以访问
        System.out.println(goo.add()); //公有方法 可以访问
    }
}

public与默认:

package day02.demo06;

/**
 * @document:
 * @Author:SmallG
 * @CreateTime:2023/7/26+16:34
 */

public class Hoo {
    int a = 10;
    public int b = 20;
}


package day02.demo06;


/**
 * @document: 同一个包下访问公有和默认的属性
 * @Author:SmallG
 * @CreateTime:2023/7/26+16:36
 */

public class Demo {
    public static void main(String[] args) {
        Hoo hoo = new Hoo();
        //同一个包下可以访问默认的属性
        System.out.println(hoo.a);
        System.out.println(hoo.b);
    }
}

protected和默认:

package day02.demo07;

/**
 * @document:
 * @Author:SmallG
 * @CreateTime:2023/7/26+16:47
 */

public class Joo {
    protected int a = 6; //受保护的属性 子类可以继承
    int b = 7; //默认

}

package day02.demo07;

/**
 * @document: 同一个包中访问受保护和默认属性
 * @Author:SmallG
 * @CreateTime:2023/7/26+16:50
 */

public class Demo {
    public static void main(String[] args) {
        Joo joo = new Joo();
        System.out.println(joo.b);
        System.out.println(joo.a);
    }
}


package day02.demo09;

import day02.demo07.Joo;

/**
 * @document: 不同的包下 子类访问受保护和默认属性
 * @Author:SmallG
 * @CreateTime:2023/7/26+16:55
 */

public class Koo extends Joo {
    public static void main(String[] args) {
        Koo koo =new Koo();
        //受保护的属性可以被任意位置的子类继承
        System.out.println(koo.a);
        //默认属性不同的包下的类不可以继承
        System.out.println(koo.b); //报错
    }
}

3、封装

将抽象性函数接口的实现细节部分包装,隐藏起来。(高内聚,低耦合)封装可以实现高内聚

/**
 * @document: 属性私有化
 * @Author:SmallG
 * @CreateTime:2023/7/26+17:21
 */

public class Person {
    private String name;
    private int age;
    private char gender;

    //为每一个私有属性提供一个该属性的方法和一个设置该属性的方法

    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 char getGender() {
        return gender;
    }

    public void setGender(char gender) {
        this.gender = gender;
    }
}



/**
 * @document: 测试类
 * @Author:SmallG
 * @CreateTime:2023/7/26+17:34
 */

public class Demo01 {
    public static void main(String[] args) {
        Person person = new Person();
        person.setName("Tom");
        person.setAge(20);
        person.setGender('男');

        System.out.println("姓名:" + person.getName());
        System.out.println("年龄:" + person.getAge());
        System.out.println("性别:" + person.getGender());
    }
}

四、练习

1 继承案例:正方形与长方形

声明一个类 Square,表示正方形:

  • 属性:int 类型的边长(width)
  • 构造器:int 类型的参数,传入数值用于在造器中初始化 width
  • 方法:名为area() ,计算并返回正方形的面积
/**
 * @document: 表示正方形
 * @Author:SmallG
 * @CreateTime:2023/7/26+8:12
 */

public class Square {
    int width;

    public Square() {
    }

    public Square(int width) {
        this.width = width;
    }

    public int area(int width) {
        return width * width;
    }
}

声明一个类 Rectangle,表示长方形:

  • 继承自 Square 类
  • 属性:int 类型的长度(length)
  • 构造器:两个 int类型的参数,用于在构造器中初始化 width 和 length
  • 重写area()方法:计算并返回长方形的面积
/**
 * @document: 表示长方形
 * @Author:SmallG
 * @CreateTime:2023/7/26+8:14
 */
//继承Square类
public class Rectangle extends Square {
    //int 类型的长度
    int length;
    //构造器
    public Rectangle(){

    }
    public Rectangle(int width, int length) {
        super(width);
        this.length = length;
    }
    //重写area()方法
    public int area(int width, int length) {
        return width * length;
    }
}

编写一个测试类:

  • 在main方法中分别创建 Square 类和 Rectangle 类的对象
  • 调用这2个对象的 area()方法,将得到的面积输出到控制台

测试输出效果如下:

继承案例

/**
 * @document: 测试类用于测试Square和Rectangle
 * @Author:SmallG
 * @CreateTime:2023/7/26+8:18
 */

public class SAndRTest {
    public static void main(String[] args) {
        //创建对象
        Square square = new Square();
        Rectangle rectangle = new Rectangle();
        //调用方法并输出
        System.out.println("正方形面积:"+square.area(20));
        System.out.println("长方形面积:"+rectangle.area(10,20));
    }
}

2 根据需求完成类的声明

有5个类,其继承关系如下图所示:

根据需求完成类的说明

1、Employee类

  • 所有员工总的父类
  • 构造器:传入员工的姓名(String类型)和生日月份(int类型)
  • 方法:getName( ),返回String类型的姓名
  • 方法:getSalary(int month) ,返回 double 类型的薪资,参数为发薪月,如果该月员工过生日,则额外奖励100元
/**
 * @document: 所有员工的父类
 * @Author:SmallG
 * @CreateTime:2023/7/26+14:55
 */

public class Employee {
    String name;
    int birthday; //生日月份
    // 构造器:传入员工的姓名(String类型)和生日月份(int类型)

    public Employee() {
    }

    public Employee(String name, int birthday) {
        this.name = name;
        this.birthday = birthday;
    }

    //方法:getName( ),返回String类型的姓名
    public String getName() {
        return name;
    }

    //方法:getSalary(int month) ,返回 double 类型的薪资,参数为发薪月,
    // 如果该月员工过生日,则额外奖励100元
    public double getSalary(int month) {
        if (month == birthday) {
            return 100;
        }
        return 0;
    }
}

2、SalariedEmployee 类:Employee的子类,拿固定工资的员工

  • 构造器:传入员工的姓名(String类型)、生日月份(int类型)、底薪(double类型)
  • 重写方法 getSalary(int month) :加上底薪
/**
 * @document: Employee的子类,拿固定工资的员工
 * @Author:SmallG
 * @CreateTime:2023/7/26+15:00
 */

public class SalariedEmployee extends Employee {

    double salary;

    public SalariedEmployee() {
    }

    // 构造器:传入员工的姓名(String类型)、生日月份(int类型)、底薪(double类型)
    public SalariedEmployee(String name, int birthday, double salary) {
        super(name, birthday);
        this.salary = salary;
    }

    //重写方法 getSalary(int month) :加上底薪
    public double getSalary(int month) {
        return salary+super.getSalary(month);
    }
}

3、HourlyEmployee 类:Employee的子类,按小时拿工资的员工

  • 薪资收入 = 每小时的工资 * 每月工作的小时数(每月工作超出160小时的部分按照1.5倍工资发放)
  • 构造器:传入员工的姓名(String类型)、生日月份(int类型)、小时薪资(double类型)、工作的小时数(int类型)
  • 重写方法 getSalary(int month) :加上小时工资
/**
 * @document: HourlyEmployee类,Employee的子类,按小时拿工资
 * @Author:SmallG
 * @CreateTime:2023/7/26+15:04
 */

public class HourlyEmployee extends Employee {
    double HourSalary;
    int Hour;
    //构造器:传入员工的姓名(String类型)、生日月份(int类型)、
    // 小时薪资(double类型)、工作的小时数(int类型)

    public HourlyEmployee() {

    }

    public HourlyEmployee(String name, int birthday, double hourSalary, int hour) {
        super(name, birthday);
        HourSalary = hourSalary;
        Hour = hour;
    }

    //薪资收入 = 每小时的工资 * 每月工作的小时数(每月工作超出160小时的部分按照1.5倍工资发放)
    //重写方法 getSalary(int month) :加上小时工资
    @Override
    public double getSalary(int month) {
        if (month == birthday) {
            if (Hour > 160) {
                return HourSalary * 160 + (160 - Hour) * HourSalary * 1.5 + 100;
            } else {
                return HourSalary * Hour + 100;
            }

        } else {
            return HourSalary * Hour;
        }

    }
}

4、SalesEmployee 类:Employee的子类,销售人员,工资由月销售额和提成率决定

  • 构造器:传入员工的姓名(String类型)、生日月份(int类型)、销售额(double类型)、提成率(double类型)
  • 重写方法 getSalary(int month) :加上销售工资=销售额*提成率
/**
 * @document: Employee的子类,销售人员,工资由月销售额和提成率决定
 * @Author:SmallG
 * @CreateTime:2023/7/26+15:43
 */

public class SalesEmployee extends Employee {
    double salesVolume;
    double royaltyRate;

    // 构造器:传入员工的姓名(String类型)、生日月份(int类型)、
    // 销售额(double类型)、提成率(double类型)

    public SalesEmployee() {
    }

    public SalesEmployee(String name, int birthday, double salesVolume, double royaltyRate) {
        super(name, birthday);
        this.salesVolume = salesVolume;
        this.royaltyRate = royaltyRate;
    }

    // 重写方法 getSalary(int month) :加上销售工资=销售额*提成率
    public double getSalary(int month) {
        double salary = super.getSalary(month);
        return salary + salesVolume * royaltyRate;
    }
}

5、BasePlusSalesEmployee 类:SalesEmployee的子类,有固定底薪的销售人员

  • 薪资收入 = 底薪 + 销售额*提成率
  • 构造器:传入员工的姓名(String类型)、生日月份(int类型)、销售额(double类型)、提成率(double类型)、底薪(double类型)
  • 重写方法 getSalary(int month) :计算薪资收入
/**
 * @document: SalesEmployee的子类,有固定底薪的销售人员
 * @Author:SmallG
 * @CreateTime:2023/7/26+16:05
 */

public class BasePlusSalesEmployee extends SalesEmployee {

    double salary; //底薪

    // 构造器:传入员工的姓名(String类型)、生日月份(int类型)、
    // 销售额(double类型)、提成率(double类型)、底薪(double类型)

    public BasePlusSalesEmployee(double salary) {
        this.salary = salary;
    }

    public BasePlusSalesEmployee(String name, int birthday, double salesVolume, double royaltyRate, double salary) {
        super(name, birthday, salesVolume, royaltyRate);
        this.salary = salary;
    }
    
    // 薪资收入 = 底薪 + 销售额*提成率
    // 重写方法 getSalary(int month) :计算薪资收入

    @Override
    public double getSalary(int month) {
        return super.getSalary(month) + salary + royaltyRate * salesVolume;
    }
}

根据上述需求完成相关类的声明,并编写一个测试类:

  • 在main方法中创建除Employee类以外的所有类的对象
  • 调用每个对象的getSalary方法
  • 将得到的工资输出到控制台,验证能否正确输出该员工的工资

运行效果如下所示:

效果图

/**
 * @document: 测试类
 * @Author:SmallG
 * @CreateTime:2023/7/26+16:10
 */

public class Test {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入几月:");
        int month = scanner.nextInt();
        System.out.println("宇宙集团" + month + "月工资表:");

        BasePlusSalesEmployee bPSE = new BasePlusSalesEmployee("赵军", 2, 1000, 0.5, 100);
        System.out.println(bPSE.name + ":" + bPSE.getSalary(month)); //600

        HourlyEmployee hE = new HourlyEmployee("宋婕", 2, 100, 30);
        System.out.println(hE.name + ":" + hE.getSalary(month));

        SalariedEmployee sE = new SalariedEmployee("王超", 2, 2000);
        System.out.println(sE.name + ":" + sE.getSalary(month));

        SalesEmployee sE2 = new SalesEmployee("小关", 6, 10000, 0.6);
        System.out.println(sE2.name + ":" + sE2.getSalary(month));
    }
}
最后修改:2023 年 07 月 26 日
如果觉得我的文章对你有用,请随意赞赏