我的校招记录:校招笔记(零)_写在前面 ,以下是校招笔记总目录。

备注
算法能力(“刷题”) 这部分就是耗时间多练习,Leetcode-Top100 是很好的选择。 补充练习:codeTop
计算机基础(上)(“八股”) 校招笔记(一)__Java_Java入门 C++后端后续更新
校招笔记(一)__Java_面对对象
校招笔记(一)__Java_集合
校招笔记(一)__Java_多线程
校招笔记(一)__Java_锁
校招笔记(一)__Java_JVM
计算机基础(下)(“八股”) 校招笔记(二)__计算机基础_Linux&Git
校招笔记(三)__计算机基础_计算机网络
校招笔记(四)__计算机基础_操作系统
校招笔记(五)__计算机基础_MySQL
校招笔记(六)__计算机基础_Redis
校招笔记(七)__计算机基础_数据结构
校招笔记(八)__计算机基础_场景&智力题
校招笔记(九)__计算机基础_相关补充
项目&实习 主要是怎么准备项目,后续更新

1.2 面对对象

1.2.1 基本问题

1.介绍一下面对对象七大原则?三大特性

七大原则
  • 单一职责原则: 就一个类来说,应该仅有一个引起它变化的原因。也就是说,一个类应该只有一个职责

    如果有多个职责,那么就相当于把这些指责耦合在起,一个职责的变化就可能削弱或抑制了这个类完成其他职责的能力,引起类的变化的原因就会有多个。所以在构造一个类时, 将类的不同职责分离至两个或多个类中(或者接口中),确保引起该类变化的原因只有一个。

  • 开闭原则(OCP): 软件组成实体应该是可扩展的,但是不可修改。开放-封闭原则认为应该试图设计永远也不需要改变的模块。可以添加新代码来打展系统的行为,不能对已有的代码进行修改。

    这个原则很好的实现了面向对象的封装性和可重用性。

  • 李氏替换原则(LSP): 子类应当可以替换父类并出现在父类能够出现的任何地方。

    以圆和椭圆为例,圆是椭圆的一一个特殊子类。因此任何出现椭圆的地方,圆均可以出现。

  • 依赖倒置原则(DIP): 在进行业务设计时,与特定业务有关的依赖关系应该尽量依赖接口和抽象类而不是依赖于具体类。具体类只负责相关业务的实现,修改具体类不影响与特定业务有关的依赖关系。

    为此,在进行业务设计时,应尽量在接口或抽象类中定义业务方法的原型,并通过具体的实现类(子类)来实现该业务方法,业务方法内容的修改将不会影响到运行时业务方法的调用。

  • 接口分离原则(ISP)采用多个与特定客户类有关的接口 比采用一个通用的涵盖多个业务方法的接口要好。

    举例:如果拥有一个针对多个客户的类,为每一个客户创建特定业务接口,然后使该客户类继承多个特定业务接口将比直接加载客户所需所有方法有效

  • 组合重用原则 :能用组合实现的地方,尽量用组合来实现,而不要使用继承来扩展功能。

    097因为组合能更好地实现封装,比继承具有更大的灵活性和更稳定的结构。

  • 迪米特原则 : 一个对象应该对于其他对象有最少的了解,这样做的好处就是可以有效地降低类之间的耦合要求。

三大特性
  • 封装封装把⼀个对象的属性私有化,同时提供⼀些可以被外界访问的属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果⼀个类没有提供给外界访问的方法,那么这个类也没有什么意义了。

  • 继承继承是使用已存在的类的定义作为基础建⽴新类的技术,新类的定义可以增加新的数据或新的功能,也可以用⽗类的功能,但不能选择性地继承⽗类。通过使用继承我们能够非常方便地复用以前的代码。

    1. 子类拥有⽗类对象所有的属性和方法(包括私有属性和私有方法),但是⽗类中的私有属性和方法子类是无法访问,只是拥有。
    2. 子类可以拥有⾃⼰属性和方法,即子类可以对⽗类进行扩展。
    3. 子类可以用⾃⼰的方式实现⽗类的方法。
  • 多态。(1)静态多态:重载 (2)动态多态:所谓多态就是指程序中定义的引用变量所指向的具体类型和<通过该引用变量发出的方法调用编程时并不确定,而是在【程序运行期间才确定】。即⼀个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。

    在 Java 中有两种形式可以实现多态:继承(多个子类对同⼀方法的重写)和接口(实现接口并覆盖接口中同⼀方法)。

1.1 java多态的原理?【阿里&待重写】

多态分两种:(1)【编译】时多态(静态多态)(2)运行时多态(动态多态)。

  1. 静态多态

    重载(overload)就是编译时多态的一个例子,编译时多态在编译时就已经确定

    运行时运行的时候调用的是确定的方法。

  2. 动态多态

    我们通常所说的多态指的都是运行时多态,也就是编译时不确定究竟调用哪个具体方法,一直延迟到运行时(链接过程)才能确定。

    通常动态多态的实现方法:

    1. 子类继承父类(extends)
    2. 类实现接口(implements)

    核心之处就在于对父类方法的改写或对接口方法的实现,以取得在运行时不同的执行效果。

  3. 多态运行的原理

    详细建议查看:深入理解Java多态的实现原理

    • 背景介绍

      img

      类加载 时会将类的元数据信息类的方法代码、类变量、成员变量的定义等等)保存到方法区,方法区主要分为两部分:

      1. 常量池:Java 类引用的一些常量信息,比如类的符号引用信息
      2. 方法区其它部分: 保存方法表

      链接过程 类的多态就发生在 链接的解析 过程,将 符号引用替换为直接引用

    • 原理简述(子类方法继承

      参考:https://www.huaweicloud.com/articles/9b805c24ab31a65f5883c0dfeaf5a39b.html

2.请解释Java中的概念,什么是构造函数?什么是构造函数重载?什么是复制构造函数

  • 构造函数: 每一个类都有构造函数,程序员没有创建时,编译器会默认创建一个构造函数;对象被创建时,构造函数被调用
  • 构造函数重载: 和方法重载类似,一个类可以创建多个构造函数,每个构造函数都有唯一参数列表
  • 复制构造函数: Java不支持像C++中那样的复制构造函数。

3.请说明Java中的方法重写(Overriding)和方法重载(Overloading)是什么意思?构造函数能否被重写

  • Overriding : 方法重写是说子类重新定义了父类的方法,有相同的方法名,参数列表和返回类型

  • Overloading: 同一个类里面两个或者是多个方法同名 ,但 参数列表不同不同。

    特别的重写要求返回类型一致,但重载不要求返回类型一致

构造函数不能被 override(重写)!但是可以 overload(重载),所以你可以看到⼀个类中有多个构造函数的情况。

3.1 f(List<String> l) f(List<Integer> l)是重载么?

不是重载。

静态类型一致,并不会因为泛型而改变。因为编译期间,会对泛型进行擦除

4.介绍一下接口和抽象类的区别

  • 设计层面

    接口,是对类的行为进行约束,强制要求不同类实现相同行为 ; 抽象类,既 1.非抽象类实现代码复用 2.又同时有抽象方法使得被继承类各自实现

  • 方法实现

    抽象类可以有非抽象方法,有方法体 ; 接口不能有。

    抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 如果你往接口中添加方法,那么你必须改变实现该接口的类。

  • 构造函数

    抽象类有;接口没有。

  • 修饰符

    抽象类除private 都有(抽象类目的是被继承,所以抽象方法是为被重写,不能私有);接口默认public

  • 继承个数 [接口优]

    抽象类只能被继承一次;接口可以有多个。

5. Java的四种引用?强软弱虚

  • 强引用 :强引用是平常中使用最多的引用,强引用在程序内存不足(OOM)的时候也不会被回收,使用方式:

    1
    2
    String str = new String("str");
    System.out.println(str);
  • 软引用: 软引用在程序内存不足时,会被回收(“软”,没钱用了第一个被打劫),使用方式:

    可用场景: 创建缓存的时候,创建的对象放进缓存中,当内存不足时,JVM就会回收早先创建的对象

    1
    2
    3
    // 注意:wrf这个引用也是强引用,它是指向SoftReference这个对象的,
    // 这里的软引用指的是指向new String("str")的引用,也就是SoftReference类中T
    SoftReference<String> wrf = new SoftReference<String>(new String("str"));
  • 弱引用:是只要JVM垃圾回收器发现了弱引用,就会将之回收,使用方式:

    可用场景: Java源码中的 java.util.WeakHashMap 中的 key 就是使用弱引用。一旦我不需要某个引用,JVM会自动帮我处理它,这样我就不需要做其它操作。

    1
    WeakReference<String> wrf = new WeakReference<String>(str);
  • 虚引用:无法通过虚引用来获取对一个对象的真实引用; 虚引用必须与ReferenceQueue一起使用,当GC准备回收一个对象,如果发现它还有虚引用,就会在回收之前,把这个虚引用加入到与之关联的ReferenceQueue中。

    可用场景: 对象销毁前的一些操作,比如说资源释放等。 Object.finalize() 虽然也可以做这类动作,但是这个方式即不安全又低效。

    1
    2
    PhantomReference<String> prf = new PhantomReference<String>(new String("str"),
    new ReferenceQueue<>());

6. JAVA创建对象的机制

  • new创建新对象

  • 通过反射机制

  • 采用clone机制

  • 通过序列化机制

7.简述Java的对象结构

Java对象由三个部分组成:对象头、实例数据、对齐填充

  1. 对象头。 在JVM中,对象在内存中除了本身的数据外还会有个对象头,对于普通对象而言,其对象头中有两类信息:mark word和类型指针。 如果是数组对象,还有数组长度。

    1. mark word(32位)

      image-20210516131559341

      • 具体的内容包含对象的hashcode、分代年龄、轻量级锁指针、重量级锁指针、GC标记(分代年龄)、偏向锁线程ID、偏向锁时间戳。
      • 当对象状态为偏向锁时,mark word存储的是偏向的线程ID;当状态为轻量级锁(lightweight locked)时,mark word存储的是指向线程栈中Lock Record的指针;当状态为重量级锁(inflated)时,为指向堆中的monitor对象的指针
    2. 存储类型指针,也就是指向类的元数据的指针,通过这个指针才能确定对象是属于哪个类的实例

    3. 数组长度:另外对于数组而言还会有一份记录数组长度的数据。

  2. 实例数据。 来存储对象真正的有效信息(包括父类继承下来的和自己定义的);

  3. 对齐填充。JVM要求对象起始地址必须是8字节的整数倍(8字节对齐)。

8. Object有哪些常用方法?

  • equals方法
  • hashCode方法
  • wait方法
  • notify方法
  • notifyAll方法

1.2.2 反射

1.请说明一下JAVA中反射的实现过程和作用分别是什么? (快手)优缺点?

  • 定义

    反射机制是在运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意个对象,都能够调用它的任意一个方法。在java中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。

    jdbc就是典型的反射 :

    1
    Class.forName('com.mysql.jdbc.Driver.class');//加载MySQL的驱动类
  • 实现和作用

    JAVA语言编译之后会生成一个.class文件,反射就是通过字节码文件找到某一个类、类中的方法以及属性等。

    • 实现: (1)代码会编译成一个.class文件 (2)类加载器加载进JVM的内存中,在方法区创建了Object类的Class对象

      不是new出来的对象,而是类的类型对象,每个类都只有一个Class对象,作为方法区类的数据结构的接口。

      我们便是通过这个class对象来进行反射获取类的信息。

    • 作用

      1. 反射机制指的是程序在运行时能够获取自身的信息。在JAVA中,只要给定类的名字,那么就可以通过反射机制来获取类的所有信息。
      2. 根据类名在运行时创建实例(类名可以从配置文件读取,不用new)
  • 反射优缺点

    • 优点:(1)对于任意一个类,都能够知道这个类的所有属性和方法;(2)对于任意一个对象,都能够调用它的任意一个方法
    • 缺点
      • 性能降低 : 反射包括了一些动态类型,所以JVM无法对这些代码进行优化
      • 安全限制: 使用反射技术要求程序必须在一个没有安全限制的环境中运行
      • 内部暴露:由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用

2. 解释一下JAVA代理模式?动态代理的原理?

参考这个:JAVA面试50讲之9:动态代理的原理是什么?

代理模式是给某一个对象提供一个代理,并由【代理对象】控制对【原对象】的引用(使用)

  • 优点:代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度;可以灵活地隐藏被代理对象的部分功能和服务,也增加额外的功能和服务
  • 缺点:由于使用了代理模式,因此程序的性能没有直接调用性能高;使用代理模式提高了代码的复杂度

根据代理模式又可以分为:静态代理和动态代理。

2.1 静态代理

静态代理:由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口、被代理类、代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。

代理模式最主要的就是有一个公共接口(Person),一个具体的类(Student),一个代理类(StudentsProxy),代理类持有具体类的实例,代为执行具体类实例方法。

  • 一个班的同学(Student)要向老师交班费,但是都是通过班长(StudentProxy)把自己的钱转交给老师。这里,班长就是代理学生上交班费,班长就是学生的代理。

  • 公共Person接口

    1
    2
    3
    4
    /public interface Person {
    //上交班费
    void giveMoney();
    }
  • Student实现Person接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Student implements Person {
    private String name;
    public Student(String name) {
    this.name = name;
    }

    @Override
    public void giveMoney() {
    System.out.println(name + "上交班费50元");
    }
    }
  • StudentsProxy实现Person接口

    实现了Peson接口,同时持有一个Student对象,那么他可以代理学生类对象执行上交班费(执行giveMoney()方法)行为。

    ⚠️ 自己实现的giveMoney,调用的是被代理的学生对象.giveMoney() 方法!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    /public class StudentsProxy implements Person{
    //被代理的学生
    Student stu;

    public StudentsProxy(Person stu) {
    // 只代理学生对象
    if(stu.getClass() == Student.class) {
    this.stu = (Student)stu;
    }
    }

    //代理上交班费,调用被【代理学生的上交班费】行为
    public void giveMoney() {
    stu.giveMoney();
    }}
  • 使用实例1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class StaticProxyTest {
    public static void main(String[] args) {
    //被代理的学生张三,他的班费上交有代理对象monitor(班长)完成
    Person zhangsan = new Student("张三");

    //生成代理对象,并将张三传给代理对象
    Person monitor = new StudentsProxy(zhangsan);

    //班长代理上交班费,实际上是调用被代理对象的giveMoney方法
    monitor.giveMoney();
    }
    }
  • 使用实例2:扩充增强原对象方法

    班长在帮张三上交班费之前,想要先反映一下张三最近学习有很大进步,通过代理模式很轻松就能办到 。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class StudentsProxy implements Person{
    //被代理的学生
    Student stu;

    public StudentsProxy(Person stu) {
    // 只代理学生对象
    if(stu.getClass() == Student.class) {
    this.stu = (Student)stu;
    }
    }

    //代理上交班费,调用被代理学生的上交班费行为
    public void giveMoney() {
    System.out.println("张三最近学习有进步!");
    stu.giveMoney();
    }
    }
2.2 动态代理

上面静态代理的例子中,代理类(studentProxy)是自己定义好的,在程序运行之前就已经编译完成 。

然而动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成“的 。

  • 优点: 可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。 比如说,想要在每个代理的方法前都加上一个处理方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     public void giveMoney() 
    {
    //调用被代理方法前加入处理方法
    beforeMethod();
    stu.giveMoney();
    }
    // 代理类其它方法
    public void giveHomework()
    {
    //调用被代理方法前加入处理方法
    beforeMethod();
    stu.giveHomework();
    }

    除了giveMonney还有很多其他的方法(giveHomework),那就需要写很多次beforeMethod方法,麻烦。

  • 简单实现

    1
    2
    3
    4
    5
     //创建一个与代理对象相关联的InvocationHandler
    InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);

    //创建一个代理对象stuProxy,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
    Person stuProxy= (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);
  • 公共Person接口

    1
    2
    3
    4
    /public interface Person {
    //上交班费
    void giveMoney();
    }
  • Student实现Person接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class Student implements Person {
    private String name;
    public Student(String name) {
    this.name = name;
    }

    @Override
    public void giveMoney() {
    try {
    //假设数钱花了一秒时间
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println(name + "上交班费50元");
    }}

    增加一个计算方法执行时间的检测方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class MonitorUtil {

    private static ThreadLocal<Long> tl = new ThreadLocal<>();

    public static void start() {
    tl.set(System.currentTimeMillis());
    }

    //结束时打印耗时
    public static void finish(String methodName) {
    long finishTime = System.currentTimeMillis();
    System.out.println(methodName + "方法耗时" + (finishTime - tl.get()) + "ms");
    }}
  • StuInvocationHandler 实现 InvocationHandler接口

    并没有像之前一样:用一个代理类 StudentsProxy实现公共Person接口,而是代理类StuInvocationHandler 实现InvocationHandler接口。

    但二者都是持有被代理的对象Student引用

    InvocationHandler中有一个invoke方法,所有执行代理对象的方法都会被替换成执行invoke方法。

    • 通过反射,可以执行被代理对象Student的相应方法giveMoney()。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class StuInvocationHandler<T> implements InvocationHandler {
    //invocationHandler持有的被代理对象
    T target;

    public StuInvocationHandler(T target) {
    this.target = target;
    }

    /**
    * proxy:代表动态代理对象
    * method:代表正在执行的方法
    * args:代表调用目标方法时传入的实参
    */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("代理执行" +method.getName() + "方法");
    */
    //代理过程中插入监测方法,计算该方法耗时
    MonitorUtil.start();
    // 原来的代理对象Student中的方法
    Object result = method.invoke(target, args);
    MonitorUtil.finish(method.getName());
    return result;
    }}
  • 具体实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class ProxyTest {
    public static void main(String[] args) {

    //创建一个实例对象,这个对象是被代理的对象
    Person zhangsan = new Student("张三");

    //创建一个与代理对象相关联的InvocationHandler
    InvocationHandler stuHandler = new StuInvocationHandler<Person>(zhangsan);

    //创建一个代理对象stuProxy来代理zhangsan,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
    Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);

    //代理执行上交班费的方法
    stuProxy.giveMoney();
    }}

    img

2.3 动态代理的原理

从 JVM 角度来说,动态代理是在运行时,通过反射动态生成类字节码并加载到 JVM 中的。

3. 如何利用反射创建一个对象?

过程如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Test {
public static void main(String[] args) {

try {
//1. 得到Stu类的运行时Class描述符
//简单理解为你现在有了这个类,可以调用相应的方法进行实例化了
Class<?> stu = Class.forName("Stu");
//2.从getDeclaredConstructor()的字面意思就能理解,得到所有声明的构造器
//这里得到的是所有声明的构造器,getConstructor()则只能得到被public修饰的构造器
Constructor<?> declaredConstructor = stu.getDeclaredConstructor(new Class[]{String.class, int.class});
//3. 使用newInstance()方法创建对象并传入参数
//简单的理解为Stu o = new Stu("zhangsan", 01)
Stu o = (Stu) declaredConstructor.newInstance(new Object[]{"zhangsan", 01});
//4.调用Stu这个类里面的方法
// getDeclaredMethod()方法能调用到所有声明的方法
Method method = stu.getDeclaredMethod("toString", new Class[]{});
//5. 简单的理解为o.toStirng()
String invoke = (String)method.invoke(o, new Object[]{});
System.out.println(invoke);

} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}