Java编程基础(整理)
- Java语言概述
- 基本语法
- 数组
- Arrays工具类,操作数组的工具类
- 面向对象
- 异常处理
- 多线程
- Java常用类
- String类
- StringBuffer,StringBuilder
- 日期时间API
- 比较器Comparable、Comparator
- System,Math,BigInteger,BigDecimal
- 枚举类与注解
- 枚举类Enum
- 注解Annotation
- Java集合框架(Java Collections Framework)
- Collection接口中的方法
- 迭代器接口Iterator、Iterable
- Collection子接口之一:List接口
- Collection子接口之二:Set接口
- Map接口
- Collections工具类,操作List、Set、Map等集合的工具类
- 泛型(Generic)
- IO流
- File类
- IO流原理及流的分类
- 文件流
- 缓冲流(处理流之一)
- 转换流(处理流之二)
- 标准输入、输出流(处理流之三)
- 打印流(处理流之四)
- 数据流(处理流之五)
- 对象流(处理流之六)
- 随机存储文件流(RandomAccessFile)
- NIO中的Path、Paths、Files类的使用
- 网络编程
- TCP编程
- UDP编程
- URL编程
- Java反射机制
- 理解Class类并获取Class的实例
- 类的加载与ClassLoader的理解
- 创建运行时类的对象
- 获取运行时类的完整结构、指定结构
- 应用:动态代理
- Java8新特性
- lambda表达式、函数式接口、方法引用
- Stream api
- Optional类
- 其他版本新特性
Java教程官方文档:https://docs.oracle.com/javase/tutorial/index.html
JDK11官方文档:https://docs.oracle.com/en/java/javase/11/
《Java编程思想》学习笔记:
《Java从入门到精通》学习笔记:
Java语言概述
- 软件开发:系统软件、应用软件
- 人机交互:GUI(图形化界面)、CLI(命令行方式)
- dos常用命令:
# 进入其他盘符直接输入 盘符:,如d:
# 查看文件dir
# 创建目录md 删除目录rd 删除文件del 后面都可以跟多个参数,如md d1 d2。可以使用通配符如del *.txt。删除目录必须目录为空,del后面跟目录表示删除目录下所有文件,需要输入y确认,但不会删除该目录下的目录。
# 切换目录cd xxDir .表示当前目录 ..表示父目录 /表示根目录
# 创建文件使用echo xxx > 1.txt,创建的文件非空包含echo和>之间的内容
# 查看命令帮助 命令后加 /?
- 计算机编程语言:机器语言、汇编语言、高级语言
- C面向过程,C++面向过程/面向对象,Java跨平台的纯面向对象,.NET跨语言的平台,Python,Scala,JS都是面向对象
- Java、C/C++都是强类型语言,Python、JS是弱类型语言,解释型语言
- Java语言概述
- 版本迭代:大版本JDK5(2004),JDK8(2014),JDK11(2018.9)
- 应用领域:Java Web开发(后台开发),大数据开发,Android应用程序开发(客户端开发)
- 特点:面向对象(类、对象,封装、继承、多态),健壮性(去除C语言的指针,自动垃圾回收),跨平台(一次编译,到处运行,归功于JVM)
- 开发环境:JDK,JRE,JVM,是包含关系(注意安装路径不要包含中文、空格),配置环境变量JAVA_HOME(如tomcat自动查找),path
- HelloWorld:只能有一个public class,且与文件名相同,可以由多个class,入口是main()方法
- 注释:单行注释//,多行注释
/*xx*/,文档注释/**xx*/(可以被javadoc解析)。良好的代码编程风格
-开发工具:文本编辑(UltraEdit,EditPlus,TextPad,NotePad),集成开发环境(eclipse,idea)
基本语法
- Java关键字和保留字:https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/package-summary.html
官方介绍:https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/doc-files/coll-index.html集合和数组都是对多个数据存储操作的结构,也称为Java容器。数组:初始化后长度确定,类型确定,提供的方法有限。
Arrays也是java集合框架的一部分。
Java集合框架可以分为Collection和Map,Collection存储一个一个对象,没有直接实现类(常用子接口List有序可重复,Set无序不重复);Map存储一对一对数据,有直接实现类
- Collection接口
- List接口:ArrayList,LinkedList,Vector
- Set接口:HashSet,LinkedHashSet,TreeSet
- Map接口:HashMap,LinkedHashMap,TreeMap,Hashtable,Properties
Collection接口中的方法
Collection接口中的常用方法:
- add(), addAll(), size(), isEmpty(), clear(),
- contains(), containsAll(), remove(), removeAll(), retainAll(), (包含,删除,差集,交集,都会调用equals()方法)
- equals(), hashCode(),
- toArray()(与Arrays.asList()相互转换,返回固定大小的原数组),
- iterator(),返回一个迭代器Iterator,使用hasNext()和next()遍历Collection, 使用remove()删除(要在next后调且只能调一次)。
注意:remove只删第一个,removeAll和retainAll分别删除和保留所有调用集合存在于另外一个集合的元素;向Collection接口的实现类的对象中添加元素时,一般元素的所在类要重写equals()方法
说明:向Collection接口中添加元素,要求元素所在类重写equals()方法,不涉及到存储,不用重写hashCode()方法
迭代器接口Iterator、Iterable
Collection实现了Iterable接口,该接口聚合了Iterator接口,实现类如ArrayList内部返回Iterator时是内部类Itr,Itr实现了Iterator接口。增强for循环利用的是迭代器
Collection子接口之一:List接口
List集合中元素有序、可重复,接口常用实现类有ArrayList,LinkedList,Vector(不常用)
List接口常用实现类源码分析:
- ArraysList:
- List接口的主要实现类,底层使用Object数组存储,线程不安全,效率高
- jdk7初始容量为10,扩容是1.5倍(不够则是现需要大小本身)
- jdk8初始容量为0,第一次add容量设为10,之后扩容1.5倍(不够则是现需要大小本身),延迟了数组创建,节省内存
- LinkedList:插入、删除效率高,底层是双向链表,first,last分别指向头尾,Node节点为私有内部静态类
- Vector:List接口的古老实现类,线程安全,效率低,初始默认为10,扩容为2倍
List接口中的常用方法:除了继承Collection的方法外,List集合里还添加了一些
根据索引操作集合元素的方法,因为List是有序的,Collection兼顾List和Set- 一些重载Collection接口的方法(带索引的):add(), addAll(), remove(), get(), indexOf(), lastIndexOf(), set(), subList()
- 增、删、改、查、插、长度、遍历
注意:subList()返回的是原list的视图,返回的类型是SubList,原list不可以有对数据的结构性修改,比如添加、删除一个元素,否则subList在进行方法调用过程中会报ConcurrentModificationException。对subList的任何修改都会反映到原list上。
remove(1)删除的是索引为1的,如果要删除对象使用Integer,List存储的都是对象,基本数据类型进行了自动类型提升。
Collection子接口之二:Set接口
Set集合中元素无序、不可重复,接口常用实现类有HashSet,LinkedHashSet(HashSet子类),TreeSet
-
无序性:不等于随机性,指的是存储的数据在底层数组中不是按照数组索引的顺序添加,而是根据数据的Hash值决定
-
不可重复性:保证添加的元素按照equals()判断时,不能返回true,即相同的元素只能添加一个
- 添加元素过程:首先调用hashCode()计算哈希值,通过某种算法计算出存储位置,如果此位置没有元素,则添加到此位置;如果有元素,比较哈希值,如果哈希值不相等则元素不同添加到该位置的链表中(哈希值不相同一定不是同一个元素,哈希值相同时元素相同的必要不充分条件),如果哈希值相等,调用equals()方法,如果返回true,添加失败,否则添加成功。
-
重写hashCode()里用31乘:系数越大,哈希值越大,冲突越小,也不能太大;31是质数,减少冲突;31只占用5bit,数据溢出概率小;31可以用左移5位-1,计算效率高;
- 重写原则:多次调用返回形同的值;equals()返回true时,hashCode()也返回true;用equals()笔比较的字段,都应该用来计算哈希值。一般自动生成即可
向Set里面添加数据要重写hashCode()方法和equals()方法,要保持一致性,即相等的对象必须有相等的哈希值
Set接口方法:没有额外定义新的方法,使用的都是Collection中声明过的方法;向Set接口实现类中添加元素,要求元素所在类重写hashCode()和equals()方法(要判断是否重复,hashCode()是为了提高效率设计的)
Set接口常用实现类:
- HashSet:线程不安全,底层是数组,jdk7初始默认16,jdk8初始为0,第一次添加时为16,底层实现是HashMap,value为PRESENT,一个Object对象(HashMap里会调用value的hashCode()和equals()方法,避免出错),可以存储null
- LinkedHashSet:HashSet的子类,记录了元素添加顺序(增加了一对指针,指向添加的前一个和后一个元素,遍历效率高),底层是数组+链表
- TreeSet:可以按照指定元素进行排序,底层是红黑树,不能添加null,只能添加同类的对象,且该类实现了Comparable接口,或使用Comparator定制排序。比较标准是compareTo()不再是equals()
Map接口
Map接口常用实现类:HashMap,LinkedHashMap,TreeMap,Hashtable,Properties
- HashMap,是Map的主要实现类,可以存放null的key和value,(jdk8是数组+链表+红黑树)
- LinkedHashMap,是HashMap的子类,频繁遍历效率高,增加了一对指针before,after记录添加顺序
- TreeMap,底层是红黑树,不可存储null的key,插入的key必须是同一个类的对象,能够自然排序或者实现定制排序,需要key元素实现hashCode()和equals()方法。内部有SubMap等类,SubMap实现SortedMap接口
- Hashtable,Map的古老实现类(现有的Hashtable后有的Map接口),线程安全,不能存放null的key和value
- Properties,Hashtable的子类,key和value都是String,常用来处理配置文件
源码分析:
- HashMap:
- jdk7是数组+链表(新元素放链表最前面),初始容量为16
- jdk8是数组+链表+红黑树(链表元素个数大于8且容量大于64转化为红黑树,否则使用扩容,提高查询效率,新元素放链表最后面),一开始不创建数组,第一次添加元素时,初始容量为16,负载因子默认为0.75f(临界值=容量*负载因子,兼顾数组利用率和链表长度,太小,频繁扩容,链表较小,太大,链表较长),元素数量大于临界值且当前位置不为空才会扩容为2倍,某个链表元素数量大于8且容量大于64转化为红黑树,否则扩容为2倍,带参数的构造器,返回一个不小于参数的2的x次幂,比如参数为负数、0、1都返回1,参数为2、3、4返回2、4、4,参数为5、6、7、8,都返回8...。
- 所以在使用HashMap时,如果数据量较大,还是传入数据大小作为容量参数尽量减少扩容比较好?
- LinkedHashMap:内部使用节点继承了HashMap里面的Node(jdk8),增加了before、after指针
Map接口常用方法:
- put(), putAll(), remove(), clear(), get(), containsKey(), containsValue(), isEmpty(), size(), equals()
- keySet(), values(), entrySet()(返回Map.Entry类型的entry,Map的内部接口,有getKey(), getValue()方法)
- 增put()、删remove()、改put()、查get()、长度size()、遍历(keySet(), values(), entrySet())
key所在的类要重写equals()和hashCode()方法(本质上hashCode()是为了存储高效设计的)
TreeMap使用:key必须是同一种对象,重写hashCode()和实现Comparable接口(或定制排序)
Properties使用:使用Properties
Collections工具类,操作List、Set、Map等集合的工具类
Arrays是操作数组的工具类,也是属于java集合框架下的
- 从数组转化为List使用Arrays.asList()
- 从List转化为Array使用toArray()
Collections常用静态方法:
- 排序操作:reverse(), shuffle()随机打乱, sort(), swap()
- 查找替换:min(), max(), frequency()出现次数, copy(), replaceAll()
- copy()需要dest的size不小于src,可以使用Arrays.asList(
new Object[src.size()])创建一个List(这个list是Arrays的内部类,不可进行结构上的改变操作)
- copy()需要dest的size不小于src,可以使用Arrays.asList(
- synchronizedXxx(), 返回相对应的线程安全的集合,内部对方法都增加了Synchronized同步代码块,锁为this
- 如果使用Iterator, Spliterator or Stream遍历时,需要用户自己在外部保证同步
泛型(Generic)
设计背景:
- 集合容器类在设计阶段、声明阶段不能确定存的到底是什么类型的对象,所以jdk5之前,集合容器类型只能设计为Object,之后可以通过泛型解决。因为这个时候只有元素的类型是不确定的,如何管理,如何保存都是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型,如
Collection,这个E就是类型参数,即泛型。 - 参数化类型,保证编译器没有发出警告,运行时就不会产生ClassCastException,同时代码更简洁、健壮(能够在编译时检查错误)
定义:
- 所谓泛型,就是允许在定义类、接口、方法时通过一个标识符表示类中某个属性的类型或者某个方法的返回值及参数类型,这个类型参数将在使用时(如继承或实现接口、用这个类型声明变量、创建对象、被调用时)确定(即传入实参时确定)
- 泛型方法:在返回值前面使用<>,与泛型类、接口无关,指明泛型方法的泛型参数,在调用该方法时根据实参类型传入
- 如果没有在形参中使用泛型参数,那么它的意义在于方法中第一次使用泛型参数进行赋值(强转)时,后面所有使用泛型参数变量时,都必须是统一的。
- 泛型的意义在于参数化类型,统一类型(类型被参数化了,那么同一个参数的类型是统一的),底部都是Object
注意:
- 泛型是编译时操作,会根据泛型进行编译器检查,编译之后会进行泛型擦除,将泛型都换成Object。如果没有使用泛型,则默认是Object,但不等价于Object。经验:要用都用,要不用都不用
- 泛型的类型必须是类,不能是基本数据类型
- 子类继承泛型类时,可以直接指明父类的泛型参数类型,也可以不指明,也可以全部保留或部分保留父类泛型,此时子类也要声明保留的父类泛型参数
- 泛型类型不同的引用不能相互赋值,虽然在编译器是两种类型,但是在运行时只有一种类型
- 静态方法不能使用类的泛型,可以从面向对象的角度解释,泛型参数只有实例化时才确定,静态方法是类方法,早于对象的创建。但是可以使用泛型方法
- 异常类不能是泛型类,因为catche时的泛型不一定是异常类
- 创建泛型类型的对象时不能使用
new T[](),可以使用T[] t = (T[]) new Object[]强转,这个t之后在使用的时候以T类型使用(想象一下泛型都会被擦除为Object,只是在编译期间通过泛型做一些统一参数的约束)
泛型的嵌套,HashMap的entrySet()返回的是EntrySet extends AbstractSet,父类是> AbstractSet
泛型在继承上的体现:
- 如果A是B的子类,那么
G是不能赋值给G的,即不具备子父类关系,是并列关系List不能赋值给List否则泛型就失去了意义,(数组String[]与Object[]是有子父类关系的,因为是原生类型)
- 通配符:
- 有限制的通配符
<? extends A>,小于等于A的类,可以理解为(-无穷,A],读取数据可以读取为A,不能写入,因为里面存储的都是小于等于A的,有多小是不确定的<? super A>,大于等于A的类,可以理解为[A,+无穷),读取数据只能为Object,写入数据可以写A及小于A的类型,因为里面存储的都是大于等于A的- 在声明泛型类、接口、方法上可以使用
或,<?>相关的只能作为形参,不能定义各种泛型
IO流
File类
- File类的三个构造器:File(String), File(String, String), File(File, String)
- 常用方法:
- 获取属性:getAbsolutePath(), getPath(), getName(), getParent(), length(), lastModified()
- 列出文件:list(), listFiles()
- 重命名:要想为true,file1.renameTo(file2), file1必须存在,file2必须不存在,file1会被移动到file2下
- File类的判断方法:isDirectory(), isFile(), exists(), canRead(), canWrite(), isHidden()
- File类的创建:createNewFile(), makdir(), makdirs()递归创建目录
- File类的删除:delete(),要求删除的目录下没有文件或目录,否则删不掉,必须递归删除。具体使用可参考其他包下的FileUtils类写工具类
IO流原理及流的分类
三种分类方式:输入流,输出流;字节流,字符流;节点流,处理流(在已有流的基础上)
- 字节流基类:InputStream,OutputStream;字符流基类:Reader,Writer,共四个抽象基类
- 节点流:FileInputStream,FileOutputStream,FileReader,FileWriter,在抽象基类前面加File,直接处理文件,也叫文件流,共四个文件流,对应四个抽象基类
- 缓冲流:BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter,在节点流前面加Buffered,处理流的一种,提高读取效率,共四个缓冲流,对应四个文件流
- 转换流
具体说明:
- 字节流 四个抽象基类
public abstract class InputStream implements Closeable
public abstract class OutputStream implements Closeable, Flushable
public abstract class Reader implements Readable, Closeable
public abstract class Writer implements Appendable, Closeable, Flushable - 文件流:两个字节流分别继承各自的抽象基类;而两个字符流分别继承InputStreamReader、OutputStreamWriter,它们又分别继承抽象基类Reader、Writer
public class FileInputStream extends InputStream
public class FileOutputStream extends OutputStream
public class FileReader extends InputStreamReader,(InputStreamReader extends Reader)
public class FileWriter extends OutputStreamWriter,(OutputStreamWriter extends Writer) - 缓冲流(处理流):两个缓冲字节流继承了FilterInputStream、FilterOutputStream,它们又分别继承了抽象基类InputStream、OutputStream,两个缓冲字符流继承各自的抽象基类
public class BufferedInputStream extends FilterInputStream,(FilterInputStream extends InputStream)
public class BufferedOutputStream extends FilterOutputStream,(FilterOutputStream extends OutputStream )
public class BufferedReader extends Reader
public class BufferedWriter extends Writer - 转换流(处理流):这两个是字符流,看后缀,都是操作的字符(字符读取,字符写出),操作的是
char[]
public class InputStreamReader extends Reader
public class OutputStreamWriter extends Writer
说明:
在文件流中,字符流FileReader、FileWriter继承了InputStreamReader、OutputStreamWriter,它们是转换流,FileReader在读取时,是通过InputStreamReader将字节流FileInputStream转换为字符流(以特定的字符集解码为字符);同样,FileWriter在写出时,是通过OutputStreamWriter将字符流FileWriter转换为字节流(以特定的字符集编码为字节)。所以底层都是字节流(二进制文件,一个字节一个字节读写),在程序层面使用时,读时通过转换流转换为字符流,写时通过转换流转换为字节流。文件流
四个文件流:
- FileReader:
read(char[]),返回读到的字节数;读到末尾返回-1,(注意将char[]转换成String时调用String(char[], int, len)) - FileWriter:
write(char[] cbuf, 0, len),写出,不需要文件存在,默认覆盖,参数传true表示追加 - FileInputStream:
read(byte[] buffer),返回读取的字节个数,读到末尾返回-1 - FileOutputStream:
write(byte[] buffer, 0, len),写出,相比于Reader/Writer只是换成字节数组
总结:标准使用步骤: 1,File类实例化,2,流实例化(可嵌套处理流),3,数据的读入写出(使用带数组参数的),4,资源的关闭
注意:
保证流一定会关闭,使用try-catch-finally处理,注意判断流是否存为null,不为null调用close()。
提供具体的流之前,要判断文件是否存在,读文件要求文件一定存在。关闭外层流自动关闭内层流。一般包一层缓冲流提高效率缓冲流(处理流之一)
BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter
- 处理流的一种,提高读取效率,内部提供了缓冲区,默认8M,可以传参数设置,将字符数组、字节数组内的数据先放入缓冲区,等缓冲区满了再flush()写出,从而减少写出的次数,增加读取、写出的效率。
- 以节点流为参数,在外层将节点流包一层
- 缓冲输出流都有flush()方法,表示将缓冲区数据强制刷新出去
- 缓冲字符流其他新方法:br.readLine()可以一次读一行,不包含换行符;bw.newLine()可以写出换行符
转换流(处理流之二)
InputStreamReader、OutputStreamWriter
-
从操作实际文件的角度分析,读取时,如果使用了字节流,使用InputStreamReader转换成字符流读入。写出时,如果使用了字符流,使用OutputStreamWriter转换成字节流写出。(本质上,FileReader、FileWriter直接读取文件和写出文件时也是利用了父类转换流进行了转换才读入为字符读出为字节的)
-
为什么没有读入的时候字符流转换成字节流呢?因为字符流内部还是用的字节流进行的读取、写出,字符流分别继承了InputStreamReader和OutputStreamWriter,将读取的字节转换为字符,将要写出的字符转换为字节
-
作用:提供字符流与字节流的转换,字节->字符为解码的过程,字符->字节为编码的过程
标准输入、输出流(处理流之三)
System.in是一个InputStream,一般从键盘读取
System.out是一个PrintStream,一般输出到控制台
System的setIn()和SetOut()可以修改流的位置打印流(处理流之四)
PrintStream、PrintWriter
- 提供了一系列重载的print()和print()方法,将基本数据、Object等数据以字节形式写出。也可以指定输出的位置(System.out是打印流的一个对象),内部依然使用了BufferedWriter,OutputStreamWriter,将输入数据转换成字节流,再输出到文件或控制台等节点
注意:
- 不会抛出IOException
- 都有自动flush()功能
- PrintStream打印的所有字符都使用平台默认的字符编码转换为字节。在需要写入字符而不是字节的情况下,应该使用PrintWriter类
- System.out返回的是PrintStream
字节写出的话不需要考虑编码,因为不涉及到字符,字节写出都是一个个
\uxxxx,字符写出是使用了字符编码的,如果使用utf-8,可以是多个字节表示一个字符- 具体说明:
public class PrintStream extends FilterOutputStream implements Appendable, Closeable(FilterOutputStream extends OutputStream)
public class PrintWriter extends Writer
数据流(处理流之五)
DataInputStream、DataOutputStream
读出或写入基本数据类型的变量或字符串,写入和读出的顺序必须相同
对象流(处理流之六)
ObjectInputStream,ObjectOutputSteam,对象序列化、反序列化
用于存储和读取基本数据类型或对象的处理流注意:
- 对象所属的类必须是可序列化的(实现Serializable接口),类的所有属性也必须是序列化的
- 并且添加一个静态常量
private static final long serialVersionUID = xxL- java在反序列化的时候会判断这个serialVersionUID,如果和之前写出的对象不同,会反序列化失败。如果不写,java运行时会自动根据类的内部细节生成,如果类的实例变量变了,那么这个自动生成的serialVersionUID肯定会变,所以如果不写修改了之后就无法反序列化
- static和transient修饰的变量是不可序列化的,为默认值,不会保存的,可以将不想序列化的属性加transient
- 实现Externalizable接口的类的序列化可以自定义序列化的属性,包括transient修饰的属性,但static修饰的不可序列化
随机存储文件流(RandomAccessFile)
既可以作为输入流,也可以作为输出流,有四种access模式:r,rw,rwd,rws,(rwd表示随时更新内容到磁盘,rws表示更新内容和元数据,会有至少一次的low-level I/O operation)
特点:
- 直接继承了Object,实现了DataOutput, DataInput接口,所以既有读也可以写
- 支持文件的任意位置读、写,内部含有一个记录指针,表示当前读的位置,getFilePointer()获取当前位置,seek()将指针定位到指定位置。可以以此实现断点下载和多线程同时下载的功能
- 当模式为r时,要求文件必须存在,否则有异常。当模式为rw时,如果文件不存在会自动创建(在new RandomAccessFile()时就会创建)
具体说明:
public class RandomAccessFile implements DataOutput, DataInput, CloseableNIO中的Path、Paths、Files类的使用
java NIO(New IO,Non-Blocking IO)是从java 1.4开始引入的一套新的IO api,可以替代标准的Java IO api,NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的(IO是面向流的)、基于通道的IO,NIO将以更加高效的方式进行文件的读写操作
Java api提供了两套NIO,一套是针对标准输入输出NIO,一套是网络编程NIO
- java.nio.channel.Channel
- FileChannel,处理文件
- SocketChannel,TCP网络编程的客户端的Channel
- ServerSocketChannel,TCP网络编程的服务器端的Channel
- DatagramChannel,UDP网络编程中发送端和接收端的Channel
jdk7中,java对NIO进行了极大的扩展,增强了对文件处理和文件系统的支持,以至于称为NIO.2
- Path接口:早期java只提供File来访问文件系统,但File功能有限,性能也不高,而且大多数方法在出错时仅返回失败,并不会提供异常信息。NIO.2为了弥补这个不足,引入了Path接口,代表一个与平台无关的路径,描述了目录结构中的位置,Path可以看成是File的升级版,实际引用的资源可以不存在。
- Files、Paths工具类
- Files工具类包含了大量静态的工具方法操作文件
- Paths工具类包含两个返回Path的静态工厂方法,Paths.get(String first, String...),Paths.get(URI uri),其实是调用的Path.of()方法,源码也说了,建议使用Path.of(),get()方法以后可能会废弃
API Note: It is recommended to obtain a Path via the Path.of methods instead of via the get methods defined in this class as this class may be deprecated in a future release.
NIO总结:
- 获取Path使用Path.of(),可以看成File的升级版,与平台无关,实际路径可以不存在
- 操作文件:Files工具类,创建文件、目录、硬链接、软连接、删除、拷贝、移动、查看文件信息(可读、可写、可执行、是隐藏文件、符号链接)、文件所在磁盘信息、列出文件、递归列出文件、直接读取流、写出流
- 读写可以使用第三方jar包,如apache commons-io,apache commons官网https://commons.apache.org/
网络编程
网络通信需要ip地址和端口号唯一定位某个主机的某个进程,ip地址和端口组成Socket(套接字)
在java中,ip地址使用InetAddress类表示,通过
InetAddress.getByName()获取一个ip地址的实例对象,参数可以是ip地址和域名。两个常用方法getHostName()获取主机名,getHostAddress()获取ip地址TCP建立连接需要三次握手,释放连接需要四次挥手
TCP编程
客户端:
- 创建Socket,指定服务器端的ip和端口
- 从socket获取输入流
- 往输出流中写数据
- 关闭资源
服务端:
- 创建ServerSocket,指定端口
- 执行accept()获取socket
- 从socket获取输入流
- 从输入流中读取数据
- 关闭资源
UDP编程
客户端:
- 创建DatagramSocket获取socket
- 创建DatagramPacket,存储UDP报文的包,指定数据和端口
- 执行socket.send(packet)发送数据
- 关闭资源
服务端:
- 创建DatagramSocket获取socket,指定端口
- 创建DatagramPacket,存储UDP报文的包,接收数据
- 执行socket.receive(packet),将报文接收在packet中
- 通过packet获取接收的报文数据
- 关闭资源
URL编程
后面使用HttpClient替代HTTPURLConnection
- 全局资源定位符,对应网络上某一资源地址,通过url可以访问网络上的资源
- 传输协议://主机名:端口//资源地址#片段名?参数列表
- java可以通过url获取网络上的资源
使用:
- 创建URL,指定url
- 执行url.openConnection(),获取到一个连接
- 执行connection.connect(),进行连接
- 根据connection获取资源
- 关闭连接
Java反射机制
反射机制允许程序在执行期间借助于Reflection api取得任何类的内部信息,并能直接操作任意对象的内部属性和方法
java里面的一切类或类型在jvm加载之后都成为Class的一个实例对象(也称为运行时类),也就是说,所有的类在jvm里面都对应着一个实例,这些实例都是Class的实例。换句话说,Class的实例对应这个一个运行时类
理解Class类并获取Class的实例
Class类
- 每个对象都由一个对应的Class类型的对象所生成,这个Class对象记录了类的结构有关的信息,对应一个.class文件
- Class本身也是一个类,它的对象只能由系统创建
- 一个加载的类在jvm中只有一个Class实例,对应一个.class文件
- 每个类的实例都会记录自己是由哪个Class实例所生成
- 通过Class可以完整的得到一个类中的所有被加载结构
- Class是reflection的根源,对任何想要动态加载、运行的类,都必须先获得相应的Class对象
获取Class实例的四种方式
- 已知类调用class属性
- 已知类的对象,调用对象的getClass()方法,每个对象都可以获取它有哪个Class实例所生成
- Class.forName(),传入类的全类名
- 通过某个类获取它的类加载器,使用类加载器load()
类的加载与ClassLoader的理解
类的加载:
- 加载,链接,初始化
- 主动引用会初始化类:
- 虚拟机启动初始化main方法所在的类
- new一个对象
- 调用类的静态成员和静态方法
- 对类反射调用
- 初始化一个类,如果父类没有初始化,先初始化父类
- 被动引用不会初始化类(初始化后才有类的结构),比如调用static final字段的变量,调用父类的static变量只会加载父类
- 当访问静态域,只要真正声明这个域的类才会被初始化,如果是通过子类引用父类的静态变量不会导致子类的初始。比如类静态属性A.b,b是父类中的,那么只会加载父类,比如访问A.M,M是static final的,不会初始化父类和A类
- 通过数组定义类引用,不会触发此类的初始化
- 引用常量不会触发类的初始化(static final修饰的)
ClassLoader:
- 作用:将class字节码加载到内存中,将这些静态数据转换成方法区的运行时结构,然后在堆中生成一个代表这个类的Class对象,作为方法区中类数据的入口(比如new一个对象,调用方法,会先找到这个Class对象,然后根据这个Class对象找到方法区的运行时结构中对应的方法代码进行执行)
- 类被加载后会被缓存一段时间,jvm也是可以回收这些对象的
- 类加载器:引导类加载器(加载核心类库),扩展类加载器(加载扩展类),系统类加载器(加载自定义类)
创建运行时类的对象
- 使用
clazz.newInstance(),从jdk9之后被弃用了,要求有空参构造器,权限够,一般是public - replaced by
clazz.getDeclaredConstructor().newInstance(),使用指定构造器创建(参数是可变形参,可以没有参数,即空参构造器),会抛出NoSuchMethodException异常更准确
获取运行时类的完整结构、指定结构
所有运行类的结构都可以获取,比如
- 获取类的属性,属性相关的权限修饰符、属性类型、变量名,(不包括静态变量,因为静态变量是类加载)等
- 获取类的方法,方法相关的权限修饰符、参数类型、注解、返回值、方法名等
- 获取类的构造器
- 获取父类 及带泛型的父类的泛型
- 获取接口
- 获取所在的包
- 获取类的注解...
总结:
- 获取完整结构使用getXxxs(),返回所有public的,包括父类。或者使用getDeclaredXxxs(),返回当前类所有权限的,不包括父类。一般使用后者
- 获取指定使用getXxx(name),要求必须是public的,可以获取父类的。或者使用getDeclaredXxx(name, args),获取当前类的指定名称指定参数的方法。一般使用后者
- 调用时,注意使用getDeclaredXxx()获取到后,setAccessible(true),保证权限可以访问
- 调用静态方法,参数随便传无所谓,因为是通过Class可以直接调用
- 设置属性值使用set,获取使用get。调用方法使用invoke(),返回值就是调用对象的方法的返回值
应用:动态代理
被代理类实现接口,创建被代理类对象,动态代理通过反射获取同样实现接口的匿名实现类对象,也就是代理类对象(将对象强转为接口),当这个代理类对象调用接口中的方法a时,在handler中都调用invoke()方法,会传入方法a的方法实例、调用参数、代理类对象本身,所以在handler中要聚合进被代理类对象,并在invoke()方法通过反射调用被代理类对象的a方法(通过方法实例Method)才能实现代理类对象调用方法,并调用被代理对象的对应方法(自己反射调用)。
Java8新特性
java8最重要的的改变,一个是lambda表达式,一个是Stream api
- 接口中可以有静态方法,默认方法(越来越像了)
- 新的时间日期api(LocalDateTime,Instant,DateTimeFormatter...)
- 可重复注解(@Repeatable),类型注解(ElementType支持泛型)
- 集合底层变化(一开始不创建,用的时候再创建;红黑树)
lambda表达式、函数式接口、方法引用
lambda表达式:
- lambda是一个匿名函数,可以把lambda理解为一段可以传递的代码。函数式接口都可以使用lambda表达式进行改写(只有一个抽象方法的接口叫函数式接口,因为只有一个方法,所以不用显式声明了,省略为lambda)
- 本质是作为函数式接口匿名实现类的对象(实例),借助接口才存在的 没有接口就没有意义了,接口必须是函数式接口(只有一个抽象方法)
- 使用总结
- 参数 有一个参数就写参数本身 其他都写括号 可以不写参数类型
- 方法体 只有一条语句可以省略大括号 有很多条则不能省略大括号
函数式接口:
- 如果一个接口中,只声明了一个抽象方法,则此接口就称为函数式接口。(Comparator接口中有个equals()方法,这个方法不是抽象的,是有默认实现的,接口也是继承自Object,接口也是一个特殊的类,在Object类中有equals()方法的具体实现,因此在Comparator接口中,只有一个抽象方法compare())
- 可以使用注解
@FunctionalInterface在编译器检查当前接口是否符合要求 - java内置四大函数式接口
- 消费型、供给型、函数型、断定型
- 其他函数式接口参考java.util.function包下
方法引用:
- 当要传递给lambda体的操作已经有实现的方法了,可以使用方法引用(
对象::非静态方法,类::静态方法) - 当方法体中第一个参数作为调用者出现,其他参数作为调用方法的参数出现,而这种调用已经有代码实现,可以使用方法引用(
类::静态方法) - 本质:方法引用本质上就是lambda表达式的简写形式,用现有代码替换要实现的功能的lambda方法体,lambda表达式、方法引用本质都是函数式接口的实例,作为实例提供给方法参数或被调用进行使用
构造器引用、数组引用
- 当方法引用中,是使用new创建对象时,可以使用构造器引用或数组引用(创建数组)
总结:
- lambda表达式是函数式接口的一个实例,在一些特殊情况下可以使用方法引用、构造器引用、数组引用代替lambda表达式,所以都是函数式接口的一个实例
Stream api
Stream api(java.util.stream)将真正的函数式编程风格引入到java中。
- 自己不存储元素,也不会改变源对象,会返回一个持有结果的新Stream,Stream操作是延迟执行的,等到需要结果的时候才执行。
- 使用
- 创建Stream:四种方式,从集合、数组创建,Stream.of(),创建无限流
- Stream的中间操作:筛选、切片、排序
- Stream的终止操作:匹配与查找,规约reduce,收集collect()
Optional类
为了在程序中避免出现空指针异常而创建的,对对象进行了封装,也是一个容器,如果为空,内部存储一个EMPTY,如果不为空,存储对象本身
- Optional.ofNullable(T t) 传入有可能为空的对象,如果为空不会报异常
- orElse(T t),如果不为空,返回自己,如果为空,返回参数里面提供的对象
- or(),参数为供给者,如果不为空,返回自己的Optional,如果为空,返回参数提供的Optional
- ifPresent(),如果不为空 执行提供者的代码
- ifPresentOrElse(),如果不为空 执行提供者的代码 否则执行Runnable的代码
- isPresent(),不为空返回true
- isEmpty(),为空返回true
- get(),不为空返回对象,为空抛异常
其他版本新特性
jdk9
- 模块化系统:在java模块的顶层目录声明一个module.info.java文件,声明哪些包可以被其他模块访问,需要依赖哪些别的模块的包。本质上是在package上又包了一层,指定哪些部分可以暴露哪些部分隐藏
- 交互式编程环境REPL,jShell.exe
- 语法改进:
- 允许接口定义私有方法
- 钻石操作符可以跟匿名内部类,以前必须指明类型,现在可以推断
- jdk8中资源的自动关闭要去在try后面的小括号里声明及初始化,jdk9中可以在try后的小括号里声明已经初始化过的资源,此时的资源是final的。多个资源用分号隔开
- String底层存储使用
byte[]加编码标记 - 快速创建只读集合:jdk8创建只读集合可以使用
Collections.unmodifiableXxx(),但是当原集合修改时,这个不可变集合的内容也会修改,jdk9中提供了of()方法创建不可变集合,如List.of() - InputStream:有了一个非常有用的方法transformTo(),可以直接将输入流写出到输出流,不用再创建数组读取再写出了,这个方法内部封装了这些操作
- Stream api:新方法,takeWhile、dropWhile、ofNullable、iterator()
- Optional:可以使用stream()也获取为一个流
jdk10
- 局部变量类型推断,使用var表示局部变量。注意var不是一个关键字,是一个类型名,在编译后会替换为实际的类型,只发生在编译阶段
- 快速创建只读集合:增加了List.copyOf()从现有集合重新拷贝一份创建为不可变集合。如果本身就是只读的,直接返回,不复制
jdk11
- 新增一系列字符串处理方法
- Optional新增了一些方法,整理在了前面
- 局部变量类型推断,可以使用注解修饰
- 新增HttpClient api,支持同步和异步
- 更简化的编译运行程序,直接java xxx.java 就可以编译运行,但是第一个类必须有main方法,不能识别别的文件里面的类
- ZGC
- Collection接口