Java基础
1. ==和equals区别
- ==:基本数据类型比较值,引用数据类型比较内存地址
- equals:基本数据类型没有,引用数据类型如果没有重写,比较值
2. hashcode和equals
- hashcode会返回调用对象的hash值,一般与equals一起使用,用于判断两个对象是否相同,比如hashMap就是使用hashcode和equals实现key唯一的特性
- 对于两个hashcode相同的对象,他们的equals不一定相同,因为可能存在hash冲突。而两个equals为true的对象,他们的hashcode必须相同,因此重写equals必须重写hashcode
3. 基本数据类型和引用数据类型、值传递和引用传递
- 数值类型:int(32位4字节)、byte(8位1字节)、short(16位2字节)、long(64位8字节)、float(32位4字节)、double(64位8字节)
- 字符类型:char(16位2字节)
- 布尔类型:boolean(1位,0和1表示)
- 引用数据类型:对象、包装类、字符串
- 如果是基本数据类型,是值传递。如果是引用数据类型,是引用传递
4. Integer对象、缓存池对象、装包拆包
- 堆对象:Integer heap = new Integer(123),在堆中创建一个123的Integer对象。
- 缓存池对象:Integer cache = 123,在缓存池中查看是否存在123,如果有,指向它,否则创建一个123放入到缓存池,指向它
- 装包:Integer.valueOf()
- 拆包:Integer.intValue()
- int和Integer比较时,将Integer拆包成int
- heap == cached,比较内存地址,false
- Integer缓存池大小:-128 ~ 127
5. String、StringBuilder、StringBuffer
- String是final类,不可变,线程安全,每次拼接都要创建一个新的String对象接收
- StringBuilder和StringBuffer有append方法进行拼接
- 线程安全:StringBuffer的append方法使用synchronized修饰
- 运行速度:StringBuilder、StringBuffer、String
- 使用场景
- String:操作量少
- StringBuffer:多线程数据量大
- StringBuilder:单线程数据量大
6. String对象、字符串缓存池对象(对象,存放在JVM的堆中)
- 堆对象:String heap = new String("123"),在堆中创建一个"123"的字符串对象
- 缓存池对象:String cache = "123",在缓存池中查看是否有"123",有就指向它,否则新建一个存入然后指向它
- heap == cache,比较内存地址,false
- String str1 = new String("A"+"B")会创建几个对象:5个
- 字符串常量池:A、B、AB
- 堆:AB
- 引用:str1
- String str2 = new String("A") + "B"会创建几个对象:3个
- 字符串常量池:AB
- 堆:AB
- 引用:str2
7. static:类、方法、变量、代码块
- 修饰类:静态内部类,不能继承,外部类.内部类访问
- 修饰方法:静态方法,类.方法名访问,不能访问非静态成员,不能使用this关键字。静态方法随类加载而加载时非静态成员和调用对象还不一定存在
- 修饰变量:静态变量,类.变量名访问,类加载时加载,只加载一次,内存中只有一份,修改后的值就是最新值
- 修饰代码块:静态代码块,类加载时被执行,只执行一次
8. 静态变量、局部变量、成员变量、类加载顺序
- 静态变量随类,类.变量名访问,存放在JVM的方法区
- 成员变量随对象,对象.变量名访问,存放在JVM的堆中
- 局部变量在方法内定义,存放在JVM的java虚拟机栈或者本地方法栈
- 类加载顺序:静态变量、静态代码块、构造函数、非静态变量、非静态代码块
9. final:类、方法、变量
- 修饰类:不可继承
- 修饰方法:不可重写
- 修饰变量:常量,一般与static一起使用,常量必须赋初值,不可修改
- 好处:线程安全、常量池可以减少重复常量的创建和销毁
10. super、this
- super
- 指代当前对象的父类,super.xxx来调用父类的成员
- super()调用父类的构造函数
- this
- 指代当前对象,this.xxx调用当前类的成员
- this()调用当前类的构造函数
11. 面向对象的三大特征:封装、继承、多态
- 封装:使用private修饰属性,通过getter和setter设置和获取
- 继承:子类可以继承父类的非私有成员,但是不能继承父类的构造函数,super()调用父类构造函数。java只支持单继承,子类只能有一个父类
- 多态
- 前提:继承和实现
- 表现:父类引用指向子类对象,接口引用指向实现类对象
- 编译时多态:重载,参数数量或参数类型不同,编译时能够确定调用哪一个方法
- 运行时多态:重写,只有方法体不同,编译时无法确定,只能在运行时根据指向关系来确定,比如父类引用指向子类对象,调用子类的方法
12. 抽象类
- 定义:abstract定义,extends继承,抽象类是内部类时可以使用private修饰,不是内部类则不能使用private修饰
- 实现:子类必须重写所有抽象方法
- 构造函数:有
- 方法:抽象方法的权限范围是public和protected。抽象类可以有普通方法,比如静态方法和非静态方法
- 变量:无权限要求,可以有静态变量和成员变量
13. 接口
- 定义:interface定义,implements实现,只能使用public修饰
- 实现:实现类必须重写所有抽象方法
- 构造函数:无
- 方法:接口的普通方法默认是抽象方法
- 变量:默认是public static final,必须赋初值
14. 接口和抽象类的区别
- 抽象类:is-a,定义了被抽象的对象它是什么,比如说鸟类这个物种,它下面其实细分有很多不同的鸟:麻雀、鸽子、鹦鹉
- 接口:has-a,定义了对象能做什么,提供了一些规范,比如说动物能做什么动作,规定一些方法比如:睡觉、吃东西、喝水、跑步之类的方法
15. 什么时候用接口什么时候用抽象类
- 定义对象的本质时,也就是需要定义它是什么东西的时候,可以使用抽象类
- 拓展事物功能时,可以使用接口
- 需要实现多继承时使用接口
14. 反射
- 含义:对于任何一个类来说,在运行都可以通过反射来获取它的属性和方法,对于任何一个对象来说,都可以通过反射调用它的方法和属性。
- 获取class对象的三种方式
- User.class,不会初始化
- User.getClass().getName(),初始化静态变量
- Class.forName(),初始化静态变量和执行静态代码块
15. 异常:Error和Exception都是继承自Throwable
- Error是JVM错误,JVM无法处理,比如OOM和SOF
- Exception是JVM异常,JVM可以处理,分为受检异常和非受检异常
- 受检异常:使用try/catch/finally代码块捕获,比如IO异常
- 非受检异常:无法捕获,RuntimeException运行时异常,比如除0异常、数组越界异常、空指针异常,Error也是属于非受检异常,无法预知
16. 设计模式:单例、代理
1. 单例:用于封装工具类,使整个系统数据统一
- 特征:唯一实例、私有的构造函数、公有的创建实例方法
- 饿汉模式:线程安全,类加载就创建实例,调用创建实例方法直接返回已创建的实例,内存只存在一份,可能会导致内存浪费,不适合创建大对象
public class Hungry{ private static Hungry hungry = new Hungry(); private static Hungry(){}; public static Hungry getInstance(){ return hungry; } }
- 懒汉模式:线程不安全,调用创建实例的方法时才会自行创建类实例,多线程下可能会创建多个实例
public class Lazy{ private static Lazy lazy = null; private static Lazy(){}; public static Lazy getInstance(){ if(lazy == null){ lazy = new Lazy(); } return lazy; } }
- 双重检查:synchronized保证线程安全,volatile保证构造实例的4个指令不重排序,第一次判空防止进入同步块,第二次判空防止创建多个实例
public class Double{ /* 1. 申请空间 2. 初始化默认值 3. 执行构造 4. 连接引用 */ private volatile static Double double = null; private static Double(){}; public static Double getInstance(){ if(double == null){ synchronized(Double.class){ if(double == null){ double = new Double(); } } } return double; } }
2. 代理模式
- 三个对象
- 抽象对象:使用接口或者抽象类定义,定义抽象方法
- 代理对象:实现抽象对象,重写抽象方法,提供具体的方法体
- 真实对象:实现抽象对象,调用代理对象提供的方法完成抽象方法
- 静态代理:手动编写
- 动态代理:反射重写
- SpringAOP的原理是动态代理
- 如果代理对象是实现类,使用jdk动态代理,利用拦截器(必须实现InvocationHandler)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理
- 如果代理对象不是实现类,使用cglib动态代理,ASM框架重写字节码文件,动态生成代理对象的子类,子类重写要代理对象的所有非final的方法