Java编程的逻辑

chapter 3 类的基础

3.3 代码的组织机制

包范围可见性

如果什么修饰符都不写,它的可见性范围就是同一个包内,同一个包内的其他类可以访问,而其他包内的类则不可以访问。

声明为protected不仅表明子类可以访问,还表明同一个包内的其他类可以访问,即使这些类不是子类也可以。

总结来说,可见性范围从小到大是:private < 默认(包) < protected < public。

chapter4 类的继承

4.1基本概念

多态:一种类型的变量可以引用多种实际类型对象。比如变量shape可以引用任何Shape子类类型的对象。

子类变量和方法与父类重名的情况下,可通过super强制访问父类的变量和方法。

子类对象可以赋值给父类引用变量,这叫多态;实际执行调用的是子类实现,这叫动态绑定。


关于可见性重写:重写方法时,一般并不会修改方法的可见性。但我们还是要说明一点,重写时,子类方法不能降低父类方法的可见性。不能降低是指,父类如果是public,则子类也必须是public,父类如果是protected,子类可以是protected,也可以是public,即子类可以升级父类方法的可见性但不能降低

为什么要这么规定呢?继承反映的是“is-a”的关系,即子类对象也属于父类,子类必须支持父类所有对外的行为,将可见性降低就会减少子类对外的行为,从而破坏“is-a”的关系,但子类可以增加父类的行为,所以提升可见性是没有问题的。

4.4 继承是把双刃剑

​ 封装就是隐藏实现细节,提供简化接口。使用者只需要关注怎么用,而不需要关注内部是怎么实现的。实现细节可以随时修改,而不影响使用者。函数是封装,类也是封装。通过封装,才能在更高的层次上考虑和解决问题。可以说,封装是程序设计的第一原则,没有封装,代码之间会到处存在着实现细节的依赖,则构建和维护复杂的程序是难以想象的。

​ 封装就是隐藏实现细节,提供简化接口。使用者只需要关注怎么用,而不需要关注内部是怎么实现的。实现细节可以随时修改,而不影响使用者。函数是封装,类也是封装。通过封装,才能在更高的层次上考虑和解决问题。可以说,封装是程序设计的第一原则,没有封装,代码之间会到处存在着实现细节的依赖,则构建和维护复杂的程序是难以想象的。

​ 子类和父类之间是细节依赖,子类扩展父类,仅仅知道父类能做什么是不够的,还需要知道父类是怎么做的,而父类的实现细节也不能随意修改,否则可能影响子类。更具体的说,子类需要知道父类的可重写方法之间的依赖关系。

​ 总结:对于子类而言,通过继承实现是没有安全保障的,因为父类修改内部实现细节,它的功能就可能会被破坏;而对于基类而言,让子类继承和重写方法,就可能丧失随意修改内部实现的自由。

4.4.3 继承没有反映 is-a 关系

​ 继承关系是设计用来反映is-a关系的,子类是父类的一种,子类对象也属于父类,父类的属性和行为也适用于子类。就像橙子是水果一样,水果有的属性和行为,橙子也必然都有。

​ 但现实中,设计完全符合is-a关系的继承关系是困难的。比如,绝大部分鸟都会飞,可能就想给鸟类增加一个方法fly()表示飞,但有一些鸟就不会飞,比如企鹅。

chapter 5 类的扩展

5.1接口的本质

5.1.6 使用接口替代继承

​ 继承至少有两个好处:一个是复用代码;另一个是利用多态和动态绑定统一处理多种不同子类的对象。使用组合替代继承,可以复用代码,但不能统一处理。使用接口替代继承,针对接口编程,可以实现统一处理不同类型的对象,但接口没有代码实现,无法复用代码。将组合和接口结合起来替代继承,就既可以统一处理,又可以复用代码了。

5.2.2 为什么需要抽象类

​ 引入抽象方法和抽象类,是Java提供的一种语法工具,对于一些类和方法,引导使用者正确使用它们,减少误用。使用抽象方法而非空方法体,子类就知道它必须要实现该方法,而不可能忽略,若忽略Java编译器会提示错误。使用抽象类,类的使用者创建对象的时候,就知道必须要使用某个具体子类,而不可能误用不完整的父类。无论是编写程序,还是平时做其他事情,每个人都可能会犯错,减少错误不能只依赖人的优秀素质,还需要一些机制,使得一个普通人都容易把事情做对,而难以把事情做错。抽象类就是Java提供的这样一种机制。

5.2.3 抽象类和接口

​ 抽象类,相对于具体类,它用于表达抽象概念,虽然从语法上抽象类不是必需的,但它能使程序更为清晰,可以减少误用。抽象类和接口经常相互配合,接口定义能力,而抽象类提供默认实现,方便子类实现接口。

5.3.2 成员内部类

​ 与静态内部类不同,成员内部类中不可以定义静态变量和方法(final变量例外,它等同于常量),下面介绍的方法内部类和匿名内部类也都不可以。Java为什么要有这个规定呢?可以这么理解,这些内部类是与外部实例相连的,不应独立使用,而静态变量和方法作为类型的属性和方法,一般是独立使用的,在内部类中意义不大,而如果内部类确实需要静态变量和方法,那么也可以挪到外部类中。

chapter6 异常

6.3异常处理

6.3.3 finally

​ finally语句有一个执行细节,如果在try或者catch语句内有return语句,则return语句在finally语句执行结束后才执行,但finally并不能改变返回值,我们来看下面的代码:

public static int test(){
    int ret = 0;
    try{
        return ret;
    }finally{
        ret = 2;
    }
}

​ 这个函数的返回值是0,而不是2。实际执行过程是:在执行到try内的return ret;语句前,会先将返回值ret保存在一个临时变量中,然后才执行finally语句,最后try再返回那个临时变量,finally中对ret的修改不会被返回。如果在finally中也有return语句呢?try和catch内的return会丢失,实际会返回finally中的返回值。finally中有return不仅会覆盖try和catch内的返回值,还会掩盖try和catch内的异常,就像异常没有发生一样,比如:

public static int test(){
    int ret = 0;
    try{
        int a = 5/0;
        return ret;
    }finally{
        return 2;
    }
}

finally中抛出了RuntimeException,则原异常ArithmeticException就丢失了。所以,一般而言,为避免混淆,应该避免在finally中使用return语句或者抛出异常,如果调用的其他代码可能抛出异常,则应该捕获异常并进行处理。

chapter 7 常用基础类

7.1 包装类

7.1.2共同点

​ hashCode和equals方法联系密切,对两个对象,如果equals方法返回true,则hashCode也必须一样。反之不要求,equal方法返回false时,hashCode可以一样,也可以不一样,但应该尽量不一样。hashCode的默认实现一般是将对象的内存地址转换为整数,子类如果重写了equals方法,也必须重写hashCode。之所以有这个规定,是因为Java API中很多类依赖于这个行为,尤其是容器中的一些类。

chapter8 泛型

泛型的好处:

  • 更好的安全性
  • 更好的可读性
    /**
     * <T extends Comparable<T>> 称为递归类型限制
     * T表示一种数据类型,必须实现Comparable接口,
     * 且必须可以与相同类型的元素进行比较。
     */
    public static <T extends Comparable<T>> T max(T[] arr) {
        T max = arr[0];
        for (T t : arr) {
            if (t.compareTo(max) > 0) {
                max = t;
            }
        }
        return max;
    }
public class DynamicArray<E> {
    private static final int DEFAULT_CAPACITY = 10;
    private int size;
    private Object[] elementData;

    public DynamicArray() {
        this.elementData = new Object[DEFAULT_CAPACITY];
    }

    private void ensureCapacity(int minCapacity) {
        int oldCapacity = elementData.length;
        if (oldCapacity >= minCapacity) {
            return;
        }
        int newCapacity = oldCapacity * 2;
        if (newCapacity < minCapacity) newCapacity = minCapacity;
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    public void add(E e) {
        ensureCapacity(size + 1);
        elementData[size++] = e;
    }


    /**
     * <T extends E> 用于定义类型参数,它申明了一个类型参数T,可放在泛型类定义中类名后面、泛型方法返回值前面
     */
    public <T extends E> void addAll(DynamicArray<T> c) {
        for (int i = 0; i < c.size; i++) {
            add(c.get(i));
        }
    }

    public void addAll0(DynamicArray<E> c) {
        for (int i = 0; i < c.size; i++) {
            add(c.get(i));
        }
    }

    /**
     * 这个方法没有定义类型参数,c的类型是DynamicArray<? extends E>,
     * ?表示通配符,<? extends E>表示有限定通配符,匹配E或E的某个子类型,
     * 具体什么子类型是未知的。
     * </p>
     * <? extends E>用于实例化类型参数,它用于实例化泛型变量中的类型参数,
     * 只是这个具体类型是未知的,只知道它是E或者E的子类型
     */
    public void addAll1(DynamicArray<? extends E> c) {
        for (int i = 0; i < c.size; i++) {
            add(c.get(i));
        }
    }

    public E get(int index) {
        return (E) elementData[index];
    }

    public int size() {
        return size;
    }

    public E set(int index, E element) {
        E oldValue = get(index);
        elementData[index] = element;
        return oldValue;
    }

    public static <T> int indexOf(T[] arr, T elm) {
        for (int i = 0; i < arr.length; i++) {
            if (arr[i].equals(elm)) {
                return i;
            }
        }
        return -1;
    }


    public static int indexOf(DynamicArray<?> arr, Object elm) {
        for (int i = 0; i < arr.size; i++) {
            if (arr.get(i).equals(elm)) {
                return i;
            }
        }
        return -1;
    }

    public static <T> int indexOf0(DynamicArray<T> arr, Object elm) {
        for (int i = 0; i < arr.size; i++) {
            if (arr.get(i).equals(elm)) {
                return i;
            }
        }
        return -1;
    }


    /**
     * <T extends Comparable<T>> 称为递归类型限制
     * T表示一种数据类型,必须实现Comparable接口,
     * 且必须可以与相同类型的元素进行比较。
     */
    public static <T extends Comparable<T>> T max(T[] arr) {
        T max = arr[0];
        for (T t : arr) {
            if (t.compareTo(max) > 0) {
                max = t;
            }
        }
        return max;
    }

    /**
     * 通配符有一个限制:只能读,不能写。如果能写就违背了java关于类型安全的承诺
     */
    public static void swap(DynamicArray<?> arr, int i, int j) {
        Object tmp = arr.get(i);
        // 下面两行是错误的
//        arr.set(i, arr.get(j));
//        arr.set(j, tmp);
        swapInternal(arr, i, j);
    }

    public static <T> void swapInternal(DynamicArray<T> arr, int i, int j) {
        T tmp = arr.get(i);
        arr.set(i, arr.get(j));
        arr.set(j, tmp);
    }


    public static <D, S extends D> void copy(DynamicArray<D> dest, DynamicArray<S> src) {
        for (int i = 0; i < src.size; i++) {
            dest.add(src.get(i));
        }
    }

    public static <D> void copy0(DynamicArray<D> dest, DynamicArray<? extends D> src) {
        for (int i = 0; i < src.size; i++) {
            dest.add(src.get(i));
        }
    }
}

关于通配符和类型参数的总结:

  • 通配符形式都可以用类型参数的形式来替代,通配符能做的,用类型参数都能做。
  • 通配符可以减少类型参数,形式上往往更为简单,可读性也更好,所以能用通配符就用通配符。
  • 如果类型参数之间有依赖关系,或者返回值依赖类型参数,或者需要写操作,则只能用类型参数。
  • 通配符形式和类型参数往往配合使用,比如上面的copy方法,定义必要的类型参数,使用通配符表达依赖,并接受更广泛的数据类型。

chapter 9 列表和队列

ArrayList的主要方法:

	public boolean add(E e) //添加元素到末尾
    public boolean isEmpty() //判断是否为空
    public int size() //获取长度
    public E get(int index) //访问指定位置的元素
    public int indexOf(Object o) //查找元素, 如果找到,返回索引位置,否则返回-1
    public int lastIndexOf(Object o) //从后往前找
    public boolean contains(Object o) //是否包含指定元素,依据是equals方法的返回值
    public E remove(int index) //删除指定位置的元素, 返回值为被删对象
    //删除指定对象,只删除第一个相同的对象,返回值表示是否删除了元素
    //如果o为null,则删除值为null的元素
    public boolean remove(Object o)
    public void clear() //删除所有元素
    //在指定位置插入元素,index为0表示插入最前面,index为ArrayList的长度表示插到最后面
    public void add(int index, E element)
    public E set(int index, E element) //修改指定位置的元素内容

下面这段代码会抛出异常: java.util.ConcurrentModificationException

    public void remove(ArrayList<Integer> list){
        for(Integer a : list){
            if(a<=100){
                list.remove(a);
            }
        }
    }

​ 原因:发生了并发修改异常,为什么呢?因为迭代器内部会维护一些索引位置相关的数据,要求在迭代过程中,容器不能发生结构性变化,否则这些索引位置就失效了。所谓结构性变化就是添加、插入和删除元素,只是修改元素内容不算结构性变化。

RandomAccess

public interface RandomAccess {
}

这个接口没有定义任何代码,这有什么用呢?这种没有任何代码的接口再java中被称为标记接口,用于声明类的一种属性。

实现了RandomAccess接口的类表示可以随机访问,可随机访问就是具备类似数组那样的特性,数据在内存是连续存放的,根据索引值就可以直接定位到具体的元素,访问效率很高。下节我们会介绍LinkedList,它就不能随机访问。

chapter22 注解

在声明式编程风格中,程序都由三个组件组成:

  • 声明的关键字和语法本身。

  • 系统/框架/库,他们负责解释、执行声明式语句。

  • 应用程序,使用声明式风格写程序。

声明式编程:SQL语言、编写网页CSS、正则表达式、函数式编程。

chapter 23 动态代理

切面:日志、性能监控、权限检查、数据库事务。

chapter24 类加载机制

负责加载类的类就是类加载器。

一个程序运行时,会创建一个Application ClassLoader,在程序中用到ClassLoader的地方,如果没有指定,一般用的都是这个ClassLoader,所以,这个ClassLoader也被称为系统类加载器(System ClassLoader)。

posted @ 2023-05-18 23:24  Jonathan1  阅读(93)  评论(0编辑  收藏  举报