Java
Java基础
1、方法、变量的作用域public、protect、protected 以及不写时的区别
2、ArrayList和Vector的区别,HashMap和Hashtable的区别
同步性: Vector是线程安全的,也就是说是同步的,而ArrayList是线程序不安全的,非同步 数据增长: 当需要增长时,Vector默认增长为原来一培,而ArrayList却是原来的一半
历史原因: Hashtable是基于陈旧的Dictionary类的,HashMap是Java 1.2引进的Map接口的一个实现
同步性: Hashtable是线程安全的,也就是说是同步的,而HashMap是线程序不安全的,不是同步的 值: 只有HashMap可以让你将空值作为一个表的条目的key或value
3、char型变量中能不能存贮一个中文汉字?为什么?
能够定义成为一个中文,因为java中以unicode编码,一个char占2个字节,所以放一个中文是没问题的
4、float型float f=3.4是否正确?
不正确。3.4是double类型赋值给float类型属于向下转型,会造成精度缺失。精度不准确,应该用强制类型转换,如下所示:float f=(float)3.4 或 float f =3.4F;
5、抽象类与接口?
抽象类与接口都用于抽象,但是抽象类(JAVA中)可以有自己的部分实现,而接口则完全是一个标识(同时有多重继承的功能)。
6、Java数据类型
分为基本数据类型和引用数据类型。
基本数据类型包括:数值型(byte、short、int、long、float、double),字符型(char)以及布尔型(boolean)。
除了基本类型外,其他数据类型都属于引用类型,包括类、接口、数组等。
7、字符串拼接方式及效率
①使用+直接拼接,String 是final对象,不会被修改,每次使用 +进行拼接都会创建新的对象,而不是改变原来的对象,效率低,是线程安全的。反编译来看,单个优化,转成了StringBuilder拼接,如果在for循环体中用+的话将不会被优化!
②使用StringBuffer可变字符串,效率较高,是线程安全的(StringBuffer的方法使用了synchronized关键字进行修饰)。
③使用StringBuilder可变字符串,效率最高,但是线程不安全。
8、简述final,finally和finalize区别
①final可以修饰类,方法和变量,被final修饰的类不可继承,被final修饰的方法不可重写,被final修饰的变量引用不可更改,引用的内容可以更改。
②finally用于try-catch代码块中,无论是否发生异常最后都将执行,作用是释放资源。
③finalize是Object类的方法,在对象被垃圾回收之前将调用一次,一般用于资源的释放。
9、说说JDK1.8
1.default关键字
2.lambda表达式
3.函数式接口
4.方法与构造函数引用 即
ClassName :: methodName
5.局部变量限制
10、lambda表达式需要注意?
Lambda表达式也允许使用自由变量(不是参数,而是在外层作用域中定义的变量),就像匿名类一样。 它们被称作捕获Lambda。 Lambda可以没有限制地捕获(也就是在其主体中引用)实例变量和静态变量。但局部变量必须显式声明为final,或事实上是final。
为什么局部变量有这些限制?
(1)实例变量和局部变量背后的实现有一个关键不同。实例变量都存储在堆中,而局部变量则保存在栈上。如果Lambda可以直接访问局部变量,而且Lambda是在一个线程中使用的,则使用Lambda的线程,可能会在分配该变量的线程将这个变量收回之后,去访问该变量。
因此, Java在访问自由局部变量时,实际上是在访问它的副本,而不是访问原始变量。如果局部变量仅仅赋值一次那就没有什么区别了——因此就有了这个限制。
(2)这一限制不鼓励你使用改变外部变量的典型命令式编程模式。
final int num = 1; Converter<Integer, String> stringConverter =(from) -> String.valueOf(from + num); stringConverter.convert(2);
11.Date Api更新
1.8之前JDK自带的日期处理类非常不方便,我们处理的时候经常是使用的第三方工具包,比如commons-lang包等。不过1.8出现之后这个改观了很多,比如日期时间的创建、比较、调整、格式化、时间间隔等。这些类都在java.time包下。比原来实用了很多。
11.1 LocalDate/LocalTime/LocalDateTime
LocalDate为日期处理类、LocalTime为时间处理类、LocalDateTime为日期时间处理类,方法都类似,具体可以看API文档或源码,选取几个代表性的方法做下介绍。
now相关的方法可以获取当前日期或时间,of方法可以创建对应的日期或时间,parse方法可以解析日期或时间,get方法可以获取日期或时间信息,with方法可以设置日期或时间信息,plus或minus方法可以增减日期或时间信息;
11.2 TemporalAdjusters
这个类在日期调整时非常有用,比如得到当月的第一天、最后一天,当年的第一天、最后一天,下一周或前一周的某天等。
11.3 DateTimeFormatter
以前日期格式化一般用SimpleDateFormat类,但是不怎么好用,现在1.8引入了DateTimeFormatter类,默认定义了很多常量格式(ISO打头的),在使用的时候一般配合LocalDate/LocalTime/LocalDateTime使用,比如想把当前日期格式化成yyyy-MM-dd hh:mm:ss的形式:
LocalDateTime dt = LocalDateTime.now(); DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss"); System.out.println(dtf.format(dt));
12、stream
工厂式(流水线)处理数据
定义:流是Java API的新成员,它允许我们以声明性方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现)。就现在来说,我们可以把它们看成遍历数据集的高级迭代器。此外,流还可以透明地并行处理,也就是说我们不用写多线程代码了。
Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。
Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。而和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。Stream 的并行操作依赖于 Java7 中引入的 Fork/Join 框架(JSR166y)来拆分任务和加速处理过程。
流的操作类型分为两种:
Intermediate:一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
Terminal:一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。
在对于一个 Stream 进行多次转换操作 (Intermediate 操作),每次都对 Stream 的每个元素进行转换,而且是执行多次,这样时间复杂度就是 N(转换次数)个 for 循环里把所有操作都做掉的总和吗?其实不是这样的,转换操作都是 lazy 的,多个转换操作只会在 Terminal 操作的时候融合起来,一次循环完成。我们可以这样简单的理解,Stream 里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在 Terminal 操作的时候循环 Stream 对应的集合,然后对每个元素执行所有的函数。
面向对象
-
类和对象有什么区别?
类是一个抽象的概念,是具有相同特征的事物的描述,是对象的模板。对象是一个个具体的存在,是类的实例。
-
简述面向对象的特性
①封装:建议成员变量私有,然后提供公有的getter/setter方法来获取值/赋值,封装的核心思想是合理隐藏,合理暴露,可以提高安全性,实现代码的组件化。
②继承:一种子类到父类的关系,是“is a”关系,可以提高代码的复用性,相同代码可写到父类,子类的功能更加强大,不仅得到了父类的功能,还有自己的功能。
③多态:同一个类型的对象执行相同的行为,在不同的状态下表现出不同的特征。多态可以降低类之间的耦合度,右边对象可以实现组件化切换,业务功能随之改变,便于扩展和维护。
-
列举Object类的方法
①equals(Object obj):判断其他对象是否与当前对象相等(通过判断两个对象的内存地址是否相等)。
②toString():打印当前对象的字符串表示。
③wait():导致当前线程等待,等待其他线程唤醒,会释放锁。
④notify()/notifyAll():随机唤醒一个/全部线程。
⑤hashCode():返回当前对象的hashCode值。
⑥finalize():当垃圾回收器要回收对象前调用。
⑦clone():创建并返回对象的一个副本。
-
方法重载和方法重写的区别?
①方法重载是同一个类中具有不同参数列表的同名方法(无关返回值类型),方法重写是子类中具有和父类相同参数列表的同名方法,会覆盖父类原有的方法。
②重载的返回值类型和权限修饰符,异常抛出类型没有要求,重写方法的返回值类型小于等于父类被重写方法的返回值类型,修饰符权限大于等于父类被重写方法权限修饰符,抛出的异常类型小于等于父类被重写方法抛出的异常类型。
③函数返回值类型不一样不是重载,因编译器方法签名的唯一性不包含函数返回值类型;
-
谈谈你理解的equals()和hashcode()
equals() 用于判断两个对象是否相等(通过判断两个对象的内存地址是否相等),默认情况下,equals()《==》==;通常重写equals()方法,若两个对象的内容相等,则认为两个对象相等;
注意:基本类型和String类型,使用equals(),true!
== 的作用: 判断两个对象的地址是否相等
hashcode() 的作用:
获取hash码(int 整数),确定对象在哈希表中的索引位置。
private native int hashcode();
如果两个对象相等,则hash值一定相等。反之不一定。
6. sleep和wait有什么区别?
Thread.sleep()
与Object.wait()
二者都可以暂停当前线程,释放CPU控制权。
主要的区别在于
Object.wait()
在释放CPU同时,释放了对象锁的控制。而
Thread.sleep()
没有对锁释放
7. notify方法调用后,会发生什么?
notify会唤醒某个处于等待队列的线程。
注意:
notify方法调用后,被唤醒的线程不会立马获得到锁对象。而是等待notify的synchronized代码块执行完之后才会获得锁对象
8. 为什么wait和notify在Object方法上?
无论是wait、notify还是notifyAll()都需要由监听器对象(锁对象)来进行调用
简单来说:他们都是在同步代码块中调用的,否则会抛出异常!
notify()
唤醒的是在等待队列的某个线程(不确定会唤醒哪个),notifyAll()
唤醒的是等待队列所有线程导致
wait()
的线程被唤醒可以有4种情况
该线程被中断
wait()
时间到了被
notify()
唤醒被
notifyAll()
唤醒调用
wait()
的线程会释放掉锁锁对象是任意的,所以这些方法必须定义在Object类中
9. 什么是ThreadLocal?
ThreadLocal提供了线程的局部变量,每个线程都可以通过
set()
和get()
来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,实现了线程的数据隔离。
每个Thread维护着一个ThreadLocalMap的引用
ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储
调用ThreadLocal的set()方法时,实际上就是往ThreadLocalMap设置值,key是ThreadLocal对象,值是传递进来的对象
调用ThreadLocal的get()方法时,实际上就是往ThreadLocalMap获取值,key是ThreadLocal对象
ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value。