JavaSE学习小结二
记录学习----------JavaSE模块三&四 其中大部分是一些工具类,会将常用方法及其功能描述整理成表格方便日后查阅
一、Object类
1. 基本概念
- java.lang.Object类是Java语言中类层次结构的根类,也就是说任何一个类都是该类的直接或者间接子类。
- 如果定义一个Java类时没有使用extends关键字声明其父类,则其父类为 java.lang.Object 类。
- Object类定义了“对象”的基本行为, 被子类默认继承
2. 常用方法
方法名 |
功能 |
Object() |
使用无参方式构造对象 |
boolean equals(Objectobj) |
用于判断调用对象是否与参数对象相等。该方法默认比较两个对象的地址是否相等,与 == 运算符的结果一致若希望比较两个对象的内容,则需要重写该方法。若该方法被重写后,则应该重写hashCode方法来保证结果的一致性。 |
int hashCode() |
用于获取调用对象的哈希码值(内存地址的编号)。若两个对象调用equals方法相等,则各自调用该方法的结果必须相同。若两个调用对象equals方法不相等,则各自调用该方法的结果应该不相同。为了使得该方法与equals方法保持一致,需要重写该方法。 |
String toString() |
用于获取调用对象的字符串形式。该方法默认返回的字符串为:包名.类名@哈希码值的十六进制。为了返回更有意义的数据,需要重写该方法。使用print或println打印引用或字符串拼接引用都会自动调用该方法 |
Class> getClass() |
用于返回调用对象执行时的Class实例,反射机制使用 |
3. 示例:
要求:
- 编程实现Student类的封装,特征:学号(id)和姓名,要求提供打印所有特征的方法。
- 编程实现StudentTest类,在main方法中使用有参方式构造两个Student类型的对象并打印特征。
import java.util.Objects;
public class Student extends Object {
private int id;
private String name;
public Student() {
}
public Student(int id, String name) {
setId(id);
setName(name);
}
public int getId() {
return id;
}
public void setId(int id) {
if (id > 0) {
this.id = id;
} else {
System.out.println("学号不合理哦!!!");
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return id == student.id &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
public class StudentTest {
public static void main(String[] args) {
Student s1 = new Student(1001, "zhangfei");
Student s2 = new Student(1001, "zhangfei");
boolean b1 = s1.equals(s2);
System.out.println("b1 = " + b1);
System.out.println(s1 == s2);
System.out.println("----------------------------------------------------------");
int ia = s1.hashCode();
int ib = s2.hashCode();
System.out.println("ia = " + ia);
System.out.println("ib = " + ib);
System.out.println("----------------------------------------------------------");
String str1 = s1.toString();
System.out.println("str1 = " + str1);
System.out.println(s1);
String str2 = "hello" + s1;
System.out.println("str2 = " + str2);
}
}
4. 小结
Object类主要需要知道一下知识点:
- equals() 方法 默认比较两个对象的地址是否相等,这一点与直接使用运算符 “ == ” 是一样的,以上例为例,如果我们希望使用equals()方法比较两个实例的内容是否相同就需要重写了
- hashCode() 必须和equals() 保持一致,这是因为如果两个对象相同,那么获取到的哈希值也是一样的,所以在重写equals() 的时候也要记得重写 hashCode()
- 一般情况下我们可以直接使用快捷键生成 equals() hashCode() toString() 的代码
二、包装类的装箱与拆箱
1. 基本数据类型 与 对应的包装类
包装类 |
基本数据类型 |
java.lang.Byte |
byte |
java.lang.Short |
short |
java.lang.Integer |
int |
java.lang.Long |
long |
java.lang.Float |
float |
java.lang.Double |
double |
java.lang.Boolean |
boolean |
java.lang.Character |
char |
1.Integer
1.1 常用变量(表示一些常用的数值,比如最大值、最小值……)
常量名 |
功能 |
public static final int MAX_VALUE |
表示 int 类型的最大值 : 2^31 - 1 |
public static final int MIN_VALUE |
表示 int 类型的最小值 : - 2^31 |
public static final int SIZE |
表示 int 类型采用二进制补码形式的位数 |
public static final int BYTES |
表示 int 类型所占的字节个数 |
public static final Class TYPE |
表示int类型的Class实例(反射机制中会用到) |
1.2 常用方法
方法名 |
功能 |
Integer(int value) |
根据参数指定的整数来构造对象(已过时) |
Integer(String s) |
根据参数指定的字符串来构造对象 (已过时) |
int intValue() |
获取调用对象中的整数值并返回 (拆箱, Java5已实现自动拆箱) |
static Integer valueOf(int i) |
根据参数指定整数值得到Integer类型对象 (装箱, Java5已实现自动装箱) |
boolean equals(Object obj) |
比较调用对象与参数指定的对象是否相等 |
String toString() |
返回描述调用对象数值的字符串形式 |
static int parseInt(String s) |
将字符串类型转换为int类型并返回 |
static String toString(int i) |
获取参数指定整数的十进制字符串形式 |
static String toBinaryString(int i) |
获取参数指定整数的二进制字符串形式 |
static String toHexString(int i) |
获取参数指定整数的十六进制字符串形式 |
static String toOctalString(int i) |
获取参数指定整数的八进制字符串形式 |
1.3 装箱与拆箱
- 自从Java5发布之后,我们不再需要手动进行繁琐的 拆箱 与 装箱 操作,JVM会帮我们完成在运算时所需要的拆箱 与 装箱 操作。
Integer te = 100; //自动拆箱
& int ib = te; //自动装箱
- 在Integer类的内部提供了自动装箱池技术,将-128到127之间的整数已经装箱完毕,当程序中使用该范围之间的整数时,无需装箱直接取用自动装箱池中的对象即可,从而提高效率。
2.Double
2.1 常用常量
常量名 |
功能 |
public static final int SIZE |
表示double类型的二进制位数 |
public static final int BYTES |
表示double类型的字节个数 |
public static final Class TYPE |
表示double类型的Class实例(反射机制中会用到) |
2.2 常用方法
方法名 |
功能 |
Double(double value) |
根据参数指定的浮点数据来构造对象(已过时) |
Double(String s) |
根据参数指定的字符串来构造对象 (已过时) |
double doubleValue() |
获取调用对象中的浮点数据并返回 (拆箱, Java5已实现自动拆箱) |
static Double valueOf(double d) |
根据参数指定浮点数据得到Double类型对象 (装箱, Java5已实现自动装箱) |
boolean equals(Object obj) |
比较调用对象与参数指定的对象是否相等 |
String toString() |
返回描述调用对象数值的字符串形式 |
static double parseDouble(String s) |
将字符串类型转换为double类型并返回 |
boolean isNaN() |
判断调用对象的数值是否为非数字 |
3.Boolean
3.1 常用常量
常量名 |
功能 |
public static final Boolean FALSE |
对应基值为false的对象 |
public static final Boolean TRUE |
对应基值为true的对象 |
public static final Class TYPE |
表示boolean类型的Class实例 |
3.2 常用方法
方法名 |
功能 |
Boolean(boolean value) |
根据参数指定的布尔数值来构造对象(已过时) |
Boolean(String s) |
根据参数指定的字符串来构造对象 (已过时) |
boolean booleanValue() |
获取调用对象中的布尔数值并返回 (拆箱, Java5已实现自动拆箱) |
static Boolean valueOf(boolean b) |
根据参数指定布尔数值得到Boolean类型对象(装箱, Java5已实现自动装箱) |
boolean equals(Object obj) |
比较调用对象与参数指定的对象是否相等 |
String toString() |
返回描述调用对象数值的字符串形式 |
static boolean parseBoolean(String s) |
将字符串类型转换为boolean类型并返回 |
4.Character
4.1 常用常量
常量类型和名称 |
功能介绍 |
public static final int SIZE |
表示char类型的二进制位数 |
public static final int BYTES |
表示char类型的字节个数 |
public static final Class TYPE |
表示char类型的Class实例 |
4.2常用方法
方法声明 |
功能介绍 |
Character(char value) |
根据参数指定的字符数据来构造对象(已过时) |
char charValue() |
获取调用对象中的字符数据并返回 |
static Character valueOf(char c) |
根据参数指定字符数据得到Character类型对象 |
boolean equals(Object obj) |
比较调用对象与参数指定的对象是否相等 |
String toString() |
返回描述调用对象数值的字符串形式 |
static boolean isUpperCase(char ch) |
判断参数指定字符是否为大写字符 |
static boolean isLowerCase(char ch) |
判断参数指定字符是否为小写字符 |
static boolean isDigit(char ch) |
判断参数指定字符是否为数字字符 |
static char toUpperCase(char ch) |
将参数指定的字符转换为大写字符 |
static char toLowerCase(char ch) |
将参数指定的字符转换为小写字符 |
5.包装类小结
- 基本数据类型转换为对应包装类的方式
调用包装类的构造方法或静态方法即可
- 获取包装类对象中基本数据类型变量数值的方式
调用包装类中的xxxValue方法即可
- 字符串转换为基本数据类型的方式
调用包装类中的parseXxx方法即可
三、数学工具类
1. Math类
方法声明 |
功能介绍 |
static int max(int a, int b) |
返回两个参数中的最大值 |
static int min(int a, int b) |
返回两个参数中的最小值 |
static double pow(double a, double b) |
返回第一个参数的幂 |
static int abs(int a) |
返回参数指定数值的绝对值 |
static long round(double a) |
返回参数四舍五入的结果 |
static double sqrt(double a) |
返回参数的平方根 |
\static double random() |
返回0.0到1.0的随机数 |
2. BigDecimal类(实现精确运算,因为float类型和double类型在运算时可能会有误差)
方法声明 |
功能介绍 |
BigDecimal(String val) |
根据参数指定的字符串来构造对象 |
BigDecimal add(BigDecimal augend) |
用于实现加法运算 |
BigDecimal subtract(BigDecimal subtrahend) |
用于实现减法运算 |
BigDecimal multiply(BigDecimal multiplicand) |
用于实现乘法运算 |
BigDecimal divide(BigDecimal divisor) |
用于实现除法运算 |
public class BigDecimalTest {
public static void main(String[] args) {
BigDecimal bd1 = new BigDecimal("5.2");
BigDecimal bd2 = new BigDecimal("1.3");
System.out.println("实现加法运算的结果是:" + bd1.add(bd2));
System.out.println("实现减法运算的结果是:" + bd1.subtract(bd2));
System.out.println("实现乘法运算的结果是:" + bd1.multiply(bd2));
System.out.println("实现除法运算的结果是:" + bd1.divide(bd2));
System.out.println("---------------------------------------------------------------");
System.out.println(0.1 + 0.2);
BigDecimal bd3 = new BigDecimal("0.1");
BigDecimal bd4 = new BigDecimal("0.2");
System.out.println("精确计算的结果是:" + bd3.add(bd4));
System.out.println("---------------------------------------------------------------");
BigDecimal bd5 = new BigDecimal("2");
BigDecimal bd6 = new BigDecimal("0.3");
System.out.println("除法运算的结果是:" + bd5.divide(bd6, RoundingMode.HALF_UP));
}
}
3.BigInteger类(表示比long类型范围还大的整数数据)
方法声明 |
功能介绍 |
BigInteger(String val) |
根据参数指定的字符串来构造对象 |
BigInteger add(BigInteger val) |
用于实现加法运算 |
BigInteger subtract(BigInteger val) |
用于实现减法运算 |
BigInteger multiply(BigInteger val) |
用于实现乘法运算 |
BigInteger divide(BigInteger val) |
用于实现除法运算 |
BigInteger remainder(BigInteger val) |
用于实现取余运算 |
BigInteger[] divideAndRemainder(BigInteger val) |
用于实现取商和余数的运算 |
public class BigIntegerTest {
public static void main(String[] args) {
BigInteger bi1 = new BigInteger("20");
BigInteger bi2 = new BigInteger("8");
System.out.println("实现加法运算的结果是:" + bi1.add(bi2));
System.out.println("实现减法运算的结果是:" + bi1.subtract(bi2));
System.out.println("实现乘法运算的结果是:" + bi1.multiply(bi2));
System.out.println("实现除法运算的结果是:" + bi1.divide(bi2));
System.out.println("实现取余运算的结果是:" + bi1.remainder(bi2));
System.out.println("-----------------------------------------------------");
BigInteger[] arr = bi1.divideAndRemainder(bi2);
for (int i = 0; i < arr.length; i++) {
System.out.println("下标为" + i + "的元素是:" + arr[i]);
}
}
}
四、String类
1.基础知识
- Java程序中所有的字符串字面值都可以使用该类的对象加以描述
- 该类由final关键字修饰(该类不能被继承)
- 从jdk1.9开始该类的底层使用 byte[]加上编码标记来存储数据,从而节约了一些空间。
- 该类描述的字符串内容是个常量不可更改,因此可以被共享使用,如:
String str1 = “abc”
; 其中"abc"这个字符串是个常量不可改变。 str1 = “123”;
将“123”字符串的地址赋值给变量str1。 改变的是str1的指向而不是改变指向的内容,换句话来说 str1 原本指向的是存储 “ abc ” 的空间的地址,当运行 str1 = “123”;
后 str1 指向的是存储 “ 123 ” 的空间的地址,原本存储 “ abc ” 的空间如果不需要就会被JVM的垃圾回收机制自动回收。
2.常量池
我们在之前提到过,字符串内容是个常量不可更改。因此Java虚拟机将首次出现的字符串放入常量池中,若后续代码中出现了相同字符串内容则直接使用池中已有的字符串对象而无需申请内存及创建对象,从而提高了性能。如果我们想验证常量池的存在可以这样做:
public class StringPool {
public static void main(String[] args) {
String str1 = "abc";
String str2 = "abc";
System.out.println(str1 == str2);
}
}
3. 常用构造方法(如同上文所说直接赋值也可以)
方法声明 |
功能介绍 |
String() |
使用无参方式构造对象得到空字符序列 |
String(byte[] bytes, int offset, int length) |
使用bytes数组中下标从offset位置开始的length个字节来构造对象 |
String(byte[] bytes) |
使用bytes数组中的所有内容构造对象 |
String(char[] value, int offset, int count) |
使用value数组中下标从offset位置开始的count个字符来构造对象 |
String(char[] value) |
使用value数组中的所有内容构造对象 |
String(String original) |
根据参数指定的字符串内容来构造对象,新创建对象为参数对象的副本 |
4.常用成员方法
方法声明 |
功能介绍 |
String toString() |
返回字符串本身 |
byte[] getBytes() |
将当前字符串内容转换为byte数组并返回 |
char[] toCharArray() |
用于将当前字符串内容转换为char数组并返回 |
char charAt(int index) |
方法charAt用于返回字符串指定位置的字符。 |
int length() |
返回字符串字符序列的长度 |
boolean isEmpty() |
判断字符串是否为空 |
判断一个字符串是否是回文(一道非常基础的题目了, 各种地方都能看见)
public class test {
public static void main(String[] args) {
String str = new String("柳庭风静人眠昼,昼眠人静风庭柳");
System.out.println(str);
int len = str.length();
for(int i = 0; i < len/2; i++) {
if(str.charAt(i) != str.charAt(len - 1 - i)) {
System.out.println("字符串不是回文");
return;
}
}
System.out.println("字符串是回文");
}
}
- compareTo() & compareToIgnoreCase() 方法用于实现字符串之间的比较
方法声明 |
功能介绍 |
int compareTo(String anotherString) |
用于比较调用对象和参数对象的大小关系 |
int compareToIgnoreCase(String str) |
不考虑大小写,也就是’a’和’A’是相等的关系 |
public class test {
public static void main(String[] args) {
String str1 = new String("hello");
System.out.println("str1 = " + str1);
System.out.println(str1.compareTo("world"));
System.out.println(str1.compareTo("haha"));
System.out.println(str1.compareTo("hehe"));
System.out.println(str1.compareTo("heihei"));
System.out.println(str1.compareTo("helloworld"));
System.out.println(str1.compareToIgnoreCase("HELLO"));
}
}
- 其他常用方法(String类封装好的方法还是很多的)
- 字符串拼接常用方法
方法声明 |
功能介绍 |
String concat(String str) |
用于实现字符串的拼接 |
boolean contains(CharSequence s) |
用于判断当前字符串是否包含参数指定的内容 |
String toLowerCase() |
返回字符串的小写形式 |
String toUpperCase() |
返回字符串的大写形式 |
String trim() |
返回去掉前导和后继空白的字符串 |
boolean startsWith(String prefix) |
判断字符串是否以参数字符串开头 |
boolean startsWith(String prefix, int toffset) |
从指定位置开始是否以参数字符串开头 |
boolean endsWith(String suffix) |
判断字符串是否以参数字符串结尾 |
- 字符串比较常用方法
方法声明 |
功能介绍 |
boolean equals(Object anObject) |
用于比较字符串内容是否相等并返回 |
int hashCode() |
获取调用对象的哈希码值 |
boolean equalsIgnoreCase(String anotherString) |
用于比较字符串内容是否相等并返回,不考虑大小写,如:'A’和’a’是相等 |
- 字符串获取下标常用方法
方法声明 |
功能介绍 |
int indexOf(int ch) |
用于返回当前字符串中参数ch指定的字符第一次出现的下标 |
int indexOf(int ch, int fromIndex) |
用于从fromIndex位置开始查找ch指定的字符 |
int indexOf(String str) |
在字符串中检索str返回其第一次出现的位置,若找不到返回-1 |
int indexOf(String str, int fromIndex) |
表示从字符串的fromIndex位置开始检索str第一次出现的位置 |
int lastIndexOf(int ch) |
用于返回参数ch指定的字符最后一次出现的下标 |
int lastIndexOf(int ch, int fromIndex) |
用于从fromIndex位置开始查找ch指定字符出现的下标 |
int lastIndexOf(String str) |
返回str指定字符串最后一次出现的下标 |
int lastIndexOf(String str, int fromIndex) |
用于从fromIndex位置开始反向搜索的第一次出现的下标。 |
- 字符串获取字串常用方法
方法声明 |
功能介绍 |
String substring(int beginIndex, int endIndex) |
返回字符串中从下标beginIndex(包括)开始到 endIndex(不包括)结束的子字符串 |
String substring(int beginIndex) |
返回字符串中从下标beginIndex(包括)开始到字符串结尾的子字符串 |
四(补)可变长字符串StringBuffer & StringBuilder
- 关于StringBuffer & StringBuilder:
- StringBuffer类是从jdk1.0开始存在,属于线程安全的类,因此效率比较低。
- StringBuilder类是从jdk1.5开始存在,属于非线程安全的类,效率比较高。(不考虑锁了,当然会更快)
StringBuilder类
1. 构造方法
方法声明 |
功能介绍 |
StringBuilder() |
使用无参方式构造对象,容量为16 |
StringBuilder(int capacity) |
根据参数指定的容量来构造对象,容量为参数指定大小 |
StringBuilder(String str) |
根据参数指定的字符串来构造对象,容量为:16+字符串长度 |
2. 常用方法(前两个注意区分一下)
方法声明 |
功能介绍 |
int capacity() |
用于返回调用对象的容量 |
int length() |
用于返回字符串的长度,也就是字符的个数 |
StringBuilder insert(int offset, String str) |
在下标为 offset 的位置插入字符串并返回调用对象的引用,就是自己。 |
StringBuilder append(String str) |
在字符串末尾追加字符串 |
StringBuilder deleteCharAt(int index) |
将当前字符串中下标为index位置的单个字符删除 |
StringBuilder delete(int start,int end) |
删除区间在 [start, end) 的字符串,其实就是左闭右开 |
StringBuilder replace(int start,int end,String str) |
替换字符串,和上面一样也是左闭右开 |
StringBuilder reverse() |
字符串反转 |
- StringBuilder的很多方法的返回值均为StringBuilder类型。这些方法的返回语句均为:return this。由此可见,这些方法在对StringBuilder所封装的字符序列进行改变后又返回了该对象的引用。基于这样设计的目的在于可以连续调用。
- 要注意insert()方法在插入时,参数index是字符串str的第一个字符出现的下标。如str = “xxxxxx” 则str.insert(3, “abc”)的结果为:xxxabcxxx
五、正则表达式
1. 基本知识
- 正则表达式本质就是一个“规则字符串”,可以用于对字符串数据的格式进行验证,以及匹配、查找、替换等操作。该字符串通常使用^运算符作为开头标志,使用$运算符作为结尾标志,当然也可以省略。
- 简单而言:正则表达式是由一些具有特殊含义的字符组成的字符串,多用于查找、替换符合规则的字符串。在表单验证、Url映射等处都会经常用到。
2.正则表达式的规则
正则表达式 |
说明 |
[abc] |
可以出现a、b、c中任意一个字符 |
[^abc] |
可以出现任何字符,除了a、b、c的任意字符 |
[a-z] |
可以出现a、b、c、……、z中的任意一个字符 |
[a-zA-Z0-9] |
可以出现az、AZ、0~9中任意一个字符 |
正则表达式 |
说明 |
. |
任意一个字符(通常不包含换行符) |
\d |
任意一个数字字符,相当于[0-9] |
\D |
任意一个非数字字符 |
\s |
空白字符,相当于[\t\n\x0B\f\r] |
\S |
非空白字符 |
\w |
任意一个单词字符,相当于[a-zA-Z_0-9] |
\W |
任意一个非单词字符 |
正则表达式 |
说明 |
X? |
表示X可以出现一次或一次也没有,也就是0 ~ 1次 |
X* |
表示X可以出现零次或多次,也就是0 ~ n次 |
X+ |
表示X可以出现一次或多次,也就是1 ~ n次 |
X{n} |
表示X可以出现恰好 n 次 |
X{n,} |
表示X可以出现至少 n 次,也就是>=n次 |
X{n,m} |
表示X可以出现至少 n 次,但是不超过 m 次,也就是>=n并且<=m次 |
- 简单的示例: 表示6位数字
String reg = "^[0-9]{6}$";
当然就像一开始说到的我们可以省略 ^ 和 $ 写成这个样子String reg = "[0-9]{6}";
之后我们还能进一步简化,用到第二个表格中的语法:String reg = "\\d{6}";
- 稍微难一些:描述身份证号码的规则:总共18位,6位数字代表地区,4位数字代表年,2位数字代表月,2位数字代表日期, 3位数字代表个人,最后一位可能数字也可能是X。
String reg = "(\\d{6})(\\d{4})(\\d{2})(\\d{2})(\\d{3})([0-9|X])";
3. 正则表达式的常用方法
正则表达式 |
说明 |
boolean matches(String regex) |
判断当前正在调用的字符串是否匹配参数指定的正则表达式规则 |
String[] split(String regex) |
参数regex为正则表达式,以regex所表示的字符串为分隔符,将字符串拆分成字符串数组 |
String replace(char oldChar, char newChar) |
使用参数newChar替换此字符串中出现的所有参数 oldChar |
String replaceFirst(String regex, String replacement) |
替换此字符串匹配给定的正则表达式的第一个子字符串 |
String replaceAll(String regex, String replacement) |
将字符串中匹配正则表达式regex的字符串替换成 replacement |
六、日期类(就是一个工具类)
1. System类中的关于时间的方法
方法声明 |
功能介绍 |
static longcurrentTimeMillis() |
返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差 |
public class SystemTest {
public static void main(String[] args) {
long msec = System.currentTimeMillis();
System.out.println("当前系统时间距离1970年1月1日0时0分0秒已经过去" + msec + "毫秒了!");
}
}
2.Date类 & SimpleDateFormat类
2.1 Date类
- java.util.Date类主要用于描述特定的瞬间,也就是年月日时分秒,可以精确到毫秒
- 常用方法如下:
方法声明 |
功能介绍 |
Date() |
使用无参的方式构造对象,也就是当前系统时间 |
Date(long date) |
根据参数指定毫秒数构造对象, 参数为距离1970年1月1日0时0分0秒的毫秒数 |
long getTime() |
获取调用对象距离1970年1月1日0时0分0秒的毫秒数 |
void setTime(long time) |
设置调用对象为距离基准时间time毫秒的时间点 |
public class DateTest {
public static void main(String[] args) {
Date d1 = new Date();
System.out.println("d1 = " + d1);
System.out.println("------------------------------------");
Date d2 = new Date(1000);
System.out.println("d2 = " + d2);
System.out.println("------------------------------------");
long msec = d2.getTime();
System.out.println("获取到的毫秒数是:" + msec);
d2.setTime(2000);
System.out.println("修改后的时间是:" + d2);
}
}
2.2 SimpleDateFormat类
- 我们可以用 SimpleDateFormat类 实现日期和文本之间的转换。
- 常用方法如下:
方法声明 |
功能介绍 |
SimpleDateFormat() |
使用无参方式构造对象 |
SimpleDateFormat(String pattern) |
根据参数指定的模式来构造对象,模式主要有: y-年 M-月 d-日 H-时 m-分 s-秒 |
final String format(Date date) |
用于将日期类型转换为文本类型 |
Date parse(String source) |
用于将文本类型转换为日期类型 |
public class SimpleDateFormatTest {
public static void main(String[] args) throws Exception {
Date d1 = new Date();
System.out.println("d1 = " + d1);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String format = sdf.format(d1);
System.out.println("转换后的日期为:" + format);
Date parse = sdf.parse(format);
System.out.println("转回日期格式的结果为:" + parse);
}
}
2.3 Calendar类
- java.util.Calender类主要用于描述特定的瞬间,取代Date类中的过时方法实现全球化。
- 该类是个抽象类,因此不能实例化对象,其具体子类针对不同国家的日历系统,其中应用最广泛的是GregorianCalendar(格里高利历),对应世界上绝大多数国家/地区使用的标准日历系统。
- 常用方法如下:
方法声明 |
功能介绍 |
static Calendar getInstance() |
用于获取Calendar类型的引用 |
void set(int year, int month, int date, int hourOfDay, int minute, int second) |
用于设置年月日时分秒信息 |
Date getTime() |
用于将Calendar类型转换为Date类型 |
void set(int field, int value) |
设置指定字段的数值 |
void add(int field, int amount) |
向指定字段增加数值 |
我们要注意一个问题那就是Calendar类是一个抽象类,那为什么可以可以通过 getInstance() 方法用于获取Calendar类型的引用?
这里实际上是使用了多态,也就是常说的多态的第三种使用方式。我们由源码可知,返回的并不是Calendar类型的对象,而是Calendar类的子类GregorianCalendar等对象,形成了多态
Calendar instance = Calendar.get Instance();
instance.set(2008, 8-1, 8, 20, 8, 8);
Date d2 = instance.getTime();
String format1 = sdf.format(d2);
System.out.println("获取到的时间是:" + format1);
小结:多态的使用场合
public static void draw(Shape s){
s.show();
}
draw(new Rect(1, 2, 3, 4));
Account acc = new FixedAccount();
Calender getInstance(){
return new GregorianCalendar(zone, aLocale);
}
3. Java8的日期类新特性
- Java 8通过发布新的Date-Time API来进一步加强对 日期与时间的处理。
- java.time包:该包日期/时间API的基础包。
- java.time.chrono包:该包提供对不同日历系统的访问。
- java.time.format包:该包能够格式化和解析日期时间对象。
- java.time.temporal包:该包包含底层框架和扩展特性。
- java.time.zone包:该包支持不同时区以及相关规则的类。
3.1 LocalDate类的概述
- java.time.LocalDate类主要用于描述年-月-日格式的日期信息,该类不表示时间和时区信息
- 常用方法如下,使用起来也很简单,
LocalDate now = LocalDate.now();
之后就可以直接输出了,比如:System.out.println("获取到的当前日期是:" + now);
方法声明 |
功能介绍 |
static LocalDate now() |
在默认时区中从系统时钟获取当前日期 |
3.2 LocalTime类的概述
- java.time.LocalTime 类主要用于描述时间信息,可以描述时分秒以及纳秒。
- 常用方法如下,使用的话和上面的LocalDate类是一样的
方法声明 |
功能介绍 |
static LocalTime now() |
从默认时区的系统时间中获取当前时间 |
static LocalTime now(ZoneId zone) |
获取指定时区的当前时间 |
3.3 LocalDateTime类的概述
- java.time.LocalDateTime类主要用于描述ISO-8601日历系统中没有时区的日期时间,如2007-12-03T10:15:30。
- 常用方法如下:
方法声明 |
功能介绍 |
static LocalDateTime now() |
从默认时区的系统时间中获取当前日期时间 |
static LocalDateTime of(int year, int month, int dayOfMonth, int hour, int minute, int second) |
根据参数指定的年月日时分秒信息来设置日期时间 |
int getYear() |
获取年份字段的数值 |
int getMonthValue() |
获取1到12之间的月份字段 |
int getDayOfMonth() |
获取日期字段 |
int getHour() |
获取小时数 |
int getMinute() |
获取分钟数 |
int getSecond() |
获取秒数 |
LocalDateTime withYear(int year) |
设置为参数指定的年 |
LocalDateTime withMonth(int month) |
设置为参数指定的月 |
LocalDateTime withDayOfMonth(int dayOfMonth) |
设置为参数指定的日 |
LocalDateTime withHour(int hour) |
设置为参数指定的时 |
LocalDateTime withMinute(int minute) |
设置为参数指定的分 |
LocalDateTime withSecond(int second) |
设置为参数指定的秒 |
LocalDateTime plusYears(long years) |
加上参数指定的年 |
LocalDateTime plusMonths(long months) |
加上参数指定的月 |
LocalDateTime plusDays(long days) |
加上参数指定的日 |
LocalDateTime plusHours(long hours) |
加上参数指定的时 |
LocalDateTime plusMinutes(long minutes) |
加上参数指定的分 |
LocalDateTime plusSeconds(long seconds) |
加上参数指定的秒 |
LocalDateTime minusYears(long years) |
减去参数指定的年 |
LocalDateTime minusMonths(long months) |
减去参数指定的月 |
LocalDateTime minusDays(long days) |
减去参数指定的日 |
LocalDateTime minusHours(long hours) |
减去参数指定的时 |
LocalDateTime minusMinutes(long minutes) |
减去参数指定的分 |
LocalDateTime minusSeconds(long seconds) |
减去参数指定的秒 |
- LocalDateTime类的使用是比较简单的,代码示例如下:
public class LocalDateTimeTest {
public static void main(String[] args) {
LocalDate now = LocalDate.now();
System.out.println("获取到的当前日期是:" + now);
LocalTime now1 = LocalTime.now();
System.out.println("获取到的当前时间是:" + now1);
LocalDateTime now2 = LocalDateTime.now();
System.out.println("获取到的当前日期时间是:" + now2);
System.out.println("-------------------------------------------------------");
LocalDateTime of = LocalDateTime.of(2008, 8, 8, 20, 8, 8);
System.out.println("指定的日期时间是:" + of);
System.out.println("获取到的年是:" + of.getYear());
System.out.println("获取到的月是:" + of.getMonthValue());
System.out.println("获取到的日是:" + of.getDayOfMonth());
System.out.println("获取到的时是:" + of.getHour());
System.out.println("获取到的分是:" + of.getMinute());
System.out.println("获取到的秒是:" + of.getSecond());
System.out.println("-------------------------------------------------------");
LocalDateTime localDateTime = of.withYear(2012);
System.out.println("localDateTime = " + localDateTime);
System.out.println("of = " + of);
LocalDateTime localDateTime1 = localDateTime.withMonth(12);
System.out.println("localDateTime1 = " + localDateTime1);
System.out.println("-------------------------------------------------------");
LocalDateTime localDateTime2 = localDateTime1.plusDays(2);
System.out.println("localDateTime2 = " + localDateTime2);
System.out.println("localDateTime1 = " + localDateTime1);
LocalDateTime localDateTime3 = localDateTime2.plusHours(3);
System.out.println("localDateTime3 = " + localDateTime3);
System.out.println("-------------------------------------------------------");
LocalDateTime localDateTime4 = localDateTime3.minusMinutes(1);
System.out.println("localDateTime4 = " + localDateTime4);
System.out.println("localDateTime3 = " + localDateTime3);
LocalDateTime localDateTime5 = localDateTime4.minusSeconds(3);
System.out.println("localDateTime5 = " + localDateTime5);
}
}
3.4 Instant类
方法声明 |
功能介绍 |
static Instant now() |
从系统时钟上获取当前时间 |
OffsetDateTime atOffset(ZoneOffset offset) |
将此瞬间与偏移量组合以创建偏移日期时间 |
static Instant ofEpochMilli(long epochMilli) |
根据参数指定的毫秒数来构造对象,参数为距离1970年1月1 日0时0分0秒的毫秒数 |
long toEpochMilli() |
获取距离1970年1月1日0时0分0秒的毫秒数 |
public class InstantTest {
public static void main(String[] args) {
Instant now = Instant.now();
System.out.println("获取到的当前时间为:" + now);
OffsetDateTime offsetDateTime = now.atOffset(ZoneOffset.ofHours(8));
System.out.println("偏移后的日期时间为:" + offsetDateTime);
System.out.println("--------------------------------------------------------");
long g1 = now.toEpochMilli();
System.out.println("获取到的毫秒差为:" + g1);
Instant instant = Instant.ofEpochMilli(g1);
System.out.println("根据参数指定的毫秒数构造出来的对象为:" + instant);
}
}
3.5 DateTimeFormatter类
方法声明 |
功能介绍 |
static DateTimeFormatter ofPattern(String pattern) |
根据参数指定的模式来获取对象 |
String format(TemporalAccessor temporal) |
将参数指定日期时间转换为字符串 |
TemporalAccessor parse(CharSequence text) |
将参数指定字符串转换为日期时间 |
public class DateTimeFormatterTest {
public static void main(String[] args) {
LocalDateTime now = LocalDateTime.now();
System.out.println("now = " + now);
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String str = dateTimeFormatter.format(now);
System.out.println("调整格式后的结果是:" + str);
TemporalAccessor parse = dateTimeFormatter.parse(str);
System.out.println("转回去的结果是:" + parse);
}
}
七、集合(灰常的重要,即便算法不是那么精通,平时也会经常用到)
1.集合的框架
- 首先来说Java集合框架的两个顶层的框架: java.util.Collection 集合 & java.util.Map 集合
- java.util.Collection 集合存取元素的基本单位是:单个元素
- java.util.Map 集合存取元素的基本单位是:单个键值对
2.Collection集合
- 从上图我们可以很清晰地看到 java.util.Collection 接口是 List 接口、Queue 接口以及 Set 接口的父接口,因此该接口里定义的方法既可用于操作 List 集合,也可用于操作 Queue 集合和 Set 集合。
- 这里也再说一句:如果想创建Collection对象,依旧是使用多态,比如
Collection c1 = new ArrayList();
- 常用方法(能记多少记多少,忘了直接查API或者看这这里~):
方法声明 |
功能介绍 |
boolean add(E e) |
向集合中添加对象 |
boolean addAll(Collection extends E> c) |
用于将参数指定集合c中的所有元素添加到当前集合中 |
boolean contains(Object o) |
判断是否包含指定对象 |
boolean containsAll(Collection> c) |
判断是否包含参数指定的所有对象 |
boolean retainAll(Collection> c) |
保留当前集合中存在且参数集合中存在的所有对象 |
boolean remove(Object o) |
从集合中删除对象 |
boolean removeAll(Collection> c) |
从集合中删除参数指定的所有对象 |
void clear() |
清空集合 |
int size() |
返回包含对象的个数 |
boolean isEmpty() |
判断是否为空 |
boolean equals(Object o) |
判断是否相等 |
int hashCode() |
获取当前集合的哈希码值 |
Object[] toArray() |
将集合转换为数组 |
Iterator iterator() |
获取当前集合的迭代器 |
public class test {
public static void main(String[] args) {
Collection c1 = new ArrayList();
System.out.println("集合中的元素有:" + c1);
System.out.println("--------------------------------------------------------");
c1.add(new String("one"));
System.out.println("集合中的元素有:" + c1);
c1.add(Integer.valueOf(2));
System.out.println("集合中的元素有:" + c1);
c1.add(new Person("zhangfei", 30));
System.out.println("集合中的元素有:" + c1);
System.out.println("--------------------------------------------------------");
Collection c2 = new ArrayList();
c2.add("two");
c2.add(4);
System.out.println("c2 = " + c2);
Collection c3 = new ArrayList();
c3.add("three");
c3.add(6);
System.out.println("c3 = " + c3);
c1.addAll(c2);
c1.add(c3);
System.out.println("c1 = " + c1);
System.out.println("--------------------------------------------------------");
System.out.println("--------------------------------------------------------");
Object[] objects = c5.toArray();
System.out.println("数组中的元素有:" + Arrays.toString(objects));
Collection objects1 = Arrays.asList(objects);
System.out.println("集合中的元素有:" + objects1);
}
}
3.Iterator接口 — 迭代器
- 迭代器,就是用于遍历集合的。而且呢,java.util.Collection 接口继承 Iterator接 口,因此所有实现 Collection接 口的实现类都可以使用该迭代器对象。
- 常用方法也就三个:
方法声明 |
功能介绍 |
boolean hasNext() |
判断集合中是否有可以迭代/访问的元素 |
E next() |
用于取出一个元素并指向下一个元素 |
void remove() |
用于删除访问到的最后一个元素 |
- 我们就可以使用这个迭代器,来模拟一下toString方法:
public class test {
public static void main(String[] args) {
Collection c1 = new ArrayList();
c1.add("one");
c1.add(2);
c1.add(new Person("zhangfei", 30));
System.out.println("------------------------------------------------");
Iterator iterator1 = c1.iterator();
StringBuilder sb1 = new StringBuilder();
sb1.append("[");
while (iterator1.hasNext()) {
Object obj = iterator1.next();
if (!iterator1.hasNext()) {
sb1.append(obj).append("]");
} else {
sb1.append(obj).append(",").append(" ");
}
}
System.out.println("c1 = " + sb1);
System.out.println("------------------------------------------------");
iterator1 = c1.iterator();
while (iterator1.hasNext()) {
Object obj = iterator1.next();
if("one".equals(obj)) {
iterator1.remove();
}
}
System.out.println("删除后集合中的元素有:" + c1);
}
}
- 注意事项 :迭代的过程中,要用迭代器的remove()而不是集合的remove()
4.List集合
4.1 基本概念
- java.util.List集合是Collection集合的子集合,该集合中允许有重复的元素并且有先后放入次序。
- 该集合的主要实现类有:ArrayList类、LinkedList类、Stack类、Vector类。
- 其中ArrayList类的底层是采用动态数组进行数据管理的,支持下标访问,增删元素不方便。
- 其中LinkedList类的底层是采用双向链表进行数据管理的,访问不方便,增删元素方便。
- 可以认为ArrayList和LinkedList的方法在逻辑上完全一样,只是在性能上有一定的差别,ArrayList 更适合于随机访问而LinkedList更适合于插入和删除;在性能要求不是特别苛刻的情形下可以忽略这个差别。
- 其中Stack类的底层是采用动态数组进行数据管理的,该类主要用于描述一种具有后进先出特征的 数据结构,叫做栈(last in first out LIFO)。
- 其中Vector类的底层是采用动态数组进行数据管理的,该类与ArrayList类相比属于线程安全的
类,效率比较低,以后开发中基本不用。
4.2 常用方法
方法声明 |
功能介绍 |
void add(int index, E element) |
向集合中指定位置添加元素 |
boolean addAll(int index, Collection extends E> c) |
向集合中添加所有元素 |
E get(int index) |
从集合中获取指定位置元素 |
int indexOf(Object o) |
查找参数指定的对象 |
int lastIndexOf(Object o) |
反向查找参数指定的对象 |
E set(int index, E element) |
修改指定位置的元素 |
E remove(int index) |
删除指定位置的元素 |
List subList(int fromIndex, int toIndex) |
用于获取子List |
4.3 Stack 栈
常用方法如下:
方法声明 |
功能介绍 |
boolean empty() |
测试堆栈是否为空。 |
Object peek( ) |
查看堆栈顶部的对象,但不从堆栈中移除它。 |
Object pop( ) |
移除堆栈顶部的对象,并作为此函数的值返回该对象。 |
Object push(Object element) |
把项压入堆栈顶部。 |
int search(Object element) |
返回对象在堆栈中的位置,以 1 为基数。 |
示例:准备一个Stack集合,将数据11、22、33、44、55依次入栈并打印,然后查看栈顶元素并打印,然后将栈中所有数据依次出栈并打印。再准备一个Stack对象,将数据从第一个栈中取出来放入第二个栈中,然后再从第二个栈中取出并打印。
public class test {
public static void main(String[] args) {
Stack s1 = new Stack();
Stack s2 = new Stack();
for(int i = 1; i<= 5; i++) {
s1.push(i*11);
}
System.out.println("s1 : " + s1);
int len = s1.size();
for(int i = 1; i <= len; i++){
Object pop = s1.pop();
s2.push(pop);
}
System.out.println("s2 : " + s2);
len = s2.size();
for (int i = 1; i <= len; i++) {
Object to = s2.pop();
System.out.println("出栈的元素是:" + to);
}
}
}
- 运行结果:
4.4 Queue集合
- java.util.Queue集合是Collection集合的子集合,与List集合属于平级关系。
- 该集合的主要用于描述具有先进先出特征的数据结构,叫做队列(first in first out FIFO)。
- 该集合的主要实现类是LinkedList类,因为该类在增删方面比较有优势
- 常用方法如下:
方法声明 |
功能介绍 |
boolean offer(E e) |
将一个对象添加至队尾,若添加成功则返回true |
E poll() |
从队首删除并返回一个元素 |
E peek() |
返回队首的元素(但并不删除) |
示例:准备一个Queue集合,将数据11、22、33、44、55依次入队并打印,然后查看队首元素并打印,然后将队列中所有数据依次出队并打印
public class test {
public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<>();
for(int i = 1; i <= 5; i++)
queue.offer(i * 11);
System.out.println(queue);
int len = queue.size();
for(int i = 1; i <= len; i++) {
Integer poll = queue.poll();
System.out.println("出队的元素是:" + poll);
}
}
}
5. Set集合
5.1 基本概念:
- 该集合中元素没有先后放入次序,且不允许重复。
- 该集合的主要实现类是:HashSet类 和 TreeSet类以及LinkedHashSet类。
- 其中HashSet类的底层是采用哈希表进行数据管理的。
- 其中TreeSet类的底层是采用红黑树进行数据管理的。
- 其中LinkedHashSet类与HashSet类的不同之处在于内部维护了一个双向链表,链表中记录了元素的迭代顺序,也就是元素插入集合中的先后顺序,因此便于迭代。
5.2 常用方法
5.3 元素放入HashSet集合的原理(数据结构哈希表的概念)
- 使用元素调用hashCode方法获取对应的哈希码值,再由某种哈希算法计算出该元素在数组中的索引位置。若该位置没有元素,则将该元素直接放入即可。若该位置有元素,则使用新元素与已有元素依次比较哈希值,若哈希值不相同,则将该元素直接放入。若新元素与已有元素的哈希值相同,则使用新元素调用equals方法与已有元素依次比较。若相等则添加元素失败,否则将元素直接放入即可。
- 这也就是为什么要求重写equals方法后要重写hashCode方法。 当两个元素调用equals方法相等时证明这两个元素相同,重写hashCode方法后保证这两个元素得到的哈希码值相同,由同一个哈希算法生成的索引位置相同,此时只需要与该索引位置已有元素比较即可,从而提高效率并避免重复元素的出现。
5.4 TreeSet集合的概念
- TreeSet集合的底层采用红黑树进行数据的管理,当有新元素插入到TreeSet集合时,需要使用新元素与集合中已有的元素依次比较来确定新元素的合理位置。
- 自然排序的规则比较单一,而比较器的规则比较多元化,而且比较器优先于自然排序**(个人倾向于优先使用比较器)**
比较元素大小的规则有两种方式:
使用元素的自然排序规则进行比较并排序,让元素类型实现java.lang.Comparable接口;
使用比较器规则进行比较并排序,构造TreeSet集合时传入java.util.Comparator接口;
public class Student implements Comparable<Student> {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Student o) {
int ia = this.getName().compareTo(o.getName());
if (ia == 0) {
return this.getAge() - o.getAge();
}
return ia;
}
}
public class TreeSetTest {
public static void main(String[] args) {
System.out.println("*****************");
Comparator<Student> comparator = new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
int ia = o1.getName().compareTo(o2.getName());
if(ia == 0) {
return o1.getAge() - o2.getAge();
}
return ia;
}
};
Set<Student> s2 = new TreeSet<>(comparator);
s2.add(new Student("zhangfei", 35));
s2.add(new Student("zhangfei", 30));
s2.add(new Student("guanyu", 35));
s2.add(new Student("liubei", 40));
System.out.println("s2 = " + s2);
}
}
6. Map集合(※※)
6.1 基本概念
- java.util.Map集合中存取元素的基本单位是:单对元素,其中类型参数如下:
K - 此映射所维护的键(Key)的类型,相当于目录。
V - 映射值(Value)的类型,相当于内容。
- 该集合中key是不允许重复的,而且一个key只能对应一个value。
- 该集合的主要实现类有:HashMap类、TreeMap类、LinkedHashMap类、Hashtable类、Properties类。
- 其中HashMap类的底层是采用哈希表进行数据管理的。
- 其中TreeMap类的底层是采用红黑树进行数据管理的。
- 其中LinkedHashMap类与HashMap类的不同之处在于内部维护了一个双向链表,链表中记录了元素的迭代顺序,也就是元素插入集合中的先后顺序,因此便于迭代。
- 其中Hashtable类是古老的Map实现类,与HashMap类相比属于线程安全的类,且不允许null作 为key或者value的数值。
- 其中Properties类是Hashtable类的子类,该对象用于处理属性文件,key和value都是String类型的。
- Map集合是面向查询优化的数据结构, 在大数据量情况下有着优良的查询性能。经常用于根据key检索value的业务场景。
6.2 常用方法
方法声明 |
功能介绍 |
V put(K key, V value) |
将Key-Value对存入Map,若集合中已经包含该Key,则替换该Key所对应的Value,返回值为该Key原来所对应的Value,若没有则返回null |
V get(Object key) |
返回与参数Key所对应的Value对象,如果不存在则返回null |
boolean containsKey(Object key) |
判断集合中是否包含指定的Key |
boolean containsValue (Object value) |
判断集合中是否包含指定的Value |
V remove(Object key) |
根据参数指定的key进行删除 |
Set keySet() |
返回此映射中包含的键的Set视图 |
Collection values() |
返回此映射中包含的值的Set视图 |
Set> entrySet() |
返回此映射中包含的映射的Set视图 |
6. 3元素放入HashMap集合的原理
- 使用元素的key调用hashCode方法获取对应的哈希码值,再由某种哈希算法计算在数组中的索引位置。
- 若该位置没有元素,则将该键值对直接放入即可。
- 若该位置有元素,则使用key与已有元素依次比较哈希值,若哈希值不相同,则将该元素直接放入。
- 若key与已有元素的哈希值相同,则使用key调用equals方法与已有元素依次比较。
- 若相等则将对应的value修改,否则将键值对直接放入即可。
6.4 相关的常量
- DEFAULT_INITIAL_CAPACITY : HashMap的默认容量是16。
- DEFAULT_LOAD_FACTOR:HashMap的默认加载因子是0.75。
- threshold:扩容的临界值,该数值为:容量*填充因子,也就是12。
- TREEIFY_THRESHOLD:若Bucket中链表长度大于该默认值则转化为红黑树存储,该数值是8。
- MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量,该数值是64。
7.Collections类 — 提供对集合操作或者返回集合的静态方法(工具类一个)
方法声明 |
功能介绍 |
static > T max(Collection extends T> coll) |
根据元素的自然顺序返回给定集 合的最大元素 |
static T max(Collection extends T> coll, Comparator super T> comp) |
根据指定比较器引发的顺序返回给定集合的最大元素 |
static > T min(Collection extends T> coll) |
根据元素的自然顺序返回给定集合的最小元素 |
static T min(Collection extends T> coll, Comparator super T> comp) |
根据指定比较器引发的顺序返回给定集合的最小元素 |
static void copy(List super T> dest, List extends T> src) |
将一个列表中的所有元素复制到另一个列表中 |
方法声明 |
功能介绍 |
static void reverse(List> list) |
反转指定列表中元素的顺序 |
static void shuffle(List> list) |
使用默认的随机源随机置换指定的列表 |
static > void sort(List list) |
根据其元素的自然顺序将指定列表按升序排序 |
static void sort(List list, Comparator super T> c) |
根据指定比较器指定的顺序对指定列表进行排序 |
static void swap(List> list, int i, int j) |
交换指定列表中指定位置的元素 |
public class Test {
public static void main(String[] args) {
List<Integer> lt1 = Arrays.asList(10, 30, 20, 50, 45);
System.out.println("集合中的最大值是:" + Collections.max(lt1));
System.out.println("集合中的最小值是:" + Collections.min(lt1));
Collections.reverse(lt1);
System.out.println("lt1 = " + lt1);
Collections.swap(lt1, 0, 4);
System.out.println("交换后:lt1 = " + lt1);
Collections.sort(lt1);
System.out.println("排序后:lt1 = " + lt1);
Collections.shuffle(lt1);
System.out.println("随机置换后:lt1 = " + lt1);
List<Integer> lt2 = Arrays.asList(new Integer[10]);
System.out.println("lt1的大小是:" + lt1.size());
System.out.println("lt2的大小是:" + lt2.size());
Collections.copy(lt2, lt1);
System.out.println("lt2 = " + lt2);
}
}
八、泛型
1. 基本概念
- 通常情况下集合中可以存放不同类型的对象,是因为将所有对象都看做Object类型放入的,因此从集合中取出元素时也是Object类型,为了表达该元素真实的数据类型,则需要强制类型转换,而强制类型转换可能会引发类型转换异常。
- 为了避免上述错误的发生,从Java5开始增加泛型机制,也就是在集合名称的右侧使用<数据类型>的方式来明确要求该集合中可以存放的元素类型,若放入其它类型的元素则编译报错。
- 泛型只在编译时期有效,在运行时期不区分是什么类型。
- 说的更加通俗一些就是,我们将数据类型作为参数传递,其中E相当于形式参数负责占位。以 Queue 队列来举例,
Queue queue = new LinkedList<>();
<>中的 Integer 就是一个实参,而 Queue< E > 类中的 E就是一个形参,当我们在具体编写代码时具体传入的是一个什么样的数据类型,这个集合就会存储什么数据类型。
2. 泛型类、泛型方法 & 泛型接口
2.1 泛型类
- 泛型类,就是相比于普通类而言在类名之后添加了类型参数列表, 比如我们熟知的Stack< E >,ArrayList< E > ,Map等等,等到实例化时指定具体的数据类型(引用数据类型,比如Integer……)
2.2 泛型方法
- 泛型方法就是我们输入参数的时候,输入的是泛型参数,而不是具体的参数。我们在调用这个泛型方法的时需要对泛型参数进行实例化。
- 语法格式:
[访问权限] <泛型> 返回值类型 方法名([泛型标识 参数名称]) { 方法体; }
- 此外还要注意一点:在静态方法中使用泛型参数的时候,需要我们把静态方法定义为泛型方法
public class Person<T> {
private String name;
private int age;
private T gender;
public T getGender() {
return gender;
}
public void setGender(T gender) {
this.gender = gender;
}
public static <T1> void printArray(T1[] arr) {
for (T1 tt: arr) {
System.out.println("tt = " + tt);
}
}
}
2.3 泛型接口
- 泛型接口的概念相比于之前的泛型类、泛型方法更加简单,仅仅是在普通接口的后面添加了类型参数列表
interface Test<T>{
public void show(T t);
}
2.4 泛型的继承
- 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型。
- 子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自己的泛型
- String是Object的子类,但是List< String >并不是List< Object >的子类(※)
public class SubPerson extends Person {
……
}
public class SubPerson extends Person<String> {
……
}
public class SubPerson<T> extends Person<T> {
……
}
public class SubPerson<T, T1> extends Person<T> {
……
}
2.5 泛型通配符(※)
- 泛型通配符共有三种,分别是:
> 无限制通配符:表示我们可以传入任意类型的参数。
extends E> 表示类型的上界是E,只能是E或者是E的子类。
super E> 表示类型的下界是E,只能是E或者是E的父类。
public class Test {
public static void main(String[] args) {
List<ClassA> list1 = new LinkedList<>();
List<ClassB> list2 = new LinkedList<>();
System.out.println("---------------------------------------------");
List<?> list3 = new LinkedList<>();
list3 = list1;
list3 = list2;
Object o = list3.get(0);
System.out.println("---------------------------------------------");
List<? extends ClassA> list4 = new LinkedList<>();
ClassA classA = list4.get(0);
System.out.println("---------------------------------------------");
List<? super ClassA> list5 = new LinkedList<>();
list5.add(new ClassA());
list5.add(new ClassB());
Object object = list5.get(0);
}
}
九、异常(※※)-- Java采用的异常处理机制是将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁、优雅,并易于维护
1.基本知识
- 异常的结构框架:
- Error类主要用于描述Java虚拟机无法解决的严重错误,通常无法编码解决,如:JVM挂掉了
- Exception类主要用于描述因编程错误或偶然外在因素导致的轻微错误,通常可以编码解决,如:0作为除数、空指针等等
- 为避免异常,我们可以使用 if 判断来避免异常的发生,但也需要注意,如果 if 语句过多会导致代码的可读性差,冗长
2. 异常的捕获
try {
编写可能发生异常的代码; }
catch(异常类型 引用变量名) {
编写针对该类异常的处理代码;
}
...
finally {
编写无论是否发生异常都要执行的代码;
}
- 注意事项
a. 当需要编写多个catch分支时,切记小类型应该放在大类型的前面;
b. 懒人的写法: catch(Exception e) {}
c. finally通常用于进行善后处理,如:关闭已经打开的文件等
3. 异常的抛出
- 在某些特殊情况下有些异常不能处理或者不便于处理时,就可以将该异常转移给该方法的调用者,
这种方法就叫异常的抛出。当方法执行时出现异常,则底层生成一个异常类对象抛出,此时异常代
码后续的代码就不再执行。
- 语法格式
访问权限 返回值类型 方法名称(形参列表) throws 异常类型1,异常类型2,…{ 方法体; }
public void show() throws IOException{}
- 方法重写的原则
a. 要求方法名相同、参数列表相同以及返回值类型相同,从jdk1.5开始支持返回子类类型;
b. 要求方法的访问权限不能变小,可以相同或者变大;
c. 要求方法不能抛出更大的异常;
- 注意:
a. 子类重写的方法不能抛出更大的异常、不能抛出平级不一样的异常,但可以抛出一样的异常、更小的异常以及不抛出异常。
b. 若父类中被重写的方法没有抛出异常时,则子类中重写的方法只能进行异常的捕获处理。
c. 若一个方法内部又以递进方式分别调用了好几个其它方法,则建议这些方法内可以使用抛出的方法处理到最后一层进行捕获方式处理。
4. 异常类的示例:
编写一个员工类 —> 属性: 姓名,年龄
如果年龄小于18龄大于150抛出年龄异常
如果姓名为null或者""抛出空异常
public class AgeException extends Exception {
public AgeException() {
}
public AgeException(String message) {
super(message);
}
}
public class NullException extends Exception{
public NullException() {
}
public NullException(String message) {
super(message);
}
}
public class Worker {
private String name;
private int age;
public Worker() {
}
public Worker(String name, int age) throws AgeException, NullException {
setName(name);
setAge(age);
}
public String getName() {
return name;
}
public void setName(String name) throws NullException{
if(!"".equals(name) && name != null){
this.name = name;
} else {
throw new NullException("姓名不可为空(null 或 '')");
}
}
public int getAge() {
return age;
}
public void setAge(int age) throws AgeException {
if(age >= 18 && age <= 150){
this.age = age;
} else {
throw new AgeException("年龄超出合法范围");
}
}
@Override
public String toString() {
return "Worker{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class WorkerTest {
public static void main(String[] args) {
try {
Worker worker = new Worker(null, 34);
String name = worker.getName();
System.out.println(name);
} catch (AgeException e) {
e.printStackTrace();
} catch (NullException e) {
e.printStackTrace();
}
}
}
十、文件File类(※)— 本质上还是一个工具类
方法声明 |
功能介绍 |
File(String pathname) |
根据参数指定的路径名来构造对象 |
File(String parent, String child) |
根据参数指定的父路径和子路径信息构造对象 |
File(File parent, String child) |
根据参数指定的父抽象路径和子路径信息构造对象 |
boolean exists() |
测试此抽象路径名表示的文件或目录是否存在 |
String getName() |
用于获取文件的名称 |
long length() |
返回由此抽象路径名表示的文件的长度 |
long lastModified() |
用于获取文件的最后一次修改时间 |
String getAbsolutePath() |
用于获取绝对路径信息 |
boolean delete() |
用于删除文件,当删除目录时要求是空目录 |
boolean createNewFile() |
用于创建新的空文件 |
boolean mkdir() |
用于创建目录 |
boolean mkdirs() |
用于创建多级目录 |
File[] listFiles() |
获取该目录下的所有内容 |
boolean isFile() |
判断是否为文件 |
boolean isDirectory() |
判断是否为目录 |
File[] listFiles(FileFilter filter) |
获取目录下满足筛选器的所有内容 |
public static void show(File file) {
File[] filesArray = file.listFiles();
for (File tf: filesArray) {
String name = tf.getName();
if (tf.isFile()) {
System.out.println(name);
}
if (tf.isDirectory()) {
System.out.println("[" + name + "]");
show(tf);
}
}
}
FileFilter fileFilter = (File pathname) -> {return pathname.getName().endsWith(".avi");};
File[] filesArray2 = f3.listFiles(fileFilter);
for (File tf : filesArray2) {
System.out.println(tf);
}
十一、IO流
用一张图来理解输入输出:
输入流主要指从文件中读取数据内容输入到程序中,也就是读文件 ------ Input
输出流主要指将程序中的数据内容输出到文件中,也就是写文件 ------ Output
1.体系结构 & 框架
- 字节流主要指以字节为单位进行数据读写的流,可以读写任意类型的文件。
- 字符流主要指以字符(2个字节)为单位进行数据读写的流,只能读写文本文件。
- 节点流主要指直接和输入输出源对接的流。
- 处理流主要指需要建立在节点流的基础之上的流
2.FileWriter & FileReader — 主要用于对文本文件的读写
2.1FileWriter 的常用方法:
方法声明 |
功能介绍 |
FileWriter(String fileName) |
根据参数指定的文件名构造对象 |
FileWriter(String fileName, boolean append) |
以追加的方式根据参数指定的文件名来构造对象 |
void write(int c) |
写入单个字符 |
void write(char[] cbuf, int off, int len) |
将指定字符数组中从偏移量off开始的len个字符写入此文件输出流 |
void write(char[] cbuf) |
将cbuf.length个字符从指定字符数组写入此文件输出流中 |
void flush() |
刷新流 |
void close() |
关闭流对象并释放有关的资源 |
2.2 FileReader 的常用方法:
方法声明 |
功能介绍 |
FileReader(String fileName) |
根据参数指定的文件名构造对象 |
int read() |
读取单个字符的数据并返回,返回-1表示读取到末尾 |
int read(char[] cbuf, int offset, int length) |
从输入流中将最多len个字符的数据读入一个字符数组中,返回读取到的字符个数,返回-1表示读取到末尾 |
int read(char[] cbuf) |
从此输入流中将最多 cbuf.length 个字符的数据读入字符数组中,返回读取到的字符个数,返回-1表示读取到末尾 |
void close() |
关闭流对象并释放有关的资源 |
2.3代码示例
完成对一个文本文件的读写操作
public class FileWriterTest {
public static void main(String[] args) {
FileWriter fw = null;
try {
fw = new FileWriter("d:/a.txt");
char[] a = {'a', 'b', 'c', 'd', 'e', 'f'};
fw.write(a);
fw.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fw != null)
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public class FileReaderTest {
public static void main(String[] args) {
FileReader fr = null;
try {
fr = new FileReader("d:/a.txt");
char[] ans = new char[5];
int len = fr.read(ans);
System.out.println(len);
for (char ch : ans) {
System.out.println(ch);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fr != null){
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
3. FileOutputStream & FileInputStream – 主要用于对图像数据之类的原始字节流的读写
3.1 FileOutputStream 的常用方法
方法声明 |
功能介绍 |
FileOutputStream(String name) |
根据参数指定的文件名来构造对象 |
FileOutputStream(String name,boolean append) |
以追加的方式根据参数指定的文件名来构造对象 |
void write(int b) |
将指定字节写入此文件输出流 |
void write(byte[] b, int off, int len) |
将指定字节数组中从偏移量off开始的len个字节写入此文件输出流 |
void write(byte[] b) |
将 b.length 个字节从指定字节数组写入此文件输出流中 |
void flush() |
刷新此输出流并强制写出任何缓冲的输出字节 |
void close() |
关闭流对象并释放有关的资源 |
3.2 FileInputStream 的常用方法
方法声明 |
功能介绍 |
FileInputStream(String name) |
根据参数指定的文件路径名来构造对象 |
int read() |
从输入流中读取单个字节的数据并返回,返回-1表示读取到末尾 |
int read(byte[] b, int off, int len) |
从此输入流中将最多len个字节的数据读入字节数组中,返回读取到的字节个数,返回-1表示读取到末尾 |
int read(byte[] b) |
从此输入流中将最多 b.length 个字节的数据读入字节数组中,返回读取到的字节个数,返回-1表示读取到末尾 |
void close() |
关闭流对象并释放有关的资源 |
int available() |
获取输入流所关联文件的大小 |
3.3 代码示例 — 拷贝一张图片
public class FileByteCopyTest {
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("d:/webserver.png");
fos = new FileOutputStream("d:/newwebserver.png");
byte[] buffer = new byte[1024];
int res = 0;
while((res = fis.read(buffer)) != -1) {
fos.write(buffer, 0, res);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fis != null)
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
if(fos != null)
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
4.缓冲流 之 BufferedOutputStream & BufferedInputStream
4.1 BufferedOutputStream 的常用方法
方法声明 |
功能介绍 |
BufferedOutputStream(OutputStream out) |
根据参数指定的引用来构造对象 |
BufferedOutputStream(OutputStream out, int size) |
根据参数指定的引用和缓冲区大小来构造对象 |
void write(int b) |
写入单个字节 |
void write(byte[] b, int off, int len) |
写入字节数组中的一部分数据 |
void write(byte[] b) |
写入参数指定的整个字节数组 |
void flush() |
刷新流 |
void close() |
关闭流对象并释放有关的资源 |
4.2 BufferedInputStream的常用方法
方法声明 |
功能介绍 |
BufferedInputStream(InputStream in) |
根据参数指定的引用构造对象 |
BufferedInputStream(InputStream in, int size) |
根据参数指定的引用和缓冲区大小构造对象 |
int read() |
读取单个字节 |
int read(byte[] b, int off, int len) |
读取len个字节 |
int read(byte[] b) |
读取b.length个字节 |
void close() |
关闭流对象并释放有关的资源 |
4.3 代码示例 — 拷贝视频
public class BufferedByteCopyTest {
public static void main(String[] args) {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
bis = new BufferedInputStream(new FileInputStream("d:/Test/FateZero01.mp4"));
bos = new BufferedOutputStream(new FileOutputStream("d:/Test/copy01.mp4"));
byte[] buffer = new byte[1024];
int res = 0;
while((res = bis.read(buffer)) != -1){
bos.write(buffer, 0, res);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(bis != null)
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
if(bos != null)
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
4.4BufferedXXXStream 源码解析
以 BufferedOutputStream 为例,我们看下面的源码:
- 源码的含义很简单,BufferedOutputStream在wirte时,会先判断要写的数据长度是否大于自己的缓冲区,**大于缓冲区则直接刷新缓冲区和数据,小于缓冲区则往缓冲区装。**直到装不下了再flush缓冲区。当然,最后的Write还是调用FileOutputStream的write方法来实现,只不过用了缓冲区后减少了IO次数。而正是因为减少了IO次数降低了访问磁盘的次数才使得效率得以提高。
5.缓冲流 之 BufferedWriter& BufferedReader
5.1 BufferedWriter 常用方法
方法声明 |
功能介绍 |
BufferedWriter(Writer out) |
根据参数指定的引用来构造对象 |
BufferedWriter(Writer out, int sz) |
根据参数指定的引用和缓冲区大小来构造对象 |
void write(int c) |
写入单个字符到输出流中 |
void write(char[] cbuf, int off, int len) |
将字符数组cbuf中从下标off开始的len个字符写入输出流中 |
void write(char[] cbuf) |
将字符串数组cbuf中所有内容写入输出流中 |
void write(String s, int off, int len) |
将参数s中下标从off开始的len个字符写入输出流中 |
void write(String str) |
将参数指定的字符串内容写入输出流中 |
void newLine() |
用于写入行分隔符到输出流中 |
void flush() |
刷新流 |
void close() |
关闭流对象并释放有关的资源 |
5.2 BufferedReader 常用方法
方法声明 |
功能介绍 |
BufferedReader(Reader in) |
根据参数指定的引用来构造对象 |
BufferedReader(Reader in, int sz) |
根据参数指定的引用和缓冲区大小来构造对象 |
int read() |
从输入流读取单个字符,读取到末尾则返回-1,否则返回实际读取到的字符内容 |
int read(char[] cbuf, int off, int len) |
从输入流中读取len个字符放入数组cbuf中下标从off开始的位置上,若读取到末尾则返回-1,否则返回实际读取到的字符个数 |
int read(char[] cbuf) |
从输入流中读满整个数组cbuf |
String readLine() |
读取一行字符串并返回,返回null表示读取到末尾 |
void close() |
关闭流对象并释放有关的资源 |
public class BufferedCharCopyTest {
public static void main(String[] args) {
BufferedReader br = null;
BufferedWriter bw = null;
try {
br = new BufferedReader(new FileReader("d:/a.txt"));
bw = new BufferedWriter(new FileWriter("d:/b.txt"));
System.out.println("正在玩命地拷贝...");
String str = null;
while ((str = br.readLine()) != null) {
bw.write(str);
bw.newLine();
}
System.out.println("拷贝文件成功!");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != bw) {
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != br) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
6.PrintStream & PrintWriter
6.1 PrintStream 常用方法
方法声明 |
功能介绍 |
PrintStream(OutputStream out) |
根据参数指定的引用来构造对象 |
void print(String s) |
用于将参数指定的字符串内容打印出来 |
void println(String x) |
用于打印字符串后并终止该行 |
void flush() |
刷新流 |
void close() |
用于关闭输出流并释放有关的资源 |
6.2 PrintWriter 常用方法
方法声明 |
功能介绍 |
PrintWriter(Writer out) |
根据参数指定的引用来构造对象 |
void print(String s) |
将参数指定的字符串内容打印出来 |
void println(String x) |
打印字符串后并终止该行 |
void flush() |
刷新流 |
void close() |
关闭流对象并释放有关的资源 |
public class PrintStreamChatTest {
public static void main(String[] args) {
BufferedReader br = null;
PrintStream ps = null;
try {
br = new BufferedReader(new InputStreamReader(System.in));
ps = new PrintStream(new FileOutputStream("d:/Test/d.txt"));
boolean flag = true;
while(true) {
System.out.print((flag ? "Tom" : "Alice") + " :");
String str = br.readLine();
if(str.equals("bye")) {
System.out.println("聊天结束");
break;
}
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
ps.println("[" + sdf.format(date) + "] " + (flag ? " Tom: " : " Alice:") + str);
flag = !flag;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (ps != null)
try {
ps.close();
} catch (Exception e) {
e.printStackTrace();
}
if (br != null)
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
7. 转换流
7.1 OutputStreamWriter
方法声明 |
功能介绍 |
OutputStreamWriter(OutputStream out) |
根据参数指定的引用来构造对象 |
OutputStreamWriter(OutputStream out, String charsetName) |
根据参数指定的引用和编码构造对象 |
void write(String str) |
将参数指定的字符串写入 |
void flush() |
刷新流 |
void close() |
用于关闭输出流并释放有关的资源 |
7.2 InputStreamReader
方法声明 |
功能介绍 |
InputStreamReader(InputStream in) |
根据参数指定的引用来构造对象 |
InputStreamReader(InputStream in, String charsetName) |
根据参数指定的引用和编码来构造对象 |
int read(char[] cbuf) |
读取字符数据到参数指定的数组 |
void close() |
用于关闭输出流并释放有关的资源 |
8.ObjectOutputStream & ObjectInputStream
8.1 ObjectOutputStream
- 基本概念
java.io.ObjectOutputStream类主要用于将一个对象的所有内容整体写入到输出流中。
只能将支持 java.io.Serializable 接口的对象写入流中。
类通过实现 java.io.Serializable 接口以启用其序列化功能。
所谓序列化主要指将一个对象需要存储的相关信息有效组织成字节序列的转化过程。
- 常用方法:
方法声明 |
功能介绍 |
ObjectOutputStream(OutputStream out) |
根据参数指定的引用来构造对象 |
void writeObject(Object obj) |
用于将参数指定的对象整体写入到输出流中 |
void close() |
用于关闭输出流并释放有关的资源 |
8.2 ObjectInputStream
- 基本概念
java.io.ObjectInputStream类主要用于从输入流中一次性将对象整体读取出来。
所谓反序列化主要指将有效组织的字节序列恢复为一个对象及相关信息的转化过程。
- 常用方法:
方法声明 |
功能介绍 |
ObjectInputStream(InputStream in) |
根据参数指定的引用来构造对象 |
Object readObject() |
主要用于从输入流中读取一个对象并返回 无法通过返回值来判断是否读取到文件的末尾 |
void close() |
用于关闭输入流并释放有关的资源 |
8.3 代码示例
public class User implements Serializable {
private static final long serialVersionUID = 8722581501774275558L;
private String name;
private String password;
private String phoneNum;
public User() {
}
public User(String name, String password, String phoneNum) {
this.name = name;
this.password = password;
this.phoneNum = phoneNum;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPhoneNum() {
return phoneNum;
}
public void setPhoneNum(String phoneNum) {
this.phoneNum = phoneNum;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", password='" + password + '\'' +
", phoneNum='" + phoneNum + '\'' +
'}';
}
}
public class ObjectOutputStreamTest {
public static void main(String[] args) {
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("d:/Test/d.txt"));
User user1 = new User("Tom", "123456", "15638948561");
oos.writeObject(user1);
System.out.println("写入对象成功!");
} catch (IOException e) {
e.printStackTrace();
} finally {
if(oos != null)
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public class ObjectInputSteamTest {
public static void main(String[] args) {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("d:/Test/d.txt"));
Object o = ois.readObject();
System.out.println(o);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if(ois != null)
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
8.4 其他知识点
- 序列化版本号
序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,
JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如
果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常
(InvalidCastException)。
- transient关键字
transient是Java语言的关键字,用来表示一个域不是该对象串行化的一部分。当一个对象被串行
化的时候,transient型变量的值不包括在串行化的表示中,然而非transient型的变量是被包括进
去的。
- 经验的分享
当希望将多个对象写入文件时,通常建议将多个对象放入一个集合中,然后将集合这个整体看做一
个对象写入输出流中,此时只需要调用一次readObject方法就可以将整个集合的数据读取出来,
从而避免了通过返回值进行是否达到文件末尾的判断。
十二、多线程
1. 线程的创建(三种方式)
-
自定义类继承Thread类并重写run方法,然后创建该类的对象调用start方法。
-
自定义类实现Runnable接口并重写run方法,创建该类的对象作为实参来构造Thread类型的对
象,然后使用Thread类型的对象调用start方法。
-
实现java.util.concurrent.Callable接口 — 重写 V call() 方法,这个方法的返回值是泛型(代码示例参考线程池部分)
-
相关方法
方法声明 |
功能介绍 |
Thread() |
使用无参的方式构造对象 |
Thread(String name) |
根据参数指定的名称来构造对象 |
Thread(Runnable target) |
根据参数指定的引用来构造对象,其中Runnable是个接口类型 |
Thread(Runnable target, String name) |
根据参数指定引用和名称来构造对象 |
void run() |
若使用 Runnable 引用构造了线程对象,调用该方法时最终调用接口中的版本 若没有使用 Runnable 引用构造线程对象,调用该方法时则啥也不做 |
void start() |
用于启动线程,Java虚拟机会自动调用该线程的run方法 |
方法声明 |
功能介绍 |
long getId() |
获取调用对象所表示线程的编号 |
String getName() |
获取调用对象所表示线程的名称 |
void setName(String name) |
设置/修改线程的名称为参数指定的数值 |
static Thread currentThread() |
获取当前正在执行线程的引用 |
2. 线程同步机制(※)
- 简单说就是OS中提及的线程同步问题,生产者-消费者问题、哲学家进餐问题……
- 上锁,可以用synchronized关键字 或者 Lock锁
2.1 synchronized关键字
- 多个需要同步的线程在访问同步块时,看到的应该是同一个锁对象引用
- 使用同步代码块的方式实现部分代码的锁定,格式如下:
synchronized(类类型的引用) {
编写所有需要锁定的代码;
}
- 使用同步方法的方式实现所有代码的锁定。
直接使用synchronized关键字来修饰整个方法即可,该方式等价于:
synchronized(this) { 整个方法体的代码 }
2.2 静态方法的锁定
- 当我们对一个静态方法加锁,如:
public synchronized static void xxx(){….}
那么该方法锁的对象是类对象。
- 每个类都有唯一的一个类对象。获取类对象的方式:类名.class。
- 静态方法与非静态方法同时使用了synchronized后它们之间是非互斥关系的。
- 原因在于:静态方法锁的是类对象而非静态方法锁的是当前方法所属对象
2.4 线程安全类和不安全类
- StringBuffer类是线程安全的类,但StringBuilder类不是线程安全的类。
- Vector类和 Hashtable类是线程安全的类,但ArrayList类和HashMap类不是线程安全的类。
- Collections.synchronizedList() 和 Collections.synchronizedMap()等方法实现安全。
2.5 使用Lock(锁)实现线程同步
- java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。
- 该接口的主要实现类是ReentrantLock类,该类拥有与synchronized相同的并发性,在以后的线程安全控制中,经常使用ReentrantLock类显式加锁和释放锁
- 常用方法
方法声明 |
功能介绍 |
ReentrantLock() |
使用无参方式构造对象 |
void lock() |
获取锁 |
void unlock() |
释放锁 |
2.6 与synchronized方式的比较
- Lock是显式锁,需要手动实现开启和关闭操作,而synchronized是隐式锁,执行锁定代码后自动释放。
- Lock只有同步代码块方式的锁,而synchronized有同步代码块方式和同步方法两种锁。
- 使用Lock锁方式时,Java虚拟机将花费较少的时间来调度线程,因此性能更好。
public class AccountRunnableTest implements Runnable {
private int balance;
private Demo dm = new Demo();
private ReentrantLock lock = new ReentrantLock();
public AccountRunnableTest() {
}
public AccountRunnableTest(int balance) {
this.balance = balance;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
@Override
public void run() {
lock.lock();
System.out.println("线程" + Thread.currentThread().getName() + "已启动...");
int temp = getBalance();
if (temp >= 200) {
System.out.println("正在出钞,请稍后...");
temp -= 200;
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("请取走您的钞票!");
} else {
System.out.println("余额不足,请核对您的账户余额!");
}
setBalance(temp);
lock.unlock();
}
public static void main(String[] args) {
AccountRunnableTest account = new AccountRunnableTest(1000);
Thread t1 = new Thread(account);
Thread t2 = new Thread(account);
t1.start();
t2.start();
System.out.println("主线程开始等待...");
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("最终的账户余额为:" + account.getBalance());
}
}
class Demo{}
3.Object类常用的方法
方法声明 |
功能介绍 |
void wait() |
用于使得线程进入等待状态,直到其它线程调用notify()或notifyAll()方法 |
void wait(long timeout) |
用于进入等待状态,直到其它线程调用方法或参数指定的毫秒数已经过去为止 |
void notify() |
用于唤醒等待的单个线程 |
void notifyAll() |
用于唤醒等待的所有线程 |
4. 线程池
- 从Java5开始提供了线程池的相关类和接口:java.util.concurrent.Executors类和java.util.concurrent.ExecutorService接口。
4.1 Executors类
- Executors是个工具类和线程池的工厂类,可以创建并返回不同类型的线程池,常用方法如下:
方法声明 |
功能介绍 |
static ExecutorService newCachedThreadPool() |
创建一个可根据需要创建新线程的线程池 |
static ExecutorService newFixedThreadPool(int nThreads) |
创建一个可重用固定线程数的线程池 |
static ExecutorService newSingleThreadExecutor() |
创建一个只有一个线程的线程池 |
4.2 ExecutorService接口
- ExecutorService接口是真正的线程池接口,主要实现类是ThreadPoolExecutor,常用方法如下:
方法声明 |
功能介绍 |
void execute(Runnable command) |
执行任务和命令,通常用于执行Runnable |
Future submit(Callable task) |
执行任务和命令,通常用于执行Callable |
void shutdown() |
启动有序关闭 |
4.3 线程池的使用 — 这里顺便说一下线程的第三种创建方法
- FutureTask类
java.util.concurrent.FutureTask类用于描述可取消的异步计算,该类提供了Future接口的基本实
现,包括启动和取消计算、查询计算是否完成以及检索计算结果的方法,也可以用于获取方法调用后的返回结果
- FutureTask类的常用方法如下:
方法声明 |
功能介绍 |
FutureTask(Callable callable) |
根据参数指定的引用来创建一个未来任务 |
V get() |
获取call方法计算的结果 |
public class ThreadCallableTest implements Callable {
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 10000; i++) {
sum +=i;
}
System.out.println("计算的累加和是:" + sum);
return sum;
}
public static void main(String[] args) {
ThreadCallableTest tct = new ThreadCallableTest();
FutureTask ft = new FutureTask(tct);
Thread t1 = new Thread(ft);
t1.start();
Object obj = null;
try {
obj = ft.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("线程处理方法的返回值是:" + obj);
}
}
public class ThreadPoolTest {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.submit(new ThreadCallableTest());
executorService.shutdown();
}
}
大致的一个思路就是 Executors类创建线程池 --- ExecutorService的实现类去运行
十三、网络编程
个人的理解就是 提供端口号与主机号,用于建立连接,之后就是一个大型的IO,具体的在JavaWeb中说,这里简单的说一点基于TCP的基础知识
1. 编程模型
- 服务器:
(1)创建ServerSocket类型的对象并提供端口号;
(2)等待客户端的连接请求,调用accept()方法;
(3)使用输入输出流进行通信;
(4)关闭Socket;
- 客户端:
(1)创建Socket类型的对象并提供服务器的IP地址和端口号;
(2)使用输入输出流进行通信;
(3)关闭Socket;
2. ServerSocket & Socket
方法声明 |
功能介绍 |
ServerSocket(int port) |
根据参数指定的端口号来构造对象 |
Socket accept() |
侦听并接收到此套接字的连接请求 |
void close() |
用于关闭套接字 |
方法声明 |
功能介绍 |
Socket(String host, int port) |
根据指定主机名和端口来构造对象 |
InputStream getInputStream() |
用于获取当前套接字的输入流 |
OutputStream getOutputStream() |
用于获取当前套接字的输出流 |
void close() |
用于关闭套接字 |
public class ServerThread extends Thread {
private Socket s;
public ServerThread(Socket s) {
this.s = s;
}
@Override
public void run() {
BufferedReader br = null;
PrintStream ps = null;
try {
br = new BufferedReader(new InputStreamReader(s.getInputStream()));
ps = new PrintStream(s.getOutputStream());
while(true) {
String s1 = br.readLine();
InetAddress inetAddress = s.getInetAddress();
System.out.println("客户端" + inetAddress + "发来的字符串内容是:" + s1);
if ("bye".equalsIgnoreCase(s1)) {
System.out.println("客户端" + inetAddress + "已下线!");
break;
}
ps.println("I received!");
System.out.println("服务器发送数据成功!");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != ps) {
ps.close();
}
if (null != br) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != s) {
try {
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
public class ServerStringTest {
public static void main(String[] args) {
ServerSocket ss = null;
Socket s = null;
try {
ss = new ServerSocket(8888);
while(true) {
System.out.println("等待客户端的连接请求...");
s = ss.accept();
System.out.println("客户端" + s.getInetAddress() + "连接成功!");
new ServerThread(s).start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != ss) {
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
public class ClientStringTest {
public static void main(String[] args) {
Socket s = null;
PrintStream ps = null;
Scanner sc = null;
BufferedReader br = null;
try {
s = new Socket("127.0.0.1", 8888);
System.out.println("连接服务器成功!");
sc = new Scanner(System.in);
ps = new PrintStream(s.getOutputStream());
br = new BufferedReader(new InputStreamReader(s.getInputStream()));
while(true) {
System.out.println("请输入要发送的数据内容:");
String str1 = sc.next();
ps.println(str1);
System.out.println("客户端发送数据内容成功!");
if ("bye".equalsIgnoreCase(str1)) {
System.out.println("聊天结束!");
break;
}
String str2 = br.readLine();
System.out.println("服务器回发的消息是:" + str2);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != br) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != ps) {
ps.close();
}
if (null != sc) {
sc.close();
}
if (null != s) {
try {
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
十四、反射
1. 基本概念
- 通常情况下编写代码都是固定的,无论运行多少次执行的结果也是固定的,在某些特殊场合中编写代码时不确定要创建什么类型的对象,也不确定要调用什么样的方法,这些都希望通过运行时传递的参数来决定,该机制叫做动态编程技术,也就是反射机制。
- 通俗来说,反射机制就是用于动态创建对象并且动态调用方法的机制。
- 目前主流的框架底层都是采用反射机制实现的。
2. Class类
- 获取Class对象的方式:
1.使用数据类型.class的方式可以获取对应类型的Class对象。 ※
Class c1 = String.class;
System.out.println("c1 = " + c1);
c1 = int.class;
System.out.println("c1 = " + c1);
c1 = void.class;
System.out.println("c1 = " + c1);
2.使用引用/对象.getClass()的方式可以获取对应类型的Class对象。
3.使用包装类.TYPE的方式可以获取对应基本数据类型的Class对象。
4.使用Class.forName()的方式来获取参数指定类型的Class对象。 ※
c1 = Class.forName("java.lang.String");
System.out.println("c1 = " + c1);
c1 = Class.forName("java.util.Date");
System.out.println("c1 = " + c1);
5.使用类加载器ClassLoader的方式获取指定类型的Class对象。
3. Constructor类
- 用于描述获取到的构造方法信息
- 我们通过调用Class类的方法,获取Constructor类
方法声明 |
功能介绍 |
Constructor getConstructor(Class>… parameterTypes) |
用于获取此Class对象所表示类型中参数指定的公共构造方法 |
Constructor>[] getConstructors() |
用于获取此Class对象所表示类型中所有的公共构造方法 |
方法声明 |
功能介绍 |
T newInstance(Object… initargs) |
使用此Constructor对象描述的构造方法来构造Class对象代表类型的新实例 |
int getModifiers() |
获取方法的访问修饰符 |
String getName() |
获取方法的名称 |
Class>[] getParameterTypes() |
获取方法所有参数的类型 |
- 现在有一个类Person,如下所示,我们可以使用反射机制完成类的创建
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
public class PersonConstructorTest {
public static void main(String[] args) throws Exception {
Person p1 = new Person();
System.out.println("无参方式创建的对象是:" + p1);
System.out.println("---------------------------------------------------");
System.out.println("请输入要创建对象的类型:");
Scanner sc = new Scanner(System.in);
String str1 = sc.next();
Class c1 = Class.forName(str1);
Constructor constructor = c1.getConstructor();
System.out.println("无参方式创建的对象是:" + constructor.newInstance());
sc.close();
System.out.println("---------------------------------------------------");
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("d:/a.txt")));
String str1 = br.readLine();
Class c1 = Class.forName(str1);
Constructor constructor = c1.getConstructor();
System.out.println("无参方式创建的对象是:" + constructor.newInstance());
br.close();
}
}
public class PersonConstructorTest {
public static void main(String[] args) throws Exception {
Person p2 = new Person("zhangfei", 30);
System.out.println("有参方式构造的对象是:" + p2);
System.out.println("---------------------------------------------------");
Constructor constructor1 = c1.getConstructor(String.class, int.class);
System.out.println("有参方式构造的对象是:" + constructor1.newInstance("zhangfei", 30));
}
}
4. Field类
- 主要用于描述获取到的单个成员变量信息
- 我们通过调用Class类的方法,获取Field类。意思就是获取到的Field类对象,就代表着一个变量,之后再调用 set() 或者 get() 方法都是对这个变量进行操作了。
方法声明 |
功能介绍 |
Field getDeclaredField(String name) |
用于获取此Class对象所表示类中参数指定的单个成员变量信息 |
Field[] getDeclaredFields() |
用于获取此Class对象所表示类中所有成员变量信息 |
方法声明 |
功能介绍 |
Object get(Object obj) |
获取参数对象obj中此Field对象所表示成员变量的数值 |
void set(Object obj, Object value) |
将参数对象obj中此Field对象表示成员变量的数值修改为参数 value的数值 |
void setAccessible(boolean flag) |
当实参传递true时,则反射对象在使用时应该取消 Java 语言访问检查 |
int getModifiers() |
获取成员变量的访问修饰符 |
Class> getType() |
获取成员变量的数据类型 |
String getName() |
获取成员变量的名称 |
public class PersonFieldTest {
public static void main(String[] args) throws Exception {
Person p1 = new Person("zhangfei", 30);
System.out.println("-------------------------------------------------------");
Class c1 = Class.forName("com.lagou.task20.Person");
Constructor constructor = c1.getConstructor(String.class, int.class);
Object object = constructor.newInstance("zhangfei", 30);
Field field = c1.getDeclaredField("name");
field.setAccessible(true);
System.out.println("获取到的成员变量数值为:" + field.get(object));
System.out.println("-------------------------------------------------------");
System.out.println("-------------------------------------------------------");
field.set(object, "guanyu");
System.out.println("修改后成员变量的数值为:" + field.get(object));
System.out.println("-------------------------------------------------------");
Field[] declaredFields = c1.getDeclaredFields();
for (Field ft : declaredFields) {
System.out.println("获取到的访问修饰符为:" + ft.getModifiers());
System.out.println("获取到的数据类型为:" + ft.getType());
System.out.println("获取到的成员变量名称是:" + ft.getName());
System.out.println("---------------------------------------------");
}
}
}
5.Method类
- 用于描述获取到的单个成员方法信息
- 我们通过调用Class类的方法,获取Method类。
方法声明 |
功能介绍 |
Method getMethod(String name, Class>… parameterTypes) |
用于获取该Class对象表示类中名字为name参数为parameterTypes的指定公共成员方法 |
Method[] getMethods() |
用于获取该Class对象表示类中所有公共成员方法 |
方法声明 |
功能介绍 |
Object invoke(Object obj,Object… args) |
使用对象obj来调用此Method对象所表示的成员方法,实参传递args |
int getModifiers() |
获取方法的访问修饰符 |
Class> getReturnType() |
获取方法的返回值类型 |
String getName() |
获取方法的名称 |
Class>[] getParameterTypes() |
获取方法所有参数的类型 |
Class>[] getExceptionTypes() |
获取方法的异常信息 |
public class PersonMethodTest {
public static void main(String[] args) throws Exception {
Person p1 = new Person("zhangfei", 30);
System.out.println("调用方法的返回值是:" + p1.getName());
System.out.println("------------------------------------------------------");
Class c1 = Class.forName("com.lagou.task20.Person");
Constructor constructor = c1.getConstructor(String.class, int.class);
Object object = constructor.newInstance("zhangfei", 30);
Method method = c1.getMethod("getName");
System.out.println("调用方法的返回值是:" + method.invoke(object));
System.out.println("------------------------------------------------------");
Method[] methods = c1.getMethods();
for (Method mt : methods) {
System.out.println("成员方法的修饰符是:" + mt.getModifiers());
System.out.println("成员方法的返回值类型是:" + mt.getReturnType());
System.out.println("成员方法的名称是:" + mt.getName());
System.out.println("成员方法形参列表的类型是:");
Class<?>[] parameterTypes = mt.getParameterTypes();
for (Class ct : parameterTypes) {
System.out.print(ct + " ");
}
System.out.println();
System.out.println("成员方法的异常类型列表是:");
Class<?>[] exceptionTypes = mt.getExceptionTypes();
for (Class ct: exceptionTypes) {
System.out.print(ct + " ");
}
System.out.println();
System.out.println("---------------------------------------------------");
}
}
}
总结
基本都是工具类,写这个文章的目的是用的时候看一眼方法,方便一些,顺便复习一下