接口
比抽象类更抽象
例:需求:需要飞到北京出差
飞机、鸟和超人不能归A个类属,但是具备有相同的特性:会飞的。所以引入了概念叫做接 口,可以用于规范实现接口的类中必须实现接口中抽象方法。
接口可以理解为一种契约
使用关键字interface
定义接口
- 接口中只能定义抽象方法,从JDK1.8引入default关键字开始允用加认实现
public interface 会飞的{
public void 起飞();
public void 巡航飞行();
public void 降落();
}
接口不能直接使用,必须有对应的实现类
public class 飞机类 implements 会飞的{ //共性是通过实现接口来表示的
private String name; //名称,这是类属的属性,这里可以定义字节类的成员,和接口无关
//如果当前类不是抽象类,则必须实现接口中的所有抽象方法
@Override
public void 起飞(){
System.out.println("使劲的跑,一抬头就飞起来了");
}
@Override
public void 巡航飞行(){
System.out.println("使劲的烧油...");
}
@Override
public void 降落(){
System.out.println("我对准...");
}
}
通过接口定义变量,使用具体类的实例,进行调用
会飞的 obj = new 飞机类();
obj.起飞();
obj.巡航飞行();
obj.降落();
引入接口的目的在于隔离实现
1 public void 出差(飞机 obj){}
2 //这种写法当前类和飞机是耦合的,如果需要坐超人出差,则必须修改源代码
3
4 public void 出差(会飞的 obj){}
5 //当前类只是和接口耦合,任何实现了接口的对象都可以作为参数进行传入
使用接口而不是使用具体的类,则可以实现在实现接口的多个具体实现类之间进行更换,例如:定义出超人类
什么是接口
在Java中不直接支持多继承,因为会出现调用的不确定性,所以Java将多继承机制进行改良,在Java中变成了多实现。一个类可以实现多个接口,一个接口可以继承多个接口
interface IA{//接口中如果不使用default只能定义抽象方法,也可不定义方法。如果没有方法 的接口称之为标识接口或者旗标接口
interface IB{}
interface ic extends IA,IB{} //正确的,类不允许多重继承,接口允许多重继承。其中IC中 的方法等于IA+IB
class A implements IA,IB{} //允许一个类实现多个接口
//要求实现类必须实现所有接口中的抽象方法,否则当前类就是抽象类
IA a=new A();
IB b=new A();
- 接口是一种特殊的抽象类,接口只能被abstract或者public修饰。实际上从语法的角度上说,外部接口还允许package范围限定,但是跨包不能访问,所以不使用
- 没有构造器方法
public interface IA ( public IAO {} //语法报错
3 }
- 没有属性,只能定义常量
public interface IA {
//Illegal modifier for the interface field IA.age; only public, static & final are permitted
private int age;
String USER_NAME="df"; //这里定义的限定词可以不写,默认就是public static final。如果要写不能冲突
//常量的命名规范,全大写,下划线分词
}
- 可以包含抽象方法,也可以没有抽象方法。从JDK1.8开始允许在方法前添加关键字default定义方法 的默认实现
//允许接口中没有方法
interface IB {
boolean DEBUG = true;
}
//允许接口中定义方法,接口允许多重继承
interface IC extends IB {
// 范围限定词使用protected则报错。Illegal modifier for the interface method pp; only
// public,
// abstract, default, static and strictfp are permitted
//vold pp() ; // 如果不写,则默认public abstract
default void cc() {
System.out.println("这个接口中提供的方法默认实现");
}
}
//由于工cl接口中的方法有默认实现,所以接口的实现类中可以没有方法,当然也允许覆盖定义 class Climpl implements IC1 {
public void cc() {
System.out.println("这个方法可以不定义,如果修改重新定义也可以覆盖默认方法");
}
- 在JDK1.8中允许在接口中定义静态方法
interface IC extends IB {
//在JDK1.8中允许在接口中定义静态方法
public static void ff() { //这个静态方法可以通过接口名称直接调用IC.ff(),注意没有抽象静态方法的概念
System.out.println("ffffff");
}
}
- 抽象方法必须在实现类中提供实现
interface IA{} //定义接口
class AImpl implements IA{} //实现类
- 可以使用default关键字给抽象方法提供默认实现,有默认实现的方法在实现类中可以重新定义,可以不重新定义
报错原因:Java中不支持类的多重继承,一个类只能有一个双亲类,但是允许接口多重继承
public class A{}
public class B{}
public class C extends A,B{} //语法错误
接口
public interface A{}
public interface B{}
public interface C extends A,B{} //语法正确
问题:
interface IA{
default void pp(){
System.out.println("IA default");
}
}
interface IB{
default void pp(){
System.out.println("IB default");
}
}
class CImpl implements IA,IB{}//?
default带来的问题:
interface IA {
default void pp() {
System.out.println("IA default");
}
}
interface IB {
default void pp() {
System.out.println("IB default");
}
}
//语法报错的原因是pp方法的实现不唯一
interface IC extends IA, IB {
//提供新的默认实现即可以解决报错问题
default void pp() {
IA.super.pp(); //调用工A2接口中的默认实现
System.out.println("IB default");
IB.super.pp();//调用工B2接口中的默认实现
}
}
//语法报错的原因是pp方法的实现不唯一
class DImpl implements IA, IB {
//提供新的方法实现即可以解决报错问题
@Override
public void pp() {
IA.super.pp(); //调用工A2接口中的默认实现
System.out.println("DImpl implements");
IB2.super.pp();//调用工B2接口中的默认实现
}
}
接口中静态访问问题:
public class Test {
public static void main(string[] args) {
// IA2 aa=new D2lmpl ();创建工A2接口类型对象调用静态方法报错
// aa.ccQ;语法报错
//
// IC2.CCO;语法报错
}
}
interface IA {
//接口中的静态方法不能继承?
//接口中的静态方法不需要构建对象即可直接调用,不允许new实现类后通过对象进行调用
public static void cc() {
System.out.println("IA static..");
}
}
最佳编程实现
允许一个类实现多个接口,但是每个接口的抽象方法都必须提供实现,否则是抽象类。提供的实现也可以是继承
- 首先通过接口定义调用的规范
- 然后使用抽象类实现接口,其中定义公共实现
- 最后定义子类继承抽象类,实现所有的抽象方法
public interface IA1{
public void p1();
}
public interface IA2{
public void p2();
}
public class A1{
public void p1(){}
}
public class A2 extends A1 implements IA1,IA2{
public void p2(){} //继承实现了p1方法,由于A2类不是抽象类,所以必须实现p2方法
}
public abstract class A3 extends A1 implements IA1,IA2{}//因为p2方法没有实现
接口的出现避免了单继承的局限性,这样定义C接口则拥有A+B的所有定义,可以使用A和B接口以及父类D声明变量类型,直接new T。但是约束时,用谁声明变量编译器系统识别就是谁这种类型,也就意味只能调用识别类型中的方法,不能调用其他方法
interface A{
public void p1();
}
interface B{
public void p2();
}
interface C extends A,B{} //语法正确,接口允许多重继承。但是类不允许多继承
class D{
public void abc(){}
}
class T extends D implements C{
public void p1(){}
public void p2(){}
}
D d=new T();//这里只能调用abc方法,如果使用其他方法则需要进行类型转换
A a=new T();//这里只能调用A接口中声明的方法pl
B b=new T();//这里只能调用B接口中声明的方法p2
T t=new T();//可以调用所有方法
//如果不是声明类型中的方法,则需要通过强制类型转换后才能调用
语法规则
访问修饰符 interface 接口名{ }
一般建议接口名称使用形容词,外部接口范围限定词可以使用 public或者默认package,内部接口4个都能使用
- 如果定义public接口,则规则和定义public类一致,要求接口名称和文件名称一致
- 一个文件中可以定义无数个类或者接口,但是只能定义一个公共类或者公共接口,要求public和文件名称一致
- 在一个包中不允许名称冲突,不论是接口名称还是类名称。记得接口也是一种特殊的类
- 外部的接口只能使用public、默认这两个范围限定词;如果定义内部接口则可以使用4大范围限定词
- 接口实际上提供了同一的操作界面(方法),如果是JDK1.8-版本则一个方法也不实现,等待某类或某几个类去实现它的方法(接口中的所有方法必须是抽象的)。如果使用的是JDK1.8+允许使用default在接口中定义默认实现,这个实现允许在实现类中重新定义覆盖,同时允许定义静态方法
- 接口中没有属性,只能定义常量,它提供一些常量,实现它的类可以共享这些常量
- 接口可以给出访问控制符,用public修饰的是公共接口,到处可见;如果定义接口没有范围限定词,则只能在同包中访问
- 在接口中不能定义静态块static{},但是可以定义静态方法。静态方法必须有实现,没有抽象静态方法的概念
- 接口中不能定义构造器,不能定义非静态代码块
- 接口不能被实例化,只能通过实现类所实现,但是可以用于声明变量的类型。
接口 变量名=new 实现接口类();
接口的特殊特征
- 一个类只能有一个父类!
- 一个类可以实现多个接口!
- 一个接口可以继承多个接口!
接口的作用
- 统一访问
接口 obj=new 实现1();
可以还有实现2,实现3等
obj只能调用接口中定义的方法 - 解耦:通过接口可以隔离具体实现
解耦就是在使用者和实现者之间没有关系。无论实现者如何改变实现,对于使用者使用不会变化
接口和抽象类的异同点
- 相同点:都是不断向上抽取而来的
- 不同点:
- 抽象类需要被继承,而且只能单继承
- 接口需要被实现,而且可以多实现
- 抽象类中可以定义抽象方法和非抽象方法,子类继承后可以叫使用非抽象方法
- 定义抽象类的主要目的在于延迟实现和公共方法骨架
- 接口中只能定义抽象方法,必须由子类去实现;JDK1.8+中允许接口中的方法有默认实现,实现类中可以直接使用默认实现,允许覆盖定义
- 抽象类的继承是is a关系,在定义该体系的基本共性内容
- 接口的实现是like a关系,在定义体系额外功能
- 接口中只能定义常量,而且必须被初始化,抽象类中可以定义属性,允许在声明时直接初始化,也可以不初始化,同时允许定义常量
接口中的方法必须全部是抽象的(JDK1.8+版本中可以通过default关键字定义方法的默认实现,允许定义静态方法),抽象类中可以有抽象方法也可以有普通方法
public class Test1 {
public static void main(String[] args) {
IA.pp();
AImpl.pp();
IA a = new AImpl();
a.pp();//语法报错
}
}
interface IA {
public static void pp() {
System.out.println("IA2 static...");
}
}
class AImpl implements IA {
public static void pp() {
System.out.println("AImpl static...");
}
}
public class Test2 {
public static void main(string[] args) {
Fa ff = new Son();
ff.PP();
}
}
class Fa {
public static void pp() {
System.out.println("Fa static...");
}
}
class Son extends Fa {
public static void pp() {
System.out.println("Son static...");
}
}
public void pp();
在接口中是抽象方法,但是如果public void pp(){}
语法错误,在JDK1.8+版本中可以使用public default void pp(){}
就合法了
如何使用接口
一般使用接口隔离具体实现,可以将类之间的相互依赖变为类对接口的依赖。例如出差类和会飞的东西 通过会飞的接口进行隔离,这样不管出差类需要修改或者会飞的东西需要修改,都不会相互影响
如果一组相关的类中有公共的方法和特殊的方法,可以使用抽象类,在抽象类中固化公共的方法,而无需具体子类重复实现;但是在抽象类中无法实现的方法可以延迟到子类中再实现。例如:排序器Bubblesorter,其中抽象类BubbleSorter固化了所使用的冒泡排序算法,而将无法实现的bigger比较算法延迟到Bubblesorter的子类PigSorter中实现,同时PigSorter中也不需要重新定义排序算法
最佳软件开发实践:先定义接口规范调用方法,在使用抽象类实现接口定义公共方法,最后再定义具体子类实现所有的方法
接口和抽象类的使用场景
从设计层面看,抽象类体现继承关系is a,它主要描述类的从属关系或者父子关系,抽象类和它的派生类之间是典型的IS-A关系,即子is a父。
interface可以多实现,而且不要求实现者和interface定义在概念本质上是一致的,仅仅是实现了 interface定义的契约而已。它主要描述的是类型间的行为合同,接口和它的实现类之间是典型的CAN-DO 关系, 即子can do父。
问:为什么接口需要默认方法
答:在接口添加默认方法不需要修改实现类,接口新增的默认方法在实现类中直接可用。 另外还要注意默认方法冲突问题。
Comparable 和 Comparator 接口
int k=10;
if(k>0)
int aa=100;
System.out.println(aa);
Comparable 接口
Comparable的含义是可比较的,如果对象所在的类实现了Comparable接口,这类对象就是可以行大小的比较,接口就是用于指定对象排序规则的。
此接口强行对实现它的每个类的对象进行整体排序。此排序被称为该类的自然排序,类的compareTo方法被称为它的自然比较方法。实现此接口的对象列表(和数组)可以通过Collections.sort (和 Arrays.sort)进行自动排序。实现此接口的对象可以用作有序映射表中的键或有序集合中的元素,无需指定比较器。
强烈推荐(虽然不是必需的)使自然排序与equals一致。所谓与equals一致是指对于类C的每一个e1和e2来说,当且仅当(e1 .compareTo((Object)e2) == 0)
与e1 .equals((Object)e2)
具有相同的布尔值时,类C的自然排序才叫做与equals一致。
public interface Comparable<T> {
public int compareTo(T o); //必须实现的方法,这里定义两个对象的比较规则,如果相 当返回为0;如果当前对象大于参数。则返回正数;如果当前对象小于参数。则返回负数
}
例如:苹果是可比较的,则需要Apple类实现Comparable接口
public class Apple implements Comparable {
private double re; // 半径值
@Override
public int compareTo(Object obj) {
if (obj != null && obj instanceof Apple) {
Apple app = (Apple) obj;
if (this.re > app.re){
return 1;
} else if (this.re == app.re){ //浮点数无法精确存放 Math.absCthis.re-app.re)<le-6
return 0;
}
}
return -1;
}
}
算法骨架类使用
public class Bubblesorter {
public void sort(Comparable[] arr) {
for(int i = 1; i < arr.length; i++) {
for(int k = 0; k < arr.length - i; k++) {
if(arr[k].compareTo(arr[k+1])>0) {
comparable tmp = arr[k];
arr[k] = arr[k + 1];
arr[k + 1] = tmp;
}
}
}
}
}
使用工具类排序
public class Test2 {
public static void main(String[] args) {
Apple[] arr = new Apple[10];
for(int i = 0; i < arr.length; i++) {
Appletmp = new Apple(Math. random。* 300 + 100);
arr[i] = tmp;
}
Arrays.sort(arr);
// int pos=Arrays.binarysearch(10); 折半查找
System.out.println(Arrays.tostring(arr));
}
}
面向对象设计的6大原则
现在的编程的主流语言基本上都是面向对象的。面向对象是一种编程思想,包括三大特性和六大原则, 其中,三大特性旨的是封装、继承和多态;六大原则指的是单一职责原则、开闭式原则、迪米特原则、 里氏替换原则、依赖倒置原则以及接口隔离原则。这些原则不是孤立使用的,最好相互参照、相互平衡的进行使用
单一职责原则
单一职责原则的定义是:就一个类而言,应该只有一个引起它变化的原因。简单来说,一个类应该是一组相关性很高的函数和数据的封装。
开闭原则
开闭式原则的定义:软件中的对象应该对扩展是开放的,但是,对修改是关闭的。
里氏替换原则
里氏替换原则的定义:所有引用基类的地方都必须能够透明的使用其子类。
依赖倒置原则
依赖倒置原则指的是高层次模块不应该依赖于低层次模块的具体实现,两者都应该依赖其抽象。
接口隔离原则
接口隔离原则的定义:类间的依赖关系应该建立在最小的接口之上
迪米特法则
迪米特法则(或迪米特原则):一个对象应该对其他对象有最少的了解。一个类应该对自己需要耦合或调用的类知道得最少,类的内部如何实现、如何复杂都与调用者或者依赖者没关系,调用者或者依赖者只需要知道他需要的方法即可,其他的一概不关心。
总结
- 类是一组具有相同属性和相同操作的对象的集合,是模板
- 对象是从一个类实例化来的,每个对象具有相同的结构和行为
- 面向对象的基本三大特性:封装、继承、多态
- 继承是类间的基本关系,是基于层次关系的不同类共享数据和操作的一种机制
- 消息传递时对象间通信的手段,一个对象通过向另外一个对象发送消息来请求其服务。消息=接收对象名+调用操作名+参数
- 消息完全由接收者解释,接收者独立决定采用什么方法完成所需的操作
- 多态是指同一个操作作用于不同对象可以有不同的解释,并产生不同的执行结果
面向对象的方法
- 符合人们对于客观世界的认识规律,易于维护、理解、扩充和修改,支持软件复用
面向对象的分析
- 获取用户需求,标识类(包括定义属性和操作),刻画类的层次结构,表示类(对象)之间的关系
- 为对象行为建模
- 对系统的静态和动态行为建模
面向对象分析的步骤
- 获取用户需求,标识类,包括定义属性和操作
- 标识场景和使用情况,建造需求模型,根据需求选择类和对象,包括属性和操作
- 刻画类的层次关系,表示类(对象)之间的关系,为对象行为建模
- 定义类的结构和层次,建造对象-关系模型,建造对象-行为模型
- 设计复审