1 继承
1.1 继承概述
继承要解决的问题:共性抽取
1.2 继承的格式
class 父类 { ... } class 子类 extends 父类 { ... }
1.3 继承中成员变量的特点(变量重名)
1.3.1 子类父类成员变量重名
- 规则1:直接通过子类访问成员变量:
等号左边是谁,就优先用谁,没有则向上寻找
- 规则2:间接通过成员方法访问成员变量:
方法属于谁,就优先用谁,没有则向上找。
//定义父类 public class Fu { int num = 10; public int method(){ return num; } } //定义子类 public class Zi extends Fu{ int num = 20;//与父类成员变量重名 } // public class demo01 { public static void main(String[] args) { Fu fu = new Fu(); Zi zi = new Zi(); System.out.println(fu.num);//输出10,Fu fu = new Fu()等号左边是Fu,所以去Fu类中找成员变量num System.out.println(zi.num);//输出20,Zi zi = new Zi() 等号左边是Zi,所以在Zi类中找成员变量num
System.out.println(fu.method()); //10,方法属于父类
System.out.println(zi.method()); //10,方法属于父类
}
}
1.3.2 三种变量重名
子类方法的局部变量:直接写成员变量
子类的成员变量:this.
父类的成员变量:super.
class Fu { // Fu中的成员变量。 int num = 10; } class Zi extends Fu { // Zi中的成员变量 int num = 20; public void show() {
int num = 30; System.out.println("local num=" + num); //访问父类中的num System.out.println("Fu num=" + super.num); //访问子类中的num System.out.println("Zi num=" + this.num); } } class ExtendsDemo03 { public static void main(String[] args) { // 创建子类对象 Zi z = new Zi(); // 调用子类中的show方法 z.show(); } }
//
10
20
30
1.4 继承中成员方法的访问特点
成员方法不重名:
对象调用方法时,会先在子类中查找有没有对
应的方法,若子类中存在就会执行子类中的方法,若子类中不存在就会执行父类中相应的方法。
成员方法重名——重写(Override)
class Fu { public void show() { System.out.println("Fu show"); } } class Zi extends Fu { //子类重写了父类的show方法 public void show() { System.out.println("Zi show"); } } public class ExtendsDemo05{ public static void main(String[] args) { Zi z = new Zi(); // 子类中有show方法,只执行重写后的show方法 z.show(); // Zi show } }
总结:在调用子类成员变量和成员方法时,如果子类中有的,就先用自己的。没有再找父类。方法是谁的,就优先用谁的。
重写和重载的区别:
重写override:方法的名称一样,参数列表页一样;
@override 注解,可以检测重写是否正确
重载overload:方法的名称一样,参数列表不一样。
重写权限:子类方法的权限必须大于等于父类方法的访问权限
public>protected>(default)>private
其中default是留空
1.5 继承中构造方法的访问特点
-
构造方法的作用是初始化成员变量的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构
造方法中默认有一个super() ,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。
public class Fu { public Fu(){ System.out.println("Father Constrictor"); } } public class Zi extends Fu { public Zi(){ System.out.println("Son Constrictor"); } } public class Constructor { public static void main(String[] args) { Zi zi = new Zi();//默认会调用super(),无参构造方法 } } // Father Constrictor Son Constrictor
- 子类构造可以通过super调用父类重载构造,且只能用一次super
- 子类必须调用父类构造方法,不写则默认赠送super()无参构造,写了则用指定的super()方法
1.6 super和this的几种用法
1.6 this当前对象的一点总结
//创建父类
public class User { String name; private double leftMoney; public User() { } public User(String name, double leftMoney) { this.name = name; this.leftMoney = leftMoney; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getLeftMoney() { return leftMoney; } public void setLeftMoney(double leftMoney) { this.leftMoney = leftMoney;////子类对象去调用父类方法时,并没有创建父类对象。this指针指的是调用该方法的对象(子类对象),所以这里的this指的是子类对象}
public void show(){
System.out.println("用户名:"+name+"余额:"+leftMoney);
}
}
//创建子类 public class Manager extends User { public Manager(){ } public Manager(String name, double money){ super(name,money); } public void setLeft(double redTotal){ double leftMoney = super.getLeftMoney(); super.setLeftMoney(leftMoney-redTotal); } }
//创建main方法 public class redMain { public static void main(String[] args) { Manager manager = new Manager("Manager",200); manager.show();//输出:用户名:Manager余额:200.0 manager.setLeft(10);//子类对象去调用父类方法时,并没有创建父类对象。this指针指的是调用该方法的对象(子类对象) manager.show();//输出:用户名:Manager余额:190.0
} }
2 接口
2.1 概述
- 接口是多个类的公共规范;
- 接口是一种引用数据类型,最重要的是其中的抽象方法;接口也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。
- 引用数据类型:数组,类,接口。
- 接口是方法的集合,如果说类的内部封装了成员变量、构造方法和成员方法,那么接口的内部主要就是封装了方法,包含抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8),私有方法(JDK 9)。
-
接口的使用:它不能创建对象,但是可以被实现( implements ,类似于被继承)。一个实现接口的类(可以看做
是接口的子类),需要实现接口中所有的抽象方法,创建该类对象,就可以调用方法了,否则它必须是一个抽象
类。
2.2 接口定义
//定义格式
public interface 接口名称 { // 抽象方法 // 默认方法 // 静态方法 // 私有方法 }
2.2.1 接口中的抽象方法定义和使用
public interface MyInterfaceAbstract { public abstract void methodAbstract();//不能有大括号 // private abstract void methodAbstrac2();//错误,只能用public abstract void methodAbstrac3();//可以省略public void methodAbstrac4();//可以同时省略public abstract }
接口的使用:
- 接口不能直接new对象使用。必须有一个实现类类实现该接口
- 类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类
似继承,格式相仿,只是关键字不同,实现使用 implements 关键字。
- 非抽象子类实现接口:
1. 必须重写接口中所有抽象方法。
2. 继承了接口的默认方法,即可以直接调用,也可以重写。
public class MyInterfaceAbstractImpl implements MyInterfaceAbstract{ @Override public void methodAbstract() { } @Override public void methodAbstrac3() { } @Override public void methodAbstrac4() { } }
2.2.2 接口中的默认方法定义和使用
默认方法的作用:
实现类会继承接口中的抽象方法。当接口升级(添加新方法)的时候,将该新方法定义为default,则已存在的实现类中不用添加代码去实现该方法(如果新方法在接口中定义为abstract,则实现类必须要实现该方法,会增加很多工作量)。
//定义接口 public interface MyInterfaceDefault { public abstract void methodAbs(); public default void methodDeft(){ System.out.println("接口的default方法"); } } //实现类 public class MyInterfaceDefaultImplA implements MyInterfaceDefault { @Override public void methodAbs() { System.out.println("实现类的抽象方法"); } } //主函数 public class demoMyDefault { public static void main(String[] args) { MyInterfaceDefaultImplA implA = new MyInterfaceDefaultImplA(); implA.methodAbs(); implA.methodDeft();//默认继承了接口的default方法,实现类中可以不用写,直接调用;也可以重写 } } //输出 实现类的抽象方法 接口的default方法
2.2.3 接口中的静态方法定义和使用
/* 从Java 8开始,接口当中允许定义静态方法。 格式: public static 返回值类型 方法名称(参数列表) { 方法体 } 提示:就是将abstract或者default换成static即可,带上方法体。 */ public interface MyInterfaceStatic { public static void methodStatic() { System.out.println("这是接口的静态方法!"); } }
/* 注意事项:不能通过接口实现类的对象来调用接口当中的静态方法。 正确用法:通过接口名称,直接调用其中的静态方法。 格式: 接口名称.静态方法名(参数); */ public class Demo03Interface { public static void main(String[] args) { // 创建了实现类对象 MyInterfaceStaticImpl impl = new MyInterfaceStaticImpl(); // 错误写法!不能用实现类的对象来调用接口中的静态方法 // impl.methodStatic(); // 直接通过接口名称调用静态方法 MyInterfaceStatic.methodStatic(); } }
2.2.4 接口中的私有方法定义和使用
/* 问题描述: 我们需要抽取一个共有方法,用来解决两个默认方法之间重复代码的问题。 但是这个共有方法不应该让实现类使用,应该是私有化的。 解决方案: 从Java 9开始,接口当中允许定义私有方法。 1. 普通私有方法,解决多个默认方法之间重复代码问题 格式: private 返回值类型 方法名称(参数列表) { 方法体 } 2. 静态私有方法,解决多个静态方法之间重复代码问题 格式: private static 返回值类型 方法名称(参数列表) { 方法体 } */
//对默认方法的重复代码私有化
public interface MyInterfacePrivateA { public default void methodDefault1() { System.out.println("默认方法1"); methodCommon(); } public default void methodDefault2() { System.out.println("默认方法2"); methodCommon(); } private void methodCommon() { System.out.println("AAA"); System.out.println("BBB"); System.out.println("CCC"); } }
//在接口中,对静态方法的重复代码的私有化 public interface MyInterfacePrivateB { public static void methodStatic1() { System.out.println("静态方法1"); methodStaticCommon(); } public static void methodStatic2() { System.out.println("静态方法2"); methodStaticCommon(); } private static void methodStaticCommon() { System.out.println("AAA"); System.out.println("BBB"); System.out.println("CCC"); } }
2.3 接口中的常量定义
/* 接口当中也可以定义“成员变量”,但是必须使用public static final三个关键字进行修饰。 从效果上看,这其实就是接口的【常量】。 格式: public static final 数据类型 常量名称 = 数据值; 注意事项: 1. 接口当中的常量,可以省略public static final,注意:不写也默认是public static final。 2. 接口当中的常量,必须进行赋值。 3. 接口中常量的名称,使用完全大写的字母,用下划线进行分隔。(推荐命名规则) */ public interface MyInterfaceConst { // 这其实就是一个常量,一旦赋值,不可以修改 public static final int NUM_OF_MY_CLASS = 12; }
2.4 接口的多实现和多继承
在继承体系中,一个类只能继承一个父类。而对于接口而言,一个类是可以实现多个接口的,这叫做接 口的多实现。并且,一个类能继承一个父类,同时实现多个接口。 格式: class 类名 [extends 父类名] implements 接口名1,接口名2,接口名3... { // 重写接口中抽象方法【必须】 // 重写接口中默认方法【不重名时可选】 } [ ]: 表示可选操作。
2.4.1 多实现会出现以下的问题:
/* 使用接口的时候,需要注意: 1. 接口是没有静态代码块或者构造方法的。 2. 一个类的直接父类是唯一的,但是一个类可以同时实现多个接口。 格式: public class MyInterfaceImpl implements MyInterfaceA, MyInterfaceB { // 覆盖重写所有抽象方法 } 3. 如果实现类所实现的多个接口当中,存在重复的抽象方法,那么只需要覆盖重写一次即可。 4. 如果实现类没有覆盖重写所有接口当中的所有抽象方法,那么实现类就必须是一个抽象类。 5. 如果实现类所实现的多个接口当中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写(两个默认方法都不用,自己重新覆盖重写)。 6. 一个类如果直接父类当中的方法,和接口当中的默认方法产生了冲突,优先用父类当中的方法。 */ public class Demo01Interface { public static void main(String[] args) { Zi zi = new Zi(); zi.method(); } }
//第1点 public interface MyInterfaceA { // 错误写法!接口不能有静态代码块 // static { // // } // 错误写法!接口不能有构造方法 // public MyInterfaceA() { // // } public abstract void methodA(); public abstract void methodAbs(); public default void methodDefault() { System.out.println("默认方法AAA"); } }
public interface MyInterfaceB { // 错误写法!接口不能有静态代码块 // static { // // } // 错误写法!接口不能有构造方法 // public MyInterfaceA() { // // } public abstract void methodB(); public abstract void methodAbs(); public default void methodDefault() { System.out.println("默认方法BBB"); } }
//第2点和第3点 public class MyInterfaceImpl /*extends Object*/ implements MyInterfaceA, MyInterfaceB { @Override public void methodA() { System.out.println("覆盖重写了A方法"); } @Override public void methodB() { System.out.println("覆盖重写了B方法"); } @Override public void methodAbs() { System.out.println("覆盖重写了AB接口都有的抽象方法"); } @Override public void methodDefault() { System.out.println("对多个接口当中冲突的默认方法进行了覆盖重写"); } }
//第6点 //优先级,继承优先于接口 public class Fu { public void method() { System.out.println("父类方法"); } } public class Zi extends Fu implements MyInterface { }
2.4.2 多继承注意事项
/* 1. 类与类之间是单继承的。直接父类只有一个。 2. 类与接口之间是多实现的。一个类可以实现多个接口。 3. 接口与接口之间是多继承的。 注意事项: 1. 多个父接口当中的抽象方法如果重复,没关系。 2. 多个父接口当中的默认方法如果重复,那么子接口必须进行默认方法的覆盖重写,【而且带着default关键字】。 */ public class Demo01Relations { }
public interface MyInterfaceA { public abstract void mehtodA(); public abstract void methodCom(); public default void methodDef(){ System.out.println("AAA"); } } public interface MyInterfaceB { public abstract void mehtodB(); public abstract void methodCom(); public default void methodDef(){ System.out.println("BBB"); } } public interface MyInterface extends MyInterfaceA,MyInterfaceB{ public abstract void mehtod(); @Override default void methodDef() {//必须重写default方法 } }
3 多态
3.1 概述
//多态体现的格式:左父右子 父类类型 变量名 = new 子类对象; 变量名.方法名();
3.2 多态中成员变量的访问特点
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,执行的是子类重写
后方法。
//定义父类 public class Fu /*extends Object*/ { int num = 10; public void showNum() { System.out.println(num); } public void method() { System.out.println("父类方法"); } public void methodFu() { System.out.println("父类特有方法"); } } //定义子类 public class Zi extends Fu { int num = 20; int age = 16; @Override public void showNum() { System.out.println(num); } @Override public void method() { System.out.println("子类方法"); } public void methodZi() { System.out.println("子类特有方法"); } } /* 访问成员变量的两种方式: 1. 直接通过对象名称访问成员变量:看等号左边是谁,优先用谁,没有则向上找。 2. 间接通过成员方法访问成员变量:看该方法属于谁,优先用谁,没有则向上找。 */ public class Demo01MultiField { public static void main(String[] args) { // 使用多态的写法,父类引用指向子类对象 Fu obj = new Zi(); System.out.println(obj.num); // 父:10 //等号左边是父类 // System.out.println(obj.age); // 错误写法!//父类没有该成员变量,向上找也没有 System.out.println("============="); // 子类没有覆盖重写,就是父:10 // 子类如果覆盖重写,就是子:20 obj.showNum();//看该方法属于谁 } }
3.3 多态中成员方法的访问特点
//父子类定义如上节
/* 在多态的代码当中,成员方法的访问规则是: 看new的是谁,就优先用谁,没有则向上找。 口诀:编译看左边,运行看右边。 对比一下: 成员变量:编译看左边,运行还看左边。 成员方法:编译看左边,运行看右边。 */ public class Demo02MultiMethod { public static void main(String[] args) { Fu obj = new Zi(); // new的Zi obj.method(); // 父子都有,优先用子 obj.methodFu(); // 子类没有,父类有,向上找到父类 // 编译看左边,左边是Fu,Fu当中没有methodZi方法,所以编译报错。 // obj.methodZi(); // 错误写法! } }
3.4 向上转型和向下转型
//向上转型
父类类型 变量名 = new 子类类型(); 如:Animal a = new Cat();
//当父类引用指向一个子类对象
//创建一个子类对象,把它当做父类来看待使用
//向上转型一定是安全的
//向下转型
子类类型 变量名 = (子类类型) 父类变量名; 如:Cat cat =(Cat) animal;
为什么要转型:当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类拥
有,而父类没有的方法。编译都错误,更别说运行了。所以,想要调用子
类特有的方法,必须做向下转型。
含义:将父类对象,还原成本来的子类对象。
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat(); //创建一只cat
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
Dog d = (Dog)a;//本来创建的是一只cat,所以向下不能还原成dog
d.watchHouse(); // 调用的是 Dog 的 watchHouse 【运行报错】
}
}
3.5 instanceof
/*
如何才能知道一个父类引用的对象,本来是什么子类?
格式:
对象 instanceof 类名称
这将会得到一个boolean值结果,也就是判断前面的对象能不能当做后面类型的实例。
*/
public class Demo02Instanceof {
public static void main(String[] args) {
Animal animal = new Dog(); // 本来是一只狗
animal.eat(); // 狗吃SHIT
// 如果希望掉用子类特有方法,需要向下转型
// 判断一下父类引用animal本来是不是Dog
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.watchHouse();
}
// 判断一下animal本来是不是Cat
if (animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.catchMouse();
}
giveMeAPet(new Dog());
}
//实际应用场景,外边传入的对象类型不定,需要自己判断一下类型
public static void giveMeAPet(Animal animal) {
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.watchHouse();
}
if (animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.catchMouse();
}
}
}