Java

JAVA开发工程师笔试试题方正 java开发工程师面试题总结_局部变量

 

Java基础

1、方法、变量的作用域public、protect、protected 以及不写时的区别

JAVA开发工程师笔试试题方正 java开发工程师面试题总结_基础面试题_02

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.4float 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 对应的集合,然后对每个元素执行所有的函数。

面向对象

  1. 类和对象有什么区别?

类是一个抽象的概念,是具有相同特征的事物的描述,是对象的模板。对象是一个个具体的存在,是类的实例。

  1. 简述面向对象的特性

①封装:建议成员变量私有,然后提供公有的getter/setter方法来获取值/赋值,封装的核心思想是合理隐藏,合理暴露,可以提高安全性,实现代码的组件化。

②继承:一种子类到父类的关系,是“is a”关系,可以提高代码的复用性,相同代码可写到父类,子类的功能更加强大,不仅得到了父类的功能,还有自己的功能。

③多态:同一个类型的对象执行相同的行为,在不同的状态下表现出不同的特征。多态可以降低类之间的耦合度,右边对象可以实现组件化切换,业务功能随之改变,便于扩展和维护。

  1. 列举Object类的方法

①equals(Object obj):判断其他对象是否与当前对象相等(通过判断两个对象的内存地址是否相等)。

②toString():打印当前对象的字符串表示。

③wait():导致当前线程等待,等待其他线程唤醒,会释放锁。

④notify()/notifyAll():随机唤醒一个/全部线程。

⑤hashCode():返回当前对象的hashCode值。

⑥finalize():当垃圾回收器要回收对象前调用。

⑦clone():创建并返回对象的一个副本。

  1. 方法重载和方法重写的区别?

①方法重载是同一个类中具有不同参数列表的同名方法(无关返回值类型),方法重写是子类中具有和父类相同参数列表的同名方法,会覆盖父类原有的方法。

②重载的返回值类型和权限修饰符,异常抛出类型没有要求,重写方法的返回值类型小于等于父类被重写方法的返回值类型,修饰符权限大于等于父类被重写方法权限修饰符,抛出的异常类型小于等于父类被重写方法抛出的异常类型。

③函数返回值类型不一样不是重载,因编译器方法签名的唯一性不包含函数返回值类型;

  1. 谈谈你理解的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()来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,实现了线程的数据隔离

  1. 每个Thread维护着一个ThreadLocalMap的引用
  2. ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储
  3. 调用ThreadLocal的set()方法时,实际上就是往ThreadLocalMap设置值,key是ThreadLocal对象,值是传递进来的对象
  4. 调用ThreadLocal的get()方法时,实际上就是往ThreadLocalMap获取值,key是ThreadLocal对象
  5. ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value