《On Java进阶卷》阅读笔记(一)

第1章 枚举类型

枚举类型的基本特性

  • 调用枚举类型中的values()方法来遍历枚举常量;
  • 编译器会为每一个自定义的枚举类型创建一个辅助类,这个类继承java.lang.Enum;
  • Enum类实现了Comparable接口,所以自动包含compareTo()方法,另外还是先了Serializable接口。

在使用枚举类型时,可使用静态导入整个枚举类。在被导入的类里直接使用枚举常量。

在枚举类型里可自定义方法,并且可重载toString()方法,与重载普通类方法相同。

public enum  EnumTest {
    BOY,GIRL;
    public static EnumTest[] getALlChildren(){
        return values();
    }
    @Override
    public String toString(){
        return "EnumTest: boys , girls";
    }
    public static void main(String[] args) {
        for (EnumTest enumTest : getALlChildren()){
            System.out.println(enumTest);
        }
    }
}

values()方法:Enum类中并没有values()方法,该方法是由编译器在枚举类的定义中插入一个静态方法。

因为所有的枚举类型都继承java.lang.Enum,且Java只支持单继承,所以枚举类型之间不能通过继承 extends 来创建个新枚举对象。

但是可以通过 implements 同一个接口,来对枚举元素进行分类,如下例子,陆地动物和湖泊动物。

interface EnumInterface<T>{

    EnumInterface[]  getAllTEnums();
}

enum LandAnimal implements EnumInterface<LandAnimal>{
    TIGER, FOX, ELEPHANT ;
    @Override
    public EnumInterface[] getAllTEnums() {
        return values();
    }
}

enum LAKEAnimal implements EnumInterface<LAKEAnimal>{
    FISH_1, FISH_2;
    @Override
    public EnumInterface[] getAllTEnums() {
        return values();
    }
}

public class   EnumTest {

    public static void main(String[] args) {
        EnumInterface[] enumInterfaces1 = getEnums(LandAnimal.FOX);
        System.out.println(enumInterfaces1.length);
        EnumInterface[] enumInterfaces2 = getEnums(LAKEAnimal.FISH_1);
        System.out.println(enumInterfaces2.length);
    }

    static EnumInterface[] getEnums(EnumInterface enumInterface){
        System.out.println(enumInterface);
        return enumInterface.getAllTEnums();
    }
}
/**
输出结果:
FOX
3
FISH_1
2
*/

如果单纯只对枚举元素进行分类,可将具体分类的类型枚举类,写在接口类内部,且都实现该接口,如下

interface  Food{
    enum Appetizer implements Food{
        SALAD,SOUP
    }
    enum MainCourse implements Food{
        LASANGE
    }
}

且可以创建另一个枚举类,来管理其他枚举类型类,

enum Course{
    APPETIZER(Food.Appetizer.class),
    MainCourse(Food.MainCourse.class);
    private Food[] values;
    Course(Class<? extends Food> kind) {
        values = kind.getEnumConstants();
    }
}

EnumSet:

配合enum的使用,以代替传统的基于int的【位标识】用法

public static void main(String[] args) {
    // 设置为空
    EnumSet<Course> courseEnumSet = EnumSet.noneOf(Course.class);
    // 添加一个
    courseEnumSet.add(APPETIZER);
    // 添加多个
    courseEnumSet.addAll(EnumSet.of(APPETIZER, MainCourse));
}

EnumSet是基于64位的long构建的,每个枚举实例需要占用1位来表达是否存在的状态,这意味着在单词long的支撑范围内,1个EnumSet最多可支持包含64个元素的枚举类型。如果实际枚举类型个数超过64个,会引入一个新的long类型变量。

EnumMap:

特殊的map,要求所有的key都来自某个枚举类型。

EnumMap中的元素顺序由定义时的顺序决定。

EnumMap支持多路分发。

常量特定方法:

Java的枚举机制可以通过为每个枚举实例编写不同的方法,来赋予他们不同的行为。

定义抽象方法,要求每个元素要重写;

定义普通方法,指定的元素可以重写;

enum ConstantSpecificMethod{
    DATE_TIME{
        @Override
        String getInfo() {
            return DateFormat.getDateInstance().format(new Date());
        }
    },
    JAVA_HOME {
        @Override
        String getInfo() {
            return System.getenv("JAVA_HOME");
        }
    },
    VERSION {
        @Override
        String getInfo() {
            return System.getProperty("java.version");
        }
    };
    // 定义方法, 然后让每个枚举元素去实现,
    abstract String getInfo();

    public static void main(String[] args) {

        for (ConstantSpecificMethod method :values()){
            System.out.println(method.getInfo());
        }
    }
}

/**
2022-9-12
D:\software\Java\jdk1.8.0_341
1.8.0_341
*/

职责链设计模式,先创建了一批用于解决目标问题的不同方法,然后将它们连成一条【链】。当一个请求到达时,会顺着这条链传下去,直到遇到链上某个可以处理该请求的方法。

枚举类型在职责链设计模式里的使用:

  1. 枚举类型里的每个元素就是职责里解决问题的不同方法;
  2. 枚举的定义顺序决定了各个问题被解决的顺序(如果循环枚举);
  3. 直到某个枚举元素下的方法执行返回成功,或者所有的枚举元素方法都执行失败。

枚举类型里各个元素下行为的切换,可以用来实现状态机,不同状态下不同的行为。

多路分发:

当要执行 a.plus(b),并且不知道a或b的具体类型时,如果保证他们间的相互作用是正确的呢?

Java本身只支持单路分发,即对上述问题,Java只能一个个检查类型是否满足要求。

多态只能在方法调用时发生,如果使用多路分发,就必须执行两次方法调用:第一次用来确定第一个未知类型,第二次用来确定第二个未知类型。

要使用多路分发,就必须对每个类型进行一次虚拟调用。如果是在操作两个不同的交互类型层次结构,则需要在每个层级结构都执行虚拟调用。

第2章 对象传递和返回

Java有指针,但是没有指针运算。Java的指针即是【引用】,可以认为它是安全的指针。

传递引用:

当将一个引用传给方法后,该引用指向的仍然是原来的对象。

引用别名指的是不止一个引用被绑定到了同一个对象上的情况。

public class ReferenceTest {
    public static void main(String[] args) {
        ReferenceTest test = new ReferenceTest();
        System.out.println("outer : " + test);
        f(test);
        ReferenceTest test1 = test;
        System.out.println("别名:" + test1);

    }
    static  void f(ReferenceTest r){
        System.out.println("inner:" + r);
    }
}

/**
outer : com.br.test.lambda.ReferenceTest@1d81eb93
inner:com.br.test.lambda.ReferenceTest@1d81eb93
别名:com.br.test.lambda.ReferenceTest@1d81eb93
*/

上面代码的调用方法传对象和别名,都指向了同一个引用对象的地址。

创建本地副本:

Java中的所有参宿和传递都是通过传递引用实现的。当传递一个对象时,实际传递的是存活于方法外部的指向这个对象的一个引用。如果通过该引用执行了任何修改,同样也会修改外部的对象。此外:

  • 引用别名会在传递参数时自动发生;
  • 并没有本地对象,只有本地引用;
  • 引用是有作用域的,对象则没有;
  • Java中的对象生命周期从来就不是个问题。

值传递:有两种不同的说法。

  1. Java传递任何事物时,都是在传递该事物的值。当向方法传递基本类型时,得到的是该基本类型的一份单独的副本。而当你向方法传入一个引用时,得到的则是该引用的副本。
  2. Java在传递基本类型时,传递的是值,但在传递对象时,传递的则是引用。

克隆对象:

同一个对象的不同引用别名,是一种浅拷贝,因为这只复制了对象的【表层】部分。

实际对象的组成部分包括该表层引用,该引用指向的所有对象,以及所有这些对象所指向的所有对象,这通常称为对象网络。创建所有这些内容的完整副本,被称为深拷贝。

增加克隆能力:

1.待克隆的类要重写Object类的clone方法,并设置重写方法为public,方便外部调用。同时重写的方法内部一定要有super.clone();

2.待克隆的类要实现Cloneable接口,因为克隆时会检查是否实现此接口。没有会抛出CloneNotSupportedException异常。

【==】和【!=】操作只是简单地比较引用,如果引用内部地址相同,那么指向的就是同一个对象,因此就是“相等”的。

最终也是由根类的clone方法来为被克隆的对象创建正确大小的存储空间,并执行了从原始对象中的所有二进制位到新对象存储中的按位复制。

克隆组合对象:必须要克隆组合类里所有的对象引用,这些引用在组合类的clone方法里要调用自己的clone方法去生成。

public class ReferenceTest   implements Cloneable{

    private CombineClass combineClass;
    
    public Object clone() throws CloneNotSupportedException {
        ReferenceTest test = (ReferenceTest)  super.clone();
        test.combineClass = (CombineClass)test.combineClass.clone();
        return test;
    }
}

class CombineClass implements Cloneable{

    public Object clone() throws CloneNotSupportedException {
        return  super.clone();
    }
}

一旦克隆了一个对象,就可以对副本进行修改了,不会影响到原副本。

序列化方式也可以实现深拷贝进行对象的clone,但是序列化方式要比clone方法慢一个数量级。

控制可克隆性:

一些方案:

  1. 不关心可克隆性,意味着该类无法被克隆,但是它的继承类可以在必要时增加克隆能力。
  2. 支持clone,实现Cloneable接口并重写clone方法。
  3. 视情况克隆。
  4. 不实现Cloneable接口,但将clone重写为protected的,并实现适用于所有字段的正确复制行为。
  5. 通过不实现Cloneable接口并重写clone(),使其抛出异常,来尽量避免克隆。
  6. 将类定义为final,以此阻止克隆。
// 无法克隆,未重写clone
class Ordinary{}

// 重写clone,但未实现Cloneable接口
class WrongClone extends Ordinary{
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

// 完美克隆
class IsCloneable extends Ordinary implements Cloneable{
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

// 通过抛出异常来关闭克隆
class NoMore extends IsCloneable{
    @Override
    public Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }
}

// 通过调用父类的clone,抛出异常来关闭克隆
class TryMore extends  NoMore{
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

// 重写clone,但未调用super.clone。更换为调用其他方法方式
class BackOn extends NoMore{
    private BackOn duplicate(){
        return new BackOn();
    }
    @Override
    public Object clone(){
        return duplicate();
    }
}

// 无法继承该类,无法像在BackOn中一样重写clone()
final class ReallyNoMore extends NoMore{}

总结克隆一个类的步骤:

  1. 实现Cloneable接口;
  2. 重写clone()方法;
  3. 在clone()方法中调用super.clone()方法;
  4. 在clone()方法中捕获异常。

其他生成一个对象方式,创建这个待可控类的构造方法,参数就是这个待克隆的类,在构造方法里对把待克隆类的属性转移到新类中。

class Cat{
    private String name;
    private Integer age;

    public Cat(){}
    
    public Cat(Cat cat){
        this.name = cat.getName();
        this.age = cat.getAge();
    }

    public static void main(String[] args) {
        
        Cat cat1 = new Cat();
        Cat cat2 = new Cat(cat1);
        
    }
} 

不可变类:

指这个类在创建完毕后,对外暴露的都是可被读的方法,没有修改类内部属性的方法。

不可变类的缺点是,修改这种类型的对象时,就必须创建一个新对象来接收修改后的结果,并返回新对象。增加了资源开销。

解决方式是增加一个伴生类,修改放在这个伴生里中。前提是需要不可变对象、经常要做修改、或者创建新的不可变对象的开销很大。

String类本质就是不可变的,它的所有修改方法返回的结果都是重新创建一个String类接收。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值