文章目录
1. 海兴电网Java面试总结
1.1 你理解多态是什么,来说一说
- Java是面向对象的语言,具有封装继承多态的特点:
- 封装性着重考虑其安全性和重用性。
- 继承性着重考虑其高效性和重用性。
- 多态性着重考虑其统一性(有机性)和高效性。
- 多态的前提是一个父类和多个子类,即父类引用指向子类对象。在调用一个方法时,从源代码上看(即编写代码的时候),无法确定调用了哪个对象的方法(因为父子类有相同的方法),只有在程序
运行期间
根据对象变量引用的实际对象才能确定此方法是哪个对象的,这种现象称之为动态绑定。
- 多态的具体体现有哪些?
- 继承:多个子类对同一方法的重写
- 接口:实现接口并覆盖接口中的同一方法
- 项目里面哪个地方用到和具体实现?(扩展了解)
- 多态有编译时多态和运行时多态。
- 第一种就是我们调用方法是不用区分参数类型,程序会自动执行相应方法,如: 加法运算,可以使int相加,可以是double相加,都是同一个方法名。
- 第二种就是动态绑定,使用父类引用指向子类对象,再调用某一父类中的方法时,不同子类会表现出不同结果。 这样的作用就是扩展性极好,玩过网游的话应该知道 游戏中有不同的角色,它们都有一个父类,它们做相同动作时表现出来的效果就会不一样,比如跑,魔法师的跑跟狂战士的跑就不会一样,这就是俩者都覆盖了父类中的跑方法,各自有自己的现实,表现出来多态。 如果有一天你想再加个角色,只用再写一个类继承该父类,覆盖其中的跑方法就行了,其他代码不用怎么改,所以可维护性也很好。
- 其实说到多态就是 面向接口编程,它不和具体类尽心挂钩了。比如 你没用多态的话,你每实例化一个对象 就要new一下,那假如你那天改变了需求了呢?那是不是又要改里面的?这样不好,所以 你可以通过多态,把需要相似的给提出来,然后继承它 这样以后需要扩展你仅仅只是继承而已,这样就很简单。
- 所以
多态性着重考虑其统一性(有机性)和高效性。
1.2 来说一说Java类的加载过程
Java类加载的目的?
- 所有的类都由类加载器将class文件加载到内存中。
类加载过程?
- 加载–连接–初始化
- 连接包括:验证–准备–解析
类加载器
ClassLoader
都有哪些?
- 启动类加载器
BootstrapClassLoader
最顶层的加载类,由C++实现
,负责加载
%JAVA_HOME%/lib目录下的jar包和类
或者被-Xbootclasspath参数指定的路径中的所有类
。- 扩展类加载器
ExtensionClassLoader
主要负责加载目录 %JRE_HOME%/lib/ext 目录下的jar包和类
,或被 java.ext.dirs 系统变量
所指定的路径下的jar包。- 应用程序类加载器
ApplicationClassLoader
面向我们用户的加载器,负责加载当前应用classpath
下的所有jar包和类。- 用户自定义类加载
全局委托机制(双亲委派机制)
- 系统中的
ClassLoder
在协同工作的时候会默认使用 双亲委派模型 。即在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。加载的时候,首先会把该请求委派该父类加载器的 loadClass() 处理
,因此所有的请求最终都应该传送到顶层的启动类加载器BootstrapClassLoader
中。当父类加载器无法处理时,才由自己来处理。当父类加载器为null时,会使用启动类加载器BootstrapClassLoader
作为父类加载器。ClassLoaderDemo.class.getClassLoader().getParent().getParent()
AppClassLoader
的父类加载器为ExtClassLoader
ExtClassLoader
的父类加载器为null,null并不代表ExtClassLoader
没有父类加载器,而是BootstrapClassLoader
。自底向上检查类是否被加载,自顶向下尝试加载类。
相同的类文件被不同的类加载器加载的结果是不是一样的?
- 是两个不同的类,JVM区分不同的类的方式不仅仅根据类型,这样结果也保证了Java的核心API(应用程序接口)不被篡改。如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为
java.lang.Object
类的话,那么程序运行的时候,系统就会出现多个不同的 Object 类。单列的通过不同的类加载器是不是相同的?
- 单列的对象被不同的类加载也是两个不同的对象?是因为…
如果我们不想用双亲委派模型怎么办?
- 为了避免双亲委托机制,我们可以自己定义一个类加载器,然后重载
loadClass()
即可。
loadClass()
源码分析(了解)private final ClassLoader parent; protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 首先,检查请求的类是否已经被加载过 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) {//父加载器不为空,调用父加载器 loadClass()方法处理 c = parent.loadClass(name, false); } else {//父加载器为空,使用启动类加载器 BootstrapClassLoader 加载 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { //抛出异常说明父类加载器无法完成加载请求 } if (c == null) { long t1 = System.nanoTime(); //自己尝试加载 c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
1.3 Java多线程的基础类?
创建一个线程对象的三种方式?(使用场景很少,一般使用线程池)
通过继承
Thread类
创建线程对象//继承自Thread类 public class CreateThreadClass extends Thread{ @Override//重写其run()---run()方法称为执行体 public void run() { super.run(); System.out.println("通过继承Thread类创建线程"); } } //测试 class TestThreadClass{ public static void main(String[] args) { CreateThreadClass myThread = new CreateThreadClass(); myThread.start(); } }
实现
Runnable接口
的方式,推荐此种方法开发多线程,因为Java单继承但是可以实现多个接口public class CreateThreadInterface implements Runnable{ @Override public void run() { System.out.println("实现Runnable接口的方式创建线程"); } } class TestThreadInterface{ public static void main(String[] args) { Runnable runnable = new CreateThreadInterface(); // 注意创建线程的方式 Thread thread = new Thread(runnable); thread.start(); } }
实现Callable接口
采用实现Runnable、Callable接口的方式创建多线程的优劣势?
- 优势是:线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
- 劣势是:
编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()
方法。使用继承Thread类的方式创建多线程时的优劣势?
- 优势是:编写简单,如果需要访问当前线程,则无需使用
Thread.currentThread()
方法,直接使用this即可获得当前线程。- 劣势是:线程类已经继承了Thread类,所以不能再继承其他父类。
线程池的底层线程实现?线程池的使用有两种方式?
**
ExecutorService
**是真正的线程池接口
Executor
是线程池的顶级接口,只是一个执行线程的工具,只提供一个execute(Runnable command)的方法,真正的线程池接口是ExecutorService
**
AbstractExecutorService
实现了ExecutorService
**接口,实现了其中大部分的方法(有没有实现的方法,所以被声明为Abstract)
ThreadPoolExecutor
,继承了**AbstractExecutorService
,是ExecutorService
**的默认实现不提倡我们直接使用**
ThreadPoolExecutor
**,而是使用Executors类中提供的几个静态方法来创建线程池ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<Runnable>(5)); threadPoolExecutor.execute(new Runnable() { @Override public void run() { } });
**
ExecutorService
**是真正的线程池接口,所以我们在通过Executors创建各种线程时,都是采用下述代码所示的方式ExecutorService executorService1 = Executors.newFixedThreadPool(3); ExecutorService executorService2 = Executors.newSingleThreadExecutor(); ExecutorService executorService3 = Executors.newCachedThreadPool(); ExecutorService executorService4 = Executors.newScheduledThreadPool(3); executorService1.execute(new Runnable() { @Override public void run() { } });
1.4 锁是怎么实现的?
悲观锁(
synchronized
和 **ReentrantLock
**使用悲观锁的思想实现的)和乐观锁(版本号机制和CAS算法实现)思想。
1.4.1 乐观锁
- 版本号机制:数据表中添加数据的版本号version字段表示数据被修改的次数,数据被修改时version值会+1;
- CAS(比较于交换–无锁算法)
1. **ABA
**问题;
2. 循环时间长开销大;
3. CAS只对单个共享变量有效,当操作涉及跨多个共享变量时CAS无效;
1.4.2 悲观锁
synchronized
- JDK1.6之前:synchronized属于重量级锁,效率低下,因为监视器锁是依赖于底层的系统Mutex Lock来实现的,java的线程是映射到操作系统的原生线程之上的。(挂起和唤醒一个线程)需要操作系统实现线程时需要从用户态切换到内核态,耗时,效率低;
- JDK1.6开始:Java官方从JVM层面对synchronized做了较大的优化(自旋锁,适应性自旋锁,锁消除…);
synchronized加锁的方式
- 修饰实例方法–给对象实例加锁?
- 修饰静态方法–给类加锁 通过ACC_synchronized标示
- 修饰代码块–实现使用monitorenter和monitorexit指令(每个java对象的对象头中 monitor 1 0);
synchronized 和 ReentrantLock的区别?
- 两者都是可重入锁
- JVM层面和API层面(ReentrantLock lock() unlock() 配合着try/finally)
- ReentrantLock 比synchronized 增加一些高级功能:
- 等待可中断
- 可实现公平锁
- 可实现选择性通知
1.5 线程之间的切换是多线程层面(上下文切换),生产者消费者模式?
生产者消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一存储空间,生产者向空间里生产数据,而消费者取走数据。
阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
wait和notifiy()
和信号量来 实现生产者方式。
1.6 说一下你知道的设计模式?
1.6.1 单列模式servlet,连接池
懒汉式:就是不在系统加载时就创建类的单例,而是在第一次使用实例的时候再创建。
public class Singleton{ private static Singleton singleton = null; private Singleton() { } public synchronized static Singleton newInstance() { if (singleton == null) { singleton= new Singleton(); } return singleton; } }
饿汉式:在加载类的时候就会创建类的单例,并保存在类中。
public class EHanDanli { private static EHanDanli dl = new EHanDanli(); private EHanDanli(){ } public static EHanDanli getInstance(){ return dl; } }
双重加锁机制:懒汉形式的加强版,将synchronized关键字移到了**
newInstance
**方法里面,同时将singleton对象加上volatile关键字,这种方式既可以避免多线程问题,又不会降低程序的性能。但volatile关键字也有一些性能问题,不建议大量使用。public class Singleton { private volatile static Singleton singleton; private Singleton() { } public static Singleton newInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
内部静态类:内部类的机制使得单例对象可以延迟加载,同时内部类相当于是外部类的静态部分,所以可以通过
jvm
来保证其线程安全。public class Singleton { private static class SingletonHolder { private static Singleton singleton = new Singleton(); } private Singleton() { } public static Singleton newInstance() { return SingletonHolder.singleton; } }
1.6.2 工厂模式
- 简单工厂模式(静态工厂模式)
- 简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类。
- 问题: 对于新产品的加入,都要在工厂类中增加相应的创建业务逻辑,不符合开闭原则(对扩展开放,对修改封闭)
- 工厂方法模式
- 去掉了静态工厂的静态属性,使得它可以被子类继承,这样简单工厂集中在工厂方法上的压力可以由工厂模式中不同的子类来承担
- 问题:工厂方法模式,仿佛已经很完美的对创建的对象进行了封装,但当产品种类非常多时,会出现大量的工厂对象
- 抽象工厂模式:
- 抽象工厂模式是工厂模式的升级版,用于创建一组相关或者相互依赖的对象,每一个具体的工厂是生产一个对应的产品族的。
1.6.3 装饰着模式
- 动态的将责任附加到对象身上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
- 动态附加
- 扩展功能
- 比继承更优
- 装饰者模式的应用:IO流
- Java的IO流是践行装饰者模式的典型实践。以InputStream字节流为例。首先,了解一下基本的继承关系(但实际上字节流的类复杂得多,以下只列出了一部分)InputStream是最上层的父类,表示字节流FileInputStream是一个具体类,用来对文件进行读取BufferedInputStream是字节缓冲流,主要是为了减少I/O操作的次数,提高效率。学习Java基础的过程中,经常会使用BufferedInputStream进行文件读取
1.6.4 代理模式
- 代理模式它的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。
- 动态代理模式:
- 动态代理机制:首先通过**
newProxyInstance
**方法获取代理类实例,而后我们便可以通过这个代理类实例调用代理类的方法,对代理类的方法的调用实际上都会调用中介类(调用处理器)的invoke方法,在invoke方法中我们调用委托类的相应方法,并且可以添加自己的处理逻辑。- 静态代理和动态代理的区别?
- 代理类对象的本身并不提供服务,而是由委托对象的方法提供服务
- 代理模式通过代理将具体的实现与调用的方法解耦,面向接口将具体的实现隐藏在内部
- 静态代理类在程序运行之前它的字节码文件已经存在了,动态代理在程序运行中利用反射动态的创建
- 静态代理事先知道自己要代理什么,而动态代理不知道
- 静态代理只能为特定的接口服务,要想代理多个接口,必须要创建多个代理类
1.7 Spring框架的核心是什么?
- spring的核心IOC和AOP
- spring里面IOC的核心生产的核心是bean
- spring的作用范围:在Spring中,可以在元素的scope属性里设置bean的作用域,以决定这个bean是单实例的还是多实例的。
- singleton :单例的,默认作用域; 特点:单例的bean在
ioc
容器被创建的时候就会创建。- prototype:多例的。
- 特点:多例的bean不会在
ioc
容器创建的时候创建对象。 bean的对象会被创建多次。每次通过getBean方法获取对象的时候都会返回一个新的bean对象- request:一次请求期间只创建一次bean对象
- session:一次会话期间只会创建一个bean对象。