编程过程也是一项细心地活动,需要考虑到的地方很多很多,比如下面的各种细节。
注意要理解一下这个集合中的Iterator,应该说iter位置是在它的next元素和previous元素位置之间,不是在元素位置上
* next可以走到最后一个元素后面的位置,所以此时调用nextIndex = size,
* previous可以走到0元素的左边位置,所以此时调用previousIndex返回-1
* 没有元素的时候,它在0的左边
Java成员变量(基本数据类型和对象)会被虚拟机初始化为0或null,而局部变量不会被虚拟机初始化,没有手动初始化就是用会报错,这是Java中规定的。原理:JVM在堆中申请对象内存的时候(或者静态方法区的静态变量),(在类加载过程的链接中的准备阶段做的)顺便将所有成员都置为0(对象就是null),再赋值(赋了两次值),没有赋值的保持原样。而JVM在栈申请局部变量后没有置为0这个操作,因为太多,影响性能,变量的值是不确定(比如有可能是复用其它局部变量,值就是那个变量的值),所以才需要手动赋值。
String中用到正则式的方法:
matches, matche.regonMatches();
split;
replaceFirst,replaceAll() (replace不是);
Java内存分区中还有一个PC寄存器 别忘了。
定义泛型类的内部,使用该类主要有两种方式:
一、将对象传进来,不操作它内部的任何东西,如容器。
二、对象传进来,只操作它内部部分内容,类族的泛型。
Java有一个无符号右移运算符 >>>,右移之后,高位值只补零。
以下来自《编写高质量代码:改善Java程序的151个建议》
三元操作符的两个返回值的类型务必一致,不一致会导致自动类型转换,类型提升int->float->double;
比如
int i = 80;
return i > 100 ? 90 : 100.0;
此时返回的90是float而非int
用a%b判断奇偶在Java中,若a是负数判断奇数得到的结果是-1
这是因为,Java的取余是这样的
mod = a - a / b * b
// -3 - (-3 / 2 * 2) = -1,和整数是不一样的。
所以判断的时候要用 ==0 来判断
不要让类型默默转换
在运算中是int,其结果是long,这样的转换会出错。
比如
constant int V = 30 * 10000 * 1000;
long dis = V * 8 * 60;
转换出错,在表达式中已经溢出了,赋值给long,自然是错的。
四舍五入会有微小误差,如果是大量计算(金融行业,上亿的交易),误差会变很大(银行亏),所以有一个银行家舍入算法,精确的减小误差。在Java的round()函数中提供了相应的功能。HALF_EVEN模式。
包装类要注意的几个问题:
基本类型的包装类放到集合中时,要注意它的NULL值,如果获取集合中的值时使用的是自动拆箱,而自动拆箱过程是调用对象的xxValue方法,因此会产生自动拆箱会产生NullPointerException。比如下面的代码
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(null);
int count = 0;
for (int i : list) {
count += i;
}
不能拿包装类直接比较相等,可能会不相等,因为比较的是对象地址,然而它们却可以比较大小。Java这点设计不太科学。
final变量序列化与反序列化问题:
通常,final变量都希望它的值就是代码中指定的值,但是如果版本变动之后final的值变了,如果是在构造函数之外,反序列化会重新赋值,而如果在构造函数之内,则不会。这是因为反序列化不会调用构造函数。所以需要序列化/反序列化的类最好不要在构造函数中为final变量赋值。
Java中易变业务可以用脚本语言写,然后运行,Java提供相应的支持。
instanceof 使用几个规则:
instanceof 只能在同一类族中比较使用,不同的编译器会报错。
null incetanceof xxx 返回false
子类实例 instanceof 父类 返回true 反之则是false
泛型里面的T instanceof
int 等基本类型 instanceof 是无效的 ,int不是类,没有类对象,int.getClass()不存在
顺便的 基本类型指的是不是类的类型,String不是他们的
整数:
byte short int long 字节数依次 *2
浮点
float double
最后是 char 和 boolean
Java中使用断言:
assert xxx: “xxx”
最后是提示信息,是可选项
但是注意assert需要Java机器启用才有效,所以只能在开发测试等生产环境中使用,在实际运行环境中是关闭了的。所以断言语句里面不能放入任何被依赖的,与实际运行有关的代码,只能放入判断语句。
switch中多个case处理一样的写法:
while (count < utflen) {
c = (int) bytearr[count] & 0xff;
switch (c >> 4) {
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
/* 0xxxxxxx*/
count++;
chararr[chararr_count++]=(char)c;
break;
case 12: case 13:
/* 110x xxxx 10xx xxxx*/
count += 2;
...
Java序列化中的UID是通过报名,继承关系,非私有属性,参数返回值等诸多因子计算得出的,十分复杂,基本上这个算出来的值是唯一的。
谨慎使用动态编译
动态编译在框架中要谨慎使用,也不适合在高性能的框架中使用,要考虑安全问题,因为如果在Web应用中动态编译网络传入的代码就可能形成注入漏洞,并且如果要进行动态编译:需要记录动态编译的日志信息,以便维护解决。
前端校验是无效的
在web开发中,前端数据校验只能对普通用户产生作用,而对于了解的人,是可以绕过前端校验传送数据的,所以后台必须再进行严格的数据校验。
注意运算式子进行值校验时,运算溢出的问题,不要当做一般的单个数那样进行校验
比如要用户输入数据x
;要校验用户输入的x
与当前值cur
相加之后的结果小于limit
,一般会怎么写代码, 可能如下
int LIMIT = 100;
if (x > 0 && x + cur < LIMIT) {
...
}
这样的代码是有问题的,x 如果取到int的最值附近,x + cur就溢出了,判断条件成了,x逃过了校验!这就是个很严重的问题,如果放在重要的系统中,比如商品,本来最多用户只能买100个,结果却卖了20多亿个,系统会垮掉的。
善用使用静态内部类可以提高封装性
善用初始化代码块构造对象
构造函数不要写得过于复杂
**List list = new ArrayList {{}} **这种是相当于创建了一个匿名内部类,内部那个{}相当于其初始化代码块,也就相当于构造函数了,因为匿名函数没有名字,没有构造函数。
匿名内部类会调用父类的同参构造函数,这和一般的调用父类默认构造不一样
关于匿名内部类只能持有final局部变量(jdk 1.8以前)
因为Java一般的机制要是内部类的变量和外部类保持同步,对于外部类的成员变量,通过外部类的this访问达到了这个效果,而对于方法内的局部变量,没变法访问达到同步效果,所以语法强制规定只能持有final的局部变量。
jdk 1.8以后据说是如果创建内部类之后不去改变变量,就可以不加final,原理应该是一样的,因为不改变了,所以不用同步了。
Java中char占用几个字节 (转载)
在讨论这个问题之前,我们需要先区分unicode和UTF。
unicode :统一的字符编号,仅仅提供字符与数字编号间的映射。符号数量在不断增加,已超百万。详细:[https://zh.wikipedia.org/zh-cn/Unicode]
UTF :unicode转换格式 (unicode transformation format) 。定义unicode中编号的编码方式。UTF-8、UTF-16、UTF-32都是将数字转换到程序数据的编码方案。其中utf8为变长表示,长度可能时1~6个字节;utf16为变长表示,长度可能是2或4个字节。详细:UTF8 [https://zh.wikipedia.org/zh-cn/UTF-8] UTF16 [https://zh.wikipedia.org/zh-cn/UTF-16]
接着,要分清内码(internal encoding)和外码(external encoding)。
内码 :某种语言运行时,其char和string在内存中的编码方式。
外码 :除了内码,皆是外码。
要注意的是,源代码编译产生的目标代码文件(可执行文件或class文件)中的编码方式属于外码。
先看一下内码
JVM中内码采用UTF16。早期,UTF16采用固定长度2字节的方式编码,两个字节可以表示65536种符号(其实真正能表示要比这个少),足以表示当时unicode中所有字符。但是随着unicode中字符的增加,2个字节无法表示所有的字符,UTF16采用了2字节或4字节的方式来完成编码。Java为应对这种情况,考虑到向前兼容的要求,Java用一对char来表示那些需要4字节的字符。所以,java中的char是占用两个字节,只不过有些字符需要两个char来表示。
详细:
[https://docs.oracle.com/javase/tutorial/i18n/text/unicode.html]
[http://www.zhihu.com/question/27562173]
外码
Java的class文件采用UTF8来存储字符,也就是说,class中字符占1~6个字节。
Java序列化时,字符也采用UTF8编码,占1~6个字符。
总结:
java中内码(运行内存)中的char使用UTF16的方式编码,一个char占用两个字节,但是某些字符需要两个char来表示。所以,一个字符会占用2个或4个字节。
java中外码中char使用UTF8的方式编码,一个字符占用1~6个字节。
UTF16编码中,英文字符占两个字节;绝大多数汉字(尤其是常用汉字)占用两个字节,个别汉字(在后期加入unicode编码的汉字,一般是极少用到的生僻字)占用四个字节。
UTF8编码中,英文字符占用一个字节;绝大多数汉字占用三个字节,个别汉字占用四个字节。
注意在调用中对集合的传递要区分好值传递和引用传递
通常要用值传递, 不要用引用传递,否则其它部分修改了集合,会导致不可知错误
如
return new ArrayList(a) 或者 tomethod (new ArrayList(a))