# 基础

面向对象(OOP,Object-Oriented Programming)是一种程序设计思想,它基于对象的特征来描述程序的行为,并且使程序的可读性和可维护性更高。

# 面向对象和面向过程

面向对象描述 引用地址:Java面向对象编程和面向过程编程的区别 (opens new window)

# 面向对象的特征

Java面向对象具有三大特征:

  • 封装
    • 将同一种对象(一切事物皆对象)的属性、方法抽象成一个类,然后通过类的方法、属性来访问类
  • 继承
    • 在封装的基础上,将同类事物继续抽象,继承时子类拥有父类的属性和方法,并且持有自己的属性和方法
  • 多态
    • 不同对象之间行为的抽象

# java类与对象

什么是类? 类(Class)是面向对象程序设计(OOP,Object-Oriented Programming),是一种抽象数据类型,是对所具有相同特征实体的抽象, 在 java 面向对象编程中就是是对一类 "事物" 的属性(成员变量)和行为(方法)的抽象(即类 = 属性 + 方法,属性描述的是状态,方法描述的是行为动作), 类是引用数据类型,类可以创建对象,对象又称为实例(instance),类创建对象这个过程称为实例化(实例化对象)

抽象数据类型:将不同类型的数据集合组成一个整体用来描述新的事物。

# 类的定义

完整的类定义:

修饰符 class 类名 {
    // 类体 = 属性(成员变量,也叫字段(field))+方法(成员方法)
    // 1. 定义成员变量
    成员变量类型 成员变量名称;
    // ...
    // 2.定义成员方法
    修饰符 方法返回值类型 方法名称(参数列表) {
        // 方法体
    }
    // ...
    
}
1
2
3
4
5
6
7
8
9
10
11
12

备注:

  • 成员变量就是描述类型对象共同的数据结构(对象的属性),成员方法就是描述对象的行为,封装对象的功能
  • 成员变量类型就是基本数据类型(byte、short、int、long、float、double、boolean、chart)和引用数据类型(String、Object、数组...)

实现一个 “学生类” 如下:

public class Student {
    // 姓名
    String name;
    // 年龄
    int age;
    // 学号
    int no;
    // 是否合格
    boolean isPass;

    public String getName() {
        String name = "张三";
        return name;
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

上面程序中name、age、no、isPass是成员变量,而getName()是成员方法。要是使用成员变量和成员方法就需要使用 new 来创建实例对象。

# 创建并使用对象

类定义后,可以使用类这个“模板”来创造“对象”了,一个类可以创建多个对象!
语法:

new 类名()
1
public class StudentTest {
    public static void main(String[] args) {
        // 创建一个学生对象
        Student s = new Student();

        // 访问学生对象的属性
        System.out.println(s.name); // null
        // 访问(调用)学生对象的方法
        System.out.println(s.getName()); // 张三

        // 再创建一个学生对象
        Student ss = new Student();
        // 给对象赋值
        ss.name = "李四";
        System.out.println(ss.name); // 李四

        /**
         * 引用类型 变量如果赋值为 null,表示没有指向任何对象,即指向null
         * 则取值会报错误 空指针异常 NullPointerException
         */
        Student sss = null;
        System.out.println(sss.name); // 报错空指针异常 java.lang.NullPointerException

    }
}
class Student {
    // 姓名
    String name;
    // 年龄
    int age;
    // 学号
    int no;
    // 是否合格
    boolean isPass;

    public String getName() {
        String name = "张三";
        return name;
    }
}

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

备注:

  • 【回顾】一个java源文件可以创建多个类,但 public class类只能有一个
  • java 语言当中凡是使用class关键字定义的类都属于引用数据类型,类名本身就是这种引用数据类型的类型名
  • 除8种基本类型以外,用类、接口、数组等声明的变量都是引用数据类型
  • null 表示空
  • java.lang.NullPointerException 表示空指针异常

# 构造方法

java构造方法是类中特殊的方法,是类的初始化方法(构造方法/构造函数),通过 new 构造方法名(实际参数列表)来调用构造方法,用于初始化对象的属性。

语法:

[修饰符列表] 构造方法名(形式参数列表){
    构造方法体;
}
1
2
3
  • 构造方法也叫实例方法
  • 构造方法的名称必需和类名相同
  • 构造方法没有返回值,也不能写 void
  • 一个类中可以有多个构造方法(也就是方法重载)
  • 构造方法用来创建对象,以及完成属性初始化操作,不允许使用 static 关键字

例如:

public class DateTest {
    public static void main(String[] args) {
        // 创建一个日期对象
        Date d = new Date(); // 调用默认无参构造方法
        // 整数型默认值是0,
        System.out.println(d.year); // 0
        System.out.println(d.month); // 0
        System.out.println(d.day); // 0
        System.out.println("------------------");
        /**
         * 有参构造方法,new 时传入参数,给成员变量添加初始值
         */
        // 调用有参构造方法,传入参数 初始化成员变量
        Date d1 = new Date(2020); //
        System.out.println(d1.year); // 2020
        Date d2 = new Date(2020, 8); //
        System.out.println(d2.year); // 2020
        System.out.println(d2.month);   // 8
        Date d3 = new Date(2020, 8, 16); //
        System.out.println(d3.year); // 2020
        System.out.println(d3.month);  // 8
        System.out.println(d3.day);   // 16

    }
}

class Date {
    int year;
    int month;
    int day;

    /**
     * 构造方法,
     * 多个构造方法形成方法重载
     */
    // 无参构造方法
    public Date() {
        System.out.println("日期对象被创建");
    }

    // 以下都为有参构造方法
    public Date(int y) {
       year = y;
    }

    public Date(int y, int m) {
        year = y;
        month = m;
    }

    public Date(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }
}

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

备注:

  • 如果没有显式的声明构造方法,则编译器会自动添加一个默认的隐式的无参构造方法(默认构造方法)
  • 构造方法可以有多个形成构造方法重载
  • this 关键字用在方法体中,指向为当前对象,也就是哪个对象调用就指向哪个对象
  • 构造方法中的形参变量名称一般与成员变量名称相同有利于代码可读性就必须使用 this.,如果不同可以忽略 this.
  • 当定义构造方法后,java编译器不会在添加默认的构造方法

# 数组

# 引用类型

java 数据类型分类 基本数据类型引用数据类型,除去八种基本数据类型(byte、short、int、long、float、double、boolean、chart)之外,其它都是引用类。

# 基本数据类型和引用类型区别

  • 基本数据类型的变量的值是存储在栈中的
  • 引用类型变量的值的内容是存储在堆中的,而栈中只存具体内容对应的内存地址

# java中的内存

提示

以下内容有些摘抄于网络,参考:
一文让你彻底分清 Java 运行时数据区 (opens new window)

.java 源文件 --> 编译成 .class字节码文件 --> 在JVM上运行 .class字节码文件

java原理图

Java程序运行在JVM(Java Virtual Machine,Java虚拟机)上,java内存分配也是在JVM虚拟机上进行的。

JVM 在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域,简称JVM 运行时数据区。这些区域都有各自的用途。以及创建和销毁的时间,有的区域随着虚拟机进程的启动就存在了,而有些区域则依赖用户线程的启动和结束而建立和销毁。 我们通常把 JVM 运行时数据区叫做 Java 内存模型(JMM:Java Memory Model),即 java 内存。

Java内存主要分为五块区域:

  1. 方法区(Method Area):
    存储.class相关信息,包含方法的信息,代码片段

  2. 栈内存(Stack):
    保存的是一块堆内存的空间地址,每一块栈内存只能够保留一块堆内存地址。方法的运行一定要在栈当中运行

  3. 堆内存(Heap):
    保存每一个对象的属性内容,即对象的真正数据。堆内存中的数据都需要用关键字 new 才可以开辟。堆内存中的每个数据都有一个唯一的16进制地址值(内存地址)

  4. 本地方法栈(Native Method Stack):
    与操作系统相关

  5. 程序计数器(Program Counter Register):
    与CPU相关

java内存划分

备注:
JVM 运行时数据区中有些数据是一直存在的,被所有线程所共享。而有些区域则是线程私有的,伴随着线程的开始而创建,线程的结束而销毁。 所以我们可以把JVM 分为两类:线程共享的、线程私有的。

java程序运行内存分配流程 java程序运行内存分配流程3

备注:

  • 常量池存放所有的常量,包括字符串、数字、布尔值、引用等。
  • 方法区存放所有的方法,包括类的方法、接口的方法、构造方法、静态方法、实例方法等。

# 栈和堆

  • 栈(Stack)

    • 先进后出,后进先出
    • 存取数据速度比堆快
    • 数据共享
    • 栈内存是在编译时就确定的,不能改变
    • 系统自动垃圾回收
  • 堆(Heap)

    • 堆内存中存储的实际对象,是被栈内存中变量所引用
    • 内存大小是动态的,是在运行时动态分配内存空间
    • java中垃圾回收机制会自动回收不用的数据

# 引用类型数组

  • 数组属于引用类型
  • 数组对象在堆中存储,数组变量属于引用类型,存存储在栈中,变量的值是数组对象的地址信息,这个地址信息指向数组对象在堆中的地址
  • 数组的元素看做是对象的成员变量只不过类型全部相同

# 引用类型数组数组

import java.util.Arrays;

public class YyArr {
    public static void main(String[] args) {
        // int 类型数组,长度为3
        int[] intArr = new int[3];
        intArr[0] = 1;
        intArr[1] = 2;
        intArr[2] = 3;
        System.out.println(Arrays.toString(intArr)); // [1, 2, 3]
        int[] intArr2 = intArr;
        System.out.println(Arrays.toString(intArr)); // [1, 2, 3]
        // 修改 intArr2 的值,intArr 也会被修改
        intArr2[0] = 4;
        System.out.println(Arrays.toString(intArr)); // [4, 2, 3]

        System.out.println("--------------------------------------------");
        // String 类型数组,长度为3
        String[] stringArr = new String[3];
        stringArr[0] = "a";
        stringArr[1] = "b";
        stringArr[2] = "c";
        System.out.println(Arrays.toString(stringArr)); // [a, b, c]
        // Cell 类型数组,长度为3
        Cell[] cellArr = new Cell[3];
        cellArr[0] = new Cell(1, 2);
        cellArr[1] = new Cell(3, 4);
        cellArr[2] = new Cell(5, 6);
        System.out.println(Arrays.deepToString(cellArr)); // [Cell{row=1, col=2}, Cell{row=3, col=4}, Cell{row=5, col=6}]
        for (Cell cell : cellArr) {
            System.out.println(cell.row + "," + cell.col);
        }
        Cell c = new Cell();
        System.out.println(c.row);
    }

}

class Cell {
    int row;
    int col;

    public Cell() {
    }

    // 构造方法 传参
    public Cell(int row, int col) {
        this.row = row;
        this.col = col;
    }
}
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

备注:
如果多个变量引用同一个引用类型数据,如上面示例 intArr 和 intArr2 改变其中一个变量的值,则其他变量的值也会被改变,因为实际改变的是公用的内存地址对应的数据的值。