Java基础面试总结,加了点个人理解

Java基础面试总结

Java特点

平台无关性:一次编译处处运行,JVM将源代码编译成字节码,该字节码可在任何安装了JVM的的系统上运行

Java程序执行流程,编译阶段Javac将源代码编译成字节码文件,然后JVM类加载器将字节码存入JVM内存,最后JVM将JVM内存中的字节码通过解释器逐条翻译成机器码并且JIT编译器会将热点代码缓存至JVM的CodeCache区域中。

javac 编译
解释器 Interpreter
JIT 编译器
.java 源代码
.class 字节码文件
类加载器 ClassLoader
加载
链接
初始化
JVM 内存中的类结构
执行引擎
逐条翻译字节码为机器码
编译热点代码为机器码缓存
CPU 执行机器码
程序输出

面向对象:Java的面向对象编程通过封装将数据与操作绑定并隐藏实现细节,通过继承使子类复用父类代码并扩展功能,通过多态实现父类引用指向子类对象时的动态方法调用,三者共同作用使程序具备模块化、安全性、可维护性与可扩展性,成为构建大型软件系统的基础范式。

多态:

1. 继承与方法重写

子类继承父类,并重写父类的方法。父类引用调用该方法时,实际执行子类的实现

2. 接口与实现类

接口定义方法签名,实现类提供具体实现。接口类型的引用可指向任意实现类对象。

3. 抽象类与子类

抽象类定义抽象方法,子类必须实现这些方法。抽象类引用同样支持多态调用

4.方法重载

同名方法不同参数列表,参数的数量,顺序,类型不同,编译器会在编译时根据参数调用

5.向上转型与向下转型

子类对象赋值给父类引用,无需显示转换,反之需要强制转换

内存管理:Java有自己的垃圾回收机制,自动管理内存和回收不再使用的对象。这样,开发者不需要手动管理内存,从而减少内存泄漏和其他内存相关的问题。

JVM,JDK,JRE的关系
  • JVM是Java虚拟机,是Java程序运行的环境。它负责将Java字节码(由Java编译器生成)解释或编译成机器码,并执行程序。JVM提供了内存管理、垃圾回收、安全性等功能,使得Java程序具备跨平台性。
  • JDK是Java开发工具包,是开发Java程序所需的工具集合。它包含了JVM、编译器(javac)、调试器(jdb)等开发工具,以及一系列的类库(如Java标准库和开发工具库)。JDK提供了开发、编译、调试和运行Java程序所需的全部工具和环境。JDK=JRE+开发工具
  • JRE是Java运行时环境,是Java程序运行所需的最小环境。它包含了JVM和一组Java类库,用于支持Java程序的执行。JRE不包含开发工具,只提供Java程序运行所需的运行环境。JRE=JVM+核心类库
JVM是什么

JVM是 java 虚拟机,主要工作是解释自己的指令集(即字节码)并映射到本地的CPU指令集和OS的系统调用。JVM屏蔽了与操作系统平台相关的信息,使得Java程序只需要生成在Java虚拟机上运行的目标代码(字节码),就可在多种平台上不加修改的运行,这也是Java能够“一次编译,到处运行的”原因。

Java高位转低位

例如long转int,如果long值溢出,则截断自己的二进制并且位数为目标类型int的位数的低位,其他同理

为什么用bigDecimal 不用double ?

double会出现精度丢失的问题,double执行的是二进制浮点运算,二进制有些情况下不能准确的表示一个小数,就像十进制不能准确的表示1/3(1/3=0.3333…),也就是说二进制表示小数的时候只能够表示能够用1/(2n)的和的任意组合,但是0.1不能够精确表示,因为它不能够表示成为1/(2n)的和的形式。

装箱和拆箱

装箱和拆箱是将基本数据类型和对应的包装类进行转换的过程,分两种情况,赋值和方法调用时

Integer i = 10;  //装箱
int n = i;   //拆箱

JDK5之前的装箱与拆箱

Integer object=Integer.valueOf(3);
int value=object.intValue();

自动装箱的弊端

在一个循环中进行自动装箱操作的情况,如下面的例子就会创建多余的对象,影响程序的性能

Long sum = 0L;  // 初始化为包装类
for (long i = 0; i < Integer.MAX_VALUE; i++) {
    sum += i;  // 每次循环都触发拆箱(sum.longValue())和装箱(Long.valueOf())
}
//sun+=i实际执行
sum = Long.valueOf(sum.longValue() + i);  // 拆箱+装箱
Integer缓存

Java的Integer类内部实现了一个静态缓存池,用于存储特定范围内的整数值对应的Integer对象。 默认情况下,这个范围是-128至127。当通过Integer.valueOf(int)方法创建一个在这个范围内的整数对象时,并不会每次都生成新的对象实例,而是复用缓存中的现有对象,会直接从内存中取出,不需要新建一个对象。

重载重写的区别
  • 重载(Overloading)指的是在同一个类中,可以有多个同名方法,它们具有不同的参数列表(参数类型、参数个数或参数顺序不同),编译器根据调用时的参数类型来决定调用哪个方法。
  • 重写(Overriding)指的是子类可以重新定义父类中的方法,方法名、参数列表和返回类型必须与父类中的方法一致,通过@override注解来明确表示这是对父类方法的重写。
  • 重载是指在同一个类中定义多个同名方法,而重写是指子类重新定义父类中的方法.
抽象类和普通类的区别
  • 实例化:普通类可以直接实例化对象,而抽象类不能被实例化,只能被继承。
  • 方法实现:普通类中的方法必须有具体的实现,而抽象类中的抽象方法没有实现,具体方法有实现。
  • 继承:一个类可以继承一个普通类,而且可以继承多个接口;而一个类只能继承一个抽象类,但可以同时实现多个接口。
  • 实现限制:普通类可以被其他类继承和使用,而抽象类一般用于作为基类,被其他类继承和扩展使用。
静态变量与静态方法

静态变量

  • 共享性:所有该类的实例共享同一个静态变量。如果一个实例修改了静态变量的值,其他实例也会看到这个更改。
  • 初始化:静态变量在类被加载时初始化,只会对其进行一次分配内存。
  • 访问方式:静态变量可以直接通过类名访问,也可以通过实例访问,但推荐使用类名。

静态方法

  • 无实例依赖:静态方法可以在没有创建类实例的情况下调用。对于静态方法来说,不能直接访问非静态的成员变量或方法,因为静态方法没有上下文的实例。
  • 访问静态成员:静态方法可以直接调用其他静态变量和静态方法,但不能直接访问非静态成员。
  • 多态性:静态方法不支持重写(Override),但可以被隐藏(Hide),隐藏指的是父类静态方法被隐藏,即原先子类类型代用同名静态方法时会执行父类方法,而隐藏之后执行的是子类方法。
非静态内部类和静态内部类的区别
  • 非静态内部类依赖于外部类的实例,而静态内部类不依赖于外部类的实例。

  • 非静态内部类可以访问外部类的所有静态,非静态,私有,公有实例变量和方法,而静态内部类只能访问外部类的静态成员。

  • 非静态内部类不能定义静态成员,而静态内部类可以定义静态成员。

  • 非静态内部类在外部类实例化后才能实例化,而静态内部类可以独立实例化。

    非静态内部类实例化,OuterClass外部类,InnerClass非静态内部类

    OuterClass outer = new OuterClass();
    OuterClass.InnerClass inner = outer.new InnerClass();
    

    静态内部类实例化

    OuterClass.StaticInnerClass staticInner = new OuterClass.StaticInnerClass();
    
  • 非静态内部类可以访问外部类的私有成员,而静态内部类不能直接访问外部类的私有成员,需要通过实例化外部类来访问。

非静态内部类能访问外部方法的原因

因为编译器在非静态内部类内部自动生成了一个指向外部类实例的引用。这个引用使得非静态内部类能够访问外部类的实例变量和方法。编译器会在生成非静态内部类的构造方法时,将外部类实例作为参数传入,并在内部类的实例化过程中建立外部类实例与内部类实例之间的联系,从而实现直接访问外部方法的功能。

Final的作用
  • 修饰方法:此方法不能被重写
  • 修饰变量:修饰基本数据类型变量,此变量的值不能被修改;修饰引用变量,此变量的引用不能被修改,但其内容允许修改。
  • 修饰类:此类不能被继承。
深拷贝和浅拷贝
  • 浅拷贝

    只复制对象本身,不复制对象内部引用的其他对象。新对象和原对象共享内部引用对象的内存,Object.clone()默认实现浅拷贝。

    所以浅拷贝总结就是,新对象和原对象在堆内存中分配不同的地址(栈中的变量引用指向不同的堆对象),引用类型字段的地址共用,基本类型的字段值一样。

  • 深拷贝

    不仅复制对象本身,还递归复制对象内部引用的所有对象。新对象和原对象完全独立,不共享任何内存。

什么是泛型

泛型是va编程语言中的一个重要特性,它允许类、接口和方法在定义时使用一个或多个类型参数,这些类型参数在使用时可以被指定为具体的类型。

泛型的作用
  • 能让编译器在编译阶段检查类型匹配,避免运行时出现ClassCastExecption异常,若无泛型还需手动强转类型。
  • 提高代码适配多类型,例如Box,T是类型占位符,可在创建对象时指定具体类型;泛型方法,泛型接口能使用不同的入参类型。
泛型的类型擦除(发生在编译后)
  • 擦除规则:

    1. 无界泛型:类型参数被替换成Object,比如 List<T> 编译后会变成 List<Object>,所有 T 的地方都用 Object 代替。
    2. 有界泛型:类型参数会被替换成最左边界的类型。比如 List<T extends Number> 编译后会变成 List<Number>;如果是 <T extends Comparable & Serializable>,则替换成 Comparable(取最左的接口)。
  • 造成影响:

    1. 运行时丢失泛型信息

      List<String> strList = new ArrayList<>();
      List<Integer> intList = new ArrayList<>();
      // 输出 true!因为运行时都是 ArrayList(泛型被擦除)
      System.out.println(strList.getClass() == intList.getClass()); 
      
    2. 不能使用基本类型作为泛型参数

      不能写基本类型的原因是因为泛型擦除会把原泛型擦除变成Object,而基本类型不属于Object类,故不能使用基本数据类型,应该使用包装类。

    3. 泛型方法签名冲突

    4. 反射无法直接获取泛型类型

    5. 泛型数组创建受限

      不能直接创建 new List<String>[10],因为类型擦除会导致数组元素类型信息丢失,运行时可能引发 ArrayStoreException。解决办法:用集合(如 ArrayList)代替数组,或用通配符(List<?>)。

  • 机制补偿

    虽然运行时泛型信息没了,但编译器会在编译期自动插入类型转换代码,保证我们写代码时的类型安全。

Java创建对象有哪些方法
  • 使用new关键字:通过new关键字直接调用类的构造方法来创建对象。
  • 使用Class类的newlnstance()方法:通过反射机制,可以使用Class类的newlnstance()方法创建对象。
  • 使用Constructor类的newlnstance()方法:同样是通过反射机制,可以使用Constructor类的newlnstance()
    方法创建对象。
  • 使用clone()方法:如果类实现了Cloneable接口,可以使用clone()方法复制对象。
  • 使用反序列化:通过将对象序列化到文件或流中,然后再进行反序列化来创建对象。
什么是反射

反射是指程序在运行时动态获取类的信息,动态调用类的方法和属性,甚至是修改类的属性。

受检异常和未检查异常
  • 受检异常:编译阶段就被编译器 “拦截”,强制要求开发者处理,避免程序因外部问题直接崩溃
  • 未检查异常:编译器 “放行”,仅在运行时暴露问题,本质是对开发者 “代码质量” 的隐性要求(需通过逻辑避免异常)。
==与equals有什么区别
  • ==

    1. 基本类型比较:比较的是两者的数值,因为基本类型只能存放数值本身在栈中,故==在比较基本类型时比较数值,而引用类型存储的是对象在堆内存中的地址。
    2. 引用类型比较:比较的是两者的引用地址
  • equals

    默认行为是比较引用地址,重写后比较内容,equals只能适用于引用类型的比较

hashcode和equals方法有什么关系
  • 一致性:如果两个对象使用 equals 方法比较结果为 true,那么它们的 hashCode 值必须相同。也就是说,如果 obj1.equals(obj2) 返回 true,那么 obj1.hashCode() 必须等于 obj2.hashCode()。
  • 非一致性:如果两个对象的 hashCode 值相同,它们使用 equals 方法比较的结果不一定为 true。即 obj1.hashCode() == obj2.hashCode() 时,obj1.equals(obj2) 可能为 false,这种情况称为哈希冲突。
  • hashCode 和 equals 方法是紧密相关的,重写 equals 方法时必须重写 hashCode 方法,以保证在使用哈希表等数据结构时,对象的相等性判断和存储查找操作能够正常工作。而重写 hashCode 方法时,需要确保相等的对象具有相同的哈希码,但相同哈希码的对象不一定相等
String、StringBuffer、StringBuilder的区别和联系
  • 可变性 :String 是不可变的(Immutable),一旦创建,内容无法修改,每次修改都会生成一个新的对象。StringBuilder 和 StringBuffer 是可变的(Mutable),可以直接对字符串内容进行修改而不会创建新对象。
  • 线程安全性 :String 因为不可变,天然线程安全。StringBuilder 不是线程安全的,适用于单线程环境。StringBuffer 是线程安全的,其方法通过 synchronized 关键字实现同步,适用于多线程环境。
  • 性能 :String 性能最低,尤其是在频繁修改字符串时会生成大量临时对象,增加内存开销和垃圾回收压力。StringBuilder 性能最高,因为它没有线程安全的开销,适合单线程下的字符串操作。StringBuffer 性能略低于 StringBuilder,因为它的线程安全机制引入了同步开销。
  • 使用场景 :如果字符串内容固定或不常变化,优先使用 String。如果需要频繁修改字符串且在单线程环境下,使用 StringBuilder。如果需要频繁修改字符串且在多线程环境下,使用 StringBuffer。
  • String的特殊优化,编译期能确定字符串会存入常量池,避免重复创建。后续创建相同内容的字符串会从常量池中复用。题外话:编译期能确定的字符串指能够通过字面量确定的字符串,即没有变量,存放在常量池中;而运行期拼接的字符串则是有变量拼接的,存放在堆内存中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值