简介
在java对象和字段的初始化过程中会遇到哪些安全性问题呢?一起来看看吧。
初始化顺序
根据JLS(Java Language Specification)中的定义,class在初始化过程中,需要同时初始化class中定义的静态初始化程序和在该类中声明的静态字段(类变量)的初始化程序。
而对于static变量来说,如果static变量被定义为final并且它值是编译时常量值,那么该static变量将会被优先初始化。
那么使用了final static变量,是不是就没有初始化问题了呢?
我们来看下面一个例子:
1
2
3
4
5
6
7
8
9
10
11
12
|
public class StaticFiledOrder { private final int result; private static final StaticFiledOrder instance = new StaticFiledOrder(); private static final int intValue= 100 ; public StaticFiledOrder(){ result= intValue - 10 ; } public static void main(String[] args) { System.out.println(instance.result); } } |
输出结果是什么呢?
答案是90。 根据我们提到的规则,intValue是final并且被编译时常量赋值,所以是最先被初始化的,instance调用了StaticFiledOrder类的构造函数,最终导致result的值是90。
接下来,我们换个写法,将intValue改为随机变量:
1
2
3
4
5
6
7
8
9
10
11
12
|
public class StaticFiledOrder { private final int result; private static final StaticFiledOrder instance = new StaticFiledOrder(); private static final int intValue=( int )Math.random()* 1000 ; public StaticFiledOrder(){ result= intValue - 10 ; } public static void main(String[] args) { System.out.println(instance.result); } } |
运行结果是什么呢?
答案是-10。为什么呢?
因为instance在调用StaticFiledOrder构造函数进行初始化的过程中,intValue还没有被初始化,所以它有一个默认的值0,从而导致result的最终值是-10。
怎么修改呢?
将顺序调换一下就行了:
1
2
3
4
5
6
7
8
9
10
11
12
|
public class StaticFiledOrder { private final int result; private static final int intValue=( int )Math.random()* 1000 ; private static final StaticFiledOrder instance = new StaticFiledOrder(); public StaticFiledOrder(){ result= intValue - 10 ; } public static void main(String[] args) { System.out.println(instance.result); } } |
循环初始化
既然static变量可以调用构造函数,那么可不可以调用其他类的方法呢?
看下这个例子:
1
2
3
4
5
6
|
public class CycleClassA { public static final int a = CycleClassB.b+ 1 ; } public class CycleClassB { public static final int b = CycleClassA.a+ 1 ; } |
上面就是一个循环初始化的例子,上面的例子中CycleClassA中的a引用了CycleClassB的b,而同样的CycleClassB中的b引用了CycleClassA的a。
这样循环引用虽然不会报错,但是根据class的初始化顺序不同,会导致a和b生成两种不同的结果。
所以在我们编写代码的过程中,一定要避免这种循环初始化的情况。
不要使用java标准库中的类名作为自己的类名
java标准库中为我们定义了很多非常优秀的类,我们在搭建自己的java程序时候可以很方便的使用。
但是我们在写自定义类的情况下,一定要注意避免使用和java标准库中一样的名字。
这个应该很好理解,就是为了避免混淆。以免造成不必要的意外。
这个很简单,就不举例子了。
不要在增强的for语句中修改变量值
我们在遍历集合和数组的过程中,除了最原始的for语句之外,java还为我们提供了下面的增强的for循环:
1
2
3
4
5
|
for (I #i = Expression.iterator(); #i.hasNext(); ) { {VariableModifier} TargetType Identifier = (TargetType) #i.next(); Statement } |
在遍历的过程中,#i其实相当于一个本地变量,对这个本地变量的修改是不会影响到集合本身的。
我们看一个例子:
1
2
3
4
5
6
7
8
9
|
public void noncompliantUsage(){ int [] intArray = new int []{ 1 , 2 , 3 , 4 , 5 , 6 }; for ( int i: intArray){ i= 0 ; } for ( int i: intArray){ System.out.println(i); } } |
我们在遍历过程中,尝试将i都设置为0,但是最后输出intArray的结果,发现没有任何变化。
所以,一般来说我们需要在增强的for语句中,将#i设置成为final,从而消除这种不必要的逻辑误会。
1
2
3
4
5
6
7
8
|
public void compliantUsage(){ int [] intArray = new int []{ 1 , 2 , 3 , 4 , 5 , 6 }; for ( final int i: intArray){ } for ( int i: intArray){ System.out.println(i); } } |
本文的例子:
learn-java-base-9-to-20/tree/master/security
补充知识:Java中String字符串初始化细节
Java中String类型细节
一 . String两种初始化方式
1 . String str1= “abc”;//String类特有的创建字符对象的方式,更高效
在字符串缓冲区中检测”abc”是否存在
若存在则不重复创建,将地址赋值给str1.
若不存在,则在字符串缓冲区中创建对象并赋地址给str1.
2 . String str1= new String( “abc”); //构造函数初始化
或者
char [] ch={‘a','b','c'};
String str1=new String (ch);
先有 “abc” 对象,然后拷贝给构造函数创建的对象(相当于str1得到的是构造函数的副本)
String对象是不可变的,它的内容不能改变,而在程序中字符串频繁使用,为了提高效率,对具有相同字符串序列的字符串直接量使用同一个实例,这样的实例被称之为限定的(interned)
注意,第二种方式的参数只支持字符串直接量或字符数组创建,这种方式是错误的 String strA = “asd”;
String strbB = new Strint(strA);
比较两种创建方式,第一种更高效,只创建了一个对象,第二种创建了两个对象。
二 . 初始化细节
栈中保存基本类型与对象的引用,基本类型在创建前会查看Stack中是否已经有, 有则赋值指向, 没有则创建。
String str1= “abc”;
String str1= new String( “abc”);
前者首先在栈中创建一个引用型变量str1,然后查看栈中是否存在“abc”如果没有,则将“abc”存放进栈,并令引用变量str指向它;如果有,则直接令str1指向它;后者是java中标准的对象创建方式,其创建的对象将直接放置到堆中,每调用一次就会创建一个新的对象。
String str = “abc”+”def”;
这条语句创建对象个数? 1个。
编译器会自己调用Stringbuilder的append方法来合成abcdef,最后只生成一个对象.
实际上,字符串直接量属于常量,在编译的时候已确定,两个常量相加,先检测栈内存中是否有”abcdef” 如有有,指向已有的栈中的”abcdef”空间,若没有,则创建。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package stringDemo; public class stringInitial { public static void main(String[] args) { String str1 = "abc" ; String str2 = new String( "abc" ); // String str2 = new String(new char[] { 'a', 'b', 'c' }); // String str2 = new String(str1);错误写法 System.out.println(str1 == str2); // false System.out.println(str1.equals(str2)); // true这里的equals()方法已经被覆盖,比较的是字符串不是地址 String str3 = "123" ; String str4 = "abc123" ; String str5 = "abc" + "123" ; String str = str1 + str3; System.out.println(str4 == str5); // true System.out.println(str4 == str1+ "123" ); // false System.out.println(str4 == str); // false } } |
可以看出,只要是+中出现非字符串直接量,就会在堆中产生新的对象,并不会检测栈内存
三.关于String str=null;String str;String str=”“;
String str=null;
声明了一个String的引用型变量并初始化为空,及未指向任何地址,不占用任何空间
String str;
只是声明了一个String的引用型变量,并未初始化(作为对象属性时会有默认的隐式初始化str=null),如果后面未用此变量编译会通过
String str=”“;
正常的字符串初始化,只不过字符串内容为空。
以上这篇java安全编码指南之:声明和初始化说明就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持服务器之家。
原文链接:https://blog.csdn.net/superfjj/article/details/108427680