JavaSE 基础:
一、Java 面向对象思想
1、面向对象都有哪些特性以及你对这些特性的理解
继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到
继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因
素的重要手段。
封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。面向对象
的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我
们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程
接口。
多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调
用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。如果将对象的方法视为对象向外
界提供的服务,那么运行时的多态性可以解释为:当A 系统访问B 系统提供的服务时,B 系统有多种提供服务的方式,
但一切对A 系统来说都是透明的。方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写
(override)实现的是运行时的多态性(也称为后绑定)。运行时的多态是面向对象最精髓的东西,要实现多态需要
做两件事:1. 方法重写(子类继承父类并重写父类中已有的或抽象的方法);2. 对象造型(用父类型引用引用子类型
对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。
抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注
对象有哪些属性和行为,并不关注这些行为的细节是什么。
二、Java 中的多态
1、Java 中实现多态的机制是什么?
靠的是父类或接口定义的引用变量可以指向子类或具体实现类的实例对象,而程序调用的方法在运行期才动
态绑定,就是引用变量所指向的具体实例对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变
量的类型中定义的方法。
三、Java 的异常处理
1、Java 中异常分为哪些种类
1)按照异常需要处理的时机分为编译时异常也叫CheckedException 和运行时异常也叫
RuntimeException。只有java 语言提供了Checked 异常,Java 认为Checked 异常都是可以被处理的异常,
所以Java 程序必须显式处理Checked 异常。如果程序没有处理Checked 异常,该程序在编译时就会发生错
误无法编译。这体现了Java 的设计哲学:没有完善错误处理的代码根本没有机会被执行。对Checked 异常
处理方法有两种:
1 当前方法知道如何处理该异常,则用try...catch 块来处理该异常。
2 当前方法不知道如何处理,则在定义该方法是声明抛出该异常。
运行时异常只有当代码在运行时才发行的异常,编译时不需要try catch。Runtime 如除数是0 和数组下标越
界等,其产生频繁,处理麻烦,若显示申明或者捕获将会对程序的可读性和运行效率影响很大。所以由系统自动
检测并将它们交给缺省的异常处理程序。当然如果你有处理要求也可以显示捕获它们。
2、调用下面的方法,得到的返回值是什么
public int getNum(){
try {
int a = 1/0;
return 1;
} catch (Exception e) {
return 2;
}finally{
//int b = 1/0;
return 3;
}}
代码在走到第3 行的时候遇到了一个MathException,这时第四行的代码就不会执行了,代码直接跳转到 catch 语句中,走到第6 行的时候,
异常机制有这么一个原则如果在catch 中遇到了return 或者异常等能使 该函数终止的话那么用finally 就必须先执行完finally 代码块里面的代码然后再返回值。
因此代码又跳到第8 行,可惜第8 行是一个return 语句,那么这个时候方法就结束了,因此第6 行的返回结果就无法被真正返回。 如果finally 仅仅是处理了一个释放资源的操作,那么该道题最终返回的结果就是2。 因此上面返回值是3。
四、Java 的数据类型
1、Java 的基本数据类型都有哪些各占几个字节
Java 有8 种基本数据类型
byte 1
char 2
sort 2
int 4
float 4
double 8
long 8
boolean 1(boolean 类型比较特别可能只占一个bit,多个boolean 可能共同占用一个字节)
2、String 是基本数据类型吗?可以被继承吗?
String 是引用类型,底层用char 数组实现的。因为String 是final 类,在java 中被final 修饰的类不能被继承,
因此String 当然不可以被继承。
五、Java 的IO
1、Java 中有几种类型的流
字节流和字符流。字节流继承于InputStream 和OutputStream,字符流继承于InputStreamReader 和
OutputStreamWriter。
2、字节流如何转为字符流
字节输入流转字符输入流通过InputStreamReader 实现,该类的构造函数可以传入InputStream 对象。
字节输出流转字符输出流通过OutputStreamWriter 实现,该类的构造函数可以传入OutputStream 对象。
3、如何将一个java 对象序列化到文件里
在java 中能够被序列化的类必须先实现Serializable 接口,该接口没有任何抽象方法只是起到一个标记作用。
//对象输出流
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new
FileOutputStream(new File("D://obj")));
objectOutputStream.writeObject(new User("zhangsan", 100));
objectOutputStream.close();
//对象输入流
ObjectInputStream objectInputStream = new ObjectInputStream(new
FileInputStream(new File("D://obj")));
User user = (User)objectInputStream.readObject();
System.out.println(user);
objectInputStream.close();
六、Java 的集合
1、HashMap 排序题,上机题。(本人主要靠这道题入职的第一家公司)
已知一个HashMap
对HashMap 的排序功能,该方法接收HashMap
要求对HashMap 中的User 的age 倒序进行排序。排序时key=value 键值对不得拆散。
:要做出这道题必须对集合的体系结构非常的熟悉。HashMap 本身就是不可排序的,但是该道题偏偏让给
HashMap 排序,那我们就得想在API 中有没有这样的Map 结构是有序的,LinkedHashMap,对的,就是他,他是
Map 结构,也是链表结构,有序的,更可喜的是他是HashMap 的子类,我们返回LinkedHashMap
即可,还符合面向接口(父类编程的思想)。
但凡是对集合的操作,我们应该保持一个原则就是能用JDK 中的API 就有JDK 中的API,比如排序算法我们不应
该去用冒泡或者选择, 而是首先想到用Collections 集合工具类。
public class HashMapTest {
public static void main(String[] args) {
HashMap
users = new HashMap<>(); users.put(1, new User("张三", 25));
users.put(3, new User("李四", 22));
users.put(2, new User("王五", 28));
System.out.println(users);
HashMap
sortHashMap = sortHashMap(users); System.out.println(sortHashMap);
/**
* 控制台输出内容
* {1=User [name=张三, age=25], 2=User [name=王五, age=28], 3=User [name=李四,
age=22]}
{2=User [name=王五, age=28], 1=User [name=张三, age=25], 3=User [name=李四,
age=22]}
*/
}
public static HashMap
sortHashMap(HashMap map) { // 首先拿到map 的键值对集合
Set
> entrySet = map.entrySet(); // 将set 集合转为List 集合,为什么,为了使用工具类的排序方法
List
> list = new ArrayList User>>(entrySet);
// 使用Collections 集合工具类对list 进行排序,排序规则使用匿名内部类来实现
Collections.sort(list, new Comparator
>() { @Override
public int compare(Entry
o1, Entry o2) { //按照要求根据User 的age 的倒序进行排
return o2.getValue().getAge()-o1.getValue().getAge();
}
});
//创建一个新的有序的HashMap 子类的集合
LinkedHashMap
linkedHashMap = new LinkedHashMap User>();
//将List 中的数据存储在LinkedHashMap 中
for(Entry
entry : list){ linkedHashMap.put(entry.getKey(), entry.getValue());
}
//返回结果
return linkedHashMap;
}
}
2、集合的安全性问题
请问ArrayList、HashSet、HashMap 是线程安全的吗?如果不是我想要线程安全的集合怎么办?
我们都看过上面那些集合的源码(如果没有那就看看吧),每个方法都没有加锁,显然都是线程不安全的。话又
说过来如果他们安全了也就没第二问了。
在集合中Vector 和HashTable 倒是线程安全的。你打开源码会发现其实就是把各自核心方法添加上了
synchronized 关键字。
Collections 工具类提供了相关的API,可以让上面那3 个不安全的集合变为安全的。
// Collections.synchronizedCollection(c)
// Collections.synchronizedList(list)
// Collections.synchronizedMap(m)
// Collections.synchronizedSet(s)
上面几个函数都有对应的返回值类型,传入什么类型返回什么类型。打开源码其实实现原理非常简单,就是将集
合的核心方法添加上了synchronized 关键字。
3、ArrayList 内部用什么实现的?(2015-11-24)
(回答这样的问题,不要只回答个皮毛,可以再介绍一下ArrayList 内部是如何实现数组的增加和删除的,因为数
组在创建的时候长度是固定的,那么就有个问题我们往ArrayList 中不断的添加对象,它是如何管理这些数组呢?)
ArrayList 内部是用Object[]实现的。接下来我们分别分析ArrayList 的构造、add、remove、clear 方法的实现
原理。
4、并发集合和普通集合如何区别?
并发集合常见的有ConcurrentHashMap、ConcurrentLinkedQueue、ConcurrentLinkedDeque 等。并发集合
位于java.util.concurrent 包下, 是jdk1.5 之后才有的, 主要作者是Doug Lea
(http://baike.baidu.com/view/3141057.htm)完成的。
在java 中有普通集合、同步(线程安全)的集合、并发集合。普通集合通常性能最高,但是不保证多线程的安全
性和并发的可靠性。线程安全集合仅仅是给集合添加了synchronized 同步锁,严重牺牲了性能,而且对并发的效率
就更低了,并发集合则通过复杂的策略不仅保证了多线程的安全又提高的并发时的效率。
参考阅读:
ConcurrentHashMap 是线程安全的HashMap 的实现,默认构造同样有initialCapacity 和loadFactor 属性,
不过还多了一个concurrencyLevel 属性,三属性默认值分别为16、0.75 及16。其内部使用锁分段技术,维持这锁
Segment 的数组,在Segment 数组中又存放着Entity[]数组,内部hash 算法将数据较均匀分布在不同锁中。
put 操作:并没有在此方法上加上synchronized,首先对key.hashcode 进行hash 操作,得到key 的hash 值。
hash 操作的算法和map 也不同, 根据此hash 值计算并获取其对应的数组中的Segment 对象( 继承自
ReentrantLock),接着调用此Segment 对象的put 方法来完成当前操作。
ConcurrentHashMap 基于concurrencyLevel 划分出了多个Segment 来对key-value 进行存储,从而避免每次put 操作都得锁住整个数组。在默认的情况下,最佳情况下可允许16 个线程并发无阻塞的操作集合对象,尽可能地减少并发时的阻塞现象。
get(key)
首先对key.hashCode 进行hash 操作,基于其值找到对应的Segment 对象,调用其get 方法完成当前操作。
而Segment 的get 操作首先通过hash 值和对象数组大小减1 的值进行按位与操作来获取数组上对应位置的
HashEntry。在这个步骤中,可能会因为对象数组大小的改变,以及数组上对应位置的HashEntry 产生不一致性,那
么ConcurrentHashMap 是如何保证的?
对象数组大小的改变只有在put 操作时有可能发生,由于HashEntry 对象数组对应的变量是volatile 类型的,
因此可以保证如HashEntry 对象数组大小发生改变,读操作可看到最新的对象数组大小。
在获取到了HashEntry 对象后,怎么能保证它及其next 属性构成的链表上的对象不会改变呢?这点
ConcurrentHashMap 采用了一个简单的方式,即HashEntry 对象中的hash、key、next 属性都是final 的,这也就
意味着没办法插入一个HashEntry 对象到基于next 属性构成的链表中间或末尾。这样就可以保证当获取到HashEntry
对象后,其基于next 属性构建的链表是不会发生变化的。
ConcurrentHashMap 默认情况下采用将数据分为16 个段进行存储,并且16 个段分别持有各自不同的锁
Segment,锁仅用于put 和remove 等改变集合对象的操作,基于volatile 及HashEntry 链表的不变性实现了读取
的不加锁。这些方式使得ConcurrentHashMap 能够保持极好的并发支持,尤其是对于读远比插入和删除频繁的Map
而言,而它采用的这些方法也可谓是对于Java 内存模型、并发机制深刻掌握的体现。
七、Java 的多线程
1、多线程的两种创建方式
java.lang.Thread 类的实例就是一个线程但是它需要调用java.lang.Runnable 接口来执行,由于线程类本身就是
调用的Runnable 接口所以你可以继承java.lang.Thread 类或者直接实现Runnable 接口来重写run()方法实现线程。
2、在java 中wait 和sleep 方法的不同?
最大的不同是在等待时wait 会释放锁,而sleep 一直持有锁。wait 通常被用于线程间交互,sleep 通常被用于暂
停执行。
3、synchronized 和volatile 关键字的作用
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile 修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是
立即可见的。
2)禁止进行指令重排序。
volatile 本质是在告诉jvm 当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;
synchronized 则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
1.volatile 仅能使用在变量级别;
synchronized 则可以使用在变量、方法、和类级别的
2.volatile 仅能实现变量的修改可见性,并不能保证原子性;
synchronized 则可以保证变量的修改可见性和原子性
3.volatile 不会造成线程的阻塞;
synchronized 可能会造成线程的阻塞。
4.volatile 标记的变量不会被编译器优化;
synchronized 标记的变量可以被编译器优化
5、什么是线程池,如何使用?
线程池就是事先将多个线程对象放到一个容器中,当使用的时候就不用new 线程而是直接去池中拿线程即可,节
省了开辟子线程的时间,提高的代码执行效率。
在JDK 的java.util.concurrent.Executors 中提供了生成多种线程池的静态方法。
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(4);
ScheduledExecutorService newScheduledThreadPool =
Executors.newScheduledThreadPool(4);
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
然后调用他们的execute 方法即可。
6、请叙述一下您对线程池的理解?
合理利用线程池能够带来三个好处。
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定
性,使用线程池可以进行统一的分配,调优和监控。
7、线程池的启动策略?
1、线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也
不会马上执行它们。
2、当调用execute() 方法添加一个任务时,线程池会做如下判断:
a. 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
b. 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列。
c. 如果这时候队列满了,而且正在运行的线程数量小于maximumPoolSize,那么还是要创建线程运行这
个任务;
d. 如果队列满了,而且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会抛出异常,告
诉调用者“我不能再接受任务了”。
3、当一个线程完成任务时,它会从队列中取下一个任务来执行。
4、当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于
corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到corePoolSize 的大小。
JavaSE 高级
一、Java 中的反射
1、说说你对Java 中反射的理解
Java 中的反射首先是能够获取到Java 中要反射类的字节码, 获取字节码有三种方法,
1.Class.forName(className)
2.类名.class
3.this.getClass()。
然后将字节码中的方法,变量,构造函数等映射成相应的Method、Filed、Constructor 等类,这些类提供了丰富的方法可以被我们所使用。
二、Java 中的动态代理
1、动静态代理的区别,什么场景使用?
静态代理通常只代理一个类,动态代理是代理一个接口下的多个实现类。
静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道。
动态代理是实现JDK 里的InvocationHandler 接口的invoke 方法,但注意的是代理的是接口,也就是你的
业务类必须要实现接口,通过Proxy 里的newProxyInstance 得到代理对象。
还有一种动态代理CGLIB,代理的是类,不需要业务类继承接口,通过派生的子类来实现代理。通过在运行
时,动态修改字节码达到修改类的目的。
AOP 编程就是基于动态代理实现的,比如著名的Spring 框架、Hibernate 框架等等都是动态代理的使用例子。
三、Java 中的设计模式&回收机制
1、你所知道的设计模式有哪些
Java 中一般认为有23 种设计模式,我们不需要所有的都会,但是其中常用的几种设计模式应该去掌握。下面列出了所有的设计模式。需要掌握的设计模式我单独列出来了,当然能掌握的越多越好。
总体来说设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录
模式、状态模式、访问者模式、中介者模式、解释器模式。
2、单例设计模式
最好理解的一种设计模式,分为懒汉式和饿汉式。
饿汉式:
public class Singleton {
// 直接创建对象
public static Singleton instance = new Singleton();
// 私有化构造函数
private Singleton() {
}
// 返回对象实例
public static Singleton getInstance() {
return instance;
}
}
懒汉式:
public class Singleton {
// 声明变量
private static volatile Singleton singleton2 = null;
// 私有构造函数
private Singleton2() {
}
// 提供对外方法
public static Singleton2 getInstance() {
if (singleton2 == null) {
synchronized (Singleton2.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
3、工厂设计模式
工厂模式分为工厂方法模式和抽象工厂模式。
工厂方法模式:
工厂方法模式分为三种:普通工厂模式,就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。
多个工厂方法模式,是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不
能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。
静态工厂方法模式,将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。
public interface Sender {
public void Send();
}
public class MailSender implements Sender {
@Override
public void Send() {
System.out.println("this is mail sender!");
}
}
public class SmsSender implements Sender {
@Override
public void Send() {
System.out.println("this is sms sender!");
}
}
public class SendFactory {
public Sender produce(String type) {
if ("mail".equals(type)) {
return new MailSender();
} else if ("sms".equals(type)) {
return new SmsSender();
} else {
System.out.println("请输入正确的类型!");
return null;
}
}
}
多个工厂方法模式
该模式是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。
public class SendFactory {
public Sender produceMail(){
return new MailSender();
}
public Sender produceSms(){
return new SmsSender();
}
}
public class FactoryTest {
public static void main(String[] args) {
SendFactory factory = new SendFactory();
Sender sender = factory.produceMail();
sender.send();
}
}
静态工厂方法模式,将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。
public class SendFactory {
public static Sender produceMail(){
return new MailSender();
}
public static Sender produceSms(){
return new SmsSender();
}
}
public class FactoryTest {
public static void main(String[] args) {
Sender sender = SendFactory.produceMail();
sender.send();
}
}
抽象工厂模式
工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修
改,这违背了闭包原则,所以,从设计角度考虑,有一定的问题,如何解决?就用到抽象工厂模式,创建多个工
厂类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。
public interface Provider {
public Sender produce();
}
-------------------------------------------------------------------------------------
public interface Sender {
public void send();
}
-------------------------------------------------------------------------------------
public class MailSender implements Sender {
@Override
public void send() {
System.out.println("this is mail sender!");
}
}
-------------------------------------------------------------------------------------
public class SmsSender implements Sender {
@Override
public void send() {
System.out.println("this is sms sender!");
}
}
-------------------------------------------------------------------------------------
public class SendSmsFactory implements Provider {
@Override
public Sender produce() {
return new SmsSender();
}
}
public class SendMailFactory implements Provider {
@Override
public Sender produce() {
return new MailSender();
}
}
-------------------------------------------------------------------------------------
public class Test {
public static void main(String[] args) {
Provider provider = new SendMailFactory();
Sender sender = provider.produce();
sender.send();
}
}
4、建造者模式(Builder)
工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象,
所谓复合对象就是指某个类具有不同的属性,其实建造者模式就是前面抽象工厂模式和最后的Test 结合起来得到的。
public class Builder {
private List
list = new ArrayList (); public void produceMailSender(int count) {
for (int i = 0; i < count; i++) {
list.add(new MailSender());
}
}
public void produceSmsSender(int count) {
for (int i = 0; i < count; i++) {
list.add(new SmsSender());
}
}
}
public class TestBuilder {
public static void main(String[] args) {
Builder builder = new Builder();
builder.produceMailSender(10);
}
}
5、适配器设计模式
适配器模式将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容
性问题。主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式。
类的适配器模式
public class Source {
public void method1() {
System.out.println("this is original method!");
}
}
-------------------------------------------------------------
public interface Targetable {
/* 与原类中的方法相同*/
public void method1();
/* 新类的方法*/
public void method2();
}
public class Adapter extends Source implements Targetable {
@Override
public void method2() {
System.out.println("this is the targetable method!");
}
}
public class AdapterTest {
public static void main(String[] args) {
Targetable target = new Adapter();
target.method1();
target.method2();
}
}
对象的适配器模式
基本思路和类的适配器模式相同,只是将Adapter 类作修改,这次不继承Source 类,而是持有Source 类的实
例,以达到解决兼容性的问题。
public class Wrapper implements Targetable {
private Source source;
public Wrapper(Source source) {
super();
this.source = source;
}
@Override
public void method2() {
System.out.println("this is the targetable method!");
}
@Override
public void method1() {
source.method1();
}
}
--------------------------------------------------------------
public class AdapterTest {
public static void main(String[] args) {
Source source = new Source();
Targetable target = new Wrapper(source);
target.method1();
target.method2();
}
}
接口的适配器模式
接口的适配器是这样的:有时我们写的一个接口中有多个抽象方法,当我们写该接口的实现类时,必须实现该接
口的所有方法,这明显有时比较浪费,因为并不是所有的方法都是我们需要的,有时只需要某一些,此处为了解决这
个问题,我们引入了接口的适配器模式,借助于一个抽象类,该抽象类实现了该接口,实现了所有的方法,而我们不
和原始的接口打交道,只和该抽象类取得联系,所以我们写一个类,继承该抽象类,重写我们需要的方法就行。
6、装饰模式(Decorator)
顾名思义,装饰模式就是给一个对象增加一些新的功能,而且是动态的,要求装饰对象和被装饰对象实现同一个
接口,装饰对象持有被装饰对象的实例。
public interface Sourceable {
public void method();
}
----------------------------------------------------
public class Source implements Sourceable {
@Override
public void method() {
System.out.println("the original method!");
}
}
----------------------------------------------------
public class Decorator implements Sourceable {
private Sourceable source;
public Decorator(Sourceable source) {
super();
this.source = source;
}
@Override
public void method() {
System.out.println("before decorator!");
source.method();
System.out.println("after decorator!");
}
}
----------------------------------------------------
public class DecoratorTest {
public static void main(String[] args) {
Sourceable source = new Source();
Sourceable obj = new Decorator(source);
obj.method();
}
}
7、策略模式(strategy)
策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法
的客户。需要设计一个接口,为一系列实现类提供统一的方法,多个实现类实现该接口,设计一个抽象类(可有可无,
属于辅助类),提供辅助函数。策略模式的决定权在用户,系统本身提供不同算法的实现,新增或者删除算法,对各
种算法做封装。因此,策略模式多用在算法决策系统中,外部用户只需要决定用哪个算法即可。
8、观察者模式(Observer)
观察者模式很好理解,类似于邮件订阅和RSS 订阅,当我们浏览一些博客或wiki 时,经常会看到RSS 图标,就
这的意思是,当你订阅了该文章,如果后续有更新,会及时通知你。其实,简单来讲就一句话:当一个对象变化时,
其它依赖该对象的对象都会收到通知,并且随着变化!对象之间是一种一对多的关系。
public interface Observer {
public void update();
}
public class Observer1 implements Observer {
@Override
public void update() {
System.out.println("observer1 has received!");
}
}
public class Observer2 implements Observer {
@Override
public void update() {
System.out.println("observer2 has received!");
}
}
public interface Subject {
/*增加观察者*/
public void add(Observer observer);
/*删除观察者*/
public void del(Observer observer);
/*通知所有的观察者*/
public void notifyObservers();
/*自身的操作*/
public void operation();
}
public abstract class AbstractSubject implements Subject {
private Vector
vector = new Vector (); @Override
public void add(Observer observer) {
vector.add(observer);
}
@Override
public void del(Observer observer) {
vector.remove(observer);
}
@Override
public void notifyObservers() {
Enumeration
enumo = vector.elements(); while (enumo.hasMoreElements()) {
enumo.nextElement().update();
}
}
}
public class MySubject extends AbstractSubject {
@Override
public void operation() {
System.out.println("update self!");
notifyObservers();
}
}
public class ObserverTest {
public static void main(String[] args) {
Subject sub = new MySubject();
sub.add(new Observer1());
sub.add(new Observer2());
sub.operation();
}
}
9、JVM 垃圾回收机制和常见算法
理论上来讲Sun 公司只定义了垃圾回收机制规则而不局限于其实现算法,因此不同厂商生产的虚拟机采用的算法
也不尽相同。
GC(Garbage Collector)在回收对象前首先必须发现那些无用的对象,如何去发现定位这些无用的对象?常用
的搜索算法如下:
1)引用计数器算法(废弃)
引用计数器算法是给每个对象设置一个计数器,当有地方引用这个对象的时候,计数器+1,当引用失效的时候,
计数器-1,当计数器为0 的时候,JVM 就认为对象不再被使用,是“垃圾”了。
引用计数器实现简单,效率高;但是不能解决循环引用问问题(A 对象引用B 对象,B 对象又引用A 对象,但是
A,B 对象已不被任何其他对象引用),同时每次计数器的增加和减少都带来了很多额外的开销,所以在JDK1.1 之后,
这个算法已经不再使用了。
2)根搜索算法(使用)
根搜索算法是通过一些“GC Roots”对象作为起点,从这些节点开始往下搜索,搜索通过的路径成为引用链
(Reference Chain),当一个对象没有被GC Roots 的引用链连接的时候,说明这个对象是不可用的。
GC Roots 对象包括:
a) 虚拟机栈(栈帧中的本地变量表)中的引用的对象。
b) 方法区域中的类静态属性引用的对象。
c) 方法区域中常量引用的对象。
d) 本地方法栈中JNI(Native 方法)的引用的对象。
通过上面的算法搜索到无用对象之后,就是回收过程,回收算法如下:
1)标记—清除算法(Mark-Sweep)(DVM 使用的算法)
标记—清除算法包括两个阶段:“标记”和“清除”。在标记阶段,确定所有要回收的对象,并做标记。清除阶
段紧随标记阶段,将标记阶段确定不可用的对象清除。标记—清除算法是基础的收集算法,标记和清除阶段的效率不
高,而且清除后回产生大量的不连续空间,这样当程序需要分配大内存对象时,可能无法找到足够的连续空间。
2)复制算法(Copying)
复制算法是把内存分成大小相等的两块,每次使用其中一块,当垃圾回收的时候,把存活的对象复制到另一块上,
然后把这块内存整个清理掉。复制算法实现简单,运行效率高,但是由于每次只能使用其中的一半,造成内存的利用
率不高。现在的JVM 用复制方法收集新生代,由于新生代中大部分对象(98%)都是朝生夕死的,所以两块内存的比
例不是1:1(大概是8:1)。
3)标记—整理算法(Mark-Compact)
标记—整理算法和标记—清除算法一样,但是标记—整理算法不是把存活对象复制到另一块内存,而是把存活对
象往内存的一端移动,然后直接回收边界以外的内存。标记—整理算法提高了内存的利用率,并且它适合在收集对象
存活时间较长的老年代。
4)分代收集(Generational Collection)
分代收集是根据对象的存活时间把内存分为新生代和老年代,根据各个代对象的存活特点,每个代采用不同的垃
圾回收算法。新生代采用复制算法,老年代采用标记—整理算法。垃圾算法的实现涉及大量的程序细节,而且不同的
虚拟机平台实现的方法也各不相同。
10、谈谈JVM 的内存结构和内存分配
a) Java 内存模型
Java 虚拟机将其管辖的内存大致分三个逻辑部分:方法区(Method Area)、Java 栈和Java 堆。
1、方法区是静态分配的,编译器将变量绑定在某个存储位置上,而且这些绑定不会在运行时改变。
常数池,源代码中的命名常量、String 常量和static 变量保存在方法区。
2、Java Stack 是一个逻辑概念,特点是后进先出。一个栈的空间可能是连续的,也可能是不连续的。
最典型的Stack 应用是方法的调用,Java 虚拟机每调用一次方法就创建一个方法帧(frame),退出该
方法则对应的方法帧被弹出(pop)。栈中存储的数据也是运行时确定的。
3、Java 堆分配(heap allocation)意味着以随意的顺序,在运行时进行存储空间分配和收回的内存管理模型。
堆中存储的数据常常是大小、数量和生命期在编译时无法确定的。Java 对象的内存总是在heap 中分配。
我们每天都在写代码,每天都在使用JVM 的内存。
b) java 内存分配
1、基础数据类型直接在栈空间分配;
2、方法的形式参数,直接在栈空间分配,当方法调用完成后从栈空间回收;
3、引用数据类型,需要用new 来创建,既在栈空间分配一个地址空间,又在堆空间分配对象的类变量;
4、方法的引用参数,在栈空间分配一个地址空间,并指向堆空间的对象区,当方法调用完后从栈空间回收;
5、局部变量new 出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间立刻被回收,
堆空间区域等待GC 回收;
6、方法调用时传入的实际参数,先在栈空间分配,在方法调用完成后从栈空间释放;
7、字符串常量在DATA 区域分配,this 在堆空间分配;
8、数组既在栈空间分配数组名称, 又在堆空间分配数组实际的大小!
11、Java 中引用类型都有哪些?(重要)
Java 中对象的引用分为四种级别,这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。
强引用(StrongReference)
这个就不多说,我们写代码天天在用的就是强引用。如果一个对象被被人拥有强引用,那么垃圾回收器绝不
会回收它。当内存空间不足,Java 虚拟机宁愿抛出OutOfMemoryError 错误,使程序异常终止,也不会靠随意
回收具有强引用的对象来解决内存不足问题。
Java 的对象是位于heap 中的,heap 中对象有强可及对象、软可及对象、弱可及对象、虚可及对象和不可到
达对象。应用的强弱顺序是强、软、弱、和虚。对于对象是属于哪种可及的对象,由他的最强的引用决定。如下
代码:
String abc=new String("abc"); //1
SoftReference
softRef=new SoftReference (abc); //2 WeakReference
weakRef = new WeakReference (abc); //3 abc=null; //4
softRef.clear();//5
第一行在heap 堆中创建内容为“abc”的对象,并建立abc 到该对象的强引用,该对象是强可及的。
第二行和第三行分别建立对heap 中对象的软引用和弱引用,此时heap 中的abc 对象已经有3 个引用,显然此
时abc 对象仍是强可及的。
第四行之后heap 中对象不再是强可及的,变成软可及的。
第五行执行之后变成弱可及的。
软引用(SoftReference)
如果一个对象只具有软引用,那么如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java 虚拟机就会把这个软引用加入到与之关联的引用队列中。
软引用是主要用于内存敏感的高速缓存。在jvm 报告内存不足之前会清除所有的软引用,这样以来gc 就有可能收集软可及的对象,可能解决内存吃紧问题,避免内存溢出。什么时候会被收集取决于gc 的算法和gc 运行时可用内存的大小。当gc 决定要收集软引用时执行以下过程,以上面的softRef 为例:
1 首先将softRef 的referent(abc)设置为null,不再引用heap 中的new String("abc")对象。
2 将heap 中的new String("abc")对象设置为可结束的(finalizable)。
3 当heap 中的new String("abc")对象的finalize()方法被运行而且该对象占用的内存被释放, softRef
被添加到它的ReferenceQueue(如果有的话)中。
注意:对ReferenceQueue 软引用和弱引用可以有可无,但是虚引用必须有。Soft Reference 指到的对象,即使没有任何Direct Reference,也不会被清除。一直要到JVM 内存不足且没有Direct Reference 时才会清除,SoftReference 是用来设计object-cache 之用的。如此一来SoftReference 不但可以把对象cache 起来,也不会造成内存不足的错误(OutOfMemoryError)。
弱引用(WeakReference)
如果一个对象只具有弱引用,那该类就是可有可无的对象,因为只要该对象被gc 扫描到了随时都会把它干掉。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。
虚引用(PhantomReference)
"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用主要用来跟踪对象被垃圾回收的活动。
虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关
联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
建立虚引用之后通过get 方法返回结果始终为null,通过源代码你会发现,虚引用通向会把引用的对象写进referent,只是get 方法返回结果为null。先看一下和gc 交互的过程再说一下他的作用。
1 不把referent 设置为null, 直接把heap 中的new String("abc")对象设置为可结束的(finalizable)。
2 与软引用和弱引用不同, 先把PhantomRefrence 对象添加到它的ReferenceQueue 中.然后在释放虚可及的对
象。