面向对象中级部分已整理完成,建议收藏 🔴🟢🟡
访问修饰符
Java 提供四种访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限(范围)
:
- 公开级别: 用 public 修饰,对外公开
- 受保护级别: 用 protected 修饰,对子类和同一个包中的类公开
- 默认级别: 没修饰符号,向同一个包的类公开
- 私有级别: 用 private 修饰,只有类本身可以访问,不对外公开
修饰符 | 同类 | 同包 | 子类) | 其他包 |
---|---|---|---|---|
public | Y | Y | Y | Y |
protected | Y | Y | Y | N |
default | Y | Y | N | N |
private | Y | N | N | N |
注意:
🔴 修饰符可以用来修饰类中的属性,成员方法以及类
🟢 只有默认的和public才可以修饰类!
🟡 成员方法的访问权限和属性完全一致
面向对象编程三大特征
面向对象编程有三大特征:封装、继承和多态
封装
什么是封装?
封装就是把抽象出的数据(属性)和对数据的操作(方法)封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作,方法,才能对数据进行操作
封装的好处?
- 隐藏实现细节
- 可以对数据进行验证,保证安全合理
封装的实现步骤(3步)
- 将属性私有化 private
- 提供一个公用的 public 方法 set ,用于对属性判断并赋值
- 提供一个公用的 public 方法 get,用于获取属性的值
练习:对属性进行封装,并且对数据进行安全验证,从而体会封装的设计思想,两个类,一个供测试
1 用户类🔴🟢🟡
public class Account {
private String name;
private int balance;
private String password;
public String getName() {
return name;
}
public void setName(String name) {
if(name.length()>=2 && name.length()<=4){
this.name = name;
}else {
System.out.println("名字只能是2位到4位");
this.name = "笨蛋";
}
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
if(balance>=20){
this.balance = balance;
}else {
System.out.println("余额最少为20");
}
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
if(password.length() == 6){
this.password = password;
}else {
System.out.println("密码必须是6位");
this.password = "000000";
}
}
public void info(){
System.out.println("信息如下: "+" 名字:"+name+" 余额:"+balance+" 密码:"+password);
}
}
2 测试类🔴🟢🟡
public class AccountTest {
public static void main(String[] args) {
Account account = new Account();
account.info();
account.setName("asdffggh");
account.setBalance(1);
account.setPassword("123");
account.info();
account.setName("liu");
account.setBalance(2000);
account.setPassword("123456");
account.info();
}
}
结果:
继承
为什么需要继承 ?
继承可以解决代码复用,让编程更加靠近人类的思维,当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过 extends 来声明继承父类即可
生活中的继承
语法
class 父类 {
}
class 子类 extends 父类 {
}
继承带来的好处
代码的复用性提高了
代码的扩展性和维护性提高了
继承细节
- 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访 问,要通过父类提供公共的方法去访问
- 子类必须调用父类的构造器, 完成父类的初始化
- 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译无法通过
- 如果希望指定去调用父类的某个构造器,则显式的调用一下 : super(参数列表)
- super 在使用时,必须放在构造器第一行,(super 只能在构造器中使用)
- super() 和 this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器
- java 所有类都是 Object 类的子类, Object 是所有类的基类
- 父类构造器的调用不限于直接父类!将一直往上追溯直到 Object 类(顶级父类)
- 子类最多只能继承一个父类(指直接继承),即 java 是单继承机制
- 不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系
继承的底层分析
super 关键字
super 代表父类的引用,用于访问父类的属性、方法、构造器
this关键字:指向自己的引用
super关键字:用来引用当前对象的父类
super 的便利
1.调用父类的构造器的好处(分工明确父类属性由父类初始化,子类的属性由子类初始化)
2.当子类中有和父类中的成员(属性和方法)重名时,为了访问父类的成员。必须通过 super。如果没有重名,使用 super、this、直接访间是一样的效果!
3.supe的访向不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用supe去访问爷爷类的成员;如果多个基类(上级类)中都有同名的成员,使用 super访向遵循就近原则。A->B->C,当然也需要遵守访问权限的相关规则
方法重写/覆盖(override)
重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!🔴🟢🟡
注意:子类方法不能缩小父类方法的访问权限
方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。
1.方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,称为方法的重载(Overloading)。
2.方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,称为重写(Overriding)。
3.方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
多态
方法或对象具有多种形态,是面向对象的第三大特征
多态是建立在封装和继承基础之上的 🔴🟢🟡
重写和重载就体现多态,难点是对象的多态(熟记👇的话)
- 一个对象的编译类型和运行类型可以不一致
- 编译类型在定义对象时就确定了,不能改变
- 运行类型是动态的,可变化的
- 编译类型取决于定义时,=的左边,运行类型 =的右边
多态的前提是:两个对象(类)存在继承关系
向上转型
本质:父类引用指向了子类对象
语法:父类类型 引用名 = new 子类类型();
特点:编译类型看左边,运行类型看右边。 在访问权限允许可以调用父类的所有成员 不能调用子类中特有的成员 最终运行结果看子类的具体实现!
向下转型
本质:子类引用指向了父类
语法:子类类型 引用名 = (子类类型) 父类引用;
特点:只能强转父类的引用,不能强转父类的对象,要求父类的引用必须指向的是当前目标类型的对象,当向下转型时,可以调用子类类型中所有的成员
属性没有重写之说!属性的值看编译类型
instanceOf 比较操作符,用于判断对象的运行类型是否为 XX 类型或 XX
Java 的动态绑定机制
Java 重要特性: 动态绑定机制 🔴🟢🟡
- 当调用对象方法的时候,该方法会和该对象的内存地址/编译类型 进行动态绑定
- 当调用对象属性的时候,没有动态绑定机制,哪里声明,哪里使用
多态数组
数组的定义类型为父类类型,里面保存的实际元素类型为子类类型
🔴🟢🟡
Person[] persons = new Person[5];
persons[0] = new Person("jack", 20);
persons[1] = new Student("mary", 18, 100);
persons[2] = new Student("smith", 19, 30.1);
persons[3] = new Teacher("scot", 30, 10000);
persons[4] = new Teacher("king", 60, 20000);
System.out.println(persons[i].say());//动态绑定机制
//person[i] 编译类型是 Person ,运行类型是是根据实际情况有 JVM 来判段
person[i] 编译类型是 Person ,运行类型是是根据实际情况有 JVM 来判段
Object 类详解
== 和 equals 的对比
== 是一个比较运算符,可以判断基本以及引用类型,基本类型判断的是值相等,引用类型判断的是地址相等
equals 是 Object 类中的方法,只能用于引用类型,默认判断地址是否相等,实际上子类往往重写该方法,用于判断内容是否相等
如何重写 equals 方法
🔴🟢🟡
//重写 Object 的 equals 方法
public boolean equals(Object obj) {
//判断如果比较的两个对象是同一个对象,则直接返回 true
if(this == obj) {
return true;
}
//类型判断
if(obj instanceof Person) {//是 Person,我们才比较
//进行 向下转型, 因为我需要得到 obj 的 各个属性
Person p = (Person)obj;
return this.name.equals(p.name) && this.age == p.age && this.gender == p.gender;
}
//如果不是 Person ,则直接返回 false
return false;
}
hashCode 方法
- 两个引用,如果指向的是同一个对象,则哈希值肯定是一样的!
- 两个引用,如果指向的是不同对象,则哈希值是不一样的
- 哈希值主要根据地址号来的!不能认为哈希值等于地址
toString 方法
1) 基本介绍
默认返回:全类名+@+哈希值的十六进制,子类往往重写 toString 方法,用于返回对象的属性信息
2) 重写 toString 方法,打印对象或拼接对象时,都会自动调用该对象的 toString 形式
3) 当直接输出一个对象时,toString 方法会被默认的调用, 比如 System.out.println(sb); 就会默认调用sb.toString()
finalize 方法
- 当对象被回收时,系统自动调用该对象的 finalize 方法。子类可以重写该方法,做一些释放资源的操作
- 什么时候被回收:当某个对象没有任何引用时,则 jvm 就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁该对象前,会先调用 finalize 方法。
- 垃圾回收机制的调用,是由系统来决定(即有自己的 GC 算法), 也可以通过 System.gc() 主动触发垃圾回收机制
零钱通项目
项目需求 :
防一个微信零钱通的压缩版,要实现文字界面,收益支出(支出细节)等的记录,对数据合法校验,比如没钱了,还消费就会提示,以及查看退出时提醒是否确定退出
项目思路 :
化繁为简,先使用判断以及分支语句,实现可以与用户互动的界面,然后依次实现对应的四个功能即可,最后做数据的校验以及函数的封装,界面优化,用户体验优化……过程中细节拉满,开始吧
具体实现 :
上代码
🔴🟢🟡
/**
* @Author: liu sen
* @Version: 1.0
* @Date: 2021/09/01/23:34
*/
public class SmallChangeApp {
public static class SmallChangeAPP {
public static void main(String[] args) {
new SmallchangeOOP().smallMenu();
}
}
}
🔴🟢🟡
/**
* @Author: liu sen
* @Version: 1.0
* @Date: 2021/09/01/23:33
*/
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;
public class SmallchangeOOP {
//while循环开关
boolean loop = true;
//创建扫描器对象
Scanner scanner = new Scanner(System.in);
String key = "";
String det = "================零钱通明细=================";
//钱
double money = 0;
//余额
double balance = 0;
//时间
Date date = null;
//关于退出
String k = "";
//零钱通菜单,先实现选择骨架,然后分别实现对应方法,化繁为简
public void smallMenu() {
do {
System.out.println("\n=================零钱通菜单=================");
System.out.println("\t\t\t1. 明细");
System.out.println("\t\t\t2. 收益入账");
System.out.println("\t\t\t3. 消费");
System.out.println("\t\t\t4. 退 出");
System.out.print("请选择[1~4]: ");
key = scanner.next();//接收输入
//使用 switch 分支控制输入后的走向 接收参数 Key
switch (key) {
case "1": {
this.details();
break;
}
case "2": {
this.splicing();
break;
}
case "3": {
this.pay();
break;
}
case "4": {
this.exit();
}
default: {
System.out.println(" ");
}
}
} while (loop);
System.out.println("小猪,你已退出系统,再见 Bye~~~");
}
//明细方法
public void details() {
System.out.println(det);
}
//收入
public void splicing() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");//日期对象
System.out.print("\n收益入账金额::");
money = scanner.nextDouble();
if (money <= 0) {
System.out.println("弄啥来,输入不合法");
return;
}
balance += money;
date = new Date();
det += "\n收益入账\t" + money + "\t" + sdf.format(date) + "\t" + balance;
}
//支出
public void pay() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
String str;
System.out.print("消费金额:");
money = scanner.nextDouble();
System.out.print("消费详情:");
if (money <= 0 || money > balance) {
System.out.println("弄啥来,你丫的,好好输入");
return;
}
str = scanner.next();
balance -= money;
//字符串拼接
det += "\n" + str + "\t" + money + "\t" + sdf.format(date) + "\t" + balance;
}
//退出系统
public void exit() {
while (true) {
System.out.println("小猪,确定要退出系统吗? y/n ");
k = scanner.next();
if ("y".equals(k) || "n".equals(k)) {
break;
} else {
System.out.println("输入有误,重新输入");
}
}
if (k.equals("y")) {
loop = false;
}
}
}
收工!!!🔴🟢🟡