面向对象(上)

Java面向对象学习的三条主线

1、Java类及其成员:属性、方法、构造器;代码块、内部类。
2、面向对象的三大特征:封装、继承、多态、(抽象)
3、一些关键字:thissuperstaticfinalabstractinterfacepackageimport

面向过程与面向对象

  • 面向过程:强调的是功能行为,以函数为最小单位,考虑的是怎么做。
  • 面向对象:强调具备了功能的对象,以类/对象为最小单位,考虑的是谁来做。

类和对象是面向对象的核心概念:

  • 类:是对一类事物的描述,是抽象的、概念上的定义。
  • 对象:是实际存在的该类事物的每个个体,因而也称为实例。

设计类,其实就是设计类的成员

  • 属性 = 成员变量 = 域/字段 = field
  • 方法 = 成员方法 = 函数 = method

类和对象的使用(面向对象思想落地的实现)
1、创建类,设计类的成员;
2、创建类的对象;
3、通过”对象.属性”或”对象.方法”调用对象的结构。

对象的内存解析

如果创建了一个类的多个对象,则每一个对象都独立的拥有一套类的属性(非static的)
内存解析的说明:
引用类型的变量,只可能存储两类值:null和地址值(含变量类型)
第5章 面向对象 - 图1
image.png

类的结构之一:属性

属性(成员变量)vs局部变量
相同点:

  • 定义变量的格式相同:数据类型 变量名 = 变量值。
  • 先声明后使用。
  • 变量都有其对应的作用域。

不同点:

  • 类中声明的位置不同:
    • 属性:直接定义在类的一对{}内。
    • 局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内。
  • 关于权限修饰符不同:
    • 属性:可以使用private、缺省、protectedpublic
    • 局部变量:不可以使用权限修饰符。
  • 默认初始化值情况不同:
    • 属性:类的属性根据其类型都有默认的初始化值。
    • 局部变量:没有默认初始化值,在调用局部变量之前必须显示赋值,特别地:形参在调用时赋值即可。
  • 在内存中加载的位置不同:
    • 属性:加载到堆空间(非static
    • 局部变量:加载到栈空间

类的结构之二:方法

方法:描述类应该具有的功能。
方法的声明:权限修饰符 其它修饰符 返回值类型 方法名(形参列表){ 方法体 }
注意:staticfinalabstract等其它修饰符来修饰的方法后面再讲。

说明:

  • 可以使用 4 种权限修饰符。
  • 关于返回值:
    • 如果方法有返回值,必须声明返回值类型,并使用return关键字返回指定类型的变量或常量;
    • 如果没有返回值,可以使用void声明,不需要使用return关键字,非要用就使用return;表示结束方法的意思。
  • 方法名:属于标识符,需要符合标识符命名规则和规范。
  • 形参列表:可以声明0个、1个、多个形参。
  • 方法体:方法功能的体现。
  • 方法的使用中,可以调用当前类的属性和方法,特殊地:方法A中调用了方法A,这属于递归方法,但是方法中不可以定义方法。

匿名对象的使用

  • 理解:创建对象时,没有显示赋给一个变量名即为匿名对象。
  • 特征:匿名对象只能调用一次。

方法的重载

定义:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。

两同一不同:

  • 同一个类里面。
  • 相同的方法名。
  • 参数列表不同(个数、类型、顺序不同)

⚠️注意:与方法的权限修饰符、返回值类型、形参变量名、方法体都没有关系。

可变个数形参的方法

  • 可变个数形参属于 jdk 5.0 新增的内容,与数组形式等价,且二者不能共存。
  • 具体使用注意点:
    • 格式:数据类型 ... 变量名
    • 当调用可变个数形参的方法时,传入的参数可以是0个、1个、多个。
    • 可变个数形参的方法与本类中同名不同形参列表的方法构成重载。
    • 在方法的形参列表中,可变个数形参必须声明在末尾。
    • 在方法的形参列表中,最多只能声明一个可变个数形参。

方法参数的值传递机制

关于变量的赋值:

  • 如果变量是基本数据类型,赋值的是变量所保存的数据值;
  • 如果变量是引用数据类型,赋值的是变量所保存的数据的地址值。

形参与实参:

  • 形参:方法定义时声明的参数。
  • 实参:方法调用时,实际传递给形参的数据。

值传递机制:

  • 如果参数是基本数据类型,实参赋给形参的是实参真实存储的数据值;
  • 如果参数是引用数据类型,实参赋给形参的是实参真实存储数据的地址值。

递归方法的使用

  • 递归方法:一个方法体内调用它自身。
  • 方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。
  • 递归一定要向已知方向递归,否则变为无穷递归,类似于死循环。

面向对象(中)

面向对象的特征一:封装性

一、问题的引入
当我们创建一个类的对象以后,我们可以通过”对象.属性”的方式,对对象的属性进行赋值。这里,赋值操作要受到属性的数据类型和存储范围的制约。除此之外,没有其他制约条件。但是,在实际问题中,我们往往需要给属性赋值加入额外的限制条件。这个条件就不能在属性声明时体现,我们只能通过方法进行限制条件的添加。(比如:setXXX()
同时,我们需要避免用户再使用”对象.属性”的方式对属性进行赋值。则需要将属性声明为私有的(private)。
此时,针对于属性就体现了封装性。

二、封装性的体现
我们将类的属性xxx私有化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)此属性的值。
拓展:封装性的体现:① 如上 ② 不对外暴露的私有的方法 ③ 单例模式 …

权限修饰符

封装性的体现,需要权限修饰符来配合

  • Java 规定的 4 种权限(从小到大排列):private、缺省、protectedpublic
  • 4 种权限都可以用来修饰类的内部结构:属性、方法、构造器、内部类
  • 修饰类的话,只能使用:缺省、public

image (1).png

总结封装性:
Java 提供了 4 种权限修饰符来修饰类及类的内部结构,体现类及类的内部结构在被调用时的可见性的大小。

类的结构之三:构造器

构造器的作用

  • 创建对象。
  • 初始化对象的信息。

说明:
1、如果没有显式的定义类的构造器的话,则系统默认提供一个空参的构造器。
2、定义构造器的格式:权限修饰符 类名(形参列表){}
3、一个类中定义的多个构造器,彼此构成重载。
4、一旦我们显式的定义了类的构造器之后,系统就不再提供默认的空参构造器。
5、一个类中,至少会有一个构造器。

总结属性赋值的顺序

1、默认初始化。
2、显式初始化、代码块初始化(两者优先级相同,按照先后顺序执行)。
3、构造器中初始化。
4、通过”对象.方法” 或 “对象.属性”的方式赋值。

JavaBean 与 UML 类图

image (2).pngimage (3).png

this关键字的使用

this可以用来修饰、调用:属性、方法、构造器。

this 修饰属性和方法,this 可以理解为:当前对象或当前正在创建的对象。

  • 在类的方法中,我们可以使用”this.属性”或”this.方法”的方式,调用当前对象属性或方法。但是,通常情况下,我们都选择省略”this.”。特殊情况下,如果方法的形参和类的属性同名时,我们必须显式的使用”this.变量”的方式,表明此变量是属性,而非形参。
  • 在类的构造器中,我们可以使用”this.属性”或”this.方法”的方式,调用当前正在创建的对象属性或方法。但是,通常情况下,我们都选择省略”this.”。特殊情况下,如果构造器的形参和类的属性同名时,我们必须显式的使用”this.变量”的方式,表明此变量是属性,而非形参。

this 调用构造器
1、我们在类的构造器中,可以显式的使用”this(形参列表)”方式,调用本类中指定的其他构造器。
2、构造器中不能通过”this(形参列表)”方式调用自己。
3、如果一个类中有n个构造器,则最多有 n - 1个构造器中使用了”this(形参列表)”。
4、规定:”this(形参列表)”必须声明在当前构造器的首行。
5、构造器内部,最多只能声明一个”this(形参列表)”,用来调用其他的构造器。

package 关键字

1、为了更好的实现项目中类的管理,提供包的概念。
2、使用package声明类或接口所属的包,声明在源文件的首行。
3、包,属于标识符,遵循标识符的命名规则、规范(xxxyyyzzz)、“见名知意”
4、每”.”一次,就代表一层文件目录。
补充:同一个包下,不能命名同名的接口、类。不同的包下,可以命名同名的接口、类。
image (4).png

import 关键字

  • 在源文件中显式的使用import结构导入指定包下的类、接口。
  • 声明在包的声明和类的声明之间。
  • 如果需要导入多个结构,则并列写出即可。
  • 如果使用的类或接口是java.lang包下定义的,则可以省略import结构。
  • 如果使用的类或接口是本包下定义的,则可以省略import结构。
  • 如果在源文件中,使用了不同包下的同名的类,则必须至少有一个类需要以全类名的方式显示。
  • 使用”xxx.*“方式表明可以调用xxx包下的所有结构。但是如果使用的是xxx子包下的结构,则仍需要显式导入。
  • import static:导入指定类或接口中的静态结构(属性或方法)。

面向对象的特征二:继承性

继承性的好处:

  • 减少了代码的冗余,提高了代码的复用性。
  • 便于功能的扩展。
  • 为之后多态性的使用,提供了前提。

继承性的格式class A extends B{}
子类:也叫派生类、subclass
父类:也叫超类、基类、superclass
说明:extends 其实是延展、扩展的意思。

Java中关于继承性的规定:
1、Java中类的单继承性:一个类只能有一个父类。但是一个类可以被多个子类继承。
2、子父类是相对的概念。子类直接继承的父类,称为:直接父类;间接继承的父类称为:间接父类。
3、子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法。父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。只是因为封装性的影响,使得子类不能直接调用父类的结构而已。
4、子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展。子类和父类的关系,不同于子集和集合的关系。
5、如果我们没有显式的声明一个类的父类的话,则此类继承于java.lang.Object类。所有的java类(除java.lang.Object类之外)都直接或间接的继承于java.lang.Object类,意味着,所有的java类都具有java.lang.Object类声明的功能。

方法的重写

重写(override / overwrite)定义:子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作。
方法的声明: 权限修饰符 返回值类型 方法名(形参列表) throws 异常的类型{ 方法体 }
约定俗称:子类中的叫重写的方法,父类中的叫被重写的方法。

重写的应用:
重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法。

重写的规定:
1、子类重写的方法与父类被重写的方法的方法名和形参列表相同。
2、子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符,特殊情况:子类不能重写父类中声明为private权限的方法。
3、关于返回值类型:

  • 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void;
  • 父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类;
  • 父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double)。

4、子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型。
5、子类和父类中的同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static的(不是重写)。

面试题:区分方法的重载与重写

super关键字

super可以理解为:父类的。
super可以用来调用:属性、方法、构造器。

super 调用属性和方法

  • 我们可以在子类的方法或构造器中。通过使用”super.属性”或”super.方法”的方式,显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略”super.”
  • 当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的使用”super.属性”的方式,表明调用的是父类中声明的属性。
  • 当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的使用”super.方法”的方式,表明调用的是父类中被重写的方法。

super 调用构造器

  • 我们可以在子类的构造器中显式的使用”super(形参列表)”的方式,调用父类中声明的指定的构造器。
  • “super(形参列表)”的使用,必须声明在子类构造器的首行!
  • 我们在类的构造器中,针对于”this(形参列表)”或”super(形参列表)”只能二选一,不能同时出现。
  • 在构造器的首行,没有显式的声明”this(形参列表)”或”super(形参列表)”,则默认调用的是父类中空参的构造器:super()
  • 在类的多个构造器中,至少有一个类的构造器中使用了”super(形参列表)”,调用父类中的构造器。

子类对象实例化的过程

1、从结果上来看:(继承性)

  • 子类继承父类以后,就获取了父类中声明的属性或方法。
  • 创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。

2、从过程上来看:

  • 当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所有的父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以考虑进行调用。
  • 虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。

特征三:多态性

多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)
理解多态性:可以理解为一个事物的多种形态。

多态的使用:虚拟方法调用
有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。
总结:编译看左边、运行看右边。

多态性的使用前提:

  • 有类的继承关系。
  • 有方法的重写。

对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)

  • 若子类重写了父类的方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中:编译看左边,运行看右边。
  • 对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量:编译运行都看左边。

【练习题】

  1. public class OOPTest {
  2. public static void main(String[] args) {
  3. Base base = new Sub();
  4. base.add(1, 2, 3); // sub_1
  5. Sub sub = (Sub) base;
  6. sub.add(1, 2, 3); // sub_2
  7. }
  8. }
  9. class Base {
  10. public void add(int a, int... arr) {
  11. System.out.println("base_1");
  12. }
  13. }
  14. class Sub extends Base {
  15. public void add(int a, int[] arr) {
  16. System.out.println("sub_1");
  17. }
  18. public void add(int a, int b, int c) {
  19. System.out.println("sub_2");
  20. }
  21. }

instanceof 关键字

使用情境:
为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,一旦返回true,就进行向下转型。如果返回false,不进行向下转型。
说明:

  • a instanceof A:判断对象a是否是类A的实例。如果是,返回true;如果不是,返回false
  • 如果类B是类A的父类 ,且a instanceof A返回true,那么a instanceof B也返回true。

Object 类

1、java.lang.Object类是所有Java类的根父类。
2、如果在类的声明中未使用extends关键字指明其父类,则默认父类为Object类 。
3、Object类中的功能(属性、方法)就具有通用性。

  • 属性:无
  • 方法:equals()toString()getClass()hashCode()clone()finalize()wait()notify()notifyAll()

4、Object类只声明了一个空参的构造器。

== 和 equals() 的区别

==运算符的使用:
1、== 是一个运算符,不是方法。
2、==可以使用在基本数据类型变量和引用数据类型变量中。

  • 如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等。(不一定类型要相同)
  • 如果比较的是引用数据类型变量:比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体,== 符号使用时,必须保证符号左右两边的变量类型一致。

equals()方法的使用:
1、equals()是一个方法,而非运算符。
2、equals()只能适用于引用数据类型。
3、Object类中equals()的定义:
public boolean equals(Object obj) { return (this == obj); }
说明:Object类中定义的equals()和==的作用是相同的:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体。
4、像StringDateFile、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的”实体内容”是否相同。
5、通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的”实体内容”是否相同。那么,我们就需要对Object类中的equals()进行重写。
重写的原则:比较两个对象的实体内容是否相同。

【练习题】

  1. public class EqualsTest {
  2. public static void main(String[] args) {
  3. //基本数据类型
  4. int i = 10;
  5. int j = 10;
  6. double d = 10.0;
  7. System.out.println(i == j);//true
  8. System.out.println(i == d);//true,自动类型提升
  9. boolean b = true;
  10. // System.out.println(i == b);//不能用于比较,编辑报错
  11. char c = 10;
  12. System.out.println(i == c);//true,自动类型提升
  13. char c1 = 'A';
  14. char c2 = 65;
  15. System.out.println(c1 == c2);//true
  16. //引用类型:
  17. Customer cust1 = new Customer("Tom",21);
  18. Customer cust2 = new Customer("Tom",21);
  19. System.out.println(cust1 == cust2);//false
  20. String str1 = new String("atguigu");
  21. String str2 = new String("atguigu");
  22. System.out.println(str1 == str2);//false
  23. System.out.println(cust1.equals(cust2));//false--->true,因为重写了equals方法
  24. System.out.println(str1.equals(str2));//true
  25. }
  26. }

Object 类中的 toString() 方法

1、当我们输出一个对象的引用时,实际上就是调用当前对象的toString()方法。
2、Object类中toString()的定义:

  1. public String toString() {
  2. return getClass().getName() + "@" + Integer.toHexString(hashCode());
  3. }

3、像StringDateFile、包装类等都重写了Object类中的toString()方法。使得在调用对象的toString()时,返回”实体内容”信息。
4、自定义类也可以重写toString()方法,当调用此方法时,返回对象的”实体内容”。

包装类

image (5).pngimage (6).png

【练习题】

  1. public class InterviewTest {
  2. // 注意:三元运算符要求数据类型统一,会有自动类型提升,所以结果是1.0
  3. @Test
  4. public void test1() {
  5. Object o1 = true ? new Integer(1) : new Double(2.0);
  6. System.out.println(o1);// 1.0
  7. }
  8. @Test
  9. public void test2() {
  10. Object o2;
  11. if (true)
  12. o2 = new Integer(1);
  13. else
  14. o2 = new Double(2.0);
  15. System.out.println(o2);// 1
  16. }
  17. @Test
  18. public void test3() {
  19. Integer i = new Integer(1);
  20. Integer j = new Integer(1);
  21. System.out.println(i == j);//false
  22. //Integer内部定义了IntegerCache结构,IntegerCache中定义了Integer[],
  23. //保存了从-128~127范围的整数。如果我们使用自动装箱的方式,给Integer赋值的范围在
  24. //-128~127范围内时,可以直接使用数组中的元素,不用再去new了。目的:提高效率
  25. Integer m = 1;
  26. Integer n = 1;
  27. System.out.println(m == n);//true
  28. Integer x = 128;//相当于new了一个Integer对象
  29. Integer y = 128;//相当于new了一个Integer对象
  30. System.out.println(x == y);//false
  31. }
  32. }

面向对象(下)

static 关键字

1、static意思是静态的,可以用来修饰:属性、方法、代码块、内部类。

2、属性,按是否使用static修饰,又分为:静态属性(类变量) 与 非静态属性(实例变量)

  • 实例变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改。
  • 静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过了的。

static修饰属性的其他说明:

  • 静态变量随着类的加载而加载。可以通过”类.静态变量”的方式进行调用。
  • 静态变量的加载要早于对象的创建。
  • 由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中。

4、使用static修饰方法:静态方法

  • 随着类的加载而加载,可以通过”类.静态方法”的方式进行调用。
  • 静态方法中,只能调用静态的方法或属性。
  • 非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性。
  • 在静态方法内,不能使用this关键字、super关键字。

5、开发中应用

  • 如何确定一个属性是否要声明为static的?
    • 属性是可以被多个对象所共享的,不会随着对象的不同而不同的。
    • 类中的常量也常常声明为static
  • 如何确定一个方法是否要声明为static的?
    • 操作静态属性的方法,通常设置为static的。
    • 工具类中的方法,习惯上声明为static的,比如:MathArraysCollections等。

单例设计模式

1、所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例。

2、如何实现?

  • 饿汉式
    • 坏处:对象加载时间过长。
    • 好处:饿汉式是线程安全的。
  • 懒汉式
    • 好处:延迟对象的创建。
    • 目前的写法坏处:线程不安全。—->到多线程内容时,再修改

【饿汉式】

  1. public class Bank {
  2. // 1、私有化类的构造器
  3. private Bank(){}
  4. // 2、内部私有化创建类的对象,并声明为static
  5. private static Bank instance = new Bank();
  6. // 3、提供公共的static方法,用来返回类的对象
  7. public static Bank getInstance() {
  8. return instance;
  9. }
  10. }

【懒汉式】

  1. public class Order {
  2. // 1、私有化类的构造器
  3. private Order() {}
  4. // 2、声明当前类的对象,并没有初始化,并且是私有化和static的
  5. private static Order instance = null;
  6. // 3、提供static的公共的方法用来创建或返回当前类的对象
  7. public static Order getInstance() {
  8. if (instance == null) {
  9. instance = new Order();
  10. }
  11. return instance;
  12. }
  13. }

类的结构之四:代码块

代码块的作用:用来初始化类、对象。
代码块如果有修饰的话,只能使用static。分为:静态代码块、非静态代码块。

静态代码块

  • 内部可以有输出语句。
  • 随着类的加载而执行,而且只执行一次。
  • 作用:初始化类的信息。
  • 如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行。
  • 静态代码块的执行要优先于非静态代码块的执行。
  • 静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构。

非静态代码块

  • 内部可以有输出语句。
  • 随着对象的创建而执行,每创建一个对象,就执行一次非静态代码块。
  • 作用:可以在创建对象时,对对象的属性等进行初始化。
  • 如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行。
  • 非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法。

【练习题1】

  1. // 由父及子,静态先行
  2. public class LeafTest {
  3. public static void main(String[] args) {
  4. new Leaf();
  5. System.out.println("******************************");
  6. new Leaf();
  7. }
  8. }
  9. class Root {
  10. public Root(){
  11. System.out.println("Root的无参数的构造器");
  12. }
  13. static{
  14. System.out.println("Root的静态初始化块");
  15. }
  16. {
  17. System.out.println("Root的普通初始化块");
  18. }
  19. }
  20. class Mid extends Root{
  21. public Mid(){
  22. System.out.println("Mid的无参数的构造器");
  23. }
  24. public Mid(String msg){
  25. this();
  26. System.out.println("Mid的带参数构造器,其参数值:" + msg);
  27. }
  28. static{
  29. System.out.println("Mid的静态初始化块");
  30. }
  31. {
  32. System.out.println("Mid的普通初始化块");
  33. }
  34. }
  35. class Leaf extends Mid{
  36. public Leaf(){
  37. super("尚硅谷");
  38. System.out.println("Leaf的构造器");
  39. }
  40. static{
  41. System.out.println("Leaf的静态初始化块");
  42. }
  43. {
  44. System.out.println("Leaf的普通初始化块");
  45. }
  46. }

执行结果:

  1. Root的静态初始化块
  2. Mid的静态初始化块
  3. Leaf的静态初始化块
  4. Root的普通初始化块
  5. Root的无参数的构造器
  6. Mid的普通初始化块
  7. Mid的无参数的构造器
  8. Mid的带参数构造器,其参数值:尚硅谷
  9. Leaf的普通初始化块
  10. Leaf的构造器
  11. ******************************
  12. Root的普通初始化块
  13. Root的无参数的构造器
  14. Mid的普通初始化块
  15. Mid的无参数的构造器
  16. Mid的带参数构造器,其参数值:尚硅谷
  17. Leaf的普通初始化块
  18. Leaf的构造器

【练习题2】

  1. class Father {
  2. static {
  3. System.out.println("11111111111");
  4. }
  5. {
  6. System.out.println("44444444444");
  7. }
  8. public Father() {
  9. System.out.println("55555555555");
  10. }
  11. }
  12. public class Son extends Father {
  13. static {
  14. System.out.println("22222222222");
  15. }
  16. {
  17. System.out.println("66666666666");
  18. }
  19. public Son() {
  20. System.out.println("77777777777");
  21. }
  22. public static void main(String[] args) { // 由父及子 静态先行
  23. System.out.println("33333333333");
  24. System.out.println("***********");
  25. new Son();
  26. System.out.println("***********");
  27. new Father();
  28. }
  29. }

执行结果为:

  1. 11111111111
  2. 22222222222
  3. 33333333333
  4. ***********
  5. 44444444444
  6. 55555555555
  7. 66666666666
  8. 77777777777
  9. ***********
  10. 44444444444
  11. 55555555555

final关键字

final表示最终的,可以用来修饰的结构:类、方法、变量。

1、修饰类时:表明此类不能被其他类继承,比如:String类、System类等。

2、修饰方法时:表明此方法不可以被重写,比如:Object类中getClass()。

3、修饰变量时:此时的”变量”就是一个常量,不可更改。

  • 如果修饰的是属性:可以考虑赋值的位置有:显式初始化、代码块中初始化、构造器中初始化
    • ⚠️注意:不可以默认初始化,也不可以通过“对象.属性”/“对象.方法”的形式赋值。
  • 如果修饰的是局部变量:尤其是修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值。

4、static final 用来修饰属性:全局常量,static final用来修饰方法:不可重写的方法。

abstract关键字

abstract:表示抽象的,可以用来修饰的结构有:类、方法

修饰类时:表示这是一个抽象类。

  • 此类不能实例化。
  • 抽象类中一定有构造器,便于子类实例化时调用。
  • 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作。

修饰方法时:表示这是一个抽象方法。

  • 抽象方法只有方法的声明,没有方法体。
  • 包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法的。
  • 若子类重写了父类中的所有的抽象方法后,此子类方可实例化。
  • 若子类没有重写父类中的所有的抽象方法,则此子类仍是一个抽象类。

abstract使用上的注意点:

  • 不能用来修饰:属性、构造器等结构。
  • 不能用来修饰private方法、static方法、final的方法、final的类。
  • 可以修饰static内部类。

接口(interface)

1、接口使用interface关键字来定义。在Java中,接口(interface)和类(class)是并列的两个结构。

2、如何定义接口:(定义接口中的成员)

  • JDK7及以前:只能定义全局常量和抽象方法
    • 全局常量:public static final的,但是书写时,可以都省略。
    • 抽象方法:public abstract的,但是书写时,可以都省略。
  • JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法
    • 静态方法:public static的,但是书写时,可以省略public
      • 接口中定义的静态方法,只能通过接口去调用,接口实现类及其对象都不能调用,这种接口有点类似于工具类。
    • 默认方法:public default的,但是书写时,可以省略public
      • 接口实现类可以重写接口中定义的默认方法,调用实现类的对象时,调用的是重写以后的方法。

3、接口中不能定义构造器的!意味着接口不可以实例化。

4、Java类可以实现多个接口,弥补了Java单继承性的局限性;接口也是通过让类去实现(使用implements关键字)的方式来使用。格式:class A extends B implements C,D,E

  • 如果实现类实现(重写)了接口中的所有抽象方法,则此实现类就可以实例化。
  • 如果实现类没有实现(重写)接口中所有的抽象方法,则此实现类仍为一个抽象类,必须使用abstract关键字。

5、接口与接口之间可以继承,而且可以多继承。
格式:interface C extends A, B

6、接口的具体使用:

  • 体现为多态性。
  • 接口,实际上可以看做是一种规范。
  • 开发中需要体会面向接口编程。

⚠️注意点:

  • 子类继承的父类与实现的接口中,声明了同名的属性,会报错。
  • 子类继承的父类与实现的接口中,声明了同名同参数的方法,那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法。(类优先原则)
  • 实现类实现的多个接口中,声明了同名同参数的默认方法,那么在实现类没有重写此方法的情况下,报错(接口冲突),必须在实现类中重写此方法,或者不重写但是继承的父类有同名同参数的方法就不报错了。
  • 如何在子类(或实现类)的方法中调用父类、接口中被重写的方法?
    • method();调用的是子类重写后的方法。
    • super.method();调用的是父类中声明的方法。
    • Interface.super.method();调用的是接口中被重写的默认方法,这种写法可以看作是一种规定。

面试题:抽象类与接口有哪些异同?
相同点:

  • 都不能实例化
  • 都可以被继承
  • 都可以定义抽象的方法

不同点:

  • 抽象类有构造器,接口不能声明构造器
  • 抽象类只能单继承,接口可以多继承
  • 抽象类和接口的结构(JDK7、8以后)不同。
  1. public class USBTest {
  2. public static void main(String[] args) {
  3. Computer computer = new Computer();
  4. // 1.创建了接口的非匿名实现类的非匿名对象
  5. Flash flash = new Flash();
  6. computer.transferData(flash);
  7. // 2.创建了接口的非匿名实现类的匿名对象
  8. computer.transferData(new Printer());
  9. // 3.创建了接口的匿名实现类的非匿名对象
  10. USB phone = new USB(){
  11. @Override
  12. public void start() {
  13. System.out.println("手机开始工作");
  14. }
  15. @Override
  16. public void stop() {
  17. System.out.println("手机结束工作");
  18. }
  19. };
  20. computer.transferData(phone);
  21. // 4.创建了接口的匿名实现类的匿名对象
  22. computer.transferData(new USB() {
  23. @Override
  24. public void start() {
  25. System.out.println("mp3开始工作");
  26. }
  27. @Override
  28. public void stop() {
  29. System.out.println("mp3结束工作");
  30. }
  31. });
  32. }
  33. }
  34. interface USB {
  35. void start();
  36. void stop();
  37. }
  38. class Computer {
  39. public void transferData(USB usb) {
  40. usb.start();
  41. System.out.println("传输细节");
  42. usb.stop();
  43. }
  44. }
  45. class Flash implements USB {
  46. @Override
  47. public void start() {
  48. System.out.println("U盘开启工作");
  49. }
  50. @Override
  51. public void stop() {
  52. System.out.println("U盘结束工作");
  53. }
  54. }
  55. class Printer implements USB {
  56. @Override
  57. public void start() {
  58. System.out.println("打印机开启工作");
  59. }
  60. @Override
  61. public void stop() {
  62. System.out.println("打印机结束工作");
  63. }
  64. }

【练习题1】

  1. interface A {
  2. int x = 0;
  3. }
  4. class B {
  5. int x = 1;
  6. }
  7. class C extends B implements A {
  8. public void pX() {
  9. // System.out.println(x); // 报错,因为接口和类同级别,编译器不知道x指向谁
  10. System.out.println(super.x); // 1
  11. System.out.println(A.x); // 0
  12. }
  13. public static void main(String[] args) {
  14. new C().pX();
  15. }
  16. }

【练习题2】
image.png
【练习题3】

  1. interface Filial {
  2. default void help() {
  3. System.out.println("老妈,我来救你了");
  4. }
  5. }
  6. interface Spoony {
  7. default void help() {
  8. System.out.println("媳妇,别怕,我来了");
  9. }
  10. }
  11. class Father{
  12. public void help(){
  13. System.out.println("儿子,救我媳妇!");
  14. }
  15. }
  16. public class Man extends Father implements Filial, Spoony {
  17. @Override
  18. public void help(){
  19. System.out.println("我该救谁呢?");
  20. Filial.super.help();
  21. Spoony.super.help();
  22. }
  23. public static void main(String[] args) {
  24. new Man().help();
  25. }
  26. }

程序执行结果:

  1. 我该救谁呢?
  2. 老妈,我来救你了
  3. 媳妇,别怕,我来了

上面的子类或实现类如果不重写help方法,也不继承父类,会报错:接口冲突,继承父类以后的输出结果是:

  1. 儿子,救我媳妇!

类的结构之五:内部类

1、Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B称为外部类。

2、内部类的分类:

  • 成员内部类:
    • 静态
    • 非静态
  • 局部内部类:
    • 方法内
    • 代码块内
    • 构造器内

3、关于成员内部类的细节:

  • 一方面,作为外部类的成员:
    • 可以调用外部类的结构
    • 可以被static修饰(外部类以及构造器是不可以被static修饰的)
    • 可以被 4 种不同的权限修饰(外部类只能使用public或缺省)
  • 另一方面,作为一个类:
    • 可以定义属性、方法、构造器等结构。
    • 可以被final修饰,表示此类不能被继承,言外之意,不使用final,就可以被继承。
    • 可以被abstract修饰,表示不能实例化,必须通过子类实例化。

4、关于局部内部类的细节:
方法内的局部内部类,调用了方法内的局部变量,此局部变量必须是final的,可以省略不写(JDK7及以前的版本不能省略)。

5、成员内部类与局部内部类在编译以后,都会生成字节码文件,格式略有不同:

  • 成员内部类:外部类$内部类名.class
  • 局部内部类:外部类$数字内部类名.class

6、开发中需要关注如下的3个问题:
(1)如何实例化成员内部类的对象?
(2)如何在成员内部类中区分调用外部类的结构?
(3)开发中局部内部类的使用

【示例1】

  1. public class InnerClassTest {
  2. public static void main(String[] args) {
  3. // 创建Dog实例(静态成员内部类)
  4. Person.Dog dog = new Person.Dog();
  5. dog.show();
  6. // 创建Bird实例(非静态成员内部类)
  7. Person p = new Person();
  8. Person.Bird bird = p.new Bird();
  9. bird.sing();
  10. bird.display("黄鹂");
  11. }
  12. }
  13. class Person {
  14. String name = "小明";
  15. int age;
  16. public void eat() {
  17. System.out.println("人吃饭");
  18. }
  19. // 静态成员内部类
  20. static class Dog {
  21. public void show() {
  22. System.out.println("卡拉是条狗");
  23. //eat(); // 不能调用
  24. }
  25. }
  26. // 非静态成员内部类
  27. class Bird {
  28. String name = "杜鹃";
  29. public Bird(){}
  30. public void sing() {
  31. System.out.println("我是一只小小鸟");
  32. Person.this.eat(); // 可以简写为eat();
  33. System.out.println(age);//不重名的变量可以直接写
  34. }
  35. public void display(String name) {
  36. System.out.println(name);// 方法的行参
  37. System.out.println(this.name);//内部类的属性
  38. System.out.println(Person.this.name);//外部类的属性
  39. }
  40. }
  41. }

【示例2】

  1. public class InnerClassTest2 {
  2. // 方法内的局部内部类(多见)
  3. // 例如:创建一个实现了Comparable接口的类,并返回一个对象
  4. public Comparable getComparable() {
  5. int num = 10; // 省略了 final
  6. // 方式一:非匿名实现类的匿名对象
  7. class MyComparable implements Comparable {
  8. @Override
  9. public int compareTo(Object o) {
  10. // num = 20; // 报错,因为num是final的,不可更改
  11. // System.out.println(num);
  12. return 0;
  13. }
  14. }
  15. return new MyComparable();
  16. // 方式二:匿名实现类的匿名对象
  17. // return new Comparable() {
  18. // @Override
  19. // public int compareTo(Object o) {
  20. // return 0;
  21. // }
  22. // };
  23. }
  24. // 构造器内的局部内部类(少见)
  25. public InnerClassTest2() {
  26. class AA {
  27. //
  28. }
  29. }
  30. // 代码块内的局部内部类(少见)
  31. {
  32. class BB {
  33. //
  34. }
  35. }
  36. }