学习目的
- 掌握static关键字和final关键字的含义及用法
- 掌握抽象类与接口的概念及用法
- 了解抽象类与接口的区别
- 了解使用UML图表达Java类之间的关系
- 掌握is-a、is-like-a、has-a的含义
一.static关键字
- 定义
static是一个关键字,表示"静态的",可以用来修饰变量、方法、代码块等,使用static修饰的变量叫做静态变量,修饰的方法叫做静态方法,修饰的代码块叫做静态代码块。 - 特点
- 凡是static 修饰的都是类级别相关的,不需要创建对象,通过类名.即可访问;
- static修饰过的静态变量、静态方法、静态代码块等都在类加载时初始化,存储在方法区当中;
- 常见问题
- 在static修饰的静态上下文中无法访问非静态(无static修饰)的方法或变量;
- 原因:static修饰的相关,在类加载时就已经加载进去JVM的方法区内存,而非static修饰的变量、方法是存放在堆内存或者栈内存中,必须要对象先创建出来了才能调用。
1.1 静态代码块
- 特点
- 一次性:静态代码块在类加载时执行,并且只执行一次;
- 静态代码块当中的代码 总是在 main 方法执行之前执行;
- 一个类当中可以编写多个静态代码块,并且静态代码块遵循自上而下的顺序依次执行。
- 作用
- 在类加载时、在main方法执行前,想只执行一次某段代码;
如:在类加载时解析某个文件,并且要求该文件只解析一次,此时可以把解析该文件的代码写到静态代码块中。
- 语法格式
类{
//静态代码块
static{
java 语句;
}
}
二.Final关键字
- 定义
final关键字表示最终的,不可变的。用于声明属性、方法和类,分别表示属性不可变,方法不可重写,类不可继承。 - 特点
- final 修饰的变量必须显示初始化,且只能初始化一次,后续不能修改;
- final修饰的类不可以被继承,final修饰的方法可以被继承但不能被重写;
- final修饰的变量是常量,与static一起使用表示该变量不可改变;
- final修饰的引用不可以重新赋值,只能指向初始对象的地址,不能重新指向新对象的地址;
- final修饰的引用不能重新指向地址,但可以对其类属性进行重新赋值;
三.Abstract类
3.1 抽象类
- 概念
抽象类是采用 abstract关键字定义的类,属于引用数据类型。抽象类是源自生活中高度抽象,是现实中不存在但是可以"具象化"的意识物体。抽象类无法实例化和创建对象,但可以被类继承,且只能被继承。 - 实质
java类 是由现实中的对象抽象出来的,是现实物体具有共同特征的抽象集合;抽象类则是在 类上的再抽象,属于意识形态的、虚构的。 - 定义格式
- 类名:抽象类使用abstract修饰class,但不能与final、private一起使用(抽象类天生用来继承和方法重写,而final修饰的类天生不能被继承,private修饰的方法不能被重写);
- 属性:抽象类中建议定义一些非抽象的、公共的方法和属性,供子类直接继承使用,不需要子类重复定义,从而满足开闭原则的扩展;
- 方法:抽象类中的方法可以是abstract修饰的,也可以不是abstract修饰的(如构造器),但使用了abstract修饰方法的类一定是抽象类。
- 抽象类无法实例化原因
- 实质原因:抽象类是 由本身已经抽取出共同特征的类,再对多个类的共同部分进行抽象的抽象;
- 过程原因:由类到对象的过程称为实例化,实例化意为产生现实的例子,抽象类是抽象的抽象,不存在现实例子;
- 不可跨级实现:对象 → 类 → 抽象类 → 上层抽象类,现实 → 抽象 → 抽象。
-
现实与抽象类图解
3.2 抽象方法
- 概念
抽象方法即采用了abstract关键字修饰的方法。抽象方法是一类对现实行为抽取出共同部分的方法,抽象方法并没有具体实现(无方法体),只能由子类继承重写才有具体的实现。如游泳是蛙泳、蝶泳、自由泳、混合泳、接力泳的一个共性部分,怎么游没有具体实现。 - 特点
- 必须有abstract修饰,无方法体,无具体实现,只能被重写实现;
- 接口中的方法都是抽象方法,而抽象类中的方法不一定全都是抽象方法;
- 如果有一个方法为抽象的,那么此类必须为抽象的;如果一个类是抽象的,并不要求该类一定具有抽象的方法;
- 若一个子类继承了抽象类(包括接口),必须重写所有的抽象方法;
- 抽象方法不能直接调用。
- 特殊点
- 抽象类有构造方法 且 必须要有无参构造方法(不管是手动无参还是默认无参);
- 在子类继承抽象类的时候,会默认有super()调用抽象类的构造方法(有参/无参),因此 若抽象类没有构造方法时,出现编译不通过错误。
3.3 注意点(重点)
- 子类与抽象父类
继承了抽象类的子类不是必须重写父类的所有方法,分以下两种情况:
- 父类方法为抽象方法时,子类必须重写(实现)所有父类的抽象方法;
- 父类方法为普通方法时,子类可以重写父类方法,也可以不重写;
- 父类方法为抽象方法,但当子类也为抽象类时,也可以不用重写父类的抽象方法。
- 子类与接口
接口为完全抽象(接口方法全部为抽象方法),子类实现接口时,必须重写覆盖接口的所有方法。 - 抽象类与抽象方法
- 抽象类中可以没有抽象方法,但抽象方法必须出现在抽象类中<接口也是抽象类>;
- 子类继承并重写抽象父类的抽象方法,但由于子类不是抽象类,重写后的方法也就不是abstract抽象的,而是"具实现"的;
- 抽象方法不能是abstract和final共同修饰,但被子类继承并重写了的abstract方法,可以使用final重新修饰。
四.Interface类
4.1 接口
- 概念
接口是使用interface 关键字修饰的特殊"抽象类",取代了abstract关键字的修饰,但是接口中的方法都是抽象的。接口是现实中具有共性部分的行为集合,接口里只存在抽象方法和常量。 - 组成
接口属于完全抽象的,因此在接口中只能定义抽象方法(无方法体实现);并且接口是现实的共性部分,因此接口的属性都是常量,不可改变的。
接口 = 常量 + 抽象方法。 - 实质
接口属于"引用数据类型",但完全抽象的引用类。 - 特点
- public修饰:抽象的属性和方法都是可以提供给所有类使用,因此接口的元素都是public公开的;
- 属性变量:接口中的变量默认都是 public static final修饰的,都是常量不可更改,必须显示的初始化;
- 方法体:接口中的方法默认都是 public abstract修饰的,不能更改;
- 多态:接口的方法可以在不同的地方被不同的类实现,而这些不同的具体实现是不同子类的特性行为。
- 作用
- 明确功能:采用通俗的接口名,可以明确的声明接口所能提供的服务;
- 解耦:将方法抽取到接口中实现,降低类与类之间的耦合度,降低继承的使用;
- 接口隔离原则:将不同性质的方法行为抽取到不同的接口中,避免堆积在一个类中;增强类本身的实质,同时提高扩展性;
- 面向抽象编程,面向接口编程,满足开闭原则。
4.2 接口语法
- 接口语法结构
[修饰符列表] interface 接口名{
public static final 数据类型 常量名;//public static final 可以省略
public abstract 数据类型 方法名(参数类型 args);//public abstract可以省略
}
- 接口实现
- 接口只能通过implements 关键字被类实现;
- 一个类实现了接口,接口中所有的方法也必须重写实现;
- 接口与接口之间支持多继承,接口与实现类之间支持多实现;
- 接口不是具体类,不能new 接口。但可以接口名 变量名 = new 实现类();
- 接口就是父类,将接口当作"抽象的父类"可以实现多态。
//实现与继承同时存在:extends在前,implements在后
interface Flyable{
void fly();
}
class Animal{
}
class Cat extends Animal implements Flyable{
public void fly(){
System.out.println("飞猫起飞,翱翔太空的一只猫!!");
}
}
class Pig extends Animal implements Flyable{
public void fly(){
System.out.println("我是一只会飞的猪!!!");
}
}
// 鱼没extends动物类,但默认继承Object
class Fish implements Flyable{
public void fly(){
System.out.println("我是六眼飞鱼(流言蜚语)!!!");
}
}
public static void main(String[] args){
// 使用多态--创建对象(表面看Animal类没起作用!)
Flyable f = new Cat();
f.fly();
Flyable f2 = new Pig();
f2.fly();// 调用同一个fly()方法,最后的执行效果不同。
//没有继承animal,但默认继承object,可以实现fly()方法
Flyable f3 = new Fish();
f3.fly();//调用Fish的fly()方法
}
- 注意点
- 在普通的类继承中,不允许没有继承关系的三个或以上的类进行强转(父亲,哥哥,弟弟,堂弟;动物,猫,狗,鱼)
- 在接口中,允许没有继承关系的类进行强转,起码在编译期时不会出现错误,运行期可能报错也可能不报错。
interface K{ }
interface M{ }
class E implements M{ }
M m = new E();
//接口和接口之间没有继承关系也可以进行强制类型转换
//但一定要注意,(编译通过)运行时出现ClassCastException异常
//K k = (K)m;// ClassCastException异常?
//避免异常,最好使用instanceof 判断再强转
if(m instanceof K){
K k = (K)m;
}
interface A{ void m1(); }
interface B{ void m2(); }
interface C{ void m3(); }
// 实现多个接口,变向实现多继承。
class D implements A,B,C{
// 实现A接口并重写m1()方法
public void m1(){ }
// 实现B接口中并重写m2()方法
public void m2(){
System.out.println("m2 ....");
}
// 实现接口C中并重写m3()方法
public void m3(){ }
}
A a = new D();
B b2 = new D();
//编译和运行可以通过,但要调用其他接口的方法需要强制转型(接口转型)
B b2 = (B)a;
b2.m2();
4.3 接口作用
- 继承
间接完成"多继承",类之间不能实现多继承,但接口之间允许多继承,而类又可以实现多个接口,利用"等同关系"。 - 耦合度
解决类与类之间的耦合度,将本来直接联系的类,抽取出一个共同联系的接口,通过接口进行连接。如(顾客-菜单-厨师<菜式>、电脑-槽口-硬件设备) - 简洁开发步骤
面向接口的开发步骤:项目经理负责编写接口,项目接口的组1编写调用者类,项目组2编写接口的具体实现类,互不干扰 - 功能粒度
- 细分:把接口粒度划分细了,使功能定义的含义更明确
- 粗分:采用一个大的接口定义所有功能,替代多个小的接口
4.4 接口调用
//测试接口的创建、调用、与实现
public class Test{
public static void main(String[] args){
// 创建厨师对象,利用面向接口的多态
FoodMenu cooker1 = new ChinaCooker();//中餐厨师
FoodMenu cooker2 = new AmericCooker();//西餐厨师
// 创建顾客对象
Customer customer = new Customer();
// 顾客点菜--点中餐
customer.order(cooker1);
// 顾客点菜--点西餐
customer.order(cooker2);
}
}
4.5 接口与JDK新特性
- JDK8新特性
- defalut默认实现:在 JDK8之前,接口是一个完全抽象的类,不能有任何的方法实现。从JDK8开始,接口提高defalut方法实现;
这是因为不支持默认方法的接口的维护成本太高了 --在 Java 8之前,如果一个接口想要添加新的方法,需要修改所有实现了该接口的类。
4.6 接口与实际开发
- 接口抽取
架构师/开发经理:根据需求分析,定义好接口(如菜单接口) - 接口使用
项目组A:根据规范,面向接口,编写接口的调用类(如顾客类)
项目组B:根据规范,面向接口,编写接口的实现类(如初始类)
项目组C:根据规范,面向接口,编写测试类 - 接口实现
开发互助:接口定义好后,项目组A与B可以同时开发编写,A与B都只关心接口,互不关心各自的具体实现,因此同时开发可以提高工作效率。 - 项目实现
最终项目也是通过接口完成拼接(积木:模块 → 接口 → 模块→ 接口 → 模块→ 接口)
五.类与类之间的关系
- 概念
UML又叫统一建模语言,是一种为面向对象的产品进行说明、可视化和编制文档的一种标准语言,是面向对象设计的建模工具,独立于任何具体程序设计语言。 - 表现形式
UML独立于程序设计语言存在,主要是通过** 图表/图形 **的形式来表现出一个面向对象的软件产品中,类与类之间、模块与模块之间、功能与功能之间的关系。通过图表可以让一个产品更加容易读懂。
5.1 UML类图
-
泛化关系(继承关系)
泛化关系又叫继承关系,指的是子类与父类之间,接口与接口之间的关联关系。
-
实现关系
实现关系描述的是类对接口的实现。
- 关联关系
类与类之间的连接,一个类可以作为另一个类的属性和方法,类作为成员变量的形式体现
- 聚合关系
聚合关系是关联关系的一种,是程度较强的关联关系。与关联关系不同,关联关系的类处在同一个层次上,而聚合关系的类处在不平等的层次上。
- 强调整体和部分的关系,整体和部分使用实例变量体现<生命周期-不同步>。如:汽车<类> 和 轮胎<实例变量,属性>,汽车没了,轮胎拆出来还能用。
- 合成关系(组合关系)
组合关系,是关联关系的一种,程度比聚合关系强的关联关系。整体决定部分的生命周期,部分对象每一时刻只与一个对象发生合成关系。
- 强调整体-决定-部分,使用实例变量体现<生命周期同步>。如:人和四肢,人和五官,人没了,四肢五官也就没了。
-
依赖关系
依赖关系,关联关系的一种,程度较弱的关联关系,体现为返回值,参数,局部变量和静态方法调用。
5.2 is-a<继承>
- 概念
is-a表现的是一种继承关系,描述为"A是一个B,A是B的一个孩子"。 - 代码示例
public class A {
public void method1() {}
}
public class B extends A {
//重写继承于A的方法
public void method1() {
}
}
5.2 like-a<接口>
- 概念
like-a表现的是一种实现关系,描述为"A像一个B,A和B拥有一样的普遍性特征" - 代码示例
public interface I {
public void method1() ;
}
public class A implements I {
public void method1() {
//具体实现
}
}
5.3 has-a<成员变量>
- 概念
has-a表现的是一种拥有关系,描述为"A拥有B,B是A的一部分组成"。 - 代码示例
public class A {
private B b;
}
public class B {
}
常见面试题
- java语言中凡是没有方法体的方法都是抽象方法,对吗?
答:
不对,错误的。
Object类中就有很多方法都没有方法体,都是用native关键字修饰,且以“;”结尾的,但都不是抽象方法。例如:public native int hashCode();方法底层调用了C++写的动态链接库程序。修饰符列表中没有abstract而是有一个native,表示调用JVM本地程序。 - 接口为什么离不开多态?
答:
面向接口编程和面向抽象编程的实现原理及实质就是多态。
多态的实质就是父类型的引用指向子类型的对象,而当父类型又是抽象类 或 接口时,既变成了面向抽象编程。面向抽象编程可以让代码拥有更高的可扩展性,满足开闭原则(对扩展开放,对修改关闭)。
现实世界万物以对象的形式存在,而java则是以秉承"对象-做-什么事情"进行编程。而不是面向做事情的具体实现过程。因此,java具有更高的耦合性,耦合性的关键则在于抽象。
abstract Animal a1 = new Cat();//父类为抽象类的引用 指向子类实现类的对象
interface Action a2 = new Move();//接口类型的引用 指向子类实现类的对象
- 谈一谈接口和抽象类的区别?
答:
- 抽象类是半抽象的<构造方法 + 抽象方法>,接口是完全抽象的<常量 + 抽象方法>。
- 抽象类中有构造方法(类都有构造方法)。接口中没有构造方法(只有无方法体的抽象方法)。
- 接口和接口之间支持多继承。类和类之间只能单继承(抽象类也是类)。类和接口之间支持多实现。
- 一个类可以同时实现多个接口。一个抽象类只能继承一个类(单继承)。
- 接口中只允许出现常量和抽象方法(public static final和public abstract);抽象类中允许出现抽象和非抽象的变量或方法。
- 接口描述了方法的特征,不给出具体实现<抽象方法都没有具体实现>,一方面解决 java 的单继承问题,实现了强大的可接插性。
- 抽象类提供了部分实现<构造方法 + 非抽象的方法>,抽象类是不能实例化的,抽象类的存在--主要是可以把公共的代码移植到抽象类中。
- 优先级:优先选择接口(继承抽象类后,此类将无法再继承,会丧失此类的灵活性)