java基础

1、java的四个基本特性(抽象、继承、封装、多态)
1)抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。

2)继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段(如果不能理解请阅读阎宏博士的《Java与模式》或《设计模式精解》中关于桥梁模式的部分)。

3)封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口(可以想想普通洗衣机和全自动洗衣机的差别,明显全自动洗衣机封装更好因此操作起来更简单;我们现在使用的智能手机也是封装得足够好的,因为几个按键就搞定了所有的事情)。

4)多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。如果将对象的方法视为对象向外界提供的服务,那么运行时的多态性可以解释为:当A系统访问B系统提供的服务时,B系统有多种提供服务的方式,但一切对A系统来说都是透明的(就像电动剃须刀是A系统,它的供电系统是B系统,B系统可以使用电池供电或者用交流电,甚至还有可能是太阳能,A系统只会通过B类对象调用供电的方法,但并不知道供电系统的底层实现是什么,究竟通过何种方式获得了动力)。方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。运行时的多态是面向对象最精髓的东西,要实现多态需要做两件事:1. 方法重写(子类继承父类并重写父类中已有的或抽象的方法);2. 对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。

2、面向对象和面向过程
例:汽车发动、汽车到站
     面向对象:汽车类对象,发动和到站为对象的行为(方法)
     面向过程:汽车发动和汽车到站两个事件,非汽车本身,形成两个函数,依次调用
2.1 区别
1)出发点不同
     面向对象:强调把问题映射到对象及对象之间的接口上
     面向过程:强调过程的抽象化与模块化,以过程为中心构造或处理客观世界问题
2)层次逻辑关系不同
     面向对象:用计算机模拟客观世界中的物理存在,以对象的集合类作为处理问题的基本单位
     面向过程:用模块的层次结构概括模块或模块间的关系与功能,以表达过程的模块作为基本单位
3)数据处理方式与控制程序方式不同
     面向对象:将数据和对应代码封装,只能由自身的成员函数进行修改,通过“事件驱动”来激活和运行程序
     面向过程:直通过程序处理数据,模块之间的相互调用和相互控制
4)分析设计与编码转换方式不同
     面向对象:无缝连接
     面向过程:有缝连接

2.2 面向对象的优点
1)较高的开发效率
2)保证软件的鲁棒性
3)保证软件的高可维护性

2.3 抽象类和接口
相同:
1)都不能实例化
2)实现类和子类实现了方法后才能被实例化
不同:
1)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;
2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的(须赋值);
3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。
5)抽象类中有构造方法,接口中没有构造方法

2.4this和super
this:指向当前实例对象(访问成员变量)
super:访问父类方法或成员变量。子类构造函数显示调用父类构造函数时,super()放在第一句

3、重载(Overload)和重写(Override)
     方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
     重载发生在一个类中, 参数项不同,和访问权限、返回值类型、异常无关
     重写发生在子类与父类之间, 参数项、返回值类型相同,子类访问权限大于父类。

4、面向对象开发六个基本原则()

1)单一职责原则
定义:一个类中应该是一组相关性很高的函数,数据的封装。
做法:根据对业务和需求的理解,去划分一个类,一个函数的职责。

2)开闭原则
定义:对于扩展是开发的,对于修改时封闭的。
做法:出现新的需求时,尽量(注意是尽量)不要修改原有代码。而是对原有代码进行扩展。比如创建一个新的实现类。
手段:通过抽象来实现开闭原则。抽象出一个抽象类或是一个接口。高层模块(调用者)通过依赖抽象使用“依赖注入”(在java中就是调用set方法)来遵循开闭原则。

3)里氏替换原则
定义:所有引用基类的地方必须能透明地使用其子类。通俗的讲,只要父类能出现的地方就一定可以使用其子类。
做法,运用抽象,抽出一个抽象的类或是接口。

4)依赖倒置原则
定义:依赖抽象,不要依赖细节。即申明时尽量使用抽象类型而非具体类型,因为抽象类型可以被其子类替代。
做法:面向接口编程。

5)接口隔原则
定义:高层模块(调用者)不应该依赖它不需要的接口
做法:将非常庞大,臃肿的接口拆分成更小,更具体的接口

6)合成聚合复用
优先使用聚合或合成关系复用代码,这样类和类继承层次小,代码不会臃肿。

迪米特原则原则
定义与做法:只与自己的直接朋友对话。类不要去访问那些远离自己的朋友。
举例:小强的车越造越多,之前用户都是在了解了车的细节后让中间商去为他们买车,为此极为的不方便。幸好这时候中间商告诉用户,只要将车的种类和价格告诉他。他就能到小强那找到一款合适的车给用户。用户很方便的就能得到自己想要的汽车。而且也不再需要到小强造车厂处了解车的细节了。减少了往来的奔波劳累。中间商也明确了自己的定位。

5、关键字(static、final、volatile)
父类静态变量-父类静态代码块-子类静态变量-子类静态代码块-父类非静态变量-父类非静态代码块-父类改造方法-子类非静态变量-子类非静态代码块-子类构造方法

static
     修饰变量:静态变量随类加载时完成初始化,内存中只有一个,JVM只分配一次内存,所有类共享;
     修饰方法:在类加载时存在,不依赖任何实例,static方法必须实现,不能用abstract修饰;
     修饰代码块:在类加载完成后执行代码块中的内容
final
     修饰变量:
          编译期常量:类加载的过程完成初始化,变异后带入到任何计算式中,只能是基本类型
          运行时常量:基本数据类型或引用数据类型,引用不可变,但引用的对象内容可变
     修饰方法:不能被继承,不能被子类修改
     修饰类:不能被继承
     修饰形参:final形参不可变
assert
volatile
     用来修饰被不同线程访问和修改的变量,其定义的变量是从对应的内存中提取,而不利用缓存。所有线程在任何时候看到的变量的值都是相同的。
     声明为volatile的简单变量当前值和变量以前的值相关,volatile不起作用(n=n+1、n++)。
     会阻止编译期对代码的优化,降低执行效率。
     建议使用synchronized关键字对线程进行同步操作。
     
6、集合
6.1、List,Set都是继承自Collection接口,Map则不是。Map是键值对映射容器,与List和Set有明显的区别,而Set存储的零散的元素且不允许有重复元素(数学中的集合也是如此),List是线性结构的容器,适用于按数值索引访问元素的情形。

6.2、List特点:元素有放入顺序,元素可重复
     Set特点:元素无放入顺序,元素不可重复,重复元素会覆盖掉,(注意:元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的,加入Set 的Object必须定义equals()方法 ,另外list支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得想要的值。)

6.3.Set、List、Map对比:
Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。不能存放重复元素(用对象的equals()方法来区分元素是否重复)
List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。以特定索引来存取元素,可有重复元素。
Map:适合储存键值对的数据。保存键值对(key-value pair)映射,映射关系可以是一对一或多对一。

Set和Map容器都有基于哈希存储和排序树的两种实现版本,基于哈希存储的版本理论存取时间复杂度为O(1),而基于排序树版本的实现在插入或删除元素时会按照元素或元素的键(key)构成排序树从而达到排序和去重的效果。

6.4、说出ArrayList、Vector、LinkedList 的存储性能和特性
Arraylist:
优点:ArrayList是实现了基于动态数组的数据结构,因为地址连续,一旦数据存储好了,查询操作效率会比较高(在内存里是连着放的)。
缺点:因为地址连续, ArrayList要移动数据,所以插入和删除操作效率比较低。   
LinkedList:
优点:LinkedList基于链表的数据结构,地址是任意的,所以在开辟内存空间的时候不需要等一个连续的地址,对于新增和删除操作add和remove,LinedList比较占优势。LinkedList 适用于要头尾操作或插入指定位置的场景
缺点:因为LinkedList要移动指针,所以查询操作性能比较低。
适用场景分析:
 当需要对数据进行对此访问的情况下选用ArrayList,当需要对数据进行多次增加删除修改时采用LinkedList。

     ArrayList 和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢, Vector由于使用了synchronized 方法(线程安全),通常性能上较ArrayList 差,而LinkedList 使用双向链表实现存储(将内存中零散的内存单元通过附加的引用关联起来,形成一个可以按序号索引的线性结构,这种链式存储方式与数组的连续存储方式相比,其实对内存的利用率更高),按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。
     Vector属于遗留容器(早期的JDK中使用的容器,除此之外Hashtable、Dictionary、BitSet、Stack、Properties都是遗留容器),现在已经不推荐使用,但是由 于ArrayList和LinkedListed都是非线程安全的,如果需要多个线程操作同一个容器,那么可以通过工具类Collections中的synchronizedList方法将其转换成线程安全的容器后再使用(这其实是装潢模式最好的例子,将已有对象传入另一个类的构造器中创建新的对象来增加新功能)。

6.5、 LinkedList、ArrayList、HashSet是非线程安全的,Vector是线程安全的;
HashMap是非线程安全的,HashTable是线程安全的;
StringBuilder是非线程安全的,StringBuffer是线程安全的

6.6、Collection 和Collections 的区别?
Collection 是一个接口,它是Set、List等容器的父接口;
Collections 是个一个工具类,提供了一系列的静态方法来辅助容器操作,这些方法包括对容器的搜索、排序、线程安全化等等。

6.7、TreeMap和TreeSet在排序时如何比较元素;Collections工具类中的sort()方法如何比较元素
TreeSet要求存放的对象所属的类必须实现Comparable接口,该接口提供了比较元素的compareTo()方法,当插入元素时会回调该方法比较元素的大小。
TreeMap要求存放的键值对映射的键必须实现Comparable接口从而根据键对元素进行排序。

Collections工具类的sort方法有两种重载的形式,第一种要求传入的待排序容器中存放的对象比较实现Comparable接口以实现元素的比较;第二种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是Comparator接口的子类型(需要重写compare方法实现元素的比较),相当于一个临时定义的排序规则,其实就是是通过接口注入比较元素大小的算法,也是对回调模式的应用。

7、String、StringBuffer、StringBuild
     Java 平台提供了两种类型的字符串:String和StringBuffer / StringBuilder,它们可以储存和操作字符串。
     其中String是只读字符串,也就意味着String引用的字符串内容是不能被改变的。而StringBuffer和StringBuilder类表示的字符串对象可以直接进行修改。
     StringBuilder是JDK 1.5中引入的,它和StringBuffer的方法完全相同,区别在于它是在单线程环境下使用的,因为它的所有方面都没有被synchronized修饰,因此它的效率也比StringBuffer略高。
     StringBuilder非线程安全(没有被synchronized修饰 )。
     String:数据量小
     StringBufer:多线程下操作大量数据
     StringBuilder:单线程下操作大量数据

8、java序列化
     把对象转换为字节序列的过程称为对象的序列化。
     把字节序列恢复为对象的过程称为对象的反序列化。

9、多线程
9.1 进程和线程
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是操作系统进行资源分配和调度的一个独立单位;线程是进程的一个实体,是CPU调度和分派的基本单位,是比进程更小的能独立运行的基本单位。
线程的划分尺度小于进程,这使得多线程程序的并发性高;进程在执行时通常拥有独立的内存单元,而线程之间可以共享内存。
使用多线程的编程通常能够带来更好的性能和用户体验,但是多线程的程序对于其他程序是不友好的,因为它占用了更多的CPU资源。

9.2 实现多线程的方式
 继承Thread类;
 实现Runnable接口;
 实现Callable接口(java 5以后)

三种方式的区别
1)实现Runnable接口可以避免java单继承特性带来的局限性;增强程序的健壮性,代码能够被多个线程共享,代码数据是独立的;适合多个相同程序代码的线程区处理同一资源的情况
2)继承Thread类和实现Runnable方法启动线程都是使用start方法,然后JVM虚拟机将此线程放到就绪队列中,如果有处理剂可用,则执行run方法
3)实现Callable接口要实现call方法,并且线程执行完毕后会有返回值。其他的两种都是重写run方法,没有返回值

     启动一个线程是调用start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM 调度并执行,这并不意味着线程就会立即运行。
     run()方法是线程启动后要进行回调(callback)的方法。
     start()方法异步的调用run()方法,直接调用run()方法是同步的,无法实现多线程。

9.3 线程同步相关方法
1)wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
2)sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException 异常;
3)notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
4)notityAll():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争;
5)JDK 1.5通过Lock接口提供了显式(explicit)的锁机制,增强了灵活性以及对线程的协调。Lock接口中定义了加锁(lock())和解锁(unlock())的方法,同时还提供了newCondition()方法来产生用于线程之间通信的Condition对象;
JDK 1.5还提供了信号量(semaphore)机制,信号量可以用来限制对某个共享资源进行访问的线程的数量。在对资源进行访问之前,线程必须得到信号量的许可(调用Semaphore对象的acquire()方法);在完成对资源的访问后,线程必须向信号量归还许可(调用Semaphore对象的release()方法)

sleep()和wait()方法比较:
sleep()方法是线程类(Thread)的静态方法,导致此线程暂停执行指定时间,将执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复(线程回到就绪(ready)状态),因为调用sleep 不会释放对象锁。需要捕获异常。
wait()是Object 类的方法,对此对象调用wait()方法导致本线程放弃对象锁(线程暂停执行),进入等待此对象的等待锁定池,只有针对此对象发出notify 方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入就绪状态。可能被其他对象调用interrupt()方法产生InterruptException异常。

sleep()和yield()方法比较:
1)sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
2)线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态;
3)sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;
4)sleep()方法比yield()方法(跟操作系统相关)具有更好的可移植性。



9.4  synchronized
     synchronized关键字可以将对象或者方法标记为同步,以实现对对象和方法的互斥访问,可以用synchronized(对象) { … }定义同步代码块,或者在声明方法时将synchronized作为方法的修饰符。在第60题的例子中已经展示了synchronized关键字的用法

     一个线程进入一个对象的synchronized方法A之后,其它线程是否可进入此对象的synchronized方法?
     答:不能。其它线程只能访问该对象的非同步方法,同步方法则不能进入。

9.5 同步和异步
同步:获得每一个线程对象的锁,保证同一时刻只有一个线程进入临界区(访问互斥资源的代码块)。只有当拥有该对象锁的线程退出临界区,锁才会被释放,等待队列中优先级最高的线程才能获得该锁,进入共享代码区。
异步:每个线程都包含了运行时自身需要的数据或方法,进行输入输出处理时,不必关心其他线程的状态或行为,也不必等待输入输出处理完毕才返回。
     如果系统中存在临界资源(资源数量少于竞争资源的线程数量的资源),例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就必须进行同步存取(数据库操作中的悲观锁就是最好的例子)。当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。事实上,所谓的同步就是指阻塞式操作,而异步就是非阻塞式操作。

9.6 线程池(thread pool)
     在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁,这就是"池化资源"技术产生的原因。线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。

Java 5+中的Executor接口定义一个执行线程的工具。它的子类型即线程池接口是ExecutorService。要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,因此在工具类Executors面提供了一些静态工厂方法,生成一些常用的线程池,如下所示:

newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
newSingleThreadExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。
如果希望在服务器上使用线程池,强烈建议使用newFixedThreadPool方法来创建线程池,这样能获得更好的性能。

9.7 线程的状态
     除去起始(new)状态和结束(finished)状态,线程有三种状态,分别是:就绪(ready)、运行(running)和阻塞(blocked)。
     其中就绪状态代表线程具备了运行的所有条件,只等待CPU调度(万事俱备,只欠东风);处于运行状态的线程可能因为CPU调度(时间片用完了)的原因回到就绪状态,也有可能因为调用了线程的yield方法回到就绪状态,此时线程不会释放它占有的资源的锁,坐等CPU以继续执行;运行状态的线程可能因为I/O中断、线程休眠、调用了对象的wait方法而进入阻塞状态(有的地方也称之为等待状态);而进入阻塞状态的线程会因为休眠结束、调用了对象的notify方法或notifyAll方法或其他线程执行结束而进入就绪状态。
     注意:调用wait方法会让线程进入等待池中等待被唤醒,notify方法或notifyAll方法会让等待锁中的线程从等待池进入等锁池,在没有得到对象的锁之前,线程仍然无法获得CPU的调度和执行。


9.8 终止线程的方法
1)stop()
     用Thread.stop(),会释放已经锁定的所有监视资源。
2)suspend()
     调用此方法不会释放锁,挂起有锁线程时,在锁恢复之前不会被释放,导致死锁(两个或以上进程争夺资源造成互相等待,如无外力作用将无法推进)。
3)自行结束(执行完run()方法)
     调用interrupt()方法产生InterruptException异常(thread.interrupt())

9.9 synchronized 和java.util.concurrent.locks.Lock的异同
Lock是Java 5以后引入的新的API
和关键字synchronized相比主要相同点:Lock 能完成synchronized所实现的所有功能;
主要不同点:Lock 有比synchronized 更精确的线程语义和更好的性能。synchronized 会自动释放锁,而Lock 一定要求程序员手工释放,并且必须在finally 块中释放(这是释放外部资源的最好的地方)。

9.10 案例

/*下面的例子演示了100个线程同时向一个银行账户中存入1元钱,
在没有使用同步机制和使用同步机制情况下的执行情况。*/

//银行账户类:
public class Account {
    private double balance;     // 账户余额

    /**
     * 存款
     * @param money 存入金额
     */
    public void deposit(double money) {
        double newBalance = balance + money;
        try {
            Thread.sleep(10);   // 模拟此业务需要一段处理时间
        }
        catch(InterruptedException ex) {
            ex.printStackTrace();
        }
        balance = newBalance;
    }

    /**
     * 获得账户余额
     */
    public double getBalance() {
        return balance;
    }
}

//存钱线程类:
public class AddMoneyThread implements Runnable {
    private Account account;    // 存入账户
    private double money;       // 存入金额

    public AddMoneyThread(Account account, double money) {
        this.account = account;
        this.money = money;
    }

    @Override
    public void run() {
        account.deposit(money);
    }

}

//测试类:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test01 {

    public static void main(String[] args) {
        Account account = new Account();
        ExecutorService service = Executors.newFixedThreadPool(100);

        for(int i = 1; i <= 100; i++) {
            service.execute(new AddMoneyThread(account, 1));
        }

        service.shutdown();

        while(!service.isTerminated()) {}

        System.out.println("账户余额: " + account.getBalance());
    }
}

同步优化:
/*在没有同步的情况下,执行结果通常是显示账户余额在10元以下,
出现这种状况的原因是,当一个线程A试图存入1元的时候,
另外一个线程B也能够进入存款的方法中,
线程B读取到的账户余额仍然是线程A存入1元钱之前的账户余额,
因此也是在原来的余额0上面做了加1元的操作,
同理线程C也会做类似的事情,所以最后100个线程执行结束时,
本来期望账户余额为100元,但实际得到的通常在10元以下。
解决这个问题的办法就是同步,当一个线程对银行账户存钱时,
需要将此账户锁定,待其操作完成后才允许其他的线程进行操作,代码有如下几种调整方案:*/

1. 在银行账户的存款(deposit)方法上同步(synchronized)关键字
public class Account {
    private double balance;     // 账户余额

    /**
     * 存款
     * @param money 存入金额
     */
    public synchronized void deposit(double money) {
        double newBalance = balance + money;
        try {
            Thread.sleep(10);   // 模拟此业务需要一段处理时间
        }
        catch(InterruptedException ex) {
            ex.printStackTrace();
        }
        balance = newBalance;
    }

    /**
     * 获得账户余额
     */
    public double getBalance() {
        return balance;
    }
}

//2. 在线程调用存款方法时对银行账户进行同步
public class AddMoneyThread implements Runnable {
    private Account account;    // 存入账户
    private double money;       // 存入金额

    public AddMoneyThread(Account account, double money) {
        this.account = account;
        this.money = money;
    }

    @Override
    public void run() {
        synchronized (account) {
            account.deposit(money);
        }
    }

}

//3. 通过JDK 1.5显示的锁机制,为每个银行账户创建一个锁对象,在存款操作进行加锁和解锁的操作

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Account {
    private Lock accountLock = new ReentrantLock();
    private double balance; // 账户余额

    /**
     * 存款
     *
     * @param money
     *            存入金额
     */
    public void deposit(double money) {
        accountLock.lock();
        try {
            double newBalance = balance + money;
            try {
                Thread.sleep(10); // 模拟此业务需要一段处理时间
            }
            catch (InterruptedException ex) {
                ex.printStackTrace();
            }
            balance = newBalance;
        }
        finally {
            accountLock.unlock();
        }
    }

    /**
     * 获得账户余额
     */
    public double getBalance() {
        return balance;
    }
}
//按照上述三种方式对代码进行修改后,重写执行测试代码Test01,将看到最终的账户余额为100元。


10、JVM
10.1 组成
  Method Area(Non-Heap)(方法区)
  Heap(堆)
  VM Stack(栈)
  Native Method Stack (本地方法栈)
  Program Counter Register(程序计数器)

  方法区和堆线程共享;
  栈、本地方法栈、程序计数器非线程共享

10.1.1 堆
  所有通过new创建的对象的内存都在堆中分配划分为新生代和旧生代
  1)新生代
     分为Eden和Survivo区,Survivor区由From Space和To Space组成。新建的对象都用新生代分配内存,Eden空间不足的时候,会把存活的对象转移到Survivor中。
  2)旧生代
     用于存放新生代中经过多次垃圾回收仍然存活的对象。

10.1.2 栈
     没个线程执行没个方法的时候都会在栈中申请一个栈帧,每个栈帧包括局部变量去和操作数栈,用于存放此次方法调用过程中的临时变量、参数和中间结果。生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束,该栈就 Over,所以不存在垃圾回收。

10.1.3 本地方法栈
     用于支持native方法的执行,存储每个native方法调用的状态。

10.1.4 方法区
     存放了要加载的类信息、静态变量、final类型的常量、属性和方法信息。jvm用持久代来存放方法区。

10.1.5 程序计数器
     程序计数器是一块较小的内存区域,作用可以看做是当前线程执行的字节码的位置指示器。分支、循环、跳转、异常处理和线程恢复等基础功能都需要依赖这个计算器来完成,不多说。

10.2 内存调优
  2.1新生代过小
     新生代GC频繁,增大系统小号;导致大对象直接进入旧生代,占据旧生代剩余空间,引发Full GC
  2.2新生代过大
     旧生代过小(堆总量一定),诱发Full GC;新生代GC耗时增加。一般来说,新生代占堆总量1/3
  2.3Survivor过小
     导致对象从eden直接进入旧生代,降低在新生代存活时间
  2.4Survivor过大
     eden过小,增加GC频率

10.3 常见内存配置
  -Xms:初始堆大小
  -Xmx:最大堆大小
  -XX:NewSize=n:新生代大小
  -XX:NewRatio=n:新生代和旧生代比值
  -XX:SurvivorRatio=n:Eden和两个Survivor区比值
  -XX:MaxPermSize=n:持久代大小

10.4 GC(收集器)设置
  -XX:+UseSerialGC:设置串行收集器
  -XX:+UseParallelGC:设置并行收集器
  -XX:+UseParallelOldGC:设置并行旧生代收集器
  -XX:+UseConcMarkSweepGC:设置并发收集器

10.5 jvm运行
  第 1 步 、向操作系统申请空闲内存。把内存段的起始地址和终止地址给 JVM,JVM 准备加载类文件。
  第 2 步,分配内存内存。JVM 分配内存。JVM 获得到 内存,首先给 heap 分个内存,然后给栈内存也分配好。
  第 3 步,文件检查和分析class 文件。若发现有错误即返回错误。
  第 4 步,加载类。加载类。由于没有指定加载器,JVM 默认使用 bootstrap 加载器,就把 rt.jar 下的所有类都加载到了堆类存的Method Area,JVMShow 也被加载到内存中。
  第 5 步、执行方法。执行 main 方法。执行启动一个线程,开始执行 main 方法
  第 6 步,释放内存。释放内存。

11、垃圾回收
11.1 内存泄露:指该内存空间使用完毕后未回收,在不涉及复杂数据结构的一般情况下,java的内存泄露表现为一个内存对象的生命周期超出了程序需要它的时间长度,我们有是也将其称为“对象游离”;
java语言规范没有明确的说明JVM 使用哪种垃圾回收算法,但是任何一种垃圾回收算法一般要做两件基本事情:(1)发现无用的信息对象;(2)回收将无用对象占用的内存空间。使该空间可被程序再次使用。


11.2 垃圾回收机制算法

  2.1.引用计数法(Reference Counting Collector)

  2.2.tracing算法(Tracing Collector) 或 标记-清除算法(mark and sweep)

  2.3.compacting算法 或 标记-整理算法( Mark-Compact)

  2.4. copying算法(Compacting Collector)

  2.5. generation算法(Generational Collector)
     分代的垃圾回收策略,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的回收算法,以便提高回收效率。
    2.5.1年轻代(Young Generation)
1.所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。
2.新生代内存按照8:1:1的比例分为一个eden区和两个survivor(survivor0,survivor1)区。一个Eden区,两个 Survivor区(一般而言)。大部分对象在Eden区中生成。回收时先将eden区存活对象复制到一个survivor0区,然后清空eden区,当这个survivor0区也存放满了时,则将eden区和survivor0区存活对象复制到另一个survivor1区,然后清空eden和这个survivor0区,此时survivor0区是空的,然后将survivor0区和survivor1区交换,即保持survivor1区为空, 如此往复。
3.当survivor1区不足以存放 eden和survivor0的存活对象时,就将存活对象直接存放到老年代。若是老年代也满了就会触发一次Full GC,也就是新生代、老年代都进行回收
4.新生代发生的GC也叫做Minor GC,MinorGC发生频率比较高(不一定等Eden区满了才触发)

5.新生代存活时间较短,因此基于Copying算法进行回收。
    2.5.2 年老代(Old Generation)
1.在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。
2.内存比新生代也大很多(大概比例是1:2),当老年代内存满时触发Major GC即Full GC,Full GC发生频率比较低,老年代对象存活时间比较长,存活率标记高。

3.存活时间较长,采用Mark算法进行回收。
   2.5.3 持久代(Permanent Generation)
       用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动 态生成或者调用一些class,例如Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。永对久代的回收主要回收两部分内容:废弃常量和无用的类。

11.3 GC(垃圾收集器)

  新生代收集器使用的收集器:Serial、PraNew、Parallel Scavenge

  老年代收集器使用的收集器:Serial Old、Parallel Old、CMS

  HotSpot(JDK 7)虚拟机提供的几种垃圾收集器,用户可以根据自己的需求组合出各个年代使用的收集器。

  3.1.Serial/Serial Old
     Serial/Serial Old收集器是最基本最古老的收集器,它是一个单线程收集器,并且在它进行垃圾收集时,必须暂停所有用户线程。Serial收集器是针对新生代的收集器,采用的是Copying算法,Serial Old收集器是针对老年代的收集器,采用的是Mark-Compact算法。它的优点是实现简单高效,但是缺点是会给用户带来停顿。

  3.2.ParNew
     ParNew收集器是Serial收集器的多线程版本,使用多个线程进行垃圾收集。

  3.3.Parallel Scavenge
     Parallel Scavenge收集器是一个新生代的多线程收集器(并行收集器),它在回收期间不需要暂停其他用户线程,其采用的是Copying算法,该收集器与前两个收集器有所不同,它主要是为了达到一个可控的吞吐量。

  3.4.Parallel Old
     Parallel Old是Parallel Scavenge收集器的老年代版本(并行收集器),使用多线程和Mark-Compact算法。

3.5.CMS
     CMS(Current Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,它是一种并发收集器,采用的是Mark-Sweep算法。

  3.6.G1
     G1收集器是当今收集器技术发展最前沿的成果,它是一款面向服务端应用的收集器,它能充分利用多CPU、多核环境。因此它是一款并行与并发收集器,并且它能建立可预测的停顿时间模型。


11.4 GC的执行
     由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。GC有两种类型:Scavenge GC和Full GC。

  4.1Scavenge GC
     一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。
  4.2Full GC
     对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个堆进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。有如下原因可能导致Full GC:
     1.年老代(Tenured)被写满
     2.持久代(Perm)被写满
     3.System.gc()被显示调用
     4.上一次GC之后Heap的各域分配策略动态变化

11.5 Java有了GC同样会出现内存泄露问题
  5.1.静态集合类像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,所有的对象Object也不能被释放,因为他们也将一直被Vector等应用着
  5.2.各种连接,数据库连接,网络连接,IO连接等没有显示调用close关闭,不被GC回收导致内存泄露。
  5.3.监听器的使用,在释放对象的同时没有相应删除监听器的时候也可能导致内存泄露。

12、异常
12.1 return和final

     Java允许在finally中改变返回值的做法是不好的,因为如果存在finally代码块,try中的return语句不会立马返回调用者,而是记录下返回值待finally代码块执行完毕之后再向调用者返回其值,然后如果在finally中修改了返回值,这会对程序造成很大的困扰,C#中就从语法上规定不能做这样的事。

12.2 异常中的关键字

     Java 通过面向对象的方法进行异常处理,把各种不同的异常进行分类,并提供了良好的接口。在Java 中,每个异常都是一个对象,它是Throwable 类或其子类的实例。当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并进行处理。Java 的异常处理是通过5 个关键词来实现的:try、catch、throw、throws和finally。一般情况下是用try来执行一段程序,如果出现异常,系统会抛出(throw)一个异常,这时候你可以通过它的类型来捕捉(catch)它,或最后(finally)由缺省处理器来处理;try用来指定一块预防所有“异常”的程序;catch 子句紧跟在try块后面,用来指定你想要捕捉的“异常”的类型;throw 语句用来明确地抛出一个“异常”;throws用来标明一个成员函数可能抛出的各种“异常”;finally 为确保一段代码不管发生什么“异常”都被执行一段代码;可以在一个成员函数调用的外面写一个try语句,在这个成员函数内部写另一个try语句保护其他代码。每当遇到一个try 语句,“异常”的框架就放到栈上面,直到所有的try语句都完成。如果下一级的try语句没有对某种“异常”进行处理,栈就会展开,直到遇到有处理这种“异常”的try 语句。

12.3 运行时异常与受检异常有何异同?

     异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误,只要程序设计得没有问题通常就不会发生。受检异常跟程序运行的上下文环境有关,即使程序设计无误,仍然可能因使用的问题而引发。Java编译器要求方法必须声明抛出可能发生的受检异常,但是并不要求必须声明抛出未被捕获的运行时异常。异常和继承一样,是面向对象程序设计中经常被滥用的东西,神作《Effective Java》中对异常的使用给出了以下指导原则:

1)不要将异常处理用于正常的控制流(设计良好的API不应该强迫它的调用者为了正常的控制流而使用异常)
2)对可以恢复的情况使用受检异常,对编程错误使用运行时异常
3)避免不必要的使用受检异常(可以通过一些状态检测手段来避免异常的发生)
4)优先使用标准的异常
5)每个方法抛出的异常都要有文档
6)保持异常的原子性
7)不要在catch中忽略掉捕获到的异常

12.4  列出一些你常见的运行时异常?

ArithmeticException(算术异常)

ClassCastException (类转换异常)

IllegalArgumentException (非法参数异常)

IndexOutOfBoundsException (下表越界异常)

NullPointerException (空指针异常)

SecurityException (安全异常)

13、java中NIO、BIO、AIO
BIO:
     同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
     BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。

NIO:
     同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。用户进程也需要时不时的询问IO操作是否就绪,这就要求用户进程不停的去询问。
     NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。

AIO:
     在此种模式下,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,因为真正的IO读取或者写入操作已经由内核完成了。
    AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。  

14、设计模式
14.1 单例模式(java.lang.Runtime)
     保证在整个应用程序的生命周期中,任何一个时刻,单例类的实例只存在一个(或不存在)
     单例模式优点:
          减少创建Java实例所带来的系统开销
          便于系统跟踪单个Java实例的生命周期、实例状态等。
     注意事项:
          尽量使用懒加载
          双重检索线程安全
          构造方法为private
          定义静态的Singleton instance对象和getInstance()方法

/**
 *
 * 单例模式的实现:饿汉式,线程安全 但效率比较低
 */
public class SingletonTest {

    private SingletonTest() {
    }

    private static final SingletonTest instance = new SingletonTest();

    public static SingletonTest getInstancei() {
        return instance;
    }

}

/**
 *
 * 单例模式的实现:饿汉式,线程安全 但效率比较低
 */
public class SingletonTest {

    private SingletonTest() {
    }

    private static final SingletonTest instance = new SingletonTest();

    public static SingletonTest getInstancei() {
        return instance;
    }

}

/**
 * 单例模式的实现:饱汉式,非线程安全
 *
 */
public class SingletonTest {
    private SingletonTest() {
    }

    private static SingletonTest instance;

    public static SingletonTest getInstance() {
        if (instance == null)
            instance = new SingletonTest();
        return instance;
    }
}

/**
 * 单例模式的实现:饱汉式,非线程安全
 *
 */
public class SingletonTest {
    private SingletonTest() {
    }

    private static SingletonTest instance;

    public static SingletonTest getInstance() {
        if (instance == null)
            instance = new SingletonTest();
        return instance;
    }
}

/**
 * 线程安全,但是效率非常低
 * @author vanceinfo
 *
 */
public class SingletonTest {
    private SingletonTest() {
    }

    private static SingletonTest instance;

    public static synchronized SingletonTest getInstance() {
        if (instance == null)
            instance = new SingletonTest();
        return instance;
    }
}

/**
 * 线程安全,但是效率非常低
 * @author vanceinfo

 *
 */
public class SingletonTest {
    private SingletonTest() {
    }

    private static SingletonTest instance;

    public static synchronized SingletonTest getInstance() {
        if (instance == null)
            instance = new SingletonTest();
        return instance;
    }
}

/**
 * 线程安全  并且效率高
 *
 */
public class SingletonTest {
    private static SingletonTest instance;

    private SingletonTest() {
    }

    public static SingletonTest getIstance() {
        if (instance == null) {
            synchronized (SingletonTest.class) {
                if (instance == null) {
                    instance = new SingletonTest();
                }
            }
        }
        return instance;
    }
}

14.2 工厂模式
     1)简单工厂(Simple Factory)
           由一个工厂对象决定创建出哪一种产品类的实例
     2)工厂方法(Factory Method)
          定义一个用于创建产品对象的工厂的接口,而将实际创建工作推迟到工厂接口的子类中(使用多态)。
     3)抽象工厂(Abstract Factory)
          有多个抽象角色时使用的一种工厂模式,抽象工厂模式可以向客户端提供一个接口,使客户端在不必指定产品的具体情况下,创建多个产品族中产品对象。
14.3 适配器模式
     将一个类的接口转换成客户希望的另外一个接口,使得原本由于不兼容而不能一起工作的类可以一起工作。
14.4 装饰者模式(IO流)
     动态地给一个对象增加一些额外的职责,就增加功能来说,此模式生成子类更加灵活。
14.5 代理(RMI)
     当客户端代码需要调用某个对象时,客户端实际上不关心是否准确得到该对象,它只要一个能提供该功能的对象即可,此时我们就可返回该对象的代理(Proxy)
14.6 迭代器模式(Interator)
     提供一个方法顺序访问一个聚合对象的各个元素,而又不需要暴露改对象的内部表示。
15、内部类

15.1 内部类特性
使用内部类最大的优点就在于它能够非常好的解决多重继承的问题,使用内部类还能够为我们带来如下特性 :

 1)内部类可以用多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独。
 2)在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类。
 3)创建内部类对象的时刻并不依赖于外围类对象的创建
 4)内部类并没有令人迷惑的“is-a”关系,他就是一个独立的实体。
 5)内部类提供了更好的封装,除了该外围类,其他类都不能访问。

15.2 内部类分类

2.1成员内部类

public class Outer{
        private int age = 99;
        String name = "Coco";
        public class Inner{
            String name = "Jayden";
            public void show(){
                System.out.println(Outer.this.name);
                System.out.println(name);
                System.out.println(age);
            }
        }
        public Inner getInnerClass(){
            return new Inner();
        }
        public static void main(String[] args){
            Outer o = new Outer();
            Inner in = o.new Inner();
            in.show();
        }
    }
1)Inner 类定义在 Outer 类的内部,相当于 Outer 类的一个成员变量的位置,Inner 类可以使用任意访问控制符,如 public 、 protected 、 private 等

2)Inner 类中定义的 show() 方法可以直接访问 Outer 类中的数据,而不受访问控制符的影响,如直接访问 Outer 类中的私有属性age

3)定义了成员内部类后,必须使用外部类对象来创建内部类对象,而不能直接去 new 一个内部类对象,即:内部类 对象名 = 外部类对象.new 内部类( );

4)编译上面的程序后,会发现产生了两个 .class 文件: Outer.class,Outer$Inner.class{}

5)成员内部类中不能存在任何 static 的变量和方法,可以定义常量:
(1).因为非静态内部类是要依赖于外部类的实例,而静态变量和方法是不依赖于对象的,仅与类相关,简而言之:在加载静态域时,根本没有外部类,所在在非静态内部类中不能定义静态域或方法,编译不通过; 非静态内部类的作用域是实例级别
(2).常量是在编译器就确定的,放到所谓的常量池了

★★注意:
1.外部类是不能直接使用内部类的成员和方法的,可先创建内部类的对象,然后通过内部类的对象来访问其成员变量和方法;
2.如果外部类和内部类具有相同的成员变量或方法,内部类默认访问自己的成员变量或方法,如果要访问外部类的成员变量,可以使用 this 关键字,如:Outer.this.name

2.2静态内部类
静态内部类: 是 static 修饰的内部类

1)静态内部类不能直接访问外部类的非静态成员,但可以通过 new 外部类().成员 的方式访问

2)如果外部类的静态成员与内部类的成员名称相同,可通过“类名.静态成员”访问外部类的静态成员;如果外部类的静态成员与内部类的成员名称不相同,则可通过“成员名”直接调用外部类的静态成员

3)创建静态内部类的对象时,不需要外部类的对象,可以直接创建 内部类 对象名 = new 内部类();

private int age = 99;
    static String name = "Coco";

    public static class Inner {
        String name = "Jayden";

        public void show() {
            System.out.println(Outer.name);
            System.out.println(name);
            System.out.println(new Outer().age);
        }
    }

    public static void main(String[] args) {
        Inner i = new Inner();
        i.show();
    }

2.3局部内部类
局部(方法)内部类:访问仅限于方法内或者该作用域内
1)局部内部类就像是方法里面的一个局部变量一样,是不能有 public、protected、private 以及 static 修饰符的。
2)只能访问方法中定义的 final 类型的局部变量,因为:当方法被调用运行完毕之后,局部变量就已消亡了。但内部类对象可能还存在, 直到没有被引用时才会消亡。此时就会出现一种情况,就是内部类要访问一个不存在的局部变量;==>使用final修饰符不仅会保持对象的引用不会改变,而且编译器还会持续维护这个对象在回调方法中的生命周期.局部内部类并不是直接调用方法传进来的参数,而是内部类将传进来的参数通过自己的构造器备份到了自己的内部,自己内部的方法调用的实际是自己的属性而不是外部类方法的参数;防止被篡改数据,而导致内部类得到的值不一致

/*
 使用的形参为何要为 final???
 在内部类中的属性和外部方法的参数两者从外表上看是同一个东西,但实际上却不是,所以他们两者是可以任意变化的,
 也就是说在内部类中我对属性的改变并不会影响到外部的形参,而然这从程序员的角度来看这是不可行的,
 毕竟站在程序的角度来看这两个根本就是同一个,如果内部类该变了,而外部方法的形参却没有改变这是难以理解
 和不可接受的,所以为了保持参数的一致性,就规定使用 final 来避免形参的不改变
 */
public class Outer {
    public void Show() {
        int a = 25;
        int b = 13;
        class Inner {
            int c = 2;

            public void print() {
                System.out.println("访问外部类:" + a);
                System.out.println("访问内部类:" + c);
            }
        }
        Inner i = new Inner();
        i.print();
    }

    public static void main(String[] args) {
        Outer o = new Outer();
        o.Show();
    }
}


2.4匿名内部类

1)匿名内部类是直接使用 new 来生成一个对象的引用;匿名内部类没有类的名称
2)对于匿名内部类的使用它是存在一个缺陷的,就是它仅能被使用一次,创建匿名内部类时它会立即创建一个该类的实例,该类的定义会立即消失,所以匿名内部类是不能够被重复使用;
3)使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或者实现一个接口;
4)匿名内部类中是不能定义构造函数的,匿名内部类中不能存在任何的静态成员变量和静态方法;
5)匿名内部类中不能存在任何的静态成员变量和静态方法,匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法
6)匿名内部类初始化:使用构造代码块!利用构造代码块能够达到为匿名内部类创建一个构造器的效果

public class OuterClass {
    public InnerClass getInnerClass(final int   num,String str2){
        return new InnerClass(){
            int number = num + 3;
            public int getNumber(){
                return number;
            }
        };        /* 注意:分号不能省 */
    }
    public static void main(String[] args) {
        OuterClass out = new OuterClass();
        InnerClass inner = out.getInnerClass(2, "chenssy");
        System.out.println(inner.getNumber());
    }
}
interface InnerClass {
    int getNumber();
}

16、自动类型的装箱和拆箱
16.1 什么是自动装箱和拆箱

自动装箱就是Java自动将原始类型值转换成对应的对象,比如将int的变量转换成Integer对象,这个过程叫做装箱,反之将Integer对象转换成int类型值,这个过程叫做拆箱。因为这里的装箱和拆箱是自动进行的非人为转换,所以就称作为自动装箱和拆箱。原始类型byte,short,char,int,long,float,double和boolean对应的封装类为Byte,Short,Character,Integer,Long,Float,Double,Boolean。

16.2 自动装箱拆箱要点

自动装箱时编译器调用valueOf将原始类型值转换成对象,同时自动拆箱时,编译器通过调用类似intValue(),doubleValue()这类的方法将对象转换成原始类型值。
自动装箱是将boolean值转换成Boolean对象,byte值转换成Byte对象,char转换成Character对象,float值转换成Float对象,int转换成Integer,long转换成Long,short转换成Short,自动拆箱则是相反的操作。


public class AutoboxingTest {

    public static void main(String args[]) {

        int i1 = 1;
        int i2 = 1;
        System.out.println("i1==i2 : " + (i1 == i2)); // true

        Integer num1 = 1; // autoboxing
        int num2 = 1;
        System.out.println("num1 == num2 : " + (num1 == num2)); // true

        Integer obj1 = 1;
        Integer obj2 = 1;

        System.out.println("obj1 == obj2 : " + (obj1 == obj2)); // true

        Integer one = new Integer(1);
        Integer anotherOne = new Integer(1);
        System.out.println("one == anotherOne : " + (one == anotherOne)); // false

    }

}

Output:
i1==i2 : true
num1 == num2 : true
obj1 == obj2 : true
one == anotherOne : false



public static void main(String args[]) {
        Integer a=100;
        Integer b=100;
        Integer c=200;
        Integer d=200;

        System.out.println(a==b);//true
        System.out.println(a==100);//ture
        System.out.println(c==d);//false
        System.out.println(c==200);//true

        //2和4进行自动拆箱为int型,"=="比较的是值 ;1和3比较的是引用,自动装箱,
        //编译时Integer a=100,翻译成Integer a=Integer.valueOf(100),
        //值在-128-127时,直接从缓存中返回已经存在的"100"对象,否则就new一个Integer对象。

    }

使用Integer a = 1;或Integer a = Integer.valueOf(1); 在值介于-128至127直接时,作为基本类型。
使用Integer a = new Integer(1); 时,无论值是多少,都作为对象。

17、java类加载机制
17.1 Java中的所有类,必须被装载到jvm中才能运行,这个装载工作是由jvm中的类装载器完成的
     JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。
     由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。
     最后JVM对类进行初始化,包括:
     1)如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;
     2)如果类中存在初始化语句,就依次执行这些初始化语句。

     类的加载是由类加载器完成的,类加载器包括: 根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader的子类)。从Java 2(JDK 1.2)开始,类加载过程采取了父亲委托机制(PDM)。PDM更好的保证了Java平台的安全性,在该机制中,JVM自带的Bootstrap是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM不会向Java程序提供对Bootstrap的引用。下面是关于几个类加载器的说明:

     Bootstrap:一般用本地代码实现,负责加载JVM基础核心类库(rt.jar);
     Extension:从java.ext.dirs系统属性所指定的目录中加载类库,它的父加载器是Bootstrap;
     System:又叫应用类加载器,其父类是Extension。它是应用最广泛的类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中记载类,是用户自定义加载器的默认父加载器。
17.2 java中的类大致分为三种: 

    1.系统类 

    2.扩展类 

    3.由程序员自定义的类
17.3 类装载方式,有两种 

    1.隐式装载, 程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中, 

    2.显式装载, 通过class.forname()等方法,显式加载需要的类 
     隐式加载与显式加载的区别: 两者本质是一样的!都是加载到JVM虚拟机中。
17.4 类加载的动态性体现 

        一个应用程序总是由n多个类组成,Java程序启动时,并不是一次把所有的类全部加载后再 

运行,它总是先把保证程序运行的基础类一次性加载到jvm中,其它类等到jvm用到的时候再加载,这样的好处是节省了内存的开销,因为java最早就是为嵌入式系统而设计的,内存宝贵,这是一种可以理解的机制,而用到时再加载这也是java动态性的一种体现

 
17.5 java类装载器 

    Java中的类装载器实质上也是类,功能是把类载入jvm中,值得注意的是jvm的类装载器并不是一个,而是三个,层次结构如下: 

      Bootstrap Loader  - 负责加载系统类 

            | 

          - - ExtClassLoader  - 负责加载扩展类 

                          | 

                      - - AppClassLoader  - 负责加载应用类 

        为什么要有三个类加载器,一方面是分工,各自负责各自的区块,另一方面为了实现委托模型,下面会谈到该模型

 
17.6  类加载器之间是如何协调工作的 

   前面说了,java中有三个类加载器,问题就来了,碰到一个类需要加载时,它们之间是如何协调工作的,即java是如何区分一个类该由哪个类加载器来完成呢。 

        在这里java采用了委托模型机制,这个机制简单来讲,就是“类装载器有载入类的需求时,会先请示其Parent使用其搜索路径帮忙载入,如果Parent 找不到,那么才由自己依照自己的搜索路径搜索类”,注意喔,这句话具有递归性 
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值