1.1 数据类型
1.1.1 数值类型
1️⃣从java7开始,加上前缀0b或0B就可以写二进制;
2️⃣指数的表示
十进制中以10为底指数的表示:
十六进制中以2位底指数的表示:
3️⃣double中的严格浮点计算:strictfp
double类型的三个特殊值:增无穷大、负无穷大、NaN
Double.POSITIVE_INFINITY // 正无穷大
Double.NEGATIVE_INFINITY // 负无穷大
Double.NaN
1.1.2 Unicode和char类型
Unicode
专业术语
UTF-16编码采用不同长度的编码表示所有的Unicode码点
char类型
在java中,char类型描述了UTF-16的一个代码单元
char类型是用16位表示
有些unicode字符可以用一个char值来描述,另一些Unicode字符需要用两个char值来描述。
区分码点、代码单元的用途:
length() 方法
想要获取实际长度,即码点数量,即字符个数
获取第几个代码单元:
获取第几个码点:
1.1.3 大数值: BigInteger和BigDecimal
1.2 规范
1.2.1 变量
关于“$”
关于“++”的使用
1.3 运算符
1.3.1 位运算符
和<<运算符将位模式左移或右移
运算符会用0填充高位;>> 会用符号位填充高位;不存在<<< 运算符;
1.3.2 运算符的级别
1.4 String
1.1 String是不可变字符串。一定不要使用“==”检测两个字符串是否相等,每次连接字符串,都会构建一个新的String对象;
每次连接都会创建一个新的字符串,这样既耗时,又浪费空间。使用StringBuilder类就可以避免这个问题的发生。
StringBuilder的前身是StringBuffer。StringBuffer效率稍微有些低,但允许采用多线程的方式执行添加或删除字符的操作。
StringBuilder 除了能够apped字符串,还能添加代码单元,添加码点
所有字符串都在一个单线程中编辑,则应该用StringBuilder代替它。
1.2 String 的length方法将返回采用UTF-16编码表示的给定字符所需要的代码单元数量
要想获取实际的字符长度,即码点数量,可以调用:codePointCount 方法
public static void test04() throws UnsupportedEncodingException {
String greeting = “Hello”;
String codeStr = “\uD835\uDD46”;
byte[] b = codeStr.getBytes(“UTF-16”);
String s = new String(b, “UTF-16”);
System.out.println(“greeting:”+greeting);
System.out.println(“一个UTF-16编码的特殊符号s:”+s);
System.out.println("greeting.length() : " + greeting.length()); // 打印出 5
System.out.println("greeting.codePointCount() : " + greeting.codePointCount(0, greeting.length())); // 打印出5
System.out.println("s.length() : " + s.length()); // 打印出 2
System.out.println("s.codePointCount() : " + s.codePointCount(0, s.length())); // 打印出 1
}
1.3 连接字符串:join 方法,可以指定分界符
1.4 所有字符串都属于CharSequence接口
1.5 控制流程
1.6 数组
类
在一个源文件中,只能有一个共有类,但可以有任意数目的非公有类。
构造器
finalize方法
对象
Java的对象都是在堆中创造;
⚠️警告:不要编写返回引用可变对象的访问器方法。如果需要返回一个可变对象的引用,应该首先对它进行克隆(clone)。
Date对象有一个更改器方法setTime。
也就是说Date对象是可变的,这一点就破坏了
封装性!
*/
public Date gethireDay(){
return this.hireDay;
}
- // 正确的做法
public Date getHireDay(){
return (Date)this.hireDay.clone();
}
封装:
封装通常提供三项内容:
封装的好处:
警告:永远不要编写返回引用 可变对象 的方法器方法。
调用构造器的具体处理步骤:
0、静态域在类被加载的时候进行初始化。静态代码块在类第一次的时候执行一次。(创建对象的时候不再执行)
1、所有数据域被初始化为默认值(0、false、或null);
2、按照在类声明中出现的顺序,依次执行所有域初始化语句和初始化块;
3、如果构造器第一行调用了第二个构造器,则执行第二个构造器主体;
4、执行这个构造器的主体;
修饰符:
final 实例域
static 方法
静态导入(import)
其他
编译运行带有包的源文件
类路径
文档注释:javadoc -d docDirectory nameofPackage1 nameofPackage2 ……
Java用于控制可见性的4个修饰符:
private 仅对本类可见;
public 对所有类可见;
protected 对本包和所有子类可见;
默认,不需要修饰符 对本包可见;
类的加载:
在启动时,包含main方法的类被加载。它会加载所需要的类。这些被加载的类又要加载它们需要的类,依次类推。
对于一个大型的应用,可以使用技巧给用户一个启动速度比较快的幻觉:
1、确保包含main方法的类没有显式地引用其他类;
2、显示一个启动画面;
3、通过调用Class.forName手工加载其他的类;
继承是Java的核心技术
super和this
有些人认为super与this引用是类似的概念,实际上这样的比较并不恰当。因为super并不是一个对象的引用,不能讲super赋给另一个对象变量,它只是一个指示编译器调用超类方法的特殊关键字。
this的用途
super的用途
子类和超类
子类
子类构造器
类型转化
多态和动态绑定
抽象类
多态:
动态绑定
覆盖
Object
编写一个完美的equals方法
设计原则,equals方法要满足五个特性:
设计思路:
完美的equals方法的建议:
1、显示参数命名为otherObject,稍后需要将它转换成另一个叫other的变量;
2、检测this域otherObject是否引用同一个对象:if (this == otherObject) return true;
3、检测otherObject 是否为null,如果为null,返回false: if (otherObjct == null) return false;
4、比较 this与otherObject 是否属于同一个类,如果equals的语义在每个子类中有所改变,就是用getClass检!= otherObject.getClass()) return false;
4、如果所有的子类都有统一的语义,就使用instanceof检测:
if (!(instanceof instanceof ClassName)) return false;
- Java为每个类型管理了一个Class对象。因此,可以利用 == 运算符实现两个类对象比较的操作。
- 5、将otherObject转化为相应的类型变量:
ClassName other = (ClassName) otherName;
- 6、现在开始对所有需要比较的域进行比较了。使用==比较基本类型域,使用equals比较对象域。所有的域都匹配返回true,否则返回false;
- 7、如果子类中重新定义equals,就要在其中包含调用super.equals(other)
- 如果重新定义equals方法,就必须重新定义hashCode方法
- toString方法
- 只要对象与一个字符串通过操作符“+”连接起来,Java编译器就会自动调用toString方法
- 在调用x.toString() 的地方可以用""+x替代
- println 方法就会直接调用x.toString() 并打印输出得到的字符串
- 强烈建议为自定义的每一个类增加toString方法
泛型数组列表ArrayList
ensureCapacity 方法
trimToSize 方法
在比较两个枚举类型的值时,永远不需要调用equals,而直接使用“==”就可以了
所有的枚举类型都是Enum类的子类
最有用的方法
toString() 这个方法能够返回枚举常量名
valueOf() 这个方法是toString的逆方法
values() 这个方法返回一个包含全部枚举值的数组
ordinal() 这个方法返回enum声明中枚举常量的位置,位置从0开始计数。
能够分析类能力的的程序称为反射
2.1.1 Class类
一个Class对象实际上表示的是一个类型,而这个类型未必一定是一种类。例如:int不是类,但int.class 是一个class类型的对象
Java为每个类型管理了一个Class对象。因此,可以利用 == 运算符实现两个类对象比较的操作。
创建一个类的实例
利用反射分析类的能力
实战:构建一个通用的toString 方法
基础概念:
抽象类和接口
新认识:
静态方法
public interface Path{
public static Path get(String first, String …more){
return FileSystem.getDefault().getPath(first,more);
}
}
默认方法
public interface Comparable {
default int compareTo(T other){return 0;}
}
可以为接口方法提供一个默认实现。必须使用default修饰符标记这样一个方法。
默认方法可以调用任何其他方法
默认方法的一个重要用途是“接口演化”
解决默认方法的冲突:
标记接口:
函数式接口:只有一个抽象方法的接口
概要:
lambda表达式是一个可传递的代码块,可以在以后执行一次或多次。
函数式接口:只有一个抽象方法的接口
在java中,lambda表达式所能做的也只是能转换为函数式接口。
想要用lambda表达式做某些处理,还是要谨记表达式的用途,为它建立一个特定的函数式接口。
ArrayList类有一个removeIf方法,它的参数就是一个Predicate。这个接口专门用来传递lambda表达式。
lambda 表达式的形式及组成:
lambda 表达式形式:参数,箭头(->)以及一个表达式
lambda有三部分构成:
代码块以及自由变量有一个术语,叫:闭包
lambda表达式可以捕获外围作用域中变量的值。但这里有一条规则:lambda表达式中捕获的变量必须实际上是最终变量。实际变量是指,这个变量初始化之后就不会再为它赋新值。
lambda中不能有同名的局部变量;
lambda 表达式中使用this关键字时,是指创建这个lambda表达式的方法的this参数。
这里的this指的是ActionListener
lambda 表达式补充:
方法引用
有些时候已经有现成的方法可以完成你想要传递到其他代码的某个动作。
方法引用,主要有三种情况:
object::instanceMethod
Class::staticMethod
Class::instanceMethod
方法引用中可以使用this和super
构造器引用
为什么使用内部类:
内部类既可以访问自身的数据域,也可以访问创建它的外围类对象的数据域。
只有内部类可以是私有类,而常规类只可以具有包可见性,或公有可见性。
内部类中所有的静态方法域都必须是final
内部类不能有static方法
只有内部类可以声明为static
内部类的种类
局部内部类
匿名内部类
静态内部类
异常的层次结构:
所有异常都是由Throwable继承而来,
而下一层立即分为两类:Error和Exception
Exception层又分解为两支:一支派生于RuntimeException,另一支包含其他异常
由程序错误导致的异常属于RuntimeException
而程序本身没问题,但由于像I/O错误这类问题导致的异常属于其他异常
受查异常和非受查异常
策略:一个方法必须声明所有可能抛出的受查异常,而非受查异常要么不可控(Error),要么就应该避免发生。
合并catch语句
形式: catch(XXXException | XXXException e)
只有当捕获的异常类型彼此之间不存在子类关系时才需要这个特性。
捕获多个异常时,异常变量隐含为final变量,不能再子句体中为e赋不同的值。
再次抛出异常,可以将原始异常设置为新异常的原因
强烈建议将try/catch和try/finally语句块解耦,这样可以提高代码的清晰度。
带资源的try语句
}
- 无论这个块如何退出,in和out都会关闭
2.7.1 泛型类
2.7.2 泛型方法
2.7.2 类型变量的限定
例如:
public static T min(T[] a)…
一个类型变量使用通配符可以有多个限定:
T extends Comparable & Serializable
为了提高效率,应该将标签(tagging)接口(即没有方法的接口)放在边界列表的末尾
2.7.3 类型擦除
2.7.4 通配符类型
例如:
Pain extends Employee>
通配符限定与类型限定十分类似
但,通配符限定有一个附加能力,可以指定一个超类型限定,如:? super Manager ,这个通配符限制为Manager的所有超类型
2.7.5 无限定通配符
class Pair>{
? getFirst()
void setFirst(?)
}
用来测试一个pair是否包含一个null引用
非法的代码:? t = p.getFirst();// Error
4.1.1 创建Jar文件;
4.1.2 清单文件;
4.1.3 可执行Jar文件;
4.2.1 属性映射
4.2.2 首选项API
Iterable
接口
Collection
Collection 是接口。
Collection实现Iterable的接口,这样就能够使用for each 语法了
Map
SortMap
Iterator
AbstractCollection
AbstractList
AbstractSequentialList
ArrayList
数组和数组列表都有一个重大的缺陷。这就是从数组的中间位置删除一个元素要付出很大的代价。在数组中间的位置上插入一个元素也是如此。
链表(Linked list) 解决了这个问题。
AbstractSet
HashSet
EnumSet
TreeSet
AbstractQueue
AbstractMap
HashMap
TreeMap
EnumMap
WeakHashMap
IdentityHashMap
双端队列
优先级队列
散列表可以快速查找所需要的对象。
原理:在Java中,散列表用链表数组实现。每个列表称为桶。要想查到表中对象的位置,就要先计算它的散列码,然后与桶的总数取余,所得的结果就是保存这个元素的桶的索引。
HashSet类,实现了基于散列表的值。
TreeSet
用迭代处理映射的键和值
HashMap和TreeMap
两个通用的实现。
要迭代处理映射的键和值,最容易的方法是使用forEach方法。可以提供一个接收键和值的lamdba表达式。
LinkedHashMap
链接散列映射将用访问顺序,而不是插入顺序,对映射条目进行迭代。
WeekHashMap
IdentityHashMap
映射类keySet() 返回一个实现了Set接口的类对象,这个类的方法对原映射进行操作。这种结合称为视图。
Arrays类的静态方法asList将返回一个包装了普通Java数组的List包装器。
Card[] cardDeck = new Card[52];
List cardList = Arrays.asList(cardDeck);
返回的对象不是ArrayList。它是一个视图对象。
同步视图
受查视图
多进程和多线程的区别
开启一个线程
方案一:实现Runnable接口
lambda方式
内部类方式
方案二:构建一个Thread类的子类
内部类方式
继承的方案
每个线程都应该不时地检查这个标志,以判断线程是否被中断
发送中断请求 interrupt()
异常
检查当前线程,并将中断状态重置为false
boolean isInterrupted()
New 新创建
new Thread®
Runnable 可运行
一旦调用start方法,线程处于runnable状态
被阻塞线程和等待线程
实际上被阻塞状态与等待状态时有很大不同的
Blocked 被阻塞
当一个线程试图获取一个内部的对象锁,而该锁被其他线程持有,则该线程进入阻塞状态。
当所有其他线程释放该锁,并且线程调度器允许本线程持有它的时候,该线程将变为非阻塞状态。
Waiting 等待
在调用Object.wait 方法或Thread.join 方法,或者等待java.util.concurrent 库中的Lock或Condition时,就会出现这种情况。
Timed waiting 计时等待
带有超时参数的方法:
Thread.sleep、Object.wait、Thread.join、Lock.tryLock以及Condition.await的计时版
Terminated 被终止
原因一: run方法正常退出而自然死亡;
原因二: 因为一个没有捕获的异常终止了run方法而意外死亡
线程的优先级
守护线程
守护线程的唯一用途是为其他线程提供服务。
当只剩下守护线程时,虚拟机就退出了,由于如果只剩下守护线程,就没必要继续运行程序了。
守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。
setDaemon(boolean isDaemon),标记为守护线程,该方法必须在线程启动之前调用
未捕获异常处理器
当线程发生异常终止的时候,异常会被传递到一个用于未捕获异常的处理器。
处理器必须实现一个Thread.UncaughtExceptionHandler接口的类
安装线程处理器
可以使用setUncaughtExceptionHandler()方法为任何线程安装一个处理器
也可以用Thread类的静态方法setDefaultUncaughtExceptionHandler为所有线程安装一个默认的处理器
背景:在多线程应用中,两个或两个以上的线程需要共享对同一数据的存取。
进行同步的两种机制
方案一:synchronized关键字,同步方法
方案二: 使用锁
方案三: 同步代码块
锁对象
方案二:ReentrantLock类, jdk1.5提供
ReentrantLock()
ReetrantLock(boolean fair)
myLock.lock();// a ReentrantLock object
try{
critical section
}finally{
// make sure that lock is unlocked even if an exception is thrown
myLock.unlock();
}
概述
条件对象
Condition
每个条件对象管理哪些已经进入被保护的代码段但还不能运行的线程。
bankLock.lock();
try{
while(condition){
condition.await();
}
critical section
condition.signalAll();
}finally{
bankLock.unlock();
}
synchronized 方法
从jdk1.0 版本,Java中的每一个对象都有一个内部锁。如果一个方法用synchronized关键字声明,那么对象的锁将保护整个方法。 也就是说,要调用方法,线程必须获得内部锁的对象锁。
案例:
synchronized 代码块
private Object lock = new Object();
public void method(){
synchronized(lock){
critical section
}
}
volatile 域
final变量
final Map
原子性
死锁
线程的局部变量
使用ThreadLock辅助类为各个线程提供各自的实例
例如: SimpleDateFormat不是线程安全的。
public static final SimpleDateFormat dataFormat = new SimpleDateFormat(“yyyy-MM-dd”);
如果两个线程都执行以下的操作:
String dateStamp = dateFormat.format(new Date());
dateFormat 使用的内部数据结构可能会被并发的访问破坏。
可以使用同步,但开销很大;也可以在需要的时候创建一个局部的SimpleDateFormat对象,不过这也太浪费了。
可以使用以下代码,为每个线程创建一个实例:
java.util.Random类是线程的。但如果多个线程需要等待一个共享的随机数生成器,这会很低效。可以使用ThreadLocal辅助类为各个线程提供一个单独的生成器。
不过jdk1.7 提供了一个便利类。只需要做以下调用:
int random= ThreadLoclRandom.current().nextInt(upperBound);
锁测试与超时
myLock.tryLock()
myLock.tryLock(100, TimeUnit.MiLLISECONDS))
myLock.await(100, TimeUnit.MiLLISECONDS))
读/写锁
ReentrantReadWriteLock
很多线程从一个数据结构读取数据而很少线程修改器数据的话,ReentrantReadWriteLock是十分有用的。
步骤
// 构建对象
priavte ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
// 获取读锁和写锁
private Lock readLock = rwl.readLock();
private Lock writeLock = rwl.writeLock();
对读方法加读锁
对写方法加写锁
弃用stop和suspend方法
阻塞队列就是线程安全的集合。
背景
java.util.concurrent
LinkedBlockingQueue
LinkedBlockingDeque
ArrayBlockingQueue
PriorityBlockingQueue
DelayQueue
TransferQueue 接口(jdk1.7 增加)
LinkedTransferQueue
这个接口,允许生产者线程等待,直到消费者准备就绪可以接受一个元素。
使用
第一类
第二类
第三类
offer、poll、和peek方法
高效的映射、集、队列
java.util.concurrent
确定这样的集合当前的大小通常需要遍历
同步包装器
任何集合类都可以通过同步包装器(synchronizationwrapper)变成线程安全的
如果在另一个线程可能进行修改时要对集合进行迭代,仍然需要使用“客户端”锁定
Runnable封装一个一部运行的任务,可以将其想象成为一个没有参数和返回值的异步方法。
Callable与Runnable类型,但是有返回值。Future 保存异步计算的结果
Callable接口是一个参数化的类型,只有一个方法call
可以启动一个计算,将Future对象交给某个线程,然后忘掉它。Future对象的所有者在计算好之后就可以获得它。
分析Future的方法
get() throws…
get(long timeout, TimeUnit unit) throws…
如果在运行过程中,线程被中断,两个方法都将抛出InterruptedException。如果计算已经完成,那么get方法立刻返回
FutureTask 包装器
使用线池的好处
通过执行器类Executor的静态方法来构建线程池
newCachedThreadPool
newFixedThreadPool
newSingleThreadExecutor
预执行器
newScheduledThreadPool
newSingleThreadScheduledExecutor
为预定执行或重复执行任务而设计
实现线程池的步骤
第一步:调用Executor类中静态方法创建线程池
第二步:调用线程池submit方法提交Runnable或Callable对象;
第三步:保存好返回的Future对象
第四步:当不再提交任何任务的时候,调用shutdown方法。关闭线程池
控制任务组
有时,使用执行器更有实际意义的原因,控制一组相关任务
invokeAny
ExecutorCompletionService
Fork-Join框架
讲一个大任务拆分成一个个小任务,最后将结果汇总
步骤
步骤一:扩展一个RecursiveTask类,或扩展RecursiveAction类;
第二步:创建ForkJoinPool 对象,并调用invoke方法提交recursiveTask
《Java编程思想》
《Effective Java》
现代操作系统 或 操作系统精髓
XMind: ZEN - Trial Version