面向对象
1、面向对象和面向过程的区别
面向过程关注于一个功能实现的步骤,按步骤编程实现功能。
面向对象关注于一个功能实现的行为,将一些行为封装为一个对象来统一调用。
面向过程是一种事件为中心的编程思想。就是分析出解决问题所需的步骤,然后用函数把这些步骤实现,并按顺序调用。面向对象是以对象为中心的编程思想。
2、四个基本特性
**抽象:**Java中抽象的概念最直接的应用就是抽象类和接口,从复杂的业务逻辑中,提炼出它的本质。
**封装:**封装将数据以及加在这些数据上的操作组织在一起,提供给可信的其他类或对象操作。
**继承:**继承就是从一般到特殊的过程。通过继承可以拥有现有类的所有功能,并在无需重修编写原来类的情况下对这些功能进行扩展。
封装考虑内部实现,抽象考虑的是外部行为。封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块;他们都是为了解决代码重用。而多态是为了实现接口重用,为了类在继承和派生的时候,保证使用“家谱”中任一类的实例的某一属性时的正确使用。
3、抽象类和接口的区别
相同点:
都是上层的抽象;
都不能被实例化;
都能包含抽象方法。
不同点:
在抽象类中可以写非抽象的方法,从而避免在子类中重复书写他们,提高代码的复用性;接口中只能有抽象方法(JDK1.8新特性:默认方法(子类不一定要实现)、JDK1.9新特性:私有方法(增强默认方法));
一个类只能继承一个直接父类(普通类或抽象类),但是可以实现多个接口。
4、访问控制符
private:同类可见
default:同包可见
protected:同包可见,子类可见
public:全局可见
5、重载和重写
重载是指一个类中允许存在多个同名函数,而这些函数的参数不同;重写是指子类重新定义父类的方法。
6、构造器Constructor是否可被override?
构造器不允许被重写。
构造器不是方法,所有用于修饰方法特性的修饰符,都不能用来修饰构造器。
构造器是实例化对象过程中用来初始化这个对象用的。
语言特性
1、自动装箱与拆箱
将Integer a = 120; 编译为:Integer a = Integer.valueOf(120); 就是JDK的自动装箱操作。
将int e = a; 编译为:int e = a.intValue(); 就是JDK的自动拆箱操作。
自动装箱也就是将基本数据类型封装到对象中的操作,自动拆箱也就是将对象中的基本数据从对象中自动取出。
2、String和StringBuffer、StringBuilder的区别
性能差别:StringBuilder > StringBuffer > String;
String对字符串的操作(修改、拼接)其实是在创建新的对象,效率低下;
StringBuffer线程安全、StringBuilder线程不安全
3、hashCode和equals方法的关系
hashcode()方法是JDK根据对象的地址或者字符串的值计算出来的int类型的数值(哈希码值)。
同一对象多次调用hashcode()方法,必须返回相同的数值。
如果两个对象根据equals()方法比较是相等的,那么两个对象调用hashcode()方法返回的结果必须相等。
如果两个对象根据equals()方法比较是不相等的,那么两个对象调用hashcode()方法返回的结果不一定不相等。
4、Java中的集合类
Collection下所有子类集合都用于存储Value,Map下所有子类集合都用于存储Key-Value。
ArrayList是线程不安全的,Vector是线程安全的(二者底层都是数组类型结构),LinkedList线程不安全(底层链表类型结构);
ArrayList每次扩容50%,而Vector每次扩容翻倍;
Set集合存储无序的不可重复元素,允许一个null元素。HashSet对象必须定义hashcode()方法,LinkedHashSet具备HashSet的性能,但内部使用链表维护元素的顺序(插入顺序)。TreeSet底层使用树结构维护有序的元素。
HashMap是线程不安全的,可以存储null值null键和;HashTable是线程安全的,不允许存储null值null键;HashTable因为是线程安全的,所以性能低于HashMap。
5、什么是泛型?为什么要使用?泛型擦除?
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
泛型(JDK1.5特性)之前,当方法的参数类型设置为基类,那么可以往方法中传入该基类下任意类型的对象,这样方法就更具有通用性。另外,将方法参数设置为接口,更加方便(可实现多个接口)。
这样存在的问题是,当需要获取一个值的时候,必须强制类型转换。而强制装换类型的时候,容易使用错误的类型转换导致报错。
泛型擦除是指:在Java中使用泛型创建对象时,在编译期间,所有的泛型信息都会被擦除,编译后会变成原始类型。
6、Java中的异常
IndexOutOfBoundsEecption:元素越界异常;
ArrayIndexOutOfBoundsEecption:多个元素越界异常;
ClassCastException:类型转换异常;
NullPointerException:空指针异常,null对象的应用;
RuntimeException:运行时异常;
IOException:IO操作异常;
ConnectException:连接失败异常;
7、Java中的BIO,NIO,AIO
BIO(同步阻塞IO): 一个连接对应一个线程
当有客户端连接请求时,服务端需要启动一个线程进行处理,如果这个连接不做任何处理,会造成不必要的线程开销,可以通过线程池机制改善,从而实现伪异步IO;
NIO(同步非阻塞IO): N个连接对应一个线程
客户端所有的连接请求都会注册到多路复用器上,服务端通过一个多路复用器来处理所有请求。
AIO(异步非阻塞IO): NIO的2.0版本,引入了异步通道的概念,可以实现异步调用。
异步实现方式:通过java.util.concurrent.Future类来表示异步操作的结果;
在执行异步操作的时候传入java.nio.channels。
8、序列化与反序列化
序列化是指将对象的状态信息转换为可以存储或传输的形式的过程,通过序列化可以将对象的状态保存为字节数组,需要的时候再将字节数组反序列化为对象。
9、IO和NIO区别
NIO是Java1.4的新特性,提供了与标准IO不同的工作方式:
标准IO基于字节流和字符流进行操作,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据从通道读取到缓冲区中,或者从缓冲区写入到通道。
NIO引入了选择器(Selectors)概念,选择器用于监听多个通道的事件(比如:连接打开、可读、可写),因此NIO可以通过一个线程监听多个数据通道。相比标准IO为每一个连接创建一个线程,NIO大大降低了线程创建的资源开销。
多线程
1、多线程的实现方式
通常使用继承Thread类或实现Runnable接口
还可以通过Callable接口实现。
2、线程的状态转换
new 新建线程
Runnable 可运行状态(执行start()方法,CPU决定是否运行)
Blocking 阻塞状态(线程被阻塞于锁)
Waiting 等待、计时等待(等待某些条件成熟)
Stop 终止状态(线程运行结束)
3、sleep和wait的区别
1、sleep()是线程Thread的方法,而wait()是Object对象的方法。
2、sleep()不会释放对象锁、wait()会释放对象锁
3、sleep()可以在任何地方调用,wait()方法只可以在同步方法或同步块中使用。
yield() 当前线程出让cpu占有权,当前线程变成可运行状态。
wait()\notify()\notifyAll()
调用以前,当前线程必须要持有锁,调用它们线程会释放锁,等待通知机制。
notify() 唤醒一个线程(谨慎使用),具体唤醒哪个线程,由CPU决定。
notifyAll() 所有在对象O上wait的线程全部唤醒(应用较多)
4、如何停止一个线程
1、run方法代码执行完成
2、线程运行时抛出一个未捕获的异常,跳出线程
3、通过标志位跳出线程
4、interrupt() 向需要中断的线程发送停止指令;isInterrupted() 线程检查自己的中断标志位;Thread.interrupted() 将中断标志位复位为false;
不安全方式
Stop() 立刻停止线程,但不会释放线程运行所应用的资源
Suspend() 立刻挂起线程,但不会释放线程运行锁应用的资源,容易造成死锁
5、volatile关键字
在多个线程之间,访问同一个被volatile修饰的对象时,所有线程共享这个对象的值。
但是volatile不是线程安全的(多个线程同时修改这个变量时,最终结果不一定是最后修改的那个值;可以保证线程的可见性,不可以保证操作的原子性)
6、synchronized如何使用
加锁
可以修饰方法或代码块以同步的方式执行(同一时间只会有一个线程执行)
类锁与实例锁本质上是两把锁,类锁锁的是每一个类的class对象。
7、synchronized和Lock的区别
synchronized是一个Java的关键字,Lock是一个接口;
synchronized代码块执行完或线程抛出异常时结束线程,Lock必须显示调用释放锁的方法:unlock();
synchronized修饰的锁其他线程在等待获取锁的阶段,会一直阻塞等待直到得到锁为止(不可中断锁);Lock有多种方式可以获取锁,不一定一直阻塞等待(可中断锁)。
synchronized无法判断锁的状态,Lock可以判断;
synchronized是非公平锁,而Lock可以设置为公平锁;
Lock用法:
lock()(阻塞线程等待获取锁)
lockInterruptibly():可中断(阻塞线程等待获取锁,会响应中断)
tryLock():尝试非阻塞的获取锁(非阻塞方式尝试获取锁,无法获取则返回false)
unlock()
公平锁与非公平锁:
公平锁,先对锁发出获取请求的一定先获得锁。非公平锁则反之(性能更高)。
锁的可重入:
递归的时候发生锁的重入
synchronized隐式支持锁的重入
ReentrantLock的lock()支持锁的重入
排它锁: 同一时刻只有一个线程获取锁;
读写锁: 同一时刻运行多个读线程访问,但是只允许一个写线程,写锁会阻塞所有锁。(ReentrantReadWriteLock,相比synchronized速度更快)
Condition接口有何作用?
Condition接口与Lock配合,来实现等待通知机制。
8、什么是线程安全
当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的。
9、死锁
当一个锁未被释放,其他线程无法获取锁的时候,程序产生死锁情况。
死锁的两种情况:
1、线程thread1先获取锁locka,然后在同步块里嵌套竞争锁lockb。而线程thread2先获取锁lockb,然后在同步块里嵌套竞争锁locka。
2、Lock.unlock()方法的错误使用,导致死锁。
10、Java线程池
什么是线程池?用于管理线程的一个工具。
线程池的作用?限制系统中执行线程的数量;降低资源的消耗、提高响应速度、提高线程的可管理性。
Java常见的线程池:
Executors.newSingleThreadExecutor:单个线程的线程池;
Executors.newFixedThreadExecutor:固定线程数量的线程池;
Executors.newCacheThreadExecutor:可缓存线程;
Executors.newScheduledThreadPool:创建一个定长线程池,支持定时和周期性的执行线程
11、并发工具类和并发容器类
常用的并发工具类:
闭锁:CountDownLatch
栅栏:CyclicBarrier
信号量:Semaphore
交换者:Exchanger
CountDownLatch 闭锁允许一个线程或多个线程等待特定情况,同步完成线程中其他任务。
CyclicBarrier 和CountDownLatch都可以协同多个线程,让指定数量的线程等待期他所有的线程都满足某些条件之后才继续执行。CyclicBarrier可以重复使用(reset),而CountDownLatch只能够使用一次,如果还需要使用,必须重现new一个CountDownLatch对象。
构造方法CyclicBarrier(int, Runnable) 所有线程达到屏障后,执行Runnable。
Semaphore 型号量用来控制同时访问特定资源的线程数量。
Exchanger 交换者用于在两个线程之间传输数据,被调用后等待另一个线程达到交换点,然后相互交互数据。
常用的并发容器:
ConcurrentHashMap: JDK1.7实现:分段锁;JDK1.8实现:元素(key)锁+链表+红黑树
SkipList: 跳表自动随机维护一套索引,用于高效的索引List中的有序数据。
ConcurrentSkipListMap: TreeMap的并发实现
ConcurrentSkipListSet: TreeSet的并发实现
ConcurrentLinkedQueue: LinkedList的并发实现
CopyOnWriteArrayList: 写时复制,在添加元素是,复制一个新的容器,在新容器中新增元素;读数据都在Old容器中操作,进行读写分离。数据一致性较弱,适合读多写少的场景。
CopyOnWriteArraySet: 同上