文章目录
- 一、Java 中有哪 8 种基本数据类型?说说这 8 种基本数据类型对应的包装类型?
- 二、包装类型的常量池技术了解么?
- 三、为什么要有包装类型?
- 四、什么是自动拆装箱?原理?
- 四、遇到过自动拆箱引发的 NPE 问题吗?
- 五、String 、 StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?
- 六、重载和重写的区别?
- 七、== 和 equals() 的区别
- 八、Java 反射?反射有什么优点/缺点?你是怎么理解反射的(为什么框架需要反射)?
- 九、谈谈对 Java 注解的理解,解决了什么问题?
- 十、Java 泛型了解么?泛型的作用?什么是类型擦除?泛型有哪些限制?介绍⼀下常用的通配符?
- 十一、内部类了解吗?匿名内部类了解吗?
一、Java 中有哪 8 种基本数据类型?说说这 8 种基本数据类型对应的包装类型?
好的,Java中有byte、short、int、long、float、double的数字类型,还有一种字符类型char,一种布尔类型boolean。对应的包装类分别是Byte、Short、Integer、Long、Float、Double、Characher、Boolean。
二、包装类型的常量池技术了解么?
提示:Java 基本类型的包装类的⼤部分( Byte , Short , Integer , Long , Character , Boolean )都实现了常量池技术
Java 基本类型的包装类的大部分都实现了常量池技术。包装类的常量池技术就是类似于缓存的作用,JVM单独创建一块空间,避免重复创建包装类常量。Byte,Short,Integer,Long 这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character 创建了数值在 [0,127] 范围的缓存数据,Boolean 直接返回 True or False。
三、为什么要有包装类型?
提示: 基本类型有默认值、泛型参数不能是基本类型
面向对象要求:Java是一种面向对象编程语言,面向对象是一种思想,它要求数据都应该是对象,而不是关键字,就像我们的八种基本类型数据就是关键字,他们没有成员方法和其他面向对象的特性。因此我们需要有对象来实现我们面向对象的思想,就引入了包装类,将基本类型封装成对象,使他们也具有面向对象的特性。
泛型的要求:在java引入了泛型之后,泛型要求的类型参数必须是对象类型,不能是基本数据类型,因此想在泛型中使用基本类型,就必须使用对应的包装类
null值表示:包装类默认的是null,而我们的基本数据类型默认表示的是0,因此在我们前后端进行交互的时候,我们后端的接口就应该使用包装类,而不是基本数据类型,因为前端有可能会传一个空数据,我们使用基本数据类型就会报500的错误,因此我们想让程序继续进行下去我们就应该使用包装类。
四、什么是自动拆装箱?原理?
提示:基本类型和包装类型之间的互转。装箱其实就是调⽤了包装类的 valueOf() ⽅法,拆箱其实就是调⽤了 xxxValue() ⽅法。
自动拆装箱:
- 装箱其实就是将基本用它们对应的引用类型包装起来;
- 拆箱其实就是将包装类型转换成基本数据类型;
原理也很简单,装箱其实就是调用了包装类的valueOf(参数)方法,拆箱调用了xxxValue()方法。
四、遇到过自动拆箱引发的 NPE 问题吗?
1.数据库的查询结果可能是 null,因为⾃动拆箱,⽤基本数据类型接收有 NPE ⻛险。2.三⽬运算符使⽤不当会导致诡异的 NPE 异常
自动拆箱时由于调用的是intValue方法,所以如果调用方本身是null的话,肯定会NPE。
解决办法就是在自动拆装箱的地方加上null值判断。
五、String 、 StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?
提示:可以从可变性、线程安全性、性能这⼏个⻆度来回答。
首先在可变性的情况下,String是不可变的,而StringBuffer和StringBuilder是可变的,其次在线程安全的情况下,String可以理解成常量,所以线程是安全的。StringBuffer对方法加了同步锁所以也是线程安全的,而StringBuilder是非线程安全的。最后在性能方面,对String类型进行改变时,都会生成一个新的String对象,然后将指针指向新的对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
六、重载和重写的区别?
可以从下⾯⼏个⻆度来回答:发⽣范围、参数列表、返回值类型、异常、访问修饰符、发⽣阶段
重载就是同一个类中多个同名方法根据不同的传参来执行不同的逻辑处理。
重写就是重写就是子类对父类方法的重新改造,外部样子不能改变,内部逻辑可以改变。
七、== 和 equals() 的区别
提示: == 对于基本类型和引⽤类型的作⽤效果是不同的, equals() 不能⽤于判断基本数据类型的变量,只能⽤来判断两个对象是否相等。 equals() ⽅法存在两种使⽤情况:1.类没有重写 equals() ⽅法 :通过 equals() ⽐较该类的两个对象时,等价于通过“==”⽐较这两个对象,使⽤的默认是 Object 类 equals() ⽅法。2.类重写了 equals() ⽅法 :⼀般我们都重写 equals() ⽅法来⽐较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。
== 对于基本类型和引用类型的作用效果是不同的:
- 对于基本数据类型来说,== 比较的是值。
- 对于引用数据类型来说,== 比较的是对象的内存地址。
equals() 不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。equals()方法存在于Object类中,而Object类是所有类的直接或间接父类,因此所有的类都有equals()方法。
equals() 方法存在两种使用情况:
- 类没有重写 equals()方法:通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是 Object类equals()方法。
- 类重写了 equals()方法:一般我们都重写 equals()方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。
八、Java 反射?反射有什么优点/缺点?你是怎么理解反射的(为什么框架需要反射)?
反射之所以被称为框架的灵魂,主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力。通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。
反射可以让我们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利。不过,反射让我们在运行时有了分析操作类的能力的同时,也增加了安全问题,比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。
静态加载和动态加载
静态加载(需要重新编译):
//获取User的实例对象
User user = new User();
动态加载(不需要重新编译):
Class c3 = Class.forName("java.reflect.User");
我们手机里的app要更新升级的时候,即使你不更新,也可以正常使用,但是不能使用更新的功能。我们的杀毒软件,需要下载一个功能插件时,不需要卸载掉重新安装完整版,只需要下载插件即可。这里面就用到了反射的知识哦。不需要重新编译,扩展所需功能即可。
(java动态代理可以在不改变被调用对象源码的前提下,在被调用方法前后增加自己的操作,极大地降低了模块之间的耦合性。Java的动态代理就是利用了反射的特性来实现的。)
九、谈谈对 Java 注解的理解,解决了什么问题?
提示: 想想你平时使⽤框架为啥能够如此⽅便。另外,需要注意注解的解析依赖于反射机制,务必要提前把反射机制搞懂。
注解可以看作是一种特殊的注释,主要用于修饰类、方法或者变量,提供某些信息供程序在编译或者运行时使用。程序可以通过反射来获取指定程序元素的Annotion对象,然后使用该对象来获取注解里面的元数据。
可以发现注解并不直接对其修饰的代码产生影响,它是为代码提供额外的信息,它是代码的元数据,注解与代码一起构成了编译器的完整输入,编译器借助注解可以生成并得到最终完整的代码。注解本身无论是使用还是定义都相对直观和简洁,非常容易理解,因为注解本身就是一种元数据,提供一种标记或者额外的数据。重点在于注解的处理,这是注解功能发挥作用的地方也就是注解功能逻辑实现的地方。
注解的本质是程序的元数据,为编译器提供的代码以外的额外的数据。注解是优雅的元编程的一种方式,可以减少重复的代码,提升开发效率。所以每当需要减少重复代码,生成代码,提供元数据时就要用注解来实现。特别是特定领域的问题,非常适合大量使用注解,如数据库(Room),网络请求(Retrofit),单元测试(JUnit)等等。并且注解的大部分应用都是在编译时生成代码,也不影响性能,所以可劲造儿,尽可能的使用注解吧。
十、Java 泛型了解么?泛型的作用?什么是类型擦除?泛型有哪些限制?介绍⼀下常用的通配符?
泛型,即参数化类型,目的是将具体类型参数化,在使用时需要传入具体类型进行替换。 参数分为实参和形参,泛型属于类型形参(好比抽象函数,是一种泛指,类似于数学函数中用 x,y,z 代表具体的值)。
泛型的作用:
- 保证类型安全,进行编译期错误检查,使代码具有更好的安全性和可读性。
- 不需要进行类型强制转换。
类型擦除: Java 泛型的实现是在编译层,编译后生成的字节码中不包含泛型中的类型信息。所以使用泛型时,加上的类型参数,会在编译器编译的时候去掉,这个过程称为类型擦除。
限制:
- Java泛型不能使用基本类型
- Java泛型不允许进行实例化
- Java泛型不允许进行静态化
- Java泛型不允许直接进行类型转换(通配符可以)
- Java泛型不允许直接使用instanceof运算符进行运行时类型检查(通配符可以)
- Java泛型不允许创建确切类型的泛型数组(通配符可以)
- Java泛型不允许定义泛型异常类或者catch异常(throws可以)
通配符:
- ?号通配符:可以匹配任意类型,任意的Java类都可以匹配。
- T号通配符:可以匹配任意类型,任意的Java类都可以匹配。
?:是只读类型的,不能进行增加和修改的操作,可以用于便利和删除。
T:可以进行增删改查,没有任何限制。
十一、内部类了解吗?匿名内部类了解吗?
内部类是定义在另一个类内部的类。内部类可以访问其外部类的成员,包括私有成员,而外部类不能直接访问内部类的成员。内部类提供了一种封装和组织代码的方式,可以用于实现更复杂的逻辑结构或实现特定的设计模式。
内部类可以分为四种类型:
-
成员内部类(Member Inner Class):成员内部类是定义在外部类的成员位置上的类。它可以访问外部类的成员,并且可以被外部类的对象实例化。成员内部类的实例与外部类的实例关联在一起,不能直接访问外部类的静态成员。
-
静态内部类(Static Inner Class):静态内部类是定义在外部类的成员位置上,并且使用static修饰的内部类。它与外部类的实例无关,可以直接访问外部类的静态成员,但不能访问外部类的非静态成员。静态内部类的实例化不依赖于外部类的实例。
-
方法内部类(Local Inner Class):方法内部类是定义在方法内部的类。它只能在方法内部使用,不能被方法外部的其他代码访问。方法内部类可以访问方法的局部变量,但需要保证局部变量是final或effectively final的。
-
匿名内部类(Anonymous Inner Class):匿名内部类是一种特殊的内部类,没有显式的类名。它通常用于在使用接口或抽象类的地方提供一个实现或子类,而无需专门定义一个具体的类。匿名内部类可以用来创建只需要一次性使用的类的实例。