Java进阶8课

一.数组与内存控制java

java数组是静态的
数组必须初始化以后才能使用,初始化就是分配内存空间。
 
1.数组必定要初始化吗?
答:不是,能够定义一个数组而后赋值给他。java程序中,全部的引用变量都不须要通过所谓的初始化操做,须要进行初始化的是引用变量所引用的对象。
 
2.在java中,声明一个数组过程当中,是如何分配内存的。
答:定义一个数组则在内存堆里开辟相对应的数组对象。而后进行赋值。最后指向所对应的数组变量。
数组变量都是放在栈内存里,而变量引用的对象则存在堆内存中。 
 
3.java数组的初始化一共有哪几种方式。
答:一共有三种。
静态初始化:就是由程序员定义数组元素,由系统决定数组长度。 
例:String[] names={“老孙”,”老猪”,”老沙”};
例:String[] names = new String[]{“老孙”,”老猪”,”老沙” };
动态初始化:就是由程序员定义数组长度,由系统分配初始值。 
例:String[] names =new String[3]; 
 
4.基本类型数组和引用类型数组之间在初始化时的内存分配机制有什么区别吗?
答:基本数据初始值为0,引用类型初始值为null。
数组扩展
1、数组特色:

1)java是静态语言,所以java数组也是静态的,当数组被初始化以后,数组长度是不可变的
2)java程序中数组必须通过初始化才可以使用(即给数组对象的元素分配内存空间,并赋初始值)。
3)数组能够存储基本数据类型和引用数据类型。
4)数组相比集合最大的优势就是随机访问速度很是快(通常状况下建议仍是使用集合)。
5)多维数组能够看作是一维数组对数组对象引用变量的存储(多维数组效率较低)。程序员

2、数组的两种初始化过程:正则表达式

1.静态初始化:初始化时由程序员显示的指定每一个数组元素的初始值,由系统决定数组长度。
2.动态初始化:初始化数组时由程序员指定数组长度,由系统为数组元素分配初始值。算法

 String[] names = { "张三" , "李四" , "王五" , "赵六"   };
  //静态初始化数组(通常形式)
  String[] names2 =  new String[]{   "刘二利" , "刘双利" , "刘三利"     };
  //动态初始化数组
  String[] std_ids =  new String[ 5 ];

names、names一、std_ids这三个变量是对数组对象的引用,通常引用变量是在方法栈里面声明和初始化的,而这三个变量所引用的对象是在堆里面进行内存分配的。 数组

3、java数组必须初始化才能使用的理解缓存

java数组必须初始化才能使用,一般指的是引用变量所指向的数组对象是否在堆内存中分配了一块连续的内存空间,这快内存空间的长度就是数组长度,以下例子:安全

  int [] appIds = { 1 , 2 , 3 , 4 , 5};
   int [] copyAppIds ;
   copyAppIds = appIds;
   System.out.println(copyAppIds.length);

copyAppIds引用变量没有被初始化,但输出的copyAppIds的长度是5,也就是经过copyAppIds = appIds;让两个引用变量指向了同一个对象。多线程

四.数组栈和堆并发

基本类型变量的值存放在栈内存中,这句话是不正确的,实际上:全部局部变量都是存放在栈内存中保存的,无论其是基本类型的变量,仍是引用类型的变量,都是存储在各自的方法栈区中,但引用类型变量所引用的对象则老是存放在堆内存中的,对于java而言,堆内存中的对象一般是不容许直接访问的,为了访问堆内存中的对象,只能经过引用变量。
五.经常使用数组方法
 
1.声明数组
String[] aArray = new String[5];  
String[] bArray = {"a","b","c", "d", "e"};  
String[] cArray = new String[]{"a","b","c","d","e"};
2. 输出一个数组
String straArray = Arrays.toString(aArray);
3.从数组中建立列表
List<String> arrayList = new ArrayList<String>();
arrayList = Arrays.asList(aArray);
4.检查数组中是否包含特定的值
boolean b = Arrays.asList(aArray).contains("a");
5.链接两个数组
String[] newArray = ArrayUtils.addAll(aArray,bArray);
6.将数组元素加入一个独立字符串中(经过自定义逗号分割形式)
String j = StringUtils.join(aArray,",");
7.将数组列表转为一个数组
  1. String[] stringArray = { "a", "b", "c", "d", "e" };  
  2. ArrayList<String> arrayList = new ArrayList<String>(Arrays.asList(stringArray));  
  3. String[] stringArr = new String[arrayList.size()];  
  4. arrayList.toArray(stringArr);  
  5. for (String s : stringArr)  
  6.     System.out.println(s); 
8.将数组转为一个集合
Set<String> set = new HashSet<String>(Arrays.asList(stringArray));
System.out.println(set);
9.反向数组
ArrayUtils.reverse(aArray);
10.删除数组元素
int[] removed = ArrayUtils.removeElement(bArray, "a");
11.将整数转为byte数组
byte[] bytes = ByteBuffer.allocate(4).putInt(8).array();  
12.填充数组
int  demo[] =  new int [ 10 ];
Arrays.fill(demo,  3 );
13.复制数组
int [] demo2 =  new  int [ 11 ];
System.arraycopy(demo,  0 , demo2,  2 4 );
for ( int  i : demo2) {
     System.out.print(i);
}
14.数组排序
int [] a3 = { 3 2 5 4 1 };
System.out.println(Arrays.toString(a3));
Arrays.sort(a3);
System.out.println(Arrays.toString(a3));
15.一维数组数值检索
int  index1 = Arrays.binarySearch(a3,  4 );
int  index2 = Arrays.binarySearch(a3, - 12 );
int  index3 = Arrays.binarySearch(a3,  15 );
System.out.println(index1 +  " " + index2 +  " " + index3);
 
二.对象与内存控制
1.能够经过实例变量来修改静态变量,以后静态变量都被改变。
static int eyeNum;
Persion.esyNum = 22;
2.一个类被建立,先分配内存空间再赋值,分配内存的顺序 静态代码-实例变量-构造器(功能是赋值)
(赋值顺序,先静态类变量再实例变量,再构造器变量赋值)
*new一个对象,首先在构造器引用以前内存已经分配了实例变量下来,构造器只是为他们进行赋值。
 
4.子类调用父类时,父类构造器若是调用了子类重写的方法,则是子类的方法,还没赋值。(致使访问不到子类变量值)
public class Derived extends Base{
    private int i = 22;
    public Derived(){
        i = 222;
    }
    public void display(){
        System.out.println("123");
        System.out.println(i);
    }
    public static void main(String[] args) {
        new Derived();
    }
 
}
 
class Base{
    private int i =2;
    public Base(){
        System.out.println(i);
        this.display();
    }
    public void display(){
        System.out.println("456");
        System.out.println(i);
    }
 
}
 
输出:
2
123
0
 
5.继承成员变量和继承方法之间存在这样的差别,访问实例变量时,该实例变量的值取决于声明该变量时类型,访问对象方法时,该方法行为取决于它所实际引用的对象的类型。
 
向上转型的例子
  1. public class Demo16 {  
  2.   
  3.     public static void main(String[] args) {  
  4.   
  5.         Base_16 b = new Base_16();  // 1  
  6.         System.out.println(b.count);  //2
  7.         b.display();  //2
  8.           
  9.         Drived_16 d = new Drived_16();  // 2  
  10.         System.out.println(d.count);  //20
  11.         d.display();  //20
  12.           
  13.         Base_16 bd = new Drived_16();  // 3  
  14.         System.out.println(bd.count);  //2
  15.         bd.display();  //20 //
  16.           
  17.         Base_16 b2d = d;  // 4  
  18.         System.out.println(b2d.count);  //2
  19.         b2d.display();  //20
  20.     }  
  21.   
  22. }  
  23. class Base_16{  
  24.     int count = 2;  
  25.     public void display(){  
  26.         System.out.println(this.count);  
  27.     }  
  28. }  
  29. class Drived_16 extends Base_16{  
  30.     int count = 20;  
  31.     @Override  
  32.     public void display(){  
  33.         System.out.println(this.count);  
  34.     }  
 
6.能够用过super.变量或者方法来调用父类的变量或者方法。
 
7.须要获取父类的属性时,也能够经过上转型((Parent)d).tag获取父类的属性。
d.tag //获取子类属性
((Parent)d).tag//获取父类属性
 
8.宏变量是:编译器会把程序全部用到该变量的地方直接替换成该变量的值,称为宏替换。
final
9.定义该变量时指定初始值才有“宏替换”效果。
static final int num = 3; //常量定义
 
10.若是程序须要再匿名内部类中使用局部变量,那么该变量必须使用final修饰符。
 
三.集合
 

1.set 元素无序,集合元素不可重复。 app

2.Map集合是Set集合的扩展,可是Set底层是Map集合,Map的key值就是Set集合 
3.map存储是根据key的hash值和key的值来判断。eq:(e.hash==hash && ((k=e.key) ==key)||key.equals(k)) 
4.HashMap如何取值原理:首先经过hashCode返回值肯定存储位置则放入同一个bucket中,若是这两个Entry的key经过equals为true则,value覆盖,key不覆盖。不然将与原有的Entry造成Entry链,新增的位于链头。 
5.threshold是该变量包含了HashMap能容纳的极限,他的值是size*loadFactor(负载因子)默认0.75 
6.负载因子(load factor) 默认值为0.75,增大负载因子能够减小Hash表(Entry数组)所占用的内存空间,但会增长查询数据的时间开销,而查询是最频繁的操做(get,put都要用到查询); 减小负载因子会提升数据查询的性能,但会增长Hash表所占用的内存空间 

7.HashMap的实际容量是Capacity,是找出大于initialCapacity的,最小的2的n次方值,例如给初始值initialCapacity为10,那么HashMap的实际容量为16;7.HashMap的实际容量是Capacity,是找出大于initialCapacity的,最小的2的n次方值,例如给初始值initialCapacity为10,那么HashMap的实际容量为16

8.TreeSet和TreeMap是采用一课”黑树“来保存,性能比Hash集合底,可是Entry老是按Key根据指定排序规
则保存有序状态。
9.TreeSet和TreeMap是根据排序二叉树的算法排序.
10.TreeMap和HashMap能够返回values的一个conllection集合。不是list集合
11.Vector还有一个Stack子类,这个Stack子类仅再Vector父类基础上增长了5个方法,这五个方法就将一个Vector拓展成Stack。
12.JDK1.6开始java不推荐使用Stack,推荐使用Deque接口,ArrayDeque类来实现它,代替Stack类。Deque接口表明双端队列。具备队列的性质先进先出,也具备栈的性质,也就是双端队列便是队列,也是栈
13.从序列化机制角度来看,ArrayList的实现比Vector的实现更安全,ArrayList能够经过synchronizedList来将一个普通的ArrayList包装成线程安全的ArrayList。
14.ArrayList再添加元素时,必须对底层数组元素进行“总体搬家”。若是添加元素致使集合长度将要超过底层数组长度,ArrayList必须建立一个长度为原来1.5倍的数组,垃圾回收机制将回收原有数组。
15.LinkedList主要开销在entry(int index)方法上,该方法必须一个个地搜索过去,知道找到index处的元素,而后再在该元素以前插入新的元素,即便这样,执行add依旧比ArrayList的性能高。
16.程序把LinkedList当成双端队列,栈只用,调用addFirst(E e),addLast(E e),getFirst(E e),getLast(E e),offer(E e),offerFirst(),offerLast()等方法来操做该集合,
17,ArrayList在查询元素比较快(添加和删除元素须要总体搬家),LinkedList添加,删除元素比较快。查询比较慢(须要检索整个集合)。
 
四.java的内存回收
1.对象在内存中的状态,
可达状态:有一个以上的引用变量引用它。
可恢复状态:程序中某个对象再也不有任何引用变量引用它,他将进入可恢复状态,系统垃圾回收机制准备回收该对象所占用的内存,在回收该对象以前,系统回调用前,系统会调用可恢复状态的对象的finalize方法进行资源清理,若是在调用finalize方法从新让一个以上的引用变量引用该对象,则该变量再次变成可达状态,不然该对象进入不可达状态。
不可达状态:当一个对象处于不可达状态时,系统才会真正回收该对象所占有的资源。
2 .强引用:程序建立一个对象,并把这个对象赋给一个引用变量,这个引用变量就是强引用。JVM确定不会回收强引用所引用的对象,因此强引用时形成java内存泄漏的主要缘由之一。
3 .软引用:软引用须要经过SoftReference类来实现,当内存空间不足时,系统将会回收它。当内存充足时,它的做用和强引用同样,当内存不足时,强引用会报错,而软引用则会自动回收对象不会报错。这就是优点。
4 .弱引用:经过WeakReference类实现,但弱引用比软引用级别更低。当系统垃圾回收机制运行时,无论系统内存是否足够,总会回收该对象所占用的内存。必须等到系统垃圾回收机制运行时才会被回收。WeakHashMap是一个道理。
5. 虚引用:须要经过引用队列ReferenceQueue类结合,经过PhantomReference类来实现。虚引用的主要做用就是跟踪对象被垃圾回收的状态,程序能够经过检查与虚引用关联的引用队列中是否已经包含指定的虚引用,从而了解虚引用所引用对象是否即将被回收。
 
虚引用的做用是,咱们能够声明虚引用来引用咱们感兴趣的对象,在gc要回收的时候,gc收集器会把这个对象添加到referenceQueue,这样咱们若是检测到referenceQueue中有咱们感兴趣的对象的时候,说明gc将要回收这个对象了,此时咱们能够在gc回收以前作一些其余事情,好比记录日志什么的。
 
6.java内存泄漏:存在无用的内存没有被回收回来,那就是内存泄漏。
ArrayList<String> arrayList = new ArrayList<String>();
arrayList[size -1]; 若是不将arrayList[size] = null;加上就会产生内存泄漏。
 
7.垃圾回收机制
垃圾回收器的设计算法:
串行回收和并行回收:串行回收就是无论系统有多少个CPU,始终只回收。bing'xing用一个CPU来执行垃圾回收操做;而并行回收就是把整个回收工做拆分红多部分,妹各部分由一个CPU负责,从而让多个CPU并行回收。并行税后的执行效率很高,可是复杂度增长,另外也有一些反作用,好比内存碎片会增长。
并发执行和应用程序中止:Stop-the-world的垃圾回收方式在执行垃圾回收的同时会致使应用程序的暂停。并发执行的垃圾回收虽然不会致使应用程序的暂停,但因为并发执行垃圾回收须要解决和应用程序的执行冲突,所以并发执行垃圾回收的系统开销比Stop-the-world更高,并且执行时也须要更多的堆内存。
 
压缩和不压缩和复制:为了减小内存碎片,支持压缩的垃圾回收器会把全部的活对象变迁在一块儿,而后将以前占用的内存所有回收。 不压缩式的垃圾回收器只是回收内存,这样回收回来的内存不多是连续的,所以将会有较多的内存碎片。较之压缩式的垃圾回收,不压缩式的垃圾回收回收内存快,而分配内存时就会更慢,并且没法解决内存碎片的问题。 复制式的垃圾回收会将全部可达对象复制到另外一块相同的内存重,这种方式的优势是垃圾回收过程不会产生内存碎片,但缺点很明显,须要复制数据和额外的内存。
详述以下:
复制:将堆内分红两个相同空间,从根开始访问每个关联的可达对象,将空间A的可达对象所有复制到空间B,而后一次性回收整个空间A.(遍历空间的成本较小,但须要巨大的复制成本和较多的内存)
标记清除:就是不压缩回收方式。垃圾回收器先从根开始访问全部可达对象,将他们标记为可达标记,而后再遍历一次整个内存区域,把全部没有标记为可达的对象进行回收处理。(须要内存较少,但形成应用程序暂停的时间多和产生的内存碎片较多)
标记压缩:这就是压缩方式,垃圾回收器先从根开始访问全部对象,将他们标记为可达标记,再将活动对象搬迁在一块儿,这个过程也被称为内存压缩,而后垃圾回收机制再次回收不可达对象占用的内存空间。
 
堆内存的分代回收:
1. young代:采用复制算法只需遍历那些处于可达状态的对象,并且这些对象的数量较少,可复制成本也不大,所以能够充分发挥复制算法的优势。由1个Eden和2个Survivor区构成(fromSurvivor,toSurvivor),每次复制就是将Eden和FromSurvivor的可达对象复制到ToSurvivor区,而后清空Eden和FromSurvivor,将ToSurvivor变成FromSurvivor。
2. Old代:采用标记压缩算法,1.Old代垃圾回收的执行频率无需过高,由于不多由对象会死掉;2.每次对Old代执行垃圾回收须要更长的时间来完成。这个算法有3个阶段:mark(标识可达对象),sweep(清除),compact(压缩).在mark阶段,回收器会识别出哪些对象仍然时可达的,在sweep阶段将会回收不可达对象所占用的内存。在compact阶段回收器执行sliding compaction,把活动对象往Old代的前段启动,而在尾部保留一块连续的空间,以便下次为新对象分配内存空间。
3. Permanent代:不会被回收。
Young代的内存将要用完时,就会对Young代进行垃圾回收,会采用较高的频率进行扫描和回收,由于这种回收的系统开销小,所以被称为次要回收。
当Old代内存将要用完时,将会进行全回收,被称为主要回收。
 
与垃圾回收配置的参数:
-Xmx:设置Java虚拟机堆内存的最大容量,如java -Xmx256m XxxClass.
-Xms:设置Java虚拟机堆内存的初始容量,如java -Xms128m XxxClass.
垃圾回收附加选项:
-XX:MinHeapFreeRatio = 40:设置Java堆内存最小的空闲百分比,默认值为40,如java -XX:MinHeapFreeRatio = 40 XxxClass.
-XX:MaxHeapFreeRatio = 70:设置Java堆内存最大的空闲百分比,默认值为70,如java -XX:MaxHeapFreeRatio = 70 XxxClass.
-XXNewRatio = 2:设置Young/Old内存的比例,如java-Xx:NewRatio = 1 XxxClass.
-XXNewSize = size:设置Young代内存的默认容量,如java -XX:NewSize = 64m XxxClass.
-XX:SurvivorRatio = 8:设置Young代中edin/survivor的比例,如java -XX:SurvivorRatio = 8 XxxClass.
-XX:MaxNewSize = size:设置Young代内存的最大容量,如java -XX:MaxNewSize = 128m XxxClass.
-XX:PermSize = size:设置Permanent代内存的默认容量,如 java - XX:PermSize = 128m XxxClass.
-XX:MaxPermSize = size:设置Permanent代内存的默认Max最大容量,如 java - XX:MaxPermSize = 128m XxxClass.
 
常见垃圾回收器
1.串行回收器:经过运行java程序时使用-XX:+UseSerialGC附加选项启用。

使用一个线程进行串行回收,新生代采用复制算法,老年代使用标记-压缩算法,回收的时候程序所有暂停

串行回收器主要有两个特色:第一,它仅仅使用单线程进行垃圾回收;第二,它独占式的垃圾回收

缺点是:停顿时间长

有点是:久经考验,bug少

串行回收器的工做原理:使用堆内存的分代回收

 

 
2.并行回收器:经过运行java程序使用-XX:+UseParallelGC附加选项启用,能够充分利用计算机的多个CPU来提升垃圾回收吞吐量。
能够用-XX:ParallelGCThreads = size来减小并行程序的数目。
特色:多个CPU并行的机器才能发挥其优点。
新生代ParNew回收器:
(1)特色:
            - 将串行回收器多线程化
            - 使用复制算法
            - 垃圾回收时,应用程序仍会暂停,只不过因为是多线程回收,在多核CPU上,回收效率会高于串行回收器,反之在单核CPU,效率会不如串行回收器
(2)设置参数:
            -XX:+UseParNewGC新生代使用ParNew回收器,老年代使用串行回收器
            -XX:+UseConcMarkSweepGC新生代使用ParNew回收器,老年使用CMS回收器
            -XX:ParallelGCThreads = n指回ParNew回收器工做时的线程数量,cpu核数小时8时,其值等于cpu数量,高于8时,可使用公式(3+((5*CPU_count)/8))
 
新生代ParallelGC回收器:

(1)特色: 
  –同ParNew回收器同样, 不一样的地方在于,它很是关注系统的吞吐量(经过参数控制) 
  –使用复制算法 
  –支持自适应的GC调节策略

(3)设置参数:

-XX:+UseParallelGC  新生代用ParallelGC回收器, 老年代使用串行回收器 
-XX:+UseParallelOldGC  新生代用ParallelGC回收器, 老年代使用ParallelOldGC回收器系统吞吐量的控制: 
-XX:MaxGCPauseMillis=n(单位ms)   设置垃圾回收的最大停顿时间, 
-XX:GCTimeRatio=n(n在0-100之间)  设置吞吐量的大小, 假设值为n, 那系统将花费不超过1/(n+1)的时间用于垃圾回收 
-XX:+UseAdaptiveSizePolicy  打开自适应GC策略, 在这种模式下, 新生代的大小, eden,survivior的比例, 晋升老年代的对象年龄等参数会被自动调整,以达到堆大小, 吞吐量, 停顿时间之间的平衡点

老年代ParallelOldGC回收器

(1)特色: 
  –同新生代的ParallelGC回收器同样, 是属于老年代的关注吞吐量的多线程并发回收器 
  –使用标记压缩算法, 
(2)设置参数: 
-XX:+UseParallelOldGC  新生代用ParallelGC回收器, 老年代使用ParallelOldGC回收器, 是很是关注系统吞吐量的回收器组合, 适合用于对吞吐量要求较高的系统 
-XX:ParallelGCThreads=n   指回ParNew回收器工做时的线程数量, cpu核数小时8时, 其值等于cpu数量, 高于8时, 可使用公式(3+((5*CPU_count)/8))
 
并发标识-清理(Mark-Sweep)回收器(CMS)
 

(1)特色: 
  –是并发回收, 非独占式的回收器, 大部分时候应用程序不会中止运行 
  –针对年老代的回收器, 
  –使用并发标记清除算法, 所以回收后会有内存碎片, 可使参数设置进行内存碎片的压缩整理 
  –与ParallelGC和ParallelOldGC不一样, CMS主要关注系统停顿时间 
(2)CMS主要步骤: 
  1. 初始标记 
  2. 并发标记 
  3. 预清理 
  4. 从新标记 
  5. 并发清理 
  6. 并发重置

–>注:初始标记与理新标记是独占系统资源的,不能与用户线程一块儿执行,而其它阶段则能够与用户线程一块儿执行 
(3)设置参数: 
-XX:-CMSPrecleaningEnabled  关闭预清理, 不进行预清理, 默认在并发标记后, 会有一个预清理的操做,可减小停顿时间 
-XX:+UseConcMarkSweepGC  老年代使用CMS回收器, 新生代使用ParNew回收器 
-XX:ConcGCThreads=n  设置并发线程数量, 
-XX:ParallelCMSThreads=n  同上, 设置并发线程数量, 
-XX:CMSInitiatingOccupancyFraction=n  指定老年代回收阀值, 即当老年代内存使用率达到这个值时, 会执行一次CMS回收,默认值为68, 设置技巧: (Xmx-Xmn)*(100-CMSInitiatingOccupancyFraction)/100)>=Xmn 
-XX:+UseCMSCompactAtFullCollection  开启内存碎片的整理, 即当CMS垃圾回收完成后, 进行一次内存碎片整理, 要注意内存碎片的整理并非并发进行的, 所以可能会引发程序停顿 
-XX:CMSFullGCsBeforeCompation=n  用于指定进行多少次CMS回收后, 再进行一次内存压缩 
-XX:+CMSParallelRemarkEnabled  在使用UseParNewGC 的状况下, 尽可能减小 mark 的时间 
-XX:+UseCMSInitiatingOccupancyOnly  表示只有达到阀值时才进行CMS回收

当一个URL被访问时,内存申请过程以下
A. JVM会试图为相关Java对象在Eden中初始化一块内存区域
B. 当Eden空间足够时,内存申请结束。不然到下一步
C. JVM试图释放在Eden中全部不活跃的对象(这属于1或更高级的垃圾回收), 释放后若Eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区
D. Survivor区被用来做为Eden及OLD的中间交换区域,当OLD区空间足够时,Survivor区的对象会被移到Old区,不然会被保留在Survivor区
E. 当OLD区空间不够时,JVM会在OLD区进行彻底的垃圾收集(0级)
F. 彻底垃圾收集后,若Survivor及OLD区仍然没法存放从Eden复制过来的部分对象,致使JVM没法在Eden区为新对象建立内存区域,则出现”out of memory错误”
 
内存管理的小技巧
1.尽可能使用直接量,String str = "hello" 而不是String str = new String("hello");
2.使用StringBuilder和StringBuffer进行字符串链接
3.尽可能早释放无用的对象的引用
4.尽可能少用静态变量
5.避免在常常调用的方法,循环中建立java对象
6.缓存常用的对象,使用HashMap缓存。
7.尽可能不要用finalize方法,会致使过多频繁的进行资源清理。
8.考虑使用SoftReference软引用。获取对象可能为空,若是为空则从新获取对象。
 
五.表达式中的陷阱
1. JVM对字符串的处理
Java程序中建立对象的常见方式有以下4中
经过new调用构造器建立java对象
经过Class对象的newInstance()方法条用构造器建立java对象
经过java反序列化机制从IO流中恢复Java对象
经过java对象提供的clone()方法复制一个新的java对象
 
Java程序中的字符直接量,JVM会使用一个字符串池来保存它们,当第一次使用某个字符串直接量时,JVM会将它放入字符串池进行缓存。
例: String str1 = "Hello Java";
        String str2 = "Hello Java";
        str1 == str2;//true
例: String str1 = "Hello Java 的长度:10";
        String str2 = "Hello" + "Java" + "的长度:" + 10;
        str1 == str2;//true
例:    String str1 = "Hello Java的长度:10"
        String str2 = "Hello"+"Java" + "的长度:"+"Hello java".length();
        int len = 10;
        String str3 = "Hello"+"Java"+"的长度:"+len;
        str1 == str2;//false 由于包含了方法调用,不能在编译时肯定
        str1 == str3;//false 包含了变量,不能在编译时肯定
例:    String str1 = "Hello Java 的长度:10";
        final String s1 = "Hello";
        String str2 = s1+"Java" + "的长度:10";
        final int len = 10;
        String str3 = "Hello"+"Java"+"的长度:"+len;
        str1 == str2;//true 若是变量是宏变量则能够,由于宏替换
        str1 == str3;//true 若是变量是宏变量则能够,由于宏替换
String str = "Hello" + "Java," + “crazyit.org";//建立了一个对象
 
2. 不可变的字符串
例:    String str = "Hello";
        System.identityHashCode(str);
        str = str + "Java";//建立另外一个对象指向它,字符串不变,引用改变
        System.identityHashCode(str);
        str = str + ",crazyit.org";//建立另外一个对象指向它
        System.identityHashCode(str);
        //三个HashCode都不相等,因此它们不是引用同一个对象。
        // 第一个和第二个对象将会一直在字符串池中,造成Java内存泄漏的缘由之一
         StringBuilder str = new StringBuilder("Hello");
        System.identityHashCode(str);
        str.append("Java");
        System.identityHashCode(str);
        str.append(,crazyit.org);
        System.identityHashCode(str);
        //三个HashCode都相等,它们是同一个StringBuilder对象。
       
3. 字符串比较
equals来比较是否相等
compareTo来比较两个字符串的大小
例: "abc" .compareTo("ax");
abc
ax
程序先将它们左对齐,而后从左向右比较每一个字符。因此ax > abc
 
表达式类型的陷阱
4. 表达式类型的自动提高
当一个算数表达式中包含多个基本类型的值时,整个算数表达式的数据类型将发生自动提高。
全部byte型,short型和char型将被提高到int型
整个算术表达式的数据类型自动提高到与表达式中最高等级操做数一样的类型。操做数的等级排列以下图所示,位于箭头右边类型的等级高于位于箭头左边类型的等级
例: short sValue = 5;
        sValue = sValue -2;//会将sValue转为int类型,因此会发生错误
例: byte b = 40;
        char c = 'a';
        int i = 23;
        double d = 3.314;
        double result = b+c+i*d;//正确,最后转为double类型
例: int val = 3;
        int intResult =23/val;//正确输出7,不能除尽。取整数位。
例: "Hello!"+'a'+7; //Hello!a7 会转成字符串计算
        'a'+7+"Hello!";//104Hello! char会被转成int计算,变成a对应的ASCII值:97。
 
5. 复合赋值运算符的陷阱
复合运算符:+=,-=,*=,/=,%=,<<=,>>=,>>>=,&=,^=,|=等。
例:short sValue = 5;
         sValue -=2; //这就是复合运算符,也不会存在问题。
        等价于 short sValue = (short ) sValue -2;
例: short st = 5;
        st+=90000;//出现 高位截断,由于short只能-32768~32767之间。
例: Object he = new CompositeAssign2();
        String crazy = "crazyit.org ,";
        crazy +=he;//会转为字符串运算,因此正确
        he +=crazy;//由于he不是字符串类型,因此报错
        换为he = he + crazy;//系统会将he转为String类型后与crazy进行链接运算
6. 输入法致使的陷阱
非法字符\12288 是全角空格。编译出错!
非法字符\xxxxx的错误提示,都是有全角字符的错。
7. 注释的字符必须合法
<br/>源码位置:G:\codes\unit5\5.4\Hello.java
不符合Java对Unicode转义字符的要求
8.转义字符的陷阱
慎用字符Unicode转义形式
例: "abc\u000a".length();//将会编译不过,变成换行。
泛型可能引发的错误
9.原始类型变量的赋值
当程序试图访问带泛型声明的集合的集合元素时,编译器老是把集合元素当初泛型类型处理。致使若是类型不对会报错。
10.原始类型带来擦除
例: Apple<Integer> a = new Apple<Integer>();
        Integer as = a.getSize();
         Apple b = a;
        Number size1 = b.getSize();
        Integer size2 = b.getSize();
11.建立泛型数组的陷阱
例: JDK虽然支持泛型,但不容许建立泛型数组。
List<String>[] lsa = new List<String>[10];// 不被容许
例: public class GenericArray<T>{
            class aA {T foo;}
            public GenericArray(){
                A[] as = new A[10];// 建立内部类A的数组
            }
        }
   
正则表达式的陷阱
正则表达式中的点号(.)可匹配任意字符.
String[] strArr = str.split(".");//由于点号(.)可匹配任意字符
String[] strArr = str.split("\\.");//须要将点号分割成多个字符
多线程的陷阱
不要调用run方法,应该使用start方法启动线程。
//Todo
电子邮箱:/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/
()表示一个组
[ ]表示能够出现其中的任意一个字符
?表示前面的条目能够出现0次或者1次
?后面使用\w+表示.号后面必须接字符
()后面的*号表示前面的条目能够出现0次或1次
@后面使用\w+表示后面必须接字符
 
 
六.流程控制的陷阱
 
switch语句陷阱
例: char score = 'c';
switch (score){
    case 'a':{system.out.println(成绩优秀); break;}
    case 'b':{system.out.println(成绩良好); break;}
    case 'c':{system.out.println(成绩中等); break;}
    case 'd':{system.out.println(成绩差劲); break;}
    default : system.out.println("输入成绩错误");
}
1.default分支的执行条件是:表达式的值与前面分支的值都不相等。
2.break的重要性:case 后面不跟break语句,则会出现继续往下执行的状况。
3.switch表达式的类型:byte,short,int,char,enum.
if语句的陷阱
1.else隐含的条件:前面条件都不符合
For循环语句:for循环里有且只能有两个分号做为分隔符。第一个分号以前的是初始化条件,两个分号中间的部分是一个返回boolean的逻辑表达式,当它返回true时for循环才会执行下一次循环;第二个分号以后的是循环迭代部分,每次循环结束后会执行循环迭代部分。
2.循环体的值是有最大值的。若是运用很差会陷入死循环。
 
七.面向对象的陷阱
instanceof运算符的陷阱
定义:它用于判断前面的对象是不是后面的类或其子类,实现类的实例。
例: Object hello = "Hello";
        (hello instanceof Object);//返回true
例: Object str = "Hello";
        Math math = (Math)str;
        (math instanceof String);//不可转换的类型
在编译阶段,强制转型要求被转型变了的编译时类型必须是以下3种状况之一:
    被转型变量的编译时类型与目标类型相同;
    被转型变量的编译时类型是目标类型的父类;
    被转型变量的编译时类型是目标类型的子类,这种状况下能够自动向上转型,无须强制转换。
例: Object obj = new Integer(5);
        String str = (String)obj;
 
在运行阶段,被转型变量所引用对象的实际类型必须是目标类型的实例,或者是目标类型的子类,实现类的实例,不然在运行时将引起ClassCastException异常。
例: String s = null;
        (s instanceof String);//返回false
instanceof运算符除了能够保证某个引用变量是特定类型的实例外,还能够保证该变量没有引用一个null。这样就能够将该引用变量转型为该类型,并调用该类型的方法,而不用担忧会引起ClassCastException或NullPointerException。
构造器的陷阱
在单例中提供readResolve方法则能够避免反序列化Java对象时不是同一个对象。
1.使用反序列化得方式恢复Java对象
让该Java类实现Cloneable接口;为该Java类提供clone()方法,该方法负责进行负责;
2.使用clone方法复制Java对象
无限递归的构造器
1.在定义实例变量时指定实例变量的值是当前类的实例;
2.初始化块中建立当前类的实例;
3.构造器内调用本构造器建立Java对象;
持有当前类的实例
例: public InstanceTest(){}
        public InstanceTest(String name){
            instance = new InstanceTest();
            instance.name = name;
        }
这样能够避免无限递归构造器,但避免不了无限递归toString();
到底调用哪一个重载的方法
例: 方法:public void info(String name,double count){}
        调用:info("crazyit.org",5);
        输出:crazyit.org   5.0
例: 方法:public void info(String name,double count){}
                    public void info(String name,int count){}
        调用:info("crazyit.org",5);        输出:crazyit.org   5
例: 方法:public void info(Object name,double count){}
                    public void info(Object[] name,double count){}
        调用:info(null,5);
        输出:null   5
例: 方法:public void info(Object name,double count){}
                    public void info(Object[] name,int count){}
        调用:info(null,5);
       输出:报错
方法重写的陷阱
重写private方法:不是重写父类的方法,而是从新定义了一个方法。
非静态内部类的陷阱
系统在编译阶段总会为非静态内部类的构造器增长一个参数,非静态内部类的构造器的第一个形参老是外部类。所以调用非静态内部类的构造器时必须传入一个外部类对象做为参数,不然程序将会引起运行时异常。
非静态内部类不能拥有静态成员
非静态内部类的子类:应提供一个无参构造器外部类.this.super();
Static关键字:属于类
静态内部类的限制:当程序使用静态内部类时,外部类至关于静态内部类的一个包,所以使用起来比较方便;但不能访问外部类的非静态成员。
native方法的陷阱:native方法不能跨平台,Thread.sleep(2);是native方法,所以不许确。
 
异常捕捉陷阱
1.正确关闭资源的方式
例: if(os != null){
            try{
                os.close();
            }catch(Exception e){
                e.printStackTrace();
            }
        }
2.finally块的陷阱
System.exit(0);被调用时,虚拟机退出前要执行两项清理工做:
执行系统中注册的全部关闭钩子,
例: Runtime.getRuntime().addShutdownHook{//为系统注册一个关闭钩子
            //执行关闭操做
        }
若是程序调用了Syatem.runFinalizerOnExit(true);那么JVM会对全部还未结束的对象调用Finalizer。
finally块的方法返回值
当程序执行try,catch块时遇到throw语句,会致使该方法当即结束,但不会马上抛异常,先执行finally块,若是finally块使用return来结束方法,系统将不会跳回去执行try块,catch块去抛出异常。
 
3.catch块的用法
进行异常捕获时,必定要记住先捕获小的异常,再捕获大的异常。
不要用catch代替流程控制
程序使用catch捕捉异常时,其实并不能为所欲为地捕捉全部异常。程序能够在任意想捕捉的地方捕捉RuntimeException异常,Exception,但对于其余Checked异常,只有当try块可能抛出该异常时,catch块才能捕捉该Checked异常。
不管如何不要在finally块或catch块中递归调用可能引发异常的方法。
相关文章
相关标签/搜索