目录
目录
8.11.5 Java的动态绑定机制 dynamic binding(非常非常重要)
8.1 - 8.3 IDE(集成开发环境)--IDEA
常用快捷键(Mac)
1) 删除当前行 command + delete
2)复制当前行 command + D
3)添加或取消注释 command + /
4)导入该行需要的类
5)格式化 command + option + L
6)快速运行 shift + control
7)生成构造器、get set方法、tostring等 control + enter
8)查看一个类的层级关系
9)定位到方法 command + B
10)自动分配变量名 .var
8.4 包 package
8.4.1 包的三大作用
1. 区分相同名字的类
2. 当类很多时,可以很好的管理类
3. 控制访问范围
8.4.2 包基本语法
package com.hspedu;
package关键字,表示打包;com.hspedu表示包名
8.4.3 包的本质
包的本质实际上就是创建不同的文件夹/目录来保存类文件
8.4.4 包的命名
命名规则:只能包含数字、字母、下划线、小圆点,但数字不能开头,不能是关键字或保留字。
命名规范:一般是 小写字母 + 小圆点;
一般是 com.公司名.项目名.业务模块名
8.4.5 常用的包
一个包下包含很多的类,Java中常用的包有:
1)java.lang.* 基本包,默认引入,无需再次引入
2)java.util.* 系统提供的工具包,工具类,使用其中的Scanner、Arrays类
3)java.net.* 网络包,网络开发
4)java.awt.* Java的界面开发,GUI
8.4.6 如何引入包
引入一个包的目的是要使用该包下的类
import java.util.Scanner;//只是引入一个类Scanner
import java.util.*;//表示将util包的所有类引入,不建议使用
8.4.7 注意事项和使用细节
1)package的作用是声明当前类所在的包,需要放在类的最上面,一个类中做多只有一句package
2)import指令 位置放在package的下面,在类定义前面,可以有多句且无顺序要求
8.5 访问修饰符 modifier
8.5.1 基本介绍
Java提供四种访问控制修饰符号,用于控制方法和属性的访问权限:
1)public公开级别:对外公开
2)protected受保护级别:对子类和同一个包中的类公开
3)默认级别:没有修饰符号,向同一个包的类公开
4)private私有级别:只有类本身可以访问,不对外公开
8.5.2 4种访问修饰符的访问范围
访问级别 | 访问控制修饰符 | 同类 | 同包 | 子类 | 不同包 | |
1 | 公开 | public | ✓ | ✓ | ✓ | ✓ |
2 | 受保护 | protected | ✓ | ✓ | ✓ | ✕ |
3 | 默认 | \ | ✓ | ✓ | ✕ | ✕ |
4 | 私有 | private | ✓ | ✕ | ✕ | ✕ |
8.5.3 使用的注意事项
1)修饰符可以用来修饰类中的属性,成员方法以及类
2)只有默认的和public才能修饰类!且遵循访问权限
3)子类中访问权限?
4)成员方法的访问规则和属性完全一样
8.6 面向对象编程三大特征
8.6.1 基本介绍
面向对象编程三大特征:封装、继承和多态
8.6.2 封装介绍
封装(encapsulation)就是把抽象的数据(属性)和对数据的操作(方法)封装在一起,数据被保护在内部,程序的其他部分只有通过授权的操作(方法),才能对数据进行操作。
8.6.3 封装的理解和好处
1)隐藏实现细节:方法(连接数据库)<--- 调用(传入参数...)
2)可以对数据进行验证,保证安全合理
8.6.4 封装的实现步骤
1)将属性进行私有化private(不能直接修改属性)
2)提供一个公共的set方法,用于对属性判断并赋值
3)提供一个公共的get方法,用于获取属性的值
public void setXxx(类型 参数名){ //Xxx表示某个属性
//加入数据验证的业务逻辑
属性 = 参数名;
}
public void getXxx(类型 参数名){ //权限判断,Xxx表示某个属性
return xx;
}
8.7 快速入门案例
案例:
不能随便(直接)查看人的年龄,工资等隐私,并对设置的年龄进行合理性验证。年龄合理(必须在1-120)就设置,否则给默认年龄。
public class Encapsulation {
public static void main(String[] args} {
Person person = new Person("jack", 80, 5000);//age >= 120则会报错
System.out.println(person.info());
}
}
class Person {
//属性
public String name;
private int age;
private double sal;
//构造器
public Person(){
//若使用无参构造器,分别初始化属性,则会经过set方法验证,不会出现下面有参构造器的情况
}
public Person(String name, int age, double sal){
//将set方法写在构造器中,仍可以验证;替换this.name = name;等
//why?
//如果使用this.name = name;形式,则创建对象输入实际参数时,会跳过set验证方法,导致无
//法验证数据(如年龄区间,名字长度),所以须将set方法写入构造器。
setName(name);//等价于this.name = name;
setAge(age);
setSal(sal);
}
//set get方法
public String getName(){
return name;
}
public void setName(String name){
//加入对数据的校验,相当于增加了业务逻辑
if(name.length() >= 2 && name.length() <= 6) {
this.name = name;
} else {
System.out.println("名字长度不对,将默认名字");
this.name = "无名人";
}
}
public int getAge(){
return age;
}
public void setAge(int age){
//加入对数据的校验,相当于增加了业务逻辑
if(age >= 1 && age <= 120) {
this.age = age;
} else {
System.out.println("年龄不对,将默认年龄");
this.age = 18;
}
}
public double getSal(){
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
//返回属性信息
public String info() {
return "信息为 name = " + name + " age = " + age + " sal = " + sal;
}
8.7.1 将构造器和setXxx结合
看上面代码
8.8 面向对象编程 -- 继承 extends
8.8.1 为什么需要继承
两个或多个类的属性和方法有很多相同的,如何解决?-- 继承(提高代码复用性、拓展性和维护性)
8.8.2 继承基本介绍和示意图
继承可以解决代码复用,让我们的编程更加靠近人类思维。当多个类存在相同的属性和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends来声明继承父类即可。
8.8.3 继承的基本语法
class 子类 extends 父类 { // 子类自动拥有父类定义的属性和方法
}
8.8.4 继承的深入讨论/细节问题
1)子类继承类所有的属性和方法,非私有的属性和方法可以在子类直接访问,但是私有属性和方法在子类中要通过父类提供的公共方法去访问
2)子类必须调用父类的构造器,完成父类的初始化
3)当创建子类对象时,不管使用过子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用super(参数列表);去指定使用父类的哪个构造器完成对父类的初始化工作,否则编译无法通过。
4)如果希望指定去调用父类的某个构造器,则显式的调用一下:super(参数列表);
5)super(参数列表);使用时,必需在构造器中使用并且放在第一行
6)super 和 this 都只能放在构造器第一行,所以不能共存
7)Java所有类都是object类的子类,object是所有类的基类
8)父类构造器的调用不限于直接父类,将一直往上追溯直到object类(顶级父类)
9)子类最多只能直接继承一个父类,即Java中是单继承机制
10)不能滥用继承,子-父类之间必须满足is - a 的逻辑关系
8.8.5 继承的本质分析(重要)
案例:
创建子类对象时内存中发生什么?(当子类对象创建好后,建立查找对象)
8.9 super关键字
8.9.1 基本介绍
super代表父类引用,用于访问父类的属性、方法、构造器
8.9.2 基本语法
1. super.属性名;指访问父类的属性,但不能访问父类的private属性
2. super.方法名(参数列表);指访问父类的方法,但不能范围父类的private方法
3. super(参数列表);只能放在构造器第一句,只能出现一句
8.9.3 super给编程带来的便利/细节
1)调用父类的构造器好处(分工明确,父类属性有父类初始化,子类属性由子类初始化)
2)当子类中有和父类中的成员重名时,为了访问父类的成员,必须通过super。如果没有重名,使用super、this和直接 访问效果一样。
3)super的访问不限于直接父类,若多个基类(上级类)都有重名成员,则遵循就近原则
8.9.4 super 与 this 比较
区别 | this | super | |
1 | 访问属性 | 本类开始 | 父类开始 |
2 | 调用方法 | 本类开始 | 父类开始 |
3 | 调用构造器 | 调用本类构造器,且放首行 | 调用父类构造器,且放首行 |
4 | 特殊 | 表示当前对象 | 子类中访问父类对象 |
8.10 方法重写/覆盖 override
8.10.1 基本介绍
方法重写(覆盖)就是子类有一个方法,和父类的某个方法的名称、返回类型、参数一样,那么我们就说子类的这个方法覆盖了父类的方法
8.10.2 注意事项和使用细节
1)子类的方法的形参列表,方法名称,要和父类方法的形参列表,方法名称完全一致
2)子类方法的返回类型和父类方法返回类型一样,或是父类返回类型的子类。如父类返回类型为Object,子类返回类型为String,是可以的
3)子类方法不能缩小(即可以一样或扩大)父类方法的访问权限
重载(overload)与 重写(override)
名称 | 发生范围 | 方法名 | 形参列表 | 返回类型 | 修饰符 |
overload | 本类 | 必须一样 | 类型、个数或者顺序至少有一个不同 | 无要求 | 无要求 |
override | 父子类 | 必须一样 | 类型、个数或者顺序至少有一个不同 | 子类重写的方法返回类型和父类一致,或为其子类 | 子类方法不能缩小父类方法的访问范围 |
8.11 面向对象编程 -- 多态 ploy
8.11.1 问题
master类 给 animal类(含有fish、bone等子类) 喂 food类(含有cat、dog等子类)
传统方法代码冗余,复用性不高,不利于维护 -- 引出 多态
传统方法输出语句为:主人 给 猫/狗/猪 喂 鱼/骨头/苞谷(会写很多行)
多态:主人 给 动物 喂 食物
public void feed(Animal animal, Food food) {
System.out.println("animal = " + animal + " food= " + food);
System.out.println(animal.getName() + " 吃 " + food.getName());
}
8.11.2 多态基本介绍
方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础上的。
8.11.3 多态的具体体现
1)方法的多态
重写 和 重载
2)对象的多态(核心)
1. 一个对象的编译类型和运行类型可以不一致
2. 编译类型在定义对象是,就确定了,不能改变
3. 运行类型可以改变,可以通过get class()查看
4. 编译类型看 定义时 = 号 的左边,运行类型看 = 号 的右边
public class PolyObject {
public static void main(String[] args) {
Animal animal = new Dog();//animal 编译类型为Animal ,运行类型为Dog
animal.cry();//运行看运行类型,输出 "Dog cry() 小狗在叫..."
animal = new Cat();//animal 编译类型为Animal ,运行类型为Cat
animal.cry();//运行看运行类型,输出 "Dog cry() 小猫在叫..."
}
}
public class Animal {
public void cry() {
System.out.println("Animal cry() 动物在叫...");
}
}
public class Cat extends Animal {
public void cry() {
System.out.println("Cat cry() 小猫在叫...");
}
}
public class Dog extends Animal {
public void cry() {
System.out.println("Dog cry() 小狗在叫...");
}
}
8.11.4 多态注意事项和细节讨论
1)多态的前提是:两个对象(类)存在继承关系
2)多态的向上转型
1. 本质:父类的引用指向了子类的对象(如上代码)
2. 语法:父类类型 引用名 = new 子类类型();
3. 特点:编译类型看左边,运行类型看右边
可以调用父类中的所有成员(即调用看编译类型,需遵守访问权限)
不能调用子类中特有成员
最终运行效果看子类的具体实现!
3)多态向下转型(实现调用子类特有方法)
1. 语法:子类类型 引用名 = (子类类型)父类引用;
2. 只能强转父类的引用,不能强转父类的对象
3. 要求父类的引用必须指向的是当前目标类型的对象
4. 当向下转型后,可以调用子类类型中所有的成员
4)属性没有重写之说,属性的值看编译类型
5)instanceOf 比较操作符,用于判断对象的运行类型是否为XX类型或XX类型的子类型
public class PolyDeatail {
public static void main(String[] args) {
BB bb = new BB();
System.out.println(bb instanceof BB);// true
System.out.println(aa instanceof AA);// true
AA aa = new BB(); // 向下
System.out.println(aa instanceof AA);// true 子类型
System.out.println(aa instanceof BB);// true
Object obj = new Object();
System.out.println(obj instanceof AA);// false
String str = "hello";
System.out.println(str instanceof obj);// true 子类型
}
}
class AA {
}
class BB extends AA {
}
8.11.5 Java的动态绑定机制 dynamic binding(非常非常重要)
Java重要特性:动态绑定机制
1)当调用对象方法时,该方法会和该对象的内存地址/运行类型绑定
2)当调用对象属性时,没有动态绑定机制,哪里声明,那里使用
public class DynamicBinding {
public static void main(String[] args){
A a = new B(); //向下
System.out.println(a.sum());// 40; 注释掉B类sum方法,30
System.out.println(a.sum1());// 30;注释掉B类sum1方法,20
}
}
class A {
public int i = 10;
public int sum() {
return getI() + 10;//因动态绑定机制调用子类 getI方法
}
public int sum1() {
return i + 10;
}
public int getI() {
return i; //使用本类i,因为属性没有动态绑定机制
}
}
class B extends A {
public int i = 20;
public int sum() {
return i + 20;
}
public int sum1() {
return i + 10;
}
public int getI() {
return i + 10;
}
}
8.11.6 多态的应用
1)多态数组
数组的定义类型为父类类型,里面保存的实际元素类型为子类类型
实例:
要求创建1个Person对象、2个Student对象和2个Teacher对象,统一放在数组中,并调用每个对象
public class PolyArray {
public static void main(String[] args) {
//定义数组
Person[] p = new Person[5];
//静态初始化
p[0] = new Person("jack", 20);
p[1] = new Student("mary", 20, 100);
p[2] = new Student("smith", 19, 33);
p[3] = new Teacher("Scott", 30, 20000);
p[4] = new Teacher("king", 50, 30000);
//遍历数组,调用say方法
for(int i = 0; i <= p.length; i++) {
System.out.println(p[i].say());//动态绑定
//调用特有方法
if(p[i] instanceof Student) {
((Student)p[i]).study();
//Student student = (Student)p[i];
//student.study();
} else if (p[i] instanceof Teacher) {
((Teacher)p[i]).teach();
} else if (p[i] instanceof Person) {
System.out.println();
} else {
System.out.println("输入有误");
}
}
}
}
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(Int age) {
this.age = age;
}
public String say() {
return name + "\t" + age;
}
}
public class Student extends Person {
private double score;
public Student(String name, int age, double score) {
super(name, age);
this.score = score;
}
public double getScore() {
return score;
}
public void setName(double score) {
this.score = score;
}
@Override
public String say() {
return super.say() + score;
}
//特有方法
public void study() {
System.out.println("学生 " + getName() + " 正在学习");
}
}
public class Teacher extends Person {
private double sal;
public Student(String name, int age, double sal) {
super(name, age);
this.sal = sal;
}
public double getScore() {
return sal;
}
public void setName(double sal) {
this.sal = sal;
}
@Override
public String say() {
return super.say() + sal;
}
//特有方法
public void teach() {
System.out.println("老师 " + getName() + " 正在教课");
}
}
2)多态参数
方法定义的形参类型为父类类型,实参类型允许为子类类型
实例1:主人喂动物吃东西
8.12 Object类详解
8.12.1 equals方法
== (比较运算符) 和 equals方法 对比
1)==:既可以判断基本类型,又可以判断引用类型
2)==:如果判断基本类型,判断的事值是否相等
3)==:如果判断引用类型,判断的是地址是否相等,即判定是不是同一个对象
4)equals:是Object类中的方法,只能判断引用类型(学习看源码)
5)Object类中的equals方法默认判断的是地址是否相等,子类中往往重写该方法,用于判断内容是否相等。(可以比较Object类和其子类如Integer、String类中的equals方法源码)
8.12.2 如何重写equals方法
实例:
判断两个Person对象的内容是否相等,如果两个person对象的各个属性值都一样,则返回true,否则返回false
public class EqualsExercise {
public static void main(String[] args) {
Person person1 = new Person("jack", 10 ,'男');
Person person1 = new Person("jack", 20 ,'男');
System.out.println(person1.equals(person2));//假
}
}
class Person {
private String name;
private int age;
private char gender;
//重写object的equals方法
public boolean equals(Object obj) {
//判断如果比较的两个对象是同一个对象,则返回true
if(this == obj) {
return true;
}
//类型判断
if(obj instanceof Person) {
Person p = (Person)obj;
return this.name.equals(p.name) && this.age == p.age && this.gender == p.gender;
}
return false;
}
//构造器 set get方法略
}
8.12.3 hashCode 方法
1)提高具有哈希结构的容器的效率
2)两个引用,如果指向同一个对象,则哈希值一样;如果指向两个不同对象,则哈希值不一样。
3)哈希值主要根据地址号转变来,但不等价于地址
4)hashCode如需也会重写,(集合讲解)
8.12.4 toString 方法
1)基本介绍
默认返回:全类名 + @ + 哈希值的十六进制
子类往往重写toString方法,用于返回对象的属性信息
2)重写toString方法,打印对象或拼接对象时,都会自动调用该对象的toString形式
3)当直接输出一个对象时,toString方法会被默认的调用
8.12.4 finalize 方法
1)当对象被回收时,系统自动调用该对象的finalize方法。子类可以重写该方法,做一些释放资源的操作
2)什么时候被回收:当某个对象没有任何引用时,则jvm就认为这个对象是一个垃圾,就会使用垃圾回收机制来销毁该对象,在销毁该对象前,会先调用finalize方法。
3)垃圾回收机制的调用,是由系统来决定(即有自己的GC算法),也可以通过System.gc()主动出发垃圾回收机制(但不一定成功)
4)实际中几乎不用该方法(应付面试)
8.13 断点调试 debug
练习
8.14 项目-零钱通
8.14.1 项目界面
先按照字符串拼接方法(面向过程),进行创建该项目,与OOP做对比。代码如下:
package com.hspedu.samallchange;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;
public class SmallChangeSys {
//化繁为简
//1,先完成显示菜单,并可以选择菜单,给出对应提示
//2,完成零钱通明细(3种 字符串拼接;数组;对象)
//3,完成收益入账
//4,完成消费
//5,退出
//6,改进代码,要求用户输入4退出时,给出提示"你确定要退出吗? y/n",必须输入正确的y/n,否则循环输入指令,直到输入y或者n。
//7,在收益入账和消费时,判断金额是否合理,并给出相应的提示。
public static void main(String[] args) {
//定义相关变量
boolean loop = true;
Scanner scanner = new Scanner(System.in);
String key = "";
//完成零钱通明细
String details = "---------------零钱通明细---------------";
//完成收益入账
//定义新变量
double money = 0;
double balance = 0;
Date date = null;//date 是 java.util.Date类型,表示日期
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");//用于日期
//格式化为中国使用格式
//消费
String note;
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.println("请选择(1 - 4):");
key = scanner.next();
//使用switch 分支控制
switch (key) {
case "1" :
System.out.println(details);
break;
case "2" :
System.out.println("收益入账金额:");
money = scanner.nextDouble();
//校验 找出不正确的条件,若用正确条件判断代码冗余
if (money <= 0) {
System.out.println("收益入账金额范围 需要大于0");
break;
}
balance += money;
//拼接收益信息
date = new Date();//获取当前日期
details += "\n收益入账\t" + money + "\t" + sdf.format(date) +
"\t" + balance; //+= ??
break;
case "3" :
System.out.println("消费金额");
money = scanner.nextDouble();
//校验
if (money <= 0 || money > balance) {
System.out.println("消费金额范围 需要小于余额");
break;
}
System.out.println("消费说明");
note = scanner.next();
balance -= money;
//拼接消费信息
date = new Date();//获取当前日期
details += "\n" + note + "\t-" + money + "\t" +
sdf.format(date) + "\t" + balance;//+=??
break;
case "4" :
String choice = "";
while (true) {
System.out.println("你确定要退出吗: y/n");
choice = scanner.next();
if ("y".equals(choice) || "n".equals(choice)) {
break;
}
}
if (choice.equals("y")) {
loop = false;
}
System.out.println("4 退 出");
break;
default:
System.out.println("选择有误,请重新选择");
}
} while (loop);
System.out.println("----------退出了零钱通项目-----------");
}
}