Java基础 3. 面向对象
文章目录
3.1. 面向对象
- 软件开发方法
- 面向过程[^PO (Procedure Oriented)]
- 关注点在实现功能的步骤上
- 面向过程就是分析出解决问题所需要的步骤, 然后用函数把这些步骤一步一步实现, 使用的时候一个一个依次调用
- 代表语言: C语言
- 例如开汽车, 启动, 踩离合, 挂挡, 松离合, 踩油门, 车开了
- 对于简单的流程适合使用面向过程的方式进行, 复杂的流程不适合使用面向过程的方式开发
- 面向对象[^OO (Object Oriented)]
- 关注点在实现功能需要哪些对象的参与
- 面向对象就是分析出解决这个问题都需要哪些对象的参加, 然后让对象与对象之间协作起来形成一个系统
- 面向对象OO包括OOA[^Analysis面向对象分析], OOD[^Design面向对象设计], OOP[^Programming面向对象编程]. 代表语言: Java, C#, Python等
- 例如开汽车, 汽车对象, 司机对象, 司机对象有一个驾驶的行为, 司机对象驾驶汽车对象
- 面向对象更符合人类的思维方式, 更容易处理现实生活中的复杂问题. 开发方式扩展能力强, 例如采用面向过程生产一台电脑, 不会分CPU, 内存和硬盘, 它会按照电脑的工作流程一次成型, 采用面向对象生产一台电脑, CPU是一个对象, 内存条是一个对象, 硬盘是一个对象, 如果觉得硬盘容量小, 后期是很容易更换的, 这就是扩展性
- 面向过程[^PO (Procedure Oriented)]
- 面向对象三大特征
- 封装[^Encapsulation]
- 继承[^Inheritance]
- 多态[^polymorphism]
3.2. 对象的创建和使用
- 类
- 现实世界中, 事物与事物之间具有共同特征, 例如: 刘德华和梁朝伟都有姓名, 身份证号, 身高等状态, 都有吃, 跑, 跳等行为, 将这些共同的状态和行为提取出来, 形成了一个模块, 成为类
- 类实际上是人类大脑思考总结的一个模板, 类是一个抽象的概念
- 状态在程序中对应属性, 属性通常用变量来表示
- 行为在程序中对应方法, 用方法来描述行为动作
- 类 = 属性 + 方法
- 对象
- 实际存在的个体
- 对象又称为实例[^instance]
- 通过类这个模板可以实例化n个对象[^通过类可以创造出多个对象]
- 例如通过"明星类"可以创造出"刘德华对象"和"梁朝伟对象"
- 明星类中有一个属性名字: String name;
- "刘德华对象"和"梁朝伟对象"由于是通过明星类创造出来的, 所以这两个都有name属性, 但是值是不同的, 因此这种属性被称为实例变量
/*
1. 语法格式
[修饰符列表] class 类名{
// 属性 (描述状态)
// 方法 (描述行为动作)
}
2. 为什么要定义类
因为要通过类实例化对象, 有了对象, 让对象和对象之间协作起来形成系统
3. 一个类可以实例化多个java对象 (通过一个类可以造出多个java对象)
4. 实例变量是一个对象一份, 比如创建3个学生对象, 每个学生对象中应该都有name变量
5. 实例变量属于成员变量, 成员变量如果没有手动赋值, 系统会赋默认值
数据类型 默认值
--------------------
byte 0
short 0
int 0
long 0L
float 0.0F
double 0.0
boolean false
char \u0000
引用数据类型 null
*/
// 例如: 学生类
public class Student{
// 属性: 姓名, 年龄, 性别
// 姓名
String name; // 实例变量
// 年龄
int age;
// 性别
boolean gender;
// 学习
public void study(){ // 实例方法
System.out.println("正在学习");
}
}
public class StudentTest01 {
public static void main(String[] args) {
int i = 10;
// 通过学生类Student实例化学生对象(通过类创造对象)
// Student s1; 是? s1是变量名, Student是一种数据类型名, 属于引用数据类型
// s1也是局部变量, 和i一样
// s1变量中保存的是: 堆内存中Student对象的内存地址
// s1有一个特殊的称呼: 引用
// 什么是引用? 引用的本质是一个变量, 这个变量保存了java对象的内存地址(为什么不叫指针呢, 害怕你晕针)
// 引用和对象要区分开, 对象在JVM堆当中, 引用是保存对象地址的变量
Student s1 = new Student();
// 访问对象的属性(读变量的值)
// 访问实例变量的语法: 引用.变量名
// 两种访问方式: 第一种读取, 第二种修改
// 读取: 引用.变量名; s1.name; s1.age; s1.gender;
// 修改: 引用.变量名 = 值; s1.name = "stephen"; s1.age = 20; s1.gender = true;
System.out.println("姓名: " + s1.name);
System.out.println("年龄: " + s1.age);
System.out.println("性别: " + (s1.gender ? "男" : "女"));
// 修改对象的属性(修改变量的值, 给变量重新赋值)
s1.name = "张三";
s1.age = 20;
s1.gender = true;
System.out.println("姓名: " + s1.name);
System.out.println("年龄: " + s1.age);
System.out.println("性别: " + (s1.gender ? "男" : "女"));
// 再创建一个新对象
Student s2 = new Student();
// 访问对象的属性
System.out.println("姓名= " + s2.name);
System.out.println("年龄= " + s2.age);
System.out.println("性别= " + (s2.gender ? "男" : "女"));
// 修改对象的属性
s2.name = "李四";
s2.age = 20;
s2.gender = false;
System.out.println("姓名= " + s2.name);
System.out.println("年龄= " + s2.age);
System.out.println("性别= " + (s2.gender ? "男" : "女"));
}
}
- JVM内存分析
public class Vip {
// 类 = 属性 + 方法
// 属性: 描述的是状态
// 方法: 描述的是行为
// 姓名
String name; // 实例变量(对象变量)
// 年龄
int age;
// 会员行为动作
// 购物动作
// 先记住: 我们通常描述一个对象的行为动作时, 不加static
// 没有添加static的方法, 被叫做实例方法(对象方法)
public void shopping(){
System.out.println("正在购物!");
}
}
public class VipTest01 {
public static void main(String[] args) {
// 实例化一个Vip对象
Vip vip1 = new Vip();
// 给name和age属性赋值
vip1.name = "stephen";
vip1.age = 20;
System.out.println("name: " + vip1.name);
System.out.println("age: " + vip1.age);
// 访问实例方法
// 实例方法需要使用"引用."来调用
vip1.shopping();
// 再创建一个Vip对象
Vip vip2 = new Vip();
// 给name和age属性赋值
vip2.name = "curry";
vip2.age = 20;
System.out.println("name: " + vip2.name);
System.out.println("age: " + vip2.age);
// 访问实例方法
vip2.shopping();
// 为什么bane和age不能使用"类名."访问
// 实例变量要想访问, 必须先new对象, 通过引用来访问实例变量
// 实例变量是不能通过类名直接访问的
// System.out.println("name: " + Vip.name);
// System.out.println("age: " + Vip.age);
// 编译报错: java: 无法从静态上下文中引用非静态 方法 shopping()
// 实例方法不能使用"类名."去调用
// Vip.shopping();
}
}
/*
* 定义一个宠物类, 属性包括名字, 出生日期, 性别, 有吃和跑的行为. 再编写测试程序, 创建宠物对象, 访问宠物属性, 调用宠物吃和跑的方法
* */
public class Pet {
// 定义一个宠物类
// 姓名
String name;
// 出生日期
String birthday;
// 性别
boolean gender;
// 吃行为
public void eat(){
System.out.println("正在吃!");
}
// 跑行为
public void run(){
System.out.println("正在跑!");
}
}
public class PetTest01 {
public static void main(String[] args) {
// 创建宠物对象
Pet rabbit = new Pet();
// 访问宠物属性
rabbit.name = "小白";
rabbit.birthday = "2024-09-01";
rabbit.gender = false;
System.out.println("兔子的名字 = " + rabbit.name);
System.out.println("兔子的生日 = " + rabbit.birthday);
System.out.println("兔子的性别 = " + (rabbit.gender ? "公" : "母"));
// 调用吃方法
rabbit.eat();
// 调用跑方法
rabbit.run();
// 再创建宠物对象
Pet dog = new Pet();
// 访问宠物属性
dog.name = "狗的";
dog.birthday = "2024-09-02";
dog.gender = true;
System.out.println("狗的名字 = " + dog.name);
System.out.println("狗的生日 = " + dog.birthday);
System.out.println("狗的性别 = " + (dog.gender ? "公" : "母"));
// 调用吃方法
dog.eat();
// 调用跑方法
dog.run();
// 错误: NullPointerException
// 注意: 引用一旦为null, 表示引用不再指向对象了, 但是通过引用访问name属性, 编译可以通过.
// 运行时会出现异常, NullPointerException(空指针异常), 这是一个非常著名的异常
// 为什么会出现空指针异常, 因为运行的时候会找真正的对象, 如果对象不存在了, 就会出现这个异常
// dog = null;
// System.out.println(dog.name);
// 会出现空指针异常
// dog.eat();
// dog.run();
// 如果没有任何引用指向一个对象, 该对象最终会被当作垃圾, 被GC(垃圾回收机制)回收
}
}
- 方法调用时传递基本数据类型
/*
* 面试题: 判断该程序的输出结果
* */
public class ArgsTest01 {
public static void main(String[] args) {
int i = 10;
add(i);
System.out.println("main-->" + i);// main-->10
}
public static void add(int i){
i++;
System.out.println("add-->" + i);// add-->11
}
}
- 方法调用时传递引用数据类型
public class User {
// 年龄
int age;
}
/*
* 面试题: 分析以下程序的输出结果
* */
public class ArgsTest02 {
public static void main(String[] args) {
User user = new User();
user.age = 10;
// user的传递历程: 实际上和ArgsTest01当中的i原理相同, 都是将变量中保存的值传递过去, 只不过这里的user变量中保存的值比较特殊, 是一个对象的内存地址
add(user);
System.out.println("main-->" + user.age);// main-->11
}
public static void add(User user){ // user是一个引用
user.age++;
System.out.println("add-->" + user.age);// add-->11
}
}
- 初识this关键字
public class Student {
// 属性 实例变量 学生
String name;
// 方法: 学习的行为
public void study(){
// this本质上是一个引用
// this中保存的是当前对象的内存地址, 谁调用这个方法就存谁的地址
// 每一个实例方法都会在局部变量表的0号槽位上存放this关键字, 而this保存的是当前对象的地址
// this. 是可以省略的, 默认访问的就是当前对象的name, 有的地方是不可以省略的, 到后面会讲到
System.out.println(this.name + "正在努力的学习!");
}
}
public class StudentTest01 {
public static void main(String[] args) {
// 创建学生对象
Student zhangSan = new Student();
zhangSan.name = "张三";
System.out.println("学生姓名: " + zhangSan.name);
// 张三正在学习
zhangSan.study();
// 再创建一个学生对象
Student liSi = new Student();
liSi.name = "李四";
System.out.println("学生姓名: " + liSi.name);
// 李四正在学习
liSi.study();
}
}
3.3. 封装
- 面向对象的三大特征之一
- 封装[^Encapsulation]
- 现实世界的封装
- 液晶电视也是一种封装好的电视设备, 它将电视所需的各种零部件封装在一个整体的外壳中, 提供给用户一个简单的使用接口, 让用户可以轻松的切换频道. 电视内部包含了很多的复杂技术, 而这些内部结构对于大多数普通用户来说是不可见的, 用户只需要通过遥控器就可以完成电视的各种设置和操作, 这就是封装的好处
- 程序角度的封装
- 封装是一种将数据和方法加以包装, 使之成为一个独立的实体, 并且把它与外部对象隔离开来的机制.
- 封装的好处
- 封装通过现之外部对象内部的直接访问和修改, 保证了数据的安全性, 并提高了代码的可维护性和可复用性
- 在代码上如何实现封装
- 属性私有化
- 对外提供getter和setter方法
- 不使用封装
package com.powernode.oop05;
/*
* 用户类: 先不使用封装机制, 分析程序有什么问题
* User类型对象的age属性非常不安全, 在外部程序中可以对其随意的访问
* */
public class User {
// 年龄
int age;
}
package com.powernode.oop05;
public class UserTest01 {
public static void main(String[] args) {
// 创建User对象
User user = new User();
// 访问User对象的age属性
// 读
System.out.println("年龄: " + user.age);
// 写
user.age = 20;
// 读
System.out.println("年龄: " + user.age);
// 目前User类没有进行封装, 在外部程序中可以对User对象的age属性进行随意的访问, 这样非常的不安全(因为现实世界中age不可能是负数, 如果是真正的业务, -100不应该能够赋值给age变量)
user.age = -100;
// 读
System.out.println("年龄: " + user.age);
}
}
- 使用封装
package com.powernode.oop06;
/*
* 为了保证User类型对象的age属性的安全, 我们需要使用封装机制, 实现封装的步骤:
* 第一步: 属性私有化(使用private进行修饰). 进制外部程序对该属性进行随意的访问, 所有被private修饰的, 都是私有的, 私有的只能在本类中访问
* 第二步: 对外提供getter和setter方法. 为了保证外部的程序仍然可以访问age属性, 因此要对外提供公开的访问入口. 访问一般包括两种: 读和改, 应该对外提供两个方法, 读(getter), 改(setter)
* 读方法的格式:
* public int getAge(){}
* 改方法的格式:
* public void setAge(int age){}
* */
public class User {
private int age;
// 读取age属性的值
// getter方法是绝对安全的, 因为这个方法是读取属性的值, 不会涉及修改操作
public int getAge(){
// return this age
return this.age;
}
// 修改age属性的值
// setter方法中就需要编写拦截过滤代码, 来保证属性的安全
public void setAge(int age){
// this.age = num;
// 拦截
if(age < 0 || age > 130){
System.out.println("对不起, 你的年龄不合法!");
return;
}
// this. 大部分情况下可以省略, 用来区分局部变量和实例变量的时候不能省略
this.age = age;
}
}
package com.powernode.oop06;
public class UserTest01 {
public static void main(String[] args) {
User user = new User();
// System.out.println(user.age);
// user.age = -100;
// System.out.println(user.age);
// 读
System.out.println("年龄: " + user.getAge());
// 改
user.setAge(100);
// 读
System.out.println("年龄: " + user.getAge());
}
}
- test
package com.powernode.oop07;
/*
* 1. 定义一个汽车类, 包括属性: 品牌, 价格, 颜色等. 并对其中的价格属性进行封装, 价格不高于50万, 不低于20万.
* */
// 定义一个汽车类
public class Car {
// 定义属性
// 品牌
String brand;
// 价格, 并私有化
private int price;
// 颜色
String color;
// 对外提供价格属性的getter和setter方法
// getter方法
public int getPrice(){
return price;
}
// setter方法
public void setPrice(int price){
// 编写拦截过滤代码, 确保价格属性的安全性
if(price > 50 || price < 20){
System.out.println("你输入的价格不合法!");
return;
}
this.price = price;
}
}
package com.powernode.oop07;
public class CarTest01 {
public static void main(String[] args) {
// 创建Car对象
Car car = new Car();
// 改
car.brand = "小米su7";
car.setPrice(30);
car.color = "蓝绿";
// 读
System.out.println("品牌: " + car.brand);
System.out.println("价格: " + car.getPrice());
System.out.println("颜色: " + car.color);
}
}
package com.powernode.oop07;
/*
* 2. 定义一个银行类, 包含属性: 账户名, 余额等. 并对其中的余额进行封装, 余额不低于0. 另外定义一个取款方法withdraw, 判断取款金额是否合法, 另外金额是否充足.
* */
// 定义一个银行类
public class Bank {
// 定义属性
// 账户名
String account;
// 余额, 并私有化
private int balance;
// 余额getter
public int getBalance() {
return balance;
}
// setter
public void setBalance(int balance) {
// 过滤拦截代码
if (balance < 0) {
System.out.println("你的余额不合法");
return;
}
this.balance = balance;
}
// withdraw方法
public void withdraw(int money) {
// 判断取款金额是否合法
if (money <= 0) {
System.out.println("你的取款金额不合法!");
return;
}
// 程序能够执行到这里, 说明取款金额是合法的
// 继续判断余额够不够
if (this.balance < money) {
System.out.println("你的余额不足!");
return;
}
// 程序执行到这里, 说明, 余额充足
this.setBalance(this.getBalance() - money);
System.out.println("取款" + money + "成功");
}
}
package com.powernode.oop07;
public class BankTest01 {
public static void main(String[] args) {
// 创建一个Bank对象
Bank bank = new Bank();
// 改
bank.account = "区区一散修";
bank.setBalance(10000);
// 读
System.out.println("账户名: " + bank.account);
System.out.println("余额: " + bank.getBalance());
// 改
bank.withdraw(5000);
// 读
System.out.println("余额: " + bank.getBalance());
// 继续取款
bank.withdraw(5000);
System.out.println("余额: " + bank.getBalance());
// 继续取款
bank.withdraw(1000);
System.out.println("余额: " + bank.getBalance());
}
}
package com.powernode.oop07;
/*
* 3. 定义一个员工类, 包含属性: 姓名, 年龄, 工资等. 并对其中的工资进行封装, 工资不得低于800元. 另外定义一个raise方法用来涨薪, 如果涨薪后的工资超过了10000元, 则不再涨薪.
* */
// 定义一个员工类
public class Employee {
// 定义属性
// 姓名
String name;
// 年龄
int age;
// 工资, 并私有化
private int salary;
// 工资getter
public int getSalary(){
return salary;
}
// setter
public void setSalary(int salary){
// 过滤拦截
if(salary < 800){
System.out.println("工资太低了!");
return;
}
this.salary = salary;
}
// raise方法
public void raise(int money){
// 判断工资是否还可以继续涨薪
if(this.salary + money >= 10000){
System.out.println("你涨薪后的工资已经超过了10000, 不再涨薪!");
return;
}
// 程序执行到这里说明还是可以继续涨薪的
this.salary += money;
System.out.println("涨薪成功!");
}
}
package com.powernode.oop07;
public class EmployeeTest01 {
public static void main(String[] args) {
// 创建一个Employee对象
Employee employee = new Employee();
// 改
employee.name = "区区一散修";
employee.age = 20;
employee.setSalary(3000);
// 读
System.out.println("姓名: " + employee.name);
System.out.println("年龄: " + employee.age);
System.out.println("初始工资: " + employee.getSalary());
// 涨薪
employee.raise(3000);
System.out.println("涨薪后的(不再涨薪的)工资: " + employee.getSalary());
// 继续涨薪
employee.raise(3000);
System.out.println("涨薪后的(不再涨薪的)工资: " + employee.getSalary());
// 继续涨薪
employee.raise(3000);
System.out.println("涨薪后的(不再涨薪的)工资: " + employee.getSalary());
}
}
package com.powernode.oop07;
/*
* 4. 定义一个顾客类Customer, 包括属性: 姓名, 生日, 性别, 联系电话等属性. 对所有属性进行封装. 然后提供一个购物的shopping()方法, 再提供一个付款的pay()方法, 在shopping()方法中购物, 购物行为在结束前需要完成支付, 因此在shopping()方法的最后调用pay()方法. 体会实例方法中调用实例方法.
* */
// 定义一个顾客类
public class Customer {
// 定义属性
// 姓名
private String name;
// 生日
private String birthday;
// 性别
private char gender;
// 联系电话
private String phone;
// shopping方法
public void shopping(){
// 购物行为
System.out.println(this.name + "正在购物!");
// 在shopping方法的最后调用pay方法
this.pay();
}
// pay方法
public void pay(){
System.out.println(this.name + "正在支付!");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
public char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
package com.powernode.oop07;
public class CustomerTest01 {
public static void main(String[] args) {
// 创建一个Customer对象
Customer customer = new Customer();
// 改
customer.setName("区区一散修");
customer.setBirthday("2002-01-01");
customer.setGender('男');
customer.setPhone("123456789");
// 读
System.out.println("姓名: " + customer.getName());
System.out.println("生日: " + customer.getBirthday());
System.out.println("性别: " + customer.getGender());
System.out.println("联系电话: " + customer.getPhone());
// 顾客购物
customer.shopping();
// 创建一个Customer对象
Customer customer2 = new Customer();
// 改
customer2.setName("张三");
customer2.setBirthday("2002-01-02");
customer2.setGender('男');
customer2.setPhone("1234567890");
// 读
System.out.println("姓名: " + customer2.getName());
System.out.println("生日: " + customer2.getBirthday());
System.out.println("性别: " + customer2.getGender());
System.out.println("联系电话: " + customer2.getPhone());
// 顾客购物
customer2.shopping();
}
}
3.4. 构造方法
-
构造方法 [^Constructor]
- 又称为构造器
-
构造方法的作用
- 对象的创建
- 对象的初始化
- 定义构造方法
[修饰符列表] 构造方法名(参数列表){ 构造方法体; } 注意 构造方法名必须和类名一致 构造方法不需要提供返回类型, 如果提供了返回值类型, 那么这个方法就算不是构造方法了, 就变成普通方法了
-
构造方法的调用
- 使用new运算符来调用
- 语法
- new 构造方法名 ( 值 );
- 注意
- 构造方法最终执行结束之后, 会自动将创建的对象的内存地址返回, 但构造方法体中不需要提供"return 值;"这样的语句
-
在java中, 如果一个类没有显示的去定义构造方法, 系统会默认提供一个无参数的构造方法 [^通常把这个方法叫做缺省构造器]
-
一个类中如果显示的定义了构造方法, 系统则不再提供缺省构造器, 所以, 为了对象创建更加方便, 建议把无参数构造方法手动的写出来
-
在java中, 一个类中可以定义多个构造方法, 而且这些构造方法自动构成了方法重载 [^overload]
-
构造方法中已经给属性赋值了, 为什么还需要单独定义setter方法给属性赋值呢
- 在构造方法中赋值是对象第一次创建时属性赋的值, setter方法可以在后期的时候调用, 来完成属性的修改
-
构造方法执行原理
-
在new的时候, 会直接在堆内存中开辟空间, 然后给所有属性赋默认值, 完成对象的创建 [^这个过程是在构造方法体执行之前就完成的]
-
构造方法体开始执行, 标志着开始进行对象初始化, 构造方法执行完毕, 表示对象初始化完毕
-
package com.powernode.oop08;
public class Student {
// // 无参数的构造方法显示的定义出来
// 建议把无参数构造方法手动的写出来
public Student(){
System.out.println("Student类的无参数构造方法执行了");
}
// 有参数的构造方法显示的定义出来
public Student(String name, int age, boolean gender, String address){
this.name = name;
this.age = age;
this.gender = gender;
this.address = address;
System.out.println("Student类的有参数构造方法执行了");
}
// 再定义一个
public Student(String name){
this.name = name;
}
// 再定义一个
public Student(String name, int age){
this.name = name;
this.age = age;
}
// 姓名
private String name;
// 年龄
private int age;
// 性别
private boolean gender;
// 住址
private String address;
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 boolean isGender() {
return gender;
}
public void setGender(boolean gender) {
this.gender = gender;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
package com.powernode.oop08;
public class ConstructorTest01 {
public static void main(String[] args) {
// 调用Student类的构造方法来完成Student类型对象的创建
// 以下代码本质上是: 通过new运算符调用无参数的构造方法来完成对象的实例化
// s1是一个引用, 保存了内存地址指向了对内存当中的Student类型的对象
// 这样就完成了学生对象的创建以及初始化
// 无参数构造方法没有给属性手动赋值, 但是系统会赋默认值
Student s1 = new Student();
System.out.println("姓名: " + s1.getName());
System.out.println("年龄: " + s1.getAge());
System.out.println("性别: " + (s1.isGender() ? "男" : "女"));
System.out.println("住址: " + s1.getAddress());
// 通过调用另一个有参数的构造方法来创建对象, 完成对象的初始化
Student s2 = new Student("区区一散修", 20, true, "深山老林");
System.out.println("姓名: " + s2.getName());
s2.setName("区区一散修2");
System.out.println("姓名: " + s2.getName());
System.out.println("年龄: " + s2.getAge());
System.out.println("性别: " + (s2.isGender() ? "男" : "女"));
System.out.println("住址: " + s2.getAddress());
// 调用构造方法
Student s3 = new Student("张三");
System.out.println("姓名: " + s3.getName());
System.out.println("年龄: " + s3.getAge());
System.out.println("性别: " + (s3.isGender() ? "男" : "女"));
System.out.println("住址: " + s3.getAddress());
// 调用构造方法
Student s4 = new Student("李四", 30);
System.out.println("姓名: " + s4.getName());
System.out.println("年龄: " + s4.getAge());
System.out.println("性别: " + (s4.isGender() ? "男" : "女"));
System.out.println("住址: " + s4.getAddress());
}
}
-
构造代码块
语法格式: // 构造代码块 { System.out.println("构造代码块执行!"); // 这里能够使用this, 这说明, 构造代码块之前对象已经创建好了, 并且系统也完成了默认赋值 System.out.println(this.name); for (int i = 0; i < 10; i++) { System.out.println("i = " + i); } } // 构造代码块事故在构造方法执行之前执行的; // 每一次在new的时候, 都会先执行一次构造代码块
package com.powernode.oop08; public class ConstructorTest02 { public static void main(String[] args) { new Student(); new Student(); new Student(); new Student(); } }
- 作用
- 如果所有的构造方法在最开始的时候有相同的一部分代码, 不妨将这个公共的代码提取到构造代码块当中, 这样代码就可以得到复用
- 作用
-
test
package com.powernode.oop09;
/**
* 1. 请定义一个交通工具Vehicle类, 属性: 品牌brand, 速度speed, 体积size等, 属性封装. 方法: 移动move(), 加速speedUp(), 减速speedDown()等. 最后在测试类中实例化一个交通工具对象, 并通过构造方法给它初始化speed, size的值, 调用加速, 减速的方法对速度进行改变.
*/
// 定义一个Vehicle类
public class Vehicle {
// 属性
// 品牌
private String brand;
// 速度
private int speed;
// 体积
private int size;
// 构造方法
public Vehicle(){
}
public Vehicle(String brand, int speed, int size){
this.brand = brand;
this.speed = speed;
this.size = size;
}
// 方法
// 移动
public void move(){
System.out.println(this.brand + "正在以" + this.getSpeed() + "的速度行驶");
}
// 加速
public void speedUp(){
// 每次加10
System.out.println("加速10");
this.setSpeed(this.getSpeed() + 10);
this.move();
}
// 减速
public void speedDown(){
// 每次减10
System.out.println("减速10");
this.setSpeed(this.getSpeed() - 10);
this.move();
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public int getSpeed() {
return speed;
}
public void setSpeed(int speed) {
this.speed = speed;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
}
package com.powernode.oop09;
public class VehicleTest01 {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle("大众", 80, 6);
// 移动
vehicle.move();
// 加速
vehicle.speedUp();
// 加速
vehicle.speedUp();
// 减速
vehicle.speedDown();
}
}
package com.powernode.oop09;
/**
* 2. 模拟简单的计算器. 定义名为Calculator的类, 其中有两个int类型属性n1, n2, 属性封装. 编写构造方法为n1和n2赋初始值, 再为该类定义加(add), 减(sub), 乘(mul), 除(div)等实例方法, 分别对两个属性执行加减乘除的运算. 在main方法中创建Number类的对象, 调用各个方法, 并显示计算结果
*/
// 定义一个类
public class Calculator {
// n1, n2
private int n1, n2;
// 构造方法
public Calculator(){
}
public Calculator(int n1, int n2){
this.n1 = n1;
this.n2 = n2;
}
// 实例方法
// 加
public void add(){
System.out.println(this.getN1() + " + " + this.getN2() + " = " + (this.getN1() + this.getN2()));
}
// 减
public void sub(){
System.out.println(this.getN1() + " - " + this.getN2() + " = " + (this.getN1() - this.getN2()));
}
// 乘
public void mul(){
System.out.println(this.getN1() + " * " + this.getN2() + " = " + (this.getN1() * this.getN2()));
}
// 除
public void div(){
System.out.println(this.getN1() + " / " + this.getN2() + " = " + (this.getN1() / this.getN2()));
}
public int getN1() {
return n1;
}
public void setN1(int n1) {
this.n1 = n1;
}
public int getN2() {
return n2;
}
public void setN2(int n2) {
this.n2 = n2;
}
}
package com.powernode.oop09;
public class CalculatorTest01 {
public static void main(String[] args) {
Calculator calculator = new Calculator(10, 2);
calculator.add();
calculator.sub();
calculator.mul();
calculator.div();
}
}
package com.powernode.oop09;
/**
* 3. 定义一个网络用户类, 要处理的信息有用户id, 用户密码, email地址. 在建立类的实例时, 把以上三个信息都作为构造方法的参数输入, 其中用户id和用户密码是必须的, 缺省的email地址是用户id加上字符串"@powernode.com"
*/
// 定义一个网络用户类
public class User {
// 用户id
private String id;
// 用户密码
private String password;
// email
private String email;
// 打印用户信息
public void display(){
System.out.println("id: " + this.getId() + ", 密码: " + this.getPassword() + ", email: " + this.getEmail());
}
// 构造方法
public User() {
}
public User(String id, String password, String email) {
this.id = id;
this.password = password;
this.email = email;
}
public User(String id, String password) {
this.id = id;
this.password = password;
this.email = getId() + "@powernode.com";
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
package com.powernode.oop09;
public class UserTest01 {
public static void main(String[] args) {
// 用户tzy
User tzy = new User("tzy", "123456789", "123456789@qq.com");
// 查看tzy信息
tzy.display();
// 用户tzy2
User tzy2 = new User("tzy2", "1234567890");
// 查看tzy2信息
tzy2.display();
}
}
3.5. this关键字
-
this出现在实例方法, 代表当前对象. 语法: this.
-
this的本质
- 是一个引用, 该引用保存了当前对象的内存地址
-
this存储在
- 栈帧的局部变量表的0号槽位上
-
this. 大部分情况下可以省略, 用于区别局部变量和实例变量时不能省略
-
this不能出现在静态方法当中
package com.powernode.oop10;
// 定义一个会员类
public class Vip {
// 姓名
private String name; // 实例变量(实例变量的访问必须通过"引用."来访问)
public void shopping(){
// System.out.println(this.name + "在购物");
System.out.println("shopping--->" + this);
// 以下是打印结果, 这两个都是内存地址
// 张三的this: shopping--->com.powernode.oop10.Vip@2f4d3709
// 李四的this: shopping--->com.powernode.oop10.Vip@7291c18f
}
// 思考: 带有static的方法, 为什么不能使用this?
// this代表的是当前对象, static的方法中没有当前对象, 所以static的方法中不能使用this
public static void test(){
// 编译报错
// System.out.println(this.name);
// this.shopping();
// System.out.println(name);
// shopping();
// test2()是普通方法, 不是实例方法, 没有对象, 所以可以直接调用
Vip.test2();
test2();
// 如果真的想要调用, 可以new一个对象
Vip vip = new Vip("王五");
System.out.println(vip.name);
vip.shopping();
}
public static void test2(){
System.out.println("test2...");
}
public Vip(){
}
public Vip(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.powernode.oop10;
public class VipTest01 {
public static void main(String[] args) {
Vip zhangsan = new Vip("张三"); // main--->com.powernode.oop10.Vip@2f4d3709
System.out.println("main--->" + zhangsan);
zhangsan.shopping();
Vip lisi = new Vip("李四");
System.out.println("main--->" + lisi); // main--->com.powernode.oop10.Vip@7291c18f
lisi.shopping();
Vip.test();
}
}
package com.powernode.oop10;
public class Test {
// 实例变量
int age = 10;
public static void main(String[] args){
// 报错原因: 是因为当前的main方法时static修饰的
//System.out.println(age);
// System.out.println(this.age);
// 调用main方法
Test test = new Test();
test.main("hello");
}
// 因为没有static, 所以这个方法是实例方法
// 实例方法调用必须new对象
// 所以这个方法中有this
public void main(String s){
System.out.println(age);
System.out.println(this.age);
}
}
- “this ( 值 )” 语法
- 只能出现在构造方法的第一行
- 通过当前构造方法去调用本类中其他的构造方法
- 作用是
- 代码复用
package com.powernode.oop10;
/**
* 需求: 定义一个日期类, 代表日期, 日期属性包括年月日. 提供两个构造方法, 一个是无参数构造方法, 当通过无参数构造方法实例化日期对象的时候, 默认创建的日期是2024-9-10. 另一个构造方法三个参数, 通过传递年月日三个参数来确定一个日期. 注意属性要提供封装
*/
// 定义一个日期类
public class Date {
// 属性
// 年
private int year;
// 月
private int month;
// 日
private int day;
// 打印日期的方法
public void display(){
System.out.println("日期: " + this.getYear() + "-" + this.getMonth() + "-" + this.getDay());
}
// 构造方法
// 无参数
public Date() {
// 代码重复
// this.year = 2024;
// this.month = 9;
// this.day = 10;
// 调用有参数构造方法, 实现代码的复用
// 不要这样写, 这样会导致再创建一个新的对象
// new Date(2024, 9, 10);
// 不会创建新的对象, 只是通过一个构造方法去调用另一个方法
this(2024, 9, 10);
}
// 有参数
public Date(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
}
package com.powernode.oop10;
public class DataTest01 {
public static void main(String[] args) {
// 创建默认日期对象
Date date1 = new Date();
date1.display();
// 创建指定日期对象
Date date2 = new Date(2024, 10, 10);
date2.display();
}
}
3.6. static关键字
- static [^静态的]
- static修饰的变量叫做静态变量, static修饰的方法叫做静态方法
- 所有的静态相关的, 都是类级别的, 不用new对象, 直接使用**类名.**调用
package com.powernode.oop11;
/*
* 什么情况下把成员变量定义成静态成员变量
当一个属性是对象级别的, 这个属性通常定义为实例变量. (实例变量是一个对象一份, 100个对象就应该有100个空间)
当一个属性是类级别的(所有对象都有这个属性, 并且这个属性的值都一样), 建议将其定义为静态变量, 在内存空间上只有一份, 节省内存开销.
静态变量存储在哪里? 静态变量在什么时候初始化?
* JDK8之后, 静态变量存储在堆内存中
* 类加载时初始化
* */
import org.w3c.dom.ls.LSOutput;
/**
* 变量分类:
* 局部变量
* 成员变量:
* 实例变量
* 静态变量
*/
/* // 实例变量
int i;
// 静态成员变量
static int j;
public static void main(String[] args) {
// 局部变量
int k;
}*/
// 中国人类
public class Chinese {
// 身份证号
String idCard;
// 姓名
String name;
// 国籍
static String country = "中国";
public Chinese(String idCard, String name) {
this.idCard = idCard;
this.name = name;
}
// 打印信息
public void display(){
System.out.println("身份证号: " + this.idCard + ", 姓名: " + this.name + ", 国籍: " + this.country
);
}
// 静态方法
public static void test(){
System.out.println("静态方法执行了");
}
}
package com.powernode.oop11;
public class ChineseTest01 {
public static void main(String[] args) {
// 创建中国人对象3人
Chinese zhangsan = new Chinese("123", "张三");
zhangsan.display();
Chinese lisi = new Chinese("456", "李四");
lisi.display();
Chinese wangwu = new Chinese("789", "王五");
wangwu.display();
System.out.println("国籍: " + Chinese.country);
// 静态变量可以采用"引用."来访问吗? 可以的(但不建议: 会给程序员造成困惑, 程序员会认为country是一个实例变量)
// 建议使用"类名."来访问, 这是正规的
System.out.println(zhangsan.country);
System.out.println(lisi.country);
zhangsan = null;
lisi = null;
// 静态变量也可以使用"引用."来访问, 但是实际上运行时与对象无关
// 以下程序也不会出现空指针异常
System.out.println(zhangsan.country);
System.out.println(lisi.country);
// 什么时候会出现空指针异常?
// 一个空引用访问实例相关的, 都会出现空指针异常
// 错误: NullPointerException
// System.out.println(zhangsan.name);
Chinese.test();
}
}
- 不加static的country
- 加了static的country
-
test
public class Test{ // doSome()实例方法访问实例变量j, 静态变量k, 实例方法doOther1(), 静态方法doOther2() // 实例方法 public void doSome(){ // 正规的方式访问 // System.out.println(this.j); // System.out.println(Chinese.k); // this.doOther1(); // Chinese.doOther2(); // 省略的方式访问 System.out.println(j); System.out.println(k); doOther1(); doOther2(); } // 实例变量 int j = 100; // 静态变量 static int k = 10; // 实例方法 public void doOther1(){ } // 静态方法 public static void doOther2(){ } }
-
静态代码块
- 语法格式
public class StaticTest01 { // 静态代码块 static{ System.out.println("静态代码1块执行了!"); } }
- 在类加载时执行, 并且只执行一次
- 可以编写多个, 并且遵循自上而下的顺序依次执行
- 本质上, 静态代码块就是为程序员预留的一个特殊的时间点 [^类加载时刻], 如果你需要在类加载时刻加载一段程序的话, 这段代码就可以写到静态代码块当中. 例如: 有这样一个需求, 请在类加载时, 记录日志. 那么记录日志的代码就可以编写到静态代码块当中
JVM体系结构 [^现阶段不用掌握]
-
JVM对应了一套规范 [^java虚拟机规范], 它可以有不同的实现
-
JVM规范是一种抽象的概念, 它可以有多种不同的实现
- HotSpot [^我们现在用的就是这个]: 是由Oracle公司开发, 是目前常用的虚拟机实现, 也是默认的java虚拟机, 默认包含在Oracle JDK和Open JDK中
- 登录oracle官网查看JVM虚拟机规范
-
程序计数器
- 是一块较小的内存空间, 此计数器记录的是正在执行的虚拟机字节码指令的地址
-
java虚拟机栈
- 用于存储栈帧, 栈帧用于存储局部变量表, 操作数栈, 动态链接, 方法出口等信息
-
堆
- 是java虚拟机所管理的最大的一块内存, 堆内存用于存放java对象实例以及数组. 堆时垃圾收集器收集垃圾的主要区域
-
方法区
- 用于存储已被虚拟机加载的类信息, 常量, 静态变量, 即时编译器编译后的代码等数据.
-
运行时常量池
- 是方法区的一部分, 用于存放编译器生成的各种字面量与符号引用 [^符号引用是由类名,属性名,方法名组成].
-
本地方法栈
- 在本地方法的执行过程中, 会使用到本地方法栈. 和java虚拟机栈十分相似
-
JVM规范的实现 [^HotSpot]
-
JDK6的HotSpot
-
年轻代
- 刚new出来的对象放在这里
-
老年代
- 经过垃圾回收之后任然存活的对象放在这里
-
符号引用
-
这个时期的永久代和堆是相邻的, 使用连续的物理内存, 但是内存空间时隔离的
-
永久代的垃圾收集是和老年代捆绑在一起的, 因此无论谁满了, 都会触发永久代和老年代的垃圾收集
-
-
JDK7的HotSpot
- 这是一个过渡的版本, 该版本相对于JDK6来说, 变化如下
- 类的静态变量转移到了堆中
- 字符串常量池转移到了堆中
- 运行时常量池中的符号引用转移到了本地内存 [^这个版本启用了本地内存,因为永久代存放了太多东西,很容易造成OOM内存溢出]
- 这是一个过渡的版本, 该版本相对于JDK6来说, 变化如下
-
JDK8+的HotSpot
- JDK8+相对于JDK7来说发生了如下变化
- 彻底删除了永久代 [^为了避免OOM错误的发生]
- 将方法区的实现转移到本地内存
- 将符号引用重新放回运行时常量池
- JDK8+相对于JDK7来说发生了如下变化
-
3.7. 单例模式 [^初级]
-
设计模式 [^Design Pattern]
- 一套被广泛接受的, 经过实验验证的, 可反复使用的基于面向对象的软件设计经验总结, 它是软件开发人员在软件设计中, 对常见问题的解决方案的总结和抽象, 设计模式是针对软件开发中常见问题和模式的通用解决方案
-
设计模式有哪些
- GoF设计模式 [^单例模式就是GoF设计模式中的一种]
- 四个外国人写的一本书, 这四个人被称为四人组 [^Gang of Four]
- …
- GoF设计模式 [^单例模式就是GoF设计模式中的一种]
-
GoF设计模式的分类
- 创建型
- 主要解决对象的创建问题
- 结构型
- 通过设计和构建对象之间的关系, 以达到更好的重用性, 扩展性和灵活性
- 行为型
- 主要用于处理对象之间的算法和责任分配
- 创建型
-
单例模式 [^Singleton]
-
饿汉式单例模式
- 类加载时对象就创建好了, 不管这个对象用还是不用, 提前先把对象创建好
- 实现
- 构造方法私有化
- 对外提供一个公开的静态的方法, 用这个方法获取单个实例
- 定义一个静态变量, 在类加载的时候, 初始化静态变量 [^只初始化一次]
package com.powernode.oop12; /** * Singleton: 单例 * * 饿汉式单例模式 */ public class Singleton { // 定义一个静态变量, 在类加载的时候, 初始化静态变量, 只初始化一次 private static Singleton s = new Singleton(); // 构造方法私有化 private Singleton(){ } // 对外提供一个公开的静态的方法, 用这个方法获取单个实例 public static Singleton get(){ return s; } }
package com.powernode.oop12; public class SingletonTest { public static void main(String[] args) { // 通过调用get方法来获取对象 Singleton s1 = Singleton.get(); Singleton s2 = Singleton.get(); System.out.println(s1 == s2); } }
-
懒汉式单例模式
- 用到这个对象的时候再创建对象, 别在类加载的时候创建对象
- 实现
- 构造方法私有化
- 对外提供一个公开的静态的方法, 用这个方法获取单个实例
- 定义一个静态变量, 但是这个变量值为null
package com.powernode.oop13; /** * Singleton是单例的 * * 懒汉式单例模式 */ public class Singleton { // 定义一个静态变量, 但是这个变量值为null private static Singleton s; // 构造方法私有化 private Singleton(){ } // 对外提供一个公开的静态的方法, 用这个方法获取单个实例 public static Singleton get(){ // 为null的时候初始化静态变量, 初始化完成之后, 静态变量就不再是null了, 这个if也就进不去了, 所以只会new一次 if(s == null){ s = new Singleton(); } return s; } }
package com.powernode.oop13; public class SingletonTest01 { public static void main(String[] args) { Singleton s1 = Singleton.get(); Singleton s2 = Singleton.get(); System.out.println(s1 == s2); } }
-
-
构造方法和static关键字相关练习题
package com.powernode.oop14;
/**
* 1. static关键字练习题
* 设计一个人类(Person), 拥有姓名, 年龄, 性别三个属性, 需要统计总人口数. 在每次创建Person对象时, 需要将总人口数加1. 实现这个功能需要使用static
*/
// 定义一个人类
public class Person {
// 姓名
private String name;
// 年龄
private int age;
// 性别
private String gender;
// 总人口数
private static int count;
// 无参构造方法
public Person() {
count++;
}
// 有参构造方法
public Person(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
count++;
}
// 获取总人口数
public static int getCount(){
return count;
}
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 getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
}
package com.powernode.oop14;
public class PersonTest01 {
public static void main(String[] args) {
// 获取总人数
System.out.println("目前的总人口数: " + Person.getCount());
// 创建Person对象
Person person = new Person();
Person person1 = new Person("区区一散修", 20, "男");
// 获取总人数
System.out.println("目前的总人口数: " + Person.getCount());
}
}
package com.powernode.oop14;
/**
* 2. this关键字练习题:
* 设计一个银行卡类(Card), 拥有持卡人姓名, 卡号, 余额三个属性, 实现构造方法, 取款, 存款, 查询余额等方法, 在实现取款和存款方法时, 需要使用this来区分对象的属性和方法的参数.
*/
// 定义一个银行卡类
public class Card {
// 姓名
private String name;
// 卡号
private int number;
// 余额
private int balance;
// 无参构造方法
public Card() {
}
// 有参构造方法
public Card(String name, int number, int balance) {
this.name = name;
this.number = number;
this.balance = balance;
}
// 取款
public void withdraw(int money){
System.out.println("取款中...");
this.setBalance(this.getBalance() - money);
System.out.println("取款成功, 当前余额" + this.balance);
}
// 存款
public void save(int money){
System.out.println("存款中...");
this.setBalance(this.getBalance() + money);
System.out.println("存款成功, 当前余额" + this.balance);
}
// 查询余额
public void query(){
System.out.println("余额查询中...");
System.out.println("当前余额" + this.balance);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
}
package com.powernode.oop14;
public class CardTest01 {
public static void main(String[] args) {
// 创建Card对象
Card card = new Card("张三", 123, 10000);
// 查询余额
card.query();
// 取款
card.withdraw(5000);
// 存款
card.save(1000);
// 查询余额
card.query();
}
}
package com.powernode.oop14;
// 课程编号, 所属学院
import org.w3c.dom.ls.LSOutput;
/**
* 3. 高级练习题:
* 设计一个学生选课系统, 有两个类, 一个是学生类(Student), 一个是课程类(Course), 学生类包括姓名, 学号, 已选课程(根据目前所学, 最多能让学生选一门课)三个属性, 课程类包含课程名称, 授课老师, 课程学分五个属性, 需要设计学生选课和退课的方法, 再设计一个打印某学生具体的选课信息的方法.
*/
// 定义一个学生类
public class Student {
// 姓名
private String name;
// 学号
private String id;
// 已选课程
private Course course;
// 构造方法
public Student() {
}
public Student(String name, String id) {
this.name = name;
this.id = id;
}
public Student(String name, String id, Course course) {
this.name = name;
this.id = id;
this.course = course;
}
// 学生选课
public void selection(Course course){
this.course = course;
System.out.println(this.getName() + "选课成功, 课程是: " + course.getCourseName());
}
// 退课
public void cancel(){
System.out.println(this.getName() + "退课成功, 课程是: " + this.getCourse().getCourseName());
this.course = null;
}
// 输出学生信息
public void display(){
if(this.getCourse() == null){
System.out.println("学号: " + this.getId() + ", 姓名: " + this.getName() + ", 还未选课");
}
else{
System.out.println("学号: " + this.getId() + ", 姓名: " + this.getName() + ", 所选课程名称: " + this.getCourse().getCourseName() + ", 授课老师: " + this.getCourse().getTeacherName());
}
}
// getter和setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Course getCourse() {
return course;
}
public void setCourse(Course course) {
this.course = course;
}
}
package com.powernode.oop14;
// 定义一个课程类
public class Course {
// 课程名称
private String courseName;
// 老师
private String teacherName;
// 构造方法
public Course() {
}
public Course(String courseName, String teacherName) {
this.courseName = courseName;
this.teacherName = teacherName;
}
// 输出课程信息
public void display(){
System.out.println("Course Name: " + this.courseName + ", Teacher Name: " + this.teacherName);
}
// getter和setter
public String getCourseName() {
return courseName;
}
public void setCourseName(String courseName) {
this.courseName = courseName;
}
public String getTeacherName() {
return teacherName;
}
public void setTeacherName(String teacherName) {
this.teacherName = teacherName;
}
}
package com.powernode.oop14;
public class StudentTest01 {
public static void main(String[] args) {
// 创建课程对象
Course course = new Course("java", "动力节点老杜");
// 创建学生对象
Student zhangsan = new Student("张三", "001");
Student lisi = new Student("李四", "002", course);
// 显示学生信息
zhangsan.display();
// 选课
zhangsan.selection(course);
// 显示学生信息
zhangsan.display();
// 退课
zhangsan.cancel();
// 显示学生信息
zhangsan.display();
// 创建课程对象
Course c = new Course("mysql", "动力节点老杨");
zhangsan.selection(c);
zhangsan.display();
zhangsan.cancel();
zhangsan.display();
}
}
3.8. 继承
-
继承作用
- 基本作用: 代码复用
- 方法覆盖和多态机制
-
继承相关术语
- 当B类继承A类时
- A类称为: 父类, 超类, 基类, superclass
- B类称为: 子类, 派生类, subclass
// 语法格式 [修饰符列表] class 类名 extends 父类名{ } // extends翻译为扩展, 表示子类继承父类后, 子类是对父类的扩展 // 以下动物类就是哺乳动物和爬行动物的父类, 哺乳动物是狗, 猫, 人的父类
- 当B类继承A类时
package com.powernode.oop15;
// 人类
public class Person {
// 名字
String name;
// 年龄
int age;
// 性别
String gender;
// 吃
public void eat(){
System.out.println(this.name + "正在吃饭");
}
// 跑
public void run(){
System.out.println(this.name + "正在跑");
}
// getter和setter
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 getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
}
package com.powernode.oop15;
/**
* Teacher类当中有很多的内容在Person类当中已经出现了, 所以为了代码的复用, 可以使用extends关键字, 把Person这个父类当中的公共内容继承到Teacher这个子类当中, 这样Teacher子类当中的重复代码就可以省略不写了, 代码也就得到了复用
* Teacher子类当中有自己独有的内容
*/
// 老师类
public class Teacher extends Person {
// // 姓名
// String name;
// // 年龄
// int age;
// // 性别
// String gender;
// 工资
double salary;
// // 吃
// public void eat(){
// System.out.println(this.name + "正在吃饭");
// }
// // 跑
// public void run(){
// System.out.println(this.name + "正在跑");
// }
// 讲课
public void teach(){
System.out.println(this.name + "正在讲课");
}
// getter和setter
// 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 getGender() {
// return gender;
// }
//
// public void setGender(String gender) {
// this.gender = gender;
// }
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
package com.powernode.oop15;
public class Test01 {
public static void main(String[] args) {
// 创建Person对象
Person person = new Person();
// 调用方法
person.setName("张三");
person.eat();
person.run();
// 创建Teacher对象
Teacher teacher = new Teacher();
// 调用方法
teacher.setName("老杜");
teacher.eat();
teacher.run();
teacher.teach();
}
}
- java只支持单继承, 一个类只能直接继承一个类
- java不支持多继承, 但支持多重继承 [^多层继承]
- 子类继承父类后, 除私有的不支持继承, 构造方法不支持继承, 其他的全部会继承
package com.powernode.oop15;
public class Test02 {
public static void main(String[] args) {
C c = new C();
c.c();
c.a();
c.b();
// 语法错误: i 在 com.powernode.oop15.A 中是 private 访问控制
// java只支持公有继承
// System.out.println(c.i);
}
}
class A extends B {
private int i = 10;
public void a() {
System.out.println("A's a method invoke!");
}
}
class B{
public void b() {
System.out.println("B's b method invoke!");
}
}
class C extends A{
public void c(){
System.out.println("C's c method invoke!");
}
}
// 语法错误: java不支持多继承, 只能直接继承一个类
//class C extends A, B{
//
//}
- 一个类没有显示继承任何类时, 默认继承
java.lang.Object
类
package com.powernode.oop15;
/**
* java中, 一个类没有显示的继承任何类, 默认继承Object
* Object是老祖宗, 是JDK的根类
*/
public class Test03 {
public static void main(String[] args) {
E e = new E();
// 因为E类默认继承Object
String s = e.toString();
System.out.println(s);
}
}
// 默认继承
class E extends Object {
}
- JDK中的帮助文档 [^包含了注释信息, 也就是javadoc注释生成的文档]
- 在oracle官网下载, 依次点击下面选项
- Products
- java
- java download
- JDK21
- Documentation download
- 在oracle官网下载, 依次点击下面选项
3.9. 方法覆盖
-
方法重载 [^overload]
- 什么情况下使用方法重载
- 在一个类中, 如果功能相似, 可以考虑使用方法重载
- 这样做的目的: 代码美观, 方便编程
- 当满足什么条件构成方法重载
- 在同一个类中
- 相同的方法名
- 不同的参数列表
- 类型
- 个数
- 顺序
- 方法重载属于编译阶段的功能
- 什么情况下使用方法重载
-
方法覆盖 [^override]
- 或者叫做方法重写 [^overwrite]
- 什么情况下使用方法覆盖
- 当从父类中继承过来的方法无法满足当前子类的业务需求时
- 当满足什么条件构成方法覆盖
- 方法覆盖发生在具有继承关系的父子类之间
- 具有相同的方法名
- 具有相同的参数列表
- 具有相同的返回值类型
- 关于方法覆盖的细节
- 当子类将父类方法覆盖之后, 将来子类对象调用方法的时候, 一定会执行重写之后的方法
- 在java中, 有一个注解 [^@Override], 这个注解可以在编译阶段检查这个方法是否是重写了父类的方法
@Override
是JDK5开始引入的, 被标注的方法必须是重写父类的方法, 如果不是重写的方法, 编译器会报错- 它只在编译阶段有用, 和运行期无关
- 如果返回类型是引用数据类型, 那么这个返回类型可以是原类型的子类型
- 访问权限不能变低, 可以变高
- 抛出异常不能变多, 可以变少 [^后面学习的时候再说]
- 私有的方法, 以及构造方法不能继承, 因此他们不存在方法覆盖
- 方法覆盖针对的是实例方法, 和静态方法无关 [^讲完多态再说]
- 方法覆盖针对的是实例方法, 和实例变量无关
package com.powernode.oop16;
// 动物类
public class Animal {
// 吃
public void eat(){
System.out.println("动物在吃东西");
}
// 移动
public void move(){
System.out.println("动物在移动");
}
protected Object getObj(int a, String b) throws Exception {
return null;
}
}
package com.powernode.oop16;
public class Bird extends Animal{
/**
* Bird对继承过来的move()方法不满意
* Bird类有权利将move()方法进行重写覆盖
*/
@Override
public void move(){
System.out.println("鸟儿在飞翔");
}
@Override
public String getObj(int a, String b) {
return "a";
}
}
package com.powernode.oop16;
public class OverrideTest01 {
public static void main(String[] args) {
// 创建鸟儿对象
Bird bird = new Bird();
// 调用方法
bird.eat();
bird.move();
}
}
3.10. 多态
- 多态的基础语法
package com.powernode.oop17;
public class Animal {
public void move(){
System.out.println("动物在移动");
}
public void eat(){
System.out.println("正在吃饭");
}
}
package com.powernode.oop17;
public class Cat extends Animal {
@Override
public void move() {
System.out.println("猫在走");
}
/**
* 这个方法行为是子类特有的, 父类没有
*/
public void catchMouse(){
System.out.println("猫在抓老鼠");
}
}
package com.powernode.oop17;
public class Bird extends Animal {
@Override
public void move() {
System.out.println("鸟儿在飞翔");
}
/**
* 这个方法也是子类特有的
*/
public void sing(){
System.out.println("鸟儿在唱歌");
}
}
package com.powernode.oop17;
/**
* 关于基本数据类型之间的转换:
* 第一种
* 小容量转换成大容量, 叫做自动类型转换
* int i = 100;
* long x = i;
*
* 第二种
* 大容量转换成小容量, 不能自动转换, 必须田间强制类型转换符才行, 叫做强制类型转换
* int y = (int)x;
*
* 除了基本数据类型之间的类型转换之外, 对于引用数据类型来说, 也可以进行类型转换. 只不过不叫作自动类型转换和强制类型转换. 我们一般成为向上转型和向下转型
*
*
* 关于java中的向上转型和向下转型
* 向上转型(upcasting): 子 ---> 父 (可以等同看作自动类型转换)
* 向下转型(downcasting)父 ---> 子 (可以等同看作强制类型转换)
*
* 不管是向上还是向下转型, 两种类型之间必须要有继承关系, 编译器才能编译通过, 这是最基本的大前提
*/
public class Test01 {
public static void main(String[] args) {
// 创建对象
Animal a1 = new Animal();
a1.eat();
a1.move();
Cat c1 = new Cat();
c1.eat();
c1.move();
Bird b1 = new Bird();
b1.eat();
b1.move();
/*
* 向上转型(upcasting):
* 1. 子 ---> 父
* 2. 也可以等同看作自动类型转换
* 3. 前提: 两种类型之间要有继承关系
* 4. 父类型引用向子类型对象, 这个就是多态机制最核心的语法
* */
Animal a2 = new Cat();
// Animal a3 = new Bird();
/**
* java程序包括两个重要的阶段
* 第一阶段: 编译阶段
* 在编译的时候, 编译器只知道a2的类型时Animal类型, 因此在编译的时候就会去Animal类中找move()方法, 找到之后, 绑定上去, 此时发生静态绑定, 能够绑定成功, 表示表示编译通过
* 第二阶段: 运行阶段
* 在运行的时候, 堆内存中真实的java对象是Cat类型, 所以move()的行为一定是Cat对象发生的. 因此运行的时候就会自动调用Cat对象的move()方法, 这种绑定成为运行期绑定/动态绑定
*
*
* 因为编译阶段是一种形态, 运行的时候是另一种形态, 因此得名: 多态
*
*/
a2.move();
/**
* 以下代码是编译报错, 因为编译器只知道a2是Animal类型, 去Animal类中找catchMouse()方法了, 结果没有找到, 无法完成静态绑定, 编译报错
*/
// a2.catchMouse();
/**
* 假如现在就是要让a2捉老鼠, 怎么办?
* 向下转型: downcasting(父 ---> 子)
*
* 什么时候考虑使用向下转型
* 当调用的方法是子类中特有的方法
*/
Cat c2 = (Cat)a2;
c2.catchMouse();
// 大前提: 不管是向上还是向下, 两种类型之间必须要有继承关系
// 没有继承关系, 编译报错
// Bird b2 = new Bird();
// Cat c3 = (Cat)b2;
Animal x = new Cat();
// 向下转型
// 为什么编译的时候可以通过? 因为x是Animal类型, Animal和Bird之间存在继承关系, 语法没问题, 所以编译通过了
// 为什么运行的时候出现ClassCastException(类型转换异常)? 因为运行时堆中真实对象时Cat对象, Cat无法转换成Bird, 则出现类型转换异常
/**
* instanceof运算符的语法队则
* 1. instanceof运算符的结果一定是: true/false
* 2. 语法格式
* (引用 instanceof 类型)
* 3. 例如:
* (a instanceof Cat)
* true表示什么?
* a引用指向的对象是Car类型
* false表示什么?
* a引用指向的对象不是Cat类型
*/
// Bird y = (Bird)x;
// 做向下转型之前, 为了避免ClassCastException的发生, 一般建议使用instanceof进行判断
System.out.println(x instanceof Bird);
if(x instanceof Bird){
System.out.println("========");
Bird y = (Bird)x;
}
// 多态
// Animal a = new Cat();
Animal a = new Bird();
a.eat();
// 需求: 程序运行阶段动态确定对象
// 如果对象是Cat, 请抓老鼠
// 如果对象是Bird, 请唱歌
if(a instanceof Cat){
Cat cat = (Cat)a;
cat.catchMouse();
}
else if(a instanceof Bird){
Bird bird = (Bird)a;
bird.sing();
}
}
}
-
软件开发七大原则
- 开闭原则 [^Open-Closed_Principle,OCP]
- 一个软件实体应该对扩展开发, 对修改关闭. 即在不修改原有代码的基础上, 通过添加新的代码来扩展功能. [^最基本的原则,其他原则都是为这个原则服务的]
- 单一职责原则
- 里氏替换原则
- 接口隔离原则
- 依赖倒置原则
- 迪米特原则
- 合成复用原则
- 开闭原则 [^Open-Closed_Principle,OCP]
-
多态在开发中的作用
- 不使用多态机制
package com.powernode.oop18;
/**
* 这个案例没有使用多态机制, 看看设计有什么缺陷?
* 不符合开闭原则 (因为这个功能的扩展时建立在修改Master类的基础上的)
*/
public class Test {
public static void main(String[] args) {
// 创建宠物猫对象
Cat cat = new Cat();
// 创建主人对象
Master master = new Master();
// 主人喂猫
master.feed(cat);
// 创建狗对象
Dog dog = new Dog();
// 主人喂狗
master.feed(dog);
}
}
package com.powernode.oop18;
// 主人类
public class Master {
public void feed(Cat c){
c.eat();
}
public void feed(Dog d){
d.eat();
}
}
package com.powernode.oop18;
// 宠物猫
public class Cat {
public void eat(){
System.out.println("吃鱼");
}
}
package com.powernode.oop18;
public class Dog {
public void eat(){
System.out.println("吃骨头");
}
}
-
- 使用多态机制
package com.powernode.oop19;
/**
* 还是主人喂养宠物的案例, 使用多态机制, 打到OCP原则
*
* 能使用多态尽量使用多态, 尽量面向抽象编程, 不要面向具体编程. 面向抽象编程好处是降低程序的耦合度, 提高扩展力
*/
public class Test {
public static void main(String[] args) {
// 创建宠物
Cat cat = new Cat();
Dog dog = new Dog();
Snake snake = new Snake();
// 创建主人
Master master = new Master();
// 喂养
master.feed(cat);
master.feed(dog);
master.feed(snake);
}
}
package com.powernode.oop19;
// 主人类
public class Master {
public void feed(Pet p){
p.eat();
}
}
package com.powernode.oop19;
// 宠物类(父类)
public class Pet {
public void eat(){
}
}
package com.powernode.oop19;
// 猫类(子类)
public class Cat extends Pet {
@Override
public void eat(){
System.out.println("吃鱼");
}
}
package com.powernode.oop19;
// 狗类(子类)
public class Dog extends Pet{
@Override
public void eat(){
System.out.println("吃骨头");
}
}
package com.powernode.oop19;
// 蛇类(子类)
public class Snake extends Pet {
@Override
public void eat() {
System.out.println("蛇吞象");
}
}
- 方法覆盖遗留的小尾巴
package com.powernode.oop20;
/**
* 方法覆盖针对的是实例方法, 和静态方法无关(方法覆盖和多态机制联合起来才有意义)
* 方法覆盖针对的是实例方法, 和实例变量无关
*/
public class Test {
public static void main(String[] args) {
Animal.test();
Cat.test();
}
// 方法覆盖和多态联合起来才有意义
// 多态: 父类型引用指向子类型对象
// 静态方法本身和多态就是没有关系, 因为多态机制需要对象的参与
// 静态方法既然和多态没有关系, 那么静态方法也就和方法覆盖没有关系了
}
package com.powernode.oop20;
public class Animal {
public static void test(){
System.out.println("Animal's test method invoke");
}
}
package com.powernode.oop20;
public class Cat extends Animal {
// 尝试去重写父类的静态方法
public static void test(){
System.out.println("Cat's test method invoke");
}
}
package com.powernode.oop20;
/**
* 方法覆盖针对的是实例方法, 和静态方法无关
*/
public class Test2 {
public static void main(String[] args) {
A a = new A();
// 实例变量不存在覆盖这一说
// a.name编译阶段绑定的是A类的name属性, 运行的时候也会输出A类的name属性
System.out.println(a.name);
// 没有用多态
B b = new B();
System.out.println(b.name);
}
}
class A{
// 实例变量
String name = "张三";
}
class B extends A{
// 实例变量
String name = "李四";
}
- test
package com.powernode.oop21;
/**
* 1. 创建一个Person类, 在其中定义方法greet(), 用于问候对方, 在此基础上, 创建一个EnglishPerson的子类和一个ChinesePerson的子类分别复写greet()方法, 分别使用英文和中文问候对方. 在main方法中, 创建EnglishPerson对象和一个ChinesePerson对象, 使用greet()方法向对方问候
*/
public class PersonTest01 {
public static void main(String[] args) {
// 创建ChinesePerson对象
ChinesePerson chinese = new ChinesePerson();
chinese.setName("区区一散修");
chinese.greet();
// 创建EnglishPerson对象
EnglishPerson english = new EnglishPerson();
english.setName("stephen curry");
english.greet();
}
}
package com.powernode.oop21;
// Person类(父类, 包含了ChinesePeron和EnglishPerson的公共属性和方法)
public abstract class Person {
String name;
// 构造方法
public Person() {
}
public Person(String name) {
this.name = name;
}
// 问候方法
// abstract 抽象方法, 抽象方法就必须是抽象类
public abstract void greet();
// getter和setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.powernode.oop21;
public class ChinesePerson extends Person {
@Override
public void greet(){
System.out.println("你好, 我的名字叫" + getName());
}
}
package com.powernode.oop21;
public class EnglishPerson extends Person {
@Override
public void greet(){
System.out.println("hello, my name is " + getName());
}
}
package com.powernode.oop22;
/**
* 2. 创建一个Shape类, 在其中定义两个属性length和width, 并提供相应的getter和setter方法来进行属性的访问和修改, 在此基础上, 创建一个Rectangle的子类和一个Square的子类, 并分别复写getArea()方法计算矩形和正方形的面积, 使用多态实现打印出各自的面积
*/
// Shape(父类)
public abstract class Shape {
// 属性
int length;
int width;
// 抽象方法getArea()
public abstract void getArea();
// getter和setter
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
}
package com.powernode.oop22;
// Square(子类)
public class Square extends Shape{
public void getArea(){
if(getLength() != getWidth()){
System.out.println("输入的正方形的长和宽不相等, 请重新输入");
}
else{
System.out.println("正方形的面积: " + getLength() * getWidth());
}
}
}
package com.powernode.oop22;
// Rectangle(子类)
public class Rectangle extends Shape{
public void getArea(){
System.out.println("矩形的面积: " + getLength() * getWidth());
}
}
package com.powernode.oop22;
public class ShapeTest01 {
public static void main(String[] args) {
// 创建对象
Square square = new Square();
// 赋值
square.setLength(20);
square.setWidth(20);
// 创建对象
Rectangle rectangle = new Rectangle();
// 赋值
rectangle.setLength(10);
rectangle.setWidth(5);
// 调用方法
square.getArea();
rectangle.getArea();
}
}
package com.powernode.oop23;
/**
* 3. 创建一个Animal类, 在其中定义方法move(), 用于输出动物的移动形式, 在此基础上, 创建一个Cat的子类和一个Fish的子类分别复写move()方法, 分别输出猫和鱼的移动方式, 在main方法中, 创建一个animal变量, 再在运行时分别将它指定为Cat和Fish的实例, 然后调用它们的move()方法
*/
public class AnimalTest01 {
public static void main(String[] args) {
// 创建对象
Fish fish = new Fish();
Cat cat = new Cat();
// 赋值
fish.setName("鱼");
cat.setName("猫");
// 调用move()方法
fish.move();
cat.move();
}
}
package com.powernode.oop23;
// Animal类(父类)
public abstract class Animal {
// name
String name;
// move方法
public abstract void move();
// getter和setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.powernode.oop23;
// 鱼类(子类)
public class Fish extends Animal {
@Override
public void move(){
System.out.println(getName() + "在游");
}
}
package com.powernode.oop23;
// 猫类(子类)
public class Cat extends Animal {
@Override
public void move(){
System.out.println(getName() + "在跑");
}
}
3.11. super关键字
- super和this要对比来学习
- this代表的是当前对象
- super代表的是当前对象中的父类型特征
- super不能使用在静态上下文中
- super大部分情况下是可以省略的
- 当父类和子类中定义了相同的属性(实例变量)或者相同的方法(实例方法)时, 如果需要在子类中访问父类的属性或者方法时, super.不能省略
package com.powernode.oop24;
// Person类(父类)
public class Person {
// 姓名
String name;
// 年龄
int age;
// 邮箱
String email;
// 住址
String address;
// 构造方法
public Person() {
}
// getter和setter
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 getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
package com.powernode.oop24;
// Teacher类(子类)
public class Teacher extends Person {
// 特有的属性: 工资
double salary;
// 当父类和子类中定义了相同的属性(实例变量)或者相同的方法(实例方法)时, 如果需要在子类中访问父类的属性或者方法时, super.不能省略
String name;
// display方法
public void display(){
// System.out.println(this.getName());
// System.out.println(this.getAge());
// System.out.println(this.getEmail());
// System.out.println(this.getAddress());
// System.out.println(this.getSalary());
System.out.println("姓名: " + super.getName());
System.out.println("年龄: " + super.getAge());
System.out.println("邮箱: " + super.getEmail());
System.out.println("住址: " + super.getAddress());
// salary不是继承的父类中的属性, 所以不能使用super访问
System.out.println("工资: " + this.getSalary());
System.out.println("姓名: " + this.name);
System.out.println("年龄: " + this.age);
System.out.println("邮箱: " + this.email);
System.out.println("住址: " + this.address);
System.out.println("工资: " + this.salary);
}
// this和super都不能使用在静态上下文当中
// public static void test(){
// System.out.println(this);
// System.out.println(super.name);
// }
// 构造方法
public Teacher() {
}
public Teacher(String name, int age, String email, String address, double salary) {
this.name = name;
this.age = age;
this.email = email;
this.address = address;
this.salary = salary;
}
// getter和setter
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
package com.powernode.oop24;
public class Test01 {
public static void main(String[] args) {
// 创建Teacher对象
Teacher t = new Teacher("区区一散修", 20, "123@qq.com", "深山老林", 10000.0);
// 调用display()方法
t.display();
}
}
- this可以单独输出, super不能单独输出
// this本身是一个引用, 所以可以直接输出
System.out.println(this);
// super本身不是一个引用, super只是代表了当前对象的父类型特征那部分, 所以它不能单独的输出
// System.out.println(super);
- super(值)
- 通过子类的构造方法调用父类的构造方法, 目的是为了完成父类型特征的初始化
- 这个语法只能出现在构造方法的第一行
- 当一个构造方法第一行没有显示的调用"super(值)“, 也没有显示的调用"this(值)”, 系统会自动调用super(). 因此一个类中的无参数构造方法建议显示的定义出来
- 在java中只要new对象, Object的无参数构造方法一定会执行
package com.powernode.oop25;
// 银行账户类(父类)
public class Account {
// 账号
String ID;
// 余额
double balance;
// 构造方法
public Account() {
System.out.println("Account无参构造方法执行了");
}
public Account(String ID, double balance) {
this.ID = ID;
this.balance = balance;
}
// getter setter
public String getID() {
return ID;
}
public void setID(String ID) {
this.ID = ID;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
package com.powernode.oop25;
// 信用卡银行账户类(子类)
public class CreditAccount extends Account {
// 子类特有的属性: 信用度
double credit;
// 构造方法
public CreditAccount() {
// 当一个构造方法第一行没有显示的调用"super(值)", 也没有显示的调用"this(值)", 系统会自动调用super()
// super();
System.out.println("CreditAccount无参构造方法执行了");
}
public CreditAccount(String ID, double balance, double credit) {
// 代码重复
// this.ID = ID;
// this.balance = balance;
// 通过子类的构造方法调用父类的构造方法
// 引入这个语法的作用: 1.代码复用 2.为了"模拟"现实世界中的要有儿子, 得先有父亲 3.通过子类构造方法调用父类构造方法是为了给继承过来的父类型特征初始化
super(ID, balance);
this.credit = credit;
}
// getter setter
public double getCredit() {
return credit;
}
public void setCredit(double credit) {
this.credit = credit;
}
}
package com.powernode.oop25;
public class Test01 {
public static void main(String[] args) {
// 创建对象
CreditAccount ca = new CreditAccount("001", 0.0, 0.999);
System.out.println("账号: " + ca.getID());
System.out.println("余额: " + ca.getBalance());
System.out.println("信誉度: " + ca.getCredit());
new CreditAccount();
}
}
3.12. final关键字
- 修饰的类不能被继承
- 修饰的方法不能被覆盖
- 修饰的变量, 一旦赋值不能重新赋值
package com.powernode.oop26;
public class FinalTest01 {
public static void main(String[] args) {
// 3. final修饰的变量一旦赋值, 不能重新赋值
int i = 10;
i = 100;
final double Π = 3.14;
// Cannot assign a value to final variable 'Π'
// 编译报错, 不能重新赋值
// Π = 3.1415926;
final int k;
// 首次初始化是可以的
k = 200;
// 再次重新赋值是不允许的
// k = 10;
}
}
// 1.final修饰的类不能被继承
// 编译报错: Cannot inherit from final 'java. lang. String'
// java.lang.String类被final修饰, 无法继承
//final class MyString extends String{}
// 2.final修饰的方法无法被覆盖
class A{
public void m() {
System.out.println("我是一个m方法, 我这个算法非常优秀, 你们别覆盖");
}
}
class B extends A{
// 编译报错: m()方法被final修室, 无法覆盖
// @Override
// public void m() {
// System.out.println("我认为m方法算法一般, 我要重写");
// }
}
- 修饰的实例变量必须在对象初始化时(构造方法执行时)手动赋值(不允许采用系统默认值)
package com.powernode.oop26;
public class User {
// 4.final修饰的实例变量必须在对象初始化时(构造方法执行时)手动赋值(不允许采用系统默认值)
final String name;
final int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
- 修饰的实例变量一般和static联合使用: 成为常量
- 常量定义:
public static final 数据类型 常量名 = 常量值;
- 常量名全部大写, 每个单词以下划线分割
- 常量定义:
package com.powernode.oop26;
public class Math {
// 5.final修饰的实例变量一般和static联合使用: 成为常量
// 常量定义: public static final 数据类型 常量名 = 常量值;
// 常量名全部大写, 每个单词以下划线分割
public static final double MATH_PAI = 3.1415926;
public static void main(String[] args) {
System.out.println("Π = " + MATH_PAI);
}
}
- 修饰的引用, 一旦指向某个对象后, 不能再指向其他对象, 但指向的对象内部的数据是可以修改的
package com.powernode.oop26;
// 商品类
public class Product {
// 名称
private String name;
// 价格
private double price;
// 构造方法
public Product() {
}
public Product(String name, double price) {
this.name = name;
this.price = price;
}
// getter setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
// display方法
public void display() {
System.out.println("名称: " + getName() + ", 价格: " + getPrice());
}
}
package com.powernode.oop26;
public class ProductTest01 {
public static void main(String[] args) {
// 6.final修饰的引用, 一旦指向某个对象后, 不能再指向其他对象, 但指向的对象内部的数据是可以修改的
// 创建对象
final Product product = new Product("西瓜", 10.0);
// 调用display方法
product.display();
// 报错
// product = new Product("辣条", 1.0);
// 指向的对象的内部内存可以修改, 没问题
product.setName("辣条");
product.setPrice(1.0);
// 调用display方法
product.display();
}
}
3.13. 抽象类
- 无法确定实现的方法, 建议定义为抽象方法, 这样在抽象类中只提供公共代码, 具体实现强行交给子类去做
- 抽象方法定义
- 修饰符列表中添加abstract, 然后不能有方法体, 以分号结束, public和abstract关键字的顺序没有要求
- 抽象类虽然有构造方法, 但是无法实例化, 抽象类中构造方法的作用是给子类实现的
- 抽象类中不一定有抽象方法, 但是如果有抽象方法, 那么要求必须是抽象类
- 一个非抽象的类继承抽象类之后, 必须将抽象类中所有的抽象方法全部重写/实现
- abstract关键字不能和private, final, static关键字并存
package com.powernode.oop27;
// 人类(父类: 所有子类的公共属性+公共方法的一个集合体)
public abstract class Person {
// 姓名
private String name;
// 年龄
private int age;
// 构造方法
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// display()方法, 显示人类的详细信息
public void display(){
System.out.println("姓名: " + this.getName() + ", 年龄: " + this.getAge());
}
// greet()方法, 问候的方法, 不同国家的人问候的方式肯定是不同的, 因此, 具体是怎么问候, Person类是不确定的, 无法实现.
// 针对这种方法既然不确定的实现是什么, 那么就不应该给实现. 注意: 在java中, 只要一个方法带着大括号, 不管大括号中有什么, 只要有大括号就表示一种实现
// 因此像这种无法确定实现的方法, 建议定义为抽象方法
// 抽象方法定义: 修饰符列表中添加abstract, 然后不能有方法体, 以分号结束, public和abstract关键字的顺序没有要求
// 当一个类中有抽象方法, 该类必须是抽象的
public abstract void greet();
// getter setter
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;
}
}
package com.powernode.oop27;
// 一个非抽象的类继承抽象类之后, 必须将抽象类中所有的抽象方法全部重写/实现
public class ChinesePerson extends Person {
// 构造方法
public ChinesePerson(){
super();
}
public ChinesePerson(String name, int age){
super(name, age);
}
// 方法重写/实现
@Override
public void greet() {
System.out.println("你好, 我的名字叫" + this.getName() + ", 我的年龄是" + this.getAge());
}
}
package com.powernode.oop27;
public class EnglishPerson extends Person {
// 构造方法
public EnglishPerson() {
// 默认就有
super();
}
public EnglishPerson(String name, int age) {
super(name, age);
}
// 方法重写/实现
@Override
public void greet() {
System.out.println("hello, my name is " + this.getName() + ", my age is " + this.getAge());
}
}
package com.powernode.oop27;
public class PersonTest01 {
public static void main(String[] args) {
// 创建对象
// 多态, 夫类型引用指向了子类型的对象
Person p1 = new ChinesePerson("区区一散修", 20);
Person p2 = new EnglishPerson("stephen", 36);
// 调用方法
p1.display();
p1.greet();
p2.display();
p2.greet();
// 编译报错: 'Person' is abstract; cannot be instantiated
// 抽象类虽然有构造方法, 但是无法实例化
// 抽象类中构造方法的作用是给子类实现的
// new Person();
}
}
- test
package com.powernode.oop28;
/**
* 1. super, final, 抽象类 综合练习:
*
* 定义一个抽象类Shape, 包含属性: name, color, 抽象方法area(), 非抽象方法display(). 思考为什么area()方法定义为抽象方法?
*
* 定义一个Circle类, 继承Shape类, 包含一个双精度浮点类型实例变量radius, 以及一个构造方法, 该构造方法使用super关键字调用父类Shaper的构造方法, 来初始化color和name, Circle类还实现了抽象方法area(), 用于计算圆形的面积, 定义一个常量类, 常量类中定义一个常量用来专门存储圆周率
*
* 定义一个Rectangle类, 继承Shape类, 包含两个双精度类型实例变量width和height, 以及一个构造方法, 该构造方法使用super调用父类Shape的构造方法, 来初始化color和name. Rectangle类还实现了抽象方法area(), 用于计算矩形的面积
*
* 在程序的main()方法中, 创建一个Circle对象, 一个Rectangle对象, 并分别调用它们的display方法, 输出结果, 调用area()方法输出面积
*/
package com.powernode.oop28;
/**
* 1. super, final, 抽象类 综合练习:
*
* 定义一个抽象类Shape, 包含属性: name, color, 抽象方法area(), 非抽象方法display(). 思考为什么area()方法定义为抽象方法?
*/
// Shape类(父类, 抽象类)
public abstract class Shape {
// 姓名
private String name;
// 颜色
private String color;
// 抽象方法area()
public abstract void area();
// 非抽象方法display()
public void display(){
System.out.println("名称: " + this.getName() + ", 颜色: " + this.getColor());
}
// 构造方法
public Shape() {
}
public Shape(String name, String color) {
this.name = name;
this.color = color;
}
// getter setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
package com.powernode.oop28;
/**
* 定义一个Circle类, 继承Shape类, 包含一个双精度浮点类型实例变量radius, 以及一个构造方法, 该构造方法使用super关键字调用父类Shaper的构造方法, 来初始化color和name, Circle类还实现了抽象方法area(), 用于计算圆形的面积, 定义一个常量类, 常量类中定义一个常量用来专门存储圆周率
*/
public class Circle extends Shape{
// 双精度浮点类型实例变量radius, 表示半径
double radius;
// 方法重写/实现, 计算圆的面积
@Override
public void area() {
System.out.println(this.getName() + "的面积是: " + radius * radius * Final.MATH_PAI);
}
// 构造方法
public Circle() {
}
public Circle(String name, String color, double radius) {
super(name, color);
this.radius = radius;
}
}
// 常量类
class Final{
// 圆周率
final static double MATH_PAI = 3.14;
}
package com.powernode.oop28;
/**
* 定义一个Rectangle类, 继承Shape类, 包含两个双精度类型实例变量width和height, 以及一个构造方法, 该构造方法使用super调用父类Shape的构造方法, 来初始化color和name. Rectangle类还实现了抽象方法area(), 用于计算矩形的面积
*/
public class Rectangle extends Shape{
// 实例变量width
double width;
// height
double height;
// 方法重写/实现, 计算矩形的面积
@Override
public void area() {
System.out.println(this.getName() + "的面积: " + width * height);
}
// 构造方法
public Rectangle() {
}
public Rectangle(String name, String color, double width, double height) {
super(name, color);
this.width = width;
this.height = height;
}
}
package com.powernode.oop28;
/**
* 在程序的main()方法中, 创建一个Circle对象, 一个Rectangle对象, 并分别调用它们的display方法, 输出结果, 调用area()方法输出面积
*/
public class ShapeTest01 {
public static void main(String[] args) {
// 创建对象c1
Shape c1 = new Circle("圆形", "蓝色", 1.0);
// 调用display()方法
c1.display();
// 调用area()方法
c1.area();
// 创建对象r1
Shape r1 = new Rectangle("矩形", "白色", 2.0, 3.0);
// 调用display方法
r1.display();
// 调用area()方法
r1.area();
}
}
3.14. 接口
基础语法
- 接口 [^interface]
- 在java中表示一种规范或契约, 它定义了一组抽象方法和常量, 用来描述一些实现这个接口的类应该具有哪些行为和属性. 接口和类一样, 也是一种引用数据类型
- 定义
[修饰符列表] interface 接口名{
}
- 抽象类是半抽象的, 接口是完全抽象的. 接口没有构造方法, 也没有实例化
- 接口中只能定义: 常量+抽象方法. 接口中的常量的public static final可以省略. 接口中的抽象方法的public abstract可以省略. 接口中所有的方法和变量都是public修饰的 [^这是JDK8之前的语法规则]
package com.powernode.oop29;
// 接口
public interface MyInterface {
// 没有构造方法
// public MyInerface(){}
// 可以省略
// 常量
public static final int num1 = 1;
int num2 = 1;
// 可以省略
// 抽象方法
public abstract void m1();
void m2();
}
package com.powernode.oop29;
public interface MyInterfaceTest01 {
public static void main(String[] args) {
// 这只是一个变量, 是一个引用
// 使用接口类型也可以定义引用
MyInterface myinterface = null;
System.out.println(MyInterface.num1);
System.out.println(MyInterface.num2);
// MyInterface.num2 = 1;
}
}
- 接口和接口之间可以多继承
interface A{
}
interface B{
}
// 接口支持多继承
interface C extends A, B{
}
-
类和接口的关系叫做实现 [^这里的实现也可以等同看作继承]. 使用implements [^实现]关键字进行接口的实现
-
一个非抽象的类实现接口必须将接口中所有的抽象方法全部实现.
-
一个类可以实现多个接口. 语法是: class 类 implements 接口A, 接口B {}
public interface MyInterface {
public abstract void m1();
void m2();
}
interface A{
void a();
}
interface B{
void b();
}
// 接口支持多继承
interface C extends A, B{
void c();
}
class MyInterfaceImpl implements MyInterface, C {
@Override
public void c() {
}
@Override
public void a() {
}
@Override
public void b() {
}
@Override
public void m1() {
}
@Override
public void m2() {
}
}
public interface MyInterfaceTest01 {
public static void main(String[] args) {
// 没有使用多态机制
// MyInterfaceImpl mii = new MyInterfaceImpl();
// mii.a();
// mii.b();
// mii.c();
// mii.m1();
// mii.m2();
// 使用了接口之后, 为了降低程序的耦合度, 一定要让接口和多态联合起来使用
// 父类型的引用指向子类型的对象
MyInterface mi1 = new MyInterfaceImpl();
// mi2.a();
// mi2.b();
// mi2.c();
// 面相接口去调用
mi1.m1();
mi1.m2();
}
}
- Java8之后, 接口中允许出现默认方法和静态方法 [^Java8新特性]
- 引入默认方式是为了解决接口演变问题: 接口可以定义抽象方法, 但是不能实现这些方法. 所有实现接口的类都必须实现这些抽象方法. 这会导致接口升级的问题: 当我们向接口添加或删除一个抽象方法时, 这会破坏该接口的所有实现, 并且所有该接口的用户都必须修改其代码才能适应更改. 这就是所谓的"接口演变"问题.
- 引入的静态方法只能使用本接口名来访问, 无法使用实现类的类名访问. 引入静态方法实际上想表达一个意思, 接口也可以作为工具来使用.
- 引入默认方式是为了解决接口演变问题: 接口可以定义抽象方法, 但是不能实现这些方法. 所有实现接口的类都必须实现这些抽象方法. 这会导致接口升级的问题: 当我们向接口添加或删除一个抽象方法时, 这会破坏该接口的所有实现, 并且所有该接口的用户都必须修改其代码才能适应更改. 这就是所谓的"接口演变"问题.
- JDK9之后允许接口中定义私有的实例方法 [^为默认方法服务的] 和私有的静态方法 [^为静态方法服务的].
- 所有的接口隐式的继承Object. 因此接口也可以调用Object类的相关方法.
public interface MyInterface {
// JDK9之后允许定义私有的实例方法(给默认方法服务的)
private void privateMethod(){
System.out.println("privateMethod执行了");
}
// 默认方法
default void defaultMethod(){
System.out.println("接口中的默认方法defaultMethod执行了");
privateMethod();
}
// JDK9之后允许定义私有的静态方法(给静态方法服务的)
private static void privateStaticMethod(){
System.out.println("privateStaticMethod执行了");
}
// 静态方法
static void staticMethod(){
System.out.println("接口中的静态方法staticMethod执行了");
privateStaticMethod();
}
}
public interface MyInterfaceTest01 {
public static void main(String[] args) {
// 调用默认方法defaultMethod
mi1.defaultMethod();
// 调用静态方法staticMethod
MyInterface.staticMethod();
// 所有的接口隐式的继承Object. 因此接口也可以调用Object类的相关方法.
System.out.println(mi1.toString());
}
}
接口的作用
- 面向接口调用的称为
- 接口调用者
- 面向接口实现的称为
- 接口实现者
- 调用者和实现者通过接口达到了解耦合. 也就是说调用者不需要关心具体的实现者, 实现者也不需要关心具体的调用者, 双方都遵循规范, 面向接口进行开发.
- 面向抽象编程, 面向接口编程, 可以降低程序的耦合度, 提高程序的扩展力
package com.powernode.oop30;
/**
* 这个程序没有使用接口, 分析存在哪些缺点?
* 违背了OCP开闭原则
* Computer类的扩展力差
* 为什么?
* Computer类中使用了HardDrive类, 以及Printer类, 导致Computer类和HardDrive, Printer耦合度太高, Computer类扩展力差.
*/
public class Test {
public static void main(String[] args) {
// 创建硬盘对象
HardDrive hardDrive = new HardDrive();
// 创建电脑对象
Computer computer = new Computer();
// 电脑连接硬盘
computer.conn(hardDrive);
// 创建打印机对象
Printer printer = new Printer();
// 电脑连接打印机
computer.conn(printer);
}
}
package com.powernode.oop30;
// 电脑类
public class Computer {
public void conn(HardDrive hardDrive){
System.out.println("连接设备成功");
hardDrive.read();
hardDrive.write();
}
public void conn(Printer printer){
System.out.println("连接设备成功");
printer.read();
printer.write();
}
}
package com.powernode.oop30;
// 硬盘类
public class HardDrive {
public void read(){
System.out.println("硬盘开始读数据");
}
public void write(){
System.out.println("硬盘开始写数据");
}
}
package com.powernode.oop30;
// 打印机类
public class Printer {
public void read(){
System.out.println("打印机开始读取数据");
}
public void write(){
System.out.println("打印机开始打印文件");
}
}
package com.powernode.oop31;
/**
* 使用了接口, 分析有什么好处?
* 接口是完全抽象的, 要面向接口编程, 接口+多态才能实现降低耦合度, 提高扩展力
*
*/
public class Test {
public static void main(String[] args) {
// 创建电脑对象
Computer computer = new Computer();
// 创建硬盘对象
HardDrive hardDrive = new HardDrive();
// 连接设备
computer.conn(hardDrive);
// 创建打印机对象
Printer printer = new Printer();
// 连接设备
computer.conn(printer);
}
}
package com.powernode.oop31;
// 这是一个抽象的Usb接口, 调用者是Computer, 实现者是HardDrive和Printer, 调用者和实现者都是面相Usb接口写代码的, 接口将Computer和具体的设备解耦合了
public interface Usb {
public abstract void read();
void write();
}
package com.powernode.oop31;
/**
* 电脑类
* 面向Usb接口调用方法即可
* 这是调用者
*/
public class Computer {
public void conn(Usb usb){
System.out.println("连接设备成功");
usb.read();
usb.write();
}
}
package com.powernode.oop31;
// 硬盘是实现者, 面向Usb接口写实现类
public class HardDrive implements Usb {
@Override
public void read() {
System.out.println("硬盘开始读数据");
}
@Override
public void write() {
System.out.println("硬盘开始写数据");
}
}
package com.powernode.oop31;
// 打印机是实现者, 面向Usb接口写实现类
public class Printer implements Usb {
@Override
public void read() {
System.out.println("打印机开始读数据");
}
@Override
public void write() {
System.out.println("打印机开始打印文件");
}
}
- 顾客与厨师
package com.powernode.oop32;
// 食谱菜单
public interface FoodMenu {
// 西红柿炒蛋
void xiHongShiChaoDan();
// 鱼香肉丝
void yuXiangRouSi();
// 油泼面
void youPoMian();
}
package com.powernode.oop32;
// 山东厨师
// 接口的实现者
public class ShanDongCooker implements FoodMenu {
// 姓名
private String name;
// 构造方法
public ShanDongCooker() {
}
public ShanDongCooker(String name) {
this.name = name;
}
// getter setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void xiHongShiChaoDan() {
System.out.println(this.getName() + "做的西红柿炒蛋");
}
@Override
public void yuXiangRouSi() {
System.out.println(this.getName() + "做的鱼香肉丝");
}
@Override
public void youPoMian() {
System.out.println(this.getName() + "做的油泼面");
}
}
package com.powernode.oop32;
// 陕西厨师
// 接口的实现者
public class ShanXiCooker implements FoodMenu {
// 姓名
private String name;
// 构造方法
public ShanXiCooker() {
}
public ShanXiCooker(String name) {
this.name = name;
}
// getter setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void xiHongShiChaoDan() {
System.out.println(this.getName() + "做的西红柿炒蛋");
}
@Override
public void yuXiangRouSi() {
System.out.println(this.getName() + "做的鱼香肉丝");
}
@Override
public void youPoMian() {
System.out.println(this.getName() + "做的油泼面");
}
}
package com.powernode.oop32;
// 顾客面向菜单点菜
// 接口的调用者
public class Customer {
public void order(FoodMenu foodMenu){
System.out.println("顾客开始点菜");
foodMenu.xiHongShiChaoDan();
foodMenu.yuXiangRouSi();
foodMenu.youPoMian();
}
}
package com.powernode.oop32;
public class Test {
public static void main(String[] args) {
// 创建顾客对象
Customer customer = new Customer();
// 创建山东厨师对象
ShanDongCooker shanDongCooker = new ShanDongCooker("山东厨师张三");
// 点菜
customer.order(shanDongCooker);
// 创建陕西厨师对象
ShanXiCooker shanXiCooker = new ShanXiCooker("陕西厨师李四");
// 点菜
customer.order(shanXiCooker);
}
}
接口和抽象类如何选择
- 抽象类和接口虽然在代码角度能达到相同的效果, 但是适用场景不同
- 抽象类
- 主要适用于公共代码的提取, 当多个类有共同的属性和方法时, 为了达到代码的复用, 建议为这几个类提取出来一个父类, 在该父类中编写公共的代码. 如果有一些方法无法在该类中实现, 可以延迟到子类中实现. 这样的类就应该使用抽象类
- 接口
- 主要用于功能的扩展, 例如有很多类, 一些类需要这个方法, 另外一些类不需要这个方法时, 可以将该方法定义到接口中. 需要这个方法的类就去实现这个接口, 不需要这个方法的类就可以不实现这个接口. 接口主要规定的是行为.
- 抽象类
- 注意: 一个类单继承另一个类, 可以同时实现多个接口
- extends在前, implements在后
/**
* 练一练
* 定义一个动物类Animal, 属性包括: name, age. 方法包括display(), eat(). display()方法可以有具体的实现, 显示动物的基本信息. 但因为不同的动物会有不同的吃的方式, 因此eat()方法应该定义为抽象方法, 延迟给子类来实现.
* 定义多个子类, 例如: XiaoYanZi, Dog, YingWu. 分别继承Animal, 实现eat()方法
* 不是所有的动物都会飞, 其中只有XiaoYanZi和YingWu会飞, 请定义一个Flyable接口, 接口中定义fly()方法, 让XiaoYanZi和YingWu都能飞.
* 不是所有的动物都会说话, 其中只有YingWu会说话, 请定义一个Speakable接口, 接口中定义speak()方法, 让YingWu会说话.
* 编写测试程序, 创建各个动物对象, 调用display()方法, eat()方法, 能飞的动物让他飞, 能说话的动物让他说话.
* 注意: 一个类继承某个类的同时可以实现多个接口, class 类 extends 父类 implements 接口A, 接口B{}
* 注意: 当某种类型向下转型为某个接口类型时, 接口类型和该类之间可以没有继承关系, 编译器不会报错的.
*/
package com.powernode.oop33;
/**
* 练一练
* 定义一个动物类Animal, 属性包括: name, age. 方法包括display(), eat(). display()方法可以有具体的实现, 显示动物的基本信息. 但因为不同的动物会有不同的吃的方式, 因此eat()方法应该定义为抽象方法, 延迟给子类来实现.
* 定义多个子类, 例如: XiaoYanZi, Dog, YingWu. 分别继承Animal, 实现eat()方法
*/
public abstract class Animal {
// 姓名
private String name;
// 年龄
private int age;
// display()方法, 具体
public void display(){
System.out.println("动物的名称是: " + this.getName() + ", 动物的年龄是: " + this.getAge());
}
// eat()方法, 抽象
public abstract void eat();
// 构造方法
public Animal() {
}
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
// getter setter
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;
}
}
package com.powernode.oop33;
public class Dog extends Animal {
@Override
public void eat() {
System.out.println(this.getName() + "在啃骨头");
}
// 构造方法
public Dog() {
}
public Dog(String name, int age) {
super(name, age);
}
}
package com.powernode.oop33;
public class XiaoYanZi extends Animal implements Flyable {
@Override
public void eat() {
System.out.println(this.getName() + "在吃虫子");
}
@Override
public void fly() {
System.out.println(this.getName() + "在飞翔");
}
// 构造方法
public XiaoYanZi() {
}
public XiaoYanZi(String name, int age) {
super(name, age);
}
}
package com.powernode.oop33;
public class YingWu extends Animal implements Flyable, Speakable{
@Override
public void eat() {
System.out.println(this.getName() + "在吃爆米花");
}
@Override
public void fly() {
System.out.println(this.getName() + "在飞翔");
}
@Override
public void speak() {
System.out.println(this.getName() + "在说话");
}
// 构造方法
public YingWu() {
}
public YingWu(String name, int age) {
super(name, age);
}
}
package com.powernode.oop33;
public interface Flyable {
public void fly();
}
package com.powernode.oop33;
public interface Speakable {
void speak();
}
package com.powernode.oop33;
public class Test {
public static void main(String[] args) {
// 创建Dog对象
Dog dog = new Dog("狗的", 3);
// 调用display()方法
dog.display();
// 调用eat()方法
dog.eat();
// 会飞的让它飞, 会说话得让它说话
if(dog instanceof Flyable){
// 向下转型, 类也可以转换成接口
// 并且java中有语法规定: 类转换成接口时, 类和接口之间不需要有继承关系, 编译器也不会报错
Flyable flyable = (Flyable)dog;
flyable.fly();
}
if(dog instanceof Speakable){
Speakable speakable = (Speakable)dog;
speakable.speak();
}
// 创建XiaoYanZi对象
XiaoYanZi xiaoYanZi = new XiaoYanZi("小燕子", 1);
// 调用display()方法
xiaoYanZi.display();
// 会飞的让它飞, 会说话得让它说话
if(xiaoYanZi instanceof Flyable){
Flyable flyable = (Flyable)xiaoYanZi;
flyable.fly();
}
if(xiaoYanZi instanceof Speakable){
Speakable speakable = (Speakable)xiaoYanZi;
speakable.speak();
}
// 创建YingWu对象
YingWu yingWu = new YingWu("鹦鹉", 1);
// 调用display()方法
yingWu.display();
// 调用eat()方法
yingWu.eat();
// 会飞的让它飞, 会说话得让它说话
if(yingWu instanceof Flyable){
Flyable flyable = (Flyable)yingWu;
flyable.fly();
}
if(yingWu instanceof Speakable){
Speakable speakable = (Speakable)yingWu;
speakable.speak();
}
}
}
/**
* 练一练
* 假设你正在编写一个游戏, 其中有一些怪兽和英雄, 并且它们都可以进行战斗, 具体来说, 每个角色都有自己的名字, 生命值, 攻击力和防御力, 并且可以进行攻击和防御等操作.
* 请按照以下步骤设计一个程序:
* 1. 创建一个Character接口, 它具有getName(), getHealth(), getAttack(), getDefense(), attack()和defense()六个方法, 分别用于获取角色的名字, 生命值, 攻击力, 防御力, 以及进行攻击和防御操作.
* 2. 创建一个Monster接口, 它继承自Character接口, 具有一个getReward()方法, 返回这个怪物打败后可以获得的奖励
* 3. 创建一个英雄类Hero, 它实现了Character接口, 具有名字, 生命值, 攻击力和防御力属性. 它的attack()和defense()方法用于进行攻击和防御操作, 根据对手的攻击力和自己的防御力计算生命值, 并输出攻击和防御的结果
* 4. 创建一个怪物类MonsterImpl, 它实现了Monster接口, 具有名字, 生命值, 攻击力, 防御力和奖励属性, 它的attack()和defense()方法同样根据对手的攻击力和自己的防御力计算生命值, 并输出攻击和防御的结果. 同时, 如果自己的生命值降到了一定程度以下, 就会发动愤怒效果, 攻击力翻倍.
* 5. 创建一些具体的英雄和怪物, 例如一位攻击力为3, 防御力为3, 生命值为20, 叫做剑士的英雄, 以及一个攻击力为5, 防御力为2, 生命值为20, 奖励为150金币, 叫做骷髅王的怪兽
* 6. 最后, 编写一个Test类, 创建一些角色对象, 模拟一些战斗场景, 并演示攻击和防御效果
*
*/
package com.powernode.oop34;
/**
* 练一练
* 假设你正在编写一个游戏, 其中有一些怪兽和英雄, 并且它们都可以进行战斗, 具体来说, 每个角色都有自己的名字, 生命值, 攻击力和防御力, 并且可以进行攻击和防御等操作.
* 请按照以下步骤设计一个程序:
* 1. 创建一个Character接口, 它具有getName(), getHealth(), getAttack(), getDefense(), attack()和defense()六个方法, 分别用于获取角色的名字, 生命值, 攻击力, 防御力, 以及进行攻击和防御操作.
*/
public interface Character {
// 获取姓名
public String getName();
// 生命值
int getHealth();
// 攻击力
int getAttack();
// 防御力
int getDefense();
/**
* 攻击另一个角色
* @param character 被攻击的对象
*/
void attack(Character character);
/**
* 防御另一个角色的攻击
* @param character 被防御的对象
*/
void defense(Character character);
}
package com.powernode.oop34;
/**
* 2. 创建一个Monster接口, 它继承自Character接口, 具有一个getReward()方法, 返回这个怪物打败后可以获得的奖励
*/
public interface Monster extends Character {
/**
* 获取奖励
* @return
*/
int getReward();
}
package com.powernode.oop34;
/**
* 3. 创建一个英雄类Hero, 它实现了Character接口, 具有名字, 生命值, 攻击力和防御力属性. 它的attack()和defense()方法用于进行攻击和防御操作, 根据对手的攻击力和自己的防御力计算生命值, 并输出攻击和防御的结果
*/
public class Hero implements Character {
// 姓名
private String name;
// 生命值
private int health;
// 攻击力
private int attack;
// 防御力
private int defense;
// 构造方法
public Hero() {
}
public Hero(String name, int health, int attack, int defense) {
this.name = name;
this.health = health;
this.attack = attack;
this.defense = defense;
}
// getter setter
@Override
public String getName() {
return name;
}
@Override
public int getHealth() {
return health;
}
@Override
public int getAttack() {
return attack;
}
@Override
public int getDefense() {
return defense;
}
public void setName(String name) {
this.name = name;
}
public void setHealth(int health) {
this.health = health;
}
public void setAttack(int attack) {
this.attack = attack;
}
public void setDefense(int defense) {
this.defense = defense;
}
/**
* 英雄向怪物发起了攻击, 攻击力3, 怪物进行了防御, 防御力1, 怪物受到了2点伤害, 当前怪物的生命值为x
* 英雄向怪物发起了攻击, 攻击力3, 怪物进行了防御, 防御力1, 怪物受到了2点伤害, 怪物挂了, 英雄得到了100金币的奖励
* 英雄向怪物发起了攻击, 攻击力3, 怪物进行了防御, 防御力1, 怪物没有受到任何伤害
* 英雄向怪物发起了攻击, 攻击力3, 怪物进行了防御, 防御力1, 怪物受到了2点伤害, 当前怪物的生命值为x, 怪物愤怒了, 怪物的攻击力翻倍
*
*/
@Override
public void attack(Character character) {
// 英雄进行了攻击
System.out.print(this.getName() + "向" + character.getName() + "发起了攻击, 攻击力" + this.getAttack() + ", " + character.getName() + "进行了防御, 防御力" + character.getDefense() + ", ");
// 怪物进行了防御
character.defense(this);
}
/**
* 怪物向英雄发起了攻击, 攻击力4, 英雄进行了防御, 防御力1, 英雄受到了3点伤害, 当前英雄的生命值为x
* 怪物向英雄发起了攻击, 攻击力4, 英雄进行了防御, 防御力1, 英雄受到了3点伤害, 英雄挂了, 请重新开始游戏
* 怪物向英雄发起了攻击, 攻击力4, 英雄进行了防御, 防御力1, 英雄没有受到任何伤害
*/
@Override
public void defense(Character character) {
// 英雄还有生命
// 获取受到了多少伤害
int damage = character.getAttack() - this.getDefense();
// 设置英雄的生命值
this.setHealth(this.getHealth() - damage);
System.out.print(this.getName() + "受到了" + damage + "点伤害, ");
if(this.getHealth() <= 0){
System.out.println(this.getName() + "挂了, " + "请重新开始游戏");
}
else if(damage <= 0){
System.out.println(this.getName() + "没有受到任何伤害.");
}
else {
System.out.println("当前" + this.getName() + "的生命值为" + this.getHealth());
}
}
}
package com.powernode.oop34;
/**
* 4. 创建一个怪物类MonsterImpl, 它实现了Monster接口, 具有名字, 生命值, 攻击力, 防御力和奖励属性, 它的attack()和defense()方法同样根据对手的攻击力和自己的防御力计算生命值, 并输出攻击和防御的结果. 同时, 如果自己的生命值降到了一定程度以下, 就会发动愤怒效果, 攻击力翻倍.
*/
public class MonsterImpl implements Monster {
// 姓名
private String name;
// 生命值
private int health;
// 攻击力
private int attack;
// 防御力
private int defense;
// 奖励
private int reward;
// 构造方法
public MonsterImpl() {
}
public MonsterImpl(String name, int health, int attack, int defense, int reward) {
this.name = name;
this.health = health;
this.attack = attack;
this.defense = defense;
this.reward = reward;
}
@Override
public int getReward() {
return reward;
}
@Override
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int getHealth() {
return health;
}
public void setHealth(int health) {
this.health = health;
}
@Override
public int getAttack() {
return attack;
}
public void setAttack(int attack) {
this.attack = attack;
}
@Override
public int getDefense() {
return defense;
}
public void setDefense(int defense) {
this.defense = defense;
}
public void setReward(int reward) {
this.reward = reward;
}
/**
* 怪物向英雄发起了攻击, 攻击力4, 英雄进行了防御, 防御力1, 英雄受到了3点伤害, 当前英雄的生命值为x
* 怪物向英雄发起了攻击, 攻击力4, 英雄进行了防御, 防御力1, 英雄受到了3点伤害, 英雄挂了, 请重新开始游戏
* 怪物向英雄发起了攻击, 攻击力4, 英雄进行了防御, 防御力1, 英雄没有受到任何伤害
*/
@Override
public void attack(Character character) {
// 怪兽进行了攻击
System.out.print(this.getName() + "向" + character.getName() + "发起了攻击, 攻击力" + this.getAttack() + ", " + character.getName() + "进行了防御, 防御力" + character.getDefense() + ", ");
// 英雄进行了防御
character.defense(this);
}
/**
* 英雄向怪物发起了攻击, 攻击力3, 怪物进行了防御, 防御力1, 怪物受到了2点伤害, 当前怪物的生命值为x
* 英雄向怪物发起了攻击, 攻击力3, 怪物进行了防御, 防御力1, 怪物受到了2点伤害, 怪物挂了, 英雄得到了100金币的奖励
* 英雄向怪物发起了攻击, 攻击力3, 怪物进行了防御, 防御力1, 怪物没有受到任何伤害
* 英雄向怪物发起了攻击, 攻击力3, 怪物进行了防御, 防御力1, 怪物受到了2点伤害, 当前怪物的生命值为x, 怪物愤怒了, 怪物的攻击力翻倍
*/
@Override
public void defense(Character character) {
// 怪兽还有生命
// 获取受到了多少伤害
int damage = character.getAttack() - this.getDefense();
// 设置怪兽的生命值
this.setHealth(this.getHealth() - damage);
System.out.print(this.getName() + "受到了" + damage + "点伤害, ");
if(this.getHealth() <= 0){
System.out.println(this.getName() + "挂了, " + character.getName() + "得到了" + this.getReward() + "金币的奖励.");
}
else if(damage <= 0){
System.out.println(this.getName() + "没有受到任何伤害.");
}
else {
System.out.print("当前" + this.getName() + "的生命值为" + this.getHealth());
if(this.getHealth() < 3){
System.out.print(", " + this.getName() + "愤怒了, "+ this.getName() + "的攻击力翻倍");
this.attack *= 2;
}
System.out.println();
}
}
}
package com.powernode.oop34;
public class Test {
public static void main(String[] args) {
// 创建一个英雄对象
Hero hero = new Hero("剑士", 20, 5, 2);
// 创建一个怪物对象
MonsterImpl monsterImpl = new MonsterImpl("骷髅王", 20, 3, 3, 150);
hero.attack(monsterImpl);
monsterImpl.attack(hero);
hero.attack(monsterImpl);
monsterImpl.attack(hero);
hero.attack(monsterImpl);
monsterImpl.attack(hero);
hero.attack(monsterImpl);
monsterImpl.attack(hero);
hero.attack(monsterImpl);
monsterImpl.attack(hero);
hero.attack(monsterImpl);
monsterImpl.attack(hero);
hero.attack(monsterImpl);
monsterImpl.attack(hero);
hero.attack(monsterImpl);
monsterImpl.attack(hero);
hero.attack(monsterImpl);
monsterImpl.attack(hero);
hero.attack(monsterImpl);
}
}
3.15. 类之间的关系
UML
- UML [^UnifiedModelingLanguage,统一建模语言]
- 是一种面向对象软件开发的图形化的建模语言. UML提供了一套通用的图形化符号和规范, 帮助开发人员以图形化的形式表达软件设计和编写的所有关键方面, 从而更好地展示软件系统的设计和实现过程
- UML是一种图形化的语言, 类似于现实生活中建筑工程师画的建筑图纸, 图纸上有特定的符号代表特殊的含义
- UML不是专门为java语言准备的, 只要是面向对象的编程语言, 开发前的设计, 都需要画UML图进行系统设计 [^设计模式,软件开发七大原则等同样也不是只为java语言准备的]
- UML图包括
- 类图 [^ClassDiagram]
- 描述软件系统中的类, 接口, 关系和其属性等
- 用例图[^UseCaseDiagram]
- 描述系统的功能需求和用户与系统之间的关系
- 序列图[^SequenceDiagram]
- 描述对象之间的交互, 消息传递和时序约束等
- 状态图[^StatechartDiagram]
- 描述类或对象的生命周期以及状态之间的转换
- 对象图
- 协作图
- 活动图
- 部署图
- 类图 [^ClassDiagram]
- 常用的UML建模工具有
- StarUML
- RationalRose
- EnterpriseArchitect
- …
StarUML安装
- 安装StarUML
- 安装Node.js
- npm更换阿里云境像源
- 为了让速度变快
npm config set disturl http://npmmirror.com
npm config set registry http://registry.npmmirror.com
- npm安装asar
npm install -g asar
asar --version
- 在dos命令窗口中切换目录到starUML的resources目录
- 注意: 这里的StarUML的路径是刚才安装该软件的路径
d: // 这个是切换到d盘
cd D:\Java\StarUML\StarUML\resources // 该路径要写成你自己安装软件的路径
- 反编译starUML:
asar extract app.asar app
- 修改这个文件:
D:\Java\StarUML\StarUML\resources\app\src\engine\license-manager.js
- 在该文件中搜索:
setStatus(this
- 找到下面的:
setStatus(this,false)
,将false修改为true。 - 将
const result = await UnregisteredDialog.showDialog(remains);
注释 - 并保存
- 修改这个文件:
D:\Java\StarUML\StarUML\resources\app\src\app-context.js
- 在该文件中搜索:
!this.config
- 将该if语句中嵌套的if语句注释掉
- 并保存
-
重新打包
asar pack app app.asar
- 破解成功
类之间的关系
- 泛化关系 [^Generalization]
- is a
- 继承
- 实现关系 [^Realization]
- is like a
- 类实现接口
- 关联关系 [^Association]
- has a
- A中有一个B
- 聚合关系 [^Aggregation]
- 一个类包含, 合成或者拥有另一个类的实例, 而这个实例是可以独立存在的, 聚合关系是一种弱关联关系, 表示整体与部分之间的关系. 例如一个教师有多个学生
- 组合关系 [^composition]
- 是聚合关系的一种特殊情况, 表示整体与部分之间的关系更加强烈, 组合关系指的是一个类包含, 合成或者拥有另一个类的实例, 而这个实例只能同时存在于一个整体对象中, 如果整体对象被摧毁, 那么部分对象也会被摧毁. 例如一个人对应四个肢体.
- 依赖关系 [^Dependency]
- 是一种临时性的关系, 当一个类使用另一个类的功能时, 就会产生依赖关系, 如果一个类的改变会影响到另一个类的功能, 那么这两个类之间就存在依赖关系. 依赖关系是一种较弱的关系, 可以存在多个依赖于同一个类的对象. 例如A类中使用了B类, 但是B类作为A类的方法参数或者局部变量等.
3.16. Object类
- java.lang.Object是所有类的超类. java中所有类都实现了这个类中的方法
- Object类是我们学习JDK类库的第一个类. 通过这个类的学习要求掌握会查阅API [^ApplicationProgramInterface应用程序编程接口]帮助文档
- 现阶段Object类中需要掌握的方法
- toString
- 将java对象转换成字符串
- equals
- 判断两个对象是否相等
- toString
- 现阶段Object类中需要了解的方法
- hashCode
- finalize
- clone
toString()
- 在IDEA中打开java源码中的toString()方法
- 代码演示
package com.powernode.oop37;
/**
* Object类中的toString()方法:
* 1. 设计toString()方法的目的是什么?
* 将java对象转换成字符串的表示形式
*
* 2. toString()方法的默认实现是怎样的?
* public String toString() {
* return getClass().getName() + "@" + Integer.toHexString(hashCode());
* }
* 默认实现是: 完整类名 + @ + 十六进制的数字
* 这个输出结果可以等同看做一个java对象的内存地址
*/
public class DateTest {
public static void main(String[] args) {
DateTest dateTest = new DateTest();
String string = dateTest.toString();
System.out.println(string); // com.powernode.oop37.DateTest@b4c966a
Date date = new Date();
String string1 = date.toString();
// Date重写toString()方法之前: com.powernode.oop37.Date@4e50df2e
// Date重写toString()方法之前: 2024年10月1日
System.out.println(string1); // com.powernode.oop37.Date@4e50df2e
Date date1 = new Date(2024, 9, 27);
String string2 = date1.toString();
System.out.println(string2); // 2024年9月27日
Date date2 = new Date(2024, 9, 28);
// 当println()输出的是一个引用的时候, 会自动调用"引用.toString()"
System.out.println(date2); // 2024年9月28日
System.out.println(date2.toString()); // 2024年9月28日
Date date3 = null;
System.out.println(date3);
// System.out.println(date3.toString()); // 空指针异常, 所以这两种写法还是有一点去别的
// System.out.println(date3);这句话的底层其实是下面这句话, 就是为了避免空指针异常
System.out.println((date3 == null) ? "null" : date3.toString());
}
}
package com.powernode.oop37;
// 自定义的日期类
public class Date {
private int year;
private int month;
private int day;
// 构造方法
public Date() {
this(2024, 10, 1);
}
public Date(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
// getter setter
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
// 重写toString()方法
@Override
public String toString(){
return getYear() + "年" + getMonth() + "月" + getDay() + "日";
}
}
equals()
- equals简单讲解
package com.powernode.oop37;
/**
* Object类中的equals()方法:
* 1. 设计equals()方法的目的是什么?
* 判断两个对象是否相等
* equals方法的返回值是true/false
* true代表两个对象相等
* false代表两个对象不相等
*
* 2. equals()方法的默认实现是怎样的?
* public boolean equals(Object obj) {
* return (this == obj);
* }
* a.equals(b) 表面是a和b的比较, 实际上方法体当中是: this和obj的比较
*
* 3. 关于 == 运算符的运算规则:
* == 永远只有一个运算规则, 永远比较的是变量中保存的值之间的比较
* 只不过有的时候这个值是基本数据类型, 有的时候这个值是对象的内存地址
*
* 4. equals方法为什么要重写?
* 因为Object类中的equals方法在进行比较的时候, 比较的是两个java对象的内存地址
* 我们希望比较的是对象的内容, 只要对象的内容相等, 则认为是相同的
*/
public class DateTest02 {
public static void main(String[] args) {
// ==
int a = 10;
int b = 10;
// true
System.out.println(a == b); // == 运算规则: 比较两个变量中保存的值是否相等
int c = 20;
// false
System.out.println(c == a);
Object obj1 = new Object(); // Object obj1 = 0x12;
Object obj2 = new Object(); // Object obj2 = 0x23;
System.out.println(obj1 == obj2); // false
Date date1 = new Date(2024, 10, 1);
Date date2 = new Date(2024, 10, 1);
System.out.println(date1 == date2); // false
// Date在重写equals方法之前: false
// Date在重写equals方法之前: true
System.out.println(date1.equals(date2));
}
}
package com.powernode.oop37;
// 自定义的日期类
public class Date {
private int year;
private int month;
private int day;
// 构造方法
public Date() {
this(2024, 10, 1);
}
public Date(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
// getter setter
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
// 重写toString()方法
@Override
public String toString(){
return getYear() + "年" + getMonth() + "月" + getDay() + "日";
}
/*
// 重写equals()方法
@Override
public boolean equals(Object obj){
// date1.equals(date2)
// this就是date1 obj就是date2
if(obj == null){
return false;
}
// 程序能走到这里说明obj一定不是null
if(this == obj){
return true;
}
// 程序走到这里说明两个对象的内存地址是不同的
// 判断obj类型是否为Date类型
if(obj instanceof Date){
// this中的年月日
int year1 = this.year;
int month1 = this.month;
int day1 = this.day;
// obj中的年月日
// 如果访问子类中特有的属性, 需要向下转型
Date d = (Date)obj;
int year2 = d.year;
int month2 = d.month;
int day2 = d.day;
if(year1 == year2 && month1 == month2 && day1 == day2){
return true;
}
}
// 程序能走到这里一定是false
return false;
}
*/
// 重写equals()方法, 这个是精简版本
@Override
public boolean equals(Object obj){
if(obj == null) return false;
if(this == obj) return true;
if(obj instanceof Date){
Date d = (Date)obj;
return this.year == d.year && this.month == d.month && this.day == d.day;
}
return false;
}
}
- equals详细讲解
package com.powernode.oop39;
public class User {
// 姓名
private String name;
// 地址
private Address address;
// 构造方法
public User() {
}
public User(String name, Address address) {
this.name = name;
this.address = address;
}
// getter setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
// 重写toString()方法
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", address=" + address +
'}';
}
// 重写equals()方法
@Override
public boolean equals(Object obj) {
// user.equals(user2)
// this就是user, obj就是user2
if(obj == null) return false;
if(this == obj) return true;
if(obj instanceof User){
User user = (User) obj;
if(this.name.equals(user.name) && this.address.equals(user.address)){
return true;
}
// return name.equals(user.getName()) && address.equals(user.getAddress());
}
return false;
}
}
package com.powernode.oop39;
public class Address {
// 城市
private String city;
// 街道
private String street;
// 构造方法
public Address() {
}
public Address(String city, String street) {
this.city = city;
this.street = street;
}
// getter setter
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
// 重写toString()方法
@Override
public String toString() {
return "Address{" +
"city='" + city + '\'' +
", street='" + street + '\'' +
'}';
}
// 重写equals()方法
@Override
public boolean equals(Object obj) {
if(obj == null) return false;
if(this == obj) return true;
if(obj instanceof Address){
Address a = (Address)obj;
return this.city.equals(a.city) && this.street.equals(a.street);
}
return false;
}
}
package com.powernode.oop39;
/**
* equals方法重写需要彻底重写
* 不是只需要重写本类中的equals方法, 如果本类中关联了另外一个引用类型, 另外一个引用类型的equals方法也是需要重写的
* 比如说这里的Address和String这两个引用数据类型, Address类当中就需要重写, 而String类是java源码中已经写好的, 这个里面也是重写了的
*/
public class Test {
public static void main(String[] args) {
// 创建住址对象
Address address1 = new Address("乱星海", "深山老林");
// 创建用户对象
User user = new User("区区一散修", address1 );
// 创建住址对象2
Address address2 = new Address("乱星海", "深山老林");
// 创建用户对象2
User user2 = new User("区区一散修", address2 );
// 只要人名一样, 家庭住址一样, 我就认为是同一个人
// 重写之前结果为false
// 重写之后结果为true
System.out.println(user.equals(user2));
}
}
package com.powernode.oop39;
/**
* 字符串的比较不能使用 ==, 必须使用equals方法进行比较
* 根据源码可知, 字符串String类型已经重写了equals方法
*/
public class Test02 {
public static void main(String[] args) {
String s1 = new String("hello");
String s2 = new String("hello");
// 比较两个字符串是否相等, 不能使用 ==
System.out.println(s1 == s2); // false
// 比较两个字符串是否相等, 应该调用equals方法
System.out.println(s1.equals(s2)); // true
// String有没有重写toString()方法? 已经重写了
String s3 = new String("hehe");
s3.toString();
System.out.println(s3); // 如果String没有重写toString()方法, 结果应该是: java.lang.String@十六进制的数字
}
}
hashCode()
- 返回一个对象的哈希值, 通常作为在哈希表中查找该对象的键值, Object类的默认实现是根据对象的内存地址生成一个哈希码 [^即将对象的内存地址转换成整数作为哈希值], hashCode()方法是为了HashMap, Hashtable, HashSet等集合类进行优化而设置的, 以便更快地查找和存储对象
package com.powernode.oop40;
/**
* 返回一个对象的哈希值, 通常作为在哈希表中查找该对象的键值
* hashCode()方法在Object类中的默认实现
* public native int hashCode();
* 这是一个本地方法, 底层调用了C++写的动态链接库程序: xxx.dll
*/
public class Test01 {
public static void main(String[] args) {
Test01 t = new Test01();
int i = t.hashCode();
System.out.println(i); // 189568618
Test01 t2 = new Test01();
int i2 = t2.hashCode();
System.out.println(i2); // 793589513
System.out.println(new Object().hashCode());
System.out.println(new Object().hashCode());
System.out.println(new Object().hashCode());
System.out.println(new Object().hashCode());
}
}
finalize()
-
当java对象被回收时, 由GC自动调用被回收对象的finalize()方法, 通常在该方法中完成销毁签的准备
-
高版本的JDK是不能用这个方法的, 可以使用低版本, 也可以在IDEA中设置
- 关闭
File
,Setting
,Editor
,Inspections
,Java
,Code maturity
,Usage of API marked for removal
这个选项, 然后点击Apply
,OK
.
- 关闭
package com.powernode.oop40;
/**
* finalize()方法
* 当java对象被回收时, 由GC自动调用被回收对象的finalize()方法, 通常在该方法中完成销毁签的准备
*
* finalize()方法的底层默认实现
* @Deprecated(since="9", forRemoval=true)
* protected void finalize() throws Throwable { }
* 从java9开始, 这个方法被标记已过时, 不建议使用, 作为了解
* 很显然, 这个方法是需要子类重写的
*/
public class Test02 {
public static void main(String[] args) {
for (int i = 0; i < 2000; i++) {
Person person = new Person();
person = null;
// 建议启动垃圾回收器
if(i % 1000 == 0){
System.gc();
}
}
}
}
package com.powernode.oop40;
public class Person {
@Override
protected void finalize() throws Throwable {
System.out.println(this + "即将被回收");
}
}
clone()
- 对象的拷贝 [^浅拷贝,深拷贝]
- protected修饰的只能在同一个包下或者子类中访问
- 只有实现了Cloneable接口的对象才能被克隆
package com.powernode.oop41;
/**
* 1. clone()方法作用
* 对象拷贝, 通常在开发中需要保护原对象数据结构, 通常复制一份, 生成一个新对象, 对新对象进行操作
*
* 2. clone()方法底层默认实现
* protected native Object clone() throws CloneNotSupportedException;
* 受保护的方法, 专门给子类使用的
* 本地方法
* 底层调用C++程序已经可以完成对象的创建了
* 我们现在要解决的问题是: 怎么调用这个方法
*
* 3. 怎么解决clone()方法的调用问题?
* 在子类中重写该clone()方法
* 为了保证clone()方法在任何位置都可以调用, 建议将其修饰符修改为: public
*
* 4. 凡是参加克隆的对象, 必须实现一个标志接口: java.lang.Cloneable
* java中的接口包括两大类
* 一类是: 起到标志的作用, 标志型接口
* 另一类是: 普通接口
*/
public class UserTest01 {
public static void main(String[] args) throws CloneNotSupportedException {
// 创建User对象
User user = new User(20);
System.out.println(user);
// 克隆一个对象
// 报错原因: 因为Object类中的clone()方法是protected修饰的
// protected修饰的只能在: 本类, 同包, 子类中访问
// 但是以下这行代码不满足以上所说的条件
// 这是一种浅克隆
Object obj = user.clone();
System.out.println(user);
// 修改克隆之后的对象的age属性
User copyUser = (User)obj;
copyUser.setAge(100);
System.out.println("克隆之后的新对象的年龄: " + copyUser.getAge()); // 100
System.out.println("原始对象的年龄: " + user.getAge()); // 20
}
}
package com.powernode.oop41;
public class User implements Cloneable {
// 年龄
private int age;
// 构造方法
public User() {
}
public User(int age) {
this.age = age;
}
// getter setter
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
// 重写toString()方法
@Override
public String toString() {
return "User{" +
"age=" + age +
'}';
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
// public void test() throws CloneNotSupportedException {
// this.clone();
// }
}
- 浅克隆 [^只克隆当前的这个对象]
package com.powernode.oop42;
public class User implements Cloneable {
// 姓名
private String name;
// 住址
private Address address;
// 构造方法
public User() {
}
public User(String name, Address address) {
this.name = name;
this.address = address;
}
// getter setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
// 重写toString()方法
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", address=" + address +
'}';
}
// 重写clone()方法
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
package com.powernode.oop42;
//public class Address implements Cloneable {
public class Address {
// 城市
private String city;
// 街道
private String street;
// // 重写克隆方法
// @Override
// public Object clone() throws CloneNotSupportedException {
// return super.clone();
// }
// 构造方法
public Address() {
}
public Address(String city, String street) {
this.city = city;
this.street = street;
}
// getter setter
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
// 重写toString()方法
@Override
public String toString() {
return "Address{" +
"city='" + city + '\'' +
", street='" + street + '\'' +
'}';
}
}
package com.powernode.oop42;
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
// 创建住址对象
Address a = new Address("乱星海", "深山老林");
// 创建User对象
User user1 = new User("区区一散修", a);
// 克隆一个User对象, 向下转型
User user2 = (User)user1.clone();
System.out.println(user1); // User{name='区区一散修', address=Address{city='乱星海', street='深山老林'}}
System.out.println(user2); // User{name='区区一散修', address=Address{city='乱星海', street='深山老林'}}
user2.getAddress().setCity("星宫");
System.out.println("===============");
// 没有达到想到的效果
System.out.println(user1); // User{name='区区一散修', address=Address{city='星宫', street='深山老林'}}
System.out.println(user2); // User{name='区区一散修', address=Address{city='星宫', street='深山老林'}}
}
}
- 深克隆 [^克隆当前对象以及和当前对象有关联的对象]
package com.powernode.oop42;
public class User implements Cloneable {
// 姓名
private String name;
// 住址
private Address address;
// 构造方法
public User() {
}
public User(String name, Address address) {
this.name = name;
this.address = address;
}
// getter setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
// 重写toString()方法
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", address=" + address +
'}';
}
// 重写clone()方法
@Override
public Object clone() throws CloneNotSupportedException {
// 重写方法, 让其达到深克隆的效果
// User要克隆, User对象关联的Address对象也需要克隆一份
Address copyAddress = (Address)this.getAddress().clone();
User copyUser = (User)super.clone();
copyUser.setAddress(copyAddress);
return copyUser;
}
}
package com.powernode.oop42;
public class Address implements Cloneable {
// 城市
private String city;
// 街道
private String street;
// 重写克隆方法
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
// 构造方法
public Address() {
}
public Address(String city, String street) {
this.city = city;
this.street = street;
}
// getter setter
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
// 重写toString()方法
@Override
public String toString() {
return "Address{" +
"city='" + city + '\'' +
", street='" + street + '\'' +
'}';
}
}
package com.powernode.oop42;
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
// 创建住址对象
Address a = new Address("乱星海", "深山老林");
// 创建User对象
User user1 = new User("区区一散修", a);
// 克隆一个User对象, 向下转型
User user2 = (User)user1.clone();
System.out.println(user1);
System.out.println(user2);
user2.getAddress().setCity("星宫");
System.out.println("===============");
// 这样才达到了想要的效果
System.out.println(user1); // User{name='区区一散修', address=Address{city='乱星海', street='深山老林'}}
System.out.println(user2); // User{name='区区一散修', address=Address{city='星宫', street='深山老林'}}
}
}
3.17. 访问控制权限
修饰符 | 同一个类 | 同一个包 | 子类 | 所有类 |
---|---|---|---|---|
private | √ | |||
缺省 | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
-
private
- 私有的, 只能在本类中访问
-
缺省
- 默认的, 同一个包下可以访问
-
protected
- 受保护的, 子类中都可以访问 [^受保护的通常都是给子孙用的]
-
public
- 公共的, 在任何位置都可以访问
-
类中的属性和方法访问控制权限共有四种
-
类的访问控制权限只有两种
- public 和 缺省
-
访问控制权限不能修饰局部变量
package com.powernode.oop35;
public class User {
// 私有的
private String name;
// 缺省的
int age;
// 受保护的
protected String email;
// 公共的
public String address;
public void doSome(){
// 在本类中都可以访问
System.out.println(name);
System.out.println(age);
System.out.println(email);
System.out.println(address);
}
}
package com.powernode.oop35;
public class UserTest {
public static void main(String[] args) {
User user = new User();
// private 只能在本类中访问, 这个编译报错
// System.out.println(user.name);
System.out.println(user.age);
System.out.println(user.email);
System.out.println(user.address);
}
}
package com.powernode.oop36;
import com.powernode.oop35.User;
public class UserTest02 {
public static void main(String[] args) {
User user = new User();
// System.out.println(user.age);
// System.out.println(user.email);
// public 修饰的, 任何位置都可以访问
System.out.println(user.address);
}
}
package com.powernode.oop36;
import com.powernode.oop35.User;
public class Vip extends User{
public void test(){
// 缺省的
// System.out.println(this.age);
// 受保护的
System.out.println(this.email);
}
}
3.18. 内部类
-
内部类
- 定义在一个类中的类
-
什么时候使用
- 一个类用到了另一个类, 而这两个类的联系比较密切, 但是如果把这两个类定义为独立的类, 不但增加了类的数量, 也不利于代码的阅读和维护
- 内部类可以访问外部类的私有成员, 这样可以将相关的类和接口隐藏在外部类的内部, 从而提高封装性
- 匿名内部类是指没有名字的内部类, 通常用于定义一个只使用一次的类, 比如在事件处理中
-
内部类包括
- 静态内部类
- 和静态变量一个级别
- 实例内部类
- 和实例变量一个级别
- 局部内部类
- 和局部变量一个级别
- 匿名内部类
- 特殊的局部内部类, 没有名字, 只能使用一次
- 静态内部类
-
静态内部类
package com.powernode.oop43;
/**
* 静态内部类: 可以把静态内部类当做静态变量来看
*
* 结论: 在静态内部类当中, 无法直接访问外部类的实例相关的数据
*/
public class OuterClass {
// 静态变量
private static int i = 100;
// 实例变量
private int j = 200;
// 静态方法
public static void m1(){
System.out.println("外部类的m1静态方法执行了");
}
// 实例方法
public void m2(){
System.out.println("外部类的m2实例方法执行了");
}
// 静态内部类
// 对于静态内部类来说: 访问控制权限修饰符在这里都可以使用
public static class InnerClass{
public void m3(){
System.out.println(i);
// 实例的报错
// System.out.println(j);
m1();
// m2();
}
public static void m4(){
System.out.println(i);
// 实例的报错
// System.out.println(j);
m1();
// m2();
}
}
}
package com.powernode.oop43;
public class OuterClassTest {
public static void main(String[] args) {
// 创建内部类对象
OuterClass.InnerClass innerClass = new OuterClass.InnerClass();
// 调用方法m3
// 实例方法通过new对象, 得到引用, 引用.方法名访问
innerClass.m3();
// 调用方法m4
// 静态方法通过类名.方法名访问
OuterClass.InnerClass.m4();
}
}
- 实例内部类
package com.powernode.oop44;
/**
* 实例内部类: 等同可以看做实例变量
*
* 结论: 实例内部类中可以直接访问外部类中的实例成员和静态成员
* 也可以使用访问权限修饰符修饰
*/
public class OuterClass {
// 实例变量
private int i = 100;
// 实例方法
public void m1(){
System.out.println("外部类的实例方法m1执行了");
}
// 静态变量
private static int j = 200;
// 静态方法
public static void m2(){
System.out.println("外部类的实例方法m2执行了");
}
// 实例内部类
public class InnerClass {
public void x(){
System.out.println(i);
System.out.println(j);
m1();
m2();
}
}
}
package com.powernode.oop44;
public class OuterClassTest {
public static void main(String[] args) {
OuterClass.InnerClass innerClass = new OuterClass().new InnerClass();
innerClass.x();
}
}
- 局部内部类
package com.powernode.oop45;
/**
* 局部内部类: 等同于局部变量
*
* 结论:
* 局部内部类能不能访问外部类的数据, 取决于局部内部类所在的方法.
* 如果这个方法是静态的, 只能访问外部类中静态的
* 如果这个方法是实例的, 可以都访问
*
* 局部内部类不能使用访问权限修饰符修饰
*
* 局部内部类在访问外部的局部变量时, 这个局部变量必须是final的, 只不过从JDK8开始, 这个final关键字不需要提供了, 系统自动提供
*/
public class OuterClass {
// 静态变量
private static int k = 1;
// 实例变量
private int f = 2;
public void m1(){
// 局部变量
int i = 100;
// 局部内部类
class InnerClass{
// 实例方法
public void x(){
System.out.println(k);
System.out.println(f);
System.out.println(i);
}
}
// new对象
InnerClass innerClass = new InnerClass();
innerClass.x();
}
public static void m2(){
// 局部内部类
class InnerClass{
public void x(){
System.out.println(k);
// 无法从静态上下文中引用非静态 变量 f
// System.out.println(f);
}
}
}
}
package com.powernode.oop45;
public class OuterClassTest {
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
outerClass.m1();
}
}
- 匿名内部类
package com.powernode.oop46;
/**
* 匿名内部类: 没有名字的类, 只能使用一次
*/
public class Test {
public static void main(String[] args) {
// 创建电脑对象
Computer computer = new Computer();
// computer.conn(new Printer());
// 以下conn方法参数上的代码做了两件事:
// 第一: 完成了匿名内部类的定义
// 第二: 同时实例化了一个匿名内部类的对象
computer.conn(new Usb() {
// 接口的实现
@Override
public void read() {
System.out.println("打印机开始读取数据");
}
@Override
public void write() {
System.out.println("打印机开始打印");
}
});
}
}
class Computer{
public void conn(Usb usb){
usb.read();
usb.write();
}
}
interface Usb{
void read();
void write();
}
编写一个接口的实现类
//class Printer implements Usb{
// @Override
// public void read() {
// System.out.println("打印机开始读取数据");
// }
//
// @Override
// public void write() {
// System.out.println("打印机开始打印");
// }
//}
3.19 面向对象考试
第一部分:
1.B 2.B 3.A 4.C 5.C
6.D 7.C 8.D 9.C 10.A
第二部分:
1.X 2.√ 3.√ 4.X 5.X
6.√ 7.√ 8.√ 9.√ 10.√
第四部分: