最近抽时间在学习Java,目前有了一点心得,在此记录下来。
由于我自己之前学过C/C++,而Java的语法与C/C++基本类似,所以这一系列文章我并不想从基础一点点的写,我想根据我已有的C/C++经验,补充一些需要注意的点,或者java中独特的内容,或者将C/C++进行对比来总结一下学习的内容。
为什么要学习java
最开始接触到Java还是在学校中开设的一门java编程语言的课,那个时候感觉java很麻烦,写个helloworld要那么多代码。后来学到web编程,我自己搭建的环境总是报错,而且还是jar包的错误。从这个时候起,我对java就没什么好感。
后来很多培训机构来学校招生,做讲演,难道大学学了4年,最后还是去了培训机构,而那些培训机构号称4个月完全掌握java,这时我感觉如果我去学了java,那么大学4年出来跟培训班4个月出来有什么差别,既然上了大学,学了计算机,要跟培训机构出来的人不一样,既然是学计算机的,当然得学计算机里面最难的语言,所以我大学从3年级开始学了大半年汇编,又从汇编转到C和C++。
但是万事逃不过真香定律,在工作之后,慢慢接触了Java,也了解了java,其实Java并不像我想想的那么简单。但是我心里一直抗拒学它。
但是最近工作中确实要用到它,之前在一些博文中提到过,现在我开始带领几个人的小队,里面有java的,有做C的,有Python的,我想作为一个leader,虽然不用干他们的活,但是至少得懂。
最主要的还是Java那边提个需求实在太难,java程序员总会跟我说很难,要改代码,动不动就说这个需求得改架构。最后做出来的跟我预计的差别太大。为了有理有据的回怼,也为了更好的带队,我想Java还是得会。
说到这里我有感而发:领导给你活,并不是要听你抱怨有多难,要很多东西。既然能告诉你要做这个,那么可行性方面肯定提前做过研究,不要说什么很难,做不了。既然给你了,领导要的是你提出一个解决方案,然后告诉我要多长时间。中间出了问题及时反馈就OK。一直抱怨难,动不动就改架构什么的,只会让领导对你的个人能力产生怀疑,甚至会萌发出换人的想法。所以领导拍下来的活,干就对了。
上述是一个原因,还有一个原因;我在关注安全漏洞的时候经常会报出来什么Struts2 漏洞、WebLogic 漏洞,这些都是java的开发框架,很多时候大牛们的博客或者公众号上已经写了漏洞原理,甚至有的还有它验证以及构造POC的思路,但是我就是看不懂,不出POC,我完全不知道如何去检测。为了以后能更好的理解这些java漏洞,我想还是需要好好学一下Java
从hello world 开始
任何语言都是从hello world开始的,java也不例外,这里我给出hello world的代码
public class HelloWorld{
public static void main(String[] args){
System.out.println("hello world");
}
}
将这段代码保存到HelloWord.java文件中,这里文件的名称必须是HelloWorld.java,文件名与主类的名称相同。然后调用javac进行编译
javac HelloWorld.java
这个时候会生成.class 文件,这里使用java执行代码
java HelloWorld
注意执行的时候java指令后面跟的是类名而不是具体的.class文件名。这里我想应该是在执行的时候,java命令根据类名去找对应的.class 文件,将文件中的二进制字节码放到虚拟机中执行。然后由虚拟机去类中查找main函数,从main函数中执行。因此这里文件名必须与类名相同,而且必须要有main函数。
再来说说这个main函数前面的修饰词,public 应该表示这个是一个公共方法,也就是外部可以访问,static 表示这个方法是一个静态方法,独立与类存在的。这两个修饰符是必须的要的。java强制使用面向对应,一切都定义在类中,但是程序必须要一个入口函数。根据java的逻辑,这个main函数也得定义到类中。但是如果定义成普通函数的话行不行呢。答案是不行的,由于main函数是一个入口函数,一切都从它开始,如果它是一个类函数,那么势必要定义一个类的对象然后再调用对象的main方法,可是既然main是程序的开始,请问如何在调用main之前定义对象呢,因此这里必须得定义成静态的;这个public能不能不加或者改成private或者其他的呢?当然也不行,既然你要将它作为入口函数,那么必然需要由虚拟机调用这个函数,而且是在类外调用,所以这里一定得定义成public,对外开放。
在Java中一切即对象,它强制你采用面向对象,这也是当时我拒绝学java的一个理由,认为它太死板。
java的跨平台
据说SUN公司当年是卖服务器,服务器上的主要程序是由C/C++开发而来的,而C/C++,每次在换一个平台都需要重新编译。这个时候SUN公司的工程师需要一种跨平台的语言,就那种代码写完,编译完成之后不需要再做任何操作,随便放到一个平台上都能跑的那种。而且当时C++ 指针、多继承满天飞,造成程序编写、理解的困难。基于这几点理由,开发出了Java,Java脱胎于C++,但是砍掉了C++中复杂的指针和多继承的内容,在现在看来应该是一个比较正确的决定。相比于现在C++各种新特性的眼花缭乱,java还是很朴实很简单的东西。
java的跨平台取决于它的虚拟机。每个平台都需要一个对应的虚拟机。虚拟机就好像一个翻译,而程序就像一个到不同国家旅行的人,比如说一个中国游客可以去美国、去日本、去英国旅行,他在酒店前台时用中文吩咐前台的工作人员给他一间房,去不同的国家有不同的翻译将开房这条指令翻译为前台能听懂的语言。而我们的游客只需要说中文即可。
java的虚拟机的工作原理也是这样的。按照统一的规则,根据具体的平台将规则中定义的指令翻译为对应平台上的机器码。比如在Windows上+1操作是 ADD 1, 在Linux上+1操作是 +1, 而在MAC中对应 1+ ,那么在java代码中这个指令可能会编译为 1++, 这个指令不管在哪个平台都不用变,当它放在windows主机上由Windows版的虚拟机将它翻译为 ADD 1,在Linux上由Linux版的虚拟机翻译为+1,在MAC上由MAC版的虚拟机翻译为 1+。
java代码执行需要经过两个步骤,首先编译为虚拟机能识别的字节码,然后有虚拟机解释并执行这个字节码。所以java具有两面性,即需要编译,也需要解释执行,那么它到底是解释性的语言还是编译型的呢?我也不知道。
基本语法
它的语法与C/C++基本类似,类似到你即使没接触过java,看它的代码基本能看懂每条语句都在干嘛。所以针对我来说,我并不关注每个代码怎么写,我只需要知道每个语法点有哪些需要注意的即可。
常量与变量
常量在java中一般是指那种用字面值表示出来的量比如说 整型的1,浮点型的1.234, 字符 'A' 字符串 'hello world',或者是用关键字 const 定义的。
java里面的常量分为:整型常量、字符串常量、浮点数常量、字符常量、布尔常量和空常量(null)。
从常量类型可以看出这些也是java中主要的数据类型,java中数据类型主要有:
- 整数类型: byte、short、int、long
- 浮点数类型: float、double
- 字符类型: char
- 布尔类型:boolean
这些都是基本数据类型,java中还有引用类型,像字符串、对象、数组、接口、lambda表达式都是引用类型。
需要注意的是java中long 是8个字节,而C/C++中long一般是4个字节,longlong才是8个。java中的char占两个字节,所以在C/C++中会将需要一个字节一个字节处理的缓冲定义为char型数组,而在java中就不能这么干了,因为它的char占两个字节,java中对于这种情况一般是定义为byte类型的数组。由于java中的char占两个字节,所以java中char是可以表示中文的
char c = '中' //这在java中是正确的,但是C/C++中不能这么写
有数据类型自然就涉及到数据类型转化的问题。java与C/C++类似都有显示转化和隐式转化。而且写法也类似。需要注意的几点是:
- 整型字面值会被java编译器默认当做int类型来处理,像
byte num = 5
、short s = 5
这样的表达式中5 这个字面值都是int,也就是它里面都发生了强制类型转化,如果想让编译器将其当做long需要在5后面加上L,写成long l = 5L
- 浮点数字面值会被默认当做double 来处理,如果想让其被作为float处理,需要在后面加上F
- 在进行整数运算的时候,运算符号两侧的变量 或者常量会被先转化为int 在进行处理。例如 下面的代码:
short s1 = 5, s2 = 10;
short result = s1 + s2;
这段代码java会报错,由于在运算的时候s1 与 s2 会被转化为int,然后运算得到的结果也是int,在最后进行结果赋值的时候将int赋值为short会发生错误。要让他不报错可以改为 short result = (short)(s1 + s2);
- 隐式类型转化发生在由表示范围小的向表示范围大的类型,一般是 byte-->short-->int-->long-->float-->double。
再来看下面一个例子:
short n = 5 + 10;
这段代码,从理论上讲,它应该会首先把5 和 10 转化为int,在计算,最后把结果的int转化为short赋值,根据上面说的,它应该会报错才对,但是实际试验的结果却是,它通过了。这就很奇怪了。
还记得在学习C/C++中提到的编译器的优化吗。在C/C++中如果你写上面的一段代码,在release版本中,你看不到类似
mov eax, 5
add eax, 10
mov n, eax
这样的机器指令,只看得到
mov n, 15
这里编译器进行了优化,你代码中采用了字面值常量进行相加,而常量是不会变化的,因此在程序运行之前就已经知道计算的结果,我就没必要在运行的时候浪费CPU给你计算这个加法值,我直接给你一个结果也是一样的。所以这里java编译器采用了同样的策略。它直接将上面的代码翻译为了 short n = 15
。
但是这也不对啊,15应该会被当做int,而n是一个short,将int这个表示范围大的转化为short这个表示范围小的,应该会报错才对啊。但是编译确实不报错。
这又涉及到java编译器的另一个策略了。当我们直接用字面值常量进行赋值操作的时候,如果字面值没有超过左侧变量的表示范围时,编译器会自动进行强制类型转化。
最后的最后
我想在你已经拥有其他语言的开发经验的时候,学习新语言的过程无外乎是数据类型、基本语句、控制结构、函数、面向对象、以及常用库这些东西,所以我想我自己的java笔记也按照这些框架来组织。这次是数据类型,下次就是基本语句与控制结构了。