知识点复习4
总结一下常用知识点
Hibernate 的理解
- 面向对象设计的软件内部运行过程可以理解成就是在不断创建各种新对象、建立对象之间的关系, 调用对象的方法来改变各个对象的状态和对象消亡的过程, 不管程序运行的过程和操作怎么样, 本质上都是要得到一个结果, 程序上一个时刻和下一个时刻的运行结果的差异就表现在内存中的对象状态发生了变化.
- 为了在关机和内存空间不够的状况下, 保持程序的运行状态, 需要将内存中的对象状态保存到持久化设备和从持久化设备中恢复出对象的状态, 通常都是保存到关系数据库来保存大量对象信息. 从Java程序的运行功能上来讲, 保存对象状态的功能相比系统运行的其他功能来说, 应该是一个很不起眼的附属功能, java采用jdbc来实现这个功能, 这个不起眼的功能却要编写大量的代码, 而做的事情仅仅是保存对象和恢复对象, 并且那些大量的jdbc代码并没有什么技术含量, 基本上是采用一套例行公事的标准代码模板来编写, 是一种苦活和重复性的工作.
- 通过数据库保存java程序运行时产生的对象和恢复对象, 其实就是实现了java对象与关系数据库记录的映射关系, 称为ORM(即Object Relation Mapping), 人们可以通过封装JDBC代码来实现了这种功能, 封装出来的产品称之为ORM框架, Hibernate就是其中的一种流行ORM框架. 使用Hibernate框架, 不用写JDBC代码, 仅仅是调用一个save方法, 就可以将对象保存到关系数据库中, 仅仅是调用一个get方法, 就可以从数据库中加载出一个对象.
- 使用Hibernate的基本流程是: 配置Configuration对象、产生SessionFactory、创建session对象, 启动事务, 完成CRUD操作, 提交事务, 关闭session.
- 使用Hibernate时, 先要配置hibernate.cfg.xml文件, 其中配置数据库连接信息和方言等, 还要为每个实体配置相应的hbm.xml文件, hibernate.cfg.xml文件中需要登记每个hbm.xml文件.
- 在应用Hibernate时, 重点要了解Session的缓存原理, 级联, 延迟加载和hql查询.
Spring 的理解
Spring实现了工厂模式的工厂类(在这里有必要解释清楚什么是工厂模式), 这个类名为BeanFactory(实际上是一个接口), 在程序中通常BeanFactory的子类ApplicationContext. Spring相当于一个大的工厂类, 在其配置文件中通过
<bean>
元素配置用于创建实例对象的类名和实例对象的属性.Spring提供了对IOC良好支持, IOC是一种编程思想, 是一种架构艺术, 利用这种思想可以很好地实现模块之间的解耦. IOC也称为DI(Depency Injection), 什么叫依赖注入呢? 譬如,
Class Programmer { Computer computer = null; public void code() { //Computer computer = new IBMComputer(); //Computer computer = beanfacotry.getComputer(); computer.write(); } public void setComputer(Computer computer) { this.computer = computer; } } 另外两种方式都由依赖, 第一个直接依赖于目标类, 第二个把依赖转移到工厂上, 第三个彻底与目标和工厂解耦了. 在spring的配置文件中配置片段如下:
<bean id=”computer” class=”cn.itcast.interview.Computer”>
</bean>
<bean id=”programmer” class=”cn.itcast.interview.Programmer”>
<property name=”computer” ref=”computer”></property>
</bean>
2
3
4
5
- Spring提供了对AOP技术的良好封装, AOP称为面向切面编程, 就是系统中有很多各不相干的类的方法, 在这些众多方法中要加入某种系统功能的代码, 例如, 加入日志, 加入权限判断, 加入异常处理, 这种应用称为AOP. 实现AOP功能采用的是代理技术, 客户端程序不再调用目标, 而调用代理类, 代理类与目标类对外具有相同的方法声明, 有两种方式可以实现相同的方法声明, 一是实现相同的接口, 二是作为目标的子类在, JDK中采用Proxy类产生动态代理的方式为某个接口生成实现类, 如果要为某个类生成子类, 则可以用CGLI B. 在生成的代理类的方法中加入系统功能和调用目标类的相应方法, 系统功能的代理以Advice对象进行提供, 显然要创建出代理对象, 至少需要目标类和Advice类. spring提供了这种支持, 只需要在spring配置文件中配置这两个元素即可实现代理和aop功能, 例如,
<bean id=”proxy” type=”org.spring.framework.aop.ProxyBeanFactory”>
<property name=”target” ref=””></property>
<property name=”advisor” ref=””></property>
</bean>
2
3
4
iBatis 与 Hibernate 有什么不同?
相同点: 屏蔽jdbc api的底层访问细节, 使用我们不用与jdbc api打交道, 就可以访问数据. jdbc api编程流程固定, 还将sql语句与java代码混杂在了一起, 经常需要拼凑sql语句, 细节很繁琐. ibatis的好处: 屏蔽jdbc api的底层访问细节;将sql语句与java代码进行分离;提供了将结果集自动封装称为实体对象和对象的集合的功能, queryForList返回对象集合, 用queryForObject返回单个对象;提供了自动将实体对象的属性传递给sql语句的参数.
Hibernate是一个全自动的orm映射工具, 它可以自动生成sql语句,ibatis需要我们自己在xml配置文件中写sql语句, hibernate要比ibatis功能负责和强大很多. 因为hibernate自动生成sql语句, 我们无法控制该语句, 我们就无法去写特定的高效率的sql. 对于一些不太复杂的sql查询, hibernate可以很好帮我们完成, 但是, 对于特别复杂的查询, hibernate就很难适应了, 这时候用ibatis就是不错的选择, 因为ibatis还是由我们自己写sql语句.
final、finally 和 finalize 的区别是什么?
这是一道再经典不过的面试题了, 我们在各个公司的面试题中几乎都能看到它的身影. final、finally 和 finalize 虽然长得像孪生三兄弟一样, 但是它们的含义和用法却是大相径庭. 这一次我们就一起来回顾一下这方面的知识.
final 关键字
我们首先来说说 final. 它可以用于以下四个地方:
- 定义变量, 包括静态的和非静态的.
- 定义方法的参数.
- 定义方法.
- 定义类.
我们依次来回顾一下每种情况下 final 的作用. 首先来看第一种情况, 如果 final 修饰的是一个基本类型, 就表示这个变量被赋予的值是不可变 的, 即它是个常量;如果 final 修饰的是一个对象, 就表示这个变量被赋予的引用是不可变的, 这里需要提醒大家注意的是, 不可改变的只是这个变量所保存的 引用, 并不是这个引用所指向的对象. 在第二种情况下, final 的含义与第一种情况相同. 实际上对于前两种情况, 有一种更贴切的表述 final 的含义的描 述, 那就是, 如果一个变量或方法参数被 final 修饰, 就表示它只能被赋值一次, 但是 JAVA 虚拟机为变量设定的默认值不记作一次赋值.
被 final 修饰的变量必须被初始化. 初始化的方式有以下几种:
- 在定义的时候初始化.
- final 变量可以在初始化块中初始化, 不可以在静态初始化块中初始化.
- 静态 final 变量可以在静态初始化块中初始化, 不可以在初始化块中初始化.
- final 变量还可以在类的构造器中初始化, 但是静态 final 变量不可以.
通过下面的代码可以验证以上的观点:
public class FinalTest {
// 在定义时初始化
public final int A = 10;
public final int B;
// 在初始化块中初始化
{
B = 20;
}
// 非静态 final 变量不能在静态初始化块中初始化
// public final int C;
// static {
// C = 30;
// }
// 静态常量, 在定义时初始化
public static final int STATIC_D = 40;
public static final int STATIC_E;
// 静态常量, 在静态初始化块中初始化
static {
STATIC_E = 50;
}
// 静态变量不能在初始化块中初始化
// public static final int STATIC_F;
// {
// STATIC_F = 60;
// }
public final int G;
// 静态 final 变量不可以在构造器中初始化
// public static final int STATIC_H;
// 在构造器中初始化
public FinalTest() {
G = 70;
// 静态 final 变量不可以在构造器中初始化
// STATIC_H = 80;
// 给 final 的变量第二次赋值时, 编译会报错
// A = 99;
// STATIC_D = 99;
}
// final 变量未被初始化, 编译时就会报错
// public final int I;
// 静态 final 变量未被初始化, 编译时就会报错
// public static final int STATIC_J;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
我们运行上面的代码之后出了可以发现 final 变量(常量)和静态 final 变量(静态常量)未被初始化时, 编译会报错.
用 final 修饰的变量(常量)比非 final 的变量(普通变量)拥有更高的效率, 因此我们在实际编程中应该尽可能多的用常量来代替普通变量, 这也是一个很好的编程习惯.
当 final 用来定义一个方法时, 会有什么效果呢?正如大家所知, 它表示这个方法不可以被子类重写, 但是它这不影响它被子类继承. 我们写段代码来验证一下:
class ParentClass {
public final void TestFinal() {
System.out.println("父类 -- 这是一个 final 方法");
}
}
public class SubClass extends ParentClass {
/**
* 子类无法重写(override)父类的 final 方法, 否则编译时会报错
*/
// public void TestFinal() {
// System.out.println("子类 -- 重写 final 方法");
// }
public static void main(String[] args) {
SubClass sc = new SubClass();
sc.TestFinal();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
这里需要特殊说明的是, 具有 private 访问权限的方法也可以增加 final 修饰, 但是由于子类无法继承 private 方法, 因此也无法重写 它. 编译器在处理 private 方法时, 是按照 final 方法来对待的, 这样可以提高该方法被调用时的效率. 不过子类仍然可以定义同父类中的 private 方法具有同样结构的方法, 但是这并不会产生重写的效果, 而且它们之间也不存在必然联系.
最后我们再来回顾一下 final 用于类的情况. 这个大家应该也很熟悉了, 因为我们最常用的 String 类就是 final 的. 由于 final 类不允 许被继承, 编译器在处理时把它的所有方法都当作 final 的, 因此 final 类比普通类拥有更高的效率. final 的类的所有方法都不能被重写, 但这并不 表示 final 的类的属性(变量)值也是不可改变的, 要想做到 final 类的属性值不可改变, 必须给它增加 final 修饰, 请看下面的例子:
public final class FinalTest {
int i = 10;
public static void main(String[] args) {
FinalTest ft = new FinalTest();
ft.i = 99;
System.out.println(ft.i);
}
}
2
3
4
5
6
7
8
9
10
运行上面的代码试试看, 结果是 99, 而不是初始化时的 10.
finally 语句
接下来我们一起回顾一下 finally 的用法. 这个就比较简单了, 它只能用在 try/catch 语句中, 并且附带着一个语句块, 表示这段语句最终总是被执行. 请看下面的代码:
public final class FinallyTest {
public static void main(String[] args) {
try {
throw new NullPointerException();
} catch (NullPointerException e) {
System.out.println("程序抛出了异常");
} finally {
System.out.println("执行了 finally 语句块");
}
}
}
2
3
4
5
6
7
8
9
10
11
运行结果说明了 finally 的作用:
- 程序抛出了异常
- 执行了 finally 语句块
请大家注意, 捕获程序抛出的异常之后, 既不加处理, 也不继续向上抛出异常, 并不是良好的编程习惯, 它掩盖了程序执行中发生的错误, 这里只是方便演示, 请不要学习.
那么, 有没有一种情况使 finally 语句块得不到执行呢?大家可能想到了 return、continue、break 这三个可以打乱代码顺序执行语句的规律. 那我们就来试试看, 这三个语句是否能影响 finally 语句块的执行:
public final class FinallyTest {
// 测试 return 语句
public ReturnClass testReturn() {
try {
return new ReturnClass();
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("执行了 finally 语句");
}
return null;
}
// 测试 continue 语句
public void testContinue() {
for (int i = 0; i < 3; i++) {
try {
System.out.println(i);
if (i == 1) {
continue;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("执行了 finally 语句");
}
}
}
// 测试 break 语句
public void testBreak() {
for (int i = 0; i < 3; i++) {
try {
System.out.println(i);
if (i == 1) {
break;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("执行了 finally 语句");
}
}
}
public static void main(String[] args) {
FinallyTest ft = new FinallyTest();
// 测试 return 语句
ft.testReturn();
System.out.println();
// 测试 continue 语句
ft.testContinue();
System.out.println();
// 测试 break 语句
ft.testBreak();
}
}
class ReturnClass {
public ReturnClass() {
System.out.println("执行了 return 语句");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
上面这段代码的运行结果如下:
执行了 return 语句
执行了 finally 语句
0
执行了 finally 语句
1
执行了 finally 语句
2
执行了 finally 语句
0
执行了 finally 语句
1
执行了 finally 语句
2
3
4
5
6
7
8
9
10
11
12
很明显, return、continue 和 break 都没能阻止 finally 语句块的执行. 从输出的结果来看, return 语句似乎在 finally 语句块之前执行了, 事实真的如此吗?我们来想想看, return 语句的作用是什么呢?是退出当前的方法, 并将值或对象返回. 如果 finally 语句块是在 return 语句之后执行的, 那么 return 语句被执行后就已经退出当前方法了, finally 语句块又如何能被执行呢?因 此, 正确的执行顺序应该是这样的: 编译器在编译 return new ReturnClass(); 时, 将它分成了两个步骤, new ReturnClass() 和 return, 前一个创建对象的语句是在 finally 语句块之前被执行的, 而后一个 return 语句是在 finally 语 句块之后执行的, 也就是说 finally 语句块是在程序退出方法之前被执行的. 同样, finally 语句块是在循环被跳过(continue)和中断 (break)之前被执行的.
finalize 方法
最后, 我们再来看看 finalize, 它是一个方法, 属于 java.lang.Object 类, 它的定义如下:
protected void finalize() throws Throwable { }
众所周知, finalize() 方法是 GC(garbage collector)运行机制的一部分, 关于 GC 的知识我们将在后续的章节中来回顾.
在此我们只说说 finalize() 方法的作用是什么呢?
finalize() 方法是在 GC 清理它所从属的对象时被调用的, 如果执行它的过程中抛出了无法捕获的异常(uncaught exception), GC 将终止对改对象的清理, 并且该异常会被忽略;直到下一次 GC 开始清理这个对象时, 它的 finalize() 会被再次调用.
请看下面的示例:
public final class FinallyTest {
// 重写 finalize() 方法
protected void finalize() throws Throwable {
System.out.println("执行了 finalize() 方法");
}
public static void main(String[] args) {
FinallyTest ft = new FinallyTest();
ft = null;
System.gc();
}
}
2
3
4
5
6
7
8
9
10
11
12
运行结果如下:
- 执行了 finalize() 方法
程序调用了 java.lang.System 类的 gc() 方法, 引起 GC 的执行, GC 在清理 ft 对象时调用了它的 finalize() 方法, 因此才有了上面的输出结果. 调用 System.gc() 等同于调用下面这行代码:
Runtime.getRuntime().gc();
调用它们的作用只是建议垃圾收集器(GC)启动, 清理无用的对象释放内存空间, 但是 GC 的启动并不是一定的, 这由 JAVA 虚拟机来决定. 直到 JAVA 虚拟机停止运行, 有些对象的 finalize() 可能都没有被运行过, 那么怎样保证所有对象的这个方法在 JAVA 虚拟机停止运行之前一定被调用 呢?答案是我们可以调用 System 类的另一个方法:
public static void runFinalizersOnExit(boolean value) {
//other code
}
2
3
给这个方法传入 true 就可以保证对象的 finalize() 方法在 JAVA 虚拟机停止运行前一定被运行了, 不过遗憾的是这个方法是不安全的, 它会导致有用的对象 finalize() 被误调用, 因此已经不被赞成使用了.
由于 finalize() 属于 Object 类, 因此所有类都有这个方法, Object 的任意子类都可以重写(override)该方法, 在其中释放系统资源或者做其它的清理工作, 如关闭输入输出流.
通过以上知识的回顾, 我想大家对于 final、finally、finalize 的用法区别已经很清楚了.