Java的基本数据类型
Java 中有 8 种基本数据类型,分别为:
-
6 种数字类型:
-
4 种整数型: byte 、 short 、 int 、 long
大小(字节):1、2、4、8
-
2 种浮点型: float 、 double
大小(字节):4、8
-
-
1 种字符类型: char
大小(字节):2
-
1 种布尔型: boolean
大小(字节):1
这八种基本类型都有对应的包装类分别为: Byte 、 Short 、Integer 、 Long 、 Float 、 Double 、 Character 、 Boolean
为什么用BigDecimal不用double/double计算出现 什么问题
-
double会出现精度丢失的问题
计算机无法精确地表示小数, 所以做浮点数计算时会出现精度 丢失问题.
-
BigDecimal底层是用字符串存储数字, 运算也是用字符串做加减乘除计算的, 所以它能做到精确计算
所以一般牵扯到金钱等精确计算,都使用Decimal。
基本类型和包装类型的区别
- 包装类型不赋值就是 null ,而基本类型有默认值且不是 null。
- 包装类型可用于泛型,而基本类型不可以。
- 基本数据类型存放在栈中。包装类型属于对象类型,几乎所有对象实例都存在于堆中。
- 相比于对象类型, 基本数据类型占用的空间非常小。
自动装箱与拆箱
- 装箱:将基本类型用它们对应的引用类型包装起来;调用了包装类的valueOf()方法
- 拆箱:将包装类型转换为基本数据类型;调用了 xxxValue()方法
Integer的缓存问题
java
Integer i1 = 100;
Integer i2 = 100;
System.out.println(i1 == i2); // true
Integer i3 = 1000;
Integer i4 = 1000;
System.out.println(i3 == i4); // false
为什么出现上面这种奇怪的现象?
- Java的Integer类内部实现了一个静态缓存池,用于存储特定范围内的整数值对应的Integer对象。
- 默认情况下,这个范围是-128至127。当创建一个在这个范围内 的整数对象时,并不会每次都生成新的对象实例,而是复用缓存中的现有对象,会直接从内存中取出,不需要新建一个对象。
所以, 在对比是一定要用equals().
深拷贝和浅拷贝区别?什么是引用拷贝
- 浅拷贝:浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。
- 深拷贝 :深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。
- 引用拷贝: 引用拷贝就是两个不同的引用指向同一个对象。
== 和 equals() 的区别
== 对于基本类型和引用类型的作用效果是不同的:
- 对于基本数据类型来说,== 比较的是值
- 对于引用数据类型来说,== 比较的是对象的内存地址
equals() 方法存在两种使用情况:
- 类没有重写 equals() 方法 :通过equals()比较该类的两个对象时,等价于通过"=="比较这两个对象,使用的默认是 Object 类equals()方法。
- 类重写了 equals() 方法 :一般我们都重写 equals()方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。
String 中的 equals 方法是被重写过的,因为 Object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的 是对象的值。
hashCode() 有什么用
hashCode() 的作用是获取哈希值。这个哈希值的作用是确定该对象在哈希表中的索引位置 (可以快速找到所需要的对象)
Java用hashcode()和equals()判断是否为同一个对象
-
如果两个对象的hashCode 值相等,那这两个对象不一定相等 (哈希碰撞)。
-
如果两个对象的hashCode 值相等并且equals()方法也返回 true,我们才认为这两个对象相等
-
如果两个对象的hashCode 值不相等,我们就可以直接认为这两个对象不相等。
重写equals为什么要重写hashcode
因为java判断两个对象是否是相等的, 需要先比较hashcode是否一 致, 如果hashcode不一致那么就认为不相等.
如果没有重写hashcode, 那么两个相等的对象由于hashcode不相等, 就会被认为是不相等的. 但是按照重写的equals规则, 他们应该是相 等的.
在集合中, 如set集合去重中就会出现, 两个相等的对象放到set中都可以存在的怪象.
抽象类和接口的区别
- 抽象类和接口都不能直接实例化。如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。
- 抽象类要被子类继承,接口要被类实现。
- 接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现。
- 接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
- 抽象方法要被实现,所以不能是静态的,也不能是私有的
- 抽象类是对事物的抽象,即对类抽象;接口是对行为抽象,即局部抽象。抽象类对整体形为进行抽象,包括形为和属性。接口只对行为进行抽象。
- 抽象类是多个子类的父类,定义了子类大概的共性的东西,是一 种模板式设计;接口是一种行为规范,是一种辐射式设计。
面向对象的三大特征
- 封装
- 为了提高代码的安全性,隐藏对象的内部细节,封装将对象的内部状态(字段、属性)隐藏起来,并通过定义公共的方法(接口)来操作对象
- 外部代码只需要知道如何使⽤这些⽅法而无需了解内部实现
- 继承
- 允许一个类(子类)继承另⼀个类(父类)的属性和⽅法的 机制
- 子类可以重用父类的代码,并且可以通过添加新的方法或修 改(重写)已有的方法来扩展或改进功能
- 提高了代码的可重用性和可扩展性
- 多态
- 多态是指相同的操作或方法法可以在不同的对象上产生不同的行为,通过方法的重载和重写实现
- 多态允许以一致的方式式处理不同类型的对象,提高了代码的灵活性
面向对象和面向过程的区别
-
面向过程: 直接将解决问题的步骤分析出来,然后用函数把步骤一步 一步实现,然后再依次调用就可以了. 面向过程思想偏向于我们做一 件事的流程,首先做什么,其次做什么,最后做什么。
-
面向对象: 将构成问题的事物,分解成若干个对象,建立对象的目的 不是为了完成一个步骤,而是为了描述某个事物在解决问题过程中 的行为。 需要完成什么事情, 直接让某个对象来干即可.
类和对象
- 类: 像是一个抽象的设计图/模板. 类往往保存一类事物的共性(属性), 共有行为.
- 对象: 是通过这个设计图/模板创造出来具体实例. 实例往往是共性个性化的表现.
说⼀说你对多态的理解
- 子类其实是⼀种特殊的父类,因此Java允许把⼀个子类对象直接赋给⼀个父类引用变量,无须任何类型转换, 或者被称为向上转 型,向上转型由系统自动完成。
- 当把⼀个子类对象直接赋给父类引用变量时,例如 FatherObj o = new SonObj() , 这个 编译时类型是 FatherObj,而运行时 类型是 SonObj,当运行时调其方法时,其方法行为实际是子类 的行为, 也就是SonObj的行为.
- 这就可能出现:相同类型的变量、调用同一个方法时出现不同的行为,这就是所谓的多态
方法的重载和重写有什么区别
- 重载方法法的重载指的是在同⼀个类中,方法名相同但参数列表不同
- 重写是在子类中重新定义⽗类中已有的方法,方法名和参数列表必须相同
静态变量和静态方法与非静态有什么区别
静态变量和静态方法是与类本身关联的,而不是与类对象关联. 它们在内存中只存在一份,可以被类的所有实例共享.
换句话说, 静态是属于类的, 是被类所有对象共享的.
静态变量
- 共享性:所有该类的实例共享同一个静态变量。如果一个实例修改了静态变量的值,其他实例也会看到这个更改。
- 初始化:静态变量在类被加载时初始化,只会对其进行一次分配内存。
- 访问方式:静态变量可以直接通过类名访问,也可以通过实例访问,但推荐使用类名访问。
静态方法
- 共享性:所有该类的实例共享同一个静态方法.
- 访问方式:静态方法可以直接通过类名调用, 不需要创建对象.
- 访问静态成员:静态方法可以直接调用其他静态变量和静态方法,但不能直接访问非静态成员. 因为静态没有依赖具体对象.
final的作用
- 被final修饰的类无法继承
- 被final修饰的方法无法重写
- 被final修饰的变量为常量, 无法重新赋值
- final修饰基本类型变量, 无法修改
- final修饰引用类型, 这个引用无法指向其他对象, 也就是地址 无法修改, 但是对象属性可修改.
String的不可变性
String类中包含一个数组, 储存数组的每一个字符: private final byte[] value;
- final数组, 地址不能改变, 导致长度不能改变
- private, 数组中的内容不能改变
String s1 = new String("abc");这句话创建了几个字符串对象?
会创建 1 或 2 个字符串。
如果字符串常量池中不存在字符串对象"abc"的引用,那么会在堆中创建2个对象
- 一个是new的String对象
- 一个是char[]对应的常量池中的数据:"abc"
如果字符串常量池中存在字符串对象"abc"的引用,那么会在堆中创建1个对象, 就是new的String对象
String和StringBuffer和StringBuilder区别
- String:字符串变量,private final修饰,不可变!
- StringBuilder:字符串变量(线程不安全,可变) 没有使用 final 和 private 关键字修饰
- StringBuffer:字符串变量(线程安全,可变) 没有使用 final 和 private 关键字修饰
- StringBuilder是StringBuffer的简易版,更快!
每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuilder 或 StringBuffer 每次都会对 StringBuilder 或 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。
对于三者使用的总结:
- 操作少量的数据: 适用 String
- 单线程做大量字符串拼接操作: 适用 StringBuilder
- 多线程做大量字符串拼接操作: 适用 StringBuffer
字符串拼接用"+" 还是 StringBuilder
对象引用和"+"的字符串拼接方式,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到 一个 String 对象 。
不过,在循环内使用"+"进行字符串的拼接的话,存在比较明显的缺陷:编译器不会创建单个 StringBuilder 以复用,会导致创建过多的 StringBuilder 对象。 StringBuilder 对象是在循环内部被创建的,这意味着每循环一次就会创建一个 StringBuilder 对 象。
所以需要把new StringBuilder() 放在循环外部.
字符串常量池的作用了解吗
字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串 (String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建
开发中使用的字符串很可能有大量的重复, 字符串常量池就可以将重复的字符串只保存一份, 极大地节省了内存.
反射的基本思想
反射机制是在运行时,能够动态获取类的所有属性和方法;动态调用对象任意方法, 动态的创建对象
但是反射需要在运行时动态解析类、方法、字段的元数据信息, 性能较差
反射特性:
- 运行时类信息访问:反射机制允许程序在运行时获取类的完整结 构信息,包括类名、包名、父类、实现 的接口、构造函数、方法和字段等。
- 动态对象创建:可以使用反射API动态地创建对象实例,即使在 编译时不知道具体的类名。这是通过 Class类的newlnstance()方法或Constructor对象的 newlnstance()方法实现的。
- 动态方法调用:可以在运行时动态地调用对象的方法,包括私有 方法。这通过Method类的invoke()方法 实现,允许你传入对象实例和参数值来执行方法。
- 访问和修改字段值:反射还允许程序在运行时访问和修改对象的 字段值,即使是私有的。这是通过 Field类的get()和set()方法完成的。
你平时什么时候会用到反射
- 当要从配置文件中读配置创建类对象时需要使用反射
- 配置文件配置某个类的全类名, 然后就需要读配置, 然后反射 创建对象
- 使用工厂模式时, 往往也需要根据全类名来获取对象, 也会使用反 射创建对象.
- 当开发注解时, 往往需要反射来获取某个类/字段的注解
- 当使用spring, mybatis时, 这些框架底层会大量使用反射
- Spring 的IoC机制:会通过反射实例化 Bean、注入依赖
- Spring的AOP用到了动态代理, 也是大量使用反射
- MyBatis 的 Mapper 接口动态代理:反射生成接口的代理对象,执行 SQL 映射方法