一、变量自增
1.1 示例
- 这种计算跟之前数据结构和算法学过的计算器一致,都是要维护一个栈,保存操作数,判断加减乘除运算符,最后栈中经过计算后只剩下一个数,就是我们需要的运算结果
- Java也需要维护一个操作数栈,来计算我们的最终结果
1 | int i = 1; |
1.2 分析
1.2.1 前两步
- 赋值操作更新局部变量表,变量操作会先将变量值压入操作数栈
- 自增操作会更新变量的局部变量表,但不会影响操作数栈的值,存在操作数就会把变量的值压入操作数栈中
- 赋值操作是把操作数栈的最终结果赋值给变量,也就是说i经过前两步后值为1
1.2.2 第三步
- 后增操作:先赋值j,再自增i
1.2.3 第四步
1.3 小结
总的来说在运算的时候维护一个局部变量表和一个操作数栈,来保存和更新我们的数据
++i会先修改i保存到局部变量表中的值,再将自增后的值放入到操作数栈中
- i++与++i相反,会先将i的值放入到操作数栈中,而后再更新i的局部变量表
- 赋值操作是在操作数栈计算完之后才执行的
二、JDK/JRE/JVM的区别
- JDK:Java标准开发包,提供编译、运行Java程序所需的各种工具和资源,包括Java编译器、Java运行时环境,以及常用的Java类库等
- JRE,Java运行环境,用于运行Java的字节码文件,JRE中包括了JVM工作所需要的类库,普通用户只需要按照JRE来运行Java程序,而程序开发者必须安装JDK来编译、调试程序
- JVM:Java虚拟机,是JRE的一部分,它是整个Java实现跨平台的最核心的部分,负责运行字节码文件
三、Java数据类型
基础数据类型
- 整数类型(byte、short、int、long)
- 浮点类型(float、double)
- 数值型
- 字符型(char)
- 布尔型(boolean)
引用数据类型
- 类(class)
- 接口(interface)
- 数组([])
四、hashCode()与equals()之间的关系
在了解hashCode()之前,首先要了解哈希表这种数据结构,这里假设存在一个哈希函数f(x),那么它的映射关系是数据->存储地址,即对于一个数据x,它存放的地址为经过函数f(x)得到的地址。
但由于计算机的存储空间不是无限的,所以哈希地址的计算一般会出现重复的情况,这里可以采用链地址法,即如果两条数据计算到的哈希值相同,那么就在这个位置上创建一个链表来存放这两个相同哈希值的数据。
有了哈希表的概念,那么这里Java对象中的hashCode就是经过某一哈希函数得到的哈希值,在应用时参考以下代码:
1 | public class Key { |
这段代码测试了使用相同id值的对象在HashMap中进行查找(工作会经常使用到的操作),测试结果如下:
- 不重写hashCode()和equals()方法,结果输出”null“
- 重写hashCode()但不重写equals(),结果仍输出“null”
- 同时重写hashCode()和equals()方法,输出结果”Key with id is 1“,内容可以正常查询到
对以上三种测试情况有以下说明:
- 集合类HashMap在进行对象查找(get()方法)时,会先根据对象的hashCode进行查找,
- 如果两个对象的hashCode不同,则说明两个对象绝对是不同的;
- 如果两个对象的hashCode相同,则调用对象的equals()方法继续进行比较,比较内容相同则两个对象一定是相同的
- 如果不重写hashCode()和equals()方法,那么对象会采用Object父类实现的默认的hashCode()和equals()方法,默认的hashCode是对象的内存地址,默认equals()的比较方式也是比较两个对象的内存地址是否相同
在使用时一般会遵循以下原则:
- 重写equals()方法必须重写hashCode()方法
- 两对象equals()相等,则这两个对象的hashCode()应该相等
- hashCode()和equals()的返回值应该是稳定的,不应具有随机性
- 如果要在HashMap的”键“部分存放自定义的对象,一定要在这个对象里重写equals和hashCode方法
在面试时会经常问到比如:
- 有没有重写过hashCode方法
- 在使用HashMap时有没有重写hashCode和equals方法,怎么写的
五、==与equals的区别
参考第三题,equals()方法在不重写的情况下,默认比较的是两个对象的内存地址,但我们往往会在类中重写equals()来比较对象之间的内容是否相等,equals()只能被对象调用。
==是Java中的一种操作符,它有两种比较方式
- 对于基础数据类型来说,==判断的是两边的值是否相等
- 对于引用类型来说,==判断的是两边的引用是否相等,也就是判断两个对象是否指向了同一块内存区域
equals()方法的特性
- 自反性:对于任何非空引用值x来说,x.equals(x)返回true
- 对称性:对于任何非空引用值x、y来说,如果x.equals(y)为true,则y.equals(x)为true
- 传递性:对于任何非空引用值x、y、z来说,如果x.equals(y)为true,y.equals(z)为true,那么x.equals(z)也为true
- 一致性:对于任何非空引用值x、y来说,如果x.equals(y)为true,那么它们要始终相等
- 非空性:对于任何非空引用值x来说,x.equals(null)必须返回false
六、String中的equals是如何重写的
String是Java中的字符串类,它整个类都是被final修饰的,这意味着String类是不能被任何类继承,任何修改String字符串的方法都是创建了一个新的字符串
equals方法是Object类定义的方法,Object是所有类的父类,当然也包括String,String重写的equals方法如下
- 首先是引用的判断,引用相等则直接返回true
- 接着判断对象是否是String实例,不是则直接返回false,否则将对象强转后,比较字符串的长度,长度不等肯定返回false
- 长度相等的情况下,再逐个字符进行比较,存在不同的字符则返回false
注意JDK1.8以后,new String(“abc”)操作和普通的字符串赋值操作作用是一样的,以下的intern()方法是获取常量池中的字符串
1 | public class Test { |
七、String s1 = new String(“abc”)在内存中创建了几个对象
一个或者两个,首先new操作一定会在堆中创建一个对象,如果常量池中不存在“abc”对象,那么就会在常量池中创建一个“abc”,否则就不进行创建
观察下面String构造器的源码可以发现,String对象的hash值和常量池“abc”对象的hash值是一致的
new操作的内存情况如下:
八、String为什么是不可变的、jdk源码中的String如何定义的、为什么这么设计
不可变对象:不可变对象就是一经创建后,其对象的内部状态不能被修改,即:
- 不可变对象内部属性都是final的
- 不可变对象的内部属性都是private的
- 不可变对象不能提供任何可以修改内部状态的方法,setter方法也不行
- 不可变对象不能被继承和扩展
String类是一种对象,它独立于Java基本数据类型而存在的,可以理解为字符串的集合
String被设计为final的,表示String对象一经创建后,它的值就不能再被修改了,任何对String值进行修改的方法就是重新创建一个字符串
String对象创建后会存在于运行时常量池中,运行时常量池属于方法区的一部分,JDK1.7后把它移到了堆中
九、String/StringBuffer/StringBuilder的区别
String类型是不可变的,属于字符串常量
StringBuffer和StringBuilder都是可修改的字符串对象,区别是StringBuffer是线程安全的,StringBuilder是线程不安全的
十、static关键字是干什么用的
- 修饰变量,static修饰的变量称为静态变量、也称为类变量,类变量属于类所有,对于不同的类来说,static变量只有一份,static修饰的变量位于方法区;static修饰的变量能够直接通过类名.变量名来进行访问,不用通过实例化类再进行使用
- 修饰方法,static修饰的方法称为静态方法,静态方法能够直接通过类名.方法名来使用,在静态方法内部不能使用非静态属性和方法
static可以修饰代码块,主要分为两种,一种直接定义在类中,使用static{},这种被称为静态代码块,一种是在类中定义静态内部类,使用static class xxx来进行定义
static可用用于静态导包,通过使用import static xxx来实现,这种方式一般不推荐使用
- static可以和单例模式一起使用,通过双重检查锁来实现线程安全的单例模式
十一、final关键字是干什么用的
final 是 Java 中的关键字,它表示的意思是 不可变的
,在 Java 中,final 主要用来
- 修饰类,final 修饰的类不能被继承,不能被继承的意思就是不能使用
extends
来继承被 final 修饰的类。 - 修饰变量,final 修饰的变量不能被改写,不能被改写的意思有两种,对于基本数据类型来说,final 修饰的变量,其值不能被改变,final 修饰的对象,对象的引用不能被改变,但是对象内部的属性可以被修改。final 修饰的变量在某种程度上起到了
不可变
的效果,所以,可以用来保护只读数据,尤其是在并发编程中,因为明确的不能再为 final 变量进行赋值,有利于减少额外的同步开销 - 修饰方法,final 修饰的方法不能被重写
- final 修饰符和 Java 程序性能优化没有必然联系
十二、抽象类和接口的区别是什么
抽象类abstract和接口interface都是Java中的关键字,
抽象类和接口的相同点:
- 都允许进行方法的定义,而不用具体的方法实现
- 都允许被继承
- 广泛的应用于 JDK 和框架的源码中,来实现多态和不同的设计模式。
抽象类和接口的不同点:
- 抽象级别不同:类、抽象类、接口其实是三种不同的抽象级别,抽象程度依次是 接口 > 抽象类 > 类。在接口中,只允许进行方法的定义,不允许有方法的实现,抽象类中可以进行方法的定义和实现;而类中只允许进行方法的实现
- 使用的关键字不同:类使用
class
来表示;抽象类使用abstract class
来表示;接口使用interface
来表示 - 变量:接口中定义的变量只能是公共的静态常量,抽象类中的变量是普通变量。
十三、重写和重载的区别
- 重写是针对子类和父类的表现形式,而重载是在同一类中的不同表现形式
- 子类重写父类的方法一般使用@override来表示,重写后的方法其方法的声明和参数类型、顺序必须要和父类完全一致
- 重载是针对同一类中概念,它要求重载的方法必须满足下面任何一个要求:方法参数的顺序、参数的个数、参数的类型任意一个保持不同即可
十四、byte的取值范围
Java的Byte的取值范围为(-128,127),占用一个字节,即8bit
Java中使用补码来表示二进制数,因此最高位为符号位,最高位为0表示正数,最高为为1表示负数
先来复习下源码、反码、补码
原码:正常的二进制计算,存在符号位,0表示正数,最高为为1表示负数
反码:正数的反码与其原码相同;负数在原码基础上,按位取反,符号位除外
补码:正数的补码与其原码相同;负数的补码是在其反码的末位+1
byte中,正数的最大值就是0111 1111
因为存在符号位,所以存在两个0,即-0:1000 0000和+0:0000 0000,因此用1000 0000来表示负数的最小值,即-128
1000 0000减1后为0111 1111,再取反1000 0000,原码的值128,在负数中就应该为-128
十五、HashMap和HashTable的区别
不同点:
- 父类不同:HashMap继承了AbstractMap类,而HashTable继承了Dictionary类
空值不同:HashMap允许空的key和value值,HashTable不允许空的key和value值。HashMap会把Null key当做普通的key对待,且不允许null key重复。HashTable存null的key就会报空指针异常
线程安全性:HashMap是线程不安全的,如果多个外部操作同时修改HashMap的数据结构比如add或者是delete,必须进行同步操作,即仅仅对key或者value的修改不是改变数据结构的操作
- 性能方面:虽然HashMap和HashTable都是基于单链表的,但是HashMap进行put或者get操作,可以达到常数时间的性能;而HashTable的put和get操作都是加了synchronized,所以效率很差
- 初始容量不同:HashTable的初始长度是11,之后每次扩充容量变为之前的2n+1(n为上一次的长度)而HashMap的初始长度为16,之后每次扩充变为原来的两倍。创建时,如果给定了容量初始值,那么HashTable会直接使用你给定的大小,而HashMap会将其扩充为2的幂次方大小