Java面向对象之抽象类、接口、内部类
一、抽象类
1. 概念
- 用abstract关键字来修饰一个类,这个类叫做抽象类。 用abstract来修饰一个方法,该方法叫做抽象方法。
- 抽象方法:只有方法的声明,没有方法的实现。以分号结束:
比如:public abstract void talk(); - 含有抽象方法的类必须被声明为抽象类。
- 抽象类不能被实例化。抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类。
- 不能用abstract修饰变量、代码块、构造器;
- 不能用abstract修饰私有方法、静态方法、final的方法、final的类。
2. 注意事项
(1)抽象类中可以有成员变量、成员方法以及构造方法。
(2)抽象类中可以没有抽象方法,也可以有抽象方法。
(3)拥有抽象方法的类必须是抽象类,因此真正意义上的抽象类是由abstract关键字
修饰并且拥有抽象方法的类。
3. 实例应用
- 在航运公司系统中,Vehicle类需要定义两个方法分别计算运输工具的燃料效率和行驶距离。
- 问题:卡车(Truck)和驳船(RiverBarge)的燃料效率和行驶距离的计算方法完全不同。Vehicle类不能提供计算方法,但子类可以。
- 解决方案
Java允许类设计者指定:超类声明一个方法但不提供实现,该方法的实现由子类提供。
这样的方法称为抽象方法。有一个或更多抽象方法的类称为抽象类。 - Vehicle是一个抽象类,有两个抽象方法。
public abstract class Vehicle{
public abstract double calcFuelEfficiency(); //计算燃料效率的抽象方法
public abstract double calcTripDistance(); //计算行驶距离的抽象方法
}
public class Truck extends Vehicle{
public double calcFuelEfficiency( ) { //写出计算卡车的燃料效率的具体方法 }
public double calcTripDistance( ) { //写出计算卡车行驶距离的具体方法 } }
public class RiverBarge extends Vehicle{
public double calcFuelEfficiency( ) { //写出计算驳船的燃料效率的具体方法 }
public double calcTripDistance( ) { //写出计算驳船行驶距离的具体方法} }
注意:抽象类不能实例化 new Vihicle()是非法的
4. 实际意义
抽象类的实际意义不在于自身创建对象而在于被继承,当一个类继承抽象类之后必须重写抽象方法,否则该类也变成抽象类。
因此抽象类对子类具有强制性和规范性,叫做模板设计模式。
- 抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模
板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象
类的行为方式。
解决的问题:
- 当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
- 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式。
二、接口 (interface)
1. 基本概念
接口主要指比抽象类还抽象的类,因此不能实例化对象。
接口是抽象方法和常量值定义的集合。
定义类的关键字使用class,而定义接口使用interface关键字。
2.特点
- 用interface来定义。
- 接口中的所有成员变量都默认是由public static final修饰的。
- 接口中的所有抽象方法都默认是由public abstract修饰的。
- 接口中没有构造器。
- 接口采用多继承机制。
3. 类和接口之间的关系
类和类之间的关系 使用extends关键字表达继承的关系 支持单继承
类和接口之间的关系 使用implements关键字表达实现的关系 支持多实现
接口和接口之间的关系 使用extends关键字表达继承的关系 支持多继承
4. 抽象类和接口之间的主要区别
- 定义抽象类的关键字是abstract class,而定义接口的关键字是interface。
- 继承抽象类的关键字是extends,而实现接口的关键字是implements。
- 继承抽象类支持单继承,而实现接口支持多实现。
- 抽象类中可以有构造方法,而接口中不可以有构造方法。
- 抽象类中可以有成员变量,而接口中只可以有常量。
- 抽象类中可以有成员方法,而接口中只可以有抽象方法。
- 抽象类中增加方法可以不影响子类,而接口中增加方法通常都会影响实现类。
- 从jdk1.8开始允许接口中有非抽象方法,但必须使用default关键字修饰。
5. 实例
- 定义Java类的语法格式:先写extends,后写implements
class SubClass extends SuperClass implements InterfaceA{ } - 一个类可以实现多个接口,接口也可以继承其它接口。
- 实现接口的类中必须提供接口中所有方法的具体实现内容,方可实例化。否则,仍为抽象类。
- 接口的主要用途就是被实现类实现。(面向接口编程)
- 与继承关系类似,接口与实现类之间存在多态性
- 接口和类是并列关系,或者可以理解为一种特殊的类。从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义(JDK7.0及之前),而没有变量和方法的实现。
一个类可以实现多个无关的接口
interface Runner { public void run();}
interface Swimmer {public double swim();}
class Creator{public int eat(){…}}
class Man extends Creator implements Runner ,Swimmer{
public void run() {……}
public double swim() {……}
public int eat() {……}
}
与继承关系类似,接口与实现类之间存在多态性
public class Test{
public static void main(String args[]){
Test t = new Test();
Man m = new Man();
t.m1(m);
t.m2(m);
t.m3(m);
}
public String m1(Runner f) { f.run(); }
public void m2(Swimmer s) {s.swim();}
public void m3(Creator a) {a.eat();}
}
6. jdk1.8 后的改进
- Java 8中,你可以为接口添加静态方法和默认方法。从技术角度来说,这是完
全合法的,只是它看起来违反了接口作为一个抽象定义的理念。 - 静态方法:使用 static 关键字修饰。可以通过接口直接调用静态方法,并执行
其方法体。我们经常在相互一起使用的类中使用静态方法。你可以在标准库中
找到像Collection/Collections或者Path/Paths这样成对的接口和类。 - 默认方法:默认方法使用 default 关键字修饰。可以通过实现类对象来调用。
我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。
比如:java 8 API中对Collection、List、Comparator等接口提供了丰富的默认
方法。
7. 接口默认方法
- 若一个接口中定义了一个默认方法,而另外一个接口中也定义了一个同名同
参数的方法(不管此方法是否是默认方法),在实现类同时实现了这两个接
口时,会出现:接口冲突。
解决办法:实现类必须覆盖接口中同名同参数的方法,来解决冲突。 - 若一个接口中定义了一个默认方法,而父类中也定义了一个同名同参数的非
抽象方法,则不会出现冲突问题。因为此时遵守:类优先原则。接口中具有
相同名称和参数的默认方法会被忽略。
interface Filial {// 孝顺的
default void help() {
System.out.println("老妈,我来救你了");
} }
interface Spoony {// 痴情的
default void help() {
System.out.println("媳妇,别怕,我来了");
} }
class Man implements Filial, Spoony { @Override
public void help() {
System.out.println("我该怎么办呢?");
Filial.super.help();"来解决冲突"
Spoony.super.help();"解决冲突"
} }
三、内部类
1.概念
- 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类。
- 在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类。
- Inner class一般用在定义它的类或语句块之内,在外部引用它时必须给出完整的名称。
Inner class的名字不能与包含它的外部类类名相同; - 分类:
成员内部类(static成员内部类和非static成员内部类)
局部内部类(不谈修饰符)、匿名内部类
为什么需要内部类?
java内部类有什么好处?为什么需要内部类?
首先举一个简单的例子,如果你想实现一个接口,但是这个接口中的一个方法和你构想的这个类中的一个方法的名称,参数相同,你应该怎么办?这时候,你可以建一个内部类实现这个接口。由于内部类对外部类的所有内容都是可访问的,所以这样做可以完成所有你直接实现这个接口的功能。
不过你可能要质疑,更改一下方法的不就行了吗?
的确,以此作为设计内部类的理由,实在没有说服力。
真正的原因是这样的,java中的内部类和接口加在一起,可以的解决常被C++程序员抱怨java中存在的一个问题 没有多继承。实际上,C++的多继承设计起来很复杂,而java通过内部类加上接口,可以很好的实现多继承的效果
2. 声明
class 外部类{
修饰符 class 类名 {
//成员内部类;
}
方法(){
class 局部内部类{
}
}
{
class 局部内部类{
}
}
}
1. 成员内部类
1. 成员内部类作为类的成员的角色:
- 和外部类不同,Inner class还可以声明为private或protected;
- 可以调用外部类的结构
- Inner class 可以声明为static的,但此时就不能再使用外层类的非static的成员变量;
2. 成员内部类作为类的角色:
- 可以在内部定义属性、方法、构造器等结构
- 可以声明为abstract类 ,因此可以被其它的内部类继承
- 可以声明为final的
- 编译以后生成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)
【注意】
- 非static的成员内部类中的成员不能声明为static的,只有在外部类或static的成员内部类中才可声明static成员。
- 外部类访问成员内部类的成员,需要“内部类.成员”或“内部类对象.成员”的方式
Outer a = new Outer();//声明外部类
Outer.Inner b = a.new Inner();//声明内部类
Outer.Inner o=new Outer().new Inner(); - 成员内部类可以直接使用外部类的所有成员,包括私有的数据
- 当想要在外部类的静态成员部分使用内部类时,可以考虑内部类声明为静态的
public class Outer {//外部类
private int s = 111;
public class Inner {//内部类
private int s = 222;
public void mb(int s) {
System.out.println(s); // 局部变量s
System.out.println(this.s); // 内部类对象的属性s
System.out.println(Outer.this.s); // 外部类对象属性s
}
}
public static void main(String args[]) {
Outer a = new Outer();
Outer.Inner b = a.new Inner();
b.mb(333);
}
}
2. 局部内部类
1)局部内部类的特点
- 内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号,以及数字编号。
- 只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类。
- 局部内部类可以使用外部类的成员,包括私有的。 局部内部类可以使用外部方法的局部变量,但是必须是final的。由局部内部类和局部变量的声明周期不同所致。
- 局部内部类和局部变量地位类似,不能使用public,protected,缺省,private
- 局部内部类不能使用static修饰,因此也不能包含静态成员
2)如何使用局部内部类
- 只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类
- 但是它的对象可以通过外部方法的返回值返回使用,返回值类型只能是局部内部类的父类或父接口类型
class 外部类{
方法(){
class 局部内部类{
}
}
{
class 局部内部类{
}
}
}
3. 匿名内部类
匿名内部类不能定义任何静态成员、方法和类,只能创建匿名内部类的一个实例。一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
格式:
接口/父类类型 引用变量名 = new 接口/父类类型(){ 方法的重写 };
new 父类构造器(实参列表)|实现接口(){
//匿名内部类的类体部分
}
匿名内部类的特点
- 匿名内部类必须继承父类或实现接口
- 匿名内部类只能有一个对象
- 匿名内部类对象只能使用多态形式引用
interface A{
public abstract void fun1();
}
public class Outer{
public static void main(String[] args) {
new Outer().callInner(new A(){
//接口是不能new但此处比较特殊是子类对象实现接口,只不过没有为对象取名
public void fun1() {
System.out.println(“implement for fun1");
}
});// 两步写成一步了
}
public void callInner(A a) {
a.fun1();
}
}
四、代码块
代码块(或初始化块)的作用:
- 对Java类或对象进行初始化
代码块(或初始化块)的分类:
- 一个类中代码块若有修饰符,则只能被static修饰,称为静态代码块(static block),没有使用static修饰的,为非静态代码块。
- static代码块通常用于初始化static的属性
class Person {
public static int total;
static {
total = 100;//为total赋初值
}
…… //其它属性或方法声明
}
静态代码块:用static 修饰的代码块
- 可以有输出语句。
- 可以对类的属性、类的声明进行初始化操作。
- 不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法。
- 若有多个静态的代码块,那么按照从上到下的顺序依次执行。
- 静态代码块的执行要先于非静态代码块。
- 静态代码块随着类的加载而加载,且只执行一次。
非静态代码块:没有static修饰的代码块
- 可以有输出语句。
- 可以对类的属性、类的声明进行初始化操作。
- 除了调用非静态的结构外,还可以调用静态的变量或方法。
- 若有多个非静态的代码块,那么按照从上到下的顺序依次执行。
- 每次创建对象的时候,都会执行一次。且先于构造器执行。