一、对象数组
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));
}
}