改善Java程序的151个建议 36-40
文章目录
36. 使用构造代码块精炼程序
什么叫代码块?
- 用大括号吧多行代码封装在一起,形成一个独立的数据体,实现特性的算法的代码集合即为代码块。
- 一般来说代码块是不能单独运行的,必须要有运行主体。在Java中一共有四种类型的代码块。
Java中的代码块类型概览
构造代码块的两个特性
- 在每个构造函数中都运行
- 在构造函数中它会首先运行
构造代码块带来的益处
- 很好地利用构造代码块的这两个特性不仅可以减少代码量,还可以让程序更容易阅读,特别是当所有的构造函数都要实现逻辑,而且这部分逻辑又很复杂时,这时就可以通过编写多个构造代码块来实现
- 每个代码块完成不同的业务逻辑(当然了,构造函数尽量简单,这是基本原则),按照业务顺序依次存放,这样在创建实例对象时JVM也就会按照顺序依次执行,实现复杂对象的模块化创建。
案例代码
package ad36to40;
/**
* @author luxiaoyang
* @create 2021-08-05-9:05
*/
public class Client {
{
// 构造代码块
System.out.println("执行构造代码块1");
}
{
System.out.println("执行构造代码块12");
}
public Client() {
System.out.println("执行无参构造");
}
public Client(String _str) {
System.out.println("执行有参构造");
}
public static void main(String[] args) {
Client client = new Client();
}
}
// 输出
//执行构造代码块1
//执行构造代码块12
//执行无参构造
37. 构造代码块会想你所想
猜一猜输出几?
package ad36to40;
/**
* @author luxiaoyang
* @create 2021-08-05-19:46
*/
public class Client2 {
public static void main(String[] args) {
Base base = new Base();
new Base("");
new Base(0);
System.out.println("实例对象数量:" + Base.getNumOfObjects());
}
}
class Base{
// 程序计数器
private static int numOfObjects = 0;
{
// 构造代码块,计算产生对象数量
numOfObjects++;
}
public Base() {
}
// 有参构造调用无参构造
public Base (String _str){
this();
}
// 有参构造不调用其他构造
public Base(int _i) {
}
// 返回在一个JVM中,创建了多少个实例对象
public static int getNumOfObjects() {
return numOfObjects;
}
}
实例对象的数量还是3,程序没有任何问题
上一个建议是说编译器会把构造代码块插入到每一个构造函数中,但是有一个例外的情况没有说明:如果遇到this关键字(也就是构造函数调用自身其他的构造函数时)则不插入构造代码块,对于我们的例子来说,编译器在编译时发现String形参的构造函数调用了无参构造,于是放弃插入构造代码块,所以只执行了一次构造代码块—结果就是如此。
构造代码块的诞生
构造代码块是为了提取构造函数的共同量,减少各个构造函数的代码而产生的,因此,Java就很聪明地认为把代码块插入到没有this方法的构造函数中即可,而调用其他构造函数的则不插入,确保每个构造函数只执行一次构造代码块。
还有一点需要说明,读者千万不要以为this是特殊情况,那super也会类似处理了。其实不会,在构造代码块的处理上,super方法没有任何特殊的地方,编译器只是把构造代码块插入到super方法之后执行而已,仅此不同。
38. 使用静态内部类提高封装性
静态内部类的两个优点
- 类的封装性
- 提高了代码的可读性
案例展示
package ad36to40;
import lombok.Data;
/**
* @author luxiaoyang
* @create 2021-08-05-20:55
*/
@Data
public class Person {
// 姓名
private String name;
// 家庭
private Home home;
private Person(String _name){
name = _name;
}
@Data
public static class Home{
// 家庭地址
private String address;
// 家庭电话
private String tel;
public Home(String _address,String _tel){
address = _address;
tel = _tel;
}
}
}
,Person类中定义了一个静态内部类Home,它表示的意思是“人的家庭信息”,由于Home类封装了家庭信息,不用在Person类中再定义homeAddre、homeTel等属性,这就使封装性提高了。同时我们仅仅通过代码就可以分析出Person和Home之间的强关联关系,也就是说语义增强了,可读性提高了。所以在使用时就会非常清楚它要表达的含义:
public static void main(String[] args) {
// 定义张三这个人
Person person = new Person("张三");
// 设置张三的家庭信息
person.setHome(new Person.Home("上海","021"));
}
定义张三这个人,然后通过Person.Home类设置张三的家庭信息,这是不是就和我们真实世界的情形相同了?先登记人的主要信息,然后登记人员的分类信息
使用静态内部类的优势
可能你又要问了,这和我们一般定义的类有什么区别呢?又有什么吸引人的地方呢?如下所示:
- 提高封装性。从代码位置上来讲,静态内部类放置在外部类内,其代码层意义就是:静态内部类是外部类的子行为或子属性,两者直接保持着一定的关系,比如在我们的例子中,看到Home类就知道它是Person的Home信息。
- 提高代码的可读性。相关联的代码放在一起,可读性当然提高了。
- 形似内部,神似外部。静态内部类虽然存在于外部类内,而且编译后的类文件名也包含外部类(格式是:外部类+$+内部类),但是它可以脱离外部类存在,也就是说我们仍然可以通过new Home()声明一个Home对象,只是需要导入“Person.Home”而已。
静态内部类与普通内部类区别
- 静态内部类不持有外部类的引用
- 在普通内部类中,我们可以直接访问外部类的属性、方法,即使是private类型也可以访问,这是因为内部类持有一个外部类的引用,可以*访问。而静态内部类,则只可以访问外部类的静态方法和静态属性(如果是private权限也能访问,这是由其代码位置所决定的),其他则不能访问。
- 静态内部类不依赖外部类
- 普通内部类与外部类之间是相互依赖的关系,内部类实例不能脱离外部类实例,也就是说它们会同生同死,一起声明,一起被垃圾回收器回收。而静态内部类是可以独立存在的,即使外部类消亡了,静态内部类还是可以存在的。
- 普通内部类不能声明static的方法和变量
- 普通内部类不能声明static的方法和变量,注意这里说的是变量,常量(也就是final static修饰的属性)还是可以的,而静态内部类形似外部类,没有任何限制。
39. 使用匿名类的构造函数
答案是能编译,输出的是3个false
l2=new ArrayList(){}
l2代表的是一个匿名类的声明和赋值,它定义了一个继承于ArrayList的匿名类,只是没有任何的覆写方法而已,其代码类似于:
就是多了一个初始化块而已,起到构造函数的功能。我们知道一个类肯定有一个构造函数,且构造函数的名称和类名相同,那问题来了:匿名类的构造函数是什么呢?它没有名字呀!很显然,初始化块就是它的构造函数。当然,一个类中的构造函数块可以是多个,也就是说可以出现如下代码:
上面的代码是正确无误,没有任何问题的。现在清楚了:匿名函数虽然没有名字,但也是可以有构造函数的,它用构造函数块来代替,那上面的3个输出就很清楚了:虽然父类相同,但是类还是不同的。
40. 匿名类的构造函数很特殊
匿名类虽然没有名字,但可以有一个初始化块来充当构造函数,那这个构造函数是否就和普通的构造函数完全一样呢?
我们来看一个例子,设计一个计算器,进行加减乘除运算,代码如下:
enum Ops {ADD, SUB}
class Calculator {
private int i, j, result;
// 无参构造
public Calculator() {
}
// 有参构造
public Calculator(int _i, int _j) {
i = _i;
j = _j;
}
//设置符号,是加法运算还是减法运算
protected void setOperator(Ops _op) {
result = _op.equals(Ops.ADD) ? i + j : i - j;
}
// 取得运算结果
public int getResult() {
return result;
}
public static void main(String[] args) {
Calculator calculator = new Calculator(1, 2) {
{
setOperator(Ops.ADD);
}
};
System.out.println(calculator.getResult());
}
}
带有参数的匿名类声明时到底是调用的哪一个构造函数呢?我们把这段程序模拟一下:
一般类(也就是具有显式名字的类)的所有构造函数默认都是调用父类的无参构造的,而匿名类因为没有名字,只能由构造代码块代替,也就无所谓的有参和无参构造函数了,它在初始化时直接调用了父类的同参数构造,然后再调用了自己的构造代码块,也就是说上面的匿名类与下面的代码是等价的:
等价于:
class Add extends Calculator {
{
setOperator(Ops.ADD);
}
// 覆写父类的构造方法
public Add(int _i,int _j) {
super(_i,_j);
}
public static void main(String[] args) {
Add add = new Add(1, 2);
System.out.println(add.getResult());
}
}
Add extends Calculator {
{
setOperator(Ops.ADD);
}
// 覆写父类的构造方法
public Add(int _i,int _j) {
super(_i,_j);
}
public static void main(String[] args) {
Add add = new Add(1, 2);
System.out.println(add.getResult());
}
}