Java基础:面向对象编程3

1 Java可变长参数

1.1 概述

Java 的可变长参数(Varargs)是在 Java 1.5 中引入的功能,允许方法接受任意数量的相同类型的参数。可变参数的语法是在参数类型后面加上三个点(...),例如 int... numbers

1.2 使用规则

  • 可变参数必须放在参数列表的最后一位 :如果方法有多个参数,可变参数必须放在最后。例如:

    java 复制代码
    public void printNumbers(String prefix, int... numbers) {
        // 方法体
    }
  • 只能有一个可变参数:一个方法中只能有一个可变参数。

1.3 原理

当使用可变参数时,Java 会在编译时创建一个数组,数组的大小就是传入的可变参数的数量。然后将这些参数放入数组中,并将数组传递给方法。

例如:

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 注意事项

  • 避免重载带有可变参数的方法:重载带有可变参数的方法可能会导致编译器无法确定调用哪个方法,从而引发编译错误。例如:

    java 复制代码
    public void print(String... args) {
        // 方法体
    }
    
    public void print(String arg1, String... args) {
        // 方法体
    }

    调用 print("a") 时,编译器无法确定是调用第一个方法还是第二个方法。

  • 明确指示:如果必须重载带有可变参数的方法,确保在调用时明确指示参数,避免编译器混淆。

1.6 示例代码

java 复制代码
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 步骤

  1. 编写带有 native 方法的 Java 类 ,生成 .java 文件。
  2. 编译 Java 类 ,生成 .class 文件。
  3. 生成头文件 :使用 javah -jnijavac -h 生成 .h 文件。
  4. 实现本地方法 :使用 C/C++ 实现 .h 文件中的方法,生成 .c.cpp 文件。
  5. 生成动态链接库 :将 C/C++ 编写的文件生成动态链接库(如 .dll.so)。

2.3.2 示例:HelloWorld 程序

  1. 编写 Java 类

    java 复制代码
    public class HelloJNI {
        static {
            System.loadLibrary("hello"); // 加载动态链接库
        }
    
        public native void sayHello(); // 声明本地方法
    
        public static void main(String[] args) {
            new HelloJNI().sayHello(); // 调用本地方法
        }
    }
  2. 编译 Java 类

    sh 复制代码
    javac HelloJNI.java
  3. 生成头文件

    sh 复制代码
    javac -h . HelloJNI.java

    这将生成 HelloJNI.h 文件。

  4. 实现本地方法

    c 复制代码
    #include <jni.h>
    #include "HelloJNI.h"
    #include <stdio.h>
    
    JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject obj) {
        printf("Hello, JNI!\n");
    }
  5. 编写编译脚本

    sh 复制代码
    # compile.sh
    gcc -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/darwin" -dynamiclib -o libhello.dylib HelloJNI.c
  6. 执行脚本

    sh 复制代码
    sh compile.sh
  7. 运行 Java 程序

    sh 复制代码
    java HelloJNI

2.4 JNI 调用 C 的流程图

plaintext 复制代码
Java 代码 -> JNI 接口 -> 本地代码 (C/C++)

2.5 native 关键字

  • native 用于修饰方法,表示该方法的实现在外部定义,通常用 C/C++ 实现。

  • 语法

    • 修饰方法的位置必须在返回类型之前。
    • 不能用 abstract 修饰,没有方法体。
    • 返回值可以是任意类型。
  • native 方法示例

java 复制代码
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 语法格式

java 复制代码
class class_name {
    public class_name(){}    // 默认无参构造方法
    public ciass_name([paramList]){}    // 定义有参数列表的构造方法
    ...
    // 类主体
}

3.4 默认构造方法

  • 无参构造方法:如果构造方法没有任何参数,它就是一个无参构造方法。
  • 默认构造方法:如果类中没有显式定义构造方法,编译器会自动提供一个无参构造方法。
  • 目的:主要为对象的字段提供默认值。
  • 代码示例
java 复制代码
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 方法为字段赋值。
  • 代码示例
java 复制代码
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 重载构造方法

  • 构造方法重载:通过提供不同的参数列表来重载构造方法。编译器会根据参数的数量和类型来决定调用哪一个构造方法。
  • 代码示例
java 复制代码
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 复制对象

复制一个对象可以通过以下三种方式完成:

  1. 通过构造方法:使用另一个对象作为参数来创建新对象。
  2. 通过对象的值:手动复制对象的每个字段。
  3. 通过 Object 类的 clone() 方法 :实现 Cloneable 接口并重写 clone() 方法。
  • 通过构造方法
java 复制代码
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 对象传递过去。

  • 通过对象的值
java 复制代码
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() 方法
java 复制代码
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 思维导图


参考链接

  1. Java可变参数详解,5分钟教会我妹

  2. 手把手教你用 C语言实现 Java native 本地方法

  3. Java构造方法:打开Java对象创建之门的钥匙

相关推荐
Dream_Snowar7 分钟前
速通Python 第三节
开发语言·python
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭22 分钟前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
暮湫38 分钟前
泛型(2)
java
超爱吃士力架43 分钟前
邀请逻辑
java·linux·后端
南宫生1 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
转码的小石1 小时前
12/21java基础
java
高山我梦口香糖1 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
李小白661 小时前
Spring MVC(上)
java·spring·mvc
sanguine__1 小时前
Web APIs学习 (操作DOM BOM)
学习
冷眼看人间恩怨1 小时前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget