java学习记录十:接口和多态
一、接口
1.接口解释
接口是一个方法的集合,主要是用来定义方法,包含常量,抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8新加),私有方法(jdk9新加)。接口的定义,它与定义类方式相似,但是使用 interface
关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。接口不能创建对象,所有需要被实现。通过创建这个类的对象,来实现接口的方法。否则不创建对象的话,实现的类必须是一个抽象类。接口就好像是对类的一个补充,所有不同的类都可以从从接口中获取同样的方法,节省代码量。类中的方法是一个类特有的,多个类需要实现某种功能就可以使用接口。
2.写法
public interface 接口名称 {
// 常量(jdk7及其以前)
// 抽象方法(jdk7及其以前)
// 默认方法(jdk8)
// 静态方法(jdk8)
// 私有方法(jdk9)
// 常量(jdk7及其以前) 使用public static final关键字修饰,这三个关键字都可以省略
public static final int NUM1 = 10;
int NUM2 = 20;
// 抽象方法(jdk7及其以前) 使用public abstract关键字修饰,这2个关键字都可以省略
public abstract void method1();
void method2();
// 默认方法(jdk8) 使用public default关键字修饰,public可以省略,default不可以省略
public default void method3(){
System.out.println("默认方法 method3");
}
// 静态方法(jdk8) 使用public static关键字修饰,public可以省略,static不可以省略
public static void method4(){
System.out.println("静态方法 method4");
}
// 私有方法(jdk9) 使用private关键字修饰,private不可以省略
private static void method5(){
System.out.println("私有静态方法 method5");
}
//私有方法2
private void method6(){
System.out.println("私有非静态方法 method6");
}
}
3.接口使用注意点
- 接口是java语言中的一种引用数据类型
- 接口中的成员:
- 常量(jdk7及其以前)
- 抽象方法(jdk7及其以前)
- 默认方法和静态方法(jdk8额外增加)
- 私有方法(jdk9额外增加)
- 定义接口使用interface关键字—接口编译后产生class文件
- 接口不能创建对象,需要使用实现类实现接口(类似于继承),实现接口的类叫做实现类(子类)
- 创建接口文件时,idea需要定义是interface类型,不是interface后缀文件。
二、接口实现
1.接口实现解释
接口是不能创建对象,要实现接口的功能一般都是一个类实现接口,再创建对象,实现和继承类似。实现接口的类叫实现类,也可以叫接口的子类,实现的关键字是implements
2.写法
interface IA{
public void show1();
}
interface IB{
public void show2();
}
class Fu{}
//一个类可以同时实现多个接口,
public class Zi implements IA ,IB{
public void show1(){
}
public void show2(){
}
}
//类可以先继承再实现,一定要先继承。
public class Zi2 extends Fu implements IA,IB{//一定要先继承,后实现
}
3.接口实现注意点
- 实现接口使用implements关键字
- 一个类同时实现多个接口时,要用逗号隔开
- 一个类可以同时继承和实现,但是继承一定要写在前面。
- 实现接口时一定要重写接口的抽象方法。
三、接口中成员的访问
1.解释
外部想要访问接口中的成员需要遵循一定的规则
2.写法
//IA文件
public interface IA {
// 常量
public static final int NUM = 10;
// 抽象方法
public abstract void method1();
// 默认方法
public default void method2(){
//method4();
//method5();
System.out.println("IA 接口中的默认方法method2");
}
// 静态方法
public static void method3(){
//method5();
System.out.println("IA 接口中的静态方法method3");
}
// 私有方法
private void method4(){
System.out.println("IA 接口中的私有方法method4");
}
private static void method5(){
System.out.println("IA 接口中的私有方法method5");
}
}
//Imp文件
public class Imp implements IA {
// 重写接口的抽象方法
@Override
public void method1() {
System.out.println("实现类重写IA接口中的抽象方法");
}
// 重写接口的默认方法
@Override
public void method2() {
System.out.println("实现类重写IA接口中的默认方法");
}
}
//main文件
public class Test {
public static void main(String[] args) {
// 访问接口常量
System.out.println(IA.NUM);// 10 推荐
//System.out.println(Imp.NUM);// 10 不推荐 常量被实现类继承了
// 创建实现类对象调用方法
Imp imp = new Imp();
// 访问抽象方法
imp.method1();
// 访问默认方法
imp.method2();
// 接口名访问静态方法
IA.method3();
//Imp.method3();// 编译报错,没有继承
}
}
3.注意事项
- 常量:主要是供接口名直接访问
- 抽象类:就是用来给实现类重写的
- 默认方法:只供实现类重写或者实现类对象直接调用,接口不能调用。
- 静态方法:只供接口名直接调用,实现类不能访问,没有继承过来。
- 私有方法:不管是静态还是非静态的,只能在本接口中调用,
四、多实现时的冲突
1.解释
即一个类实现多个接口时,几个接口中相同的成员会发生继承冲突。
2.写法
interface A{
//公共常量
public static final int NUM1 = 10;
//公共私有方法
public abstract void method();
//公有默认方法
public default void method(){
System.out.println("A 接口的默认方法method");
}
}
interface B{
//公共常量
public static final int NUM1 = 20;
//单个常量
public static final int NUM2 = 30;
//公共私有方法
public abstract void method();
//公有默认方法
public default void method(){
System.out.println("A 接口的默认方法method");
}
}
class Imp implements A,B{
@Override
public void method() {
System.out.println("实现类重写");
}
@Override
public void method() {
System.out.println("实现类重写的默认方法");
}
}
public class Test {
public static void main(String[] args) {
/*
公有静态常量的冲突: 如果多个接口中有相同的常量,那么实现类就无法继承,会报错
公有抽象方法的冲突:实现类只需要重写一个
公有默认方法的冲突:实现类必须重写一次最终版本
公有静态方法的冲突:静态方法是直接属于接口的,不能被继承,所以不存在冲突
私有方法只能在本接口中直接使用,不存在冲突
*/
//System.out.println(Imp.NUM1);// 编译报错,无法访问
System.out.println(Imp.NUM2);// 30
Imp imp = new Imp();
imp.method();
}
}
3.注意点
- 公有静态常量的冲突: 如果多个接口中有相同的常量,那么实现类就无法继承,会报错
- 公有抽象方法的冲突:实现类只需要重写一个
- 公有默认方法的冲突:实现类必须重写一次最终版本
- 公有静态方法的冲突:静态方法是直接属于接口的,不能被继承,所以不存在冲突
- 私有方法只能在本接口中直接使用,不存在冲突
五、接口多继承的冲突
1.解释
接口与接口之间的关系是继承,接口可以实现多继承,不同于类与类之间只能实现单继承。
2.写法
interface A{
public static final int NUM1 = 10;
public abstract void method();
public default void method(){
System.out.println("A 接口中的默认方法method");
}
}
interface B{
public static final int NUM1 = 20;
public static final int NUM2 = 30;
public abstract void method();
public default void method2(){
System.out.println("A 接口中的默认方法method");
}
}
interface C extends A,B{
@Override
public void method() {
System.out.println("实现接口的抽象方法");
}
@Override
public default void method2() {
System.out.println("重写父接口中的method方法");
}
}
public class Test {
public static void main(String[] args) {
/*
公有静态常量的冲突: 子接口无法继承父接口中冲突的常量,alt+回车可以解决冲突
公有抽象方法的冲突:子接口只会继承一个有冲突的抽象方法
公有默认方法的冲突:子接口中必须重写一次有冲突的默认方法
面试题:
实现类重写接口中的默认方法,不需要加default
子接口重写父接口中的默认方法,必须加default
公有静态方法和私有方法:不存在冲突,因为静态方法是直接属于接口的,只供本接口直接调用
*/
//System.out.println(C.NUM1);// 编译报错,说明无法继承
System.out.println(C.NUM2);// 30
Imp imp = new Imp();
imp.method();
imp.method2();
}
}
3.注意点
- 公有静态常量的冲突:子接口无法继承父接口中冲突的常量
- 公有抽象方法的冲突:子接口只会继承一个有冲突的抽象方法
- 公有默认方法的冲突:子接口中必须重写一次有冲突的默认方法(注意要加default)
- 公有静态方法和私有方法的冲突:
不冲突,因为静态方法是直接属于接口的,只能使用本接口直接访问,而私有方法只能在接口中访问,也没有冲突
六、实现类继承父类又实现接口时的冲突
1.解释
即在继承父类和实现接口时,父类和父接口中的成员发生的冲突
2.写法
class Fu{
public static final int NUM1 = 10;
public static final int NUM2 = 100;
public void method(){
System.out.println("Fu 类中的默认方法method");
}
public static void method(){
System.out.println("Fu 类中的静态方法method");
}
}
interface A{
public static final int NUM1 = 20;
public abstract void method();
public default void method(){
System.out.println("A 接口中的默认方法method");
}
public static void method(){
System.out.println("A 接口中的静态方法method");
}
}
//抽象方法一定定义在抽象类中
abstract class Fu2{
public abstract void method();
}
class Zi extends Fu2 implements A{
public abstract void method();
}
class Zi2 extends Fu implements A{
}
public class Test {
public static void main(String[] args) {
/*
公有静态常量的冲突:子类无法继承有冲突的常量
公有抽象方法的冲突:子类必须重写一次有冲突的抽象方法
公有默认方法的冲突:优先访问父类的
公有静态方法的冲突:只会访问父类的静态方法
父类和接口的私有方法:不存在冲突,因为子类不会继承
*/
//System.out.println(Zi.NUM1);// 编译报错
System.out.println(Zi.NUM2);
Zi zi2 = new Zi2();
zi2.method();
Zi zi = new Zi();
zi.method();// Fu 类中的默认方法method
}
}
3.注意点
- 公有静态常量的冲突:子类无法继承有冲突的常量
- 公有抽象方法的冲突:子类必须重写一次有冲突的抽象方法
- 公有默认方法的冲突:优先访问父类的
- 公有静态方法的冲突:只会访问父类的静态方法
- 私有方法的冲突: 不存在冲突
七、多态
1.解释
多态是面向对象三大特征之一:封装,继承,多态。
同一行为,可以通过不同的事物,来体现出不同的形态,多态,描述的就是这样的状态。比如跑的动作,小猫、小狗和大象,跑起来是不一样的。比如飞的动作,昆虫、鸟类和飞机,飞起来也是不一样的。要出现多态,一定要有继承或实现,方法要重写,且父类引用指向子类对象.多态就是指同一行为,不同对象具有不同的表现形式.
2.写法
//父类 变量名 = new 子类()
//这就是父类引用指向子类对象.
class Animal{
public void eat(){
System.out.println("吃东西");
}
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头...");
}
}
public class Test1 {
public static void main(String[] args) {
// 父类引用指向子类对象
Animal anl = new Dog();// 多态
anl.eat();// 狗吃骨头...
}
}
3.注意点
多态出现的前提
- 继承或者实现【二选一】
- 父类引用指向子类对象\接口引用指向实现类对象【格式体现】
- 方法的重写【意义体现:不重写,无意义】
4.多态访问成员
class Animal{
int num = 10;
public void method1(){
System.out.println("Animal 非静态method1方法");
}
public static void method2(){
System.out.println("Animal 静态method2方法");
}
}
class Dog extends Animal{
int num = 20;
public void method1(){
System.out.println("Dog 非静态method1方法");
}
public static void method2(){
System.out.println("Dog 静态method2方法");
}
}
public class Test {
public static void main(String[] args) {
// 父类的引用指向子类的对象
Animal anl = new Dog();
System.out.println(anl.num);// 10
anl.method1();// Dog 非静态method1方法,必须父类要有这个方法,不然会报错。因为编译的时候是看左边
anl.method2();// Animal 静态method2方法
}
}
成员变量:编译看父类,运行看父类(编译看左边,运行看左边),即访问的是父类的
成员方法:
非静态方法:编译看父类,运行看子类(编译看左边,运行看右边),即调用的是子类的非静态方法
静态方法: 编译看父类,运行看父类(编译看左边,运行看左边),即调用的是父类的静态方法
结论:除了非静态方法是编译看父类,运行看子类,其余都是看父类,即多态时子类调用的方法,父类一定要有。因为编译的时候是看父类。
5.多态出现的情况
1.普通父类多态
public class Fu{}
public class Zi extends Fu{}
public class Demo{
public static void main(String[] args){
Fu f = new Zi();//左边是一个“父类”
}
}
2.抽象父类多态
public abstract class Fu{}
public class Zi extends Fu{}
public class Demo{
public static void main(String[] args){
Fu f = new Zi();//左边是一个“父类”
}
}
3.父接口多态
public interface A{}
public class AImp implements A{}
public class Demo{
public static void main(String[] args){
A a = new AImp();
}
}
6.多态使用场景
1. 变量多态
- 解释
父类类型的变量指向子类类型的对象, 如果变量的类型为父类类型,该变量就可以接收该父类类型的对象或者其所有子类对象 - 写法
class Animal{
public void eat(){
System.out.println("吃东西...");
}
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
public class Test {
public static void main(String[] args) {
//这就是变量多态,可以使用父类的方法,其实完全可以使用继承,意义不大
Animal anl = new Dog();
anl.eat();
}
}
2.形参多态
- 解释
参数类型为父类类型,该参数就可以接收该父类类型的对象或者其所有子类对象,这样就可以把不同的子类的对象作为实参来传进方法中,形参多态就是扩大了传参类型。 - 写法
class Animal{
public void eat(){
System.out.println("吃东西...");
}
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
public class Test {
public static void main(String[] args) {
// 形参多态:参数类型为父类类型,该参数就可以接收该父类类型的对象或者其所有子类对象
Dog d = new Dog();
method(d);
Cat c = new Cat();
method(c);
}
// 需求: 定义一个方法,带有一个参数,该参数可以接收Animal类对象以及Animal类的所有子类对象
//可以通过这个方法来调用不同子类对象里的方法。
//这里就可以传Dog类型和Cat类型了。放大了参数类型,不再限于某个类型了。以前都是String和int之类的。
public static void method(Animal anl){
anl.eat();
}
}
3.返回值多态
- 解释
如果返回值类型为父类类型,那么就可以返回该父类类型的对象或者其所有子类对象,返回值多态即扩大了返回类型。可以返回多种类型的对象,但都是在一个父类里的。 - 写法
class Animal{
public void eat(){
System.out.println("吃东西...");
}
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
public class Test {
public static void main(String[] args) {
// 返回值多态:如果返回值类型为父类类型,那么就可以返回该父类类型的对象或者其所有子类对象
Animal anl = method();
anl.eat();
}
public static Animal method(){
//return new Animal();
//return new Dog();
return new Cat();
}
public static Animal method1(){
if (1==1){
// 条件1成立
return new Animal();
}else if (2==2){
// 条件2成立
return new Dog();
}else{
// 否则
return new Cat();
}
}
}
7.多态的好处和弊端
1.好处
- 提高了代码的复用性,子类能共用父类功能
2.弊端
- 无法访问子类特有功能,因为必须父类有的成员,子类才能有。
class Animal{
public void eat(){
System.out.println("吃东西...");
}
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头...");
}
// 特有的功能
public void lookHome(){
System.out.println("狗在看家...");
}
}
public class Test {
public static void main(String[] args) {
// 父类的引用指向子类的对象
Animal anl = new Dog();
anl.eat();//可以访问,因为父类有这个方法,编译不会报错。
//anl.lookHome();// 编译报错,因为多态成员访问的特点是,编译看父类,而父类中没有子类独有的功能
}
}
八、引用类型转换
1.解释
基本类型之间可以转换,引用类型之间也可以转换,父类转子类,是向下转型,需要手动强制转换,而子类转为父类,是默认的。从右往坐看,什么类型转什么类型,转型可以使多态下的子类转型为自己的类型,这样可以调用自己的特有方法了。
2.写法
class Animal{
public void eat(){
System.out.println("吃东西...");
}
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头...");
}
// 特有的功能
public void lookHome(){
System.out.println("狗在看家...");
}
}
class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼...");
}
}
class Person{}
public class Test {
public static void main(String[] args) {
// 向上转型:
Animal anl = new Dog();
// 向下转型:Animal 类转Dog类
Dog dog = (Dog)anl;
System.out.println("===================================");
// 注意:右边父类类型的变量一定要指向要转型的子类类型的对象,即一定要存在多态,
//Animal anl1 = new Animal(); //不存在转型
//Dog d1 = (Dog)anl1;// 运行报错,类型转换异常ClassCastException
//Animal anl2 = new Cat(); //Cat类不能转Dog类
//Dog d2 = (Dog)anl2;// 运行报错,类型转换异常ClassCastException
//Animal anl3 = new Person();// 编译报错,因为Animal和Person不是父子关系
//Animal anl3 = (Animal) new Person();// 编译报错,因为Animal和Person不是父子关系
}
}
3.使用注意点
- 向下转型的时候:右边父类类型的变量一定要指向要转型的子类类型的对象
- 不管是向上转型还是向下转型,一定满足父子类关系或者实现关系
- 转型是为了让多态子类对象使用自己的特有方法,这样即使父类的成员不存在的,也不会报错了, 解决了多态的弊端。
九、instanceof关键字
1.解释
在引用类型转换的时候很容易出现类型转换异常,所以为了提高代码的严谨性,转型之前得先判断一下,防止转型失败报错。
判断前面变量指向的对象类型是否是后面的数据类型:
如果前面变量指向的对象类型是属于后面的数据类型,那么就返回true
如果前面变量指向的对象类型不是属于后面的数据类型,那么就返回false
2.写法
// 向上转型
Animal anl = new Cat();
// 向下转型
//Dog d = (Dog)anl;// 运行的时候会出现类型转换异常,因为anl属于cat类型
// 先判断,再转型
if (anl instanceof Dog){
Dog d = (Dog)anl;
}
System.out.println("正常结束");
五、内部类
1.解释
将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类。
比如,汽车类Car
中包含发动机类Engine
,这时,Engine
就可以使用内部类来描述,定义在成员位置。
2.写法
class Car { //外部类
class Engine { //内部类
}
}
3.访问特点
1.外部类访问内部类成员
public class Body {
public void methodW1(){
// 访问内部类的成员,
//Body.Heart bh = new Body().new Heart();
//也要先创建内部类对象
Heart bh = new Heart();
//然后直接访问即可
System.out.println(bh.numN);// 10
bh.methodN1();// 内部类的成员方法 methodN1
}
// 成员变量
private int numW = 100;
// 成员方法
private void methodW2(){
System.out.println("外部类的成员方法 methodW2");
}
// 内部类
public class Heart{
// 成员变量
int numN = 10;
// 成员方法
public void methodN1(){
System.out.println("内部类的成员方法 methodN1");
}
public void methodN2(){
// 访问外部类的成员
System.out.println(numW);
methodW2();
}
}
}
2.其他类访问内部类成员
首先要创建内部类对象,然后直接方法成员即可
public class Test {
public static void main(String[] args) {
// 创建内部类的对象
Body.Heart bh = new Body().new Heart();
System.out.println(bh.numN);// 10
bh.methodN1();// 内部类的成员方法 methodN1
System.out.println("=======================");
// 创建外部类对象
Body b = new Body();
b.methodW1();
System.out.println("=======================");
bh.methodN2();// 100 外部类的成员方法 methodW2
}
}
3.总结
成员内部类的访问特点:
- 内部类可以直接访问外部类的成员,包括私有成员。
- 外部类要访问内部类的成员,必须要建立内部类的对象。
六、匿名内部类
1.解释
匿名内部类是内部类的简化写法。它的本质是一个带具体实现的
父类或者父接口的
匿名的
子类对象,就是一个类的匿名子类的对象,比如一个抽象类,要调用其中的抽象方法,首先需要创建一个类继承这个抽象方法,然后在子类种重写这个抽象方法,在main中先创建一个子类对象,然后通过这个对象来调用这个方法。必须一步一步来才能调用,比较繁琐,而这时候就可以使用匿名内部类,它是一个类的匿名子类的对象,就是这个已经继承抽象方法的类,且创建好了对象,匿名内部类可以看作是个对象。这样就能直接调用这个抽象方法了。在不创建子类的情况下,可以直接得到一个子类对象。
2.写法
abstract class Animal{
public abstract void eat();
}
public class Test {
public static void main(String[] args) {
//new Animal() {}就是匿名内部类,就是Animal的子类对象。
Animal anl = new Animal() {
//在匿名内部类中重写抽象方法。
@Override
public void eat() {
System.out.println("匿名内部类");
}
};// 是Animal类的子类的对象
//anl就是子类对象
anl.eat();
}
}
//要是不用匿名内部了调用eat方法
//先要创建一个子类继承抽象类
class Dog extends Animal{
//重写抽象方法
@Override
public void eat() {
System.out.println("狗吃骨头...");
}
}
public class Test {
public static void main(String[] args) {
Dog d = new Dog();// 创建Animal子类对象
d.eat();// d---->是Animal类的子类的对象
}
}
//综上:匿名内部了省略了创建子类
3.注意点
- 接口和类的匿名内部类同样的实现过程。
- 要注意匿内部类一个对象,是一个类的子类的对象,而且改子类继承或实现了这个接口。
- 匿名内部类就是用来简化代码的,没有其他的功能
- 要是一个方法的形参是抽象类或者接口类型,为了简化代码,就可以直接传入该抽象类或者接口的匿名内部类,这时候就形成了多态,父类引用指向了子类对象
比如
abstract class Animal{
public abstract void eat();
}
public class Test {
public static void main(String[] args) {
method(new Animal(){
@Override
public void eat() {
System.out.println("狗吃骨头...");
}
})//这里调用的时候就传入了子类对象,形参了多态
public static void method(Animal an){}//这里的形参是抽象类,又由于抽象类不能创建对象,所以传入的一定是抽象类的子类对象,这里形成了多态,这时候可以使用匿名内部类了
}}
七、引用类型使用总结
- 只要用在数据类型的地方,引用类型都可以使用
- 类名作为方法参数和返回值:可以直接传入该类的对象;返回该类的对象
- 抽象类作为方法参数和返回值:只能传入该类的子类对象;返回该类的子类对象
- 接口作为方法参数和返回值:只能传入该接口的实现类对象;返回该接口的实现类对象
- 类作为成员变量: 赋该类的对象
- 抽象类作为成员变量 :赋该类的子类对象
- 接口作为成员变量 : 赋该接口的实现类对象