# Java 面向对象编程

# Java简介

  • Java 是一种以​​面向对象为核心​​的多范式编程语言
  • Java 是基于 JVM 虚拟机的跨平台语言,一套代码多端部署

Java运行的原理和其他编程语言不同,它基于 JVM 的虚拟机运行:

先将 Java 源代码 (.java文件) 通过解释器 javac 生成字节码 (.class文件),这是一种二进制文件可以在 JVM 中执行,JVM 会把字节码逐行解释为机器指令并在本地机器执行。 所以不论任何操作系统只要有 JVM 就可以运行 Java 程序,从而实现跨平台。

Java 有三个平台,分别为:

  • JavaSE:是Java标准版,提供了基础语言功能和核心类库,用于开发桌面应用程序、小型服务器应用
  • JavaEE:基于JavaSE,用于构建大型、分布式、高并发的企业级应用
  • JavaME:Java精简版,针对嵌入式设备的开发

# Java程序基础

# Java程序基本结构

这是一个最基本的 Java 程序

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello, world!"); //输出 Hello, world!
    }
}

因为 Java 是一门面向对象编程的语言,所以一个程序的基本单位是 class(类),跟在后面的 Hello 是这个类的类名

  • 类名必须以英文字母开头,习惯以大写字母开头,之后可以是英文字母、数字和下划线的组合
  • public 是访问修饰符,被 public 修饰的类、方法、变量可以在任何地方访问
  • public 类可以从不同包中导入使用,而没有 public 的私有类只能在同包内访问
  • public static void main(String[] args) 是程序入口,JVM 在启动时会寻找 main 方法作为程序起点
  • main 方法作为程序入口点,必须是 public,否则 JVM 无法调用该方法启动程序
  • String[] args 是一个字符串数组,参数用于接收运行Java程序时传入的参数

# 注释

单行注释

// 单行注释

多行注释

/* 
多行注释
注释内容
注释结束
*/

# 变量和数据类型

在 Java 中定义一个变量如下:

int x = 1;

该语句定义了一个整型 int 类型的变量,名称为 x,初始值为 1

# 变量的命名规则

  • 可以使用字母、数字、_$ 命名,但变量名不能以数字开头
  • 变量名区分大小写
  • 不能使用 Java 关键字作为变量名
  • 建议命名时遵循驼峰命名法

# 基本数据类型

  • 整数类型:byteshortintlong
  • 浮点数类型:floatdouble
  • 字符类型:char
  • 布尔类型:boolean
数据类型 字节数
byte 1 Byte
short 2 Byte
char 2 Byte
int 4 Byte
float 4 Byte
double 8 Byte
long 8 Byte

# 整数

以下是不同的整数类型所能表达的最大范围:

  • byte: -128 ~ 127
  • short: -32768 ~ 32767
  • int: -2147483648 ~ 2147483647
  • long: -9223372036854775808 ~ 9223372036854775807

为了方便看数字的位数可以用下划线这样标注,当作为整数类型输出时下划线是不会输出的,输出:2000000000

int a = 2_000_000_000;

long 类型的数据结尾需要加 L,如果不加 L 会被当做 int 类型,这说明 int 类型的值是可以赋值给 long 类型的变量的

long b = 9000000000000000000L;

可以将变量的值用十六进制、二进制或其他进制表示,但输出时的值是按照十进制输出的

int x = 0xff0000; // 十六进制表示的16711680
int y = 0b1000000000; // 二进制表示的512

# 浮点数

对于 float 类型的数据,需要加上 f 后缀,输出时 f 不会被输出

float f1 = 3.14f;

用科学计数法表示

float f2 = 3.14e38f; // 科学计数法表示的3.14x10^38

使用 double 类型,值不需要加后缀 f

double d1 = 1.79308;

如果在 double 类型中加了 f 后缀就相当于将 float 值赋给 double 变量时,会发生自动类型提升。 这个过程中可能会出现一些潜在风险,例如

double d1 = 1.79308f;
System.out.println(d1);

执行这段代码输出结果为 1.7930799722671509 而并非是 1.79308,这是因为 1.79308f 是一个 float 类型字面量且转换为二进制时是无限循环小数,所以需要精度截断然后再赋值给 double 类型的变量

控制输出的浮点数位数可以通过格式化输出,%.3f 表示输出到小数点后三位

double value = 1.79308;
System.out.printf("%.3f%n", value); // 输出: 1.793

# 布尔类型

布尔类型 boolean 只有 truefalse 两个值

boolean b1 = true;
boolean b2 = false;

boolean isGreater = 5 > 3; // 计算结果为true

int age = 12;
boolean isAdult = age >= 18; // 计算结果为false

# 字符类型

char 类型使用单引号 ',且仅有一个字符

char a = 'A';

引用类型

以上的这些数据类型 byteshortintlongfloatdoublecharboolean 都是基本类型,除此以外的其他类型都是引用类型

  • 基本类型直接存储值本身,引用类型指向对象的内存地址
  • 基本类型在栈内存中直接分配空间存储值,引用类型把引用对象的地址存在栈内存中,实际对象在堆内存中
  • 基本类型的默认值为0,引用类型的默认值为 null
  • 在基本类型中可以直接使用 == 比较值是否相等,引用类型中 == 只能比较引用地址,equals() 比较对象内容

String 就是最常见的引用类型,用于表示字符串

String s = "hello";

变量 s 只存储一个地址,这个地址会指向字符串 "hello" 在内存中存放的位置

# 常量

定义变量的时候,如果加上 final 修饰符,这个变量就变成了常量,初始化后该常量就不可再次赋值

final double PI = 3.14;

提示

常量名通常全部大写

# var关键字

为了简化局部变量的声明,Java 10 引入的局部变量类型推断关键字 var,编译器会根据赋值语句自动推断出变量的类型

使用限制

  • 只能用于局部变量声明
  • 必须在声明时进行初始化
  • 不能用于成员变量、方法参数或返回类型

例如,这段代码类型的名称长,写起来比较麻烦

StringBuilder sb = new StringBuilder();

可以统统使用 var ,编译器会自动判断变量的类型

var sb = new StringBuilder();

# 变量的作用域

变量的作用域仅限于它声明时所在的代码块(由括号 {} 包围的代码区域)中

# 运算

# 运算优先级

在 Java 的计算表达式中,运算优先级从高到低依次是:

优先级 运算符
1 ()
2 ! ~ ++ --
3 * / %
4 + -
5 << >> >>>
6 &
7 |
8 += -= *= /=

算数运算符

运算符 说明
+ 加法
- 减法
* 乘法
/ 除法
% 取模
++ 自增
-- 自减

关系运算符

运算符 说明
== 等于
!= 不等于
> 大于
< 小于
>= 大于等于
<= 小于等于

逻辑运算符

运算符 说明
&& 逻辑与
|| 逻辑或
! 逻辑非

位运算符

运算符 说明
& 按位与
| 按位或
^ 按位异或
~ 按位取反
<< 左移
>> 右移
>>> 无符号右移

赋值运算符

运算符 说明
= 赋值
+= 加法赋值
-= 减法赋值
*= 乘法赋值
/= 除法赋值
%= 取模赋值

# 三元运算符

condition ? value1 : value2

condition 是运算条件,如果条件为真则返回值 value1 否则返回 value2,例如:

int max = (a > b) ? a : b;

# 类型自动提升与强制转型

类型自动提升:在表达式计算过程中,Java自动将范围较小的数据类型转换为范围较大的数据类型以避免数据丢失

  • byteshortchar 在运算时会自动提升为 int 类型
  • 类型提升优先级顺序:byteshortintlongfloatdouble
  • 如果表达式中有 double,整个表达式提升为 double
  • 如果表达式中有 float 但没有 double,整个表达式提升为 float
  • 如果表达式中有 long 但没有 floatdouble,整个表达式提升为 long

强制转型:是将一个数据类型显式转换为另一个数据类型的操作

一般计算时小范围数会自动提升为大范围的数,如果要将大范围数转换为更小范围的类型就可以使用强制类型转换

使用括号 () 将目标类型括起来,放在要转换的变量或表达式前面

int i = 12345;
short s = (short) i; // 12345

使用强制类型转换具有风险,可能得到错误结果,例如下面的这个例子: int的两个高位字节直接被截掉了,仅保留了低位的两个字节,所以导致结果错误

public class Main {
    public static void main(String[] args) {
        int i1 = 1234567;
        short s1 = (short) i1; // -10617
        System.out.println(s1);
        int i2 = 12345678;
        short s2 = (short) i2; // 24910
        System.out.println(s2);
    }
}

# 浮点数运算误差

浮点数 0.1 在计算机中就无法精确表示,因为十进制的 0.1 换算成二进制是一个无限循环小数,无论使用 float 还是 double 都只能存储一个 0.1 的近似值,所以浮点数运算可能会产生误差

public class Main {
    public static void main(String[] args) {
        double x = 1.0 / 10;
        double y = 1 - 9.0 / 10;
        // 观察x和y是否相等:
        System.out.println(x);
        System.out.println(y);
    }
}

由于浮点数存在运算误差,所以比较两个浮点数是否相等常常会出现错误的结果。可以通过判断两个浮点数之差的绝对值是否小于一个很小的数,如果小于就默认两个浮点数相等

double r = Math.abs(x - y);
if (r < 0.00001) {
    // 可以认为相等
} else {
    // 不相等
}

# 短路运算

&&(逻辑与短路运算符) 当左侧操作数为 false 时,整个表达式必然为 false,右侧操作数不会被计算 只有左侧为 true 时,才会计算右侧操作数

||(逻辑或短路运算符) 当左侧操作数为 true 时,整个表达式必然为 true,右侧操作数不会被计算 只有左侧为 false 时,才会计算右侧操作数

// 非短路运算示例
boolean flag = false;
// 即使flag为false,isTrue()仍会被调用
if (flag & isTrue()) {
    System.out.println("执行");
}

// 短路运算示例
// isTrue()不会被调用,因为flag为false
if (flag && isTrue()) {
    System.out.println("执行");
}

短路运算可以提高程序性能,使用时要注意右侧表达式在程序中的作用

# 字符串连接

在 Java 中可以使用 + 连接任意字符串和其他数据类型

// 字符串连接
public class Main {
    public static void main(String[] args) {
        String s1 = "Hello";
        String s2 = "world";
        String s = s1 + " " + s2 + "!";
        System.out.println(s); // Hello world!
    }
}

# 多行字符串

从 Java 13开始,字符串可以用""" """表示多行字符串

public class Main {
    public static void main(String[] args) {
        String s = """
                   关关雎鸠,
                   在河之洲。
                   窈窕淑女,
                   君子好逑
                   """;
        System.out.println(s);
    }
}

输出结果:

关关雎鸠,
在河之洲。
窈窕淑女,
君子好逑

# 数组

  • 数组所有元素初始化为默认值,整型都是0,浮点型是0.0,布尔型是false
  • 数组一旦创建,大小就不可改变

声明数组

int[] array;

也可以

int array[];

创建数组

array = new int[10]; // 创建长度为10的整型数组

声明并初始化

int[] array = new int[10];
int[] array = {1, 2, 3, 4, 5}; // 直接初始化

# 输入与输出

这是一个简单的输出程序

public class Main {
    public static void main(String[] args) {
        int x = 100; // 定义int类型变量x,并赋予初始值100
        System.out.print(x); // 打印该变量的值
    }
}

与其他的程序设计语言不同,Java是通过 print 方法进行输出的,该方法属于 System 类,定义在 java.lang 包中,但我们写代码时不需要导入它,因为 java.lang 包是 Java 的默认导入包,所有类都会自动导入

# 面向对象编程基础

# 什么是面向对象?

顾名思义,面向对象编程可不是对着男/女朋友面对面编程

这里的面向和对象编程是一种编程范式(或者说是一种编程思想),它将现实世界中的事物抽象为程序中的对象,通过对象之间的交互来解决问题。

假设在 Java 里定义一个类 class,类就像一个模板,而对象就是这个模板下的具体实例,例如每一辆车都会有自己的特征,比如品牌,颜色,速度等,这些特征就是属性,类就是由这些属性所构成的模板

// 定义一个类
class Car {
    // 属性(成员变量)
    String brand;
    String color;
    int speed;
}

如果要在程序中使用这个定义好的类,就需要为该类编写构造方法,用于创建对象时初始化对象的状态

  • 构造方法的名称必须与类名相同
  • 构造方法没有返回类型
  • 一个类可以有多个构造方法(构造方法重载)
// 构造方法
public Car(String brand, String color) {
    this.brand = brand;
    this.color = color;
    this.speed = 0;
}

除了用于创建对象的构造方法,我们还要给创建好的对象设计其他的活动,例如启动汽车,或者显示汽车的当前速度

// 方法(行为)
//给对象定义一个启动的行为
public void start() {
    System.out.println(brand + "汽车启动了");
}
//给对象定义一个显示当前速度的行为
public void accelerate(int increment) {
    speed += increment;
    System.out.println("当前速度: " + speed + " km/h");
}

现在已经设置好了类 Car,创建一个 Car 的对象,对象通过 new 关键字和构造方法来创建

Car myCar = new Car("丰田", "红色");

这段代码中 Car myCar 中的 Car 是类名,表示数据类型 myCar 是变量名,new Car("丰田", "红色") 中的 Car 是构造方法

一个类可以创建多个对象,每个对象都有自己独立的数据

// 同一个类可以创建多个不同的对象
Car car1 = new Car("丰田", "红色");
Car car2 = new Car("本田", "蓝色");
Car car3 = new Car("奔驰", "黑色");

创建好对象后就可以在该对象中使用之前设置的方法,例如在对象 car1 上使用 start() 方法

// 让对象 car1 使用 start() 方法
car1.start();
完整实例
class Car {
    // 属性
    String brand;
    String color;
    int speed;

    // 构造方法
    public Car(String brand, String color) {
        this.brand = brand;
        this.color = color;
        this.speed = 0;
    }

    // 方法
    public void start() {
        System.out.println(brand + "汽车启动了");
    }

    public void accelerate(int increment) {
        speed += increment;
        System.out.println("当前速度: " + speed + " km/h");
    }
}

// 使用示例
public class Main {
    public static void main(String[] args) {
        // 创建对象
        Car myCar = new Car("丰田", "红色");
        myCar.start();
        myCar.accelerate(50);
    }
}

#

包就像一个文件夹,一个包可以包含多个 .java 文件

# 参考资料

廖雪峰的官方网站_Java教程 (opens new window)