前言

Java 是纯粹的面向对象编程语言,提供了类、接口和继承等特性。

# 理解面向对象

如果你对于面向对象还不够理解,请查看一下内容

点击查看面向对象的理解内容

面向过程、结构化和面向对象

面向过程

while (西瓜没吃完 && 猪没吃饱){
	猪张嘴;
	猪咬西瓜;
	猪吞西瓜;
}
1
2
3
4
5

结构化

void 吃(动物, 食物){
	···
}
吃(猪, 西瓜);
1
2
3
4

面向对象

class{
	···
	吃(食物){
		...
	}
	...
}.(西瓜);
1
2
3
4
5
6
7
8

面向过程

面向过程是一种以过程为中心的编程思想。分析程序所需要完成的步骤,利用程序将这些步骤逐一实现,程序执行时按步骤一个个完成,最终解决问题。

结构化

什么是结构化?
结构化是一种以功能为中心的编程思想。自顶向下分析,将程序拆分为若干模块。每个模块负责一个功能,接受数据并返回数据。整个软件由一个个函数组成。
三种基本结构

  • 顺序结构
  • 选择结构
  • 循环结构

面向对象

什么是面向对象?
面向对象是一种以对象为中心的编程思想。它从现实事物出发,将其抽象为类。类中往往包含着对象的属性及操作属性的方法,因此,面向对象不再人为地将数据和动作分离。
设计方法
从面向对象的眼光来看,人类如果看待对象,程序员就该如何定义和使用类。

  • 一个对象应该有哪些属性,那么就在类中设置相应的成员变量;
  • 一个对象应该有哪些行为,那么就在类中定义相应的方法。

基本特征

  • 封装:将对象的实现细节隐藏起来,通过一些公用方法来暴露该对象的功能。
  • 继承:面向对象实现代码复用的重要手段,当子类继承父类后,将直接获得父类的属性和方法。
  • 多态:同一个方法,在使用不同的实例时,会执行不同的操作。

JAVA 中的面向对象

一切都是对象
在 JAVA 语言中,除了 8 个基本数据类型以外,一切都是对象。
对象具有状态,JAVA 中一个对象通过它的成员变量数值来描述它的状态;对象具有行为,JAVA 中一个对象通过它的方法来描述它的行为。对象把数据和对数据的操作封装成一个有机的整体,实现了数据和操作的结合。
JAVA 语言中使用 class 关键字定义类,在类中描述对象的属性及方法。定义了类以后,用 new 关键字来创建类的实例--对象。
类与对象
类是具有相同属性和方法的一组对象的集合,对象的抽象化是类,类的具体化就是对象。
类是对一类事物的描述,是抽象的、概念上的定义;对象是实际存在的属于某一类的个体,也被称为实例。
类与类
类与类之前有以下几种关系:

  • 无关
  • 一般-特殊:又称为继承关系,在 JAVA 中,通常用 子类 extends 父类 来表示。
class 水果 {
  
}
class 香蕉 extends 水果 {
  
}
1
2
3
4
5
6
  • 整体-部分:又称为组装关系。在 JAVA 中,通常在一个类里保存另一个对象的引用来实现这种组合关系。
class 球员 {
  ···
}
class team {
  球员 球员1;
  球员 球员2;
  球员 球员3;
}
1
2
3
4
5
6
7
8

# 类与对象

# 定义类

修饰符 class 类名{
	零到多个初始化块;
	零到多个构造器;
	零到多个变量;
	零到多个方法;
}
1
2
3
4
5
6

修饰符
修饰符可以省略,也可以是 public|final|abstract
构造器

修饰符 类名(形参列表){
	···
}
1
2
3

变量

修饰符 类型 变量名 [= 默认值];
1

方法

修饰符 返回值类型 方法名(形参列表){

}
1
2
3

# 创建对象

创建对象的根本途径是构造器,通过 new 来调用某个类的构造器即可创建对象。

类名 对象名 = new 类名(实参列表);
1

# 对象和引用

对象和引用

Person p = new Person();
1

多个引用
因此,也可以使多个引用变量指向同一个对象,共同管理:

Person p2 = p;
1

垃圾回收
如果堆内存中的对象不被任何变量指向,则程序无法再访问它,因此它也变成了垃圾,JAVA 的垃圾回收器将会在适当的时候回收释放它。如果希望系统回收某个对象,只需要切断对象的引用变量跟它的联系即可。

# this

JAVA 提供 this 关键字,它表示对象本身。
示例

public class dog{
	// 定义一个"吠"方法
	public void bark(){
		System.out.println("汪!");
	}
	// 定义一个"狂吠"方法,该方法需要借助"吠"方法
	public void barkbarkbark(){
		for (int i = 0; i < 3; i++){
			this.brak();
		}
	}
}
1
2
3
4
5
6
7
8
9
10
11
12

JAVA 允许省略 this.

  • 如果调用类成员,则使用类作为主调
  • 如果调用实例成员,则使用对象本身作为主调

# 方法

# 方法的定义

修饰符 返回值类型 方法名(参数类型 参数名){
    方法体
    return 返回值;
}
1
2
3
4

# 方法的调用

方法名(参数)
1

# 参数传递

传递方式是:单向传递,值传递
需要注意的是:

  • 传递基本类型,将实参的值(也就是数据)拷贝给形参,方法中对变量的改动将不会影响原来的变量
  • 传递引用类型,将参数的值(也就是数据的地址)拷贝给形参,方法中对引用变量的改动同样不会影响原来的引用变量,但能通过引用变量对引用对象做出修改

提示

可以理解为: 将地址传递进方法中,虽然对地址的修改仅在方法中有效,但却可以顺着指针对其指向的内存做修改。

# 方法重载

方法重载是指:

  • 多个方法在同一个类中
  • 多个方法具有相同的方法名
  • 多个方法的参数不同(类型或数量不同) JAVA 会根据传入参数的数量与类型,自动选择对应的方法并执行。

# 形参个数可变

定义
JAVA 允许为方法指定数量不确定的形参。如果在定义方法时,在最后一个形参的类型后增加 ... ,则表示该形参可以接受多个参数值,多个参数值被当作数组传入。

修饰符 返回值类型 方法名(形参列表, 数据类型... 形参名){

}
1
2
3

调用

方法名(实参列表, 形参1, 形参2, ···, 形参n)
1

示例

public static void test(int a, String... words){
	System.out.println(a);
	for (var tmp : words){
		System.out.println(tmp);
	}
}
public static void main(String[] args){
	test(5, "Hello", "World", "!");
}
1
2
3
4
5
6
7
8
9

提示

可以在调用函数时传入任意个数的参数值,在方法中,会将它们当作数组处理。

# 构造器

# 什么是构造器?

构造器是一种特殊的方法,用于创建实例时执行初始化,它是创建对象的重要途经。

提示

即使是使用工厂模式、反射等方式创建对象,其本质依然是依赖于构造器

# 默认构造器

  • 如果没有为类设置构造器,系统会自动为类设置一个无参数、无动作的构造器;
  • 如果为类设置了构造器,则系统不再提供默认的构造器

# 自定义构造器

如果希望改变默认的初始化,便可以通过自定义构造器来实现:

public class 类名 {
	数据类型 成员变量;
	public 类名() {
		this.成员变量 = 默认值;
	}
	public 类名(数据类型 形参) {
		this.成员变量 = 形参;
	}
}
1
2
3
4
5
6
7
8
9

# 初始化块

# 初始化块

class 类名 {
	[static] {
        ···
    }
    ···
}
1
2
3
4
5
6

初始化块用于初始化对象或类,其代码中可以包含任意可执行语句。

# 执行顺序

在 JAVA 创建一个对象后,

  • 系统会首先加载类(如果类此前没有被加载的话)
  • 然后为该对象的所有实例变量分配内存
  • 接着执行初始化顺序 其中,初始化块声明变量时指定初始值的执行顺序与它们在代码中的顺序相同
public class Test {
    int a = 6;
    {
      a = 5;
    }
    {
      b = 5;
    }
    int b = 6;
    void fun() {
    System.out.println(a);
    System.out.println(b);
    }
    public static void main(String[] args) {
      Test test = new Test();
      test.fun();
    }
  }
// 运行结果
  5
  6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 实例初始化块

public class Test2 {
    {
        System.out.println("实例初始化块");
    }
    public Test2() {
        System.out.println("构造器");
    }

    public static void main(String[] args) {
        Test2 test2 = new Test2();
    }
}
// 运行结果
  实例初始化模块
  构造器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

提示

需要注意的是: 在编译 JAVA 文件之后,实例初始化块会被"还原"到构造器之中,并且位于构造器的最前面。

# 封装

封装是面向对象的三大特征之一。将成员变量隐藏在对象内部,不允许外部直接访问 ,仅能通过该类提供的方法来实现对成员变量的操作与访问。
通过封装,可以:

  • 隐藏类的实现细节
  • 限制不合理访问
  • 进行数据检查,从而保证对象信息的完整性
  • 便于修改,提升代码可维护性

# 访问修饰符

JAVA 符号

# 常用做法

  • 将变量用 private 修饰,以限制外界的访问
  • 如果变量不需要被外部使用,无需额外处理,仅在内部调用它即可
  • 如果变量需要被外部使用,提供 getXXX()setXXX() ,可以通过方法中的代码逻辑保护成员变量。

提示

使外界仅能得到大致的范围,而无法得到具体数据:

public String getSalary(String name) {
	···
	if (salary >= 5000) {
		return "high";
	}else {
		return "medium"
	}
}
1
2
3
4
5
6
7
8

阻止对数据的不当修改:

public void setAge(String name, int age) {
	···
	if (age >= 150 || age < 0) {
		return;
	}
	··
}
1
2
3
4
5
6
7

# 继承

继承就是子类继承父类的特征和行为,使得子类对象具有父类的变量与方法。

# 继承的语法

class 父类 {

}
class 子类 extends 父类 {

}
1
2
3
4
5
6

# 继承的特征

  • 继承后,类将会获得父类的(非 private)属性与方法,但不包括构造器
  • 每个类只能有一个直接父类

# 方法重写

提示

方法重载:名字相同但参数不同的多个函数 方法重写:在子类中重写父类的方法

class Animal {
	···
	void calls {
		嗷呜
	}
}
class Dog extends Animal {
	···
	void calls {
		汪汪汪
	}
}
1
2
3
4
5
6
7
8
9
10
11
12

方法重写,即在子类中重写父类的方法。需要遵守“两同两小一大”规则:

  • 方法名相同
  • 参数相同
  • 子类方法返回值类型应该更小或相等
  • 子类方法声明抛出的异常类应该更小或相等
  • 子类方法的访问权限应该更大或相等

# super

访问被重写的方法
可以在子类的方法中使用 super 调用父类中被重写的方法。

class Animal {
  ···
  void calls {
      嗷呜
  }
}
class Dog extends Animal {
  ···
  void calls {
      汪汪汪
  }
  void fatherCalls {
      super.calls();
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

提示

直接使用 方法名() 将访问子类重写的方法, 在方法中使用 super.方法名() ,将可以调用父类的方法。

# 多态

相同类型的变量,在调用同一个方法时,呈现出多种不同的行为特征。

# 多态的条件

  • 有继承/实现关系
  • 有方法重写
  • 引用类型变量的编译时类型为父类,运行时类型为子类

# 多态示例

// 父类Animal
public class Animal {
    String name = "Animal";

    void eat() {
        System.out.println("动物什么都吃");
    }
}

// 子类Dog
public class Dog extends Animal{
    String name = "Dog";

    @Override
    void eat() {
        System.out.println("狗吃骨头");
    }
    void call() {
        System.out.println("汪汪汪");
    }
}

// 子类Cat
public class Cat extends Animal{
    String name = "Cat";

    @Override
    void eat() {
        System.out.println("猫吃鱼");
    }
}

// 测试类
public class Test {
    public static void main(String[] args) {
        Animal animal = new Animal();
        Animal dog = new Dog();
        Animal cat = new Cat();
        // 非多态
        System.out.println("---animal---");
        System.out.println(animal.name);
        animal.eat();
        // 多态
        System.out.println("---dog---");
        System.out.println(dog.name);
        dog.eat();
		// dog.call();  // 因为父类中不存在call(),因此无法调用
        ((Dog)dog).call();
        System.out.println("---cat---");
        System.out.println(cat.name);
        cat.eat();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

# 抽象类

抽象类只描述类型的基本特征与功能,具体如何实现由子类完成。

# 抽象方法与抽象类

  • 抽象方法和抽象类都用 abstract 定义
  • 如果类中存在抽象方法,则类必须定义为抽象类
  • 抽象类无法被实例化
  • 没有抽象类方法、没有抽象变量、没有抽象构造器
  • abstract 不能和 final、static、private 同时使用

# 语法

abstract class 类名 {
	···
    abstract 返回值类型 方法名();
	···
}
1
2
3
4
5

# 抽象类示例

// 抽象父类
public abstract class Animal {
    private String name;
    private int age;

    public abstract void eat();

	// get/set方法

    // 构造方法
    
    // 重写toString()
}

// 子类
public class Cat extends Animal{

    public Cat() {
    }

    public Cat(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
}

// 测试类
public class Test {
    public static void main(String[] args) {
        Animal cat = new Cat("喵喵", 1);
        System.out.println(cat);
        cat.eat();
    }
}
// 运行结果
  name='喵喵', age=1
  猫吃鱼
	
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

# 接口

在 JAVA 中,接口是一系列方法的声明,这些方法应该被类实现。
接口定义了规范,它不关心类的具体实现,只是单纯规定类必须提供某些方法。

# 接口的特性

  • 无法被实例化
  • 如果类实现接口,则它必须实现接口描述的所有方法 如果抽象类实现接口,则它无需实现接口所描述的方法,但它的子类也必须实现接口描述的所有方法
  • 可以包含方法,方法会被隐式指定且只能指定为 public abstract 可以包含变量,变量会被隐式指定且只能指定为 public static final
  • 一个类可以实现多个接口

# 声明接口

[public] interface 接口名 [extends 其他的接口名] {
    // 变量
    // 方法
}
1
2
3
4

提示

接口能够继承其它接口

# 接口示例

// 接口Action
public interface Action {
    void jump();
}

// 继承自Action的AnimalAction接口
public interface AnimalAction extends Action {
    void stand();
}

// 抽象父类
public abstract class Animal {
    private String name;
    private int age;

    public abstract void eat();

	// get/set方法

    // 构造方法
    
    // 重写toString()
}

// 子类
public class Cat extends Animal implements AnimalAction{

    public Cat() {
    }

    public Cat(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }

    @Override
    public void jump() {
        System.out.println("跳跳跳");
    }

    @Override
    public void stand() {
        System.out.println("乖乖站着");
    }
}

// 测试类
public class Test {
    public static void main(String[] args) {
        Cat cat = new Cat("喵喵", 1);
        System.out.println(cat);
        cat.eat();
        cat.jump();
        cat.stand();
    }
}
// 运行结果
  name='喵喵',age=1
  猫吃鱼
  跳跳跳
  乖乖站着
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

# 函数式接口

函数式接口就是一个有且仅有一个抽象方法的接口,可以通过 @FunctionalInterface 进行注解。

@FunctionalInterface
interface MyInterface
{
    void fun();
}
1
2
3
4
5

# 枚举类

一些类的对象是有限且固定的,它们便被称为枚举。

小例子

季节类,有且仅有:春天、夏天、秋天、冬天
性别类,有且仅有:男性、女性

# 常用方法

方法 说明
枚举类名.valueOf(枚举值") 返回指定枚举类中的指定枚举
枚举1.compareTo(枚举2) 比较罗列枚举值时的顺序(若枚举 1 在枚举 2 之后,则返回整数···)
枚举.ordinal() 返回枚举值在枚举类中的索引值
枚举.toString() 返回枚举的名称

# 枚举语法

JAVA 通过枚举类来表达枚举关系。

enum 枚举类名 {
	枚举实例1, 枚举实例2, ···, 枚举实例n;
}
1
2
3

# 枚举示例

public enum Season {
    SPRING, SUMMER, FALL, WINTER;
}


public static void main(String[] args) {
    // Season i = Season.SPRING;
    Season i = Season.valueOf("SPRING");
    System.out.println(i.compareTo(Season.SUMMER));
    System.out.println(i.ordinal());
    System.out.println(i);
}
// 运行结果
  -1
  0
  SPRING
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 类变量和类方法

可以在枚举类中定义类变量和类方法。

public enum Season {
    SPRING, SUMMER, FALL, WINTER;
    final static int seasonNum = 4;
    static void sayHi() {
        System.out.println("你好!我是季节类。");
    }
}

public class Test {
     public static void main(String[] args) {
       System.out.println(Season.seasonNum);
       Season.sayHi();
    }
}
// 运行结果
  4
  你好!我是季节类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 成员方法

可以为枚举类定义成员方法,用于进行每个枚举的特有动作。

  • 定义抽象方法 / 实现接口
  • 在枚举值之后紧跟 { } ,在其中重写方法
enum 枚举类名 [implements 接口名]{
	枚举实例1{
		// 重写方法
	}, 
	枚举实例2{
		// 重写方法
	},
	···, 
	枚举实例n{
		// 重写方法
	};
	
	[抽象方法]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 成员方法示例

public enum Season {
  SPRING{
      public void sayHi() {
          System.out.println("你好!我是春天。");
      };
  },
  SUMMER{
      public void sayHi() {
          System.out.println("你好!我是夏天。");
      };
  },
  FALL{
      public void sayHi() {
          System.out.println("你好!我是秋天。");
      };
  },
  WINTER{
      public void sayHi() {
         System.out.println("你好!我是冬天。");
      };
  };
  public abstract void sayHi();
}
public class Test {
  public static void main(String[] args) {
     Season.SPRING.sayHi();
     Season.SUMMER.sayHi();
     Season.FALL.sayHi();
     Season.WINTER.sayHi();
  }
}
// 运行结果
  你好!我是春天。
  你好!我是夏天。
  你好!我是秋天。
  你好!我是冬天。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36