文章目录
- CH3.Java的基本程序设计结构~CH5
- 3.1 基本语法差异
- 关键字差异——
- 相同关键字的用法差异——
- 运算符差异——
- 其他差异——
- 3.2 面向对象差异
- 基本
- 封装
- 继承
- 多态
- Java中的新概念
- 3.3 基本类的使用
- 3.4 基本工具使用
CH3.Java的基本程序设计结构~CH5
(从java基本语法与c++的对比上看,与原章节不一致)
前言:本文是作者在看书的过程中自己的一点心得和笔记,对于初学者可能有一些帮助。
3.1 基本语法差异
关键字差异——
-
final
——java使用final关键字声明常量,类常量需要使用static final声明 -
break label
——java提供break label跳出到label标识的嵌套层级(类似goto,不建议使用)
相同关键字的用法差异——
运算符差异——
一般运算符都与c++相同
- 移位运算符——<<,>>java使用的是算数移位(即>>不会移动最高的符号位,而c++的不一定)另外,java提供了逻辑移位>>>(符号位也移动,没有<<<运算符)
-
==
运算符——java的String类型不要使用==
比较是否相等,而应使用equal函数
其他差异——
- 注释——多了文档注释,/**标识文档注释开始,*/标识结尾
- 基本类型的差异:
- java有8种基本类型(4整型,byte,short,int,long,其大小1,2,4,8固定与平台无关;2浮点型float,double;一个字符型,char;一个布尔型,boolean)
- 一个易混淆的,java八进制数以0开头,容易看成十进制,如010表示的是十进制的8
- 数字字面量中可以加下划线区分位数,编译的时候会自动去除,如1_000_000=>1000000
- java基本类型有对应的类类型,可处理其与无符号数的转换,如Byte.toUnsignedInt(b)转成int,处理完了转回byte存储
- boolean与整型值不能互相转换
- 等等…
- 转义字符\uXXXX——这个可以出现在引号内外都可,转义字符的替换会先于代码解析,如"\u0022+\u0022"会先把\u0022替换为",于是变成"“+”",也就是一个空字符串;注意注释中使用\u也会先转义替换,可能会导致问题
- 大数——BigInteger与BigDecimal:代任意大小、精度的整数与浮点数
- 字符串差异:
- java的字符串不可看做是“字符的数组”,而应看做是常量字符串;
- 有一些便利的函数substring,repeat,join等
- 检测是否相等应该用equal方法,而不是==运算符
- 空串(用length函数判断长度是否位空,或者用equal函数判断是否等于"")与NUll串(直接==null判断是否为NUll串)
- 数组——
- java声明数组int[] a=new int[10];同时也支持类似c++的方式:int a[]=new int[10];(但是
int a[]={1,2,3};
的方式不行,得是int[] a={1,2,3};
,所以,还是统一用java自己的类型加[]的方式定义数组) - java数组可以长度为0,
int[] a=new int[0];
或int[] a=new int[]{};
;长度为0的数组与null不同,就像空字符串与null也不同
3.2 面向对象差异
基本
- 对象内存——java对象都存储在堆上,java类对象变量类似于c++的类指针变量
- java访问器与更改器——类似c++的成员方法后加const与否,但是java中并未在语法上区分二者,依靠开发者自己的编码规范,如,java访问器方法不要直接返回对象成员变量,因为这样返回的是引用,外界可以通过这个引用改变类内部的该变量值,破坏了封装性;应该返回该成员对象的一个clone
- null实参的校验——java方法中以类型作为参数设置成员变量,需要考虑该成员变量是否接受null,如果不接受,可以考虑使用
Objects.requireNonNull(someVar,errMsg)
(直接抛异常并给出errMsg提示)或者scopeVar=Objects.requireNonNullElse(someVar,defaultVal)
(如果参数someVar为null则使用defaultVal) - main函数——每个类都可以有一个main函数(用来对这个类进行单元测试),如TestClass中有一个main函数,调用
java TestClass
即可运行其main,对其进行单元测试;如果从其他类的main开始执行,则永远不会调到这个main函数 - 静态方法调用——java中调类的静态方法用
类名.静态方法
(c++是用类名::静态方法
) - 类数据字段初始化——1)声明时直接初始化(c++不推荐,但其实也可以);2)通过构造函数初始化(构造函数可重载,还能通过
this(...)
(注意不是super
)调用另一个构造函数);3)初始化块(类中不在任何函数中的{}
代码块)
- 分为实例初始化块和静态初始化块;静态初始化块前加static,在执行器生成类定义时就会执行,而实例初始化块在new一个类的实例时会执行)
- java7之前甚至可以无main函数,通过静态初始化块来实现"Hello World"程序(java7及之后不行,因为运行时会先检查有无main,后开始定义类,走静态初始化块);
- 初始化块在构造函数之前执行,初始化块之间的执行顺序按照在类中出现的先后顺序
- 初始化块必须在变量声明之后
- 析构函数——java有自动垃圾回收,不需要析构函数;但是java有finilize函数,建议不要在这回收资源,因为这个由GC在回收对象内存空间之前调,不确定具体什么时候调
- 类设计的技巧——与c++基本相似:保证数据私有、对每个数据进行初始化、不要过多使用基本类型(而是把类中多个基本字段抽象成一个类的概念)、不要盲目地给所有字段提供getter,setter、分解职责过多的类、类名/方法名体现其职责、优先使用不可变的类(多线程安全)
- 继承的设计技巧——将公共字段和操作放在基类(超类)中;不要使用受保护的字段;继承体现
is-a
关系;继承之后所有的方法应该仍然有意义;覆盖方法时不要改变预期行为;使用多态而不使用类型信息;不要滥用反射
封装
- package的概念——包的概念类似c++的namespace而不是包含文件;包同样也是非封闭的,可以自己定义一个类然后package打包为已有的包(类似c++的namespace拓展);但是以
java.
开头的包用户无法自定义类 - 权限修饰符——public与private含义与c++一致(以及多了类的public private修饰),不一样的是没有public,private修饰时的默认行为:
- public的类可以被任何包里的类使用;未加修饰的话可以被同一个包里的所有方法使用(类只能public或者无修饰)
- 类中public的方法可以被任何包的任何方法使用;private方法只能被自己使用;未加修饰则只能被同一个包里的所有方法使用
- protected修饰的方法、变量能被子类访问;以及同一个包的任何类访问(java的protected比c++封装性差得多)
- 使用package包——通过
import 包名.嵌套包名
导入包,java其实不需要导入包,导入包的话可以省去类全称(只是为了使用方便);静态导入:可以import static ...
导入类中的静态方法和静态字段;导入的包必须在本地有对应的目录结构xxx/yyy/zzz
- 类查找路径——编译器javac命令与运行器java命令(jvm)可以指定类查找路径(二者不一样的话可能出现编译过了运行时找不到类的问题)
- 打包——在程序开头先于所有的import语句加上
package xxx.yyy.zzz
,其中一般xxx.yyy.zzz
是域名反过来,比如com.mycompany.util
- jar包——使用jar命令可以把
.class
文件(包含目录结构)压缩成jar包,提供给用户,类似于c++的exe或者dll;用户可以使用jar包中的类(相当于dll了),也可以打包时为jar包指定一个启动类(相当于exe了),这样用户可以双击启动;实际启动时运行的命令是:java -jar xxx.jar
,如果用户未装jre,可以把jre一起打包发给用户,然后写一个启动脚本,使用包里的jre中的java命令即可 - jar转exe——可以使用Launch4j或是其他的工具将jar包转换成exe
- 封装jre——使用jlink选择模块生成jre
- 自动生成文档——在源码中需要按照格式写注释(注释文本支持html),以
/**...*/
标志文档注释;javadoc -d docDirectory package1 (package2...)
,指定生成目录docDirectory与生成文档的package(可多个);或者执行javadoc -d docDirectory *.java
为无名包生成注释文档
常用的javadoc标记:
@param 方法参数描述
@return 方法返回值描述
@throws 方法抛出异常描述
@author 作者名
@version 版本
@see 跳转链接,如@see package.class#function,@see <a href="...">label</href>,@see "Some book at some page"
@link 跳转,如{@link package.class#function}
@index entry,(java9支持)如{@index entry},为文档搜索框指定一个索引条目
包的注释:
包注释需要在包所在的目录下加一个注释文件。提供两种方式:
- 加一个package-info.java的文件,然后是
/**...*/
标识的注释以及package xxx
,无其他代码或注释 - 加一个package.html的文件,会抽取
<body>...</body>
间的所有文本
继承
- 继承——java的继承使用关键字
extends
取代c++中的:
,同时java只有公有继承,没有保护和私有继承 - super——java中子类调用父类(java中叫超类)同名方法需要使用
super.方法名
,c++中是父类名::方法名
;子类中也通过super调用父类的构造器,必须在子类构造器的第一句调用父类构造器 - 多继承——java不支持多继承,而是通过接口的方式实现类似多继承的功能
- 强制类型转换——尽量不要用。如果要用,只在有继承结构的超类、子类之间用(将超类强制转为子类;子类转为超类不需要强制转换),而且使用之前先判断是否类型匹配,
if(a instanceof B){b=(B)a;}
(B->A,且a,b分别为A,B类型的引用) - 继承权限——子类覆盖父类方法时,不能降低方法的权限(即public的方法不能覆盖后变成了private的)
- 关键字final——类声明为final,不可继承,其方法均自动变为final;方法声明为final,子类不可覆盖(无多态);数据成员声明为final,则为常量;数据成员final与类的final无关联
- 抽象类——方法声明为abstract则为抽象方法,不需要实现;有抽象方法的类必须声明成abstract类,没有抽象方法的类也可以声明成abstract;声明为abstract的类无法实例化,只能实例化为其子类
- 所有类的根类——所有类的根类都是Object,如果没有指定类的继承关系,java编译器会让它默认继承自Object;java中除了基本类型(int,double…)不继承自Object,其他所有类型包括数组类型(基本类型的数组或者对象的数组)、接口类型、枚举类型都继承自Object
多态
- 默认多态——java中类的函数默认是具有多态的,也就是默认与c++中virtual函数一样;如果不想要这种动态绑定,可以声明函数为final的
- 对象变量是多态的——也就是超类的对象变量也可以引用子类的对象;同理,子类的对象数组可以赋值给超类的对象数组;但是需要注意不能对超类对象数组的元素重新赋值一个超类的对象,因为这样改变了子类数组中对应元素的类型为超类了,会抛出ArrayStoreException异常
- 函数覆盖——这个就是函数的动态绑定,多态,父类的变量可以引用子类的对象,调用父类子类中都有的同名函数时,子类的覆盖父类的;但是java不像c++一样,c++连基类的private函数也可以覆盖(只要是virtual的),java不行
- java使用@override注解,说明该方法覆盖了父类的相同方法,如果实际没有覆盖,编译器会警告,防止手误写错函数签名;类似于c++的函数override
Java中的新概念
- 相等性的检查,重载Object.equals(Object otherObject),需要满足——自反性,对称性、传递性、一致性、不为null的对象与null相比始终是false;经典的equals重写如下:
class ClassName
{
@Override
public boolean equals(Object otherObject)
{
//1.是否相同引用判断
if(this==otherObject)return true;
//2.是否有null判断
if(null==otherObject)return false;
//3.是否同一个类型比较(如果子类也有相同的相等性语义的话可以用instanceof
if(!(getClass()==otherObject.getClass()))return false;
//4.otherObject转换成当前类型,然后可以比较数据成员
ClassName other=(ClassName)otherObject;
//5.然后基本类型数据成员用==比较
//6.对象类型数据成员用Objects.equals(这个函数会调用第一个参数的equals函数)
return field1==other.field1
&& Objects.equals(field2,other.field2)
...
}
}
- 注解——常用注解:
- 自动装箱——java中基本类型与对象类型Byte,Short,Integer,Long,Float,Double会自动转换,成为自动装箱;可变参数的函数,不定数量的参数相当于对应类型的数组,比如
double max(double... values)
相当于double max(double[] values)
,传入多个参数会自动装箱成数组,如max(1.2,2.3,3.3)
会自动转化为max(new double[]{1.2,2.3,3.3})
- 枚举类——java中枚举也是对象类型,都继承自
Enum
类,枚举值则相当于枚举类型的实例,也可以有构造器(必须是私有,仅用于构造枚举实例) - java自带反射机制
- 异常——java中的异常分为非检查型与检查型,非检查型异常是指那些调用者应该尽力避免(且可以避免)的异常,检查型异常则是无论如何努力也无法完全避免的异常(比如取决于运行时状态可能出现的异常);非检查型异常不用处理,检查型异常要么需要try…catch捕捉处理,要么需要在函数后加
throws ExceptionType
子句 - 资源——java的
Class
类有操作资源的方法,URL getResource(String)
与InputStream getResourceAsStream(String)
用来获取图片与文本资源;java中图片与文本都是资源(比如国际化的程序可以替换不同的文本资源,从而实现翻译)
3.3 基本类的使用
- String以及更高效地操作字符串的StringBuilder,StringBuffer(与StringBuilder接口相同,线程安全,效率略低)
- 操作数组的类:Arrays
- 基本输入输出:System.in与System.out,Scanner
- 数学库:Math
- 表示日期时间的类:Date与LocalDate
- 提供强制非空限制:Objects.requireNonNull,Objects.requireNonNullElse
- 生成随机数:Random
-
Object
类的常见函数——equals(Object OtherObject)
,hashCode(Object a)
与hash(Object... objects)
,toString()
,getClass()
(返回Class
类型对象,包含本类的运行时表示信息);getName()
,getSuperClass()
- 泛型数组类型:ArrayList
- 反射类:Class——提供资源操作,反射操作(如获取类的字段、方法、构造器等信息,以及调用构造器创建实例)
-
Enum
类——枚举类也是类类型,也继承自Object
;可以定义构造函数(必须私有,在初始化枚举常量时使用,也就是说枚举类型实际上是只能有其中枚举常量这么几个实例的特殊类;常用函数,static Enum valueOf(Class enumClass, String name)
,toString()
,ordinal()
,compareTo(E other)
3.4 基本工具使用
- 编译:
javac SomeClass.java
如果SomeClass.java中的类依赖其他的类,会自动查找相应类的.class
文件,如果无则会查找.java
文件并编译
2. 运行:
java SomeClass
注意不要带后缀,直接类名即可。这样是错的:
java SomeClass.class
- 反编译
javap xxx.class > xxx.java
- 打包jar
打包时file也可以指定一个路径
jar cvf jarFileName.jar file [file2 ...]
另外可以增加参数e与xxx.jar后面的xxx.class参数,使jar包变成可启动的,
jar cvfe jarFileName.jar startupMain.class file [file2 ...]
启动方式为双击jar包(安装了jre的机器会自动设置.jar
文件的关联程序为javaw,javaw与java类似只是执行的时候不会启动shell),或者执行java -jar jarFileName.jar
5. 生成注释文档
如果是中文注释需要设置编码格式
javadoc -d docDirectory package [package2...] [-encoding utf-8]
- java11选择模块生成jre
jlink --module-path jmods --add-modules java.base[,模块2 ...] --output jre
- 交互式java编程环境(>=jdk9)
jshell