Java面试-高频基础题(附答案与解析)【1】
2020-02-12 本文已影响0人
韶华易逝_铭
1、求以下这些例子的输出结果(考察点:自增变量):
public static void main(String[] args){
int i = 1;
i = i++ ;
int j = i++;
int k = i+ ++i * i++;
System.out.println("i="+i);
System.out.println("j="+j);
System.out.println("k="+k);
}
1.1分析:
1、int i = 1, 得i = 1
2、i = i++ ,目前i = 1,将其压入操作数栈,在“=”号返回之前,修改i的值为2,然后“=”将栈中的1赋给i,因此目前i = 1
3、同理上一步,j = 1,i = 2
4、目前i的值为2,将其压入操作数栈,然后遇到了++i,将3压入操作数栈,又遇到i++,也将3压入操作数栈,本地变量i变为4。
所以目前操作数栈:3 3 2。局部变量i = 4,j = 1
最后使用运算符计算值:k = 2 + 3 * 3 = 11
最终结果:
i = 4
j = 1
k = 11
1.2小结:
1、赋值操作,最后才计算值。
2、等号右边的,从左到右加载值并依次压入操作数栈。
3、实际算哪个要看运算符的优先级。
4、自增、自减操作都是直接改变变量的值,不经过操作数栈。
5、没赋值之前,临时结果存储在操作数栈中。
2、单例模式(考点:多种实现方式,多线程环境)
- 一个类只有一个实例
- 构造器私有化
- 必须是他自己创建实例
- 必须有一个静态变量保存这个类的唯一实例
- 必须向系统提供这个实例
- 提供一个方法给外部获取实例
实现的方式
1.1饿汉式
特点:直接创建对象,不存在线程问题
- 直接饿汉
- 枚举类
- 静态代码块实现饿汉式
/**
* 直接饿汉:
* 直接创建对象,不存在线程问题
*
*/
public class Singleton01 {
//使用public,而且是final
public final static Singleton01 INSTANCE = new Singleton01();
//私有构造
private Singleton01(){
}
}
/**
* 枚举饿汉式
* 枚举类就是限定若干个实例
* 我们只限定一个实例就是单例模式
*/
public enum Singleton02 {
INSTANCE;
}
/**
* 静态代码块的饿汉式
* 在静态代码可以编写一些设置属性的方法,更灵活
*/
public class Singleton03 {
//使用public,而且是final
public final static Singleton03 INSTANCE ;
private String info ;
static {
Properties properties = new Properties();
try {
//读取配置文件(提前在类路径下,写一个文件singleton/kv.properties)
properties.load(Singleton03.class.getClassLoader().getResourceAsStream("singleton/kv.properties"));
//读取值
String info1 = properties.getProperty("info");
INSTANCE = new Singleton03(info1);
} catch (IOException e) {
throw new RuntimeException();
}
}
private Singleton03(){
}
private Singleton03(String info){
this.info = info;
}
1.2懒汉式
特点: 延迟创建对象
- 普通懒汉式,有线程安全问题
- 基于普通懒汉式,增加双重校验锁避免线程安全问题
- 用静态内部类实现,也能适应多线程环境
/**
* 普通懒汉式:
* 延迟加载,但是有线程安全问题
*/
public class Singleton04 {
private static Singleton04 instance ;
private Singleton04(){
}
/**
* 调用获取方法时才加载实例
* @return
*/
public static Singleton04 getInstance() {
if (null == instance){
try {
//设置一些阻碍,更容易暴露多线程中的问题
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Singleton04();
}
return instance;
}
}
public class TestSingleton04 {
public static void main(String[] args) {
/**
* 多线程环境
*/
Callable<Singleton04> singleton04Callable = () -> Singleton04.getInstance();
ExecutorService executorService = Executors.newFixedThreadPool(2);
Future<Singleton04> submit = executorService.submit(singleton04Callable);
Future<Singleton04> submit2 = executorService.submit(singleton04Callable);
try {
System.out.println(submit.get() == submit2.get());
System.out.println(submit.get());
System.out.println(submit2.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}finally {
executorService.shutdown();
}
}
}
/**
* 双重锁校验的懒汉式:
* 延迟加载,且线程安全
*/
public class Singleton05 {
private static Singleton05 instance ;
private Singleton05(){
}
/**
* 调用获取方法时才加载实例
* @return
*/
public static Singleton05 getInstance() {
//双重锁校验,提高了性能和安全
if (null == instance){
synchronized (Singleton05.class){
if (null == instance){
try {
//设置一些阻碍,更容易暴露多线程中的问题
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Singleton05();
}
}
}
return instance;
}
}
/**
* 静态内部类实现的懒汉式:
* 延迟加载,且线程安全
*/
public class Singleton06 {
private Singleton06(){
}
/**
* 静态内部类只有在调用时才会加载
*/
private static class Inner{
private static Singleton06 instance = new Singleton06();
}
/**
* 调用获取方法时才加载实例
* @return
*/
public static Singleton06 getInstance() {
return Inner.instance;
}
}
3、类的初始化和实例的初始化(考点:类是怎么初始化的?实例是怎么初始化的?多态性?)
Q:求出输出结果
public class Father {
private int i = test();
private static int j = method();
static {
System.out.println("(1)");
}
Father(){
System.out.println("(2)");
}
{
System.out.println("(3)");
}
public int test(){
System.out.println("(4)");
return 1;
}
public static int method(){
System.out.println("(5)");
return 1;
}
}
public class Son extends Father{
private int i = test();
private static int j = method();
static {
System.out.println("(6)");
}
Son(){
System.out.println("(7)");
}
{
System.out.println("(8)");
}
public int test(){
System.out.println("(9)");
return 1;
}
public static int method(){
System.out.println("(10)");
return 1;
}
public static void main(String[] args) {
Son son = new Son();
System.out.println();
Son son1 = new Son();
}
//解题步骤:
//第一步:类的初始化:
//从main方法存在的类开始加载,也就是Son类,但是加载前要先加载他的父类father的<clinit>()方法
//一个类的初始化就是执行< clinit >()方法,且只会执行一次
//< clinit >()方法由静态类变量显式赋值代码和静态代码块组成,从上到下按顺序执行
//所以先输出(5)(1)
//然后回到子类的初始化,输出(10)(6)
//第二步:实例的初始化
//执行实例初始化是执行<init>()方法,<init>()方法可能重载有多个,有几个构造器就有几个< init >()方法
// < init >()方法由非静态实例变量显式赋值代码和非静态代码块、对应构造器代码组成
// 非静态实例变量显式赋值代码和非静态代码块从上到下顺序执行,而对应构造器的代码最后执行
// 每次创建实例对象,调用对应构造器,执行的就是对应的< init >()方法
// < init >()方法的首行是super()或super(实参列表),即对应父类的< init >()方法
// 所以执行new Son()时,先执行父类的非静态实例变量显式赋值和非静态代码块
//执行父类 private int i = test();时,由于该方法已被子类重写,this的指向为子类,
//因此执行的是子类的method方法,输出(9)
//然后执行到父类的非静态代码块,输出(3)
//最后才会执行父类的无参构造函数,输出(2)
//回到子类,遇到子类的 private int i = test();时,输出(9)
//执行子类的非静态代码块,输出(8)
//最后才执行子类的无参构造函数,输出(7)
/**最终结果:
(5)
(1)
(10)
(6)
(9)
(3)
(2)
(9)
(8)
(7)
(9)
(3)
(2)
(9)
(8)
(7)
*/
}
解题关键:
类初始化的过程
- 一个类要创建实例需要先加载并初始化该类
- main方法所在的类需要先加载和初始化
- 一个子类的初始化需要先初始化父类
- 一个类的初始化就是执行< clinit >()方法,且只会执行一次
- < clinit >()方法由静态类变量显式赋值代码和静态代码块组成
- 类变量显式赋值代码和静态代码块从上到下按顺序执行
- < clinit >()方法只执行一次
实例初始化的过程
实例初始化就是执行< init >()方法
- < init >()方法可能重载有多个,有几个构造器就有几个< init >()方法
- < init >()方法由非静态实例变量显式赋值代码和非静态代码块、对应构造器代码组成
- 非静态实例变量显式赋值代码和非静态代码块从上到下顺序执行,而对应构造器的代码最后执行
- 每次创建实例对象,调用对应构造器,执行的就是对应的< init >()方法
- < init >()方法的首行是super()或super(实参列表),即对应父类的< init >()方法
4、方法的参数传递机制
Q:以下代码的输出结果是?
/**
* 参数的传递机制
*/
public class TestParameter {
public static void main(String[] args) {
int i = 1;
String str = "hello";
Integer num = 200;
int[] arr = {1,2,3,4,5};
MyData my = new MyData();
change(i,str,num,arr,my);
System.out.println("i = " + i);
System.out.println("str = " + str);
System.out.println("num = " + num);
System.out.println("arr = " + Arrays.toString(arr));
System.out.println("my.a = " + my.a);
}
public static void change(int j, String s, Integer n, int[] a, MyData m){
j += 1;
s += "world";
n += 1;
a[0] += 1;
m.a += 1;
}
}
class MyData{
int a = 10;
}
结果:
i = 1
str = hello
num = 200
arr = [2, 2, 3, 4, 5]
my.a = 11
分析:
- 1、形参是基本数据类型
- 传递数据值
- 2、实参是引用数据类型
- 传递地址值
- 特色的类型:String、包装类等对象不可变性
5、递归
时间复杂度?
空间复杂度?
Q:求N步的台阶,有多少种走法?
实现方式:
1、递归
//递归实现 01
public int f01(int n){
if (n < 1){
return 0;
}
if (n == 1 || n == 2){
return n;
}
return f01(n-1) + f01(n-2);
}
2、迭代
/**
* 迭代实现 02
* @param n
* @return
*/
public long f02(int n){
if (n < 1){
return 0;
}
if (n == 1 || n == 2){
return n;
}
//初始化:走第一级台阶有一种走法,走第二级台阶有两种走法
int first = 1, second = 2;
int third = 0;
//把每次计算的结果保存在变量中,避免重复计算
for (int i = 3; i <= n; i++) {
third = first + second;
first = second;
second = third;
}
return third;
}