Java笔记
Java快速入门
1.1第一个java程序
| 1 | public class test1{ | 
继承
Student是Person子类
向上转型
| 1 | Student s = new Student(); | 
向下转型
| 1 | Person p1 = new Student(); // upcasting, ok | 
instanceof,在向下转移前可以先判断
多态
 Override是覆写(重写)Overload是新方法
接口
定义一个接口用interface
实现一个接口用implements 
java核心类
1.1 字符串和编码
比较字符串用equals(),要忽略大小写比较,使用equalsIgnoreCase()方法
学习地址:https://www.liaoxuefeng.com/wiki/1252599548343744/1260469698963456
1.2 StringBuilder
StringBuffer,这是Java早期的一个StringBuilder的线程安全版本,它通过同步来保证多个线程操作StringBuffer也是安全的,但是同步会带来执行速度的下降。
StringBuilder和StringBuffer接口完全相同,现在完全没有必要使用StringBuffer。
1.3 包装类型
- 基本类型: - byte,- short,- int,- long,- boolean,- float,- double,- char
- 引用类型:所有 - class和- interface类型
- 基本类型 - 对应的引用类型 - boolean - java.lang.Boolean - byte - java.lang.Byte - short - java.lang.Short - int - java.lang.Integer - long - java.lang.Long - float - java.lang.Float - double - java.lang.Double - char - java.lang.Character 
1.X BigInteger
java.math.BigInteger用来表示任意大小的整数。BigInteger内部用一个int[]数组来模拟一个非常大的整数;
和long型整数运算比,BigInteger不会有范围限制,但缺点是速度比较慢;
BigInteger和Integer、Long一样,也是不可变类,并且也继承自Number类;
使用longValueExact()方法时,如果超出了long型的范围,会抛出ArithmeticException;
如果BigInteger的值超过了float的最大范围,返回 Infinity 。
1.X BigDecimal
BigDecimal用于表示精确的小数,常用于财务计算;
比较BigDecimal的值是否相等,必须使用compareTo()而不能使用equals()。
1.x Java提供的常用工具类有:
- Math:数学计算
- Random:生成伪随机数
- SecureRandom:生成安全的随机数
异常处理
因为Java的异常是class,它的继承关系如下:
| 1 | ┌───────────┐ | 
异常类型
| 1 | Exception | 
日志,使用JDK Logging
JDK的Logging定义了7个日志级别,从严重到普通:
- SEVERE
- WARNING
- INFO
- CONFIG
- FINE
- FINER
- FINEST
因为默认级别是INFO,因此,INFO级别以下的日志,不会被打印出来。使用日志级别的好处在于,调整级别,就可以屏蔽掉很多调试相关的日志输出。
日志,使用Commons Logging
默认情况下,Commons Loggin自动搜索并使用Log4j(Log4j是另一个流行的日志系统),如果没有找到Log4j,再使用JDK Logging。
Commons Logging定义了6个日志级别:
- FATAL
- ERROR
- WARNING
- INFO
- DEBUG
- TRACE
默认级别是INFO。
反射
class类
方法一:直接通过一个class的静态变量class获取:
| 1 | Class cls = String.class; | 
方法二:如果我们有一个实例变量,可以通过该实例变量提供的getClass()方法获取:
| 1 | String s = "Hello"; | 
方法三:如果知道一个class的完整类名,可以通过静态方法Class.forName()获取:
| 1 | Class cls = Class.forName("java.lang.String"); | 
注意一下Class实例比较和instanceof的差别:
| 1 | Integer n = new Integer(123); | 
用instanceof不但匹配指定类型,还匹配指定类型的子类。而用==判断class实例可以精确地判断数据类型,但不能作子类型比较。
通常情况下,我们应该用instanceof判断数据类型,因为面向抽象编程的时候,我们不关心具体的子类型。只有在需要精确判断一个类型是不是某个class的时候,我们才使用==判断class实例。
如果获取到了一个Class实例,我们就可以通过该Class实例来创建对应类型的实例:
| 1 | // 获取String的Class实例: | 
上述代码相当于new String()。通过Class.newInstance()可以创建类实例,它的局限是:只能调用public的无参数构造方法。带参数的构造方法,或者非public的构造方法都无法通过Class.newInstance()被调用。
访问字段
- Field getField(name):根据字段名获取某个public的field(包括父类)
- Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
- Field[] getFields():获取所有public的field(包括父类)
- Field[] getDeclaredFields():获取当前类的所有field(不包括父类)
- getDeclaredFiled 仅能获取类本身的属性成员(包括私有、共有、保护)
- getField 仅能获取类(及其父类可以自己测试) public属性成员
Java的反射API提供的Field类封装了字段的所有信息:
通过Class实例的方法可以获取Field实例:getField(),getFields(),getDeclaredField(),getDeclaredFields();
通过Field实例可以获取字段信息:getName(),getType(),getModifiers();
通过Field实例可以读取或设置某个对象的字段,如果存在访问限制,要首先调用setAccessible(true)来访问非public字段。
通过反射读写字段是一种非常规方法,它会破坏对象的封装。
调用方法
- Method getMethod(name, Class...):获取某个- public的- Method(包括父类)
- Method getDeclaredMethod(name, Class...):获取当前类的某个- Method(不包括父类)
- Method[] getMethods():获取所有- public的- Method(包括父类)
- Method[] getDeclaredMethods():获取当前类的所有- Method(不包括父类)
一个Method对象包含一个方法的所有信息:
- getName():返回方法名称,例如:- "getScore";
- getReturnType():返回方法返回值类型,也是一个Class实例,例如:- String.class;
- getParameterTypes():返回方法的参数类型,是一个Class数组,例如:- {String.class, int.class};
- getModifiers():返回方法的修饰符,它是一个- int,不同的bit表示不同的含义。
小结
Java的反射API提供的Method对象封装了方法的所有信息:
通过Class实例的方法可以获取Method实例:getMethod(),getMethods(),getDeclaredMethod(),getDeclaredMethods();
通过Method实例可以获取方法信息:getName(),getReturnType(),getParameterTypes(),getModifiers();
通过Method实例可以调用某个对象的方法:Object invoke(Object instance, Object... parameters);
通过设置setAccessible(true)来访问非public方法;
通过反射调用方法时,仍然遵循多态原则。
调用构造方法
调用Class.newInstance()的局限是,它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。
- getConstructor(Class...):获取某个- public的- Constructor;
- getDeclaredConstructor(Class...):获取某个- Constructor;
- getConstructors():获取所有- public的- Constructor;
- getDeclaredConstructors():获取所有- Constructor。
Constructor对象封装了构造方法的所有信息;
通过Class实例的方法可以获取Constructor实例:getConstructor(),getConstructors(),getDeclaredConstructor(),getDeclaredConstructors();
通过Constructor实例可以创建一个实例对象:newInstance(Object... parameters); 通过设置setAccessible(true)来访问非public构造方法。
获取继承关系
小结
通过Class对象可以获取继承关系:
- Class getSuperclass():获取父类类型;
- Class[] getInterfaces():获取当前类实现的所有接口。
通过Class对象的isAssignableFrom()方法可以判断一个向上转型是否可以实现。
泛型
Java的泛型是采用擦拭法实现的;
擦拭法决定了泛型<T>:
- 不能是基本类型,例如:int;
- 不能获取带泛型类型的Class,例如:Pair<String>.class;
- 不能判断带泛型类型的类型,例如:x instanceof Pair<String>;
- 不能实例化T类型,例如:new T()。
泛型方法要防止重复定义方法,例如:public boolean equals(T obj);
子类可以获取父类的泛型类型<T>。
小结
使用类似<? extends Number>通配符作为方法参数时表示:
- 方法内部可以调用获取Number引用的方法,例如:Number n = obj.getFirst();;
- 方法内部无法调用传入Number引用的方法(null除外),例如:obj.setFirst(Number n);。
即一句话总结:使用extends通配符表示可以读,不能写。
使用类似<T extends Number>定义泛型类时表示:
- 泛型类型限定为Number以及Number的子类。
对比extends和super通配符
我们再回顾一下extends通配符。作为方法参数,<? extends T>类型和<? super T>类型的区别在于:
- <? extends T>允许调用读方法- T get()获取- T的引用,但不允许调用写方法- set(T)传入- T的引用(传入- null除外);
- <? super T>允许调用写方法- set(T)传入- T的引用,但不允许调用读方法- T get()获取- T的引用(获取- Object除外)。
一个是允许读不允许写,另一个是允许写不允许读。
无限定通配符
因为<?>通配符既没有extends,也没有super,因此:
- 不允许调用set(T)方法并传入引用(null除外);
- 不允许调用T get()方法并获取T引用(只能获取Object引用)。
换句话说,既不能读,也不能写,那只能做一些null判断:
小结
使用类似<? super Integer>通配符作为方法参数时表示:
- 方法内部可以调用传入Integer引用的方法,例如:obj.setFirst(Integer n);;
- 方法内部无法调用获取Integer引用的方法(Object除外),例如:Integer n = obj.getFirst();。
即使用super通配符表示只能写不能读。
使用extends和super通配符要遵循PECS原则。
无限定通配符<?>很少使用,可以用<T>替换,同时它是所有<T>类型的超类。
集合
HashMap
要正确使用HashMap,作为key的类必须正确覆写equals()和hashCode()方法;
一个类如果覆写了equals(),就必须覆写hashCode(),并且覆写规则是:
- 如果equals()返回true,则hashCode()返回值必须相等;
- 如果equals()返回false,则hashCode()返回值尽量不要相等。
实现hashCode()方法可以通过Objects.hashCode()辅助方法实现。
TreeMap
SortedMap在遍历时严格按照Key的顺序遍历,最常用的实现类是TreeMap;
作为SortedMap的Key必须实现Comparable接口,或者传入Comparator;
要严格按照compare()规范实现比较逻辑,否则,TreeMap将不能正常工作。
Properties
Java集合库提供的Properties用于读写配置文件.properties。.properties文件可以使用UTF-8编码。
可以从文件系统、classpath或其他任何地方读取.properties文件。
读写Properties时,注意仅使用getProperty()和setProperty()方法,不要调用继承而来的get()和put()等方法。
Set
Set用于存储不重复的元素集合,它主要提供以下几个方法:
- 将元素添加进Set<E>:boolean add(E e)
- 将元素从Set<E>删除:boolean remove(Object e)
- 判断是否包含元素:boolean contains(Object e)
Queue
在Java的标准库中,队列接口Queue定义了以下几个方法:
- int size():获取队列长度;
- boolean add(E)/- boolean offer(E):添加元素到队尾;
- E remove()/- E poll():获取队首元素并从队列中删除;
- E element()/- E peek():获取队首元素但并不从队列中删除。
PriorityQueue
PriorityQueue实现了一个优先队列:从队首获取元素时,总是获取优先级最高的元素。
PriorityQueue默认按元素比较的顺序排序(必须实现Comparable接口),也可以通过Comparator自定义排序算法(元素就不必实现Comparable接口)。
Deque
| Queue | Deque | |
|---|---|---|
| 添加元素到队尾 | add(E e) / offer(E e) | addLast(E e) / offerLast(E e) | 
| 取队首元素并删除 | E remove() / E poll() | E removeFirst() / E pollFirst() | 
| 取队首元素但不删除 | E element() / E peek() | E getFirst() / E peekFirst() | 
| 添加元素到队首 | 无 | addFirst(E e) / offerFirst(E e) | 
| 取队尾元素并删除 | 无 | E removeLast() / E pollLast() | 
| 取队尾元素但不删除 | 无 | E getLast() / E peekLast() | 
Deque实现了一个双端队列(Double Ended Queue),它可以:
- 将元素添加到队尾或队首:addLast()/offerLast()/addFirst()/offerFirst();
- 从队首/队尾获取元素并删除:removeFirst()/pollFirst()/removeLast()/pollLast();
- 从队首/队尾获取元素但不删除:getFirst()/peekFirst()/getLast()/peekLast();
- 总是调用xxxFirst()/xxxLast()以便与Queue的方法区分开;
- 避免把null添加到队列。
Stack
IO流
IO流是一种流式的数据输入/输出模型:
- 二进制数据以byte为最小单位在InputStream/OutputStream中单向流动;
- 字符数据以char为最小单位在Reader/Writer中单向流动。
Java标准库的java.io包提供了同步IO功能:
- 字节流接口:InputStream/OutputStream;
- 字符流接口:Reader/Writer。
File
Java标准库的java.io.File对象表示一个文件或者目录:
- 创建File对象本身不涉及IO操作;
- 可以获取路径/绝对路径/规范路径:getPath()/getAbsolutePath()/getCanonicalPath();
- 可以获取目录的文件和子目录:list()/listFiles();
- 可以创建或删除文件和目录。
InputStream
Java标准库的java.io.InputStream定义了所有输入流的超类:
- FileInputStream实现了文件流输入;
- ByteArrayInputStream在内存中模拟一个字节流输入。
总是使用try(resource)来保证InputStream正确关闭。
OutputStream
Java标准库的java.io.OutputStream定义了所有输出流的超类:
- FileOutputStream实现了文件流输出;
- ByteArrayOutputStream在内存中模拟一个字节流输出。
某些情况下需要手动调用OutputStream的flush()方法来强制输出缓冲区。
总是使用try(resource)来保证OutputStream正确关闭。
| 1 | input.transferTo(output); // copy功能;目录已存在 | 
Reader
Reader定义了所有字符输入流的超类:
- FileReader实现了文件字符流输入,使用时需要指定编码;
- CharArrayReader和- StringReader可以在内存中模拟一个字符流输入。
Reader是基于InputStream构造的:可以通过InputStreamReader在指定编码的同时将任何InputStream转换为Reader。
总是使用try (resource)保证Reader正确关闭。
使用InputStreamReader,可以把一个InputStream转换成一个Reader。
Writer
Writer定义了所有字符输出流的超类:
- FileWriter实现了文件字符流输出;
- CharArrayWriter和- StringWriter在内存中模拟一个字符流输出。
使用try (resource)保证Writer正确关闭。
Writer是基于OutputStream构造的,可以通过OutputStreamWriter将OutputStream转换为Writer,转换时需要指定编码。
正则表达式
小结
单个字符的匹配规则如下:
| 正则表达式 | 规则 | 可以匹配 | 
|---|---|---|
| A | 指定字符 | A | 
| \u548c | 指定Unicode字符 | 和 | 
| . | 任意字符 | a,b,&,0 | 
| \d | 数字0~9 | 0~`9` | 
| \w | 大小写字母,数字和下划线 | az,AZ,0~`9,_` | 
| \s | 空格、Tab键 | 空格,Tab | 
| \D | 非数字 | a,A,&,_,…… | 
| \W | 非\w | &,@,中,…… | 
| \S | 非\s | a,A,&,_,…… | 
多个字符的匹配规则如下:
| 正则表达式 | 规则 | 可以匹配 | 
|---|---|---|
| A* | 任意个数字符 | 空, A,AA,AAA,…… | 
| A+ | 至少1个字符 | A,AA,AAA,…… | 
| A? | 0个或1个字符 | 空, A | 
| A{3} | 指定个数字符 | AAA | 
| A{2,3} | 指定范围个数字符 | AA,AAA | 
| A{2,} | 至少n个字符 | AA,AAA,AAAA,…… | 
| A{0,3} | 最多n个字符 | 空, A,AA,AAA | 
小结
复杂匹配规则主要有:
| 正则表达式 | 规则 | 可以匹配 | 
|---|---|---|
| ^ | 开头 | 字符串开头 | 
| $ | 结尾 | 字符串结束 | 
| [ABC] | […]内任意字符 | A,B,C | 
| [A-F0-9xy] | 指定范围的字符 | A,……,F,0,……,9,x,y | 
| [^A-F] | 指定范围外的任意字符 | 非 A~`F` | 
| AB|CD|EF | AB或CD或EF | AB,CD,EF | 
多线程
Java用Thread对象表示一个线程,通过调用start()启动一个新线程;
一个线程对象只能调用一次start()方法;
线程的执行代码写在run()方法中;
线程调度由操作系统决定,程序本身无法决定调度顺序;
Thread.sleep()可以把当前线程暂停一段时间。
线程的优先级
可以对线程设定优先级,设定优先级的方法是:
| 1 | Thread.setPriority(int n) // 1~10, 默认值5 | 
优先级高的线程被操作系统调度的优先级较高,操作系统对高优先级线程可能调度更频繁,但我们决不能通过设置优先级来确保高优先级的线程一定会先执行。
中断线程
对目标线程调用interrupt()方法可以请求中断一个线程,目标线程通过检测isInterrupted()标志获取自身是否已中断。如果目标线程处于等待状态,该线程会捕获到InterruptedException;
目标线程检测到isInterrupted()为true或者捕获了InterruptedException都应该立刻结束自身线程;
通过标志位判断需要正确使用volatile关键字;
volatile关键字解决了共享变量在线程间的可见性问题。
线程同步
多线程同时读写共享变量时,会造成逻辑错误,因此需要通过synchronized同步;
同步的本质就是给指定对象加锁,加锁后才能继续执行后续代码;
注意加锁对象必须是同一个实例;
对JVM定义的单个原子操作不需要同步。
同步方法
用synchronized修饰方法可以把整个方法变为同步代码块,synchronized方法加锁对象是this;
通过合理的设计和数据封装可以让一个类变为“线程安全”;
一个类没有特殊说明,默认不是thread-safe;
多线程能否安全访问某个非线程安全的实例,需要具体问题具体分析。
使用wait和notify
wait和notify用于多线程协调运行:
- 在synchronized内部可以调用wait()使线程进入等待状态;
- 必须在已获得的锁对象上调用wait()方法;
- 在synchronized内部可以调用notify()或notifyAll()唤醒其他等待线程;
- 必须在已获得的锁对象上调用notify()或notifyAll()方法;
- 已唤醒的线程还需要重新获得锁后才能继续执行。
使用ReentrantLock
ReentrantLock可以替代synchronized进行同步;
ReentrantLock获取锁更安全;
必须先获取到锁,再进入try {...}代码块,最后使用finally保证释放锁;
可以使用tryLock()尝试获取锁。
使用Condition
可见,使用Condition时,引用的Condition对象必须从Lock实例的newCondition()返回,这样才能获得一个绑定了Lock实例的Condition实例。
Condition提供的await()、signal()、signalAll()原理和synchronized锁对象的wait()、notify()、notifyAll()是一致的,并且其行为也是一样的:
- await()会释放当前锁,进入等待状态;
- signal()会唤醒某个等待线程;
- signalAll()会唤醒所有等待线程;
- 唤醒线程从await()返回后需要重新获得锁。
此外,和tryLock()类似,await()可以在等待指定时间后,如果还没有被其他线程通过signal()或signalAll()唤醒,可以自己醒来:
| 1 | if (condition.await(1, TimeUnit.SECOND)) { | 
可见,使用Condition配合Lock,我们可以实现更灵活的线程同步。
ReadWriteLock
使用ReadWriteLock可以提高读取效率:
- ReadWriteLock只允许一个线程写入;
- ReadWriteLock允许多个线程在没有写入时同时读取;
- ReadWriteLock适合读多写少的场景。
使用Concurrent集合
| interface | non-thread-safe | thread-safe | 
|---|---|---|
| List | ArrayList | CopyOnWriteArrayList | 
| Map | HashMap | ConcurrentHashMap | 
| Set | HashSet / TreeSet | CopyOnWriteArraySet | 
| Queue | ArrayDeque / LinkedList | ArrayBlockingQueue / LinkedBlockingQueue | 
| Deque | ArrayDeque / LinkedList | LinkedBlockingDeque | 
ExecutorService
JDK提供了ExecutorService实现了线程池功能:
- 线程池内部维护一组线程,可以高效执行大量小任务; 
- Executors提供了静态方法创建不同类型的- ExecutorService;
- 必须调用 - shutdown()关闭- ExecutorService;
- ScheduledThreadPool可以定期调度多个任务。
- 思考: - Q1:在FixedRate模式下,假设每秒触发,如果某次任务执行时间超过1秒,后续任务会不会并发执行? - A1:If any execution of this task takes longer than its period, then subsequent executions may start late, but will not concurrently execute. - 译:如果此任务的任何执行时间超过其周期,则后续执行可能会延迟开始,但不会并发执行。 - Q2:如果任务抛出了异常,后续任务是否继续执行? - A2:If any execution of the task encounters an exception, subsequent executions are suppressed. - 译:如果任务的任何执行遇到异常,则将禁止后续任务的执行。 
使用Future
当我们提交一个Callable任务后,我们会同时获得一个Future对象,然后,我们在主线程某个时刻调用Future对象的get()方法,就可以获得异步执行的结果。在调用get()时,如果异步任务已经完成,我们就直接获得结果。如果异步任务还没有完成,那么get()会阻塞,直到任务完成后才返回结果。
一个Future<V>接口表示一个未来可能会返回的结果,它定义的方法有:
- get():获取结果(可能会等待)
- get(long timeout, TimeUnit unit):获取结果,但只等待指定的时间;
- cancel(boolean mayInterruptIfRunning):取消当前任务;
- isDone():判断任务是否已完成。
网络编程
TCP编程
使用Java进行TCP编程时,需要使用Socket模型:
- 服务器端用ServerSocket监听指定端口;
- 客户端使用Socket(InetAddress, port)连接服务器;
- 服务器端用accept()接收连接并返回Socket;
- 双方通过Socket打开InputStream/OutputStream读写数据;
- 服务器端通常使用多线程同时处理多个客户端连接,利用线程池可大幅提升效率;
- flush()用于强制输出缓冲区到网络。
UDP
使用UDP协议通信时,服务器和客户端双方无需建立连接:
- 服务器端用DatagramSocket(port)监听端口;
- 客户端使用DatagramSocket.connect()指定远程地址和端口;
- 双方通过receive()和send()读写数据;
- DatagramSocket没有IO流接口,数据被直接写入- byte[]缓冲区。



