1.去掉 main 方法的 static 修饰符,程序会怎样?

A:程序无法编译

B:程序正常编译,正常运行

C:程序正常编译,正常运行一下马上退出

D:程序正常编译,运行时报错

答:D

题目解析:运行时异常如下:

错误: main 方法不是类 xxx 中的 static, 请将 main 方法定义为:

public static void main(String[] args)

2.以下程序运行的结果是?

public class TestClass {
    public static void main(String[] args) {
        System.out.println(getLength());
    }
    int getLength() {
        private String s = "xyz";
        int result = s.length();
        return result;
    }
}

A:3

B:2

C:4

D:程序无法编译

答:D

题目解析:成员变量 s 不能使用任何修饰符(private/protected/public)修饰,否则编译会报错。

3.以下程序有几处错误?

abstract class myAbstractClass
    private abstract String method(){};
}

A:1

B:2

C:3

D:4

答:C

题目解析:类少一个“{”类开始标签、抽象方法不能包含方法体、抽象方法访问修饰符不能为 private,因此总共有 3 处错误。

4.以下程序执行的结果是?

class A {
    public static int x;
    static {
        x = B.y + 1;
    }
}
public class B {
    public static int y = A.x + 1;
    public static void main(String[] args) {
        System.out.println(String.format("x=%d,y=%d", A.x, B.y));
    }
}

A:程序无法编译

B:程序正常编译,运行报错

C:x=1,y=2

D:x=0,y=1

答:C

5.switch 语法可以配合 return 一起使用吗?return 和 break 在 switch 使用上有何不同?

答:switch 可以配合 return 一起使用。return 和 break 的区别在于 switch 结束之后的代码,比如以下代码:

String getColor(String color) {
    switch (color) {
        case "red":
            return "红";
        case "blue":
            return "蓝";
    }
    return "未知";
}

String getColor(String color) {
    String result = "未知";
    switch (color) {
        case "red":
            result = "红";
            break;
        case "blue":
            result = "蓝";
    }
    return result;
}

对于以上这种 switch 之后没有特殊业务处理的程序来说,return 和 break 的效果是等效的。然而,对于以下这种代码:

String getColor(String color) {
    switch (color) {
        case "red":
            return "红";
        case "blue":
            return "蓝";
    }
    return "未知";
}

String getColor(String color) {
    String result = "未知";
    switch (color) {
        case "red":
            result = "红";
            break;
        case "blue":
            result = "蓝";
    }
    if (result.equals("未知")) {
        result = "透明";
    } else {
        result += "色";
    }
    return result;
}

如果 switch 之后还有特殊的业务处理,那么 return 和 break 就有很大的区别了。

6.一个栈的入栈顺序是 A、B、C、D、E 则出栈不可能的顺序是?

A:E D C B A

B:D E C B A

C:D C E A B

D:A B C D E

答:C

题目解析:栈是后进先出的,因此:

  • A 选项:入栈顺序 A B C D E 出栈顺序就是 E D C B A 是正确的;
  • B 选项:A B C D 先入栈,D 先出栈,这个时候 E 在入栈,E 在出栈,顺序 D E C B A 也是正确的;
  • C 选项:D 先出栈,说明 A B C 一定已入栈,因为题目说了入栈的顺序是 A B C D E,所以出栈的顺序一定是 C B A,而 D C E A B 的顺序 A 在 B 前面是永远不可能发生的,所以选择是 C;
  • D 选项 A B C D E 依次先入栈、出栈,顺序就是 A B C D E。

7.可以在 finally 块中使用 return吗?

答:不可以,finally 块中的 return 返回后方法结束执行,不会再执行 try 块中的 return 语句。

8.FileInputStream 可以实现什么功能?

A:文件夹目录获取

B:文件写入

C:文件读取

D:文件夹目录写入

答:C

题目解析:FileInputStream 是文件读取,FileOutputStream 才是用来写入文件的,FileInputStream 和 FileOutputStream 很容易搞混。

9.以下程序打印的结果是什么?

Thread t1 = new Thread(){
    @Override
    public void run() {
        System.out.println("I'm T1.");
    }
};
t1.setPriority(3);
t1.start();
Thread t2 = new Thread(){
    @Override
    public void run() {
        System.out.println("I'm T2.");
    }
};
t2.setPriority(0);
t2.start();

答:程序报错 java.lang.IllegalArgumentException,setPriority(n) 方法用于设置程序的优先级,优先级的取值为 1-10,当设置为 0 时,程序会报错。

10.如何设置守护线程?

答:设置 Thead 类的 setDaemon(true) 方法设置当前的线程为守护线程。

守护线程的使用示例如下:

Thread daemonThread = new Thread(){
    @Override
    public void run() {
        super.run();
    }
};
// 设置为守护线程
daemonThread.setDaemon(true);
daemonThread.start();

11.以下说法中关于线程通信的说法错误的是?

A:可以调用 wait()、notify()、notifyAll() 三个方法实现线程通信

B:wait() 必须在 synchronized 方法或者代码块中使用

C:wait() 有多个重载的方法,可以指定等待的时间

D:wait()、notify()、notifyAll() 是 Object 类提供的方法,子类可以重写

答:D

题目解析:wait()、notify()、notifyAll() 都是被 final 修饰的方法,不能再子类中重写。选项 B,使用 wait() 方法时,必须先持有当前对象的锁,否则会抛出异常 java.lang.IllegalMonitorStateException。

12.ReentrantLock 默认创建的是公平锁还是非公平锁?

答:默认创建的是非公平锁,看以下源码可以得知:

/** * Creates an instance of {@code ReentrantLock}. * This is equivalent to using {@code ReentrantLock(false)}. */
public ReentrantLock() {
    sync = new NonfairSync();
}

Nonfair 为非公平的意思,ReentrantLock() 等同于代码 ReentrantLock(false)。

13.ReentrantLock 如何在一段时间内无阻塞尝试访问锁?

答:使用 tryLock(long timeout, TimeUnit unit) 方法,就可以在一段时间内无堵塞的访问锁。

14.枚举比较使用 equals 还是 ==?

答:枚举比较调用 equals 和 == 的结果是一样,查看 Enum 的源码可知 equals 其实是直接调用了 ==,源码如下:

public final boolean equals(Object other) {
    return this==other;
}

15.在 Spring 中使用 @Value 赋值静态变量为什么 null?怎么解决?

答:因为在 Springframework 框架中,当类加载器加载静态变量时,Spring 上下文尚未加载,因此类加载器不会在 bean 中正确注入静态类,导致了结果为 null。可使用 Setter() 方法给静态变量赋值,代码如下:

@Component
public class ConfigValue {
    private static String accessKey;
    public String getAccessKey() {
        return accessKey;
    }
    @Value("${accessKey}")
    public void setAccessKey(String accessKey) {
        ConfigValue.accessKey = accessKey;
    }
}
/* * 调用赋值变量 */
@Component
public class TestClass {
    @Autowired
    private ConfigValue configValue;
    public void method() {
        // 读取配置文件
        configValue.getAccessKey();
    }
}

16.如何自己实现一个定时任务?

答:启动一个后台线程,循环执行任务。代码示例如下:

Thread getTocketThread = new Thread(new Runnable() {
    @Override
    public void run() {
        while (true) {
            try {
                // 执行业务方法
                TimeUnit.HOURS.sleep(2); // 每两小时执行一次
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
});
if (!getTocketThread.isAlive()) {
    System.out.println("启动线程");
    getTocketThread.start();
}

17.如何定义一个不定长度的数组?

答:在 Java 中使用数组必须要指定长度,如果长度不固定可使用 ArrayList、LinkedList 等容器接收完数据,再使用 toArray() 方法转换成固定数组。

18.如何优雅的格式化百分比小数?

答:使用数字格式化类 DecimalFormat 来处理,具体实现代码如下:

double num = 0.37500;
DecimalFormat df = new DecimalFormat("0.0%");
System.out.println(df.format(num)); // 执行结果:37.5%

19.什么是跨域问题?为什么会产生跨域问题?

答:跨域问题指的是不同站点直接,使用 ajax 无法相互调用的问题。跨域问题是浏览器的行为,是为了保证用户信息的安全,防止恶意网站窃取数据,所做的限制,如果没有跨域限制就会导致信息被随意篡改和提交,会导致不可预估的安全问题,所以也会造成不同站点间“正常”请求的跨域问题。

20.跨域的解决方案有哪些?

答:常见跨域问题的解决方案如下:

  • jsonp(只支持 get 请求);
  • nginx 请求转发,把不同站点应用配置到同一个域名下;

  服务器端设置运行跨域访问,如果使用的是 Spring 框架可通过 @CrossOrigin 注解的方式声明某个类或方法运行跨域访问,或者配置全局跨域配置,请参考以下代码:

// 单个跨域配置
@CrossOrigin(origins = "http://xxx", maxAge = 3600)
@RestController
public class testController{
}

// 全局配置
@Configuration
public class CorsConfiguration {
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/api/**").allowedOrigins("https://xxx");
            }
        };
    }
}

21.什么原因会导致 Nginx 转发时丢失部分 header 信息?该如何解决?

答:部分 header 信息丢失的原因是,丢失的 header 的 key 值中有下划线,因为 Nginx 转发时,默认会忽略带下划线的 header 信息。

解决方案有两个,一是去掉 key 值中的下划线,二是在 Nginx 的配置文件 http 中添加“underscoresinheaders on;” 不忽略有下划线的 header 信息。

22.如何设计一个高效的系统?

答:要设计一个高效的系统,通常要包含以下几个方面。

(1)优化代码

  代码优化分为两种情况:

  • 代码问题导致系统资源消耗过多的问题,比如,某段代码导致内存溢出,往往是将 JVM 中的内存用完了,这个时候系统的内存资源消耗殆尽了,同时也会引发 JVM 频繁地发生垃圾回收,导致 CPU 100% 以上居高不下,这个时候又消耗了系统的 CPU 资源。这种情况下需要使用相应的排查工具 VisualVM 或 JConsole,找到对应的问题代码再进行优化;
  • 还有一种是非问题代码,这种代码不容易发现,比如,LinkedList 集合如果使用 for 循环遍历,则它的效率是很低的,因为 LinkedList 是链表实现的,如果使用 for 循环获取元素,在每次循环获取元素时,都会去遍历一次 List,这样会降低读的效率,这个时候应该改用 Iterator (迭代器)迭代循环该集合。

(2)设计优化

  有很多问题可以通过我们的设计优化来提高程序的执行性能,比如,使用单例模式来减少频繁地创建和销毁对象所带来的性能消耗,从而提高了程序的执行性能。

(3)参数调优

  JVM 和 Web 容器的参数调优,对系统的执行性能也是也很大帮助的。比如,我们的业务中会创建大量的大对象,我们可以通过设置,将这些大对象直接放进老年代,这样可以减少年轻代频繁发生小的垃圾回收(Minor GC),减少 CPU 占用时间,从而提升了程序的执行性能。Web 容器的线程池设置以及 Linux 操作系统的内核参数设置,也对程序的运行性能有着很大的影响,我们根据自己的业务场景优化这两项内容。

(4)使用缓存

  缓存的使用分为前端和后端:

  • 前端可使用浏览器缓存或者 CDN,CDN 的全称是 Content Delivery Network,即内容分发网络。CDN 是构建在现有网络基础之上的智能虚拟网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。CDN 的关键技术主要有内容存储和分发技术;
  • 后端缓存,使用第三方缓存 Redis 或 Memcache 来缓存查询结果,以提高查询的响应速度。

(5)优化数据库

  数据库是最宝贵的资源,通常也是影响程序响应速度的罪魁祸首,它的优化至关重要,通常分为以下六个方面:

  • 合理使用数据库引擎
  • 合理设置事务隔离级别,合理使用事务
  • 正确使用 SQL 语句和查询索引
  • 合理分库分表
  • 使用数据库中间件实现数据库读写分离
  • 设置数据库主从读写分离

(6)屏蔽无效和恶意访问

  前端禁止重复提交:用户提交之后按钮置灰,禁止重复提交;用户限流,在某一时间段内只允许用户提交一次请求,比如,采取 IP 限流。

(7)搭建分布式环境,使用负载分发

  可以把程序部署到多台服务器上,通过负载均衡工具,比如 Nginx,将并发请求分配到不同的服务器,从而提高了系统处理并发的能力。