Java面向对象07
Java接口详解
什么是接口
接口就是规范,定义的是一组规则。
接口的本质
接口的本质就是契约,制定好之后大家都遵守。
体现在现实生活中的话可以理解为:“如果要实现这个项目,那么就必须要完成项目规定的任务”
接口的声明
接口通过interface关键字来声明,接口中可以定义一些常量和方法
格式:
public interface 接口名{
//常量和方法
}
示例:
public interface UserService {
/**
* 常量
*/
int AGE = 20;
/**
* 抽象方法
*/
void add(String name);
void delete(String name);
/**
* 静态方法
*/
static void a(){
//代码块
}
/**
* 默认方法
*/
default void b(){
//代码块
}
}
public class UserServiceImpl implements UserService,TimeService{
@Override
public void add(String name) {
}
@Override
public void delete(String name) {
}
}
定义接口的注意事项
1.接口中定义的变量编译器会在编译时帮我们自动加上修饰符 public static final ,即定义的均为常量
在Java官方文档里也有这样的一个声明:Every field declaration in the body of an interface is implicitly public,static,and final.
那么我们是否就可以将接口当作常量类来使用呢?答案是否定的,因为接口的本意就是对不同类中的方法进行抽象,而常量接口会对子类中的变量造成命名空间上的“污染”
一般情况下很少会有人在接口里面定义常量的,我们都是在接口中定义一些方法来约束,然后让子类去实现
2.接口中定义的抽象方法编译器会帮我们自动加上修饰符 public abstract (编译器帮我们实现了的,如果没有特别要求就不要去做多余的动作,如果想知道编译器帮我们做了哪些辅助,反编译就ok了,前面的构造器里面讲过)
3.接口中允许有静态方法(从Java 8开始)
静态方法无法由实现了该接口的类的对象来调用,它只能通过接口的名字来调用
接口中定义静态方法的目的是为了提供一种简单的机制,使我们不必创建对象就能调用方法,从而实现接口的竞争力
4.接口中允许定义默认方法(也从Java 8开始)
它始终由一个代码块组成,为实现该接口而不覆盖该方法的类提供默认实现,即default方法不能直接使用一个;来结束方法,要有本体。
接口中定义默认方法的理由是,因为一个接口可能由多个实现类,这些类就必须实现接口中定义的抽象方法,否则编译器报错。当我们需要在所有的实现类中追加某个具体的方法时,如果没有default方法的帮助,我们就必须逐一对实现类进行修改。
总结
1.类可以通过关键字implements实现多个接口
2.类实现接口后,必须要重写接口中的方法 类名一般以Impl结尾
3.接口中允许定义变量,但因为被public static final 隐式修饰,其值是无法改变的,即为常量。
4.接口中只允许定义抽象方法、静态方法(Java 8开始)和默认方法(Java 8开始)
5.接口不能直接实例化,必须先定义一个类去实现接口,然后再实例化。
6.接口可以是空的,即可以既不定义变量,也不定义方法
7.不能在声明接口的时候使用final关键字,否则编译器会报错!因为接口就是为了让子类实现的,而你非要整个final来阻止这种行为。(你这怕是要让人家断子绝孙😄😄)
8.接口中的方法的修饰符只能为public和default,不能使用protected、private或者final(public修饰抽象方法和静态方法,default为默认方法修饰符)
接口的作用
1.使某些实现类具有我们想要的功能。
比如,实现了Cloneable接口的类具有拷贝的功能,实现了Comparable或者Comparator的类具有比较功能。
Cloneable和Serializable一样,都属于标记型接口,它们的内部都是空的。实现了Cloneable接口的类可以使用Object.clone()方法,否则会抛出异常CloneNotSupportedException。
2.Java原则上类只支持单继承,但可通过接口实现多继承的目的
为什么说Java类只支持单继承呢?原因是容易引发意义不明确!⭐
举个栗子:有一个类TestA,如果允许它可以同时继承TestB类和TsetC类(即public class TestA extends TestB,TestC{}),且TestB、TestC两个类中都有同一方法run(),声明对象:TestA a = new TestA();
那么此时,a.run();应该调用哪一个父类的run()方法呢???显然无法给出答案,因此Java禁止类的多继承,提出了接口概念。但C++中是可以的,所以C++在这一点上就可以显示出C++语法是比Java要复杂一点。
但一个类可以实现多个接口,这又是为什么?⭐
举个栗子:假设类TestImplementationA实现了TestInterfaceB、TestInterfaceC两个接口(即public class TestImplementationA implements TestInterfaceB,TestInterfaceC{}),且TestInterfaceB、TestInterfaceC两个接口都有一个抽象方法run(),现在调用方法时,就没有任务不明确的地方。因为抽象方法是没有具体实现的,并且超类的任何方法都需要在子类中覆盖实现,所以在调用的时候其实是调用自己覆盖实现的方法,不存在调用不明确的地方。
public interface TestInterfaceB {
void run();
}
public interface TestInterfaceC {
void run();
}
public class TestImplementationA implements TestInterfaceB, TestInterfaceC {
@Override
public void run() {
System.out.println("实现接口类覆盖实现的方法");
}
public static void main(String[] args) {
TestImplementationA a = new TestImplementationA();
a.run();
}
}
3.实现多态性
同一操作作用于不同对象,可以有不同的解释,产生不同的执行结果。
多态的实现方式有两种:子类继承父类和类实现接口
即多个子类继承一个父类,多个类实现一个接口
单继承(一个类只能继承一个类)是类的特性,多接口(一个类可以实现多个接口)是接口的特性。绝对不能将多接口的概念和多态的概念搞混,紧抓多态的定义!
Test接口
public interface Test {
/**
* 接口方法drink
*/
void drink();
}
Beverage实现类
/**
* 类Beverage实现了接口Test
*/
public class Beverage implements Test {
/**
* Beverage重写了接口Test
*/
@Override
public void drink() {
System.out.println("饮料是可以喝的");
}
}
Beer实现类
/**
* 类Beer实现了接口Test
*/
public class Beer implements Test {
/**
* Beer重写了接口Test
*/
@Override
public void drink() {
System.out.println("啤酒是可以喝的");
}
}
Application测试类
public class Application {
public static void main(String[] args) {
//接口型变量t1指向Beer实现类对象
//接口型变量t2指向Beverage实现类对象
Test t1 = new Beer();
Test t2 = new Beverage();
t1.drink();
t2.drink();
/*
匿名对象的使用
new Beer().drink();
new Beverage().drink();
*/
}
}
输出测试结果
接口与抽象类的区别
抽象类是用来捕获子类的通用特性的,接口是抽象方法的集合。
抽象类可以允许有任意变量和普通方法,说明做还不够抽象,抽象的不够彻底。那么进行更彻底的抽象,规定只能写抽象方法,而这种规定我们给它起名叫接口。因此我们可以发现,接口的意义就是为了更好地将设计与实现分离。
接口的定义与类的定义相似,但是使用interface关键字。它也会被编译成.class文件,但是一定要明确它不是类类型,而是一种特殊的引用类型。
接口是系统中最高层次的抽象模型,不属于类!
接口可以多继承,注意是接口!相关内容以后再扩充
设计层面⭐
从设计层面来说,抽象类是对类的抽象,是一种模板的设计;接口是行为的抽象,是一种行为规范。
语法层面⭐
相同点
- 接口和抽象类都不能实例化
- 都用于被其他类实现或继承
- 都包含抽象方法,其子类都必须重写所有的抽象方法,不能选择性重写
不同点
\ | 抽象类 | 接口 |
---|---|---|
声明 | 抽象类使用abstract关键字声明 | 接口使用interface关键字声明 |
实现 | 子类使用extends关键字来继承抽象类,如果子类不是抽象类的话,它必须重写抽象类中的所有方法 | 子类使用implements关键字来实现接口,子类必须重写接口中的所有抽象方法 |
构造器 | 抽象类有构造器 | 接口没有构造器 |
权限修饰符 | 抽象类中的方法可以是任意访问修饰符 | 接口方法默认修饰符为public,并且不允许定义private或者procted |
继承实现关系 | 一个类只能继承一个抽象类 | 一个类可以实现多个接口 |
字段声明 | 抽象类的字段可以是任意的 | 接口的字段默认都是static和final的 |
备注:Java8之后接口中引入静态方法和默认方法,Java9引入了私有方法
接口和抽象类各有优缺点,在接口和抽象类的选择上,必须要遵守一个原则:
- 行为模型使用接口,少或避免使用抽象类
- 如果需要定义子类的行为,又要为子类提供通用的功能,可以选择抽象类
不过随着JDK的不断更新,接口的性能变得越来越强大,因此尽可能地使用接口。
OO的精髓就是,对对象的抽象,而最能体现这一点的就是接口。这也是为什么我们讨论设计模式都只针对具备了抽象能力的语言(比如C++、Java、C#等)的原因。设计模式所研究的,实际上就是如何合理的去抽象。
内部类
现阶段当作选学,如果JavaSE学完还不明白这部分内容,那就得将它作为必学了。
内部类就是在一个类的内部再定义一个类,比如在A类中定义一个B类,那么B类相对A来说就称为内部类,而A类相对B类来说就是外部类了。
- 成员内部类(只是定义在外部类中)
- 静态内部类(定义在外部类中,且被static修饰)
- 局部内部类(定义在外部类的方法里)
- 匿名内部类(实现接口的类,)
- Lambda表达式(JDK8的新特性,知道有这么个概念就行,多线程的时候学)
一个java文件里并列的类,只能有一个public class,但是可以有多个class!
1.成员内部类
- 启动类
public class Application {
public static void main(String[] args) {
Outer outer = new Outer();
//调用外部类
outer.out();
//通过这个外部类来调内部类的实例化对象并将其地址赋给inner
Outer.Inner inner = outer.new Inner();
//调用内部类
inner.in();
inner.getId();
}
}
- 成员内部类
public class Outer {
/**
* 这里的实例变量id是可以转换为getId方法的局部变量的
* 但是没有转的原因,只是为了说明内部类方法是可以获取外部类属性这一问题
*/
private int id = 10;
public void out(){
System.out.println("这是外部类的方法");
}
public class Inner{
public void in(){
System.out.println("这是内部类的方法");
}
//获得外部类的私有属性 从这里我们看出了内鬼的可怕性😄😄
//外部类都可以获得自己的方法,它是内部类必然也可以,就不方法代码了
public void getId(){
System.out.println(id);
}
}
}
2.静态内部类
public class Outer {
private int Id = 10;
public void out(){
System.out.println("这是外部类的方法");
}
public static class Inner{
public void in(){
System.out.println("这是静态内部类的方法");
}
//无法获得外部类的私有属性 不能从静态境况中引入非静态字段Id
//即前面方法调用一样,你不能用一个已经存在的去调一个还不存在的东西
/*public void getId(){
System.out.println(id);
}
*/
}
}
3.局部内部类
public class Outer {
/**
* 局部内部类 和局部变量一样定义在方法中,不允许加修饰符public,否则编译器就报错
*/
public void method(){
class Inner{
}
}
}
4.匿名内部类
public class Test {
public static void main(String[] args) {
/*
没有名字初始化类
匿名对象的使用,不用把实例对象保存在变量(引用)里
*/
new Apple().eat();
/*
正常的创建类的实例化对象及方法的调用
Apple apple = new Apple();
apple.eat();
*/
/*
实现了接口的类的匿名使用
现阶段不要求全部掌握,后面在分析源码的时候,见一个说一个
多线程里面很常见 另外Android的监听...
*/
new UserService(){
@Override
public void pay() {
}
};
/*
类“从UserService派生的匿名类”必须声明为抽象,或在“UserService”中实现抽象方法“pay()”
去和前面学习的抽象类对比学习,你能发现一些相似之处
UserService userService = new UserService(){
@Override
public void pay() {
}
};
*/
}
}
class Apple{
public void eat(){
System.out.println("1");
}
}
/**
* 实现用户服务接口
*/
interface UserService{
/**
* pay来实现支付的用户服务
*/
void pay();
}
如果我的博客对你有一点点帮助,望请大侠可以给文章点个赞再走~
声明:因个人能力有限,博文中必有不足之处,望学术同仁不吝赐教!一起学习一起进步!
以上内容均为原创,转载请注明出处!