文章目录
访问修饰符
面向对象编程三大特征
封装
继承
super 关键字
方法重写/覆盖(override)
多态
Java 的动态绑定机制
多态数组
Object 类详解
零钱通项目
访问修饰符
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 |
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 方法
1.当对象被回收时,系统自动调用该对象的 finalize 方法。子类可以重写该方法,做一些释放资源的操作
2.什么时候被回收:当某个对象没有任何引用时,则 jvm 就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁该对象前,会先调用 finalize 方法。
3.垃圾回收机制的调用,是由系统来决定(即有自己的 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; } } }
收工!!!🔴🟢🟡