面向对象(高级)
知识回顾:
1.面向对象基础
类的定义
对象的使用
private关键字
this关键字
封装
构造方法
JavaBean
2.API 的使用
知识回顾
Scanner
Random
String
Date
1.继承
查看下述代码: 发现有什么问题?
你所抽取的代码没有了,怎么办?我们再写一个类,先不用管它叫什么名字,但是里面一定包含着之前看到的相同的内容,是不是长这样啊(切PPT)此时问题来了,之前的代码还对吗?是不是少东西了?所以必须有一种新的机制出现,保障学生和老师类与中间这个类产生一定的关系,有了这个关系之后,就可以实现和之前的类效果相同?这个关系就是我们要讲的继承。
1.继承概述
继承是面向对象三大特征之一。(封装,继承,多态)
可以使得子类具有父类的属性和方法,还可以在子类中重新定义,追加属性和方法。
2.练习:老师和学生
需求:定义老师类和学生类,然后写代码测试;最后找到老师类和学生类当中的共性内容,抽取出一个父类,用继承的方式改写代码,并进行测试
思路:
定义老师类(姓名,年龄,教书())
定义学生类(姓名,年龄,学习())
定义测试类,写代码测试
共性抽取父类,定义人类(姓名,年龄)
定义老师类,继承人类,并给出自己特有方法:教书()
定义学生类,继承人类,并给出自己特有方法:学习()
定义测试类,写代码测试
3.继承的格式:
1.格式:
public class 子类名 extends 父类名 { }
2.范例:
public class Zi extends Fu { }
Fu:是父类,也被称为基类、超类
Zi:是子类,也被称为派生类
3.继承中子类的特点:
一个子类只能有一个父类
子类可以有父类的内容
子类还可以有自己特有的内容
4.继承的好处和弊端
4.1 继承好处
提高了代码的复用性(多个类相同的成员变量/成员方法可以放到同一个类中)
提高了代码的维护性(如果方法的代码需要修改,修改一处即可)
4.2 继承弊端
继承让类与类之间产生了关系,类的耦合性增强了,当父类发生变化时子类实现也不得不跟着变化,削弱了子类的独立性
4.3 什么时候使用继承?
继承体现的关系:is a
假设法:我有两个类A和B,如果他们满足A是B的一种,或者B是A的一种,就说明他们存在继承关系,这个时候就可以考虑使用继承来体现,否则就不能滥用继承
举例:苹果和水果,猫和动物,猫和狗不是
5.探究继承的访问特性
5.1 成员变量的访问特性
在子类方法中访问一个变量
子类局部范围找
子类成员范围找
父类成员范围找
如果都没有就报错(不考虑父亲的父亲…)
总结:就近原则
public class Fu {int age = 18;
}
public class Zi extends Fu{
// int age = 20;public void show(){
// int age = 23;System.out.println(age);}
}
public class Test {public static void main(String[] args) {Zi zi = new Zi();zi.show();}
}
5.1.1super 关键字
super 关键字的用法和 this 关键字的用法相似
this:代表调用该方法的对象(一般我们是在当前类中使用this,所以我们常说this代表本类对象的引用)
super:代表父类存储空间的标识(可以理解为父类对象引用)
关键字 | 访问成员变量 | 访问构造方法 | 访问成员方法 |
---|---|---|---|
this | this.成员变量访问本类成员变量 | this(…)访问本类构造方法 | this.成员方法(…)访问本类成员方法 |
super | super.成员变量访问父类成员变量 | super(…)访问父类构造方法 | super.成员方法(…)访问父类成员方法 |
子类中所有的构造方法第一行默认有个super();语句。
super访问构造方法只能出现在构造方法第一行
1.访问成员变量
public class Fu {int age = 18;
}
public class Zi extends Fu{int age = 20;public void show(){int age = 23;System.out.println(this.age);//20System.out.println(super.age);//18}
}
public class Test {public static void main(String[] args) {Zi zi = new Zi();zi.show();}
}
2.访问构造方法
public class Fu {
String name;
int age;public Fu() {}public Fu(String name) {this.name = name;}public Fu(int age) {this.age = age;System.out.println("调用父类构造器");}}
public class Zi extends Fu{String sex;int height;public Zi(int height) {
// this(sex);super(18);this.height = height;}public Zi(String sex) {this.sex = sex;System.out.println("本类中调用其它构造器");}
}
public class Test {public static void main(String[] args) {Zi zi = new Zi(180);}
}
3.访问成员方法
5.2 构造方法的访问特性
子类中所有的构造方法默认都会访问父类中无参的构造方法,为什么?
因为子类会继承父类中的数据,可能还会使用父类的数据。所以,子类初始化之前,一定要先完成父类数据的初始化
每一个子类构造方法的第一条语句默认都是:super()
如果父类中没有无参构造方法,只有带参构造方法,该怎么办呢?
① 通过使用super关键字去显示的调用父类的带参构造方法
② 在父类中自己提供一个无参构造方法
推荐:自己给出无参构造方法
public class Fu {String name;int age;public Fu() {System.out.println("访问父类无参构造");}public Fu(String name) {this.name = name;}public Fu(int age) {this.age = age;System.out.println("调用父类构造器");}
}
public class Zi extends Fu {String sex;int height;public Zi() {}public Zi(int height) {this("男");
// super(18);this.height = height;}public Zi(String sex) {this.sex = sex;System.out.println("本类中调用其它构造器");}
}
public class Test {public static void main(String[] args) {Zi zi = new Zi(180);}
}
5.3 方法的访问特性
5.3.1继承中成员方法的访问特点
通过子类对象访问一个方法
子类成员范围找
父类成员范围找
如果都没有就报错(不考虑父亲的父亲…)
总结:就近原则
5.3.2 方法重写
1.方法重写概述
子类中出现了和父类中一模一样的方法声明
2.方法重写的应用
当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法,这样,即沿袭了父类的功能,
又定义了子类特有的内容
3.@Override
是一个注解(注解后面会学习到)
可以帮助我们检查重写方法的方法声明的正确性
6. 练习: 项目经理和程序员
需求:请使用继承的思想设计出项目经理类和程序员类,并进行测试。
项目经理:
成员变量:工号,姓名,工资,奖金
成员方法:工作()
程序员:
成员变量:工号,姓名,工资
成员方法:工作()
员工:
成员变量:工号,姓名,工资
成员方法:工作()
项目经理继承自员工,添加一个新的成员变量奖金
程序员继承自员工,不需要添加新的成员
2.修饰符
接下来,我们来学习修饰符,在学习修饰符之前,我们先说一下修饰符的分类。
修饰符分为权限修饰符和状态修饰符,我们先来学习权限修饰符。
修饰符的分类
1.权限修饰符
2.状态修饰符
2.1 权限修饰符
修饰符 | 同一个类中 | 同一个包中 | 不同包,但父子关系 | 不同包,非父子关系 |
---|---|---|---|---|
private | √ | |||
默认(缺省) | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
2.2 状态修饰符
static(静态)
final(最终态)
2.21 static 关键字
static 静态修饰为所有对象使用
关键字是静态的意思,可以修饰成员方法,成员变量
static 修饰的特点
① 被类的所有对象共享,这也是我们判断是否使用静态关键字的条件
② 可以通过类名调用,当然,也可以通过对象名调用,推荐使用类名调用
解释:
JVM加载类:
所有的类都是在对其第一次使用时,动态加载到JVM中的。当程序创建第一个类的静态成员的引用时,就会加载这个类,进而可以得到该类的类型信息
即使在构造器之前并没有使用static关键字。因此,使用new操作符创建类的新对象也会被当做对类的静态成员的引用。
被static修饰的方法会首先被Classloader对象先加载进内存,而这个时候可能其它的非静态方法或者变量还没有被加载进来
2.22 static 的访问特点
非静态的成员方法
能访问静态的成员变量
能访问非静态的成员变量
能访问静态的成员方法
能访问非静态的成员方法
静态的成员方法
能访问静态的成员变量
能访问静态的成员方法
总结成一句话就是:静态成员方法只能访问静态成员
public class Non_Static {public static String name;String sex;public void play(){System.out.println("play");}public static void run(){System.out.println("run");}//非静态成员方法public void show(){System.out.println(name);System.out.println(sex);play();run();}public static void show2(){System.out.println(name);run();}
}
2.23 main 方法格式的详细说明
public static void main(String[] args) { }
public 被jvm调用,访问权限足够大
static 被jvm调用,不用创建对象,直接类名访问
void 被jvm调用,不需要给jvm返回值
main 一个通用的名称,虽然不是关键字,但是被jvm识别
String[] args 以前用于接收键盘录入的
Java执行的程序 参数1 参数2 参数3 第一个参数就是数组中的第一个元素
演示args接收数据
public static void main(String[] args) {System.out.println(args.length);for (int i = 0; i < args.length; i++) {System.out.println(args[i]);}}
2.24 static 的应用
工具类的特点
构造方法私有 举例:Math
成员用static修饰 (成员变量、成员方法)
2.25 final 关键字概述
在Math源码中 有个值 public static final double PI = 3.14159265358979323846; 这个值不能改变
final 关键字是最终的意思,可以修饰成员方法,成员变量,类
final 修饰的特点
修饰方法:表明该方法是最终方法,不能被重写
修饰变量:表明该变量是常量,不能再次被赋值
修饰类:表明该类是最终类,不能被继承
2.26 final 修饰局部变量
变量是基本类型:final 修饰指的是基本类型的数据值不能发生改变
变量是引用类型:final 修饰指的是引用类型的地址值不能发生改变,但是地址里面的内容是可以发生改变的
public void show2(final int a){
// a = 5;//报错}public void show3(){final int a = 5;
// a = 6;//报错}
public class Test {public static void main(String[] args) {//final修饰基本类型变量final int age = 18;
// age = 20;//报错//final修饰引用类型变量final People people = new People();people.setAge(18);people.setName("灰灰");}
}
2.27 Math类的使用
Math类留给学生作为课堂练习,或者晚上的作业。通过该练习看学员是否掌握类名.静态方法的使用。
Math类是:数学工具类,包含对数学运算的方法
帮助文档中,没有看到构造方法,因为成员都用static修饰了,可以通过类名直接访问
2.28 jar包和帮助文档
jar包:也就是后缀名为.jar 的文件,也叫做jar文件
JAR文件(Java归档,英语:JavaARchive)是一种软件包文件格式,一般情况下jar文件中打包了多个class文件
简单的理解:jar包就是.class文件的压缩包
jar包的使用
3.抽象类
抽象类的引入:在继承过程中提取公共方法和成员属性的过程中有的方法描述不清【即方法名一样 但是执行的内容不一致】直接上代码展示 然后写出解决方案 最后提出抽象的概念
之前我们在继承关系中 完全操作了成员变量 但是成员方法呢?
3.1 抽象类概述
举例: dog类和cat类都会吃 能不能把吃这个方法提取到父类animal中?
什么是抽象类?
一种特殊的父类 里面可以写抽象方法
什么是抽象方法?
当我们将共性方法提取到父类中之后,发现这个方法在父类中无法给出具体的描述【描述不清了】,而且这个方法还是子类中必须要有的方法
3.2 抽象类的定义格式
在Java中,一个没有方法体的方法应该定义为抽象方法,而类中如果有抽象方法,该类必须定义为抽象类
抽象类和抽象方法必须使用 abstract 关键字修饰
访问修饰符 abstract 返回值类型 方法();
访问修饰符 abstract 返回值类型 方法();
注意事项:
1.抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
2.存在构造方法 —交给子类通过super进行访问的
3.抽象类不能实例化:无法通过new去创建自身的对象的【new 构造方法】 new 父类
4.抽象类的子类 —1.要么重写抽象类的所有方法 —2.要么也是一个抽象类
public abstract class TT {public abstract void show();public static void main(String[] args) {TT tt = new TT() {@Overridepublic void show() {}};}
}
3.3 abstract关键字冲突问题:
final:被abstract修饰的方法,强制要求重写子类,被final修饰的方法子类不能重写
private:被abstract修饰的方法,强制要求重写子类,被private修饰的方法子类不能重写
static:被static修饰的方法可以直接类名调用,类名调用抽象方法没有意义 两个关键字不能同时还出现
3.4 抽象类中总结成员特点
成员变量
可以是变量
也可以是常量
构造方法
有构造方法,但是不能实例化
那么,构造方法的作用是什么呢?用于子类访问父类数据的初始化
成员方法
可以有抽象方法:限定子类必须完成某些动作
也可以有非抽象方法:提高代码复用性
3.4 练习 猫和狗
需求:请采用抽象类的思想实现猫和狗的案例,并在测试类中进行测试
思路:
定义动物类(Animal)
成员变量:姓名,年龄
构造方法:无参,带参
成员方法:get/set,吃();
定义猫类(Cat),继承动物类,重写吃的方法
构造方法:无参,带参
成员方法:吃(){…}
定义狗类(Dog),继承动物类,重写吃的方法
构造方法:无参,带参
成员方法:吃(){…}
定义测试类(AnimalDemo),写代码测试
4.接口
那我们在之前学完了抽象类 我们知道抽象类中 可以有抽象方法 也可以有非抽象的方法,可以有成员变量 也可以有常量。
那我们能不能抽取出来 在一个类中只定义抽象方法和常量。 让我们更加规范的使用。
4.1 接口概述
接口就是一种公共的规范标准,只要符合规范标准,大家都可以通用
Java中的接口更多的体现在对行为(方法)的抽象
4.2 接口的特点
接口用关键字interface修饰
public interface 接口名 {public abstract 返回值类型 方法();…
}
注释: public abstract 可以省略
public interface Window {public abstract void show();public void show2();void show3();
}
类实现接口用implements表示
1.public class 类名 implements 接口{重写方法;}2.创建一个抽象类
public abstract class 类名 implements 接口{ //重写所需接口中的抽象方法}
接口不能实例化
接口如何实例化呢?参照多态的方式,通过实现类对象实例化,这叫接口多态接口的实现类
要么重写接口中的所有抽象方法
要么是抽象类
4.3 接口的成员特点
为什么接口实现不能直接实例化【new出来】=>如果能new,就可以直接通过对象变量调用方法,毫无意义?
1.成员变量
只能是常量
默认修饰符:public static final
2.成员方法
只能是抽象方法
默认修饰符:public abstract
关于接口中的方法,JDK8和JDK9中有一些新特性 default,后面再讲解
3.构造方法
没有,因为接口主要是扩展功能的,而没有具体存在
一个类如果没有父类,默认继承自Object类
4.4 类和接口关系
类和类的关系
继承关系,只能单继承,但是可以多层继承
类和接口的关系
实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接
接口和接口的关系
继承关系,可以单继承,也可以多继承
4.5 抽象类和接口的区别
成员区别
抽象类 :变量,常量;有构造方法;有抽象方法,也有非抽象方法
接口 : 常量;抽象方法
关系区别
类与类 继承,单继承
类与接口 实现,可以单实现,也可以多实现
接口与接口 继承,单继承,多继承
设计理念区别
抽象类 对类抽象,包括属性、行为
接口 对行为抽象,主要是行为
接口是抽象类,但是抽象类不一定是接口!
抽象类在继承过程中占用extends关键字,耦合度高,不建议使用。建议使用接口!
4.6 应用和练习
我们先看一下抽象类的应用场景,再来对比一下接口的应用场景。
package com.gaohe.order;
public interface OrderInterface {
// 登录void log();
// 选择void choose();
// 购物车void gouwuche();
// 下单void xiadan();
//void pay();
// 结束void end();
}
package com.gaohe.order;public class OrderClass implements OrderInterface{@Overridepublic void log() {System.out.println("国内登录成功");}@Overridepublic void choose() {System.out.println("国内选择");}@Overridepublic void gouwuche() {System.out.println("国内加入购物车");}@Overridepublic void xiadan() {System.out.println("国内下单");}@Overridepublic void pay() {System.out.println("国内支付");}@Overridepublic void end() {System.out.println("国内结束");}
}
package com.gaohe.order;public class OrderWai implements OrderInterface{@Overridepublic void log() {System.out.println("国外登录成功");}@Overridepublic void choose() {System.out.println("国外选择");}@Overridepublic void gouwuche() {System.out.println("国外加入购物车");}@Overridepublic void xiadan() {System.out.println("国外下单");}@Overridepublic void pay() {System.out.println("国外支付");}@Overridepublic void end() {System.out.println("国外结束");}public void panduan(){System.out.println("判断网络结果为国外ip");}
}
package com.gaohe.order;import java.util.Scanner;public class OrderTest {public static void main(String[] args) {System.out.println("请选择版本 1代表国内 2代表国外");Scanner sc = new Scanner(System.in);int banben = sc.nextInt();OrderInterface o =null;if(banben == 1){o=new OrderClass();}else if(banben == 2){o=new OrderWai();}else{System.out.println("滚 ");}
//
// o.panduan();
// 强制类型转化
// if(banben == 2){
// OrderWai o1 = (OrderWai) o;
// o1.panduan();
// }if(o instanceof OrderWai){OrderWai o1 = (OrderWai) o;o1.panduan();}o.log();o.choose();o.gouwuche();o.xiadan();o.pay();o.end();
/*
* OrderWai orderwai = new OrderWai();orderwai.log();orderwai.choose();orderwai.gouwuche();orderwai.xiadan();orderwai.pay();orderwai.end();
* */}
}
4.7 JDK8以后接口中新增的方法
接口的组成
1.常量
public static final
2.抽象方法
public abstract
默认方法(Java 8)
静态方法(Java 8)
私有方法(Java 9)(自行研究,仅在新版框架底层有所使用)
4.8 默认方法
接口中默认方法的定义格式:
格式:public default 返回值类型 方法名(参数列表) { }
范例:public default void show1() { }
接口中默认方法的注意事项:
public可以省略,default不能省略
4.9 静态方法
接口中静态方法的定义格式:
格式:public static 返回值类型 方法名(参数列表) { }
范例:public static void show2() { }
接口中静态方法的注意事项:
静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
public可以省略,static不能省略
4.10 私有方法
接口中私有方法的定义格式:
格式1:private 返回值类型 方法名(参数列表) { }
范例1:private void show3() { }
格式2:private static 返回值类型 方法名(参数列表) { }
范例2:private static void show4() { }
接口中私有方法的注意事项:
默认方法可以调用私有的静态方法和非静态方法
静态方法只能调用私有的静态方法
4.11 新接口方法应用场景
可以用做项目中的日志打印。
在这里插入图片描述
5.多态
引入:订单业务=>先写接口=>国内业务的实现类=>测试类的书写?思考新增国务业务应该怎么书写?
5.1 多态概述
多态:指的是同一个对象,在不同时刻表现出来的多种形态
举例:猫
5.2 多态的常见形式
多态的形式:具体类多态,抽象类多态,接口多态
父类类型 对象名称 = new 子类构造器;
接口 对象名称 = new 实现类构造器;
我们可以说猫是猫:猫 cat = new 猫();
我们也可以说猫是动物:动物 animal = new 猫();
这里猫在不同的时刻表现出来了多种形态,这就是多态
5.3 多态的前提和体现
1.有继承/实现关系
2.有方法重写
3.有父(类/接口)引用指向(子/实现)类对象
5.4 练习 猫和狗
需求:请采用多态的思想实现猫和狗的案例,并在测试类中进行测试
思路:
定义动物类(Animal)
成员变量:姓名,年龄
构造方法:无参,带参
成员方法:get/set,吃(){}
定义猫类(Cat),继承动物类,重写吃的方法
构造方法:无参,带参
成员方法:吃(){}
定义狗类(Dog),继承动物类,重写吃的方法
构造方法:无参,带参
成员方法:吃(){}
定义测试类(AnimalDemo),写代码测试
5.5 多态中成员访问特点
成员变量:编译看左边,执行看左边
成员方法:编译看左边,执行看右边
为什么成员变量和成员方法的访问不一样呢?
因为成员方法有重写,而成员变量没有
public class Animal {public String name = "动物";public void run(){System.out.println("动物跑得快");}
}
public class Lion extends Animal{public String name = "狮子";public int age = 18;public void run(){System.out.println("狮子跑得飞快");}}
public class Test {public static void main(String[] args) {Animal a = new Lion();System.out.println(a.name);
// System.out.println(a.age);//报错了 ! 对变量 运行编译都看左边a.run();//狮子跑得飞快 编译看左边 运行看右边}
}
5.6 多态的好处和弊端
多态的好处:提高了程序的扩展性
具体体现:定义方法的时候,使用父类型作为参数,将来在使用的时候,使用具体的子类型参与操作
多态的弊端:不能使用子类的特有功能
1.对象多态
好处:方法的形参定义为父亲类型,这个元素就可以接受该父类的任意子类对象了
2.行为多态
好处:同一个方法,具有多种不同的表现,或形态能力
public abstract class Animal {public abstract void eat();
}
public class Cat extends Animal{@Overridepublic void eat(){System.out.println("吃猫粮");}
}
public class Dog extends Animal{@Overridepublic void eat(){System.out.println("喜欢吃**");}public void lookDoor(){System.out.println("看门");}
}
public class AnimalOperator {// public void make(Dog dog){// dog.eat();// }// public void make(Cat cat){// cat.eat();// }public void make(Animal animal){animal.eat();//animal.lookdoor();}
}
public class Test {public static void main(String[] args) {AnimalOperator ao1 = new AnimalOperator();ao1.make(new Dog());AnimalOperator ao2 = new AnimalOperator();ao2.make(new Cat());}
}
5.7 多态中的转型
1.向上转型(自动转型)
从子到父
父类引用指向子类对象
2.向下转型(强制转型)
从父到子
父类引用转为子类对象
public class Test {public static void main(String[] args) {//向上转型 Animal a = new Lion();// System.out.println(a.name);a.run();//狮子跑得飞快 //向下转型// Lion lion = (Lion) a;// lion.attack();Tiger tiger = (Tiger) a;//java.lang.ClassCastExceptiontiger.attack();}
}
5.8 多态中的转型问题
概述:如果被转型的引用类型变量,对应的实际类型和目标类型不是同一种类型,那么在转换的时候就会出现ClassCastException
解决方法
关键字:instanceof
使用格式:
对象名 instanceof 类型
判断一个对象是否是一个类的实例
通俗理解:判断关键字的左边的对象,是否是右边的类型,返回boolean类型结果
{Animal a1 = new Dog();//自动类型转换 父类类型 对象名 = new 子类构造器();Animal a2 = new Pig();check(a1);check(a2);}//将要判断的内容提到一个静态方法中 方便调用 提高了代码的复用效率public static void check(Animal animal){if (animal instanceof Dog){//Dog dog1 = (Dog) animal;dog1.lookDoor();}else if (animal instanceof Pig){Pig pig1 = (Pig) animal;pig1.sleep();}}
5.9 案例
public interface Pay {public abstract void pay(double money);
}
public class ZhiFuPingTai implements Pay{@Overridepublic void pay(double money) {System.out.println("通过支付平台支付"+ money);}
}
public class BankCard implements Pay{@Overridepublic void pay(double money) {System.out.println("通过银行卡支付了" + money);}
}
public class XinYongKa implements Pay{@Overridepublic void pay(double money) {System.out.println("通过信用卡支付"+ money);}
}
public class Test01 {public static void main(String[] args) {Scanner sc = new Scanner(System.in);System.out.println("请选择支付方式1.支付平台支付2.银行卡支付3.信用卡支付");System.out.println("请选择支付方式:");int i = sc.nextInt();System.out.println("请选择支付金额:");double money = sc.nextDouble();Pay pay = null;if (i== 1){pay = new ZhiFuPingTai();pay.pay(money);}else if(i == 2){pay = new BankCard();pay.pay(money);}else if(i ==3){pay = new XinYongKa();pay.pay(money);}}
}