美文网首页
Java面试题整理2017

Java面试题整理2017

作者: 任重而道元 | 来源:发表于2017-10-18 16:28 被阅读11次

Java基础

1.下面程序运行结果

class A{  
    static{  
        System.out.print("1");  
    }  
    public A(){  
        System.out.print("2");  
    }  
}  
class B extends A{  
    static{  
        System.out.print("a");  
    }  
    public B(){  
        System.out.print("b");  
    }  
}  
public class Hello{  
    public static void main(String[] args){  
        A ab = new B();  
        ab = new B();  
    }  
}  

执行结果:1a2b2b。
创建对象时构造器的调用顺序是:先初始化静态成员,然后调用父类构造器,再初始化非静态成员,最后调用自身构造器。

2.如何实现字符串的反转及替换

public static String reverse(String str) {
        if(str == null || str.length() <= 1) return str;
        return reverse(str.substring(1)) + str.charAt(0);
}

3.时间操作

<dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.9.9</version>
            <optional>true</optional>
</dependency>
# 获取毫秒
System.currentTimeMillis();
# 格式化时间
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date = format.format(new Date());
System.out.println(date);
# 获取年月日时分秒
Calendar calendar = Calendar.getInstance();
System.out.println("年:"+ calendar.get(Calendar.YEAR));
System.out.println("月:"+ (calendar.get(Calendar.MONTH)+1));
System.out.println("日:"+ calendar.get(Calendar.DATE));
System.out.println("时:"+ calendar.get(Calendar.HOUR_OF_DAY));
System.out.println("分:"+ calendar.get(Calendar.MINUTE));
System.out.println("秒:"+ calendar.get(Calendar.SECOND));

4.final, finally, finalize 的区别

final:修饰符(关键字)有三种用法:

如果一个类被声明为final,意味着它不能再派生出新的子类,即不能被继承,因此它和abstract是反义词。

将变量声明为final,可以保证它们在使用中不被改变,被声明为final 的变量必须在声明时给定初值,而在以后的引用中只能读取不可修改。

被声明为final 的方法也同样只能使用,不能在子类中被重写。

finally:通常放在try…catch的后面构造总是执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要JVM不关闭都能执行,可以将释放外部资源的代码写在finally块中。

finalize:Object类中定义的方法,Java中允许使用finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。

这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize() 方法可以整理系统资源或者执行其他清理工作。

5.异常处理

class Annoyance extends Exception {
}

class Sneeze extends Annoyance {
}

public class Human {
    public static void main(String[] args) throws Exception {
        try {
            try {
                throw new Sneeze();
            } catch (Annoyance a) {
                System.out.println("Caught Annoyance");
                throw a;
            }
        } catch (Sneeze s) {
            System.out.println("Caught Sneeze");
            return;
        } finally {
            System.out.println("Hello World!");
        }
    }
}

运行结果如下:

Caught Annoyance
Caught Sneeze
Hello World!

6.Java集合操作

Map集合

Paste_Image.png

List和Set

Paste_Image.png
List<?> list = new ArrayList<>();
List<?> list1 = new Vector<>();
List<?> list2 = new LinkedList<>();
        
Set<?> set = new HashSet<>();
Set<?> set1 = new LinkedHashSet<>();
Set<?> set2 = new TreeSet<>();
        
Map<?, ?> map = new HashMap<>();
Map<?, ?> map1 = new LinkedHashMap<>();
Map<?, ?> map2 = new TreeMap<>();

Collection:每个位置对应一个元素

  • List:存放有序,允许重复元素,允许元素为null

  • ArrayList:

内部结构是数组;

初始容量是10;

存放有序,元素可以重复也可以为null;

插入和删除的移动速度慢;动态扩容1.5倍;

线程不安全;

  • LinkedList:

内部结构是双向链表;

元素存放有序,允许元素为null,可重复;

线程不安全;

  • Vector :

内部结构是数组,与ArrayList及其相似;

初始容量是10;

线程安全;

动态扩容为原来的两倍;

  • Set:不允许重复元素

  • HashSet:

底层实现是HashMap(),所以不允许重复元素(对应的是key);

存放无序(根据hash确定索引位置);

允许元素为null;

  • LinkedHashSet:

继承HashSet,跟HashSet类似,唯一区别:存放元素有序;

遍历性能比HashSet好,但插入删除的性能会差点;

允许元素为null;

  • TreeSet:

是SortedSet接口的唯一实现类;

不允许元素重复, 不允许元素为null;

自动排序元素;

Map:以Key-Value键值对存在

  • HashMap:

数据结构是数据和链表实现的。Entry[] table,链表存储来解决hash冲突;

初始容量:16;

允许key和value为null,key重复会覆盖;

存放无序;线程不安全;

  • LinkedHashMap:

数据结构是双向链表;

存放元素有序;

线程不安全

  • TreeMap:

不允许Key为null, value可以为null;

元素默认排序(如:字符串->字典);

线程不安全

7.请说出与线程同步相关的方法

wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;

sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException 异常;

notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;

notityAll():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争;

JDK 1.5通过Lock接口提供了显式(explicit)的锁机制,增强了灵活性以及对线程的协调。Lock接口中定义了加锁(lock())和解锁(unlock())的方法,同时还提供了newCondition()方法来产生用于线程之间通信的Condition对象;

JDK 1.5还提供了信号量(semaphore)机制,信号量可以用来限制对某个共享资源进行访问的线程的数量。在对资源进行访问之前,线程必须得到信号量的许可(调用Semaphore对象的acquire()方法);在完成对资源的访问后,线程必须向信号量归还许可(调用Semaphore对象的release()方法)。

8.线程的生命周期

生命周期

除去起始(new)状态和结束(finished)状态,线程有三种状态,分别是:就绪(ready)、运行(running)和阻塞(blocked)。

其中就绪(ready)状态代表线程具备了运行的所有条件,只等待CPU调度(万事俱备,只欠东风);

处于运行(running)状态的线程可能因为CPU调度(时间片用完了)的原因回到就绪状态,也有可能因为调用了线程的yield方法回到就绪状态,此时线程不会释放它占有的资源的锁,坐等CPU以继续执行;

运行状态的线程可能因为I/O中断、线程休眠、调用了对象的wait方法而进入阻塞状态(有的地方也称之为等待状态);

而进入阻塞状态的线程会因为休眠结束、调用了对象的notify方法或notifyAll方法或其他线程执行结束而进入就绪状态。

注意:调用wait方法会让线程进入等待池中等待被唤醒,notify方法或notifyAll方法会让等待锁中的线程从等待池进入等锁池,在没有得到对象的锁之前,线程仍然无法获得CPU的调度和执行。

9.sleep()和yield()有什么区别?

① sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;

② 线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态;

③ sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;

④ sleep()方法比yield()方法(跟操作系统相关)具有更好的可移植性。

10.下面用IO和NIO两种方式实现文件拷贝

public static void fileCopy(String source, String target) throws IOException {
        try (InputStream in = new FileInputStream(source)) {
            try (OutputStream out = new FileOutputStream(target)) {
                byte[] buffer = new byte[4096];
                int bytesToRead;
                while ((bytesToRead = in.read(buffer)) != -1) {
                    out.write(buffer, 0, bytesToRead);//将输入流写入到输出流
                }
            }
        }
}

public static void fileCopyNIO(String source, String target) throws IOException {
        try (FileInputStream in = new FileInputStream(source)) {
            try (FileOutputStream out = new FileOutputStream(target)) {
                FileChannel inChannel = in.getChannel();
                FileChannel outChannel = out.getChannel();
                ByteBuffer buffer = ByteBuffer.allocate(4096);
                while (inChannel.read(buffer) != -1) {
                    buffer.flip();
                    outChannel.write(buffer); // 通过通道将输入流写入到输出流
                    buffer.clear();
                }
            }
        }
}

11.写一个方法,输入一个文件名和一个字符串,统计这个字符串在这个文件中出现的次数

public static int countWordInFile(String filename, String word) {
        int counter = 0;
        try (FileReader fr = new FileReader(filename)) { // 获取文件流
            try (BufferedReader br = new BufferedReader(fr)) { // 获取buffer流
                String line = null;
                while ((line = br.readLine()) != null) {
                    int index = -1;
                    while (line.length() >= word.length() && (index = line.indexOf(word)) >= 0) {
                        counter++;
                        line = line.substring(index + word.length());
                    }
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return counter;
}

12.单例模式

饿汉模式
public class Singleton {  
    private Singleton(){}  
    private static final Singleton instance = new Singleton();  
    public static Singleton getInstance(){  
        return instance;  
    }  
}

可以看到饿汉式的实现非常简单,适合那些在初始化时就要用到单例的情况,如果单例对象初始化非常快,而且占用内存非常小的时候这种方式是比较合适的,可以直接在应用启动时加载并初始化。

懒汉模式
public class Singleton {  
    private static Singleton instance;  
    private Singleton() {}  
    public static Singleton getInstance(){  
        if (instance==null) instance=newSingleton();  
        return instance;  
    }  
} 

为什么这种实现是线程不安全的呢?

如一个线程A执行到singleton = new Singleton();这里,但还没有获得对象(对象初始化是需要时间的),第二个线程B也在执行,执行到if(singleton == null)判断,那么线程B获得判断条件也是为真,于是继续运行下去,线程A获得了一个对象,线程B也获得了一个对象,在内存中就出现两个对象,造成单例模式的失效!

懒汉模式-同步锁
public class Singleton {  
    private static Singleton instance;  
    private Singleton() {}  
    public static synchronized Singleton getInstance(){  
        if (instance==null) instance=newSingleton();  
        return instance;  
    }  
} 

synchronized关键字来锁住getInstance()。也就是说getInstance()方法块只能运行在一个线程中,如果该段代码已经在一个线程中运行,另外一个线程试图运行这块代码,他会被阻塞而一直等待。而在这个线程安全的方法块中我们进行了Singleton的实例化:single = new Singleton();这样一来就保证了多线程模式下单例对象的唯一性。
  懒汉单例模式得优点是单例模式只有在使用时才会被实例化,相比饿汉模式节约了资源。最大的问题是,每次访问都要进行线程同步(调用synchronized锁),实际上我们只在第一次调用该方法的时候才需要同步,一旦实例创建成功之后的同步完全没有必要。因此这种每次都需要同步的方法显然会造成不必要的同步开销。

懒汉模式-双重检查锁
public class Singleton {
    private Singleton(){}
    private static Singleton instance;
    
    public static synchronized Singleton getInstance() {
        if(instance == null) { //1重锁
            synchronized (Singleton.class) {//2重锁
                if(instance == null) instance = new Singleton();
            }
        }
        return instance;
    }
}

第一层检查是为了避免不必要的同步,当instance实例已经存在时,系统调用getInstance()方法进入第一层判断。判断不为空不会进入synchronized代码块,直接返回instance对象。

第二层检查是为了在多个线程突破第一层检查时,仍然只创建一个实例假设最开始Instance对象不存在,线程A调用getInstance()方法,当该A线程运行到②位置时,此时又一个B线程也调用了getInstance()方法。因为A线程并没有执行instance = new Singleton();,此时instance仍然为空,因此B线程也能突破第一层非空判断,运行到①位置等待synchronized中的A线程执行完毕。

当A线程释放同步锁时,instance已经非空。此时B线程从位置①开始执行到位置②。此时第二层非空判断就开始起作用了——由于有第二层非空判断,那么B线程在进行第二层非空判断的时候就不会通过,因此也不会创建多余的实例。试想一下加入没有第二层判断,那B线程岂不是顺利的重新创建了一个实例了。

13.说说你所熟悉或听说过的设计模式以及你对设计模式的看法

在GoF的《Design Patterns: Elements of Reusable Object-Oriented Software》中给出了三类(创建型[对类的实例化过程的抽象化]、结构型[描述如何将类或对象结合在一起形成更大的结构]、行为型[对在不同的对象之间划分责任和算法的抽象化])共23种设计模式,包括:Abstract Factory(抽象工厂模式),Builder(建造者模式),Factory Method(工厂方法模式),Prototype(原始模型模式),Singleton(单例模式);Facade(门面模式),Adapter(适配器模式),Bridge(桥梁模式),Composite(合成模式),Decorator(装饰模式),Flyweight(享元模式),Proxy(代理模式);Command(命令模式),Interpreter(解释器模式),Visitor(访问者模式),Iterator(迭代子模式),Mediator(调停者模式),Memento(备忘录模式),Observer(观察者模式),State(状态模式),Strategy(策略模式),Template Method(模板方法模式), Chain Of Responsibility(责任链模式)。

所谓设计模式,就是一套被反复使用的代码设计经验的总结(情境中一个问题经过证实的一个解决方案)。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。设计模式使人们可以更加简单方便的复用成功的设计和体系结构。将已证实的技术表述成设计模式也会使新系统开发者更加容易理解其设计思路。

14.你在开发中都用到了那些设计模式?用在什么场合?

1)工厂模式:工厂类可以根据条件生成不同的子类实例,这些子类有一个公共的抽象父类并且实现了相同的方法,但是这些方法针对不同的数据进行了不同的操作(多态方法)。当得到子类的实例后,开发人员可以调用基类中的方法而不必考虑到底返回的是哪一个子类的实例。

2)代理模式:给一个对象提供一个代理对象,并由代理对象控制原对象的引用。实际开发中,按照使用目的的不同,代理可以分为:远程代理、虚拟代理、保护代理、Cache代理、防火墙代理、同步化代理、智能引用代理。

3)适配器模式:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起使用的类能够一起工作。

4)模板方法模式:提供一个抽象类,将部分逻辑以具体方法或构造器的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法(多态实现),从而实现不同的业务逻辑。

除此之外,还可以讲讲上面提到的门面模式、桥梁模式、单例模式、装潢模式(Collections工具类里面的synchronizedXXX方法把一个线程不安全的容器变成线程安全容器就是对装潢模式的应用,而Java IO里面的过滤流(有的翻译成处理流)也是应用装潢模式的经典例子)等,反正原则就是拣自己最熟悉的用得最多的作答,以免言多必失。

15.你的项目使用Spring框架的原因

Spring提供了企业级开发的一站式选择,有大量的功能模块可供选择,并且可以根据项目的需要自由取舍。Spring通过POJO简化了JavaEE开发,低侵入式的编程提供了代码的持续集成能力和易测试性。

Spring框架的核心功能是依赖注入(DI)。DI使得代码的单元测试更加方便、系统更好维护、代码也更加灵活。DI代码自身很容易测试,通过构建实现了应用所需的接口的“模拟”对象就可以进行功能的黑盒测试。DI代码也更容易复用,因为其“被依赖的”功能封装在在定义良好的接口中,允许其他对象根据需要将其插入到所需的对象中,这些对象是在其他应用平台中进行配置的。DI使代码更加灵活,由于其天生的松耦合性,它允许程序员仅需考虑自己所需的接口和其他模块暴露出来的接口来就可以决定对象之间如何关联。

Spring支持面向切面编程(AOP),允许通过分离应用业务逻辑和系统服务从而进行内聚性的开发。AOP通常用来支持日志、审计、性能和内存监控等功能。

Spring还提供了许多实现基本功能的模板类,使得Java EE应用的开发更加容易。例如,JdbcTemplate类和JDBC、JpaTemplate类和JPA,JmsTemplate类和JMS都可以很好地结合起来使用。RestTemplate类非常简洁,使用这个模板的代码的可读性和可维护性也都很好。

Spring提供了声明性事务处理,工作调度,身份认证,成熟的Web MVC框架以及和其他框架的集成,例如Hibernate、MyBatis、JasperReports、JSF、Struts、Tapestry、Seam和Quartz等等。
Spring Bean对象可以通过Terracotta在不同的JVM之间共享。这就允许使用已有的Bean并在集群中共享 ,将Spring应用上下文事件变为分布式事件,还可以通过Spring JMX导出集群Bean,使得Spring应用高可用、集群化。

Spring倾向于使用非受检异常(运行时异常)和减少不当try、catch和finally代码块,例如JpaTemplate这样的Spring模板类会负责关闭或释放数据库连接,这避免了潜在的外部资源泄露问题并提高了代码的可读性。

16.你是如何理解控制反转(IoC)和依赖注入(DI)的?

控制反转 (Inversion of Control, IoC)是把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓的“控制反转”就是对组件对象控制权的转移,从程序代码本身转移到了外部容器,由容器来创建对象并管理对象之间的依赖关系。IoC体现了好莱坞原则:“Don’t call me, we will call you”。依赖注入(Dependency Injection,DI)的基本原则是:应用组件不应该负责查找资源或者其他依赖的协作对象。配置对象的工作应该由容器负责,查找资源的逻辑应该从应用组件的代码中抽取出来,交给容器来完成。DI是对IoC更准确的描述,即组件之间的依赖关系由容器在运行期决定,形象的来说,即由容器动态的将某种依赖关系注入到组件之中。

举个例子:一个类A需要用到接口B中的方法,那么就需要为类A和接口B建立关联或依赖关系,最原始的方法是在类A中创建一个接口B的实现类C的实例,但这种方法需要开发人员自行维护二者的依赖关系,也就是说当依赖关系发生变动的时候需要修改代码并重新构建整个系统。如果通过一个容器来管理这些对象以及对象的依赖关系,则只需要在类A中定义好用于关联接口B的方法(构造器或setter方法),将类A和接口B的实现类C放入容器中,通过对容器的配置来实现二者的关联。

17.转发(forward)和重定向(redirect)的区别?

forward是容器中控制权的转向,是服务器请求资源,服务器直接访问目标地址的URL,把那个URL 的响应内容读取过来,然后把这些内容再发给浏览器,浏览器根本不知道服务器发送的内容是从哪儿来的,所以它的地址栏中还是原来的地址。redirect就是服务器端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址,因此从浏览器的地址栏中可以看到跳转后的链接地址。前者更加高效,在前者可以满足需要时,尽量使用转发(通过RequestDispatcher对象的forward方法,RequestDispatcher对象可以通过ServletRequest对象的getRequestDispatcher方法获得),并且,这样也有助于隐藏实际的链接;在有些情况下,比如,需要跳转到一个其它服务器上的资源,则必须使用重定向(通过HttpServletResponse对象调用其sendRedirect方法)。

18.JSP有哪些内置对象?作用分别是什么?

JSP有9个内置对象:

request:封装客户端的请求,其中包含来自GET或POST请求的参数;
response:封装服务器对客户端的响应;
pageContext:通过该对象可以获取其他对象;
session:封装用户会话的对象;
application:封装服务器运行环境的对象;
out:输出服务器响应的输出流对象;
config:Web应用的配置对象;
page:JSP页面本身(相当于Java程序中的this);
exception:封装页面抛出异常的对象。

19.get和post请求的区别?

①get请求用来从服务器上获得资源,而post是用来向服务器提交数据;

②get将表单中数据按照name=value的形式,添加到action 所指向的URL 后面,并且两者使用“?”连接,而各个变量之间使用“&”连接;
post是将表单中的数据放在HTML头部(header),传递到action所指向URL;

③get传输的数据要受到URL长度限制(1024字节);而post可以传输大量的数据,上传文件只能使用post方式;

④使用get时参数会显示在地址栏上,如果这些数据不是敏感数据,那么可以使用get;对于敏感数据还是应用使用post;

⑤get使用MIME类型application/x-www-form-urlencoded的URL 编码(URL encoding,也叫百分号编码)文本的格式传递参数,保证被传送的参数由遵循规范的文本组成,例如一个空格的编码是"%20"。

相关文章

网友评论

      本文标题:Java面试题整理2017

      本文链接:https://www.haomeiwen.com/subject/fhgxuxtx.html