一、面向对象的继承
1.继承基础
继承是多态的前提,如果没有继承就没有多态
继承主要解决的问题就是:共性抽取
父类(超类、基类)==》子类(派生类)
继承关系当中的特点:
1、子类可以拥有父类的“内容”
2. 子类还可以拥有自己专属的内容
package func.extend;
/*
在继承的关系中,“子类就是一个父类”。也就是说,子类可以被当做父类看待。
例如父类是员工,子类是讲师,那么“讲师就是一个员工”。关系:is-a.
定义父类的格式:(一个普通的类定义)
public class 父类名称{
.....
}
定义子类的格式:
public class 子类名称 extends 父类名称{
//。。。
}
在父子类的继承关系当中,如果成员变量重名,则创建子类对象时,访问有两种方式:
1.直接通过子类对象访问成员变量:
等号左边是谁,就优先用水,没有则向上找。 如new一个子对象,那么等号左边就是子对象,那么先找自己
2.间接通过成员方法访问成员变量;
该方法属于谁,就优先用谁,没有向上找
*/
public class Demo01Extends {
public static void main(String[] args) {
Teacher teacher = new Teacher();
System.out.println(teacher.numZi);
System.out.println(teacher.numFu); //除了可以调用本类的变量,还可以调用父类的变量
teacher.method(); //本类没有方法,执行继承的父类的Method方法
}
}
2.继承扩展:
1.
//三种成员变量的获取
package func.extend;
public class Fu {
int num = 20;
}
------------------
package func.extend;
public class Zi extends Fu {
int num = 10;
public void method() {
int num = 30;
System.out.println(num); //30,局部变量
System.out.println(this.num); //10,本类变量
System.out.println(super.num); //20,父类的成员变量
}
}
--------------------
package func.extend;
/*
局部变量: 直接写成员变量名
本类的成员变量: this.成员变量名
父类的成员变量: super.成员变量名
*/
public class ExtendsField {
public static void main(String[] args) {
Zi zi = new Zi();
Fu fu = new Fu();
zi.method(); //30,10,20
}
}
2.继承中成员方法的访问特点
同Python
1.覆盖重写:可以使用 @Override 检测是否重写成功
2.必须保证父子类之间方法名称跟参数列表相同
2.子类方法的返回值必须小于等于父类方法的返回值范围
3.子类方法的权限必须大于等于父类方法的权限修饰符
扩展提示:public>protected>(default)>private
备注:(default)不是关键字default,耳饰什么都不写,留空
应用场景:
对于已经投入使用的类,尽量不要进行修改。而推荐定义一个新的类,来重复利用其中共性内容,并且添加新内容
在子类的成员方法中,如果重写了父类的方法,又想使用父类的方法功能,可以在子类方法中使用super.重写方法名,获得父类当中需要继续使用的部分
3.继承中构造方法的访问特点:
父类:
package func.extend;
public class Fu {
public Fu(int num) {
System.out.println("父类构造方法");
}
}
子类:
package func.extend;
public class Zi extends Fu {
public Zi() {
// super();
//子类构造方法默认有一个隐含的super();当父类自定义了有参构造方法,那么父类将不再默认拥有系统赠送的构造方法
//那么这里如果不调用父类的有参构造方法,隐含的子类super()将会导致程序出错
super(1);
}
}
调用:
package func.extend;
/*
继承关系中,父子类构造方法的访问特点:
1.子类构造方法当中有一个默认隐含的“super()"调用,所以一定是先调用父类构造,随后调用子类构造方法
2.子类构造可以通过super关键字来调用父类重载构造
3.super的父类构造调用,必须是子类构造方法的第一个语句。不能一个子类构造调用多次super构造
总结:
子类必须调用父类构造方法,不写则赠送super();写了则用写的指定的super调用。
super只能有一个且必须是第一个语句
*/
public class Constructor {
public static void main(String[] args) {
Zi zi = new Zi();
}
}
4. super关键字的三种用法:
/*
super关键字的用法有三种:
1.在子类的成员方法中,访问父类的成员变量。
2.在子类的成员方法中,访问父类的成员方法。
3.在子类的构造方法中,调用父类的构造方法
*/
5. this关键字的用法:
/*
super关键字用来访问父类内容,而this关键字用来访问本类内容。用法也有三种:
1.在本类的成员方法中,访问本类的成员变量。
int num = 1;
public void showNum(){
System.out.printlt(this.num);
}
2.在本类的成员方法中,访问本类的另一个成员方法。
public void showNum(){
this.showNum1();
}
public void showNum1(){
System.out.printlt(this.num)
}
3.在本类的构造方法中,访问本类的另一个构造方法。(无参有参相互调用)
a.在此方法中,this()调用也必须是构造方法的第一个语句,也是唯一一个
b.super和this两种构造调用,不能同时使用
*/
6. Java继承的三个特点:
Java语言是单继承的,一个类的直接父类只能有唯一一个。
Java语言可以多级继承,父类可以有父类,爷爷类也叫父类,所有类都是继承Object类
二、抽象类
如果父类当中的方法不确定如何进行{}方法实现,那么这就应该是一个 抽象方法
1.抽象类的定义:
package Demo316;
/*
抽象方法:就是加上abstract关键字,然后去掉大括号,直接分号结束
抽象类:抽象方法所在的类,必须是抽象类才行。在class之前写上abstract即可
*/
//public class Animal {
// public void eat(){
// System.out.println("吃鱼?");
// System.out.println("吃骨头?"); //对于动物,有很多种食物种类,不确定
// }
//
//}
public abstract class Animal {
//这是一个抽象方法,代表吃东西,但是具体吃什么(大括号内容)不确定。
public abstract void eat();
//抽象类中一样可以定义普通方法
public void normalMethod() {
}
}
2.如何使用抽象类和抽象方法:
2.1.定义子类继承
package Demo316;
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼 ");
}
}
2.2.使用:
package Demo316;
/*
如何使用抽象类和抽象方法:
2.2.1.不能直接创建new抽象类对象.
2.2.2必须用一个子类来继承抽象父类。
2.2.3.子类必须覆盖重写抽象父类当中所有的抽象方法
覆盖重写(实现):去掉抽象方法的abstract关键字,然后补上大括号方法体
2.2.4.创建子类对象进行使用。
*/
public class AbstractUse {
public static void main(String[] args) {
Cat cat = new Cat();
cat.eat(); //猫吃鱼
}
}
3.注意事项:
3.1抽象类不能创建对象,如果创建,编译无法通过而报错,只能创建其非抽箱子类的对象
3.2抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的
- 子类的构造方法中,有默认的super(),需要访问父类构造方法(跟普通类一样)
3.3抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类
3.4抽象类的子类,必须重写抽象父类中所有的抽象方法。否则,编译无法通过而报错。除非该子类也是抽象类。
4. 继承的综合案例:
群主发普通红包。某群有多名成员,群主给成员发普通红包。红包的规则:
1、群主的一笔金额,从群主余额中扣除,平均分成n等分,让成员领取。
2.成员领取红包后,保存到成员余额中。
请根据描述,完成案例中所以后类的定义以及指定类之间的继承关系,并完成发红包的操作。
分析:
发红包的逻辑,三要素:
返回值类型:ArrayList<Integer> //返回几份分好的红包
方法名称:send
参数列表:1.总共发多少钱 int totalMoney 2. 分成多少份 int count
public ArrayList<Integer> send(int totalMoney,int count){
....
}
收红包的逻辑:三要素:
返回值类型:void //直接自己余额相加,不需要返回
方法名称:receive
参数列表:ArrayList<Integer>
public void receive(ArrayList<Integer> list){
}
接上红包游戏:1.用户父类
package Demo316;
/*
* 发红包,用户类
* */
public class User {
private String name; //姓名
private int money; //余额
public User() {
}
public User(String name, int money) {
this.name = name;
this.money = money;
}
//展示一下当前用户有多少钱:
public void show() {
System.out.println("我叫:" + name + ",我有多少钱:" + money);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
}
2.群主类:
package Demo316;
import java.util.ArrayList;
//群主类
public class Manager extends User {
public Manager() {
}
public Manager(String name, int money) {
super(name, money);
}
public ArrayList<Integer> send(int totalMoney, int count) {
//首先需要一个集合,用来存储若干个红包的金额
ArrayList<Integer> moneyList = new ArrayList<>();
//查看群主余额,红包不能超过余额
int leftMoney = super.getMoney();
if (totalMoney > leftMoney) {
System.out.println("余额不足");
return moneyList;
}
//扣钱,其实就是重新设置余额:
super.setMoney(leftMoney - totalMoney);
//发红包,平均拆分
int avg = totalMoney / count;
int mod = totalMoney % count; //余数,就是除不尽的零头
//零头包在最后一个红包当中
//把红包一个个放在集合当中
for (int i = 0; i < count - 1; i++) {
moneyList.add(avg);
}
//最后一个红包,如果有零头,最后一个红包稍微大一点,上边count-1是为了让这个红包有位置,不影响总体个数
//微信怎么处理?微信没有总额平均分的形式,只有总额运气红包 或者平均分,需要设定单个金额跟红包个数,避免无限小数
int last = avg + mod;
moneyList.add(last);
return moneyList;
}
}
3.群员类:
package Demo316;
import java.util.ArrayList;
import java.util.Random;
//群员类
public class Member extends User {
public Member() {
}
public Member(String name, int money) {
super(name, money);
}
public void receive(ArrayList<Integer> list) {
Random random = new Random();
//随机从红包集合中获取一个红包
int getIndex = random.nextInt(list.size());
int getMoney = list.remove(getIndex);
//获取当前用户当前余额
int currentMoney = super.getMoney();
//设置余额为当前余额加红包
super.setMoney(currentMoney + getMoney);
}
}
4.结果展示:
package Demo316;
import java.util.ArrayList;
public class DemoShow {
public static void main(String[] args) {
Manager manager = new Manager("群主", 100);
Member member1 = new Member("群员1", 0);
Member member2 = new Member("群员2", 0);
Member member3 = new Member("群员3", 0);
Member member4 = new Member("群员4", 0);
//展示全体信息:
manager.show();
member1.show();
member2.show();
member3.show();
member4.show();
System.out.println("-------------------------");
//群主发红包,并获取红包列表,为了验证随机性,金额设置为21,除不尽
int redMoney = 21;
ArrayList<Integer> redList = manager.send(redMoney, 4);
//核对群主余额:
manager.show();
//群员领红包:因为维护的是同一个集合地址,所以领取remove后后面的成员共享修改后的集合
member1.receive(redList);
member2.receive(redList);
member3.receive(redList);
member4.receive(redList);
//群员展示领取后金额:
member1.show();
member2.show();
member3.show();
member4.show();
}
}
三、接口
接口就是一种公共的规范标准。
只要符合规范标准,就可以大家通用
1.接口的定义:
/*
接口就是多个类的公共规范。
接口就是一种引用数据类型,最重要的内容就是其中的:抽象方法
如何定义一个接口的格式:
public interface 接口名称{
//接口内容
}
备注:与类的编译一致,接口编译生成的字节码文件仍然是: .java --> .class
如果是Java7,那么接口中可以包含的内容有:
1.常量
2.抽象方法
如果是Java8,还可以额外包含有:
3.默认方法
4.静态方法
如果是Java9,还可以额外包含有:
5.私有方法
*/
1.1
package demo317.interface1;
/*
在任何版本的Java中,接口都能定义抽象方法。
格式:
public abstract 返回值类型 方法名称(参数列表);
注意事项:
1.接口当中的抽象方法,修饰符必须是两个固定的关键字:public abstract
2.这两个关键字修饰符,可以选择性地省略(熟悉之前不推荐).
3.方法的三要素可以随意定义。
*/
public interface MyInterfaceAbstract {
//这是一个抽象方法
public abstract void methodAbs1();
//这也是抽象方法
abstract void methodAbs2();
//这也是抽象方法
public void methodAbs3();
//这也是抽象方法
void methodAbs4();
}
2.接口的实现
2.1 定义实现类
package demo317.interface1;
public class InterfaceAbstractImpl implements MyInterfaceAbstract {
@Override
public void methodAbs1() {
System.out.println("这是第一个方法!");
}
@Override
public void methodAbs2() {
System.out.println("这是第二个方法!");
}
@Override
public void methodAbs3() {
System.out.println("这是第三个方法!");
}
@Override
public void methodAbs4() {
System.out.println("这是第四个方法!");
}
}
2.2 使用接口抽象方法:
package demo317.interface1;
/*
接口使用步骤:
1.接口不能直接使用,必须有一个实现类“”来“实现”该接口。
格式:
public class 实现类名称 implements 接口名称{
//....
}
2.接口的实现类必须覆盖重写(实现)接口中的所有的抽象方法
实现:去掉abstract关键字,加上方法体大括号。
3.创建实现类的对象,进行使用
注意事项:
如果实现类并没有覆盖重写接口中所有的抽象方法,那么这个实现类自己就必须是抽象类
*/
public class Demo01Interface {
public static void main(String[] args) {
//错误写法!不能直接New接口对象使用
// MyInterfaceAbstract inter = new MyInterfaceAbstract();
//创建实现类的对象使用
InterfaceAbstractImpl impl = new InterfaceAbstractImpl();
impl.methodAbs1();
impl.methodAbs2();
}
}
3.接口默认方法定义:
package demo317.interface1;
/*
从Java8开始,接口里允许定义默认方法。
格式:
public default 返回值类型 方法名称(参数列表){
//方法体
}
备注:接口当中的默认方法,可以解决接口升级的问题。
接口升级:由于实现接口的抽象方法,必须要定义一个实现类implements来继承接口,并且覆盖重写
接口类的所有抽象方法,才能通过这个继承类使用方法,所以当接口升级,新增了抽象方法,会导致继承接口的所有类
报错,直至它们把新增的方法覆盖重写,这样会很麻烦且可能导致很多牵涉问题
*/
public interface MyInterfaceDefault {
//抽象方法
public abstract void methodAbs();
//新添加了一个抽象方法,继承的类相应的要重写
// public abstract void methodAbs2();
//新添加的方法,改成默认方法
public default void methodDefault(){
System.out.println("这是新添加的默认方法");
}
}
3.1 默认方法实现类:
实现类A:
package demo317.interface1;
public class InterfaceDefault implements MyInterfaceDefault {
@Override
public void methodAbs() {
System.out.println("实现了抽象方法,AAA");
}
}
实现类B:
package demo317.interface1;
public class InterfaceDefaultB implements MyInterfaceDefault{
@Override
public void methodAbs() {
System.out.println("实现了抽象方法:BBB");
}
@Override
public void methodDefault() {
System.out.println("实现类B重写了接口的默认方法");
}
}
调用实现类:
package demo317.interface1;
/*
1.接口的默认方法,可以通过接口实现类对象,直接调用。
2.接口的默认方法,也可以被接口实现类进行覆盖重写。
*/
public class Demo02Interface {
public static void main(String[] args) {
//创建了实现类对象
InterfaceDefault a = new InterfaceDefault();
a.methodAbs(); //