☰
Java和C++通过new创建的对象有何区别?
2026/10/26 3:21:42 网站建设 项目流程

前言


Java 和 C++ 都用new创建对象,语法看起来差不多,但背后的内存模型、生命周期和类型系统差别很大。把 Java 的习惯直接搬到 C++,最常见的后果是忘记释放内存;把 C++ 的习惯搬到 Java,则会误以为能控制对象何时销毁。


需要先澄清一个说法:Java 的new返回的是一个引用(reference),对象本身由 JVM 管理,通常分配在堆上;C++ 的new返回指针,指向一块由程序员负责释放的堆内存。两者都不是"返回值本身"。


另一个常见误解是"Java 有析构函数"。Java 没有 C++ 意义上的析构函数,finalize早已被弃用,且不保证执行;资源清理要靠try-with-resources、AutoCloseable或java.lang.ref.Cleaner。本文按内存、生命周期、引用语义、初始化顺序四条线对比。


一、对象放在哪里


Java 的对象由 JVM 分配,语言层面不规定必须在堆上;主流的 HotSpot 实现通常在堆上分配,逃逸分析等优化可能把某些对象拆解或分配到栈上,但这是实现细节,不是语言保证。


C++ 的对象可以放在三个地方:



  • 栈上:User u("alice");,作用域结束自动析构;

  • 堆上:new User("alice"),返回指针,需要delete;

  • 静态存储区:全局对象、static对象。


new只是堆分配的一种方式,现代 C++ 更推荐用std::make_unique/std::make_shared把裸new包起来。


// 适用于 Java 17
public class Demo {
static class User {
private final String name;

User(String name) {
this.name = name;
}
}

public static void main(String[] args) {
User u = new User("alice");
User v = u; // 复制引用,不是复制对象
System.out.println(v == u); // true,两个引用指向同一对象
}
}

// 适用于 C++17
#include <iostream>
#include <memory>
#include <string>

class User {
public:
explicit User(std::string name) : m_name(std::move(name)) {}

private:
std::string m_name;
};

int main()
{
auto u = std::make_unique<User>("alice");
User *v = u.get(); // 观察指针,不拥有
std::cout << (v == u.get()) << "\n"; // 1

User w("bob"); // 栈对象,离开作用域自动析构
return 0;
}

二、生命周期与释放


Java 的对象由垃圾回收器(garbage collector, GC)回收。只要还有可达的引用,对象就不会被回收;没有任何引用时,GC 会在某个不确定的时刻回收它。程序员不能指定回收时间,也不能强制立即回收。


C++ 的堆对象不会自动回收。new必须配对delete,new[]必须配对delete[]。忘记释放就是内存泄漏;重复释放或释放后使用是 UB。现代做法是用 RAII:std::unique_ptr独占所有权,std::shared_ptr共享所有权,容器和std::string自己管理内部内存。


// 适用于 C++17
#include <memory>
#include <string>

void scoped()
{
std::unique_ptr<std::string> p = std::make_unique<std::string>("hi");
// 离开作用域,p 析构,字符串自动释放
}

std::shared_ptr用引用计数决定何时释放,但计数本身是原子操作,且循环引用会导致对象永远不释放,需要用std::weak_ptr打破环。这一点和 Java 的 GC 不同:Java 能处理循环引用,C++ 的引用计数不能。


三、引用语义


Java 的引用可以为null,可以重新绑定到另一个对象,不能做指针算术。方法传参是值传递,传的是引用的副本,所以在方法里让参数指向新对象,不会影响调用方:


// 适用于 Java 17
static void reassign(User u) {
u = new User("bob"); // 只改了局部引用
}

C++ 的引用是别名,必须在初始化时绑定,之后不能改绑,也不能为 null(合法程序中)。指针可以为空、可以改指向、可以做算术,但需要自己保证有效性。


C++ 按值传参会发生拷贝,除非用引用或指针。多态传参通常按const Base&或Base*,按值传会发生对象切片。


四、初始化顺序


Java 创建对象的顺序大致是:分配内存并零初始化字段;调用父类构造;按声明顺序执行字段初始化器和实例初始化块;执行构造体。


C++ 的顺序是:分配内存;按基类声明顺序调用基类构造;按成员声明顺序调用成员构造;执行构造体。成员初始化列表的顺序不决定实际顺序,真正决定顺序的是成员在类中的声明顺序。


这个差异会导致一个经典错误:C++ 的初始化列表里写了m_b(b),但m_b声明在m_a之前,实际先用未初始化的m_a去初始化m_b。编译器通常会警告,但不会阻止。


五、对象比较


Java 的==比较引用身份,内容相等要用equals。C++ 的==可以被重载,对类类型通常比较内容;比较指针才是比较地址。


Java 的String是类,==比较引用,必须用equals。C++ 的std::string重载了==,直接比较内容。这是两种语言习惯差异最明显的地方之一。


六、对照表




维度JavaC++



new返回对象引用对象指针

对象位置由 JVM 决定,通常堆上栈、堆、静态区可选

释放GC 自动回收手动delete或 RAII

析构无(finalize已弃用)确定性析构函数

引用能否为空能引用不能,指针能

引用能否改绑能引用不能

对象比较==比身份,equals比内容==可重载

循环引用GC 可处理shared_ptr会泄漏,需weak_ptr



七、异常路径上的资源释放


C++ 的析构函数在栈展开(stack unwinding)时自动调用,所以 RAII 对象即使在中途抛异常也能释放资源,这是 C++ 资源管理的基石:


// 适用于 C++17
#include <fstream>
#include <stdexcept>

void writeFile()
{
std::ofstream out("data.txt"); // 构造即打开
if (!out) {
throw std::runtime_error("open failed");
}
out << "hello\n";
// 无论正常返回还是抛异常,out 析构时都会关闭文件
}

Java 没有确定性析构,资源清理要显式写。try-with-resources会在离开代码块时调用close(),即使抛出异常也会调用:


// 适用于 Java 17
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

void writeFile() throws IOException {
try (BufferedWriter out = new BufferedWriter(new FileWriter("data.txt"))) {
out.write("hello");
}
}

两者的效果类似,但机制不同:C++ 靠对象生命周期,Java 靠语言层面的try结构。因此 C++ 的 RAII 可以用于任意资源(锁、socket、临时状态),Java 的try-with-resources要求资源实现AutoCloseable。


常见坑点



  1. ❌ 把 Java 的引用当成 C++ 的引用,以为可以为空。


✅ C++ 引用必须绑定有效对象;可空语义用指针或std::optional。



  1. ❌ 在 C++ 里new之后不释放,指望有 GC。


✅ 用std::unique_ptr/std::shared_ptr,或把对象放栈上。



  1. ❌ 认为 Java 对象一定在堆上。


✅ 语言不保证;JVM 的具体优化由实现决定。



  1. ❌ Java 里用==比较字符串内容。


✅ 用equals;==比较的是引用身份。



  1. ❌ 认为 Java 的finalize会可靠执行。


✅ 它已弃用且不保证时机;用try-with-resources或Cleaner。



  1. ❌ C++ 返回局部对象的指针或引用。


✅ 返回对象值(依赖拷贝省略/移动),或返回智能指针。



  1. ❌ Java 方法里给参数重新赋值,以为能改变调用方的引用。


✅ 传的是引用的副本;要修改对象内容,调用对象的方法。



  1. ❌ C++ 通过基类指针delete派生对象,而基类析构不是virtual。


✅ 多态基类析构必须virtual,否则是 UB。


总结




问题Java 的答案C++ 的答案



谁分配JVM程序员选择栈或堆

谁释放GC析构函数 / 智能指针

何时释放不确定确定性,作用域或引用计数归零

能否控制不能能

主要风险内存占用、GC 停顿泄漏、悬垂、重复释放



两种语言的new只是拼写相同。Java 把内存管理的责任交给运行时,换来的是不可控的回收时机;C++ 把控制权交给程序员,换来的是确定性,也换来了必须自己保证正确性的负担。理解这一点,就不会把一边的习惯生搬到另一边。




需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询