总结
JAVA的三大版本
Java分为三个版本
- Java SE Standard Edition(标准版) 包含那些构成Java语言核心的类,数据
库连接、接口定义、输入/输出、网络编程 - Java EE Enterprise Edition(企业版) 包含Java SE 中的类,并且还包含用
于开发企业级应用的类,例如EJB、servlet、JSP、XML、事务控制 - Java ME Micro Edition(微缩版) 包含Java SE中一部分类,用于消费类电子
产品的软件开发,例如呼机、智能卡、手机、PDA、机顶盒
JVM、JRE和JDK
JDK包含JRE,JRE包含JVM
- JVM Java虚拟机,Java程序需要运行在虚拟机上,不同的操作系统有自己的虚
拟机,使用相同的字节码,因此Java语言可以实现跨平台。JVM 可以理解的代码就
叫做字节码(即扩展名为.class 的文件),它不面向任何特定的处理器,只面向
虚拟机 - JRE 包括Java虚拟机和Java程序所需的核心类库等。核心类库主要是java.lang
包:包含了运行Java 程序必不可少的系统类,如基本数据类型、基本数学函数、字
符串处理、线程、异常处理类等,系统缺省加载这个包,如果想要运行一个已编译
的Java程序,计算机中只需要安装JRE即可 - JDK 包含了Java的开发工具,也包括了JRE。其中的开发工具:编译工具
(javac.exe),打包工具(jar.exe)等
编译型语言和解释型语言
- 编译型语言 指使用专门的编译器,针对特定平台(操作系统)将某种高级
语言源代码一次性”翻译”成可被该平台硬件执行的机器码(包括机器指令和
操作数),并包装成该平台所能识别的可执行性程序的格式,这个转换过程
称为编译(Compile)编译生成的可执行性程序可以脱离开发环境,在特定
的平台上独运行。运行时不需要重新翻译,直接使用编译的结果就行了。C
C++ Objective-C Swift kotlin - 解释型语言 先编译成一种与平台无关的字节码文件,然后在运行时由解释
器翻译成特定机器码执行,字节码文件由十六进制值组成,而JVM 以两个十六
进制值为一组,即以字节为单位进行读取。在Java 中一般是用javac 命令编
译源代码为字节码文件
Java语言特点
- Java是半编译半解释型语言 因为Java虚拟机既有解释器也有JIT即时编译
器,JIT即时编译器能够捕获程序中的热点代码,编译成机器码缓存起来存入
方法区中,当遇到相同的代码时,不必再去使用解释器翻译,直接去找对应
的机器码执行 - Java 完全支持面向对象的三种基本特征:继承、封装和多态。Java语言完全以
对象为中心,Java程序的最小程序单位是类,整个Java程序是由一个个类组成 - Java是一门强类型语言,强类型包括两个方面
- 所有的变量必须先声明、后使用
- 指定类型的变量只能接受类型与之匹配的值,强类型语言可以在编译过程中
发现源代码的错误 ,从而保证程序更加健壮
- 具有跨平台性,一次编写,到处运行,具有很好的可移植性
- 支持网络编程并且很方便。Java 语言诞生本身就是为简化网络编程设计的,
因此Java 语言不仅支持网络编程而且很方便
Java与C++的异同
- 都是面向对象的语言,都支持封装、继承和多态
- C++ 支持指针,而 Java 没有指针的概念
- C++ 支持多继承,而Java 不支持多重继承,但允许一个类实现多个接口
- Java 自动进行无用内存回收操作,不再需要程序员进行手动删除,而C++
中必须由程序释放内存资源,这就增加了程序员的负担 - Java 不支持操作符重载
- Java 取消了C/C++ 中的结构和联合,使编译程序更加简洁
- C 和C++ 不支持字符串变量,在C 和C++ 程序中使用“Null”终止符代表字
符串的结束。在Java 中字符串是用类对象(String和StringBuffer)来实现
面向对象的三大特征
- 封装 封装是指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允
许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法
来操作属性 - 继承 子类继承父类,子类作为一种特殊的父类,将直接获得父类的属性和
方法 - 多态 子类对象可以直接赋给父类对象,但运行时表现为子类的行为特征,
也是说同一个类型的对象执行同一个方法,表现不同行为特征
面向对象和面向过程的区别
- 面向过程 性能比面向对象高,因为类调用时需要实例化,开销比较大,
比较消耗资源。比如单片机、嵌入式开发、Linux/Unix 等一般采用面向过程
开发,性能是最重要的因素 - 面向对象 易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的
特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护
字符型常量和字符串常量的区别
- 形式 : 字符常量是单引号引起的一个字符,字符串常量是双引号引起的0个
或若干个字符 - 含义 : 字符常量相当于一个整型值(ASCII 值),可以参加表达式运算;字符
串常量代表一个地址值(该字符串在内存中存放位置) - 占内存大小 : 字符常量只占 2 个字节; 字符串常量占若干个字节 (注意
:char在Java中占两个字节)
Java 数据类型
Java语言支持的类型分为两类:基本类型和引用类型
- 基本类型包括 boolean类型和数值类型。数值类型有整数类型和浮点类型。
整数类型包括byte short int long char,浮点类型包括float double - 引用类型 包括类、接口和数组类型,还有一种特殊的 null 类型所谓引
用数据类型就是对一个对象的引用,对象包括实例和数组两种。实际上引用
类型变量就是一个指针,只是Java语言里不再使用指针这个说法,空引用
(null)只能被转换成引用类型,不能转换成基本类型,因此不要把一个
null值赋给基本数据类型的变量
基本数据类型的转换
- 如果直接将一个较小的整数值(在 byte short类型的表数范围内〉赋给byte
short变量系统会自动把这个整数值当成byte或者short类型来处理 - 如果使用一个巨大的整数值(超出了int类型的表数范围)时Java不会自动把
这个整数值当成long 类型来处理。如果希望系统把一个整数值当成long类型
来处理,应在这个整数值后增加L或者l作为后缀。通常推荐使用L - Java语言的默认浮点类型是double类型,double占8个字节,float占4个
字节如果希望将一个浮点数当成float,在浮点数值后加f或F,浮点数必须要
有小数点
自动类型转换和强制类型转换的区别
- 自动类型转换 表数范围小的可以向表数范围大的自动转换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20int a = 6;
// int 类型可以自动转换为 float 类型
float f = a ;
// 下面将输出 6.0
System.out.println(f);
// 定义 byte 类型的整数变量
byte b = 9 ;
// 下面代码将出错,byte 类型不能自动类型转换为 char 类型
// char c = b;
// byte 类型变量可以自动类型转换为 double 类型
double d = b ;
// 下面将输出 9.0
System out printl (d) ;
//由于1是int类型,因此s1+1运算结果也是int型,需要强制转换类型才能赋值给short型
short s1 = 1;
s1 = s1 + 1;
//可以正确编译,因为 s1+=1;相当于s1 = (short(s1 + 1);其中有隐含的强制类型转换。
short s1 = 1;
s1 += 1; - 浮点数强制转换为整数时会截断浮点数的小数部分
1
2
3
4
5
6
7
8
9
10int iValue = 233;
// 强制把一个 int 类型的值转换为 byte 类型 的值
byte bValue = (byte)iValue;
// 将输出 -23
System.out.println(bValue) ;
double dValue = 3.98 ;
// 强制把 double 类型的值转换为 int 类型的值
int tol = (int)dValue
// 将输出3
System.out.println(tol);
JAVA采用何种编码?
这里Java文件的编码可能有多种多样,但Java编译器会自动将这些编码按照
Java文件的编码格式正确读取后产生class文件,这里的class文件编码是
Unicode编码(具体说是UTF-16编码)
char可以存储汉字吗
char是按照字符存储的,不管英文还是中文,固定占用占用2个字节,用来储
存Unicode字符
随机字符串是如何生成的?
首先可以随机生成一个指定范围的int数字,例如生成一个小写字母就是在97
~122之间,然后强制转换为char类型,将这些字符拼接起来就是一个验证
字符串
1 | //定义一个空字符串 |
字符型常量和字符串常量的区别?
- 形式上: 字符常量是单引号引起的一个字符,字符串常量是双引号引起
的若干个字符 - 含义上: 字符常量相当于一个整型值(ASCII值),可以参加表达式运算,
字符串常量代表一个地址值(该字符串在内存中存放位置,相当于对象占 - 内存大小:字符常量只占2个字节;字符串常量占若干个字节(至少一个
字符结束标志) (注意: char 在Java中占两个字节)
什么是字符串常量池?
Java中常量池的概念主要有三个:全局字符串常量池,class文件常量池,
运行时常量池
- 全局字符串常量池 jvm 为了提升性能和减少内存开销,避免字符的重复
创建,其维护了一块特殊的内存空间,即字符串池,当需要使用字符串时,
先去字符串池中查看该字符串是否已经存在,如果存在,则可以直接使用,
如果不存在,初始化,并将该字符串放入字符串常量池中 - class文件常量池 用于存放编译器生成的各种字面量(Literal)和符号
引用(Symbolic References )。字面量就是我们所说的常量概念,如文本
字符串、被声明为final 的常量值等。符号引用是一组符号来描述所引用的
目标,符号可以是任何形式的字面量 - 运行时常量池 当类加载到内存中后,jvm 就会将class常量池中的内容
存放到运行时常量池。将每个class常量池中的符号引用值替换成直接引用
BigDecimal的使用
- 浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型
不能用equals 来判断 - 使用 BigDecimal 来定义浮点数的值,再进行浮点数的运算操作
- 使用BigDecimal时为了防止精度丢失,推荐使用BigDecimal(String)
构造方法来创建对象
switch的格式是什么样的?
控制表达式的数据类型可以是short byte char int enum string,目前
不支持long
1 | switch (expresson) |
break continue return的区别
- break 指跳出整个循环体,继续执行循环下面的语句
- continue 跳出本次循环,继续执行下次循环(结束正在执行的循环,
进入下一个循环条件) - return 用于跳出所在方法,结束该方法的运行。直接使用return 结束
方法执行,用于没有返回值函数的方法。return 一个特定值,用于有返回
值函数的方法
数组初始化的几种形式有哪些?
- 静态初始化 初始化时由程序员显式指定每个数组元素的初始值,由系统
决定数组长度1
2
3
4int[] array;
array=new int[]{5,6,7,8};
//或者简写如下
array={5,6,7,8}; - 动态初始化 初始化时程序员只指定数组长度,由系统为数组元素分配初始值
1
int[] array=new int[4];
foreach是什么?
Java5之后提供了一种比for更简洁的循环,这种循环遍历数组和集合
1 | for(type variablename:array|collection) |
一个类中的成员
类中有三个最常见的成员:构造器、成员变量和方法,static修饰的成员不能
访问没有static修饰的成员
- 构造器 如果不显示创建构造器系统会默认提供一个构造器,不能重写
1
2
3
4
5
6//修饰符 可以省略或者是public protected private其中之一
//构造器名 必须与类名相同
[修饰符] 构造器名(形参列表)
{
//可执行语句
} - 成员变量 包括基本类型和引用类型
1
2
3//修饰符 可以省略,也可以是 public protected private static final
//其中public protected private只能出现一个,可以与 static final组合
[修饰符] 类型 成员变量名 [=默认值]; - 方法 返回值类型可以是任意类型,包括基本类型和引用类型,也可以是void
1
2
3
4
5
6//修饰符可以省略,也可以是public/protected/private static final/abstract
//public protected private只能出现一个,final和abstract只能出现一个,可与static组合
[修饰符] 方法返回值类型 方法名(形参列表)
{
//可执行语句
}
成员变量和局部变量的区别
- 从语法形式上看,成员变量是属于类的,而局部变量是在代码块或方法中
定义的变量或是方法的参数;局部变量不能被访问控制修饰符及static所修
饰;但是成员变量和局部变量都能被final 所修饰 - 从变量在内存中的存储方式来看,如果成员变量是使用static 修饰的,那
么这个成员变量是属于类的,如果没有使用static 修饰,这个成员变量是属
于实例的。而对象存在于堆内存,局部变量则存在于栈内存 - 从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象
的创建而存在,而局部变量随着方法的调用而自动消失 - 从变量是否有默认值来看,成员变量如果没有被赋初,则会自动以类型的默
认值而赋值(一种情况例外:被 final 修饰的成员变量也必须显式地赋值),
而局部变量则不会自动赋值
this的作用
实例变量和非静态方法必须通过对象来引用。Java提供了一个this关键字,this关
键字总是指向调用该方法的对象,在大部分时候,一个方法访问该类中定义的其他
方法、成员变量时加不加this前缀的效果是完全一样的。static定义的方法中不能
使用this引用,因此Java语法规定静态成员不能访问非静态成员
静态方法和实例方法的区别
- 调用静态方法可以无需创建对象。
- 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态
方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制。因为静态
方法是属于类的,在类加载的时候就会分配内存,可以通过类名直接访问。而非静
态成员属于实例对象,只有在对象实例化之后才存在,然后通过类的实例对象去
访问。在类的非静态成员不存在的时候静态成员就已经存在了,此时调用在内存
中还不存在的非静态成员,属于非法操作
访问修饰符有哪些?区别是什么?
private default protected public
- private 在同一类内可见。使用对象:变量、方法和构造器。 注意:不能修
饰类(外部类),用于修饰成员变量最合适,使用它来修饰成员变量就可以
把成员变量隐藏在该类的内部 - default 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、
方法和构造器。default访问控制的成员或外部类可以被相同包下的其他类访问 - protected 对同一包内的类和所有子类可见。使用对象:变量、方法和构造
器。注意:不能修饰类(外部类)。 这个成员既可以被同一个包中的其他类
访问,也可以被不同包中的子类访问,如果使用protected 来修饰一个方
法,通常是希望其子类来重写这个方法。注意不同包下对孙类不可见 - public 对所有类可见。使用对象:类、接口、变量、方法
&和&&的区别是什么?
&&运算符是短路与运算。逻辑与跟短路与的差别是非常巨大的,虽然二者都要求运
算符左右两端的布尔值都是true 整个表达式的值才是 true。&&之所以称为短路
运算,是因为如果&&左边的表达式的值是 false,右边的表达式会被直接短路
掉,不会进行运算
类的继承关系
每个子类只有一个直接父类,子类可以获得父类的全部成员变量和方法,但是不能
获得父类的构造器。如果定义一个类的时候没有指定这个类的直接父类,这个类默
认扩展java.lang.Object类
- 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类
中的私有属性和方法子类是无法访问,只是拥有 - 子类可以拥有自己属性和方法,即子类可以对父类进行扩展
- 子类可以用自己的方式实现父类的方法
重写父类方法时注意什么?
方法的重写遵循“两同两小一大”规则。两同指方法名相同、形参列表相同。两小指子
类方法的返回值类型比父类返回值类型更小或相同、子类方法抛出的异常比父类抛
出的异常更小或相同。一大指子类方法的访问权限比父类更大或相同并且覆盖方法
和被覆盖方法必须都是类方法或者实例方法。子类的对象无法调用父类被重写的方
法,只能在子类方法中通过super或类来调用父类方法,如果父类中有一个方法是
private访问权限,那么子类就不算是重写没有限制
final
用于修饰类、属性和方法
- 被final修饰的类不可以被继承,final类中的所有成员方法都会被隐式的指定
为final方法 - 被final修饰的方法不可以被重写,如果再加上一个private就说明不是方法
重写而相当于重新定义一个方法,因为private 修饰的方法只能在当前类可见
,所以不存在重写pirvate方法 - final修饰的变量是常量,如果是基本数据类型的变量,则其数值一旦在初始化
之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能让其指向另
一个对象
system.exit(1) 和 system.exit(0)?
- 为0时为正常退出程序,也就是结束当前正在运行中的java虚拟机
- 为非0的其他整数,表示非正常退出当前程序
final finally finalize的区别是什么?
- final 可以修饰类、变量、方法,修饰类表示类不能被继承、修饰方法表示
该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值,修饰
的变量必须初始化 - finally 作为异常处理的一部分,它只能在 try/catch 语句中,通常我们
将一定要执行的代码方法finally 代码块中,表示不管是否出现异常,该代码
块都会执行,一般用来存放一些关闭资源的代码。System.exit(0) 可以阻断
finally 执行 - finalize 是一个方法,属于Object类的一个方法,而Object类是所有类
的父类,该方法一般由垃圾回收器来调用,当我们调用System.gc()方法的时
候,由垃圾回收器调用finalize()回收垃圾,是一个对象是否可回收的最后
判断
super的用法是什么?
super用于限定该对象调用它从父类继承到的实例变量和方法,创建子类的时候也
会为父类的变量分配空间,可以通过super.调用。不管是否使用super调用来执行
父类构造器的初始化代码,子类总是会调用父类构造器一次
super和this的区别是什么?
- super()在子类中调用父类的构造方法,this()在本类内调用本类的其它构
造方法 - 从本质上讲,this是一个指向本对象的指针, 然而super是一个Java关键字
static 的作用
- static的主要意义是在于创建独立于具体对象的域变量或者方法。以致于即
使没有创建对象,也能使用属性和调用方法 - static关键字还有一个比较关键的作用就是 用来形成静态代码块以优化程
序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类
初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会
执行一次,创建对象时先执行初始化块后执行构造器 - 为什么说static块可以用来优化程序性能,是因为它的特性:只会在类加
载的时候执行一次。因此很多时候会将一些只需要进行一次的初始化操作都
放在static代码块中进行 - static 变量在Java中是属于类的,它在所有的实例中的值是一样的。当类
被Java虚拟机载入的时候,会对static变量进行初始化。如果你的代码尝试不
用实例来访问非static的变量,编译器会报错,因为这些变量还没有被创建出
来,还没有跟任何实例关联上
Java静态变量、代码块、和静态方法的执行顺序是什么?
- 代码块执行顺序 静态代码块——> 构造代码块 ——> 构造函数
- 继承中代码块执行顺序:父类静态块——>子类静态块——>父类代码块
——>父类构造器——>子类代码块——>子类构造器
多态
多态分为两种
- 编译时多态(又称静态多态)重载(overload)就是编译时多态的一个例子
,编译时多态在编译时就已经确定,运行的时候调用的是确定的方法 - 运行时多态(又称动态多态)Java引用变量有两个类型,一个是编译时类
型,另一个是运行时类型,编译时类型由声明该变量时使用的类型决定,运
行时类型由实际赋给该变量的对象决定,如果编译类型和运行类型不一致就
可能出现多态。Java 允许把一个子类对象直接赋给一个父类引用变量,当
调用引用变量的方法时总是表现子类方法的特征,但是不能调用子类独有
的方法。与方法相反,对象的实例变量不具备多态性,只能访问编译时定
义的成员变量
多态的实现原则是什么?
Java实现多态有三个必要条件:继承、重写、向上转型
- 继承:在多态中必须存在有继承关系的子类和父类
- 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用
子类的方法 - 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才
能够具备技能调用父类的方法和子类的方法
引用变量只能使用编译时类型的方法,如果要使用运行时类型的方法,需要进行强
制类型转换,引用类型之间的转换只能在具有继承关系的两个类之间
instanceof运算符的用法?
instance运算符的前一个操作数通常是一个引用类型变量,后一个操作数通常是一
个类或者接口,用于判断前面的对象是否是后面的类或者其子类需要注意前一个操
作数的编译类型要么相同要么与后面的类具有父子继承关系否则编译错误
自动装箱和拆箱
Java为8种基本数据类型分别定义了相应的引用类型,并称之为基本数据类
型的包装类,JDK提供了自动装箱和自动拆箱功能
- 自动装箱 把一个基本数据类型直接赋给相应的包装类变量
- 自动拆箱 把一个包装类变量直接赋给一个对于的基本数据类型
包装类还可以实现基本类型变量和字符串之间的转换,把字符串类型转换为
基本类型有两种方式
- parseXxx(String s) 静态方法
- valueOf(String s) 静态方法
基本类型和包装类型的区别
- 包装类型可以为null,而基本类型不可以
- 包装类型可用于泛型,而基本类型不可以。因为泛型在编译时会进行类型擦
除,最后只保留原始类型,而原始类型只能是 Object 类及其子类 - 基本类型比包装类型更高效,占用空间更少
自动装箱和拆箱的本质
在装箱的时候自动调用的是Integer的valueOf()方法,在拆箱的时候自动调
用的是Integer的intValue()方法,注意valueOf方法本质是new Integer。
Byte,Short,Integer,Long 这 4 种包装类默认创建了数值 [-128,127]
的相应类型的缓存数据,Character 创建了数值在[0,127]范围的缓存数据
,Boolean 直接返回 True Or False。
对于Integer==int类似的比较,即两个操作数中有一个是int的情况, 比较的
是数值是否相等(即Integer类型的那个对象会触发自动拆箱的过程)
1 | public static void main(String[] args) { |
抽象类是什么?
抽象方法和抽象类必须用abstract修饰,有抽象方法的类只能被定义为抽象
类,抽象类里可以没有抽象方法,提供方法但是不提供具体实现,这就是抽
象方法。抽象类是从多个具体类中抽象出来的父类,具有更高的抽象层次,
抽象类就相当于子类的模板,体现一种模板模式的设计
抽象类的特点有哪些?
- 抽象类不能实例化,无法使用new关键字来调用抽象类的构造器,但是可
以被子类继承 - 抽象类可以包含成员变量、方法(普通方法和抽象方法)、构造器、初始化
块,内部类等。构造器是由子类调用而不是创建实例 - 包含抽象方法的类(直接定义一个抽象方法、继承一个抽象类但没有完全
实现父类包含的抽象方法、实现一个接口但没有完全实现接口中的方法)只
能被定义为抽象类 - static和abstract不能修饰一个方法,但是可以修饰一个内部类
- final和abstract不能同时使用,private和abstract也不能同时使用
抽象类能使用final修饰吗?
不能,定义抽象类就是让其他类继承的,如果定义为final该类就不能被继
承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类
接口是什么?有哪些特点?
接口实际上是一种更为特殊的抽象类,只提供实现类需要实现的方法。Java9对接口
进行了改进,允许接口定义默认方法和类方法,并且都可以提供具体实现,还增加
了私有方法,也可以提供具体实现
- 接口的修饰符可以是public或者省略,如果省略就是默认包访问权限
- 一个接口可以有多个父接口,只能继承接口不能继承类
- 接口中不能有构造器和初始化块
- 接口里的成员变量只能是静态变量
- 接口定义的是多个类共同的行为规范,所以接口里的常量、方法、内部类
和内部枚举都是public访问权限,定义接口成员时可以省略访问修饰符,
如果显示指定只能是public - 接口里的方法只能是抽象方法、默认方法、类方法或私有方法,如果不是默
认类或私有方法,系统会自动为普通方法增加abstract修饰符,所以普通方
法总是使用 public abstract 修饰,其他方法必须有方法体 - 接口里的内部类、内部接口和内部枚举默认使用 public static 修饰
如何使用接口?
接口不能创建实例,但是接口可以声明为引用类型变量,这个引用类型变量必须
引用到其实现类的对象,一个类可以实现多个接口,必须实现接口中的所以抽象
方法,否则就会定义为抽象类,一个类可以继承父类并实现多个接口,extends
必须放在implements之前
抽象类和接口的区别?
- 语法层面上的区别
- 抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract
方法 - 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public
static final类型的 - 接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静
态方法 - 一个类只能继承一个抽象类,而一个类却可以实现多个接口
- 设计层面上的区别
- 抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类
是对整个类整体进行抽象,包括属性行为,但是接口却是对类局部(行为)
进行抽象 - 设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口
是一种行为规范,它是一种辐射式设计
什么是内部类?
在某些情况下把一个类放在另一个类的内部定义,这个定义在其他类的内部的类就
是内部类,包含内部类的类也称为外部类。内部类可以分为四种:成员内部类、局
部内部类、匿名内部类和静态内部类
- 静态内部类 定义在类内部的静态类,就是静态内部类,可以包括静态和非
静态成员,静态内部类可以访问外部类所有的静态变量,而不可访问外部类
的非静态变量 - 成员内部类 定义在类内部,成员位置上的非静态类,就是成员内部类,成
员内部类可以访问外部类所有的变量和方法,包括静态和非静态,私有和公
有。成员内部类依赖于外部类的实例 - 局部内部类 定义在方法中的内部类,就是局部内部类,定义在实例方法中
的局部类可以访问外部类的所有变量和方法,定义在静态方法中的局部类只能
访问外部类的静态变量和方法 - 匿名内部类 匿名内部类就是没有名字的内部类,适合创建那种只需要使用一
次的类
- 匿名内部类必须继承一个抽象类或者实现一个接口
- 匿名内部类不能定义任何静态成员和静态方法。
- 当所在的方法的形参需要被匿名内部类使用时,必须声明为final
- 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽
象方法
内部类的优点有哪些?
- 一个内部类对象可以访问创建它的外部类对象的内容,包括私有数据
- 内部类不为同一包的其他类所见,具有很好的封装性
- 内部类有效实现了“多重继承”,优化 java 单继承的缺陷
- 匿名内部类可以很方便的定义回调
内部类有哪些应用场景?
- 一些多算法场合
- 解决一些非面向对象的语句块
- 适当使用内部类,使得代码更加灵活和富有扩展
- 当某个类除了它的外部类,不再被其他的类使用时
Lambda表达式的作用?
Lambda表达式支持将代码块作为方法参数,Lambda允许使用更简洁的代码来
创建只有一个抽象方法的接口(这种接口被称为函数式接口)实例
- 形参列表允许省略形参类型,如果形参只有一个参数那么圆括号也可以省略
- 箭头 ->
- 如果方法体中只有一条返回语句则可以省略花括号,return也可以省略,会
自动返回这条语句的值
Lambda表达式的目标类型必须是函数式接口,一个函数式接口代表只含一个抽
象方法的接口,可以包含多个默认方法和类方法,但是只能有一个抽象方法
内部类访问局部变量的时候,为什么变量必须要加上final?
是因为生命周期不一致,局部变量直接存储在栈中,当方法执行结束后,非
final的局部变量就被销毁。而局部内部类对局部变量的引用依然存在,如
果局部内部类要调用局部变量时,就会出错。加了final,可以确保局部内
部类使用的变量与外层的局部变量区分开,解决了这个问题
重写与重载的区别
- 重载:发生在同一个类中,方法名相同参数列表不同(参数类型不同、个
数不同、顺序不同),与方法返回值和访问修饰符无关,即重载的方法不能
根据返回类型进行区分。方法声明的两个组件构成了方法签名,方法的名称
和参数类型 - 重写:发生在父子类中,方法名、参数列表必须相同,返回值小于等于父
类,抛出的异常小于等于父类,访问修饰符大于等于父类(里氏代换原则)
,如果父类方法访问修饰符为private则子类中就不是重写,构造方法无法
被重写
==和equals的区别
- == 它的作用是判断两个对象的地址是不是相等。即判断两个对象是
不是同一个对象。(基本数据类型==比较的是值,引用数据类型==比较
的是内存地址) - equals() 它的作用也是判断两个对象是否相等。但它一般有两种使用
情况,在Java中null == null将返回true
- 没有覆盖equals()方法。比较该类的两个对象时,等价于通过==比较这
两个对象 - 类覆盖了equals()方法。一般我们都覆盖equals()方法来判断两个对象
的内容相等,若它们的内容相等,则返回true
equals 的特性
- 自反性。对于任意不为null的引用值x,x.equals(x)一定是true
- 对称性。对于任意不为null的引用值x和y,当且仅当x.equals(y) 是
true时,y.equals(x)也是true - 传递性。对于任意不为null的引用值x、y和z,如果x.equals(y)是true
,同时y.equals(z)是true,那么x.equals(z)一定是true - 一致性。对于任意不为null的引用值x和y,如果用于equals比较的对象
信息没有被修改的话,多次调用时x.equals(y)要么一致地返回true 要么
一致地返回false - 对于任意不为null的引用值x,x.equals(null)返回false
什么是hashCode()
hashCode() 的作用是获取哈希码,也称为散列码,它实际上是返回一个int
整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode()定
义在JDK的Object中,这就意味着Java中的任何类都包含有hashCode()函数
Object 的hashcode 方法是本地方法,也就是用c 语言或c++ 实现的,该
方法通常用来将对象的内存地址转换为整数之后返回
hashCode()的作用
散列表的本质是通过数组实现的。当我们要获取散列表中的某个“值”时,实际
上是要获取数组中的某个位置的元素。而数组的位置,就是通过“键”来获取的
;更进一步说,数组的位置,是通过“键”对应的散列码计算得到的。若两个元
素相等,它们的散列码一定相等;但反过来确不一定。首先通过hashCode 比
较对象是否相同,如果hashCode相同再通过equals比较,减少了equals 的
次数
深拷贝和浅拷贝的区别
- 浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的
拷贝,此为浅拷贝。 - 深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对
象,并复制其内容,此为深拷贝
如果想要深拷贝一个对象,这个对象必须要实现Cloneable 接口,实现clone
方法,并且在clone方法内部,把该对象引用的其他对象也要clone一份,这就
要求这个被引用的对象必须也要实现Cloneable接口并且实现clone方法。
如果在拷贝一个对象时,要想让这个拷贝的对象和源对象完全彼此独立,那么
在引用链上的每一级对象都要被显式的拷贝。所以 创建彻底的深拷贝是非常
麻烦的,尤其是在引用关系非常复杂的情况下, 或者在引用链的某一级上引
用了一个第三方的对象, 而这个对象没有实现clone方法, 那么在它之后
的所有引用的对象都是被共享的。String是不可变对象,保存在常量池中,
也算是深拷贝。
序列化和反序列化
如果我们需要持久化Java 对象比如将Java 对象保存在文件中,或者在网络
传输Java 对象,这些场景都需要用到序列化
- 把对象转换为字节序列的过程称为对象的序列化,以便在网络上传输或者
保存在本地文件中。核心作用是对象状态的保存与重建 - 把字节序列恢复为对象的过程称为对象的反序列化,客户端从文件中或网
络上获得序列化后的对象字节流,根据字节流中所保存的对象状态及描述信
息,通过反序列化重建对象
只有实现了Serializable和Externalizable接口的类的对象才能被序列化。
Externalizable接口继承自 Serializable接口,实现Externalizable接
口的类完全由自身来控制序列化的行为,而仅实现Serializable接口的类可
以采用默认的序列化方式
什么是serialVersionUID?
serialVersionUID 用来表明类的不同版本间的兼容性。
Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一
致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与
本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致
的,可以进行反序列化,否则就会出现序列化版本不一致的异常
为什么还要显示指定serialVersionUID的值?
- 如果不显示指定serialVersionUID,JVM 在序列化时会根据属性自动生成
一个serialVersionUID,然后与属性一起序列化,再进行持久化或网络传输。
在反序列化时, JVM会再根据属性自动生成一个新版serialVersionUID ,然
后将这个新版serialVersionUID与序列化时生成的旧版serialVersionUID
进行比较, 如果相同则反序列化成功,否则报错 - 如果显示指定,在序列化和反序列化时仍然都会生成serialVersionUID
,但值为显示指定的值,这样在反序列化时新旧版本的serialVersionUID
就一致了
serialVersionUID的作用?
在实际开发中是不可能的,我们的类会不断迭代,一旦类被修改了,那旧对象
反序列化就会报错。所以在实际开发中,我们都会显示指定,值是多少无所谓
,只要不变就行。如果要在序列化后添加或减少一个字段或者方法,不会影响
还原。序列化时,并不保存静态变量,这其实比较容易理解,序列化保存的是
对象的状态,静态变量属于类的状态,因此序列化并不保存静态变量
serialVersionUID什么时候修改?
如果完全不兼容升级可以修改,但是会抛出序列化运行时异常
防止序列化
- 对于不想进行序列化的变量,使用transient关键字修饰。transient关
键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该
变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初
始值。transient 只能修饰变量,不能修饰类和方法 - 静态变量不会被序列化 serialVersionUID也被static修饰,在序列
化对象时会自动生成一个 serialVersionUID ,然后将我们显示指定的
serialVersionUID属性值赋给自动生成的serialVersionUID
序列化的作用是什么?
- 对象序列化可以实现分布式对象。RMI(即远程调用)要利用对象序列化运
行远程主机上的服务,就像在本地机上运行对象时一样 - java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个
对象的数据,可以进行对象的”深复制”,即复制对象本身及引用的对象本身 - 序列化可以将内存中的类写入文件或数据库中
- 对象、文件、数据,有许多不同的格式,很难统一传输和保存,序列化以后
就都是字节流了
序列化的过程?
- 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流
- 通过对象输出流的writeObject()方法写对象
1 | ObjectOutputStream oo = new ObjectOutputStream( |
反序列化的过程?
- 创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流
- 通过对象输入流的readObject()方法读取对象
1 | ObjectInputStream ois = new ObjectInputStream(new FileInputStream( |
Java中泛型
Java 泛型是JDK5中引入的一个新特性, 泛型提供了编译时类型安全检测机制,
该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就
是说所操作的数据类型被指定为一个参数,在使用/调用时传入具体的类型(类
型实参)
泛型的三种使用方式
泛型一般有三种使用方式:泛型类、泛型接口、泛型方法
- 泛型类
1
2
3
4
5
6
7
8
9
10//在实例化泛型类时,必须指定T的具体类型
public class Generic<T> {
private T key;
public Generic(T key) {
this.key = key;
}
public T getKey() {
return key;
}
} - 泛型接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public interface Generator<T> {
public T method();
}
//实现泛型接口,不指定类型:
class GeneratorImpl<T> implements Generator<T>{
public T method() {
return null;
}
}
//实现泛型接口,指定类型:
class GeneratorImpl implements Generator<String>{
public String method() {
return "hello";
}
} - 泛型方法
1
2
3
4
5public static <E> void printArray(E[] inputArray) {
for (E element : inputArray) {
System.out.printf("%s ", element);
}
}
类型擦除
Java 的泛型是伪泛型,这是因为 Java 在编译期间,所有的泛型信息都会被
擦掉,当你在使用泛型时,任何具体的类型信息都被擦除了,这也就是通常所
说类型擦除。Java 没有C++ 那样的实例模板
- 大部分情况下,泛型类型都会以 Object 进行替换
- 使用到了extends和super语法的有界类型 这种情况的泛型类型,num会被
替换为String而不再是Object。这是一个类型限定的语法,它限定T是String
或者 String 的子类,也就是你构建 Caculate 实例的时候只能限定 T 为
String 或者String 的子类,所以无论你限定T 为什么类型,String都是
父类,不会出现类型不匹配的问题,于是可以使用 String 进行类型擦除
类型擦除的好处
- 如果JVM 将泛型类型延续到运行期,那么到运行期时JVM 就需要进行大量
的重构工作了,提高了运行期的效率 - 版本兼容。在编译期擦除可以更好地支持原生类型
泛型的好处
- 如果使用 Object 来实现通用、不同类型的处理,有这么两个缺点:
- 每次使用时都需要强制转换成想要的类型
- 在编译时编译器并不知道类型转换是否正常,运行时才知道,不安全
- 类型安全 编译时期就可以检查出因Java 类型不正确导致的类型转换异常
- 消除强制类型转换
- 潜在的性能收益 由于泛型的实现方式,支持泛型(几乎)不需要JVM 或
类文件更改,所有工作都在编译器中完成,编译器生成的代码跟不使用泛型
(和强制类型转换)时所写的代码几乎一致,只是更能确保类型安全而已 - 节省一些类型所占的内存空间
泛型翻译
编译器不仅关注一个泛型方法的调用,它还会为某些返回值为限定的泛型类型
的方法进行强制类型转换,由于类型擦除,返回值为泛型类型的方法都会擦除
成 Object 类型,当这些方法被调用后,编译器会额外插入一行 checkcast
指令用于强制类型转换。这一个过程就叫做泛型翻译
通配符
- 限定通配符对类型进行限制。有两种限定通配符,一种是<? extends T>
它通过确保类型必须是T 的子类来设定类型的上界,另一种是<? super T>
它通过确保类型必须是T 的父类来设定类型的下界。泛型类型必须用限定内
的类型来进行初始化,否则会导致编译错误 - 非限定通配符 ?,可以用任意类型来替代。如List<?> 的意思是这个集合
是一个可以持有任意类型的集合,它可以是List<A>,也可以是List<B>
,或者List<C> 等等 - 不可以把List<String>传递给一个接受List<Object>参数的方法。
这样做的话会导致编译错误。因为List<Object>可以存储任何类型的对
象包括String Integer等等,而List<String>只能用来存储String - ArrayList<String>与ArrayList<Integer>相等
Class类型都是一致的,都是ArrayList.class。差别体现在类编译的时候。
当 JVM 进行类编译时,会进行泛型检查,如果一个集合被声明为 String
类型,那么它往该集合存取数据的时候就会对数据进行判断,从而避免存
入或取出错误的数据。Array不可以使用泛型
Java 创建对象有哪几种方式?
java中提供了以下四种创建对象的方式:
- new创建新对象
- 通过反射机制
- 采用clone机制
- 通过序列化机制
前两者都需要显式地调用构造方法。对于clone机制,需要注意浅拷贝和深拷贝的区
别,对于序列化机制需要通过实现Externalizable或者Serializable来实现
什么是不可变对象?
不可变对象指对象一旦被创建,状态就不能再改变,任何修改都会创建一个新的
对象,如String、Integer 及其它包装类.不可变对象最大的好处是线程安全
能否创建一个包含可变对象的不可变对象?
可以,比如final Person[] persons = new Persion[]{}。persons是不可变
对象的引用,但其数组中的Person实例却是可变的
什么是反射机制
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性
和方法,对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获
取的信息以及动态调用对象的方法的功能称为java语言的反射机制
反射机制的优缺点
- 优点 能够运行时动态获取类的实例,提高灵活性,可与动态编译结合,
加载MySQL的驱动类 - 缺点 使用反射性能较低,需要解析字节码,将内存中的对象进行解析。
其解决方案是:通过setAccessible(true)关闭JDK的安全检查来提升反
射速度,多次创建一个类的实例时,有缓存会快很多,ReflflectASM 工
具类,通过字节码生成的方式加快反射速度。增加了安全问题。比如可以无
视泛型参数的安全检查(泛型参数的安全检查发生在编译时)
反射机制的应用场景
- 我们在使用JDBC连接数据库时使用Class.forName()通过反射加载数据库的
驱动程序,通过 DriverManager 类进行数据库的连接,通过Connection 接
口接收连接 - Spring框架也用到很多反射机制,最经典的就是xml的配置模式。Spring通
过XML配置模式装载 Bean 的过程:
- 将程序内所有 XML 或 Properties 配置文件加载入内存中
- Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符
串以及相关的属性信息 - 使用反射机制,根据这个字符串获得某个类的Class实例
- 动态配置实例的属性
Spring这样做的好处是
- 不用每一次都要在代码里面去new或者做其他的事情
- 以后要改的话直接改配置文件,代码维护起来就很方便了
- 有时为了适应某些需求,Java类里面不一定能直接调用另外的方法,可以
通过反射机制来实现
类的加载过程是什么样的?
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过加
载、连接和初始化三个步骤对该类进行初始化。类的class文件读入内存后,
会在内存中创建一个java.lang.Class对象,类实际上也是一种对象,都是
java.lang.Class的实例
类的初始化过程?
对类变量进行初始化,声明时指定和静态初始化块都会变为类初始化语句,会按照
顺序执行初始化语句。初始化一个类的步骤如下
- 如果这个类还没有加载连接就先加载连接
- 如果该类的直接父类还没有初始化就先初始化直接父类
- 如果类中有初始化语句则依次执行
类初始化的时机有哪几种?
通过以下6种方式使用类或接口时就会进行初始化,注意使用final的时候不会导
致初始化,final修饰的在编译时就已经确定的变量不会初始化,类的loadClass
方法加载类时只是加载,也不会导致初始化
- 创建类的实例 为某个类创建实例的方式包括:使用new操作符来创建实例,
通过反射来创建实例,通过反序列化的方式来创建实例 - 调用某个类的类方法(静态方法)
- 访问某个类或接口的类变量,或为该类变量赋值
- 使用反射方式来强制创建某个类或接口对应的java.lang.C1ass对象 例如代
码C1ass.forName(“Person”) ,如果系统还未初始化Person类,则这行代码
将会导致该Person类被初始化并返回Person类对应的java.lang.C1ass对象 - 初始化某个类的子类,当初始化某个类的子类时,该子类的所有父类都会被
初始化 - 直接使用java.exe命令来运行某个主类,当运行某个主类时,程序会先初始化
该主类
Java获取Class对象的三种方法?
- 对象名.getClass()
- Class.forName(“类的路径”),当你知道该类的全路径名时,你可以使
用该方法获取 Class 类对象 - 类名.class。这种方法只适合在编译前就知道操作的Class
- 如果是基本类型的包装类,可以调用包装类的Type属性来获得该包装
类的Class对象
Java反射API有几类?
反射API 用来生成 JVM 中的类、接口或则对象的信息
- Class 类:反射的核心类,可以获取类的属性,方法等信息
- Field 类:Java.lang.reflec 包中的类,表示类的成员变量,可
以用来获取和设置类之中的属性值 - Method 类:Java.lang.reflect包中的类,表示类的方法,它可以用
来获取类中的方法信息或者执行方法 - Constructor 类:Java.lang.reflec 包中的类,表示类的构造方法
反射的步骤?
- 获取想要操作的类的Class对象,这是反射的核心,通过Class对象我们可
以任意调用类的方法 - 调用 Class 类中的方法,既就是反射的使用阶段。
- 使用反射 API 来操作这些信息
如何从Class中获取构造器有几种方式?
- getConstructor() 返回此Class对象对应的带指定参数的public构造器
- getConstructors() 返回所有public构造器
- getDeclaredConstructor() 返回带指定参数的构造器,与访问权限无关
- getDeclaredConstructors() 返回所有构造器
1 | Class[] p={int.class,String.class}; |
如何通过反射操作对象?
- 获取类的Class 对象实例
- 根据Class对象实例获取Constructor对象,传入构造参数是class类型
- 使用Constructor 对象的newInstance 方法获取反射类对象,传入构
造器参数,返回的是一个Object类型 - 通过class对象获取方法的Method 对象,传入参数是方法名和参数类型
,类型是class类型 - 通过Method对象调用invoke 方法调用方法传入Object实例和方法参数
- 通过Class对象的getFields() 或getField() 方法可以获得该类所有
成员,然后通过get和set来操作变量的值,参数传入对象实例和设置值
- getXxx(obj) 获取成员变量的值,Xxx对应8种基本数据类型,如果是引用类型
则不需要Xxx - setXxx(Object obj,Xxx value) 设置成员变量值,Xxx对应8种基本数据类型
,如果是引用类型则不需要Xxx
反射机制的原理是什么?
- 反射获取类实例Class.forName(),先获取 ClassLoader,然后调用native
方法,获取信息,加载类则是回调 java.lang.ClassLoader。最后,jvm又会回
调ClassLoader 进类加载 - newInstance() 调用具体方法的无参构造方法,生成实例并返回
- 获取Method对象 JVM 为每个类管理一个独一无二的Class对象,这份Class
对象里维护着该类的所有Method,Field,Constructor 的cache ,这份
cache也可以被称作根对象。每次getMethod获取到的Method对象都持有对
根对象的引用,每一次创建都会调用根对象的copy方法复制一份 - 调用invoke()方法 调用Method.invoke之后,会直接去调MethodAccessor
.invoke。MethodAccessor就是上面提到的所有同名method共享的一个实例
,由ReflectionFactory创建
异常
异常机制就是当程序出现错误的时候的处理机制。异常和错误的区别就是异常
是可以被处理的,错误一般是无法处理的。异常可以分为运行时异常和编译异
常
- 运行时异常 可以通过编译不能通过运行,如NullPointerException、
IndexOutOfBoundsException - 编译异常 从语法角度必须进行相应的强制处理异常否则不能通过编译,如
IOException、SQLException
Error 和 Exception 区别
Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable
类。Throwable 类有两个重要的子类Exception(异常)和Error(错误)
- Exception :程序本身可以处理的异常,可以通过catch来进行捕获,
通常遇到这种错误,应对其进行处理,使应用程序可以继续正常运行。
Exception又可以分为运行时异常(RuntimeException, 又叫非受检
查异常)和非运行时异常(又叫受检查异常) - Error :Error 属于程序无法处理的错误 ,我们没办法通过 catch 来
进行捕获 。例如,系统崩溃,内存不足,堆栈溢出等,编译器不会对这类错
误进行检测,一旦这类错误发生,通常应用程序会被终止,仅靠应用程序本
身无法恢复。StackOverflowError OutOfMemoryError
运行时异常和受检查异常区别
- 运行时异常:包括RuntimeException 类及其子类,表示JVM 在运行
期间可能出现的异常。Java 编译器不会检查运行时异常。例如:(空指针)
、(字符串转换为数字)、(数组越界)、(类转换异常)、(数据存储异常,
操作数组时类型不一致)等 - 受检查异常:是Exception 中除RuntimeException 及其子类之外的
异常。Java 编译器会检查受检查异常。常见的受检查异常有:IO 相关的
异常、ClassNotFoundException 、SQLException等
非受检查异常和受检查异常之间的区别:是否强制要求调用者必须处理此异
常,如果强制要求调用者必须进行catch/throw 处理,那么就使用受检查
异常,否则就选择非受检查异常
Throwable 类常用方法
1 | public string getMessage()://返回异常发生时的简要描述 |
try-catch-finally
- try块: 用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch
块,则必须跟一个 finally 块 - catch块: 用于处理 try 捕获到的异常
- finally 块: 无论是否捕获或处理异常,finally 块里的语句都会被执行
。当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在方法
返回之前被执行
如何处理异常
将业务逻辑代码放在try块中,将错误处理逻辑放在catch块中处理
1 | //注意异常可以出现在任何地方,并不是在try块中才会产生异常 |
如果在try块中出现异常系统会自动生成一个异常,这个异常对象会提交给Java
运行环境,这个过程就叫做throw,也就是抛出异常。Java环境接收到异常后会
寻找能够处理异常的catch块,如果找到合适的catch块就会交给catch块来处
理,这个过程就叫做catch,也就是捕获异常。如果Java环境找不到捕获异常
的catch块,那么运行环境就会终止
finally
有些情况下系统打开了一些物理资源,比如数据库连接、网络连接和磁盘文件,
这些物理资源都必须显示回收,注意垃圾回收机制回收的是JVM内存中的空间。
接下来分析一下在哪里回收资源,如果在try中回收资源,那么程序运行到一
半可能产生异常,那么之后的代码就不会执行。如果在catch中回收资源,可
能程序不会产生异常那么catch块也就不会执行。所以异常处理机制提供了
finally块来回收资源,finally块一定会被执行
finally什么时候不执行
- 在 try 或 catch块中用了 System.exit(int)退出程序。但是,如果
System.exit(int) 在异常语句之后,finally 还是会被执行 - 程序所在的线程死亡。
- 关闭 CPU
throw 和throws 的区别是什么?
可以通过throws 关键字在方法上声明该方法要拋出的异常,或者在方法内
部通过throw 拋出异常对象
- throw 当程序出现错误时会自动抛出异常,此外程序也可以自行抛出异常
,自行抛出异常用throw完成,每次只能抛出一个异常实例,注意运行时异常
可以显示捕获也可以不理会,Checked异常必须显示捕获 - throws 用在方法声明上,用来声明一个方法可能产生的所有异常,调用
该方法的方法必须包含可处理异常的代码,否则也要在方法签名中用throws
关键字声明相应的异常
NoClassDefFoundError 和ClassNotFoundException 区别?
- NoClassDefFoundError 是一个 Error 类型的异常,是由JVM 引起的
,不应该尝试捕获这个异常。引起该异常的原因是 JVM 或ClassLoader 尝
试加载某类时在内存中找不到该类的定义,该动作发生在运行期间,即编译
时该类存在,但是在运行时却找不到了,可能是变异后被删除了等原因导致 - ClassNotFoundException 是一个受检查异常,需要显式地使用try-
catch 对其进行捕获和处理,或在方法签名中用 throws 关键字进行声明
。当使用 Class.forName, ClassLoader.loadClass 或 ClassLoader
.findSystemClass 动态加载类到内存的时候,通过传入的类路径参数没
有找到该类,就会抛出该异常,另一种抛出该异常的可能原因是某个类已
经由一个类加载器加载至内存中,另一个加载器又尝试去加载它
try-catch-finally 中哪个部分可以省略?
catch 可以省略。更为严格的说法其实是:try 只适合处理运行时异常,try
+catch 适合处理运行时异常+普通异常。也就是说,如果你只用try去处理普
通异常却不加以catch 处理,编译是通不过的,因为编译器硬性规定,普通
异常如果选择捕获,则必须用catch 显示声明以便进一步处理。而运行时异
常在编译时没有如此规定,所以catch可以省略,你加上catch编译器也觉
得无可厚非
catch中return了,finally还会执行吗
会执行,在return 前执行。
在finally 中改变返回值的做法是不好的,因为如果存在finally 代码块,
try中的return 语句不会立马返回调用者,而是记录下返回值待finally代
码块执行完毕之后再向调用者返回其值,然后如果在finally 中修改了返回
值,就会返回修改后的值。显然在finally 中返回或者修改返回值会对程序
造成很大的困扰,Java 中也可以通过提升编译器的语法检查级别来产生警
告或错误。如果finally中没有return语句那么catch返回的值有两种情况
- 基本数据类型 返回的值与finally中的修改不会改变返回值
- 引用类型 finally代码会改变返回值。本质是在catch中return的时候
会创建一个副本,根据数据类型判断是否把副本设置为修改的值
try-with-resources代替try-catch-finally
- 适用范围(资源的定义): 任何实现 java.lang.AutoCloseable或者
java.io.Closeable 的对象,可以在括号中声明多个资源 - 关闭资源和 finally 块的执行顺序: 在 try-with-resources 语句中
,任何 catch 或 finally 块在声明的资源关闭后运行
JVM 是如何处理异常的?
- 在一个方法中如果发生异常,这个方法创建一个异常对象,并转交给JVM
,该异常对象包含异常名称,异常描述以及异常发生时应用程序的状态。创
建异常对象并转交给JVM 的过程称为抛出异常。可能有一系列的方法调用,
最终才进入抛出异常的方法,这一系列方法调用的有序列表叫做调用栈 - JVM 会顺着调用栈去查找看是否有可以处理异常的代码,如果有则调用
异常处理代码。当 JVM 发现可以处理异常的代码时,会把发生的异常传递
给它。如果 JVM 没有找到可以处理该异常的代码块,JVM就会将该异常转
交给默认的异常处理器(默认处理器为JVM的一部分),默认异常处理器
打印出异常信息并终止应用程序
String StringBuffer StringBuilder的区别
- 可变与不可变 String类中使用字节数组保存字符串,因为有“final”修饰
符,所以string对象是不可变的。对于已经存在的String对象的修改都是重新
创建一个新的对象,然后把新的值保存进去。StringBuilder与StringBuffer
是可变的,没有使用final修饰字节数组 - 是否线程安全 String中的对象是不可变的,也就可以理解为常量,显然线
程安全。StringBuilder是非线程安全的。StringBuffer对方法加了同步锁,
所以是线程安全的 - 性能 每次对String 类型进行改变的时候,都会生成一个新的String 对象
,然后将指针指向新的String对象。StringBuffer 每次都会对StringBuffer
对象本身进行操作,而不是生成新的对象并改变对象引用
String为什么要设计成不可变的?
- 便于实现字符串池(String pool)在Java中,由于会大量的使用String
常量,如果每一次声明一个String 都创建一个String 对象,那将会造成极
大的空间资源的浪费。Java 提出了String pool的概念,在堆中开辟一块存
储空间String pool ,当初始化一个String 变量时,如果该字符串已经存
在了,就不会去创建一个新的字符串变量,而是会返回已经存在了的字符串
的引用。如果字符串是可变的,某一个字符串变量改变了其值,那么其指向
的变量的值也会改变,String pool将不能够实现 - 使多线程安全 在并发场景下,多个线程同时读一个资源,是安全的,不
会引发竞争,但对资源进行写操作时是不安全的,不可变对象不能被写,所
以保证了多线程的安全 - 避免安全问题 在网络连接和数据库连接中字符串常常作为参数,例如,
网络连接地址URL,文件路径path,反射机制所需要的String参数。其不可
变性可以保证连接的安全性。如果字符串是可变的,黑客就有可能改变字符
串指向对象的值,那么会引起很严重的安全问题 - 加快字符串处理速度 由于String是不可变的,保证了hashcode 的唯一
性,于是在创建对象时其hashcode就可以放心的缓存了,不需要重新计算。
这也就是Map喜欢将String作为Key的原因,处理速度要快过其它的键对象
。所以HashMap中的键往往都使用String
直接赋予一个字符串和new String一样吗?
- 使用String a = “aaa” ,程序运行时会在常量池中查找”aaa”字符串,
若没有,会将”aaa”字符串放进常量池,再将其地址赋给a,若有,将找到的
”aaa”字符串的地址赋给a - 使用String b = new String(“aaa”),程序会在堆内存中开辟一片新空
间存放新对象,同时会将”aaa”字符串放入常量池,相当于创建了两个对象,
无论常量池中有没有”aaa”字符串,程序都会在堆内存中开辟一片新空间存放
新对象
intern()函数的作用?
intern函数的作用是将对应的符号常量进入特殊处理
- JDK1.6 在JDK1.6中,intern的处理是 先判断字符串常量是否在字符串
常量池中,如果存在直接返回该常量,如果没有找到,则将该字符串常量加
入到字符串常量区,也就是在字符串常量区建立该常量 - JDK1.7 在JDK1.7中,intern的处理是 先判断字符串常量是否在字符串
常量池中,如果存在直接返回该常量,如果没有找到,说明该字符串常量在
堆中,则处理是把堆区该对象的引用加入到字符串常量池中,以后别人拿到
的是该字符串常量的引用,实际存在堆中
从用户输入密码到密码存储到登录密码校验的整个流程?
- 为什么不用https协议
- 如果用的https协议,就一定是安全的吗
- 写代码的人是知道明文密码后进行加密存储到数据库的,这个过程怎么保证安全性
Object 类的常见方法
1 | public final native Class<?> getClass() |