对象与类
Java 程序设计语言对对象采用的不是引用调用,实际上,对象引用是按值传递的。
一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)。
一个方法可以改变一个对象参数的状态。
- 方法得到的参数是对象引用的拷贝,即引用同一个对象
如果多个方法(比如, StringBuilder 构造器方法)有相同的名字、不同的参数,便产生了重载。
- 举例:indexOf(int) indexOf(int, int) indexOf(String) indexOf(String, int)
继承
一个对象变量(例如,变量 e ) 可以指示多种实际类型的现象被称为多态(polymorphism)。 在运行时能够自动地选择调用哪个方法的现象称为动态绑定(dynamic binding )。
- 当 e 引用 Employee 对象时,e.getSalary( ) 调用的是 Employee 类中的 getSalary 方法;当 e 引用 Manager对象时,e.getSalary( ) 调用的是 Manager 类中的 getSalary 方法
- 一个 Employee 变量既可以引用一个 Employee 类对象,也可以引用一个 Employee 类的任何一个子类的对象
- 使用多态方法或接口编写的代码比使用对多种类型进行检测的代码更加易于维护和扩展
不允许扩展(extend)的类被称为 final 类。如果在定义类的时候使用了 final 修饰符就表明这个类是 final 类。
类中的特定方法也可以被声明为 final。如果这样做,子类就不能覆盖这个方法。
- final 的作用:阻止继承
- 如果将一个类声明为 final,则其中的方法自动地成为 final,但不包括域
- 若类中域为 final 域,则该域值不允许被修改
接口、lambda 表达式与内部类
接口绝不能含有实例域,在 JavaSE 8 之前,也不能在接口中实现方法。提供实例域和方法实现的任务应该由实现接口的那个类来完成。因此,可以将接口看成是没有实例域的抽象类。
- 接口中可以有常量,但不能有实例域
Java 不支持多继承,接口可以提供多重继承的大多数好处,同时还能避免多重继承的复杂性和低效性。
- 多重继承:一个类有多个超类
集合
当在程序中使用队列时,一旦构建了集合就不需要知道究竟使用了哪种实现。因此,只有在构建集合对象时,使用具体的类才有意义。
- 队列有两种实现方式:循环数组和链表。声明队列时,就决定了队列的实现方式。
Queue expresslane = new CircularArrayQueue<>(100)
Queue expresslane = new LinkedListQueue<>(100)
在 Iterator 接口中没有 add 方法。相反地,集合类库提供了子接口 Listlterator,其中包含 add 方法。
- add 方法将元素添加到迭代器当前位置之前,remove 方法移除上一个访问过的元素
- add 方法只依赖于迭代器的位置,而 remove 方法依赖于迭代器的状态
- 为了避免发生并发修改的异常,请遵循下述简单规则:可以根据需要给容器附加许多的迭代器,但是这些迭代器只能读取列表。另外,再单独附加一个既能读又能写的迭代器。
在 Java 中,散列表用链表数组实现,每个列表被称为桶(bucket)
散列映射(HashMap)中,散列或比较函数只能作用于键,与键关联的值不能进行散列或比较
- Java 用拉链法解决散列冲突,即使用单向链表来存储相同索引值的元素
- Java 8 中使用平衡树来替代链表存储冲突的元素
- 若装填因子过高,则散列表就会用双倍的桶数自动地进行再散列
树集是一个有序集合,可以以任意顺序将元素插入到集合中。在对集合进行遍历时,每个值将自动地按照排序后的顺序呈现。
优先级队列使用了一个优雅且高效的数据结构,称为堆(heap)。堆是一个可以自我调整的二叉树,对树执行添加和删除操作,可以让最小的元素移动到根,而不必花费时间对元素进行排序。
- set 不能存储相同的元素
- TreeSet 和 PriorityQueue 中的元素都是有序的
并发
在任何给定时刻,一个可运行的线程可能正在运行也可能没有运行(这就是为什么将这个状态称为可运行而不是运行)。
- Java 线程状态没有“正在运行”,一个正在运行中的线程仍然处于可运行状态
当一个线程试图获取一个内部的对象锁(而不是 java.util.concurrent 库中的锁),而该锁被其他线程持有,则该线程进人阻塞状态。
当线程等待另一个线程通知调度器一个条件时,它自己进入等待状态。
- 阻塞和等待的区别在于阻塞状态是线程还没有拿到锁(未执行临界区代码),而等待状态是拿到锁后暂时释放锁,等待条件成熟会再次获取锁(已进入临界区)
- sleep() 方法不会释放锁,wait() 方法释放对象锁
守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。
- 守护线程的唯一用途是为其他线程提供服务,计时线程就是一个例子
- 守护线程要考虑到“突然关机”的情况
有两种机制防止代码块受并发访问的干扰。Java语言提供一个 synchronized 关键字达到这一目的,并且 Java SE 5.0引入了 ReentrantLock 类。
如果一个方法用 synchronized 关键字声明,那么对象的锁将保护整个方法。也就是说,要调用该方法,线程必须获得内部的对象锁。
- synchronized 关键字等价于给方法加一个显式的锁
- synchronized 方法只有一个条件对象。使用 wait 方法添加一个线程到等待集中,notifyAll/notify 方法解除等待线程的阻塞状态。
volatile 关键字为实例域的同步访问提供了一种免锁机制。如果声明一个域为 volatile,那么编译器和虚拟机就知道该域是可能被另一个线程并发更新的。
- 并发编程要考虑原子性和可见性。原子性指一个操作不会被中断,要么不做,要么全做;可见性指对共享变量的修改会立即同步回主内存,因此能及时被其他线程看到。
- volatile 和 synchronized 都能保证可见性
- volatile 不能保证原子性,仍可能读到脏数据,例如自增操作。一个线程从主存读取数据并自增,但未写回主存就被另一线程中断,则另一线程读到的仍是原始的数据。
- synchronized 可以保证原子性,只能有一个线程执行被保护的代码块,保护级别更高
- 假设对共享变量除了赋值之外并不完成其他操作,那么可以将这些共享变量声明为 volatile