Java 是一门面向对象的编程语言。
从 POP 和 OOP 的区别,理解 OOP。
- 面向过程:POP(Procedure Oriented Programming)
- 侧重点:需要做什么。
- 实现:把问题模型分解成一个个执行步骤。
- 面向对象:OOP(Object Oriented Programming)
- 侧重点:涉及什么对象,对象有什么属性和行为。
- 实现:定义对象,把现实世界映射到计算机模型。
面向对象是一种更优秀的程序设计方法,基本思想是使用类、对象、继承、封装、消息等基本概念进行程序设计。
更符合现实世界,将事物抽象为系统中的类,作为系统的一部分,也可以保持事物之间的关系。
1、类 & 对象
1.1、语法
类(class):抽象模板,定义了对象的元数据(属性和方法)。
本身是一种(引用)数据类型。
-
定义类:通过关键字
class
定义,指定类名。class 类名 { }
-
注意:一个 Java 源文件可以定义多个类,但最多定义一个 public 类。
- 若定义了 public 类,源文件名需要与 public 类名一致。
- 若无定义 public 类,源文件名任意。
-
示例:
class Person { public String name; public int age; }
实例(instance):类的具体实现,是程序的基本运行单位。
对象
和实例
的概念通用。
-
对象实例化:通过关键字
new
创建实例,定义相应类型的变量并指向该实例。类名 实例名 = new 类名();
-
注意:
- 若无特殊处理,对象实例化后的属性均为默认值。
- 一个类可以创建多个实例,不同实例拥有独立的内存空间。
-
示例:
Person p1 = new Person(); Person p2 = new Person();
┌─────────┐ p1 ──────>│ Person │ ├─────────┤ │name = ""│ │age = 0 │ └─────────┘ ┌─────────┐ p2 ──────>│ Person │ ├─────────┤ │name = ""│ │age = 0 │ └─────────┘
1.2、说明
1.2.1、命名规范
Java 中涉及命名的地方,需要遵循规范。
- 硬性要求:以英文字母开头,字母、数字、下划线组合。
- 规范:多个英文字母采用驼峰命名法。
1.2.2、注释
注释(comment):标注代码,提高代码可读性。
-
单行注释:
// 打印输出 System.out.println("Hello World!");
-
多行注释:
public void doSomething() { /* 以下代码用于xxxxx功能。 1. 2. 3. */ }
-
文档注释:用于类和方法的定义处,可用于自动生成文档(JavaDoc)。
/** * 文档注释 * * @author Jaywee * @date 2022/12/12 */ public class Hello { }
2、属性
属性/字段(field):描述类的特征。
在类内部定义,一个类可以定义多个属性。
-
定义属性:通常定义在类内部的最上方。
class xxx { 权限修饰符 数据类型 属性名; }
-
访问:可在类内部或外部访问。
-
内部:
this
指向当前对象实例。- 若无命名冲突可省略
this.
。 - 若局部变量与属性同名,需要添加
this.
区分。
- 若无命名冲突可省略
-
外部:需要拥有相应访问权限,取决于属性的权限修饰符。
// 内部 this.属性名; // 外部 实例名.属性名;
-
-
示例:由 public 修饰的属性,外部可直接访问。
// 写:赋值 p1.name = "Secret"; p1.age = 18; // 读 System.out.println(p1.name + ":" + p1.age);
3、方法
方法(method):一组执行语句,描述类的行为。
在类内部定义,一个类可以定义多个方法。
3.1、语法
-
定义方法:
class xxx { 权限修饰符 返回值类型 方法名(参数列表) { // 执行语句 // return 返回值; } }
-
访问:可在类内部或外部访问。
-
内部:
this
表示当前对象实例。 -
外部:访问时需要拥有相应权限,取决于方法或属性的权限修饰符。
// 内部 this.方法名(参数); // 外部 实例名.属性名; 实例名.方法名(参数);
-
-
示例:属性由 private 修饰,定义方法暴露给外部使用。
class Person { public static void main(String[] args) { Person p = new Person(); p.setName("Mike"); p.setAge(200); // 抛异常,超出了0-100范围 p.setAge(10); } private String name; private int age; public String getName() { return this.name; } public void setName(String name) { this.name = name; } public int getAge() { return this.age; } public void setAge(int age) { // 合法性检查 if (age < 0 || age > 100) { throw new IllegalArgumentException("invalid age value"); } this.age = age; } }
3.2、方法返回
方法内遇到
return
关键字结束并返回。
- 返回值:根据方法是否需要返回值,分为两种情况。
- 是:方法内最后一行使用
return
,返回相应类型的变量。 - 否:即返回值类型为
void
,此时可省略return
语句。
- 是:方法内最后一行使用
- 结束方法:无论是否需要返回值,通常可结合
if
,在特定条件下return;
提前结束方法。
3.3、方法参数
3.3.1、参数列表
参数列表可以包含任意个参数(包括 0)。
-
参数类型:
形参 实参 含义 方法定义的参数列表 调用方法时传入的参数 声明位置 方法签名 方法调用前的任意位置 内存单位 方法被调用时分配,调用结束后释放 变量声明时分配,与是否调用方法无关 作用域 方法内部 取决于参数定义的位置 -
可变参数:使用
类型...
定义,效果等同于类型[]
,但有所区别。-
可变参数:参数为
null
时,接收的实际值是一个空数组。 -
数组:参数为
null
时,接收的实际值是 null,容易导致NPE
。class Demo { public static void main(String[] args) { Demo demo = new Demo(); // 结果:arr1 == null,arr2 == [] demo.doSomething(null, null); } public void doSomething(int[] arr1, int... arr2) { // ... } }
-
3.3.2、参数绑定
Java 中只有值传递,没有引用传递。
- 基本类型:传递参数值的副本值,二者互不影响(二者指原参数和副本)。
- 引用类型:传递参数值的堆地址副本,二者指向同一个对象实例。
① JVM 相关知识
- 栈帧:每个方法对应栈内存的一个独立栈帧,方法中定义的局部变量均存放在栈帧中。
- 基本类型:通常直接存放变量值,也有例外。
- 引用类型:存放变量在堆内存中实例的地址。
- 方法之间传递参数时,传递的是各自栈帧中存储的参数值的副本。
② 值传递 & 引用传递
-
值传递:副本拷贝。
-
方法调用时,实参复制一份副本,传递到方法的形参(此过程即参数绑定)。
-
在方法内对参数的任何操作,都是对栈帧中参数副本的操作,不影响原参数的值。
class Demo { public static void main(String[] args) { int n = 10; Person p = new Person(); // 调用方法 method1(n, p); } public void method(int num, Person person) { // 不影响原实参,如下图所示 num = 0; person = new Person(); } }
-
-
引用传递:直接指向。
- 方法调用时,将实参的地址直接传递给方法的形参(而非副本)。
- 个人理解:
- 值传递:传递地址副本,两个方法各自拥有一个变量,二者指向堆内存的同一地址。
- 引用传递:共享同一个变量。
3.4、方法重载
重载(overload):在一个类中允许定义多个同名方法,要求具有不同参数类型或个数。
- 使用场景:功能类似,但参数有所不同。
- 调用重载方法时,Java 编译器检查调用方法的参数类型和个数,自动选择恰当的方法。
示例:
String
类提供了多个indexOf()
重载方法。查找字符串中特定字符的索引。
int indexOf(int ch)
:根据字符 Unicode 码查找。int indexOf(String str)
:根据字符串查找。int indexOf(int ch, int fromIndex)
:根据字符 Unicode 码查找,指定起始搜索位置。int indexOf(String str, int fromIndex)
:根据字符串查找,指定起始搜索位置。
3.5、构造方法
构造方法(Construction method):也称构造器。
创建一个对象时,自动调用相应参数的构造方法完成实例化。
- 作用:完成对象初始化。
- 特点:
- 构造方法与类同名,没有返回值,可以重载。
- 任何类都有构造方法,无论是否有显式定义。
- 构造方法之间可以相互调用,通过
this(参数列表);
。
3.5.1、默认构造方法
默认构造方法:没有参数、没有执行语句。
-
无显式定义:编译器会自动生成一个默认构造方法。
// 编译前 class Person { } // 编译后等价于 class Person { // 自动生成 public Person() { } }
-
显式定义:
-
若显式定义了构造方法,编译器不会生成默认构造方法。
class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } }
-
如需同时使用无参和有参构造方法,需要同时定义。
class Person { private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } }
-
3.5.2、变量默认值
Java 中任何变量使用前必须先设置初值。
① 初始化类型
-
无显式赋值:编译器会为其进行隐性赋值,为相应数据类型的默认值。
class Person { private String name; // null private int age; // 0 }
-
声明时赋值:
class Person { private String name = "default"; private int age = 18; }
-
构造器赋值:
class Person { private String name; private int age; public Person() { name = "default"; age = 18; } }
② 初始化顺序
赋值顺序:Java 按以下顺序进行初始化,后赋值的会覆盖先赋值的。
- 默认值:为属性赋相应类型的默认值。
- 初始值:赋值为声明时的初始值。
- 构造方法:根据构造方法的代码,对属性进行赋值。
示例:实例化 Person,思考最终结果是什么。
-
默认值:name 为 null,age 为 0。
-
初始值:name 为 Jerry,age 为 10。
-
构造方法:name 为 Tom,age 为 20(最终结果)。
class Person { public static void main(String[] args) { Person p = new Person("Tom", 20); } private String name = "Jerry"; private int age = 10; public Person(String name, String age) { this.name = name; this.age = age; } }