全网最全Java高级面试题汇总
为什么要有包装类Java语言是面向对象的语言其设计理念是一切皆对象但是八种基本数据类型却出现了例外他们不具备对象的性质正是为了解决这个问题Java为每个基本数据类型都定义了一个对应的引用类型就是包装类。基本类型与包装类型的区别**默认值**包装类型不赋值就是null基本类型有默认值且不是null能否用作泛型包装类型可用于泛型而基本类型不可以**存储**基本数据类型的局部变量存放在Java虚拟机栈的局部变量表中而成员变量未被static修饰存放在Java虚拟机的堆中包装类型属于对象类型几乎所有的对象实例都存在于堆中**空间占用**相比包装类型基本类型占用的空间非常少注泛型要求包容的类型是对象类型而基本数据类型在Java中不属于对象类型其封装类属于对象类型。包装类型的缓存机制Java 基本数据类型的包装类型的大部分都用到了缓存机制来提升性能。Byte,Short,Integer,Long这 4 种包装类默认创建了数值[-128127]的相应类型的缓存数据Character创建了数值在[0,127]范围的缓存数据Boolean直接返回TrueorFalse。如果超出对应范围仍然会去创建新的对象缓存的范围区间的大小只是在性能和资源之间的权衡。两种浮点数类型的包装类Float,Double并没有实现缓存机制。另外阿里巴巴规范中提到了所有整型包装类对象之间值的比较全部使用equals方法比较。解释对于Integer在-128至127之间的赋值Integer对象是在cache中产生会复用已有对象这个区间内的Integer值可以使用进行判断但是这个区间之外的所有数据都会在堆上产生并不会复用已有对象推荐使用equals方法进行判断。什么是自动装箱和拆箱装箱将基本类型用它们对应的引用类型包装起来拆箱将包装类型转换为基本数据类型装箱其实是调用了包装类的valueOf()方法拆箱其实就是调用了xxxValue()方法。注意如果频繁拆装箱的话也会严重影响系统的性能我们应该避免不必要的拆装箱操作。类与对象一个Java文件中可以有多个类不含内部类吗一个Java文件中可以有多个类但是最多只能有一个被public修饰。并且被public修饰的类名必须与文件名相一致。如果该Java文件中没有public的类则文件名可以任意需要注意的是当用javac指令编译有多个类的Java文件时它会给每一个类生成一个对应的.class文件。为什么只能有一个public类每个编译单元即文件只能有一个public类这表示每个编译单元都有唯一的公共接口用public类来表现只能有一个public类是为了给类加载器提供方便。该接口可以按要求包含众多的支持包访问权限的类。如果在某个编译单元内有一个以上的public类编译器就会给出报错信息。编译单元里可以没有public类指的是没有公开的接口但是可以在同一个包里访问的。public的作用是包内包外均可访问。方法的重载和重写的区别重载发生在同一个类中方法名必须相同参数类型、参数个数、参数顺序不同方法返回值和访问修饰符可以不同。 简单来说重载就是同一个类中多个同名方法根据不同的传参来执行不同的逻辑处理。重写发生在运行期是子类对父类的允许访问的方法的实现过程进行重新编写。方法名、参数列表必须相同子类方法返回值类型应比父类方法返回值类型更小或相等抛出的异常范围小于等于父类访问修饰符范围大于等于父类。 如果父类方法访问修饰符为private/final/static则子类就不能重写该方法但是被static修饰的方法能够被再次声明。 构造方法不能被重写。方法的重写和重载是Java多态性的不同表现重写是父类与子类之间多态性的一种变现重载可以理解为多态的具体表现。 1重载是一个类中定义了多个方法名相同但是参数的数量、类型、次序不同。 2重写是在子类中存在方法的方法名与父类相同而且参数个数、类型也一样。 3重载是一个类的多态性的表现而重写是子类与父类的一种多态性的表现。运行项目并下载源码成员变量和局部变量的区别由类和方法的定义可知在类和方法中都可以定义属于自己的变量。类中定义的变量是成员变量而方法中定义的变量则是局部变量。类的成员变量和方法的局部变量是有一定区别的。1从语法形式上看成员变量是属于类的而局部变量是在方法中定义的变量或者是方法的参数成员变量可以被public、priavte、static等修饰符所修饰而局部变量则不能被访问控制修饰符及static修饰成员变量和局部变量都可以被final修饰。2从变量在内存中的存储方式上看成员变量是对象的i一部分而对象是存在堆内存的而局部变量是存在于栈内存的。3从变量在内存中生存的时间来看成员变量是对象的一部分随着对象的创建而存在而局部变量随着方法的调用而产生随着方法调用的结束而自动消失。4成员变量如果没有被赋初值则会自动以类型默认值赋值有一种情况例外被final修饰但没有被staitc修饰的成员变量必须显式的赋值而局部变量则不会自动赋值必须被显式的赋值以后才能使用。equals和 的区别1equals是方法是操作符2对于基本类型的变量来说只能使用 因为这些基本类型的变量没有equals方法一般来说是比较它们的值。3对于引用类型的变量来说才有equals方法。对于该类型对象的比较默认情况下就是没有重写Object类的equals方法使用equals和 的比较是一样的都是比较它们在内存中的地址但是如果重写了equals方法就会以实际情况来区分比如String使用equals方法会比较它们的值。4 对于基本类型是比较值是否相同对于引用类型是比较内存中的地址是否相同equals默认情况下比较内存地址是否相同可以按照逻辑需求重写对象的equals方法。为什么重写equals方法还要重写hashCode方法equals方法和hashCode方法的关系如果equals方法相等则它们的hashCode的值一定相同两个对象必然相等如果equals方法不等则它们的hashCode的值不一定不同两个对象必然不相等如果hashCode的值不等两个对象一定不相等如果hashCode的值相等两个对象不一定相等要使用equals是否相等来判断两个对象是否相等如果我们只重写equals方法是可以的但是在HashMap或者HashSet这种散列表中使用equals的效率太低了而直接比较hashCode的值效率比较高但是hashCode的值相等并不一定就说键值对或者对象相等所以重写eqauls必须要重写hashCode方法一方面是为了效率另一方面是为了确保结果的正确性。Java的接口和抽象类区别1接口接口是Java语言中的一个抽象类型用于定义对象的公共行为。使用关键字interface实现在接口的实现中可以定义方法和常量其普通方法是不能有具体代码实现的。在JDK8之后接口中可以创建static和default方法了并且这两种方法可以有默认的方法实现。JDK8接口中可以定义static和default方法并且这两种方法都可以包含具体的代码实现实现接口要使用implements关键字接口不能直接实例化接口中定义的变量默认是 public static final 类型子类不可以重写接口中的static和default方法不重写的情况下默认调用的是接口的实现方法2抽象类抽象类使用关键字abstract实现。一个方法无法给出具体明确的该方法就可以声明为抽象类。抽象类使用abstract关键字声明抽象类中可以包含普通方法和抽象方法抽象方法不能有具体的代码实现抽象类需要使用extends关键字来继承抽象类不能直接实例化抽象类中属性控制符无限制可以定义private类型的属性3区别不同点 0**定义的关键字不同。**接口使用interface定义而抽象类使用abstract定义。并且子类对于接口使用的关键字是实现implement对于抽象类使用的是继承extends。 1**概念不同。**接口主要用于对类的行为进行约束实现了某个接口就具有了对应的行为。抽象类主要用于代码复用强调的是所属的关系。 2**类型扩展不同。**抽象类只能单继承而接口可以多实现。 3**方法访问控制符不同。**接口中的成员变量只能是public、static、final类型的不能被修改且必须要有初始值而抽象类的成员变量默认default可在子类中被重新定义也可被重新赋值。 4**内部方法的实现不同。**接口中普通方法没有实现但JDK8中static和default方法必须有实现而抽象类中普通方法必须有实现而抽象方法不能有实现。共同点 1都不能被实例化。2都可以包含抽象方法。3都可以有默认实现的方法Java8中可以使用default关键字在接口中定义默认方法。篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了Java面试、场景题、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc需要全套面试笔记及答案【点击此处即可/免费获取】https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1hoString 不可变String为什么不可变什么是不可变对象不可变对象遵守以下几条规则类内部所有的字段都是final修饰的类内部的所有字段都是私有的即被private所修饰类不能被集成和拓展类不能对外提供能够修改内部状态的方法类内部的字段如果是引用也就是说指向可变对象那么这个引用不可被获取String为什么不可变1String保存数据的char数组被final修饰且是私有的并且String类没有提供/暴露修改这个字符串的方法String的方法对字符串每次操作时都会返回一个新的字符串对象原有的字符串不会改变。2String类被final修饰导致其不能被继承进而避免了子类破坏String的不可变。String不可变的好处1字符串常量池的要求字符串常量池是方法区中的一个特殊的存储区。当一个字符串被创建并且该字符串已经存在于池中时将返回现有字符串的引用而不是创建一个新的对象。如果一个字符串是可变的用一个引用改变字符串的值会导致其他引用的错误。2缓存哈希值字符串的哈希码在Java中经常被使用例如在HashMap和HashSet中经常被使用不可变保证哈希码始终相同因此无需担心即可实现。每次使用哈希码时都无需计算。3安全性考虑String被广泛用作Java类的参数例如网络连接、打开文件等。如果String不是不可变的则连接或者文件将被更改这可能会导致严重的安全威胁。4线程安全问题因为不可变对象不能改变所以可以在多个线程之间自由共享这消除了对于同步的要求。String、StringBuilder、StringBuffer的区别String是不可变字符串而StringBuilder和StringBuffer是可变字符串。String字符串对象是不可变对象每次操作都会返回新的字符串原有字符串不会被改变StringBuilder和StringBuffer是可变字符串他们的字符串对象可以被更改对字符串的操作不会生成新的对象即对同一个字符串进行修改。StringBuilder 是线程不安全的适用于单线程环境而StringBuffer是线程安全的适用于多线程环境。它们两个都适用于大量字符串拼接的场景因为它们对于字符串的拼接操作来说不仅效率高而且不占用额外的空间。什么方法能改变String// 可以通过反射来改变String的值 String name xiaoao; Field field String.class.getDeclaredField(value); field.setAccessible(true); char[] values (char[]) field.get(name); System.out.println(name name.hashCode()); values[0] z; System.out.println(name name.hashCode()); // 输出 // xiaoao -759499955 // ziaoao -759499955运行项目并下载源码java运行虽然String的字符数组声明为final并且被private修饰但是这个final仅仅只是让value的引用不改变而不能让字符数组的字符不能改变。可以看到上面的代码即时字符串的值发生了改变但是它的hashCode仍然是一样的。private int hash; // Default to 0 public int hashCode() { int h hash; if (h 0 value.length 0) { char val[] value; for (int i 0; i value.length; i) { h 31 * h val[i]; } hash h; } return h; }运行项目并下载源码java运行为什么hashCode不会改变呢因为在第一次调用过hashCode之后字符串对象内通过hash这个属性缓存了hashCode的计算值只要缓存过之后就不会再计算了所以hashCode的值不会发生改变。StringBuilder和StringBuffer的线程安全问题场景在方法的内部一个String类型的List将这些元素用逗号分割拼接成一个完整的字符串然后作为方法的返回值返回这个方法可能在多线程的环境下然后用StringBuilder或者StringBuffer会产生线程安全问题吗1使用StringBuilder在多线程情况下拼接字符串会产生线程安全问题而StringBuffer则不会。2为什么StringBuilder会产生线程安全问题因为StringBuilder底层的append方法调用的其实是父类的append方法。而AbstractStringBuilder的append方法中有一个字符串长度的相加的操作count len这个操作不是一个原子操作首先从内存中加载count到寄存器在寄存器执行相加操作将结果写入内存在多线程环境下各个线程的操作的结果可能被其他线程给覆盖掉所以得到的结果不会是正确结果。而且还有可能会抛出下标越界异常这是因为StringBuilder会进行扩容操作如果在进行扩容操作的时候数组的长度被其他线程修改了那么可能扩容后的数组放不下新的字符串就会出现数组下标越界异常。String有长度限制吗String类型的对象是有长度限制的String对象并不能“存储”无限制长度的字符串。关于String的长度限制要从编译时限制和运行时限制两方面考虑。1编译时限制。对于String s abc这样的字符串声明字符串常量abc肯定会被放入方法区的常量池中String长度之所以会受到限制是因为JVM的规范对常量池有所限制。常量池中的每一种数据项都有自己的类型。Java中UTF-8编码的Unicode字符串在常量池中以CONSTANT_Utf8类型表示。CONSTANT_Utf8_info { u1 tag; u2 length; u1 bytes[length]; }运行项目并下载源码java运行bytes数组就是真正存储常量数据的地方。而length就是数组可以存储的最大字节数。length的类型是u2u2是无符号的16位整数因此理论上允许的最大长度是216-1 65535。所以上面byte数组的最大长度是65535。//65535个d编译报错 java: 常量字符串过长 String s dd..dd; //65534个d编译通过 String s1 dd..d;运行项目并下载源码java运行起始Javac编译器的额外限制在Javac源代码中可以看到private void checkStringConstant(DiagnosticPosition var1, Object var2) { if (this.nerrs 0 var2 ! null var2 instanceof String ((String)var2).length() 65535) { this.log.error(var1, limit.string, new Object[0]); this.nerrs; } }运行项目并下载源码java运行代码中可以看出当参数类型为String并且长度大于等于65535的时候就会导致编译失败。注意String的限制并不是对字符串长度的限制而是对字符串底层存储的限制。Java中的字符串都是使用UTF8编码的UTF8编码使用1~4个字节来表示具体的Unicode字符。所以有的字符占1个字节而平常使用的中文需要3个字节来存储。//65534个a编译通过 String s1 aa..a; //21845个中文”自“,编译通过 String s2 好...好; // 一个英文字母a加上21845个中文”自“编译失败 java: 对于常量池来说, 字符串 a好好好好好好好好好好好好好好好好好好好... 的 UTF8 表示过长 String s3 a好...好;运行项目并下载源码java运行对于s1一个字母a的UTF8编码占用一个字节65534字母占用65534个字节长度是65534也没超过Javac的限制所以可以编译通过。对于s2一个中文占用3个字节21845个正好占用65535个字节而且字符串长度是21845并没有超过javac对长度的限制所以可以编译通过。对于s3一个英文字母a加上21845个中文”自“占用65535个字节超过了最常限制编译失败。2运行时限制String运行时的限制主要体现在String的构造函数上。// 分配一个新的Stringoffset参数是子数组的第一个字符的索引count参数指定子数组的长度 public String(char value[], int offset, int count) { ... }运行项目并下载源码java运行上面的count值也就是字符串的最大长度。在Java中int的最大长度是231-1。所以在运行时String的最大长度就是231-1。但是这个也是理论上的长度实际长度还是要看JVM的内存。一个最大的字符串会占用(2^31-1)*2*16/8/1024/1024/1024 4GB所以在最坏的情况下一个最大的字符串需要4GB的内存如果虚拟机不能分配这么多内存则直接报错。(2^31-1)*2*16/8/1024/1024/1024 4GB 在 Unicode 编码下每个字符通常占用 2 个字节16 位即使用 2 个字节表示一个字符。char占2个字节 由于 1 GB 1024 MB 1024 KB 1024 Bytes所以可将该字节数转换为单位 GB运行项目并下载源码3总结String的长度是有限制的编译期限制字符串的UTF8编码值的字节数不能超过65535字符串的长度不能超过65534。运行时限制字符串的长度不能超过231 - 1占用的内存数不能超过虚拟机能提供的最大值。泛型什么是泛型Java泛型是JDK5中引入的一个新特性。使用泛型参数可以增强代码的可读性以及稳定性。泛型的本质是为了参数化类型在不创建新的类型的情况下通过泛型指定的不同类型来控制形参具体限制的类型。**编译器可以对泛型参数进行检测并且通过泛型参数可以指定传入的对象类型。**比如ListPerson persons new ArrayList();这行代码就指明了ArrayList对象只能传入Person对象如果传入其他类型的对象就会报错。使用泛型的意义1适用于多种数据类型执行相同的代码即代码复用。2泛型中的类型在使用时指定不需要进行强制类型转换即类型安全编译器会检查类型。如何使用泛型泛型有三种使用方式分别为泛型类、泛型接口、泛型方法。? 无限制通配符 ? extends E extends 关键字声明了类型的上界表示参数化的类型可能是所指定的类型或者是此类型的子类 ? super E super 关键字声明了类型的下界表示参数化的类型可能是指定的类型或者是此类型的父类 // 使用原则《Effictive Java》 // 为了获得最大限度的灵活性要在表示 生产者或者消费者 的输入参数上使用通配符使用的规则就是生产者有上限、消费者有下限 1. 如果参数化类型表示一个 T 的生产者使用 ? extends T; 2. 如果它表示一个 T 的消费者就使用 ? super T 3. 如果既是生产又是消费那使用通配符就没什么意义了因为你需要的是精确的参数类型。运行项目并下载源码java运行篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了Java面试、场景题、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc需要全套面试笔记及答案【点击此处即可/免费获取】https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho如何理解Java中的泛型是伪泛型Java泛型这个特性是从JDK1.5才开始加入的因此为了兼容之前的版本Java泛型的实现采取了“伪泛型”的策略即Java在语法上支持泛型但是在编译阶段会进行所谓的“类型擦除”Type Erasure将所有的泛型表示尖括号中的内容都替换为具体的类型其对应的原生态类型就像完全没有泛型一样。泛型的类型擦除的原则是消除类型参数声明即删除及其包围的部分。根据类型参数的上下界推断并替换所有的类型参数为原生态类型如果类型参数是无限制通配符或没有上下界限定则替换为Object如果存在上下界限定则根据子类替换原则取类型参数的最左边限定类型即父类。为了保证类型安全必要时插入强制类型转换代码。自动产生“桥接方法”以保证擦除类型后的代码仍然具有泛型的“多态性”。List如果不用泛型会出现什么问题如果List不使用泛型那么默认的数据类型是Object。1需要强制类型转换。在想要获取对应的元素的类型时必须要进行强制转换。2可读性较差很可能会出现ClassCastException。在使用的时候如果不考虑兼容那么会出现类型转换异常。泛型的好处**1类型的安全。**泛型的主要目标是提高 Java 程序的类型安全。通过知道使用泛型定义的变量的类型限制编译器可以在很高的程度上验证类型假设。**2消除强制类型转换。**泛型的一个附带好处是消除源代码中的许多强制类型转换。这使得代码更加可读并且减少了出错机会。**3潜在的性能收益。**泛型为较大的优化带来可能。在泛型的初始实现中编译器将强制类型转换插入生成的字节码中。但是更多类型信息可用于编译器这一事实为未来版本的 JVM 的优化带来可能。由于泛型的实现方式支持泛型几乎不需要 JVM 或类文件更改。所有工作都在编译器中完成编译器生成类似于没有泛型和强制类型转换时所写的代码只是更能确保类型安全而已。反射什么是反射反射是在运行状态中 对于任意一个类都能知道这个类的所有属性和方法 对于任意一个对象都能够调用它的任意一个方法和属性这种动态获取信息以及动态调用对象的方法功能称为Java语言的反射机制。反射机制的应用场景1JDBC中利用反射动态加载了数据库驱动程序。2Web服务器中利用反射调用了Servlet的服务方法。3很多框架都用到反射机制注入属性调用方法比如spring使用Component注解就可以声明一个类为SpringBean这就是基于反射获取类然后再获取类上的注解进行一定的逻辑梳理。除此之外除了使用new创建对象之外还可以使用Java反射创建对象。反射机制的优缺点1优点能够在运行时动态获取类的实例提高灵活性可以和动态编译相结合2缺点对性能有影响需要解析字节码将内存中的对象进行解析这类操作总是慢于直接执行Java代码3解决方法通过setAccessible(true)关闭JDK的安全检查来提升反射速度多次创建一个类的实例时有缓存会快很多可以使用ReflectASM工具类通过字节码生成的方式加快反射速度注解什么是注解Annotation(注解)是JDK5开始引入的新特性可以看作是一种特殊的注释主要用于修饰类、方法或者变量提供某些信息供程序在编译或者运行时使用。注解本质上就是继承了Antation接口。注解解析的方法注解只有被解析后才会生效常见的解析方法有两种编译期直接扫描编译器在编译Java代码的时候扫描对应的注解并处理比如某个方法使用Override注解编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。运行期通过反射处理像框架中自带的注解(比如 Spring 框架的Value、Component)都是通过反射来进行处理的。序列化什么是序列化和反序列化如果我们需要持久化 Java 对象比如将 Java 对象保存在文件中或者在网络传输 Java 对象这些场景都需要用到序列化。序列化 将数据结构或对象转换成二进制字节流的过程反序列化将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程要实现Java对象的序列化只需要实现接口Serializable。下面是序列化和反序列化常见应用场景对象在进行网络传输比如远程方法调用 RPC 的时候之前需要先被序列化接收到序列化的对象之后需要再进行反序列化将对象存储到文件之前需要进行序列化将对象从文件中读取出来需要进行反序列化将对象存储到数据库如 Redis之前需要用到序列化将对象从缓存数据库中读取出来需要反序列化序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。常见的序列化方式序列类型是否跨语言优缺点hession支持跨语言序列化后体积小速度较快protostuff支持跨语言序列化后体积小速度快但是需要Schema可以动态生成jackson支持跨语言序列化后体积小速度较快且具有不确定性fastjson支持跨语言支持较困难序列化后体积小速度较快只支持java c#kryo支持跨语言支持较困难序列化后体积小速度较快fst不支持跨语言支持较困难序列化后体积小速度较快兼容jdkjdk不支持序列化后体积很大速度快不想序列化的字段**1transient。**对于不想进行序列化的变量使用transient关键字修饰。transient关键字的作用是阻止实例中那些用此关键字修饰的的变量序列化当对象被反序列化时被transient修饰的变量值不会被持久化和恢复。关于transient还有几点注意transient只能修饰变量不能修饰类和方法。transient修饰的变量在反序列化后变量值将会被置成类型的默认值。例如如果是修饰int类型那么反序列后结果就是0。2static。static变量因为不属于任何对象(Object)所以无论有没有transient关键字修饰均不会被序列化。**3默认方法。**也可以通过在目标类中定义默认的writeObject()和readObject()方法来实现指定的序列化字段没有被指定的字段不会被序列化。为什么不推荐使用JDK的序列化1不支持语言调用如果开发的时候是其他语言开发的服务就不支持JDK的序列化2性能差相比于其他序列化框架的性能更低主要原因是序列化之后的字节数组体积大导致传输成本加大。3存在安全问题序列化和反序列化本身并不存在问题但是当输入的反序列化的数据可以被用户控制那么攻击者可以通过恶意构造让反序列化产生非预期的对象在此过程种任意构造代码。