这几天在复习Java,就顺便写一下C++和Java的区别,顺便记录一下如何从C++快速转到Java
Java相较于C++的区别
首先Java不用为内存分配考虑了,自带垃圾回收的Java让你再也不用delete内存。
然后Java取消了多继承,但是也可以通过接口实现多继承。
Java没有重载运算符。。。
好吧因为我不是专门学Java的,所以给我最大的感觉就是第一条:不用内存分配😂挺爽的,其他的我感觉没啥区别,都一样用😂。
基础语法
Hello World
首先不打破老规矩,来一份Hello World:
|
|
这里来看一下Java和C++语法上的区别:
-
第一行
package
语句:比起C++,Java可以更加灵活地控制自己的工程代码层级。
package
是包命令,这一行表示我现在的这个HelloWorld.java
文件在com/VisualGMQ
文件夹下。也就是说如果你的工程根目录在~/Documents
下,那么你的这个文件就在~/Documents/com/VisualGMQ/HelloWorld.java
。 -
第二行被注释掉了,但同时展现了两个点:
- 注释:和C++一模一样。以
//
表示行注释,以/** **/
表示块注释 import
语句:由于你可以用package
来打包你的java文件,那么相应的你也可以通过import
来引入文件,相当于C++的#include
。这里引入的是Java标准IO,载入了Java_installation/java/io/
文件夹下所有Java文件。
- 注释:和C++一模一样。以
-
第五行的类声明:
和C++一样类由
class
声明,不同的是类前面可以加上访问修饰符,这里public
表示这个java文件是对所有工程,包括其他工程可见的(包外可见)。一个源文件只能有一个public
类。类最后不需要加分号(结构体,枚举后面都不需要)。需要注意的是:Java的main函数所在的文件名称必须和类名一致。
-
第六行
main
函数的声明:函数的声明也和C++一样(Java里习惯叫函数为方法),只不过每个方法前面都需要加上访问修饰符表示方法的访问权限(不加表示包内访问)。这里的
public
表示其他包可以访问。这里的
String[] args
其实和int argc, char** argv
一样,只不过Java里面用String
表示字符串,数组有length
变量可以得到数组大小,所以没必要加上int argc
,最后就简化成这样了。 -
第七行
System.out.println()
是Java的控制台输出函数,println
是print line
的简写,也有我们熟悉的printf
函数:System.out.printf()
。
为什么main函数要写在类中?🤔
因为Java遵循万事万物皆对象的说法,不允许将方法或者变量(Java习惯称字段)的声明暴露在类外。当我们运行Java的时候,其实是Java虚拟机JVM调用HelloWorld.main()
函数。在Java中,一切都是在类中运作的。
变量的声明
普通变量
普通变量和C++一样,但是要注意一下基本数据类型的名称变化:byte
,short
,int
,double
,long
,float
,boolean
,char
,其中byte
是表示8位字节的数据类型(也是整数类型中的一份子),取值-128~127。
Null还是nullptr?
答案是都不是,Java为了表示空,定义了自己的类型null
,千万别搞混了。
静态变量
使用static
常量
和C++不一样,使用final
:
|
|
数组
数组就比较特殊了,需要使用new
关键字创建:
|
|
这代码简直就像C++中利用指针创建数组:
|
|
new
一般是用来实例化类对象的,可能Java也将数组视为对象了吧。因为数组有length
字符安可以得到数组的大小:
|
|
二维数组也差不多:
|
|
delete呢?
Java中没有delete这种东西,因为根本不需要。
在你使用new
在内存中分配内存之后,Java虚拟机JVM会使用垃圾回收机制,在对象不再需要的时候自动回收内存。妈妈再也不用担心我没有delete了。
Arrays类
Java的java.util.Arrays
类是一个静态类(所有的类成员都是静态的),专门用于对数组操作,比如Arrays.sort()
用于排序,Arrays.fill()
用于向数组里填充元素等。
类的实例化
类的实例化也是使用new:
|
|
和C++不一样的是,如果你调用的是默认构造函数,也得加上括号:
|
|
同样JVM会帮你回收内存,不需要delete
函数(方法)声明
和C++一模一样,只是要注意方法前面的访问修饰符。
传值还是传引用?
由于Java将指针概念隐藏了起来,导致我们没有办法使用指针,那么就带来一个问题:函数参数什么时候传值,什么时候传引用呢?其实和Python差不多:
八种基本数据类型都是传值的;数组,类,接口等其他数据类型是传引用的。
那么这个时候就有人要问了:那我要将基本数据类型传引用岂不是无法?那也不是。Java给每个基本数据类型一个对应的类,称为闭包,只要你使用其相应的类对象传值就可以了。
闭包
每一个基本数据类型对应的类称为闭包,比如int
对应Integer
,char
对应Character
。所有的闭包名称都是基本数据类名称的全称,并且首字母大写:
|
|
每个闭包都可以通过传递基本数据类型来构造,并且也都可以像原本类型一样进行操作:
|
|
但是不能将不兼容类型放入来构造:
|
|
强转的方法
Java的开发者很痛恨C/C++中使用(double)value;
这种强制转换方式,所以在Java中一律去掉了这种转换法则。而且还规定,不同类型的数据之间不能相互转换。
比如boolean
就不能和数类(int
, float
等)转换。而作为最基础的整型提升还保留了下来(就是说int
可以隐式转换为float
这种)。
如果想要转换的话,必须构建对应类型的闭包,使用闭包的方法转换:
|
|
一般转换的方法都是<type>Value()
格式。
条件判断
和C++一模一样,都是短路的,不说了。
循环结构
和C++一模一样,就是for循环多了一种foreach循环(Java8新增):
|
|
其实和C++11的foreach也一样,只不过Java中没有auto关键字,所以你必须显式地写上变量类型。
枚举类型
和C++一样,使用enum
关键字。但是由于Java的关系,没有办和和数型之间相互转换。而且枚举类型本身也属于类,所以在同一个文件中不能同时有public enum和public class。
包
所谓包,其实如果你学过Python的话就非常好理解了,就像是Python里面的包一样,通过文件夹来将源代码分层。
包的路径不使用/
或者\
作为分割,而是.
,也就是说,如果你的工程根目录在~/Documents
,那么你的com.VisuaGMQ
包就在~/Documents/com/VisualGMQ
。
使用package
可以告诉Java当前文件在哪个包中,package
语句必须是在代码的第一行 。
再举个例子,如果你使用的是C++,那么你可能要这样管理你的工程:
|
|
然后你得在MapEditor.cpp
中写上#include "include/MapEditor.hpp"
(假设你没有改变头文件搜索路径),在structs.cpp
中写#include "include/common/structs.hpp"
最后在main.cpp
中以同样的方式包含你要的头文件。
但是在Java中,你可以这样:
|
|
然后你得在MapEditor.java, Main.java
中写package src;
,在header.java, structs.java
中写package src.common;
,然后在Main.java
中使用import src.common.header;
来引用header.java
文件(注意不能直接使用import common.header;
必须指定包的全路径)
实际上,包和C++的namespace一样开辟了新的命名空间,不同的包在不同的命名空间内,所以不同包内的类可以重名。
访问修饰符
由于所有字段和方法都必须在类里面,所以先了解一下访问修饰符。
和C++一样,存在public protected private
三种访问修饰符关键字。但是访问权限却有四个,因为不写访问修饰符也是一种访问权限——包内访问
注意不能在类前加protected
public让其他包的代码可以访问这个包内的类和方法。
protected不能让其他包访问这个包内的信息,同一个包内也不能访问,类内可访问,并且继承下去之后仍然是protcected。
private包外包内都不能访问,继承之后仍为private。
不写(默认)访问权限是包内访问,其他的包不能访问,同一个包可以相互访问。
面向对象
类
使用class
声明,类内的方法和字段全部必须加上访问修饰符,类末尾不需要分号:
|
|
和Python一样,Java没有重载运算符,为了弥补这个缺陷,Java保留了一些特殊函数,这些函数有一定的作用,你可以重写来实现自己的功能,最常用的就是toString()
方法,他会在类被输出的时候自动调用这个方法:
|
|
可能你已经注意到了第17行。在Java中,所有的变量都可以在声明时直接赋值,如果没有赋值,对象会是null,基本数据类型会是0,boolean变量会是false。
构造函数
构造函数和C++一样的意义,没给构造函数的时候会默认给个空的。
析构函数?
有析构函数函数吗?没有,不过如果你硬要有一个的话,可以参考finalize()
函数。
继承
继承的话使用extends
关键字:
|
|
没错你没办法像C++一样控制继承的权限,也就是说你每次继承都是public继承。
Java取消了多继承,这意味着extends
后面只能跟一个类名。
所有类的基类
和Python一样,Java有着万类之根的类Object
,这个类主要有一些RTTI的处理和定义toString()
方法。任何不继承的类默认继承Object
。
无法继承的类
在类前面加上final
可以防止类被继承,如果继承了会报编译时错误。
重载
和C++一样,注意重载是不能改变访问权限的,比如你的方法在父类是public
,到了子类必须仍然是public
,不然会报编译时错误。
在重载中如果想要调用父类的方法,需要使用super
关键字,super关键字代指当前类的父类:
|
|
虚类
使用abstract
来声明虚类和虚方法,如果一个类中有一个虚方法,那么这个类必须冠以abstract
:
|
|
接口
当虚类只有虚函数,没有字段的时候就变成了接口:
|
|
接口使用interface
关键字声明。和类的区别在于:接口的方法不能被实现。其实接口就是C++中没有成员变量的纯虚类。
但是和C++纯虚类一样,嘴上说着不能实现函数,其实还是可以实现的,但是你要冠以default
关键字:
|
|
这样如果你不想实现这个函数,就是使用这个默认函数。
实现接口
接口可以被实现:
|
|
使用implements
来实现接口,后面可以跟多个接口。所以我们可以通过接口来实现多继承。
实现接口必须将接口所有的方法全部实现。这里推荐使用@Override
注解,他可以帮你辨别方法是否是被重写的,以方便在编译时找出重写错误。
静态方法和字段
当然是使用static
来实现了。但是别的类在使用的时候,不是用::
而是使用.
:
|
|
注解
注解是个新东西,详见廖雪峰老师的博客
异常
处理异常
格式和C++一模一样:
|
|
但是Java中有一个至今我都感觉很烦的事情:如果一个方法可能会抛出异常(其方法体内有throw语句),那么你必须在使用这个方法的时候捕捉异常,不然会有编译时错误(除了RuntimeException
,Error
及它们的子类)。
所以你常常会看见Java中存在大量的try..catch
块,严重影响代码美观。
一般而言Error
是不需要捕获的严重异常(你就算写了try_catch
块也不用对其进行处理),如果碰到了整个程序适合直接挂。Exception
应当是可处理的异常。
抛出异常
创建异常类
要抛出异常,你可以使用系统的异常类,或者自己创建。方法是继承Throwable
类并且改写其中的方法(一般可以改写toString()
方法以便找到错误所在)。
而且你的函数内可能抛出什么异常,你就需要使用throws
关键字指定函数要抛出什么异常:
|
|
而且你不能写出这样的代码:
|
|
这样Java会认为一个函数不能同时抛出两个异常而给出编译时错误。
使用throw抛出异常
这个很显然了,和C++一样:
|
|
断言
使用assert
断言,会抛出AssertException
。
|
|
日志打印
Java自带了java.util.logging
模块可以打印日志,或者你可以下载使用广受好评的第三方模块log4j
。
泛型
泛型的声明
和C++一样Java也拥有泛型:
|
|
Java中免去了template
这种关键字。泛型类和泛型接口直接在名称后面加上<T1,T2,...>
即可,泛型方法则是在返回值前加上范型。
而且要注意的是:静态方法是不受类的泛型类型影响的,也就是说你不能这样写:
|
|
必须要将静态方法变为泛型方法才行:
|
|
当然这里的T和泛型类的T已经不是一个东西了。
泛型的使用
|
|
泛型的继承
和C++一样:泛型类可以继承泛型类,非泛型类只能继承泛型类的特化类。也就是说可以这样写:
|
|
需要注意的是泛型类型必须是类类型,导致这种情况是因为泛型擦除
。
泛型擦除及其他
泛型擦除的内容很多,参考廖雪峰老师的博客。extends和super通配符也一并参考。