摘要:本章文章是把自己之前看Java核心技术卷1时,遗忘或者漏掉的内容重新归纳总结
前言:这两天我把Java核心技术卷1这本书重新复习了一遍,查缺补漏,把内容重新归纳总结一遍。
一、对象与类
一定要认识到:一个对象变量并没有实际包含一个对象,而是仅仅引用一个对象。任何对象变量的值都是对存储在另一个地方的一个对象的引用。
所有的java对象都存储在堆中。
在java中,必须使用clone方法获得对象的完整拷贝。
当java编译器发现A.java使用了B.java类时,会查找名为B.class的文件,如果没有找到这个文件,就会自动的搜索B.java,然后对它进行编译。如果B.java版本较已有的B.class文件版本新,java编译器就会自动地重新编译B.java这个文件。
构造器:
- 构造器与类同名
- 每个类可以有一个以上的构造器
- 构造器可以有0个、1个或多个参数
- 构造器没有返回值
- 构造器总是伴随着new操作一起调用
显示参数与隐私参数:

方法参数的使用情况:
- 一个方法不能修改一个基本数据类型的参数
- 一个方法可以改变一个对象参数的状态
- 一个方法不能让对象参数引用一个新对象
先运行初始化块,然后才运行构造器的主体部分
所有的静态初始化语句以及静态初始化块都将依照类定义的顺序执行
类设计技巧:
- 一定要保证数据私有
- 一定要对数据初始化
- 不要在类中使用过多的基本类型
- 不是所有的域都需要独立的域访问器或者域更改器
- 将职责过多的类进行分解(单一职责)
- 类名和方法名要能够体现它们的职责
二、继承
super不是一个对象的引用,不能将super赋给另一个对象变量,它只是一个指示编译器调用超类方法的特殊关键字。
一个对象变量(例如,变量e) 可以指示多种实际类型的现象被称为多态(polymorphism)
在运行时能够自动地选择调用哪个方法的现象称为动态绑定(dynamic binding)
对象变量是多态的:可以将一个子类的对象赋给超类变量
注意:

调用过程的详细描述:
- 编译器査看对象的声明类型和方法名:假设调用x.f(param)(x为A类型)编译器将会一一列举所有A类中名为f的方法和其超类中访问属性为public且名为f的方法(超类的私有方法不可访问)。
- 接下来,编译器将査看调用方法时提供的参数类型:如果在所有名为f的方法中存在一个与提供的参数类型完全匹配,就选择这个方法。这个过程被称为重载解析。如果编译器没有找到与参数类型匹配的方法,或者发现经过类型转换后有多个方法与之匹配,就会报告一个错误。
- 如果是private方法、static方法、final方法或者构造器,那么编译器将可以准确地知道应该调用哪个方法, 我们将这种调用方式称为静态绑定(static binding)。与此对应的是,调用的方法依赖于隐式参数的实际类型, 并且在运行时实现动态绑定。
- 当程序运行,并且采用动态绑定调用方法时,虚拟机一定调用与x 所引用对象的实际类型最合适的那个类的方法。假设x的实际类型是D,它是C类的子类。如果D 类定义了方法f(String),就直接调用它;否则,将在D类的超类中寻找f(String),以此类推。
方法表(method table),其中列出了所有方法的签名和实际调用的方法。
强制类型转换:
- 只能在继承层次内进行类型转换
- 在将超类转换成之类之前,应该使用instanceof进行检查。
访问修饰符:
- 对所有类可见 ———— public:
- 对本包和所有子类可见 ———— protected。
- 对本包可见 ———— 默认,不需要修饰符
- 仅对本类可见 ———— private。
| 修饰符 | 当前类 | 同 包 | 子 类 | 其他包 |
|---|---|---|---|---|
| public | √ | √ | √ | √ |
| protected | √ | √ | √ | × |
| default | √ | √ | × | × |
| private | √ | × | × | × |
Object(超类):
在Java 中,只有基本类型(primitive types)不是对象:数值、字符和布尔类型的值都不是对象。
所有的数组类型,不管是对象数组还是基本类型的数组都扩展了Object类。
equals方法具有的特性:
- 自反性:对于任何非空引用x,x.equals(x)应该返回true。
- 对称性:对于任何引用x和y,当且仅当y.equals(x)返回true,x.equals(y)也应该返回true。
- 传递性:对于任何引用x、y和z,如果x.equals(y)返回true,y.equals(z)返回true,x.equals(z)也应该返回true。
- 一致性:如果x和y引用的对象没有发生变化,反复调用x.equals(y)应该返回同样的结果。
- 对于任意非空引用x,x.equals(null)应该返回false。

类型化与原始数组列表的兼容性:

对象包装器:




参数数量可变的方法:

三、接口、lambda表达式、内部类
接口
- Cloneable接口:

在Java SE 8中,允许在接口中增加静态方法。
可以为接口方法提供一个默认实现。必须用default修饰符标记这样一个方法。

默认方法冲突时:



- 浅拷贝与深拷贝

lambda表达式
Lambda表达式是Java SE 8中一个重要的新特性。lambda表达式允许你通过表达式来代替功能接口。 lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块)。
Lambda表达式还增强了集合库。 Java SE 8添加了2个对集合数据进行批量操作的包: java.util.function 包以及java.util.stream 包。 流(stream)就如同迭代器(iterator),但附加了许多额外的功能。 总的来说,lambda表达式和stream是自Java语言添加泛型(Generics)和注解(annotation)以来最大的变化。 在本文中,我们将从简单到复杂的示例中见认识lambda表达式和stream的强悍。
Lambda表达式的语法
1 | 基本语法: |
下面是Java lambda表达式的简单例子:
1 | // 1. 不需要参数,返回值为 5 |
可以参考下面文章:
https://www.cnblogs.com/franson-2016/p/5593080.html
四、异常、断言和日志
异常
异常层次结构的一个简化示意图:

抛出异常(throws)
- 找到一个合适的异常类
- 创建这个类的一个对象
- 将对象抛出
捕获异常(try/catch)
- finally

- 带资源的try语句

断言
断言机制允许在测试期间向代码中插入一些检査语句。当代码发布时,这些插人的检测
语句将会被自动地移走。
关键字assert。这个关键字有两种形式:
1 | assert 条件 |
这两种形式都会对条件进行检测,如果结果为false,则抛出一个AssertionError异常。在第二种形式中,表达式将被传入AssertionError的构造器,并转换成一个消息字符串。
启用和禁用断言

断言的使用场合
- 断言失败是致命的、不可恢复的错误。
- 断言检查只用于开发和测试阶段。
因此,不应该使用断言向程序的其他部分通告发生了可恢复性的错误,或者,不应该作为程序向用户通告问题的手段。断言只应该用于在测试阶段确定程序内部的错误位置。
日志
日志的优点:
- 可以很容易地取消全部日志记录,或者仅仅取消某个级别的日志,而且打开和关闭这个操作也很容易。
- 可以很简单地禁止日志记录的输出,因此,将这些日志代码留在程序中的开销很小。
- 日志记录可以被定向到不同的处理器,用于在控制台中显示,用于存储在文件中等。
- 日志记录器和处理器都可以对记录进行过滤。过滤器可以根据过滤实现器制定的标准丢弃那些无用的记录项。
- 日志记录可以采用不同的方式格式化,例如,纯文本或XML。
- 应用程序可以使用多个日志记录器, 它们使用类似包名的这种具有层次结构的名字,例如:com.mycompany.myapp。
- 在默认情况下, 日志系统的配置由配置文件控制。如果需要的话,应用程序可以替换这个配置。
日志记录器级别:
- SEVERE
- WARNING
- INFO
- CONFIG
- FINE
- FINER
- FINEST
补充
String、StringBuffer、StringBuilder的区别
这三个类之间的区别主要是在两个方面,即运行速度和线程安全这两方面。
- 首先说运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > String
String最慢的原因:String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。以下面一段代码为例:
1 | String str="abc"; |
如果运行这段代码会发现先输出“abc”,然后又输出“abcde”,好像是str这个对象被更改了,其实,这只是一种假象罢了,JVM对于这几行代码是这样处理的,首先创建一个String对象str,并把“abc”赋值给str,然后在第三行中,其实JVM又创建了一个新的对象也名为str,然后再把原来的str的值和“de”加起来再赋值给新的str,而原来的str就会被JVM的垃圾回收机制(GC)给回收掉了,所以,str实际上并没有被更改,也就是前面说的String对象一旦创建之后就不可更改了。所以,Java中对String对象进行的操作实际上是一个不断创建新的对象并且将旧的对象回收的一个过程,所以执行速度很慢。
而StringBuilder和StringBuffer的对象是变量,对变量进行操作就是直接对该对象进行更改,而不进行创建和回收的操作,所以速度要比String快很多。
另外,有时候我们会这样对字符串进行赋值
1 | String str="abc"+"de"; |
这样输出结果也是“abcde”和“abcde”,但是String的速度却比StringBuilder的反应速度要快很多,这是因为第1行中的操作和String str=”abcde”;是完全一样的,所以会很快,而如果写成下面这种形式
1 | String str1="abc"; |
那么JVM就会像上面说的那样,不断的创建、回收对象来进行这个操作了。速度就会很慢。
- 再来说线程安全,在线程安全上,StringBuilder是线程不安全的,而StringBuffer是线程安全的
如果一个StringBuffer对象在字符串缓冲区被多个线程使用时,StringBuffer中很多方法可以带有synchronized关键字,所以可以保证线程是安全的,但StringBuilder的方法则没有该关键字,所以不能保证线程安全,有可能会出现一些错误的操作。所以如果要进行的操作是多线程的,那么就要使用StringBuffer,但是在单线程的情况下,还是建议使用速度比较快的StringBuilder。
- 总结一下
- String:适用于少量的字符串操作的情况
- StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况
- StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况
- 本文作者: th3ee9ine
- 本文链接: https://www.blog.ajie39.top/2021/05/05/Java核心技术卷1复习笔记/
- 版权声明: 本博客所有文章除特别声明外,均采用 LICENSE 下的许可协议。转载请注明出处!