1. Java 中的几种基本数据类型是什么?对应的包装类型是什么?各自占用多少字节呢?
| 字节 | 包装类(包装类缓存) | |
|---|---|---|
| byte | 1 | Byte[-128, 127] |
| short | 2 | Short[-128, 127] |
| int | 4 | Integer[-128, 127] |
| long | 8 | Long[-128, 127] |
| char | 2 | Character[0, 127] |
| float | 4 | Float 无缓存 |
| double | 8 | Double 无缓存 |
| boolean | 1bit(不是1字节) | Boolean (True/False均缓存) |
为什么要有包装类?
- java是面向对象的,很多常见需要对象
- 满足泛型的要求,泛型类型参数只能是对象类型
- 包装类允许null值
2. String 、 StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?
StringBuffer和StringBuilder都继承AbstractStringBuilder,( 可变性) 其中字符数组没有加final关键字,然后从线程安全性考虑,然后从性能比较,最后说适用的场景。
可变性:String 是不可变的。StringBuilder 与 StringBuffer可变,StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,这个 AbstractStringBuilder 类还提供了很多修改字符串的方法比如 append 方法。
线程安全性: String不可变的,线程安全。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder ****并没有对方法进行加同步锁,所以是非线程安全的。
性能: 相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
对于三者使用的总结:
- 操作少量的数据: 适用 String
- 单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
- 多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer
String的为什么不可变?
- 首先final修饰的变量,如果是引用类型(char[]),则不能指向其他对象。
- 其次char[] 被private修饰 且 String类没有提供修改这个字符数组本身的方法(理论上可以修改数组内容,只是不能将value指向其他数组),那么外部无法修改字符数组的内容。
- String类被final关键字修饰不能继承,避免子类破坏 不可变性。
Java9以后String的底层实现 由char[] 变为 byte[]
如何破坏String的不可变?
通过反射,破坏String类的不可变。
反射可以获取到String类对象中的私有成员。
final关键字的特性?
- 修饰的类不能被继承。
- 修饰的普通变量值不能被修改。
- 修饰的引用变量 引用不能指向其他对象。
- 修饰的方法不能被重写。
字符串拼接用“+” 还是 StringBuilder?
字符串对象通过“+”的字符串拼接方式,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象
不过,在循环内使用“+”进行字符串的拼接的话,存在比较明显的缺陷:编译器不会创建单个 StringBuilder 以复用,会导致创建过多的 StringBuilder 对象。
3. String s1 = new String("abc");这段代码创建了几个字符串对象?
看情况,可能创建一个也可能创建两个。
- 如果字符串常量池StringTable中存在"abc"的引用,则会在堆中创建一个对象。
- 如果字符串常量池StringTable中不存在"abc" 对象的引用,则会在堆中创建两个,其中一个对象的引用保存到常量池中,另一个对象引用赋给s1。
4. == 与 equals?hashCode 与 equals ?
- ==
-
- 基本数据类型比较 值
- 引用数据类型 比较 对象内存地址
- equals(是Object类的方法,默认为==)
-
- 如果类没有重写,与==相同,比较内存地址。
- 如果类重写了,按重写equals的规则,一般是对象中的某些属性相等则相等。
- hashCode(是Object类的native方法,计算对象的hashCode值)
-
- 用于HashSet和HashMap计算对象hash值
- 两对象hashCode相等,对象不一定相等(可能碰撞) ,hashCode不相同则对象一定不相同。
包装类
5. 包装类型的缓存机制了解么?
- Byte,Short,Integer,Long 这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character 创建了数值在 [0,127] 范围的缓存数据,Boolean 直接返回 True or False
- 超过缓存范围将 new新的对象
- 所有整型包装类对象之间值的比较,全部使用 equals 方法比较,用==比较是一个大坑(超出缓存范围将在堆上比较对象地址)。
源码
6. 自动装箱与拆箱了解吗?原理是什么?
- 装箱:将基本类型 转为 包装类型,原理,例:Integer i = Integer .valueOf(10)
- 拆箱:将包装类 转换为 基本类型,原理,例:int n = i.intValue()
7. 深拷贝和浅拷贝区别了解吗?什么是引用拷贝?
文章详解:【Java】深拷贝浅拷贝(Java实现)_java反射实现浅拷贝-CSDN博客
浅拷贝: 继承cloneable接口,重写clone方法并调用super.clone()。对象中值类型会拷贝一份,对象中的引用类型变量仅复制地址,而不会创建新对象。
深拷贝: 继承cloneable接口,重写clone方法并调用super.clone(),还需要调用成员引用对象的clone()方法(前提是引用类型以及重写了)。对象中值类型会拷贝一份,对象中的引用类型对象也会拷贝一份创建新对象。
cloneable接口也是一个标记接口,clone方法是Object类的方法
@Override
public Person clone() {
try {
Person person = (Person) super.clone();
person.setAddress(person.getAddress().clone());
return person;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
引用拷贝:简单的理解为对象赋值。
序列化 反序列化实现深拷贝
虽然利用调用成员引用类型的clone方法可以完成深拷贝,但在对象引用多层套娃时会比较繁琐(每个嵌套的引用都要调用clone),可以使用序列化反序列化解决多层套娃时深拷贝问题。
@Override
protected Student clone() {
try {
// 将对象本身序列化到字节流
// 利用 字节数组输出流-->对象输出流 写到内存
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream =
new ObjectOutputStream( byteArrayOutputStream );
objectOutputStream.writeObject( this );
// 再将字节流通过反序列化方式得到对象副本
// 利用 字节数组输入流-->对象输出流 从字节数组中 读取对象
ObjectInputStream objectInputStream =
new ObjectInputStream( new ByteArrayInputStream( byteArrayOutputStream.toByteArray() ) );
return (Student) objectInputStream.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
深拷贝浅拷贝的应用场景?
在某一个场景(不可变类的get方法)中可以用到。
比如我们设计了一个不可变类,对于不可变类中的 引用成员。我们不提供set方法让外部修改成员,但提供了get方法让外部获取内部引用成员。
这个get方法提供给外部的引用成员,外部拿到后其实是可以修改引用成员的内容的,如何使得让get方法让外部获取成员,但又不让外部改变我们的引用成员呢?
这时可以利用深拷贝,get方法返回给外部的是一个引用成员的克隆,这样外部获取到内部成员的克隆后就无法破坏不可变类。
序列化、反序列化
从三个方面来说。序列化方式方法,接口作用,序列化ID的作用
序列化方式方法
以字节的方式,序列化为文件(FileInput/OutputStream) 或字节数组(ByteArrayInput/OutStream)
ObjectOutputStream objectOutputStream =
new ObjectOutputStream( new FileOutputStream( new File("student.txt") ) );
objectOutputStream.writeObject( student );
ObjectInputStream objectInputStream =
new ObjectInputStream( new FileInputStream( new File("student.txt") ) );
Student student = (Student) objectInputStream.readObject();
objectInputStream.close();
Serializable接口的作用
标记接口
如果一个对象既不是字符串、数组、枚举,而且也没有实现Serializable接口的话,在序列化时就会抛出NotSerializableException异常!
serialVersionUID序列化id
serialVersionUID序列化ID,可以看成是序列化和反序列化过程中的“暗号”,在反序列化时,JVM会把字节流中的序列号ID和被序列化类中的序列号ID做比对,只有两者一致,才能重新反序列化,否则就会报异常来终止反序列化的过程- 如果在定义一个可序列化的类时,用户(程序员)没有显式地给它定义一个
serialVersionUID的话,则Java运行时环境会根据该类的各方面信息自动地为它生成一个默认的serialVersionUID,一旦像上面一样更改了类的结构或者信息,则类的serialVersionUID也会跟着变化 - 如果用户/程序员声明了
serialVersionUID,即使在序列化完成之后修改了类导致类重新编译,则原来的数据也能正常反序列化,只是新增的字段值是默认值而已
8. 谈谈对 Java 注解的理解,解决了什么问题?
注解运行时 配合反射使用,为spring框架提供了便利。
异常
Exception 和 Error 有什么区别?
- 首先都是Throwable的子类, Throwable是一个类。
- Exception 异常 :程序本身可以处理的异常,可以通过 catch 来进行捕获。异常又分为受检查异常 和 非受检查异常。
- Error:程序无法处理的错误,不建议通过catch捕获。如OOMError(堆内存溢出),Virtual MachineError(虚拟机错误),StackOverFlowError(栈溢出)。
Checked Exception 和 Unchecked Exception 有什么区别?
- Checked Exception受检查异常:
-
- 也叫编译时异常,在代码中必须 catch 或者 throws,不然过不了编译。
- 除了RuntimeException及其子类以外其他都是受检查异常。
- 如ClassNotFoundException(类找不到),IOException(IO相关) ,SQLException(sql异常)
- Unchecked Exception非受检查异常:
-
- 也叫运行时异常,编译时不处理这种异常也能过编译。
- 所有 RuntimeException 及其子类
- 如NullPointerException空指针异常、ArrayIndexOutOfBoundsException数组越界异常、ArithmeticException算术异常(除0)
反射
10. Java 反射?反射有什么缺点?你是怎么理解反射的(为什么框架需要反射)?
反射:
- 反射之所以被称为框架的灵魂,!!!!!!!反射机制是在运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意个对象,都能够调用它的任意一个方法。它赋予了我们在运行时分析类以及执行类中方法的能力
- 主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力。通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。
- 应用场景:
-
- 正是因为反射,你才能这么轻松地使用各种框架。SpringIOC就是通过反射创建bean的,像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。
- 动态代理AOP的实现也依赖于反射
- 注解 的使用也依赖于反射(例如框架里Component和value等注解)。
反射优缺点:
- 优点:代码更加灵活,运行动态获取类和使用类。为各种框架提供了开箱即用的功能。
- 缺点:
-
- 运行时操作分析类,增加了安全问题(因为可以通过反射获得 私有方法和属性)
- 额外的性能开销,对于框架来说影像不大。
11. Java 泛型了解么?什么是类型擦除?介绍一下常用的通配符?
美团一面:为什么要使用泛型?使用泛型带给你的好处有哪些 ?差点翻车。。。_哔哩哔哩_bilibili
泛型有泛型类/泛型接口 和 泛型方法。
- 泛型:
- 类型擦除:
- 通配符:
为什么要用泛型?
看Java核心技术后 的 总结
- 泛型其实是一种 类的参数。使用泛型意味着 代码可以被不同的类型的对象重用,提高代码复用性。
- 如果不使用泛型,例如在使用 集合类 时需要为每个类型都创建一个集合类。(使用Object不行吗?Object强转 可读性低 且 不安全可能出现转换错误)
- 如果使用泛型,例如在集合类 中使用泛型,可以用一个集合类聚集任何类的对象。
内部类
12. 内部类了解吗?匿名内部类了解吗?
内部类主要有 成员内部类、局部内部类、匿名内部类、静态内部类。
成员内部类:成员内部类定义在外部类的成员位置,可以访问外部类的所有成员变量和方法。
局部内部类:局部内部类定义在方法或作用域内部,只能在所在的方法或作用域中访问。
匿名内部类:匿名内部类没有名字,匿名内部类常用于实现接口或继承普通类 , 直接在创建对象的地方进行定义和实例化。
静态内部类: 静态内部类是在一个类内部定义的静态类,它不依赖于外部类的实例,可以直接使用外部类的静态成员,而不需要创建外部类对象。
匿名内部类
匿名内部类常用于实现接口或继承普通类。
实现接口的 匿名内部类
Contents是接口
//匿名内部类实现接口方式2
Contents obj2 = new Contents(){
private int value = 2;
public int getValue(){return value;}
};
继承 普通类的 匿名内部类
BaseClass 是一个普通的类
// 使用匿名内部类扩展普通类
BaseClass baseObject = new BaseClass() {
@Override
public void doSomething() {
System.out.println("Anonymous inner class is doing something.");
}
};
静态内部类 和 非静态内部类 区别
记前两点就行,访问方式和创建方式
创建方式:
静态内部类可以不依赖于外部类实例被创建。它可以直接通过外部类.内部类的方式被创建。非静态内部类需要依赖于外部类实例被创建,它必须先有一个外部类实例才能创建内部类实例。非静态内部类依赖于外部类的实例,而静态内部类不依赖于外部类的实例。非静态内部类在外部类实例化后才能实例化,而静态内部类可以独立实例化。
访问方式:
静态内部类可以访问外部类的静态成员,如果需要访问外部类的非静态成员,则需要外部类的实例。非静态内部类可以访问外部类的私有变量和方法,无论是静态还是非静态。
非静态内部类可以访问外部类的私有实例变量和方法,而静态内部类只能访问外部类的静态成员。
非静态内部类不能定义静态成员,而静态内部类可以定义静态成员。
public class test {
public static void main(String[] args) {
OuterClass out = new OuterClass("hhb", 20);
//创建内部类对象方式1
//使用 .new 语法,通过外部类对象直接创建内部类对象
OuterClass.innerClass innerObj2 = out.new innerClass();
innerObj2.innerMethod();
}
}
拓展:静态内部类只有在使用的时候才会加载和初始化。
public class OuterClass {
public static class InnerClass{
public static void print(){
System.out.println("调用静态内部类方法");
}
// 类加载执行
// 八股:类加载的三大步骤:加载、链接、初始化
// static代码块在初始化步骤执行
static {
System.out.println("静态内部类加载了");
}
}
public static void main(String[] args) throws Exception{
OuterClass outerClass = new OuterClass();
System.out.println("外部类加载了");
Thread.sleep(1000);
System.out.println("休眠结束");
// 这个时候才加载静态内部类,使用的时候才加载
OuterClass.InnerClass.print();
// 静态内部类实例的创建方式
OuterClass.InnerClass innerClass = new OuterClass.InnerClass();
}
}