面向对象
知识点导图
1 基本概念
1.1 面向对象与面向过程
面向过程:以函数作为组织代码的基本单元,强调的是功能行为。
面向对象:以类/对象作为组织代码的基本单元,强调具备了功能的对象。
1.2 类与对象
类:对一类事物的描述,是抽象的、概念上的定义。
对象:是实际存在的该类事物的每个个体,也被称为实例。
2 类的成员
2.1 属性
属性与局部变量的区别
相同点
- 定义变量的格式相同:
数据类型 变量名 = 变量值
。- 都需要先声明,后使用。
- 属性与局部变量都有对应的作用域。
不同点:
在类中声明的位置不同:
属 性:直接定义在类的一对{}
内
局部变量:可声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量关于权限修饰符的不同
属 性:可使用权限修饰符
局部变量:不可以使用权限修饰符默认初始化值的情况不同
属 性:根据类型都有默认的初始化值:
- 整型 (byte、short、int、long):
0
- 浮点型(float、double):
0.0
- 字符型(char):
0
或\u0000
- 布尔型:
false
- 引用类型(类、数组、接口):
null
局部变量:没有默认的初始化值,因此在调用局部变量之前,一定要显示赋值。
内存中加载的位置不同
属 性:非static的属性加载在堆空间中,static的属性加载在方法区中。
局部变量:加载在栈空间中
3.2 方法
方法的重载
定义
在同一个类中,允许存在一个以上的同名方法,但它们的参数个数或者参数类型要不同。
规则
两同一不同:
- 同一个类、相同的方法名。
- 参数列表不同:参数个数不同,参数类型不同,包括参数顺序不同
注意点
- 方法的重载跟方法的权限修饰符、返回值类型、形参变量名、方法体都没有关系。
- JAVA的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。
可变个数形参方法(JDK5.0新增)
格式 :
数据类型 ... 变量名
注意点
可变个数形参方法与本类中方法同名,形参不同的方法之间构成重载。(下面代码示例中方法
show(String s)
与方法show(String... str)
之间构成重载。)可变个数形参的方法与本类中方法名相同,形参类型也相同的数组之间不构成重载,即不能共存。(下面代码示例中方法
show(String[] str)
与方法show(String... str)
之间无法进行重载,编译失败。)可变个数形参在方法的形参中,必须声明在末尾,且最多只能声明一个。
代码示例:
public class VariableParaTest {
/**
* 与下面的可变个数形参方法构成重载
*
* @param s 字符串
*/
public void show(String s) {
System.out.println("Call Method: show(String s)");
}
/**
* 可变个数形参方法举例
*
* @param str 可变个数参数
*/
public void show(String... str) {
System.out.println("Call Method: show(String ... str)");
for (String s : str) {
System.out.println(s);
}
}
/**
* 参数为数组类型,与上面的可变个数形参方法不构成重载,编译失败
*
* @param str 可变个数参数
*/
public void show(String[] str) {
System.out.println("Call Method: show(String[] str)");
}
public static void main(String[] args) {
VariableParaTest test = new VariableParaTest();
test.show("11"); // 调用第一个方法,优先调用参数确定的方法,而非可变个数形参方法。
test.show(); // 调用可变个数形参方法,支持传入0个参数。
test.show("22", "33"); // 调用可变个数形参方法。
test.show(new String[]{"44", "55", "66"}); // 调用可变个数形参方法,也支持数组的方式。
}
}
值传递
参数传递机制规则
- 形参是基本数据类型,将实参基本数据类型变量的“数据值”传递给形参。
- 形参是引用数据类型,将实参引用类型变量的“地址值”传递给形参。
3.3 构造器
作用
- 创建对象
- 初始化对象的信息
说明
- 如果没有显示的定义类的构造器,则系统默认提供一个空参的构造器。
- 定义构造器的格式:
权限修饰符 类名(形参列表) {}
。- 一个类中可定义的多个构造器,且彼此构成重载。
- 一旦显示定义类的构造器后,系统就不在提供默认的空参构造器。
- 一个类中,至少会定义一个构造器(包括抽象类)。
3.4 代码块
作用 :用来初始化类、对象的信息
代码块仅能使用关键字static
进行修饰,因此代码块可分为两类:静态代码块、非静态代码块。
静态代码块
- 作用为初始类的信息。
- 内部可以有输出语句。
- 随着类的加载而执行,而且只执行一次。
- 如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行。
- 静态代码块的执行要优先于非静态代码块的执行。
- 静态代码块内只能调用静态的属性、静态的方法、不能调用非静态的结构。
非静态代码块
- 作用是可以在创建对象时,对对象的属性进行初始化。
- 内部可以有输出语句。
- 随着对象的创建而执行。
- 每创建一个对象,就执行一次非静态代码块。
- 如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行。
- 非静态代码块内可以调用静态的属性,静态的方法,非静态的属性、非静态的方法。
3.5 内部类
描述
JAVA运行将一个类A声明在类B中,则类A就是内部类,类B称为外部类。
内部类的分类
根据内部类所在的位置,可分为:成员内部类、局部内部类。
- 成员内部类根据是否使用static修饰可分为:
- 静态成员内部类。
- 非静态成员内部类。
- 局部内部类根据声明的位置可分为:
- 声明在方法内的内部类。
- 声明在代码块内的内部类。
- 声明在构造器内的内部类。
成员内部类相关说明
- 作为一个类:
- 类中可以定义属性、方法、构造器。
- 可以被
final
、abstract
关键字修饰。- 作为一个外部类的成员:
- 可以调用外部内的结构
- 可以被
static
关键字修饰- 可以被4中不同的权限修饰
内部类使用举例
关注以下问题:
- 如何实例化成员内部类(静态与非静态)的对象?
- 如何在成员内部类中区分调用外部类的结构?
public class InnerClassTest {
public static void main(String[] args) {
// 举例1:静态成员内部类
// 静态成员内部类对象的创建
PersonTest.StaticMemberInnerClass staticMemberInnerClass = new PersonTest.StaticMemberInnerClass();
// 静态成员内部类方法调用
staticMemberInnerClass.test();
// 举例2:非静态成员内部类
// 非静态成员内部类对象的创建
PersonTest personTest = new PersonTest();
PersonTest.MemberInnerClass memberInnerClass = personTest.new MemberInnerClass();
// 非静态成员内部类方法的调用
memberInnerClass.test();
}
}
class PersonTest {
/**
* 外部类PersonTest的属性
*/
public String name;
/**
* 举例:静态成员内部类
*/
static class StaticMemberInnerClass {
String name;
public void test() {
System.out.println("This StaticMemberInnerClass Method");
}
}
/**
* 举例:非静态成员内部类
*/
class MemberInnerClass {
String name;
public MemberInnerClass() {
System.out.println("This MemberInnerClass Constructor");
}
public void test() {
System.out.println("This MemberInnerClass Method");
// 在内部类的方法中直接调用外部类的方法
eat(); // 相当于:PersonTest.this.eat();
}
public void show(String name) {
System.out.println(name); // 方法的形参
System.out.println(this.name); // 内部类的属性
System.out.println(PersonTest.this.name); // 获取外部类的属性
}
}
/**
* 外部类PersonTest的方法
*/
public void eat() {
System.out.println("This Person eat Method");
}
{
// 举例:在代码块中声明的局部内部类
class LocalInnerClassInCodeBlock {
// 省略代码
}
}
/**
* 外部类PersonTest的构造器
*/
public PersonTest() {
//举例:在构造器中声明的局部内部类
class LocalInnerClassInConstructor {
// 省略代码
}
}
/**
* 举例:在方法内声明的局部内部类
*
* @return
*/
public Comparable getComparable() {
// 常见使用场景:创建一个实现了Comparable接口的类
class MyComparable implements Comparable {
@Override
public int compareTo(Object o) {
return 0;
}
}
return new MyComparable();
}
}
说明
- 成员内部类和局部内部类在编译后,都会生成字节码文件,其字节码文件格式为:
- 成员内部类:
【外部类名】$【内部类名】.class
- 局部内部类:
【外部类名】$【数字】【内部类名】.class
- 在局部内部类的方法中,如果调用局部内部类所声明的方法中的局部变量的话。要求此局部变量声明为final的。(JDK7以及之前的版本,要求此局部变量显示声明为final。JDK8以及之后的版本,则可以省略final的声明)。
class PersonTest { public void test() { int num = 10; // 在方法中声明局部内部类 class LocalInnerClassMethod { public void show() { // num = 20; 由于num为隐性final,试图修改则编译报错 System.out.println(num); } } } }
3 面向对象特征
3.1 封装性
描述
类通过暴露有限的访问接口,授权外部仅能通过类提供的方式来访问内部信息或数据。
优点
隐藏对象内部的复杂性,只对外公开简单的接口,便于外部调用。从而提高系统的可扩展性、可维护性。
场景
- 将类的属性私有化(private),同时提供公共的(public)方法来获取或者设置该属性的值(JavaBean)。
- 不对外暴露的私有的方法。
- 单例模式,将构造器进行私有化。
- 如果不希望类在包外调用,可以将类设置为缺省的。
权限修饰符
Java规定的4种权限(从小到大排列):private
、缺省
、protected
、public
修饰符 | 类内部 | 同一个包 | 不同包的子类 | 同一个工程 |
---|---|---|---|---|
private | Yes | |||
缺省 | Yes | Yes | ||
protected | Yes | Yes | Yes | |
public | Yes | Yes | Yes | Yes |
说明
- 权限修饰符可用于修饰类的内部结构:属性、方法、构造器、内部类。
- 修饰类的权限修饰符只能使用:缺省、public。
3.2 继承性
描述
继承用于表示类之间的 is-a 的关系。
优点
- 能够减少了代码的冗余,提高代码的复用性。
- 能够提高代码功能扩展性。
- 为多态性的使用提供前提。
说明
- JAVA中类的单继承性,一个类只能有一个父类。
- 一个父类可以被多个子类继承。
- 子类直接继承的父类,称为直接父类。间接继承的父类称为间接父类。
- 子类继承父类后,就获取了直接父类以及间接父类中声明的属性和方法(包括私有的)。
方法的重写
描述
子类继承父类后,可以对父类中同名参数的方法,进行覆盖操作。方法重写后,当创建子类对象后,通过子类对象调用子父类中同名同参数的方式时,实际执行的是子类重写父类的方法。
方法重写的规则
以下将根据方法的签名:权限修饰符 返回值类型 方法名(形参列表) throws 异常类型 {// 方法体}
,签名中各个部分进行说明重写的规则:
规则说明
权限修饰符
- 子类重写的方法的权限修饰符不小于被重写方法的权限修饰符。(特殊情况:子类不能重写父类中声明为private权限的方法)
返回值类型
- 父类被重写的方法的返回值类型为void,则子类重写的方法的返回值类型只能是void。
- 父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类。
- 父类被重写的方法的返回值类型是基本数据类型,则子类重写的方法的返回值类型必须是相同的基本数据类型。
方法名、形参列表
- 子类重写的方法名和形参列表与父类被重写的方法的方法名和形参列表相同。
异常类型
- 父类被重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型。
注意点
子类和父类的同名同参数的方法要么都声明为非static的(此时可考虑重写),要么都声明为static的(此时则不是重写)。
方法的重载与重写的区别
- 二者定义的区别:
- 重载:在同一个类中,允许存在一个以上的同名方法,但它们的参数个数或者参数类型要不同。
- 重写:子类继承父类后,可以对父类中同名参数的方法,进行覆盖操作。
- 二者规则的区别:
- 重载:两同一不同(详细见3.2章节)。
- 重写:参见上述规则。
- 二者编译和运行的不同:
- 重载:方法名相同但参数不同,编译器根据方法不同的参数表对同名方法的名称做修饰。因此对于这些同名方法就变成了不同的方法,它们的调用地址在编译期就绑定了。因此对于重载而言,在方法调用前,编译器就已经确定了所要调用的方法,称为“早绑定”或“静态绑定”。
- 重载:重载只有等到方法调用的那一刻,解释运行器才会确定所有调用的具体方法,称为“晚绑定”或者“动态绑定”。
子类的实例化
- 从结果上看
- 子类继承父类后,就获取父类中声明的属性或方法。
- 创建子类对象,在堆空间中,就会加载所有父类中声明的属性。
- 从过程上看
- 当通过子类构造器创建子类对象时,一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器至到调用Object类中的空参构造器为止,正因为加载过所有父类结构,所以在内存才看到父类结构,子类则可以考虑调用。
注意
虽然在创建子类的对象时,调用了父类的构造器,但是自始至终就创建了一个对象,即为new的子类对象。
子父类加载顺序
代码举例
public class SuperClass {
public static int num = 0;
{
num += 1;
System.out.println("调用父类中的非静态代码块,num = " + num );
}
static {
num += 1;
System.out.println("调用父类中的静态代码块,num = " + num);
}
public SuperClass() {
num += 1;
System.out.println("调用父类中的构造器,num = " + num);
}
}
public class SubClass extends SuperClass {
public static int num = 100;
{
num += 1;
System.out.println("调用子类中的非静态代码块,num = " + num);
}
static {
num += 1;
System.out.println("调用子类中的静态代码块,num = " + num);
}
public SubClass() {
num += 1;
System.out.println("调用子类中的构造器,num = " + num);
}
public static void main(String[] args) {
SubClass s1 = new SubClass();
System.out.println("========= 第二次创建子类对象 ========");
SubClass s2 = new SubClass();
}
}
输出结果:
调用父类中的静态代码块,num = 1
调用子类中的静态代码块,num = 101
调用父类中的非静态代码块,num = 2
调用父类中的构造器,num = 3
调用子类中的非静态代码块,num = 102
调用子类中的构造器,num = 103
========= 第二次创建子类对象 =========
调用父类中的非静态代码块,num = 4
调用父类中的构造器,num = 5
调用子类中的非静态代码块,num = 104
调用子类中的构造器,num = 105
结论
- 先执行父类的静态属性、静态代码块,执行顺序根据声明顺序,谁在前面谁先执行。
- 再执行子类的静态属性、静态代码块,执行顺序根据声明顺序,谁在前面谁先执行。
- 再执行父类的非静态属性、非静态代码块,执行顺序根据声明顺序,谁在前面谁先执行。
- 再执行父类的构造方法。
- 再执行子类的非静态属性、非静态代码块,执行顺序根据声明顺序,谁在前面谁先执行。
- 最后执行子类的构造方法。
- 当类已经被加载后,第二次加载时不会再执行静态变量的赋值以及静态代码块,但是会执行非静态属性的赋值和非静态代码块。
子父类属性的赋值顺序
在实例化对象时,对属性可以赋值的位置有
① 默认初始化。
② 显示初始化 / ⑤ 在代码块中赋值。
③ 构造器中初始化。
④ 有了对象后,通过对象.属性
或者对象,方法
的方式进行赋值。
赋值顺序为: ① -> ②/⑤ ->③ -> ④
java.lang.Object类
说明
- 一个类没有显式的声明父类的时候,则此类默认继承于
java.lang.Object
类- JAVA中所有的类,除了
java.lang.Object
类之外都直接或间接的继承于java.lang.Object
类,因此JAVA中所有的类都具有java.lang.Object
类声明的功能。
java.lang.Object类中equals()方法(扩展)
说明
- 只能适用于引用数据类型。
- Object类中定义的equals()和==的作用是相同的,即比较两个对象的地址值是否相同,即两个引用是否指同一个对象实体。
equals()与 == 的区别
==
是一个运算符;equals()
是一个方法,而非运算符。==
运算符可以使用在基本数据类型变量和引用数据类型变量中;equals()
方法仅适用于引用数据类型。==
运算符如果比较的是基本数据类型,则比较两个变量中的数据是否相等(不一定类型要相同)。==
运算符如果比较的是引用数据类型,则是比较两个对象对应的地址值是否相同;equals()
方法默认情况下与==
作用相同,但可以通过重写equals()
方法来比较两个对象的“实体内容”是否相同。
3.3 多态性
描述
多态意为一个事物的多种形态。对象的多态性可理解为父类的引用指向子类的对象,子类可以替换父类,在实际的代码运行过程中,调用了子类的方法实现。
优点
多态性能够提高代码的可扩展性和复用性。
说明
- 使用多态性的前提:
- 类的继承关系
- 方法的重写
- 使用了对象的多态性后,在编译阶段,只能调用父类中声明的方法。但是在运行阶段,实际执行的是子类重写父类的方法。(总结:编译看左边,运行看右边)。
- 多态性仅适用于方法,不适用于属性(属性是运行编译都看左边)。
4 关键字
4.1 package
使用说明
- JAVA中为了更好的对项目中的类进行管理,引入包的概念。
- 使用package来声明类或者接口所属的包,声明在源文件的首行。
- 包名也属于标识符,遵循标识符的命名规则,规范为全小写。
- 每
.
一次,则代表一层目录文件。- 同一个包下,不能命名同名的接口、类;不同的包下,可以命名同名的接口、类。
4.2 import
使用说明
- 在源文件中显式的使用import结构导入指定包下的类、接口。
- import声明在包的声明和类的声明之间。
- 如果需要导入多个结构,并列写出即可。
- 可以使用
xxx.*
的方式,表示可以导入xxx
包下的所有结构。但是如果使用的是xxx
子包下面的结构,则仍需要显式导入。- 如果使用的类或接口是
java.lang
包下定义的,则可以省略import结构。- 如果使用的类或接口是本包下定义的,则可以省略import结构。
- 如果在源文件中,使用了不同包下的同名的类,则必须至少一个类需要以全类名的方式显示。
import static
可导入指定类或者接口中的静态方法或者属性。
4.3 this
this
可以理解为:当前对象,或当前正在创建的对象
this可调用的结构
- 属性
- 方法
- 构造器
this
调用属性、方法
说明
- 在类的方法中,可以使用
this.属性
、this.方法
的方式,调用当前对象的属性或者方法,通常情况下,可以省略this.
。但如果方法的形参和类的属性同名时,必须显式使用this.属性
,表明此变量为属性而非形参。- 在类的构造器中,可以使用
this.属性
、this.方法
的方式,调用正在创建的对象的属性或者方法,通常情况下,可以省略this.
,如果构造器的形参和类的属性同名时,必须显式使用this.属性
,表明此变量为属性而非形参。
this
调用构造器
说明
- 在类的构造器中,可以使用
this(形参列表)
方式,调用本类中指定的其他构造器。- 构造器不能通过
this(形参列表)
的方式调用自己。- 如果一个类中有N个构造器,则最多有n-1构造器中使用了
this(形参列表)
。- 规定
this(形参列表)
必须声明在当前构造器的首行。- 构造器内部,最多只能声明一个
this(形参列表)
,用来调用其他构造器。
4.4 super
super
可理解为:父类的
super可调用的结构
- 属性
- 方法
- 构造器
super
调用属性、方法
说明
- 在子类的方法或构造器中,可以使用
super.属性
、super.方法
、的方式,显式调用父类中声明的属性或者方法,通常情况下,可以省略this.
。- 当子类和父类中定义了 同名属性时,如果想在子类中调用父类的属性,则必须显式使用
super.属性
的方式,表明调用的是父类中声明的属性- 当子类重写了父类中的方法后,如果想在子类的方法中调用父类中被重写的方法时,必须显示的使用
super.方法
的方式,表明调用的是父类中被重写的方法
super
调用构造器
说明
super(形参列表)
的使用,必须声明在子类构造器的首行。- 在类的构造器中,针对于
this(形参列表)
或super(形参列表)
只能二选一,不能同时出现。- 在构造器首行,没有显示的声明
this(形参列表)
或super(形参列表)
,则默认调用的是父类中空参的构造器super()
。- 在类的构造器中,至少有一个类的构造器中使用了
super(形参列表)
,调用父类的构造器。
4.5 static
static可修饰的结构
- 属性
- 方法
- 代码块
- 内部类
static
修饰属性
静态变量与实例变量的区别
- 实例变量:创建类的对个对象,每个对象都独立拥有类的的非静态属性,修改其中一个对象的非静态属性不会影响其他对象的属性。
- 静态变量:创建类的对个对象,多个对象共享同一个静态变量。当修改某一对象的静态变量时,其他对象的静态变量也同步修改。
静态变量说明
- 静态变量随着类的加载而加载,可以通过
类.静态变量
的方式进行调用。- 静态变量的加载早于对象的创建。
- 由于类只会加载一次,因此静态变量在内存中也会存在一份,存在方法区的静态域中。
静态变量使用场景
- 属性需要被多个对象所共享,不会随着对象的不同而不同。
- 类中的常量也常常需要声明为static。
static
修饰方法
静态方法说明
- 静态方法随着类的加载而加载,可以通过
类.静态方法
的方式进行调用。- 静态方法中,只能调用静态的方法或属性;非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性。
- 在静态方法中,不能使用
this
、super
关键字。
静态方法使用场景
- 需要操作静态属性的方法,通常需要设置为static。
- 工具类中的方法,通常也声明为static。
static
修饰代码块
相关说明参见3.4章节
static
修饰内部类
相关说明参见3.5章节
4.6 final
final可修饰的结构
- 类
- 方法
- 变量
final
修饰类
说明
final
修饰类后,表示此类不能被其他类所继承。
final
修饰方法
说明
final
修饰方法后,表示此方法不可以被重写。
final
修饰变量
说明
- 当
final
修饰变量后“变量”变为一个常量。- 当
final
修饰的变量为属性时:
- 显示初始化:一般用于所有对象的属性值明确且一致时的场景。
- 代码块中初始化:一般用于属性赋值存在特殊场景,例如抛异常。
- 构造器中初始化:一般用于每个对象的属性值可能存在不同的场景。
- 当
final
修饰的变量为局部变量时:
- 修饰方法体内的局部变量时,表示一个常量。
- 修饰方法的形参,表示方法体内可使用此形参,但不能重新赋值。
- 当
static final
修饰属性则表示全局常量。
4.7 abstract
abstract可修饰的结构
- 类
- 方法
abstract
修饰类:抽象类
说明
- 类不能被实例化。
- 抽象类中一定有构造器,便于子类实例化时调用。
abstract
修饰方法:抽象方法
说明
- 抽象方法只有方法的声明,没有方法体。
- 包含抽象方法的类,一定是一个抽象类,但抽象类中可以没有抽象方法。
- 子类需要重写父类中所有的抽象方法后,才能实例化。若子类没有重写父类中的所有抽象方法,则此子类也是一个抽象类,需要使用
abstract
进行修饰。
注意
- abstract不能用于修饰:属性、构造器等结构。
- abstract不能用来修饰私有方法、静态方法、final的方法、final的类。
4.8 interface
接口的使用说明
- 接口使用
interface
关键字来定义。- JAVA中,接口和类是两个并列的结构。
- 接口中可定义的成员:
- JDK7以及之前的版本:只能定义全局常量和抽象方法
全局常量:使用public static final
修饰,书写时可以省略不写。
抽象方法:使用public abstract
修饰,书写时可以省略不写。- JDK8,除了可以全局常量和抽象方法之外,新增定义静态方法、默认方法。
注意
- 接口中不能定义构造器,因此接口是不能实例化的。
- 实现类需要覆写接口中所有的抽象方法,实现类才能实例化,如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类。
- JAVA类可以实现多个接口,弥补了JAVA单继承性的局限性。
- 接口与接口之间可以继承、而且可以多继承。
interface Test1 { void method1(); } interface Test2 { void method2(); } // 接口的多继承 interface Test3 extends Test1, Test2 { }
JAVA8中接口新规范的使用说明
- 接口中定义的静态方法,只能通过接口来调用。
- 通过实现类的对象,可以调用接口中的默认方法。如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法。
- 如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,那么子类在没有重写的情况下,默认调用的父类中同名同参数的方法 (类优先原则)。
- 如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没有重写此方法的情况下,报错(接口冲突)。此时必须在实现类中重写该默认方法 。
- 在子类(实现类)的方法中调用父类、接口中被重写的方法 :
接口名.super.方法名()
。
代码举例:
SubClassTest.java
public class SubClassTest {
public static void main(String[] args) {
SubClass s = new SubClass();
// 1. 接口中定义的静态方法只能通过接口来调用
// s.method1(); 编译失败
// SubClass.method1(); 编译失败
CompareA.method1(); // 正常调用
// 通过实现类的对象,可以调用接口中的默认方法
s.method2();
// 2. 如果实现类重写了接口中的默认方法,调用时则调用重写的方法
s.method3();
// 3. 如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的方法,那么子类在没有重写的情况下,默认调用的父类中同名同参数的方法(类优先原则)
s.method4();
}
}
class SubClass extends SuperClass implements CompareA, CompareB {
/**
* 重写接口CompareA中的默认方法method3
*/
public void method3() {
System.out.println("Execute SubClass method3");
}
/**
* 4. 如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没有重写此方法的情况下,报错(接口冲突)。
* 此时必须在实现类中重写该默认方法,否则编译报错。
*/
@Override
public void method5() {
System.out.println("Execute SubClass method5");
}
/**
* 5. 在子类(实现类)的方法中调用父类、接口中被重写的方法 :接口名.super.方法名()
*/
public void myMethod() {
method5(); // 调用自己定义的重写的方法
super.method5(); // 调用父类中声明的方法
CompareA.super.method5(); // 调用CompareA定义的默认方法
CompareB.super.method5(); // 调用CompareB定义的默认方法
}
}
SuperClass.java
public class SuperClass {
/**
* 定义方法method4
*/
public void method4() {
System.out.println("Execute superClass method4");
}
/**
* 定义方法method5
*/
public void method5() {
System.out.println("Execute superClass method5");
}
}
CompareA.java
public interface CompareA {
/**
* 定义静态方法
*/
public static void method1() {
System.out.println("Execute CompareA static method1");
}
/**
* 定义默认方法method2
*/
public default void method2() {
System.out.println("Execute CompareA default method2");
}
/**
* 定义默认方法method3
*/
default void method3() {
System.out.println("Execute CompareA default method3");
}
/**
* 定义默认方法method4
*/
default void method4() {
System.out.println("Execute CompareA default method4");
}
/**
* 定义默认方法method5
*/
default void method5() {
System.out.println("Execute CompareA default method5");
}
}
CompareB.java
public interface CompareB {
default void method5() {
System.out.println("Execute CompareB default method5");
}
}
抽象类和接口的区别
- 相同点:
- 抽象类和接口都可以包含抽象方法,因此都不能实例化。
- 不同点:
- 使用场景不同:
- 抽象类作为很多子类的父类,体现的是一种自下而上的模板式设计;而接口体现的是一种自上而下的行为规范,规定了实现者必须向外提供哪些功能。
- 抽象类是对一类事物的抽象,接口则是对行为的抽象。一个类继承一个抽象类代表“是不是”的关系,而一个类实现一个接口则表示“有没有”的关系。
- 内部结构不同:
- 接口仅能定义抽象方法、静态方法以及默认方法(JDK8以及之前版本)。而抽象类中可以包含普通方法,如私有方法。
- 抽象类中的成员变量没有访问权限的限制,但接口中的变量只能被public static final修饰(全局常量);
- 一个接口可以继承多个接口,但一个类只能有一个父类,类可以实现多个接口;
5 扩展
5.1 向上转型和向下转型
向上转型
向上转型即可理解为多态的使用。详细参见3.3章节相关描述。
向下转型
为什么要向下转型?
在使用了对象的多态性后,内存中实际上是加载了子类特有的属性和方法,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用,因此可以使用向下转型。
如何使用向下转型?
可使用强制类型转换符:()
使用向下转型注意点
- 使用强转时,可能出现
ClassCastException
的异常。- 为了避免在向下转型时出现
ClassCastException
的异常,在向下转型之前,先进行instanceof
的判断,如果返回true则可以进行向下转型,否则不能进行。instanceof
相关使用说明如下:
a instanceof A
判断对象a是否是类A的实例。如果是则返回true,如果不是则返回false。- 如果
a instanceof A
返回true,则a instanceof B
也返回true,类B是类A的父类。x instanceof A
要求对象x所属类与类A必须是子类或者父类的关系,否则编译报错。
5.2 匿名对象与匿名类
匿名对象
理解:创建的对象,没有显式的赋给一个变量名,即为匿名对象。
特征:匿名对象只能调用一次。
使用举例:
/**
* 匿名对象测试类
*/
public class AnonymousObjectTest {
public static void main(String[] args) {
InternetBar bar = new InternetBar();
// 此处使用了Computer类的匿名对象
bar.show(new Computer());
}
}
class InternetBar {
public void show(Computer computer) {
computer.playGame();
}
}
class Computer {
public void playGame() {
System.out.println("I'm playing game.");
}
}
匿名类
理解:创建的实现类,没有显式的赋给一个变量名,即为匿名类。
特征:匿名类只能调用一次。
public class AnonymousClassTest {
public static void main(String[] args) {
// 创建了一个匿名子类的对象
Person anonymousClassObject = new Person() {
@Override
public void eat() {
System.out.println("Anonymous Class Object Eat ...");
}
@Override
public void work() {
System.out.println("Anonymous Class Object Work ...");
}
};
methodTest(anonymousClassObject);
// 创建一个匿名子类的匿名对象
methodTest(new Person() {
@Override
public void eat() {
System.out.println("Anonymous Class Anonymous Object Eat ...");
}
@Override
public void work() {
System.out.println("Anonymous Class Anonymous Object Work ...");
}
});
}
public static void methodTest(Person person) {
person.eat();
person.work();
}
}
abstract class Person {
public abstract void eat();
public abstract void work();
}
5.3 包装类
基本数据类型对应的包装类
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | integer |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Character |
基本数据类、包装类、String之间的转换
说明
Integer内部定义了IntegerCache
结构,IntegerCache中定义了Integer[]
,保存了-128~127范围的整数,如果使用自动装箱的方式给Integer赋值在该范围内,则从缓存中获取而不是new。