面向对象(进阶篇)
1. this关键字的使用(当前对象)
-
遇到的问题,以及对应的解决方案
-
在声明属性的setXxx方法时,如果形参和对应的属性重名,如何在方法中区分两者
-
使用this关键字可以解决上述问题。
-
具体来说:使用this修饰的是属性,不使用this修饰的是局部变量
-
-
this可以调用的结构:成员变量,方法,构造器
-
this的理解:this代表的是当前的对象(在方法的调用时),代表正在创建的对象(在构造器调用时)
-
this调用属性和方法
【针对于方法内的】
- 一般情况:我们通过对象a调用方法,可在方法内调用当前对象a的属性和其他方法,此时我们可以用
this.
来表示当前属性或方法属于对象a,但是一般情况下都省略this. - 特殊情况:如果方法名中的形参列表和属性的名字一样,可以使用
this.
的方式来区分谁是成员变量,谁是局部变量
【针对于构造器的】
- 一般情况:我们通过构造器创建对象,可在构造器调用当前对象的属性和方法,此时我们可以用
this.
来表示当前属性或方法属于该对象,但是一般情况下都省略this. - 特殊情况:如果构造其中的形参列表和对象的属性的名字一样,可以使用
this.
的方式来区分谁是成员变量,谁是局部变量
- 一般情况:我们通过对象a调用方法,可在方法内调用当前对象a的属性和其他方法,此时我们可以用
-
this调用构造器
-
使用构造器调用对象:格式:this(形参列表)
this()调用空参
-
在类的构造器中调用其他构造器(不能
自己调用自己
,形成递归无法退出) -
要求构造器调用只能
写在首行
-
一个构造器只能存在
一个构造器的调用
-
1.1 练习
- 男孩女孩this练习
public void marry(Girl girl) {
System.out.println(girl.getName() + "愿意和我结婚吗?");
girl.marry(this);
}
public void shout() {
if (age > 22) {
System.out.println("我终于可以结婚了");
} else {
System.out.println("我只能和你谈恋爱");
}
}
public void marry(Boy boy) {
System.out.println(boy.getName() + "我愿意");
}
public int compare(Girl girl) {
if (this.age > girl.age) {
return 1;
} else if (this.age < girl.age) {
return -1;
} else {
return 0;
}
}
package com.ygc.oop03._thisExer;
/**
* @author: YGC
* @createTime: 2023/09/04 8:58
* @blogs: <a href="https://www.cnblogs.com/ygcDiary"></a>
* @description: 测试
*/
public class BoyGirlTest {
public static void main(String[] args) {
Boy boy = new Boy("姚贵春", 24);
Girl girl = new Girl("杨泽荣", 17);
boy.marry(girl);
Girl girl1 = new Girl("rose", 18);
girl.compare(girl);
if (girl.getAge() > girl1.getAge()) {
System.out.println(girl.getName() + "大");
} else if (girl.getAge() < girl1.getAge()) {
System.out.println(girl1.getName() + "大");
} else {
System.out.println("一样大");
}
}
}
- 账户练习
package com.ygc.oop03._thisExer01;
/**
* @author: YGC
* @createTime: 2023/09/04 9:12
* @blogs: <a href="https://www.cnblogs.com/ygcDiary"></a>
* @description: 银行账户
*/
public class Account {
private double balance;
public Account(double init_balance) {
this.balance = init_balance;
}
public double getBalance() {
return balance;
}
public void deposit(double amt) {
balance += amt;
System.out.println("存款成功:" + amt + "元");
}
public void withdraw(double amt) {
if (amt > balance) {
System.out.println("您的余额不足!");
} else {
balance -= amt;
System.out.println("取款成功");
}
}
}
package com.ygc.oop03._thisExer01;
/**
* @author: YGC
* @createTime: 2023/09/04 9:22
* @blogs: <a href="https://www.cnblogs.com/ygcDiary"></a>
* @description: 银行
*/
public class Bank {
private Customer[] customers;
private int numberOfCustomer;
public Bank() {
customers = new Customer[10];
}
public void addCustomer(String f, String l) {
Customer customer = new Customer(f, l);
customers[numberOfCustomer] = customer;
numberOfCustomer++;
System.out.println("增加账户成功:" + f + l);
}
public int getNumOfCustomer() {
return numberOfCustomer;
}
public Customer getCustomer(int index) {
if (index < 0 || index > numberOfCustomer) {
return null;
} else {
return customers[index];
}
}
}
package com.ygc.oop03._thisExer01;
/**
* @author: YGC
* @createTime: 2023/09/04 9:18
* @blogs: <a href="https://www.cnblogs.com/ygcDiary"></a>
* @description: 客户
*/
public class Customer {
private String firstName;
private String lastName;
private Account account;
public Customer(String f, String l) {
this.firstName = f;
this.lastName = l;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
}
}
package com.ygc.oop03._thisExer01;
/**
* @author: YGC
* @createTime: 2023/09/04 9:38
* @blogs: <a href="https://www.cnblogs.com/ygcDiary"></a>
* @description: 测试
*/
public class BankTest {
public static void main(String[] args) {
Bank bank = new Bank();
bank.addCustomer("jack", "11");
//取钱,通过匿名对象,创建账户,并通过构造器,赋值balance为1000
bank.getCustomer(0).setAccount(new Account(1000));
bank.getCustomer(0).getAccount().withdraw(100);
System.out.println("账户余额为:" + bank.getCustomer(0).getAccount().getBalance());
//通过账户来进行取钱方法
bank.addCustomer("rose", "22");
bank.getCustomer(1).setAccount(new Account(10000));
bank.getCustomer(1).getAccount().withdraw(100);
System.out.println(bank.getCustomer(1).getAccount().getBalance());
}
}
- 内存解析:
2. 继承性
2.1 问题的引入
-
在定义类A,在定义了类B时,发现类B和类A的功能相似,考虑使用继承,B继承A的功能
-
定义了类B,C,D等,B,C,D有相同的属性和方法,则考虑将相同的方法抽取,封装到类A中,让B,C,D继承
同时B,C,D中的对应的属性和方法就可以删除了
2.2 继承性的好处
- 减少了代码的冗余
- 继承的出现,更有利于功能的扩展,提高代码的复用性
- 继承描述了事物之间的所属关系,
is-a
,由此可见,父类更通用,子类更具体 - 实际上,封装性并不受继承的影响,继承了私有属性,仍然不能访问
- 代码实例
public class Extend_Exer {
public static void main(String[] args) {
Person person = new Person();
person.name = "jack";
person.age = 20;
Student student = new Student();
System.out.println(student.name = "ygc");
System.out.println(student.age = 12);
}
}
public class Person {
String name;
int age;
public void eat() {
System.out.println("吃饭");
}
}
public class Student extends Person {
// private String name;
// private int age;
private int grade;
// public void eat() {
// System.out.println("吃饭");
// }
}
2.3 默认的父类
java,没有显示的的声明其父类时,默认继承java.lang.object
//获取student的父类
System.out.println(student.getClass().getSuperclass());//class com.ygc.extend01.Person
//获取person的父类
System.out.println(person.getClass().getSuperclass());//class java.lang.Object
2.4 补充说明
- 支持
多级层
继承(直/间接父类) - java子父类是相对的
- java的一个父类可以声明多个子类,但是一个子类只能继承
一个父类
2.5 权限修饰符的访问范围
修饰符 | 本类内部 | 本包内 | 其他包的子类 | 其他包非子类 |
---|---|---|---|---|
private | √ | × | × | × |
缺省 | √ | √ | × | × |
protected | √ | √ | √ | × |
public | √ | √ | √ | √ |
3.方法的重写(overwrite / override)
- 为什么需要重写:
子类在继承了父类之后,获得了父类的所有属性和方法,但是父类的方法可能存在功能不完善的缺点,子类继承的时候,需要对父类该方法进行覆写、覆盖
-
何为重写:
- 方法的重写的定义:子类对父类继承的方法的覆盖
-
重新应该遵循的规则:
- 方法的声明格式:权限修饰符 返回值类型 方法名(形参列表){}
- 重写的规则依据方法的定义的规则:
- 父类被重写的方法应该与子类重写的方法名和形参列表保持一致
- 子类重写的方法的权限修饰,不小于父类的权限修饰符
- 子类不能重写父类中的
private
方法
- 子类不能重写父类中的
- 返回值类型:
- 无返回值类型时:父类无返回值,子类应该也无返回值
- 有返回值时:
- 返回值是基本数据类型时:子类与父类的返回值类型相同
- 返回值是引用数据类型时:子类的返回值可以是
父类的返回值
,或者父类的其他子类
或本身
- 重写的方法抛出的异常都类型相同,或者是父类的子类。
- 重载和重写的区别:
- 重载:”两同一不同“:类相同,方法名相同,参数列表不同
- 重写:发生在继承之后,子类覆盖父类的方法
4. Super
- 为什么需要super呢?
-
在子类继承父类之后,父类中的方法如果发生重写,在子类其他的方法中,无法在调用父类已经写好的方法,这显然是不符合逻辑的,这时候,
super
就能解决这样的问题。 -
子类在继承父类之后发现子类和父类中定义了同名的属性,可以通过super来区分这两个变量
-
如何调用:
- 使用super关键字即可
- 调用属性、方法:
- 子类在
继承父类
之后,使用super关键字,直接调用父类中的属性或者方法,如果不添加super关键字,则默认先找子类,没有此方法或者属性时才会去父类找这个方法或属性子类构造器默认有一个super()
。 - 一般情况下可以省略,如果子类重写了父类的方法或子类和父类出现了
同名的属性
时,必须通过super的方法调用父类的方法,或者同名的属性
- 子类在
- 调用构造器
- super和this一样的要求
- super和this调用构造器,只能出现一个(他俩都必须在首行)
- 如果子类没有写调用构造器,子类会默认调用一个空参的父类的构造器
- 调用属性、方法:
Student_Super studentSuper1 = new Student_Super(213, "sadad"); //00000000000000
public Student_Super(int id, String school) { }
public Person() { System.out.println("00000000000000"); }
- 使用super关键字即可
- 在定义子类的对象时,同时初始化父类的属性(调用父类的构造器)
//通过super的方式,将父类拿到的值,在子类中进行初始化,达到共享值的目的
Student_Super studentSuper1 = new Student_Super("ygc", 18);
System.out.println(studentSuper1.getAge());
System.out.println(studentSuper1.getName());
public Student_Super(String name, int age) {
super(name, age);
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
- super的含义:父类的
实例:透支(super)
package super_Exer;
public class CheckAccountTest {
public static void main(String[] args) {
CheckAccount checkAccount = new CheckAccount(1, 500, 0.045, 600);
checkAccount.withdraw(700);
}
}
- CheckAccount(继承父类account)
package super_Exer;
public class CheckAccount extends Account {
private double overdraft;
public CheckAccount(int id, double balance, double annualInterestRate) {
super(id, balance, annualInterestRate);
}
public CheckAccount(int id, double balance, double annualInterestRate, double overdraft) {
super(id, balance, annualInterestRate);
this.overdraft = overdraft;
}
public void withdraw(double amount) {
if (amount < getBalance()) {
System.out.println("您可以直接取款!");
super.withdraw(amount);
} else {
if (overdraft + getBalance() < amount) {
System.out.println("您的可用额度不足");
} else {
//取出所有的的钱
overdraft -= amount - getBalance();
super.withdraw(getBalance());
System.out.println("取款成功!您的剩余额度是" + overdraft);
}
}
}
}
- Account
package super_Exer;
public class Account {
private int id;
private double balance;
private double annualInterestRate;
public Account(int id, double balance, double annualInterestRate) {
this.id = id;
this.balance = balance;
this.annualInterestRate = annualInterestRate;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public double getBalance() {
return balance;
}
public double getAnnualInterestRate() {
return annualInterestRate;
}
public void setAnnualInterestRate(double annualInterestRate) {
this.annualInterestRate = annualInterestRate;
}
public double getMonthlyInterest() {
return annualInterestRate / 12;
}
public void withdraw(double amount) {
if (balance < amount) {
System.out.println("余额不足!");
System.out.println("余额为:" + balance);
} else {
balance -= amount;
System.out.println("取款成功!余额为:" + balance);
System.out.println("您的月利率是:" + getMonthlyInterest());
}
}
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
} else {
System.out.println("你输入的有误");
}
}
}
- AccountTest
package super_Exer;
public class AccountTest {
public static void main(String[] args) {
Account account = new Account(1, 30000.0, 0.45);
account.withdraw(40000);
}
}
5. 多态性
-
Java中多态的体现:
- 父类的引用指向子类的对象:
- 声明一个父类的对象,却构造了一个子类的对象。
-
多态的应用:
- 虚拟方法调用: 虚拟方法的调用,编译器认为是父类,实际是调用子类的方法
Person person = new Man(); person.eat();//调用的是子类的方法
-
多态的使用前提:
- 要有继承关系
- 要有方法的
重写
-
多态的适用性:
- 仅适用于
方法
,不适用属性
- 仅适用于
-
多态的好处:
- 在开发中使用最多的是:用父类作为方法的形参,即使增加了新的子类,也不需要改变方法,提高了扩展性,符合
开闭原则
【开闭原则】:
- 对扩展开放,对修改关闭
- 软件中各种组件如:类,模块,功能等,应该不修改现有代码的基础上增加新的功能
- 在开发中使用最多的是:用父类作为方法的形参,即使增加了新的子类,也不需要改变方法,提高了扩展性,符合
-
多态的弊端:
- 在内存中实际是存在子类对象的,但是无法直接调用子类的属性和特殊的方法【父类是一个泛化的概念,无法指定为一个特殊子类,所以不能调用子类特有的属性和方法也是合情合理的】
-
虚方法的调用:编译看左边,运行看右边,属性不存在多态
6. 向下转型
【解决多态弊端:】
- 向上转型:可以类比为自动类型提升(多态)
- 向下转型:可以理解为强制类型转换
- 指向对空间的同一个对象
- 向下转型可能会出现:类型转换的异常
:ClassCastException
编译器不会报错 - 建议在向下转型的时候加入
instanceof
关键字判断:提升程序的健壮性
* 如果判断结果为真,那么其父类,超类都能成立
package duotai_gainian;
/**
* @author: YGC
* @createTime: 2023/09/28 14:25
* @blogs: <a href="https://www.cnblogs.com/ygcDiary"></a>
* @description:
*/
public class PersonTest01 {
public static void main(String[] args) {
Person p1 = new Man();
//向下转型:指向对空间的同一个对象
Man man = (Man) p1;
man.earnMoney();
/*
* 向下转型可能会出现:类型转换的异常:ClassCastException
* 编译器不会报错
* */
Person p2 = new Woman();
//Man w2 = (Man) p2;
//w2.earnMoney();//
System.out.println("=====================");
/*
* 建议在向下转型的时候加入instanceof关键字判断
* 避免出现错误
* */
if (p2 instanceof Man) {
Man w2 = (Man) p2;
System.out.println("w2对象是一个男人");//不输出
}
if (p2 instanceof Woman) {
System.out.println("w2对象是一个女人");//输出
}
}
}
- 练习:
package duotai_Exer.exer03;
/**
* @author: YGC
* @createTime: 2023/09/28 15:31
* @blogs: <a href="https://www.cnblogs.com/ygcDiary"></a>
* @description:
*/
public class Per {
public void method(Person e) {
System.out.println(e.getInfo());
if (e instanceof Person) {
System.out.println("a person");
}
if (e instanceof Student) {
System.out.println("a Student");
System.out.println("a person");
}
if (e instanceof Graduate) {
System.out.println("a graduated student" + "a student" + "a person");
}
}
public static void main(String[] args) {
Per per = new Per();
per.method(new Person());
}
}
class Person {
protected String name = "person";
protected int age = 50;
public String getInfo() {
return "Name: " + name + "\n" + "age: " + age;
}
}
class Student extends Person {
protected String school = "pku";
public String getInfo() {
return "Name: " + name + "\nage: " + age
+ "\nschool: " + school;
}
}
class Graduate extends Student {
public String major = "IT";
public String getInfo() {
return "Name: " + name + "\nage: " + age
+ "\nschool: " + school + "\nmajor:" + major;
}
}
- instanceOf的使用
package duotai_Exer.exer04;
/**
* @author: YGC
* @createTime: 2023/09/28 15:49
* @blogs: <a href="https://www.cnblogs.com/ygcDiary"></a>
* @description:
*/
public class Exer4 extends Person {
public static void meeting(Person... ps) {
for (int i = 0; i < ps.length; i++) {
ps[i].eat();
ps[i].toilet();
if (ps[i] instanceof Man) {
Man man = (Man) ps[i];
man.smoke();
}
if (ps[i] instanceof Woman) {
Woman woman = (Woman) ps[i];
woman.makeup();
}
System.out.println("================");
}
}
public static void main(String[] args) {
Exer4 exer4 = new Exer4();
meeting(new Man(), new Woman());
}
}
package duotai_Exer.exer04;
public class Man extends Person {
public void eat() {
System.out.println("男人大口吃饭");
}
@Override
public void toilet() {
System.out.println("男人上厕所");
}
public void smoke() {
System.out.println("男人抽烟");
}
}
package duotai_Exer.exer04;
public class Person {
public void eat() {
System.out.println("人吃饭");
}
public void toilet() {
System.out.println("上洗手间");
}
}
package duotai_Exer.exer04;
public class Woman extends Person {
@Override
public void eat() {
System.out.println("女人小口吃饭");
}
@Override
public void toilet() {
System.out.println("女人上厕所");
}
public void makeup() {
System.out.println("女人化妆");
}
}
【总结:】
- 编译属性看左边
- 运行方法看右边
- 属性不覆盖,多态谈方法
7. Object中常用的方法
-
clone():克隆一个新的对象实际上是构建了一个新的对象,在堆内存中实际new了一个(不常用)
-
finalize:在jdk8之后不推荐使用这个方法,这个方法是在垃圾回收器GC执行之前,做一些操作(不常用)
-
equals():
-
适用性:
- 任何
引用数据类型
都可以使用
User u1 = new User(); u1.setName("ygc"); u1.setAge(18); User u2 = new User(); u2.setName("yzr"); u2.setAge(18); System.out.println(u1.equals(u2));//false
- 说明:比较两个一一引用数据类型是否相等,调用的是object中的equals()方法
- equals和==的区别:
- ==:就是一个运算符
- 基本数据类,引用数据类型都可以使用
- 对于基本数据类型:==比较数值
- 对于引用数据类型:==比较地址值
- equals():
- 是一个方法
- 只可以对引用数据类型
- ==:就是一个运算符
String str1 = new String("hello"); String str2 = new String("hello"); System.out.println(str1 == str2);//false System.out.println(str1.equals(str2));//true
- 对于String、File、Date和各种包装类,他们都重写了equals方法,用于比较两个对象
实体
是否相等
//重写equals方法,比较两个对象实体的属性是否相同 @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof User) { User user = (User) obj; return this.age == user.age && this.name.equals(name); } return false; }
- IDEA自动实现(alt+insert+equals)
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; return age == user.age && Objects.equals(name, user.name); }
- 在实际开发中,Object提供的equals方法并不能满足需求,需要使用重写,判断属性是否相同
- 任何
-
-
toSting :(自述方法,重写后跟返回对象的是实体内容)
- 默认的使用Object中定义的方法,但是实际开发中是需要重写的
- 子类的声明:
- 对于String、File、Date和各种包装类,已经重写了调用时,返回当前实体的内容