疯狂java学习笔记之面向对象(九) - 抽象和接口
一、抽象(abstract):
1、抽象类:
使用abstract修饰的类就是抽象类;
相比于普通类抽象类增加了支持抽象方法的功能,但也丢失了创建实例的功能(抽象类中不能创建实例),其他普通类有的抽象类可以有。
抽象类的三个注意点:
①、虽然抽象类不能创建实例,但它依然有构造器(主要给其子类的构造器调用-子类至少调用父类构造器一次);
②、抽象类中可以没有抽象方法(可有可无);
③、抽象类总是不能创建实例/对象的 - 即使它没有包含抽象方法。
1 public abstract class TestAbstract{ 2 private int age; 3 4 { 5 System.out.println("-这是个对象初始化块-"); 6 } 7 8 public TestAbstract(){} 9 10 //虽然抽象类不能创建实例,但它依然有构造器(主要是供其子类构造器调用,至少调用一次) 11 //子类不使用super()时则隐式调用父类无参构造器,有super()则根据参数来决定。 12 public TestAbstract(int age){ 13 this.age = age; 14 } 15 16 public static void main(String[] args){ 17 System.out.println("Hello World!!"); 18 //抽象类中不能创建实例,编译时即报错 19 //TestAbstract ta = new TestAbstract(); 20 } 21 }
2、抽象方法:
使用abstract修饰且没有方法体的方法就是抽象方法。
注意点:
①、抽象方法不能有方法体 - 花括号内容(出现花括号没任何代码都算有方法体);
②、abstract不能和final同时出现 - 因为abstract方法必须由子类去重写,而final修饰的方法又不允许重写,因此不能同时出现;
对于abstract类来说,该类主要用于派生子类,final修饰的类同样不允许被集成,因此也不能同时出现 - abstract和final是永远互斥的;
③、abstract与static不能同时修饰方法 - static修饰的方法编译时已经确定下来,无法让子类@Override;
④、abstract与private不能同时修饰方法 - private修饰的方法不能被子类访问,而abstract又要求方法必须由子类重写,所以两者是互斥的。
1 public abstract class TestAbstract2{ 2 //抽象类相对于普通类除了不能创建实例,其他功能都是有的,普通方法等能存在。 3 public void info(){} 4 5 //抽象方法中不能有方法体-即使方法体内容为空 6 //public abstract void aa(){} 7 8 public abstract void test(); 9 10 //abstract 与static不能同时修饰方法 - static修饰的方法编译时已经确定下来,无法让子类@Override 11 //public abstract static void bb(); 12 13 //abstract方法必须由子类重写,而private又不能让子类访问所以出错 14 private abstract void cc(); 15 16 }
三、抽象的作用:
抽象类的作用:主要是与"模版模式" 结合一起使用的。
比如有如下场景需求:程序要实现A方法,但A方法又需要调用B方法且B方法在该类中暂时不知道如何实现(因为其子类实现B方法的方法可能都不一样,因此B方法可定义成抽象方法)。
抽象类的子类一般要实现抽象父类提供的所有方法,否则该子类也只能是抽象类(这样就无实际意义~)
1 abstract class SpeedMeter{ 2 //定义一个转动速度。 3 private double turnRate; 4 5 //抽象类的构造器主要用于被子类的构造器调用 6 public SpeedMeter(double turnRate){ 7 this.turnRate = turnRate; 8 } 9 10 //对于private修饰的Field一般推荐设置个set和get方法,典型的封装 11 public void setTurnRate(double turnRate){ 12 this.turnRate = turnRate; 13 } 14 15 public double getTurnRate(){ 16 return this.turnRate; 17 } 18 19 //计算速度的方法 20 /* 21 这里就是模版模式,此处我们定义一个计算速度的模板:转速*周长 22 但此处并不清楚计算周长方法,所以此处把周长getSpeed()定义成抽象方法,子类只需提供计算周长的方法即可 23 计算周长的方法是有差异的,车轮:2*PI*半径等 24 */ 25 public abstract double getSpeed(); 26 27 } 28 29 class CarSpeedMeter extends SpeedMeter{ 30 private double radius; 31 32 public CarSpeedMeter(double radius,double turnRate){ 33 super(turnRate); 34 this.radius = radius; 35 } 36 37 @Override 38 public double getSpeed(){ 39 return 2 * radius * Math.PI; 40 } 41 } 42 43 class TankSpeedMeter extends SpeedMeter{ 44 private double bianchang; 45 public TankSpeedMeter(double bianchang,double turnRate){ 46 super(turnRate); 47 this.bianchang = bianchang; 48 } 49 50 //假设坦克的周长计算方法为 8 * 边长 51 @Override 52 public double getSpeed(){ 53 return 8 * bianchang; 54 } 55 } 56 57 public class TestSpeedMeter{ 58 public static void main(String[] args){ 59 SpeedMeter sm = new CarSpeedMeter(20,2000); 60 System.out.println("汽车的速度为: " + sm.getSpeed()); 61 SpeedMeter tk = new TankSpeedMeter(8,1000); 62 System.out.println("坦克的速度为: " + tk.getSpeed()); 63 } 64 }
二、接口(interface):
接口的作用非常丰富,接口往往是和设计模式结合一起使用的。
接口可以认为是一种"彻底"的抽象类,它是从多个相似的类中抽取出来的一种规范,接口体现的是规范。
接口体现的规范:如主板上各种不同类型的"插槽",无论接入的哪个厂商、型号的硬件都可以彼此进行通信(任何一套公开的标准/规范,需要通过接口来体现)。
接口定义语法:
[修饰符] interface 接口名{
//0~N 个Field、特殊的Field
//0~N 个抽象方法
//0~N 个内部类、内部接口、内部枚举类定义
}
[修饰符]:只能是public和默认省略,不能是protected因为接口在保外,而protected只能在包名进行访问
接口名: 由多个单词连缀而成,每个单词的首字母要大写,推荐以i开头(c#定义已i开头且是个不错的习惯)。
注意点:
①、接口不能有构造器,也不能有初始化块;
②、接口的Field默认有3个修饰符: public static final,无论你写或不写,反正都有这三个修饰符;
接口的Field定义时必须指定初始值 -- 接口Field默认有static final修饰,final修饰的变量只能被赋值一次,你不指定系统给你赋默认值0、0.0、null等。
③、接口的方法默认有2个修饰符:public abstract,无论你写与不写,反正都有这二个修饰符;
接口的方法不能有static,因为接口的方法默认有abstract修饰。
④、接口的内部类、内部枚举类、内部接口,默认有两个修饰符: public static
1 public interface iWalk{ 2 int age = 33; 3 //接口中的Field默认有public static final修饰,必须在声明时指定初始值 4 //final修饰的变量必须在声明时/初始块中指定,但接口又不能有初始化块,那必须在声明时指定了 5 //String name; 6 7 //接口的所有成员都需要提供给外部类、包访问,接口公开的标准必须由public修饰 8 //private double weight = 99; 9 10 //接口中的方法默认有public abstract修饰 11 void info(); 12 13 //抽象方法不能有方法体 14 //void test(){} 15 }
接口的继承:
一个接口可以有N个直接父接口;
接口可用于定义变量;不能直接创建实例;提供其他类来实现自己。
implements:实现一个或N个接口,接口实现类要么为接口所有的抽象方法提供实现,否则你的实现类也只能是抽象类。接口主要是在"面向接口编程"时提供更好、更灵活的机制。
1 interface IEatable{ 2 void eat(); 3 } 4 5 interface IFlyable{ 6 void fly(); 7 } 8 9 interface Iwalkable extends IEatable{ 10 int age = 20; 11 void info(); 12 /* 13 接口中的方法必须为抽象方法 14 public static void main(String[] args){ 15 Iwalkable iw = new Sparrow(); 16 } 17 */ 18 } 19 20 public class Sparrow implements IFlyable,Iwalkable{ 21 //因为Sparrow实现了两个接口,所以它必须实现两个接口的所有方法 22 @Override 23 public void eat(){ 24 System.out.println("这只麻雀在一口一口吃麦子"); 25 } 26 27 public void fly(){ 28 System.out.println("这麻雀在飞走了!"); 29 } 30 31 public void info(){ 32 System.out.println("这是一只小小麻雀"); 33 } 34 35 public static void main(String[] args){ 36 //向上转型,iw引用变量编译时只是Iwalkable类型,编译器只能允许它调用Iwalkable内的方法 37 Iwalkable iw = new Sparrow(); 38 iw.info(); 39 iw.eat(); 40 //iw编译时为Iwalkable类型,直接调用fly方法会报错 41 //iw.fly(); 42 43 //Sparrow sp = new Sparrow(); 44 //强制向下转型 45 Sparrow sp = (Sparrow)iw; 46 sp.fly(); 47 48 IFlyable ia = new Sparrow(); 49 ia.fly(); 50 } 51 }
接口与抽象类之间的相似之处:
①、都可以包含抽象方法;
②、都不能创建实例
③、子类抽象类或实现接口,都要求实现所有抽象方法,否则这个子类也只能是抽象类
接口与抽象类之间的区别:
①、接口里只能有抽象方法,而抽象类中可以没有抽象方法也可以包含普通方法;
②、接口中不能定义static方法,而抽象类中可以包含static方法;
③、接口的Field默认有public static final三个修饰符修饰,而抽象类的Field完全可以是普通Field;
④、接口不能包含构造器,而抽象类完全可以有构造器(供子类至少调用一次);
⑤、接口不能包含初始化块,而抽象类完全可以有初始化块;
⑥、接口可以有多个直接的父接口,而抽象类只能有一个直接的父类。