09-接口和抽象类
09-接口和抽象类
子类继承父类,如果多个子类各自行为(方法)和父类行为(方法具体功能)不一样,所以都需要继承后各自重写父类方法。
此时父类方法其实已经没有实现的必要了。在Java中有个概念叫抽象方法的概念:
1. 抽象方法只声明,没有实现的方法。即没有方法体,是一个不完整的方法。
2. 定义了抽象方法的类必须是抽象类。
3. 抽象类不能被实例化(无法创建抽象类的对象),必须被继承,由子类进行实例化。
抽象类
用abstract关键字来修饰一个类时,该类叫做抽象类。
语法:[访问权限] abstract class 类名{ 类体... }
1. 抽象类必须被继承。--- 抽象方法,类无法自己实例化对象
2. 无法直接对抽象类进行实例化,必须由子类实例化对象实体。
3. 子类继承抽象类时,子类必须重写抽象类中的所有的抽象方法。
4. 如果不想重写父类的抽象方法,子类也必须定义成抽象类
抽象方法
- 只声明,没有实现的方法;——即没有方法体,是一个不完整的方法。
- 抽象方法必须定义在抽象类或者接口;
- 语法:
访问权限 abstract 返回值类型 方法名(参数【可选】);
//子类继承抽象父类,如果存在抽象方法,子类必须去重写父类抽象方法。
//重写原则: 子类的访问权限 不能比 父类方法访问权限 还严格==>public~ 缺省。 private最严格
//声明一个普通方法
public final static void 方法名(){
//方法体: if/for / 代码段
}
//抽象方法
public final static abstract void 抽象方法名(); // 声明了一个抽象方法,不完整的方法————抽象方法只有声明,不能有`{ }`
- 抽象方法不能被private修饰,即只要影响子类无法重写的因素都不可有。
抽象类的特点
- 抽象类和抽象方法必须用abstract修饰。
- 抽象类不一定有抽象方法,但有抽象方法的一定是抽象类或者接口;
抽象类不能被直接实例化,只能由子类实例化对象实体。
- ——抽象类只能被继承,作为其它类的超类,
- 这一点与最终类(final类)正好相反。
- final修饰类的特点:不能被继承,不能被重写,与抽象类正好相反
- 抽象类的子类:
- 子类必须重写抽象类中的所有的抽象方法;
- 如果子类不想实现,那么必须将自已也定义一个抽象类。
例子:水果类Fruit、苹果Apple
/**
* 当父类的方法,每次都需要被不同的子类进行重写,此时父类方法已经没有实现的必要,只需要声明就好了.
* 在Java中有个概念叫抽象方法的概念:
* 抽象方法:
* 1.抽象方法只声明,没有实现的方法。即没有方法体,是一个不完整的方法。
* 2.抽象方法的语法:由abstract关键字修饰方法,并且没有{}, 以';'结尾
* 3.抽象方法必须定义在抽象类或者接口
* 抽象类:
* 1.由abstract关键字修饰类;
* 2.抽象类必须被继承,无法直接对抽象类进行实例化,必须由子类实例化对象实体.
* 3.子类继承抽象类,必须重写父类中所有的抽象方法
* 4.如果不想重写父类的抽象方法,子类也必须定义成抽象类
* @author wyn
*/
abstract class Fruit{
int num;
String color;
abstract void eat();
// public abstract void cycly();//结果周期
public void cycly1() {//结果周期
System.out.println("水果从开花到结果需要一定的生长周期");
}
}
//继承抽象类,子类必须重写父类中所有的抽象方法;
//如果不重写父类的抽象方法,子类也必须定义成抽象类
//abstract class Apple extends Fruit{
class Apple extends Fruit{
@Override
public void eat() {
System.out.println("吃苹果要先洗一洗,削皮后,再吃..");
}
}
//测试类
public class TestAbstract {
public static void main(String[] args) {
// Fruit f = new Fruit(); //无法直接对抽象类进行实例化对象实体
Fruit f = new Apple(); //必须由子类进行实例化对象实体
Apple a = new Apple();
}
}
什么时候用抽象类?
当一个类总被继承,并且存在某个方法,父类无法去描述它,只能交给子类自己定义时,可以考虑用抽象方法,此时父类为抽象类或者接口。
创建接口类:右击包名--new-- interface
接口
接口(interface)就是方法定义和常量值的集合。接口是公开的抽象类,并且是通过关键字interface定义接口。不能有私有的方法或变量,接口中的所有方法都没有方法体。
——接口是特殊的抽象类,本质上并不是类。
• 通过接口可以实现不相关类的相同行为,而不需要考虑这些类之间的层次关系。
• 通过接口可以指明多个类需要实现的方法。
• 通过接口可以了解对象的交互界面,而不需了解对象所对应的类。
接口的定义
- 通过关键字interface修饰
- 只有常量,没有变量
- 只有抽象方法(JDK1.8之前的版本)
- 某些方法是可以有方法体的(JDK1.8版本之后)
- 由static修饰方法
- 由default修饰方法
//语法:
[public] interface 接口名 [extends SuperInterfaceList] {
//1.常量 +
//2.抽象方法()+
//3.由static或者default修饰的方法(JDK1.8版本之后)
}
注意:
- 接口中的方法和常量必须是public的;
- 一个public接口只能定义在同名的.java文件中;
接口与类的关系
由于接口本质不属于类,所以接口可实现多继承。与类继承不同,一个接口可以继承多个父接口。
//接口的实现:
class 类名 implements 接口名1,接口名2 { ... }
- 用implements子句来表示一个类实现某个接口;
- 子类可以实现多个接口,在implements子句中用逗号分隔。
- 子类可以是抽象类,但意义不大。
- 也可以是具体类,需要重写接口中所有的抽象方法(推荐)
- 在子类体中可以使用接口中定义的常量,而且必须实现接口中定义的所有方法。
接口的案例分析:
接口的成员特点
/**
* 接口:是特殊的抽象类,本质上不算类。是常量和方法定义(抽象方法)的集合。
* 1.接口是公开的抽象类,并且由interface定义接口;
* 2.成员变量:只能是常量,并且是静态的公开的;
* 默认修饰符:public static final
* 建议手动给出;
* 3.成员方法:1)方法定义(抽象方法):
* 默认修饰符: public abstract;---> 建议手动给出
* 2)某些方法是可以有方法体的(JDK1.8版本之后):
* 方法必修由 static修饰 或者 default修饰;
* 4.构造方法:接口没有构造方法
* 5.接口本质上不算类,但是同一个.java文件也只能有一个public修饰的接口
* @author wyn
*
*/
public interface MyInterface1 {
int num =100; //没有权限修饰,默认由public static final修饰
// int num2; //不能定义变量,必须给定初始值
// protected int[] arr = {1,2}; 权限修饰符只能是public
public final String SUCC_CODE ="1004";
public static final String ERR_CODE ="1004";
// protected void looks();
//Illegal modifier for the interface method look1; only public,
//abstract, default, static and strictfp are permitted
void fly();//方法定义不写时,默认public abstract
public abstract void fly1();
public static void fun1() {
System.out.println(num);//在静态方法中可以直接访问,num是静态的
//num无法修改,默认由final修饰的常量.
// num=20;//The final field MyInterface1.num cannot be assigned
System.out.println("JDK1.8及以上版本,由static修饰的方法可以有方法体..");
}
public default void fun2() {
this.fun2();
System.out.println("JDK1.8及以上版本,由default修饰的方法可以有方法体..");
}
//构造函数 Interfaces cannot have constructors
// public MyInterface1() {}
}
interface MyInterface2 {
int num =100; //没有权限修饰
}
- 接口的成员变量
- 只能是常量,并且是静态的公开的;
- 默认修饰符:public static final
- 建议手动给出;
- 接口的成员方法
- 1)方法定义(抽象方法):
- 认修饰符: public abstract;---> 建议手动给出
- 2)某些方法是可以有方法体的(JDK1.8版本之后):
- 方法必修由 static修饰 或者 default修饰;
- 接口的构造方法
- 接口没有构造方法
接口为父接口及简单应用
接口的子接口(interface),可以继承多个接口
- 由extends表示继承父接口,且不必重写父接口的抽象方法
/**
*接口可以实现多继承,即可以继承extends多个接口,由逗号分隔
*/
public interface MyInterface3 extends MyInterface1,MyInterface2 {
}
接口的子类(class),可以实现多个接口
- 由implements表示实现接口:可以是具体类,也可以是抽象类(意义不大)。
/**
* 接口的子类只能由implements关键字表示子类实现接口,且子类可以实现多个接口
* 1.接口的子类是具体类时:
* 1)只能由implements关键字表示子类'实现'接口,且子类可以实现多个接口
* 2)必须重写接口中所有的抽象方法;
* 3)可以使用父接口中定义的常量;
* 4)子类在调用父类的静态方法,必须用[父接口.静态方法名];
5)如果在子类中直接使用 静态方法名,会导致无法获取到父接口的静态方法,因为会默认在子类中进行寻找静态方法
* 2.也可以是抽象类,但意义不大大-->接口是特殊的抽象类,所以抽象类实现接口可以不重写父接口的抽象方法
*
* 注意:子类实现多个接口时,存在同名的
* 1)成员变量时,需要用[父接口.]的方式,指定具体调用哪一个;
* 2)静态方法时,需要用[接口/类名.静态方法],指定具体调用哪一个;
* 若该静态方法如果在子类中也定义了,它是不算被重写的。
* 3)default修饰的[方法名和参数]都相同的方法,子类必须重写该方法;
*
* @author wyn
*/
//public class MyClass1 extends MyInterface1 {
//The type MyInterface1 cannot be the superclass of MyClass1; a superclass must be a class
public class MyClass1 implements MyInterface2,MyInterface1 {
int num=5;
public static void fun1() {
System.out.println("Myclass的fun1()..");
}
@Override
public void run() {
System.out.println(SUCC_CODE);
//子类实现多个接口时,存在同名的成员变量/方法时,需要用'父接口.'的方式,指定具体调用哪一个
System.out.println(num);
System.out.println(MyInterface1.num);
System.out.println(MyInterface2.num);
//调用父接口的静态方法,必须用'接口名.'的方式;
//若父接口的静态方法如果在子类中也定义了,它是不算被重写的。
//fun11();
MyInterface2.fun1(num);
MyInterface1.fun1();
fun1();//本类中可以忽略类名
}
@Override
public void fly() {
}
@Override
public void fly1() {
}
//Duplicate default methods named fun2 with the parameters ()
//and () are inherited from the types MyInterface2 and MyInterface1
//如果多个父接口存在default修饰的方法名和参数相同的方法,子类必须重写该方法
@Override
public void fun2() {
MyInterface1.super.fun2();
}
}
//可以是抽象类,可以不重写父接口的抽象方法-->接口是特殊的抽象类
abstract class MyClass2 implements MyInterface1 {
}
子类继承父类又实现父接口
- 注意:当一个类实现一个接口的default方法,同时又继承另一个类的方法,则子类的方法优先和父类方法一致
设计接口的目的
例子1:如果有一个抽象类是鸟类,此时乌鸦和飞机都会飞,但是乌鸦是鸟类,此时飞机也是鸟类的子类吗?
public abstract class Bird {
String name;
public Bird(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
abstract void fly();
}
class Crow extends Bird {
@Override
void fly() {
System.out.println("乌鸦飞。。。。。。");
}
}
//class Plane extends Bird ==> 是bird的子类吗??
例子2:定义一个飞(Fly)的接口,由飞机去实现这个功能.
public interface Fly {
void fly();
}
class AirPlane implements Fly {
@Override
public void fly() {
System.out.println("我是飞机,我会飞");
}
}
class Crow implements Fly {
@Override
public void fly() {
System.out.println("我是乌鸦,我会飞");
}
}
接口设计理念:
- 接口只关心功能,并不关心功能的具体实现;
- 接口的思想在于它可以增加很多类都需要的功能,使用相同的接口不一定有继承关系。
java中抽象类和接口的区别是什么?
都是有抽象方法
接口是特殊的抽象类。
接口本质上不属于类, 抽象类本质上还一个类,但是我们会认为抽象类不是一个完整的类(不完整的方法 抽象方法)。
为了让这个父类(是抽象类,但是它没有抽象方法),为了让这个类使用子类实例化出来的对象实体
最后还是 需要引用子类的对象实体。
1.成员区别
抽象类 | 接口 | |
---|---|---|
变量、常量、访问权限控制符都不限制(可以是四个访问权限) | 成员变量 | 只能是public static final修饰的常量 访问权限(只能是public,其他三个访问权限不允许) |
有 | 构造函数 | 无(因为接口不是类,同时接口没有实现方法(除非是由static/default修饰,构造函数没有这两个修饰词)) |
抽象和非抽象都可以,并且抽象方法的访问权限符除了private都可以修饰(抽象方法 必须要被子类重写,私有的只允许本类去调用) | 成员方法 | 抽象方法只能由public abstract修饰;非抽象方法必须由static修饰或者default修饰 |
2.关系区别
继承关系(extends):拿来就用 | 实现关系(implements):把不完整的方法实现成 具体的 | |
---|---|---|
类与类 | 只能单继承,若父类是抽象类,要重写父类中的所有抽象方法 | 无实现关系 |
类与接口 | 无继承关系 | 可以实现多个父接口,并且要重写所有抽象方法;若多个父接口有同名、同参数的default方法,子类必须重写 如果是抽象类实现接口,意义不大,抽象类可以不用重写接口的抽象方法,(一般都是由具体类去实现接口。重写我们的接口抽象方法,这样的去调用一个具体的方法) |
接口与接口 | 可以多继承() | 无实现关系 |
3.理念区别
- 抽象类与接口 都必须被继承
- 抽象类,被继承体现的是is-a的关系。关心的是类之间的层次关系以及他们的共性功能;
- 接口,被实现体现的是has-a的关系。关心的是类之间具有的相同功能而不关心他们的从属问题;
抽象类与接口的相同点:
抽象类和接口不能直接实例化,都必须通过其它子类实现才能使用。
内部类(inner)
内部类:在一个类的内部声明的类,称为内部类。内部类编译后也会形成一个单独的class,但它附属于其包含类。
分类
1.定义在类体中的:
- 定义内部类:由class定义。
- 由static修饰,表示静态内部类可以定义静态成员和非静态成员,但不可以访问非静态成员;
- 无static修饰,表示实例内部类,不可以定义静态的成员,可以访问外部类所有的成员
- 内部类可以直接使用其附属的外部类的成员变量和成员方法;
- 内部类相当于类的成员变量,可以在类体中或者被其他类使用:
- 被附属的外部类调用:可以把内部类当作正常类来访问;
- 在其他类中调用内部类,必须先导包。再根据内部类的分类,即:
- 静态内部类,直接通过 new 内部类名()的方式创建内部类的对象实体;
- 实例内部类,通过外部类对象.new 内部类名()的方式创建内部类对象实体。
- 内部类的实例化对象需要绑定一个外围类的实例化对象,而静态嵌套类的实例化对象不能也无法绑定外围类的实例化对象。
- 字节码文件的命名格式:外部类
$内部类.class
eg: Outer$Inner.class
/**
* 内部类:在一个类的内部声明的类;内部类编译后会有单独的.class文件,但是附属于包含它的外部类下.
* 内部类的分类:
* 1.定义在类体中:
* 1)内部类可以直接使用其附属的外部类的成员变量和成员方法;
* 2)外部类要调用内部类的成员变量和方法,需要把内部类当作正常类来访问;
* 3)在其他类中调用内部类,可以看作是外部类的成员变量,如果要使用内部类,
* 必须先导包,再根据内部类的分类,判断是否需要通过 [外部类的对象.new 内部类名()]的方式创建内部类的对象实体.
* 2.定义在方法中:
*
* @author wyn
*
*/
public class TestInner {
public static void main(String[] args) {
Outer out = new Outer();
out.test();
//内部类在外部类中可以看成是成员变量,所以创建内部类的对象实体为[外部类的对象.new 内部类名()]的方式
// Inner in2 = new Inner();
Inner in2 = out.new Inner();
in2.inNum=55;
System.out.println("in2.inNum="+in2.inNum);
out.new Inner().print();
}
}
//外部类
class Outer{
int outNum =100;
static String city="厦门";
public static void eat() {
System.out.println("会吃饭...");
}
public void test() {
//System.out.println("Outer.test().."+inNum);
//外部类使用内部类的实例变量和实例方法,必须创建对象
Inner in = new Inner();
System.out.println("Outer.test() 外部类只能通过创建内部类的对象,通过[对象.]的方式访问内部类实例变量:"+in.inNum);
new Inner().print();
// System.out.println("外部类使用Inner2中的静态变量:"+school);//不算本类
System.out.println("外部类使用Inner2中的静态变量通过[类名.]的方式:"+Inner2.school);
System.out.println("外部类使用Inner2中的静态变量也可以[对象.]的方式:"+new Inner2().school);
}
//定义内部类:由class定义,可以由static修饰
class Inner{
int inNum =90;
//除非使用常量表达式初始化,否则不能在非静态内部类型中声明字段类型为静态
//static String school="厦大";
public void print() {
System.out.println("Inner.print() 可以直接打印外部类的实例变量 outNum="+outNum);
System.out.println("Inner.print() 可以直接打印外部类的静态变量 city="+city);
eat();
}
}
static class Inner2{
static String school="厦大";
}
}
2.定义在方法体中的:
在方体中(代码块{}中),都可以声明:有名内部类(只要在{}中定义都可以)+匿名内部类(一般用于实现抽象方法)
- 只能在当前方法中可以直接使用内部类的变量和方法;
- 生命周期:随着方法消失而消失
- 字节码文件命名格式:
- 有名内部类:外部类
$
数字内部类.class - 匿名内部类:外部类
$
数字.class -- 由编译器产生一个数字表示匿名内部类
- 有名内部类:外部类
- 好处:如果抽象类/接口只被执行一次,则可以用匿名内部类充当抽象类的子类,进行实例化创建对象,并且重写抽象方法;
/**
* 内部类定义在方体中(代码块{}中),都可以声明
* 有名内部类(只要在{}中定义都可以) + 匿名内部类(一般用于实现抽象方法):
* 1.在当前方法中可以直接使用内部类的成员,其他方法无法调用到内部类;-->随着方法消失而消失
* 2.字节码文件命名格式:
* 有名内部类:外部类$数字内部类.class
* 匿名内部类:外部类$数字.class --由编译器产生一个数字表示匿名内部类
*
* 3.匿名内部类的好处:
* 匿名内部类可以充当抽象类/接口的子类对象实体,并在匿名内部类中实现抽象方法的重写:
* 即抽象类不能直接实例化,但是可以通过子类和匿名内部类创建对象实体;
* 4.定义在方体中的内部类,随着方法消失而消失;
* @author wyn
*
*/
public abstract class OutFun {
int num;
static String country="China";
public void fun1() {
//Inner3 i3= new Inner3(); //无法使用其他方法中定义的内部类
//匿名外部类,只能使用一次,随着该方法调用完毕,匿名内部类不再被引用时,处于游离状态等待被垃圾回收
OutFun of = new OutFun() {//匿名内部类 OutFun$1.class
@Override
public void fun2() {
System.out.println("重写抽象方法 fun2()...");
}
};
}
public abstract void fun2(); //抽象方法
public void outFun() {
//有名字的内部类:无法声明静态的变量/方法
//内部类可以直接使用外部类和其他类的成员
//只有当前的方法中才能使用内部类的成员,方法外则无法调用到内部类成员
class Inner3{ //OutFun$1Inner3.class
int xx=66;
// static int yy;
// public static void inFun() {
public void inFun() {
System.out.println("outFun()方法中声明的Inner3.inFun()");
System.out.println("Inner3.inFun() 访问外部类成员变量:num="+num+",country="+country);
fun1();
class Inner02{//OutFun$1Inner3$1Inner02.class
int xx1;
}
}
}
Inner3 i3= new Inner3();
System.out.println("OutFun.outFun() 使用内部类成员 xx="+i3.xx);
i3.inFun();
}
}
3.何时使用内部类:
- 使用内部类好处: 可以直接使用该包含类的变量和方法。 内部类只能在包含它的类中使用,同时它可以看作是该包含类的一段特殊代码。
- 当一个类能直接访问另一个类中的成员时,将第一个类定义为第二个类的内部类。
- java中内部类多用在GUI(图形用户界面)部分的事件处理机制中。
面试题:
- 一个抽象类如果没有抽象方法,可不可以定义为抽象类?若可以,有什么意义?
- 可以,意义:为了不让该类创建本类对象实体,只能交给子类去完成
- abstract 不能和哪些关键字共存?
- final、static、private