1 Java可变长参数
1.1 概述
Java 的可变长参数(Varargs)是在 Java 1.5 中引入的功能,允许方法接受任意数量的相同类型的参数。可变参数的语法是在参数类型后面加上三个点(...
),例如 int... numbers
。
1.2 使用规则
- 可变参数必须放在参数列表的最后一位:如果方法有多个参数,可变参数必须放在最后。例如:
public void printNumbers(String prefix, int... numbers) { // 方法体 }
- 只能有一个可变参数:一个方法中只能有一个可变参数。
1.3 原理
当使用可变参数时,Java 会在编译时创建一个数组,数组的大小就是传入的可变参数的数量。然后将这些参数放入数组中,并将数组传递给方法。
例如:
public void printNumbers(int... numbers) {
for (int number : numbers) {
System.out.println(number);
}
}
调用 printNumbers(1, 2, 3)
时,Java 会创建一个 int[]
数组 {1, 2, 3}
,然后传递给方法。
1.4 使用场景
可变参数通常用于需要处理任意数量相同类型对象的场景。例如:
- 打印任意数量的整数
- 计算任意数量的数字的和
- 处理任意数量的字符串
1.5 注意事项
-
避免重载带有可变参数的方法:重载带有可变参数的方法可能会导致编译器无法确定调用哪个方法,从而引发编译错误。例如:
public void print(String... args) { // 方法体 } public void print(String arg1, String... args) { // 方法体 }
调用
print("a")
时,编译器无法确定是调用第一个方法还是第二个方法。 -
明确指示:如果必须重载带有可变参数的方法,确保在调用时明确指示参数,避免编译器混淆。
1.6 示例代码
public class VarargsExample {
public static void main(String[] args) {
printNumbers(1, 2, 3);
printNumbers(4, 5);
printNumbers();
}
public static void printNumbers(int... numbers) {
for (int number : numbers) {
System.out.println(number);
}
}
}
2 Java native方法
2.1 概念
本地方法(Native Method)是用 native
关键字修饰的方法,通常不需要用 Java 语言实现。本地方法允许 Java 代码调用其他语言(如 C/C++)编写的代码。
2.2 JNI:Java Native Interface
JNI(Java Native Interface)是 Java 平台的一部分,从 Java 1.1 开始引入,允许 Java 代码与其他语言编写的代码进行交互。JNI 主要用于以下场景:
- 标准 Java 类库不支持的功能
- 调用已有的 C/C++ 库
- 提高性能,特别是在需要接近硬件或运行次数特别多的方法中
2.2.1 JNI 的优点
- 扩展 Java 功能:可以通过 JNI 调用其他语言编写的库。
- 性能优化:在某些情况下,使用本地代码可以提高性能。
2.2.2 JNI 的缺点
- 跨平台性丧失:本地代码通常不跨平台,需要在不同系统环境下重新编译。
- 安全性降低:本地代码的不当使用可能导致程序崩溃。
2.3 用 C 语言编写本地方法
2.3.1 步骤
- 编写带有 native 方法的 Java 类,生成
.java
文件。 - 编译 Java 类,生成
.class
文件。 - 生成头文件:使用
javah -jni
或javac -h
生成.h
文件。 - 实现本地方法:使用 C/C++ 实现
.h
文件中的方法,生成.c
或.cpp
文件。 - 生成动态链接库:将 C/C++ 编写的文件生成动态链接库(如
.dll
或.so
)。
2.3.2 示例:HelloWorld 程序
-
编写 Java 类:
public class HelloJNI { static { System.loadLibrary("hello"); // 加载动态链接库 } public native void sayHello(); // 声明本地方法 public static void main(String[] args) { new HelloJNI().sayHello(); // 调用本地方法 } }
-
编译 Java 类:
javac HelloJNI.java
-
生成头文件:
javac -h . HelloJNI.java
这将生成
HelloJNI.h
文件。 -
实现本地方法:
#include <jni.h> #include "HelloJNI.h" #include <stdio.h> JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject obj) { printf("Hello, JNI!\n"); }
-
编写编译脚本:
# compile.sh gcc -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/darwin" -dynamiclib -o libhello.dylib HelloJNI.c
-
执行脚本:
sh compile.sh
-
运行 Java 程序:
java HelloJNI
2.4 JNI 调用 C 的流程图
Java 代码 -> JNI 接口 -> 本地代码 (C/C++)
2.5 native 关键字
-
native 用于修饰方法,表示该方法的实现在外部定义,通常用 C/C++ 实现。
-
语法:
- 修饰方法的位置必须在返回类型之前。
- 不能用
abstract
修饰,没有方法体。 - 返回值可以是任意类型。
-
native 方法示例
public native void sayHello();
2.6 小结
- JNI 允许 Java 代码与其他语言编写的代码进行交互,扩展了 Java 的功能。
- 本地方法 用
native
关键字修饰,通常用于调用 C/C++ 库或提高性能。 - 注意事项:使用 JNI 会丧失跨平台性,且本地代码的不当使用可能导致程序崩溃。
3 Java构造方法
3.1 概念
构造方法(Constructor)是 Java 中的一种特殊方法,用于在创建对象时初始化对象的状态。每次使用 new
关键字创建对象时,构造方法至少会被调用一次。如果没有显式定义构造方法,编译器会提供一个默认的无参构造方法。
3.2 创建构造方法的规则
- 名称与类名相同:构造方法的名称必须与类名完全一致。
- 无返回类型:构造方法没有返回类型,包括
void
。 - 不能是抽象的、静态的、最终的、同步的:
- 抽象:构造方法不能被子类继承,因此用
abstract
修饰没有意义。 - 静态:构造方法用于初始化对象,因此用
static
修饰没有意义。 - 最终:构造方法不能被子类继承,因此用
final
修饰没有意义。 - 同步:多个线程不会同时创建内存地址相同的同一个对象,因此用
synchronized
修饰没有必要。
- 抽象:构造方法不能被子类继承,因此用
3.3 语法格式
class class_name {
public class_name(){} // 默认无参构造方法
public ciass_name([paramList]){} // 定义有参数列表的构造方法
…
// 类主体
}
3.4 默认构造方法
- 无参构造方法:如果构造方法没有任何参数,它就是一个无参构造方法。
- 默认构造方法:如果类中没有显式定义构造方法,编译器会自动提供一个无参构造方法。
- 目的:主要为对象的字段提供默认值。
- 代码示例
public class DefaultConstructorExample {
private int value;
// 编译器提供的默认构造方法
public DefaultConstructorExample() {
this.value = 0; // 默认值
}
public static void main(String[] args) {
DefaultConstructorExample obj = new DefaultConstructorExample();
System.out.println(obj.value); // 输出: 0
}
}
3.5 有参构造方法
- 有参数的构造方法:可以有一个或多个参数,用于为不同的对象提供不同的初始值。
- 替代方案:如果没有有参构造方法,可以通过
setter
方法为字段赋值。 - 代码示例
public class ParameterizedConstructorExample {
private String name;
public ParameterizedConstructorExample(String name) {
this.name = name;
}
public static void main(String[] args) {
ParameterizedConstructorExample obj = new ParameterizedConstructorExample("Java");
System.out.println(obj.name); // 输出: Java
}
}
3.6 重载构造方法
- 构造方法重载:通过提供不同的参数列表来重载构造方法。编译器会根据参数的数量和类型来决定调用哪一个构造方法。
- 代码示例
public class OverloadedConstructorExample {
private int id;
private String name;
public OverloadedConstructorExample(int id) {
this.id = id;
}
public OverloadedConstructorExample(int id, String name) {
this.id = id;
this.name = name;
}
public static void main(String[] args) {
OverloadedConstructorExample obj1 = new OverloadedConstructorExample(1);
OverloadedConstructorExample obj2 = new OverloadedConstructorExample(2, "Java");
System.out.println(obj1.id); // 输出: 1
System.out.println(obj2.id + " " + obj2.name); // 输出: 2 Java
}
}
3.7 构造方法和方法的区别
特性 | 方法 | 构造方法 |
---|---|---|
目的 | 反映对象的行为 | 初始化对象的字段 |
返回类型 | 可以有返回类型 | 没有返回类型 |
调用方式 | 明确的,开发者通过代码决定调用 | 隐式的,通过编译器完成 |
编译器提供 | 不会由编译器提供 | 如果没有明确提供无参构造方法,编译器会提供 |
名称 | 可以和类名相同,也可以不同 | 必须和类名相同 |
3.8 复制对象
复制一个对象可以通过以下三种方式完成:
- 通过构造方法:使用另一个对象作为参数来创建新对象。
- 通过对象的值:手动复制对象的每个字段。
- 通过
Object
类的clone()
方法:实现Cloneable
接口并重写clone()
方法。
- 通过构造方法
public class CopyConstrutorPerson {
private String name;
private int age;
public CopyConstrutorPerson(String name, int age) {
this.name = name;
this.age = age;
}
public CopyConstrutorPerson(CopyConstrutorPerson person) {
this.name = person.name;
this.age = person.age;
}
public void out() {
System.out.println("姓名 " + name + " 年龄 " + age);
}
public static void main(String[] args) {
CopyConstrutorPerson p1 = new CopyConstrutorPerson("沉默王二",18);
p1.out();
CopyConstrutorPerson p2 = new CopyConstrutorPerson(p1);
p2.out();
}
}
在上面的例子中,有一个参数为 CopyConstrutorPerson 的构造方法,可以把该参数的字段直接复制到新的对象中,这样的话,就可以在 new 关键字创建新对象的时候把之前的 p1 对象传递过去。
- 通过对象的值
public class CopyValuePerson {
private String name;
private int age;
public CopyValuePerson(String name, int age) {
this.name = name;
this.age = age;
}
public CopyValuePerson() {
}
public void out() {
System.out.println("姓名 " + name + " 年龄 " + age);
}
public static void main(String[] args) {
CopyValuePerson p1 = new CopyValuePerson("沉默王二",18);
p1.out();
CopyValuePerson p2 = new CopyValuePerson();
p2.name = p1.name;
p2.age = p1.age;
p2.out();
}
}
直接拿 p1 的字段值复制给 p2 对象(p2.name = p1.name)
- 通过
Object
类的clone()
方法
public class ClonePerson implements Cloneable {
private String name;
private int age;
public ClonePerson(String name, int age) {
this.name = name;
this.age = age;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public void out() {
System.out.println("姓名 " + name + " 年龄 " + age);
}
public static void main(String[] args) throws CloneNotSupportedException {
ClonePerson p1 = new ClonePerson("沉默王二",18);
p1.out();
ClonePerson p2 = (ClonePerson) p1.clone();
p2.out();
}
}
通过 clone()
方法复制对象的时候,ClonePerson
必须先实现 Cloneable
接口的 clone()
方法,然后再调用clone()
方法(ClonePerson p2 = (ClonePerson) p1.clone())
。
3.9 小结
- 构造方法虽然没有返回值,但返回的是类的对象。
- 初始化字段只是构造方法的一种工作,它还可以做更多,比如启动线程、调用其他方法等。
4 思维导图
参考链接
-
Java可变参数详解,5分钟教会我妹
-
手把手教你用 C语言实现 Java native 本地方法
-
Java构造方法:打开Java对象创建之门的钥匙