Java面试题(持续更新)

Java基础篇

修饰权限符

  • public: 可以被任何其他类访问。如果一个类是public的,那么你可以从任何其他类中访问它。
  • protected: 可以被同一包内的任何类访问。可以被不同包中的子类访问。但是,它不能被其他不同包中的非子类访问。
  • (default) package-private (没有修饰符)::可以被同一包内的任何类访问。不能被其他包中的类访问,无论它们是否是子类。  
  • private:只能被其所在的类访问。类的成员(如字段、方法、内部类等)可以声明为private,但最外层的类不能声明为private。 

equals()和hashCode的关系

HashSet,HastMap都不允许重复,在存放数据时会先调用HoahCode来判断和之前的数据是否重复,如果和其他对象的hashCode值一样就会再调用equals来判断是否是一个对应,equals如果也一样的话就断定是一个值。

可以使用 int Objects.hash(args...)函数来实现hashCode,参数一般填为自己的所有字段。

垃圾回收

System.gc(),建议vm进行垃圾回收的函数。

垃圾回收时,会从所有的GC Root作为是起始点,从这些点开始,垃圾回收器会尝试查找和访问堆上的对象。换句话说,从GC Root可以直接或间接地引用到的对象都被认为是"活动的",也就是还在使用中。而那些从GC Root无法访问到的对象则可能是垃圾,它们可能会在下一次垃圾回收时被清除。

以下是常见的GC Root:

  1. 虚拟机栈引用的对象:当前线程中的局部变量、参数、返回值等都属于这一类。

  2. 方法区中静态属性引用的对象:也就是所有的类级别的静态变量。

  3. 方法区中常量引用的对象:例如字符串常量池中的引用。

  4. 本地方法栈中JNI(即通常所说的Native方法)引用的对象。

  5. 活跃的线程本身。

  6. 所有被同步锁synchronized持有的对象。

硬引用、软引用、弱引用(虚引用)

各种引用的区别就是垃圾回收的时候,处理的方式不一样。

硬引用

普通创建的对象就是硬引用,除非引用变量在栈中被销毁,否则不会被回收。

软引用

一般作为缓存使用。

当系统内存充足时,持有软引用的对象不会被垃圾收集器回收。

当系统内存不足或即将耗尽时,持有软引用的对象会被垃圾收集器考虑回收,即使这个对象还有软引用指向它。

举例:

想要创建一个图像的缓存。你不希望一直持有这些图像,以防止占用太多内存,但当内存充足时,你也不希望它们被频繁地回收和重新加载。这时,软引用是一个很好的选择:

import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;

public class ImageCache {

    private Map> cache = new HashMap<>();

    public Image getImage(String path) {
        SoftReference softRef = cache.get(path);
        if (softRef != null) {
            Image image = softRef.get();
            if (image != null) {
                return image; // 使用缓存中的图像
            }
        }

        // 缓存中没有图像或图像已被回收,重新加载它
        Image image = loadImageFromDisk(path);
        cache.put(path, new SoftReference<>(image));
        return image;
    }

    private Image loadImageFromDisk(String path) {
        // 从磁盘加载图像的代码
        // ...
        return new Image();
    }

    // Image类的定义,为简洁起见,此处省略实际实现
    private static class Image {}
}

多线程篇 

Java中有几种创建线程的方式

四种:继承Thread类,实现Runnable接口,

ThreadLocal

ThreadLocal只能放入一个对象和取出一个对象,但是很神奇的是,同一个线程中get出的对象是相同的。

static ThreadLocal xin = new ThreadLocal<>(); 一般把这个变量作为静态变量写在工具类中,属于同一个线程的方法使用了,存入与取出的对象是一样的。

Spring篇

Bean的作用域

Spring中Bean默认作用域是单利的,spring中的一个类加上@Component时,这个类有一个属性为a,a默认为0,第一个通过@AutoWrite获取时设置a为1,第二次通过@AutoWrite获取这个类的示例时时a的值是1。

可以用过@Bean与@Scope("prototype")来设置作用域。

Spring 的常用作用域:

  1. Singleton: 默认。每个Spring容器只有一个实例。
  2. Prototype: 每次请求都新建实例。
  3. Request: 一个HTTP请求内共享同一个实例。
  4. Session: 一个HTTP Session内共享同一个实例。

MVC架构

MVC 是 Model-View-Controller 的缩写,它是一种设计模式。MVC 将应用程序逻辑分为三个互相关联的组件,以实现模块化和更容易的管理。以下是这三个组件的解释:

  1. Model(模型):这是应用程序的数据部分。它包含所有的业务逻辑、数据和规则。模型是独立于视图和控制器的,这意味着模型不知道用户界面如何显示数据。

  2. View(视图):这是用户看到和与之互动的部分。它负责展示模型的数据。当模型中的数据发生变化时,视图会相应地更新。

  3. Controller(控制器):控制器是模型和视图之间的接口。它接收用户的输入并决定如何处理。基于用户的行为,控制器可以请求模型进行更改或更新,或者更改视图的表示。

前后端分离的现在,后端已经没有View层了。

Server、Mapper,domain等层属于Model类,Controller层属于controller类。

Spring如何解决循环依赖

注意:如果A变量的构造函数需要B,B的构造函数需要A,这种是误解的。

能解决什么类型的循环依赖问题

如果两个Java类在它们的构造函数中相互依赖,那么这会导致问题,因为无法在初始化一个类的同时初始化另一个类。

但是,Spring框架确实提供了处理循环依赖的机制。以下是Spring如何解决这个问题的:

  1. 字段注入:如你所说,如果A类有一个B类型的字段,并且这个字段是通过Spring注入的,同时B类也有一个A类型的字段并且这个字段是通过Spring注入的,那么Spring可以解决这种依赖关系。这是因为Spring首先创建了A和B的实例,然后再为它们的字段注入依赖。

  2. Setter方法注入:与字段注入类似,使用setter方法注入也可以解决循环依赖。Spring首先创建对象实例,然后使用setter方法为对象注入依赖。

Spring解决循环依赖的原理

这里举例A依赖B。B依赖A:

初始化A时,会先创建A类原始的Bean(未填充属性的),然后把这个Bean放入二级缓存,并在三级缓存中创建A的工厂对象。

填充A的属性时,发现需要B,于是就创建B。

创建B时发现需要A,Spring会从一级缓存中查找A,找不到就去二级缓存中查找A,发现二级有A的早期实例,就会拿这个填充。

这时B创建完了,就用B填充A。

三级缓存的问题
三级缓存中的工厂是什么时候放进去的

三级缓存中的工厂对象是在bean实例化但还未完全初始化(如属性填充)时放进去的。

三级缓冲中的工厂有什么用

三级工厂就是为了返回未初始化完成的Bean而创建。三级工厂返回的Bean会放入二级缓存。

这么做是为了不影响二级中的Bean初始化,二级中的Bean初始化完成之后要直接放入一级缓存。

工厂返回的Bean与二级缓存中的Bean是一样的,同一个引用。

对象没有循环依赖的情况

当Spring容器开始初始化一个bean(例如A)时,它会首先调用A的构造方法来实例化它。

然后Spring会进行属性注入、执行初始化方法等其他初始化步骤。

一旦bean完全初始化,它会直接放入一级缓存,这时它已经是一个完全初始化的单例bean,可以被其他bean或应用代码引用。

常用注解

@Autowired与@Resource区别

这两个都是做Bean注入用的,@Resource并不是Spring的注解,他的包是javax.annotation.Resource需要导入,但是Spring支持改注解的注入。

共同点:都可以写在字段和setter方法上,两者如果都写在字段上。

@Autowired默认按照类型(byType)装配依赖对象

默认情况下他要求依赖的对象必须存在,如果允许为null,可以设置他的required属性为false。

如果我们想按名称(byName)来装配,可以结合@Qualifier注解一起使用,@Qualifier注解来指定名称。

@Resource默认按照名称(ByName)装配依赖对象,由j2ee提供

@Resource有两个重要的属性,name和type。

Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。

@Indexed

需要额外添加依赖:


    org.springframework.boot
    spring-boot-context-indexer

这是@Indexed的依赖,添加这个依赖之后可以提高Springboot的启动速度。

添加这个依赖编译项目(使用Maven的install)后会自动把:@Component,@Indexed,@Controller,@Service,@Repository这些相关的额注解标注的类的路径自动包含在“target/META-INF/spring-components”文件中,启动时提高速度。

@Import

@Import注解的作用:让Spring容器从其他指定的配置类中加载被@Bean修饰过的方法。

public class Person {
    ...
}

public class Address {
    ...
}

@Configuration
public class MainConfig {

    @Bean
    public Person john() {
        return new Person("John");
    }
}

@Configuration
public class AdditionalConfig {

    @Bean
    public Address newYorkAddress() {
        return new Address("New York");
    }
}

@Configuration
@Import({MainConfig.class, AdditionalConfig.class})
public class CombinedConfig {

    @Autowired
    private Person john;

    @Autowired
    private Address newYorkAddress;

    @Bean
    public String johnsAddressInfo() {
        return john.getName() + " lives in " + newYorkAddress.getCity();
    }
}

上面代码中:

CombinedConfig类上的@Import注解使MainConfig和AdditionalConfig中所有用@Bean标注的方法的返回值被注册到Spring容器中。

当Spring启动并处理CombinedConfig时,它不仅会注册CombinedConfig中定义的beans,还会注册MainConfig和AdditionalConfig中定义的beans。这正是@Import注解的主要目的。

@Controller与RestController的区别

@RestController
public class HelloController {
    @GetMapping("/restController")
    public User test() {
        return new User();
    }
}

@Controller
public class HelloController {
    @GetMapping("/controller")
    public User test() {
        return new User();
    }
}

@RestController:

它是一个组合注解,等同于 @Controller 和 @ResponseBody 的组合。
它意味着这个控制器的所有方法的返回值都将自动序列化为 JSON 或其他媒体类型,并作为 HTTP 响应的体返回。因此,当你访问 http://localhost/restController时,在浏览器中直接看到的是 xxx 对象的 JSON 表示。

问:@RequestMapping("/hello") public String Hello(){ return "hello"; } }:返回字符串时:RestController不是返回的json格式吗,为什么,为什么浏览器的网络相应中返回的是hello,而不是“hello”,为什么hello会直接输出到页面上,RestController什么时候返回json什么时候返回xml

答:

为什么返回的是 "hello" 而不是 “"hello"”?

当你返回一个简单的字符串时,Spring MVC 会直接将这个字符串作为响应体返回,而不会将它视为一个 JSON 字符串值。所以,返回的就是 hello 而不是 JSON 格式的 "hello"。

为什么 "hello" 会直接输出到页面上?

这是因为默认的 Content-Type 是 text/plain。所以浏览器会把它当作纯文本来处理,并直接显示。

@RestController 何时返回 JSON 和何时返回 XML?

@RestController 会基于请求的 Accept 头和项目的依赖来确定返回什么格式的数据。

如果请求头包含 Accept: application/json 并且你的项目中有合适的 JSON 库(如 Jackson),那么默认返回的是 JSON 格式。
如果请求头包含 Accept: application/xml 并且你的项目中有 XML 绑定库,那么会返回 XML 格式。
如果方法返回一个对象或集合,并且接受的请求头是 Accept: application/json,则 @RestController 通常会返回 JSON 格式的数据。如果没有特定的 Accept 头或者接受多种内容类型,那么通常默认返回的是 JSON,前提是你的项目中有相关的 JSON 库。

@Controller:

当你在方法上没有使用 @ResponseBody 注解时,@Controller 注解的方法默认返回的是一个视图(view)的名字(通常用于模板引擎,例如 Thymeleaf, JSP 等)。
http://localhost:86/controller 时方法返回的是一个 User 对象。但因为你没有指定视图名,也没有使用 @ResponseBody,Spring 将尝试查找一个与 User 对象名匹配的视图,这通常不是你想要的。
这里的视图,指的是JSP那种。

普通方法与REST风格方法的对比

REST风格:

在REST风格中,HTTP动词(方法)如 GET, POST, PUT, DELETE 通常有以下的含义和约定:

  1. GET:

    • 用于检索资源。
    • 它应该只是读操作,不会更改资源的状态或执行任何副作用。
    • 例如:GET /api/users/123 可以用来检索ID为123的用户的详细信息。
  2. POST:

    • 用于创建新资源。
    • 通常在创建新资源时使用,响应中经常会返回一个 Location 头,指示新创建资源的URI。
    • 例如:POST /api/users 可以用来创建一个新用户,请求体中包含用户的详细信息。
  3. PUT:

    • 用于更新现有资源或创建新资源(如果它不存在的话)。
    • 与POST不同,PUT是幂等的,这意味着多次执行同一个PUT请求应该总是产生相同的结果。
    • 例如:PUT /api/users/123 可以用来更新ID为123的用户的详细信息或创建一个新用户(如果它之前不存在)。
  4. DELETE:

    • 用于删除资源。
    • 例如:ELETE /api/users/123 会删除ID为123的用户。
  • URL 中可以使用路径变量来指定具体的资源实例,如 /api/users/{id}。
  • 数据通常以 JSON 或 XML 格式传输。
  • get类型请求用于查询,delete请求用来删除等。

REST风格使用多个参数:@GetMapping("/api/users/{userId}/orders/{orderId}");,对应的普通请求链接:“/api/userOrder?userId=123&orderId=456”。

普通风格:

GET /getUser?id=1:用来查询。

RESTful

REST 描述了一种理论上的架构风格和一组原则,而当一个 Web 服务实际遵循这些原则时,我们称其为 RESTful。

数据库篇

索引篇

什么是索引:

排好序可以快速查找的数据结构。

索引的优势与劣势:

优势:提高数据检索的效率,降低数据库的io成本,通过索引排序能够大大减少CPU的消耗(因为索引已经是排好序的)。

劣势:需要经常增删的表不建议使用索引,因为更新表的数据时,也会更新索引数据,会降低更新的速度。

索引的分类:

主键索引,单值索引,唯一索引,复合索引。

单值索引和唯一索引的区别就是,单值索引允许重复,单一索引不允许。

设计模式篇

观察者模式

对象间的一种一对多的关系,当一个对象的状态发生改变时,所有依赖它的对象都会通知并自动更新。

一般主线程中一个while(1)循环,不停的判断子线程的状态,状态变化时通知其它子线程,比如任务的撤销。

工厂模式

工厂根据传递过来的参数决定生产哪个对象,Spring中可以通过getBean方法获取对象的时候根据id或者name获取,就是简单的工厂模式。

适配器模式

将一个类的接口转换为客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。

装饰者模式(包装模式)

用来动态的为一个对象添加新的功能,装饰者模式是一种用于替代继承的技术,无需通过继承增加子类就能扩展对象的新功能,使用对象的关联关系替代继承关系,更加灵活,同时避免体系的快速膨胀。

代理模式

Spring AOP的实现就是代理模式。

策略模式

策略模式对应于解决某一个问题的一个算法族,允许用户从该算法族中任选一个算法解决莫一问题,同时可以方便的更换算法或者增加新的算法,并且由客户端决定使用哪个算法。

你可能感兴趣的:(java,开发语言)