8种基本数据类型
- 字符型:char
- 布尔型:boolean
- 数值型:整型 byte、short、int、long; 浮点型 float、double
注意:
- String不是基本数据类型,是引用类型
- Java 里使用 long 类型的数据一定要在数值后面加上 L
- java中,只要是字符,不管是数字还是英文还是汉字,都占2个字节,都是一个char
拆装箱
-
装箱: 就是将基本数据类型转换成对应的包装类。
-
拆箱:就是将包装类转换成对应的基本数据类型。
Integer i = 10; //自动装箱 相当于Integer.valueOf(10) int b = i; //自动拆箱 相当于i.intValue()
这里需要注意的是Byte、Short、Long、Integer这些类中的valueOf方法对于-128到127的数值会从缓存中返回相同的对象,不在此范围中的会返回一个新的对象。
==与equals区别
== 对于基本数据类型,它比较的是值;对于引用类型,它比较的是内存地址。
equals它的作用也是判断两个对象是否相等,它不能用于比较基本数据类型的变量。
equals方法存在Object类中,而Object类是所有类的间接或者直接父类,所以在使用equals方法时存在两种情况:
- 类没有重写equals方法,则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象
- 类重写equals方法,则比较两个对象的value值是否相等(String中就重写了)
总和拆装箱时提到的数值缓存问题,所有整型包装类对象之间值的比较,全部使用 equals 方法比较。
为什么重写equals()需要重写hashcode()
如果只重写equals方法,会出现两个hashcode不相等(地址不等)的对象,其equals相等。举个例子
正常情况下,map中应该只会存在instanceTwo和instanceThree
执行结果并不如我们所想,这是因为我重写了ProcessInstanceInfo的equals方法,没有重写hashCode方法,导致map在计算key的hash值出现不一致。
final关键字
- 被final修饰的类无法被继承。
- 被final修饰的方法不能被重写。
- 对于final修饰的变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象,但是这个变量的值是可以修改的
static关键字
被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问。
一个类中的静态变量,不属于类的对象或者实例。因为静态变量与所有的对象实例共享,因此他们不具线程安全性。
静态方法中不能访问非静态成员方法和非静态成员变量,反之则可以。
静态方法不能被覆盖(重写),因为方法的覆盖是基于运动时动态绑定,而静态方法是在编译器静态绑定。
重载与重写
- 重载是在同一个类中,重写是子类与父类之间
- 重载必须要改变参数列表【参数个数、参数类型、参数顺序】,而重写 参数必须相同
- 重载对访问修饰没有特殊要求,重写访问修饰符的限制一定要大于被重写方法的访问修饰符
接口与抽象类的区别
使用abstract来修饰一个类或方法,则称为抽象类或抽象方法;从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义,而没有变量和方法的实现。
抽象类
- 含有抽象方法的类必须被声明为抽象类,有抽象方法的类一定是抽象类,抽象类不一定有抽象方法
- 抽象类必须被继承,(抽象类本身无法实例化,不被继承也就没有存在的意义)
- 抽象方法必须被重写 (抽象方法不全重写,子类也应该是抽象类)
- 抽象类不能被实例化 (可以有自己的构造方法,但是不能直接通过new进行实例化)
- 抽象方法只需声明,而不需实现
接口
- 接口不能用于实例化对象,接口没有构造方法。
- 接口中所有的方法必须是抽象方法。
- 接口除了 static 和 final 变量不能包含其他动态变量。
- 接口不是被类继承了,而是要被类实现。
- 接口支持多继承。
String、StringBuffer、StringBuilder
String类长度是不可变的,StringBuffer和StringBuilder类长度是可以改变的;
StringBuffer类是线程安全的,StringBuilder不是线程安全的;
String和StringBuffer的主要性能区别:String是不可变的对象,因此每次在对String类进行改变的时候都会生成一个新的String对象,然后将指针指向新的String对象,所以经常要改变字符串长度的话不要使用String,因为每次生成对象都会对系统性能产生影响,特别是当内存中引用的对象多了以后,JVM的GC就会开始工作,性能就会降低;
使用StringBuffer类时,每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用,所以多数情况下推荐使用StringBuffer,特别是字符串对象经常要改变的情况
字符串池
在JVM中,为了减少相同的字符串的重复创建,为了达到节省内存的目的。会单独开辟一块内存,用于保存字符串常量,这个内存区域被叫做字符串常量池。
当代码中出现双引号形式(字面量)创建字符串对象时,JVM 会先对这个字符串进行检查,如果字符串常量池中存在相同内容的字符串对象的引用,则将这个引用返回;否则,创建新的字符串对象,然后将这个引用放入字符串常量池,并返回该引用。
jdk1.8之后 字符串常量池在元空间中
continue、break 和 return 的区别是什么?
- continue:是在一个循环体中结束本次循环,但会继续执行下一次的循环。
- break:是结束当前循环体。**注意:**当两个for循环嵌套时,如果break语句位于内层的for循环,它只会跳出内层的for循环,但不会跳出外层的for循环。
- return:是直接结束整个方法。
为什么要序列化
- 序列化:把对象转换为字节序列的过程称为对象的序列化。
- 反序列化:把字节序列恢复为对象的过程称为对象的反序列化。
序列化最终的目的是为了对象可以跨平台存储,和进行网络传输。而我们进行跨平台存储和网络传输的方式就是IO,IO支持的数据格式就是字节数组。但是我们单方面的只把对象转成字节数组还不行,因为没有规则的字节数组我们是没办法把对象的本来面目还原回来的,所以我们必须在把对象转成字节数组的时候就制定一种规则(序列化),那么我们从IO流里面读出数据的时候再以这种规则把对象还原回来(反序列化)。
Exception与Error的区别
- Error:属于程序无法处理的错误,是JVM需要负担的责任,无法通过try-catch来进行捕获。
- Exception:程序本身可以处理,可以通过catch来进行捕获,通常遇到这种错误,应对其进行处理,使应用程序可以继续正常运行。Exception又可以分为运行时异常(RuntimeException,又叫非受检查异常unchecked Exception)和非运行时异常(又叫受检查异常checked Exception)。
Checked Exception 和 Unchecked Exception 有什么区别?
Checked Exception:是指编译器要检查这类异常,如果不处理这类异常,编译时会报错。
Unchecked Exception:又称为运行时异常,编译器不做检查,这类异常是程序本身就有问题,是可以避免的。
面向对象的三大特征
**封装:**通过公有的方法访问私有的属性
**继承:**继承就是子类继承父类的特征和行为,或子类从父类继承方法,使得子类具有父类相同的行为,但是子类不能选择性地去继承父类,它可以增加新的属性和行为。关于继承如下 3 点请记住:
- 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有。
- 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
- 子类可以用自己的方式实现父类的方法。
**多态:**同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。多态的前提:
- 类与类之间必须有关系,要么继承(extends),要么实现(implement)。
- 子类要重写父类的方法
- 父类的引用指向子类对象
反射机制
反射机制是在运行时,只要给定类的名字,就可以通过反射机制来获得类的所有信息,包括它的属性和方法。举例:
- Spring MVC 的请求调用对应方法,是通过反射。
- JDBC 的 Class.forName(String className) 方法,也是使用反射。
- Jdk动态代理
动态代理
动态代理可以在不修改别代理对象代码的基础上,通过扩展代理类,进行一些功能的附加与增强。比如Spring的AOP就是使用动态代理。
JDK动态代理
使用JDK动态代理的五大步骤:
- 通过实现InvocationHandler接口来自定义自己的InvocationHandler;
- 通过Proxy.getProxyClass获得动态代理类;
- 通过反射机制获得代理类的构造方法,方法签名为getConstructor(InvocationHandler.class);
- 通过构造函数获得代理对象并将自定义的InvocationHandler实例对象传为参数传入;
- 通过代理对象调用目标方法。
CGLIB动态代理
CGLIB包的底层是通过使用字节码处理框架ASM,来转换字节码并生成新的类。
CGLIB代理实现如下:
- 首先实现一个MethodInterceptor,方法调用会被转发到该类的intercept()方法。
- 然后在需要使用的时候,通过CGLIB动态代理获取代理对象。
JDK动态代理要求被代理的类必须实现接口,有很强的局限性。由于JDK版本的升级,效率上要比CGLIB快
而CGLIB动态代理则没有此类强制性要求。简单的说,CGLIB会让生成的代理类继承被代理类,并在代理类中对代理方法进行强化处理(前置处理、后置处理等),不能对final类以及final方法进行代理。