前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >读书笔记 | 类的使用 | Effective Java 3

读书笔记 | 类的使用 | Effective Java 3

作者头像
架构探险之道
发布2023-03-04 11:03:15
3460
发布2023-03-04 11:03:15
举报
文章被收录于专栏:架构探险之道架构探险之道

读书笔记:

  • Effective Java 3
  • 2022-04-08

23. 类层次结构优于标签类

标签类含义

通过一个枚举类的形式定义标签,管理实例的不同逻辑处理的分发控制。举个例子,内部枚举对象 Shape 表示图形的类型,计算面积的时候,通过标签类型分别选择圆形和矩形的计算方式来计算。

代码语言:javascript
复制
// Tagged class - vastly inferior to a class hierarchy!
class Figure {
    enum Shape { RECTANGLE, CIRCLE };
    // Tag field - the shape of this figure
    final Shape shape;
    // These fields are used only if shape is RECTANGLE
    double length;
    double width;
    // This field is used only if shape is CIRCLE
    double radius;
    // Constructor for circle
    Figure(double radius) {
        shape = Shape.CIRCLE;
        this.radius = radius;
    }
    // Constructor for rectangle
    Figure(double length, double width) {
        shape = Shape.RECTANGLE;
        this.length = length;
        this.width = width;
    }
    double area() {
        switch(shape) {
          case RECTANGLE:
            return length * width;
          case CIRCLE:
            return Math.PI * (radius * radius);
          default:
            throw new AssertionError(shape);
        }
    }
}

这样做的缺点有哪些?

  • 扩展的时候需要修改源文件,新增 Shape 标签类型和面积计算的 case 分支逻辑
  • 初始化错误的话,final 类型后续无法修改,必须通过外部入参的强校验才能保证业务逻辑的正确性
  • 图形的标签类型多了之后,整个类会十分冗长,篇幅过长的代码整体 Review 往往无法有效发现问题,整体开发效率会降低,维护成本会上升。

如何优化呢?

  • 逻辑拆分,定义抽象基类:
    • 我们可以使用 Java 的类层次结构来解除对标签的依赖。
    • 图形的父类定义核心计算面积的抽象方法,交由具体的子类实现
  • 子类差异化实现,业务对标签的强管控转化为业务使用具体的子类
    • 子类和子类之间的运算逻辑互相独立,业务关注自身使用的子类即可,不会导致类文件膨胀
  • 子类的相似实现也会更加灵活,可以通过子类继承子类来实现,如正方形的面积计算就可以继承矩形,长和宽都传入正方形的边长即可
代码语言:javascript
复制
// Class hierarchy replacement for a tagged class
abstract class Figure {
    abstract double area();
}
class Circle extends Figure {
    final double radius;
    Circle(double radius) { this.radius = radius; }
    @Override double area() { return Math.PI * (radius * radius); }
}
class Rectangle extends Figure {
    final double length;
    final double width;
    Rectangle(double length, double width) {
        this.length = length;
        this.width  = width;
    }
    @Override double area() { return length * width; }
}

class Square extends Rectangle {
    Square(double side) {
        super(side, side);
    }
}

24. 支持使用静态成员类而不是非静态类

先解释下几种嵌套类的类型

  • 静态成员类
代码语言:javascript
复制
public class Main{
    public static class NestClass{}
}
//在外面使用时候形式如下,在Main中使用则不需要加上外部类限定
Main.NestClass nestClass = new Main.NestClass();
  • 内部类:内部类中不能定义任何静态的东西
    • 非静态成员类:在类里面,但不在块、构造器、方法里面。
代码语言:javascript
复制
public class Main{
    // 静态和非静态的之前唯一的区别是,静态成员类的声明中包含static
    public class MemberClass{}
 }
  • 匿名类:匿名类相当于在定义类的同时再新建这个类的实例。我们来看看匿名类的编译结果。
    • 不能定义静态初始化代码块(Static Initializer)
    • 不能在匿名类里面定义接口
    • 不能在匿名类里面定义接口
    • 可以访问外层Class里面的字段。
    • 不能访问外层方法中的本地变量。除非变量是 final,或是数组形式(不受编译器控制)
    • 如果内部类的名称和外面能访问的名称相同,则会把名称覆盖掉。
    • 匿名类中可以包含的东西有:字段、方法、实例初始化代码、本地类
    • 匿名类中不可以包含的东西有:
代码语言:javascript
复制
// 匿名类示例
public class Test {  
  public void test() {  
    Runnable r = new Runnable(){  
      @Override  
      public void run(){  
        System.out.println("hello");  
      }  
    };  
  }  
}

// 非法语法:不能定义静态初始化代码块(Static Initializer)
public class A {  
    public void test() {  
        Runnable r = new Runnable() {  
            static { System.out.println("hello"); }  
            
            @Override
            public void run() {
            }
        };  
    }  
}
// 非法语法:不能在匿名类里面定义接口
public class A {  
    public void test() {  
        Runnable r = new Runnable() {  
            public interface Hello { };  
            
            @Override
            public void run() {
            }
        };  
    }  
}
// 非法语法:不能在匿名类中定义构造函数
public class A {  
    public void test() {  
        Runnable r = new Runnable() {  
            public Runnable() { }  
            
            @Override
            public void run() {
            }
        };  
    }  
}
  • 局部类:是四种嵌套类中用得最少的类。在一个方法中定义的。在任何“可以声明局部变量”的地方,都可以声明局部类。
    • 同局部变量,局部类不能用 public,private,protected,static 修饰,但可以被 final 或者 abstract 修饰。
    • 可以访问其外部类的成员
    • 不能访问该方法的局部变量,除非是 final 局部变量。
代码语言:javascript
复制
public class Test {
    {
        class AA{}//块内局部类
    }
    public Test(){
        class AA{}//构造器内局部类
    }
    public void test(){
        class AA{}//方法内局部类
    }
}
//注意到了吧,可以同名,编译后,形成诸如:外部类名称+$+同名顺序+局部类名称
//Test$1AA.class/Test$2AA.class/Test$3AA.class

非静态类使用有哪些问题呢?

  • 非静态类常见使用方法一般是在内部的 Adapter (适配器)类似的功能,以 Set 和 List 为例,内部通过非静态成员来实现它们的迭代器
代码语言:javascript
复制
// Typical use of a nonstatic member class
public class MySet<E> extends AbstractSet<E> {
    ... // Bulk of the class omitted
    @Override 
    public Iterator<E> iterator() {
        return new MyIterator();
    }
    private class MyIterator implements Iterator<E> {
        ...
    }
}
  • 在语法上,静态成员类和非静态成员类之间的唯一区别是静态成员类在其声明中具有 static 修饰符。尽管句法相似,但这两种嵌套类是非常不同的。
  • 非静态成员类的每个实例都隐含地与其包含的类的宿主实例相关联。
  • 在非静态成员类的实例方法中,可以调用宿主实例上的方法,或者使用限定的构造获得对宿主实例的引用。
  • 如果嵌套类的实例可以与其宿主类的实例隔离存在,那么嵌套类必须是静态成员类:不可能在没有宿主实例的情况下创建非静态成员类的实例。
  • 非静态成员类实例和其宿主实例之间的关联是在创建成员类实例时建立的,并且之后不能被修改。通常情况下,通过在宿主类的实例方法中调用非静态成员类构造方法来自动建立关联。尽管很少有可能使用表达式 enclosingInstance.new MemberClass(args) 手动建立关联。正如你所预料的那样,该关联在非静态成员类实例中占用了空间,并为其构建添加了时间开销。

那么如何使用更好呢?

如果你声明了一个不需要访问宿主实例的成员类,总是把 static 修饰符放在它的声明中,使它成为一个静态成员类,而不是非静态的成员类。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-04-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 架构探险之道 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 23. 类层次结构优于标签类
  • 24. 支持使用静态成员类而不是非静态类
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档