111、说说你对Java中反射的理解。
Java中的反射是可以让我们在运行时获取类的方法、属性、父类、接口等类的内部信息的机制。也就是说,反射本质上是一个“反着来”的过程。我们通过new创建一个类的实例的时候,实际上是由Java虚拟机根据这个类的Class对象在运行时构建出来的,而反射是通过一个类的Class对象来获取它的定义信息,从而我们可以访问到它的属性、方法,知道这个类的父类、实现哪些接口等信息。
Java中的反射首先是能够获取到Java中要反射类的字节码,获取字节码有三种方法:1、Class.forName(className) 2、类名.class 3、this.getClass()。然后将字节码中的方法、变量、构造函数等映射成相应的Method、Filed、Constructor等类,这些类提供了丰富的方法可以被我们所使用。
112、静态代理和动态代理的区别?什么场景使用?
静态代理通常只代理一个类,动态代理是代理一个接口下的多个实现类。
静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道。
动态代理是实现JDK里的InvocationHandler接口的invoke方法,但注意的是代理的接口,也就是你的业务类必须要实现接口,通过Proxy里的newProxyInstance得到代理对象。还有一种动态代理CGLIB,代理的是类,不需要业务类继承接口,通过派生的子类来实现代理。通过在运行时,动态修改字节码达到修改类的目的。
AOP编程就是基于动态代理实现的,比如著名的Spring框架就是动态代理的使用例子。
113、说出你所知道的设计模式。
总的来说设计模式分为三大类:
创建型模式:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式,访问者模式、中介者模式、解释器模式。
Java中一般认为有如上23种设计模式,我们不需要所有都会,但是其中常用的几种设计模式应该要掌握。下面几道题分别简介一下几种常用的设计模式。
114、简单介绍一下单例设计模式。
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一,根据对象创建的时间分为懒汉式和饿汉式。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
- 1、单例类只能有一个实例。
- 2、单例类必须自己创建自己的唯一实例。
- 3、单例类必须给所有其他对象提供这一实例。
饿汉式:
public class Singleton{
//直接创建对象实例
public static Singleton instance = new Singleton();
//私有构造函数
private Singleton(){}
//返回对象实例
public static Singleton getInstance(){
return instance;
}
}
懒汉式:
public class Singleton{
//声明变量
private static volatile Singleton singleton = null;
//私有构造函数
private Singleton(){}
//提供对外的方法
public static Singleton getInstance(){
if(singleton == null){
synchronized(Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
115、介绍一下工厂设计模式。
工厂设计模式分为工厂方法模式和抽象工厂模式。
①工厂方法模式又可分为:普通工厂模式、多个工厂方法模式、静态工厂方法模式。
普通工厂模式就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。
public interface Sender{
public void Send();
}
----------------------------------------------------------
public class MailSender implements Sender{
@Override
public void Send(){
System.out.println("this is a mail sender");
}
}
----------------------------------------------------------
public class SmsSender implements Sender{
@Override
public void Send(){
System.out.println("this is a 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 a mail sender");
}
}
-----------------------------------------------------
public class SmsSender implements Sender{
@Override
public void send(){
System.out.println("this is a 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();
}
}
116、说一下JVM垃圾回收机制和常见算法
理论上来讲Sun公司只定义了垃圾回收机制规则而不局限于其实现算法,因此不同厂商生产的虚拟机采用的算法也不尽相同。GC(Garbage Collector)在回收对象之前首先必须发现那些无用的对象,如何去发现和定位那些无用的对象?常见的搜索算法有引用计数器算法(已废弃)和根搜索算法(在使用)。
引用计数器算法:引用计数器算法是给每个对象设置一个计数器,当有地方引用这个对象的时候,计数器加一,当引用失效的时候,计数器-1,当计数器为0的时候,JVM就认为对象不再被使用,是“垃圾”了。引用计数器实现简单,效率高,但是不能解决循环引用的问题(A对象引用B对象,B对象又引用A对象,但是A,B对象已不被任何其他对象引用),同时每次计数器的增加和减少都会带来很多额外的开销。所以在JDK1.1之后,这个算法就被抛弃不再使用了。
根搜索算法:根搜索算法是通过一些“GC Roots”对象作为起点,从这些节点开始往下搜索,搜索通过的路径成为引用链(Reference Chain),当一个对象没有被GC Roots的引用链连接的时候,说明这个对象是不可用的了。
GC Roots对象可以是:
- 虚拟机栈(栈帧中的本地变量表)中的引用的对象
- 方法区域中的类静态属性引用的对象。
- 方法区域中常量引用的对象
- 本地方法栈中JNI(Native方法)的引用的对象
117、说一下垃圾回收算法中的标记-清除算法。
标记-清除算法(Mark-Sweep)包括两个阶段:“标记”和“清除”。在标记阶段,确定所有要回收的对象,并做标记。清除阶段紧随标记阶段,将标记阶段确定不可用的对象清除。标记-清除算法是基础的收集算法,标记和清除阶段效率不高,而且清楚后会产生大量不连续空间,这样当程序员需要分配大内存对象的时候,可能无法找到足够的连续空间。
118、说一下垃圾回收算法中的复制算法。
复制算法是把内存分成大小相等的两块,每次使用其中的一块,当垃圾回收的时候,把存活的对象复制到另一块上,然后把这块内存整个清理掉。复制算法实现简单、运行效率高,但是由于每次只能使用其中的一半,造成内存的利用率不高,现在的JVM用复制算法收集新生代,由于新生代大部分对象(98%)都是朝生夕死的,所以两块内存的比例不是1:1(大概是8:1)。
119、说一下垃圾回收算法中的标记-整理算法
标记-整理算法和标记-清除算法一样,但是标记-整理算法不是把存活对象复制到另一块内存,而是把存活对象往内存的一端移动,然后直接回收边界以外的内存,标记-整理算法提高了内存的利用率,并且它适合在收集对象存活时间较长的老年代。
120、说一下垃圾回收算法中的分代搜集算法
分代收集算法是根据对象的存活时间把内存分为新生代和老年代,根据各个代对象的存活特点,每个代采用不同的垃圾回收算法。新生代采用复制算法,老年代采用标记-整理算法。垃圾收集算法的实现涉及大量的程序细节,而且不同的虚拟机平台实现的方法也各不相同。