java学习篇(一)—— CPP和Java的区别之基础概念
一些感想
写在开头,如果你是一个坚定选择工作的研究生,在CPP和Java之间反复跳转,那么有以下几种情况,建议你选CPP:
- 对CPP有强大的兴趣,且组内有成熟的方向,例如:高性能计算、音频开发等方向,可以选择。
- 仅仅想找一份工作,不要跟自己过不去,Java很好,尤其是后端。
博主是在模型部署里做了一年,但是由于确实不感兴趣,荒废时间后发现自己的工作上毫无优势,最终无奈学习Java的。
Java有很明确的学习路线:Java基础--->JDBC + 数据库--->Java web---> spring--->SpringCloud + Redis + 中间件巴拉巴拉。
由于博主之前在CPP上有一些学习经验,但是网上的学习路线都是针对于零基础的,这样无疑会增加学习成本,所以想要记录一下整个学习过程,给同样苦苦挣扎的人一些帮助。
在写完这个博客之后,个人认为以下几个方面可以帮助你快速了解Java和cpp的不同:JVM、接口、引用变量类型、Object类、接口与泛型。这几个是最能体现Java面向对象编程思想的。
程序运行的区别
C++的代码想要运行就需要编译,编译过程包括:预处理、编译、汇编、连接。针对不同的操作系统和CPU架构,需要特定的编译器,例如x86和arm的指令不同,那么需要的编译工具链也不同。而Java程序的.java文件通过javac编译成.class字节码文件(不是机器码),字节码由 Java 虚拟机JVM解释或JIT编译为本地机器码运行,不同操作系统和硬件平台,只要安装了对应的 JVM,就能运行这些.class或.jar文件。综上,Java在可移植性上具有强大的优势。
因此,Java也被叫做解释型语言,Java源代码被编译成字节码后,需要解释这些字节码才能在计算机上运行。
JVM带来的优势
cpp想要实现什么功能,都是以调用的形式,例如调用操作系统Socket的接口、操作系统thread的接口、调用自己写的接口,且由于依赖于底层的框架和操作系统,因此cpp针对一些功能实现没有相对应的库,例如实现webserver,就需要自己实现http协议、路由分发、多线程和数据库等等,而这些在Java里只需要对应的模块就可以。根本原因是,Java的可移植性更强,使得它可以做一套统一的接口供不同的平台调用。
面向过程与面向对象
这是两个有点抽象的概念,但观察.cpp和.java的程序,一个显著的差异就是main()函数的位置,在.java程序里,main()只能定义在类里,或者说,所有代码都必须属于类。回忆一下cpp代码,.h是头文件,定义类,成员函数,成员变量等,.cpp是我们想要实现的功能。功能被拆解成了无数个子任务,最终在main()里顺序执行,这就是面向过程。Java 是“纯面向对象语言”(除了基本类型),强调“一切皆类”,所以main也必须属于某个类,即使它是程序入口点。Java将所有的函数定义为了方法(Method),方法的调用必须通过对象或者类来实现,而CPP中则可以通过直接调用。Java的接口机制使得它能够很好的对对象进行封装,Object类使得JVM的任何对象都可以被方法统一调用。
Java代码的执行方式包括两种,分别是:
| 执行方式 | 描述 |
|---|---|
| 解释执行 | 最初由 JVM 中的 解释器 一条条解释字节码指令执行(速度较慢) |
| 即时编译(JIT, Just-In-Time Compilation) | 对“热点代码”(重复调用的方法)进行 动态编译成本地机器码,大幅提升运行速度 |
正是这种执行方式,使得Java可以运行时多态。
Q:一个有main函数的类实例化了另一个有main函数的类,那么调用谁的main?
在.cpp里,一个main函数意味着一个可执行文件,不可能存在可执行文件调用另一个可执行文件,只能依赖操作系统再开一个进程。但是在Java里,却实现了这个,Java 允许多个类都有 public static void main(String[] args) 方法,但是程序执行时只会从你运行的那个类的main方法开始执行,其它的main不会自动调用。目前能想到的优势,大概只有调试比较方便,毕竟在一个封装好的类里加main很奇怪。
变量类型
C++的数据类型包括:基本类型、自定义类型、指针类型、引用类型。你可以指定变量在堆上还是栈上,不使用new的话,默认对象在栈上,而在堆上申请的内存需要手动释放。
在Java里,包括两种变量类型:基本类型和引用类型,除了int这种基本类型外的类、接口、数组、自定义对象(包括 String、List、自定义类等)全是引用类型,只有你new出一个对象才会在堆上分配实际空间。
我们使用代码来说明两者的区别:
Myclass a;
Myclass b = a;
在C++里,上述代码会在栈区创建两个实例,sizeof(a)==sizeof(b)==sizeof(Myclass),也就是说,栈上的就是对象本身,等号进行的是赋值操作(堆操作符=的重写)。但是当Java执行这个语句之后,sizeof(a)==sizeof(b)==4/8字节,可以认为,这只是两个指针,由于没有指向具体的对象实例,所以是空。这里可以看出,Java的引用可以为空,C++不可以。
再看一个代码:
Myclass a = new Myclass();
Myclass *a = new Myclass();
上面的语句是Java在堆上实例化对象,下面语句是C++在堆上实例化对象,a都是储存在栈区的变量,指向堆上的实例。
线程调度的区别?
cpp的线程管理依赖于操作系统的API,而Java由于有自己的虚拟机,所以在操作系统的线程管理上可以再进行一次封装,所以Java通过线程池框架来对线程进行管理。CPP的优势在移动端对性能敏感的业务里更能体现,比如线程绑定核心,安卓移动应用开发使用的是JNI调用底层的C/C++接口。Java线程生命周期有六个状态,在操作系统的线程状态上对等待态进行了划分,分为:阻塞,等待,定时等待。
网络编程
我没有网络编程开发经验。。。。
| 维度 | Java网络编程 | C++网络编程 |
|---|---|---|
| 易用性 | 高(内置库+面向对象封装) | 低(依赖系统API或第三方库) |
| 跨平台 | 统一平台(JVM) | 需适配多平台 |
| 性能 | 较高 | 极高 |
| 内存安全 | 自动管理 | 手动管理,需谨慎 |
| 异步支持 | NIO、AIO | Boost.Asio等异步库 |
| 典型应用 | 企业级应用、服务端 | 高性能服务器、底层系统 |
接口编程 + 多态
cpp的多态通过虚函数实现的,子类重写父类的虚函数来实现多态。而对于Java而言,实现多态有两个机制: 1. 重写父类的成员函数,不需要声明为虚函数或者接口,直接子类前面+@override即可。 2. 直接声明一个接口,而实现它的类必须重写接口。这里需要强调的是,这个接口不是父类,这种实现是重写,而不是继承。接口可以继承,一个接口可以继承另一个接口
父类中的任何函数都可以重写吗?
有限制,以下几种方法不可以重写:
-
private方法:子类无法访问父类的private方法,自然也无法重写,如果你在子类中写了同名方法,其实是一个新的方法,不是重写。 -
final方法:被final修饰的方法不能被重写,编译器会报错。 -
static方法:静态方法属于类,不属于对象,不能被重写,但可以在子类中定义一个同名的静态方法(这叫“隐藏”,不是重写)。
注意!由于Java的垃圾回收机制,所以不需要析构函数!
模板与泛型
这里体现了JVM存在的优势,cpp编译器编译出可执行文件之后,一切按照可执行文件来运行,而Java则是在编译时擦除类型信息,在运行时由JVM根据对象的类型指针来知道这个对象是什么类型的,进行类型强制转化。而泛型擦除的安全,又编译器负责检查,JVM知道当前代码一定是类型安全的,只需要根据你传入的对象类型,判断实际的类型就可以了。
泛型
Java 编译器在编译时使用泛型进行类型检查和类型推导,但在编译完成后,所有的泛型类型信息都会被“擦除”。例如:
List<String> list = new ArrayList<>();
list.add("hello");
// 编译后的字节码中其实是:
List list = new ArrayList();
list.add("hello"); // 没有泛型类型
泛型类型参数会被擦除为其上界(默认为 Object),如果你写了 Number,所有泛型方法、类只会生成一份.class文件。
JVM并不需要知道泛型的类型,它只当作是Object或擦除后的上界类型来处理,依靠的是多态机制(方法分发)和强制类型转换。
模板
C++ 在模板使用时,每用一个类型实例化模板,就生成一个新的代码版本。按需实例化:遇到新类型就生成新函数/类,每个模板参数类型对应一个完整实现,生成的是普通函数/类,与泛型无区别。可以认为是,编译器帮你做了重复性的代码工作,但是在编译时,他们就是不同的类型。

浙公网安备 33010602011771号