笔记
- 一·Java程序基础
-
- 代码练习合集
-
- 1.Hello,world
- 2.BMI
- 3.Grade
- 4.InputNum
- 5.Gamecaiquan
- 6.Main
- 7.Reserve_arrays
- 8.Sum_dowhile
- 9.Sum
- 10.Sumnum
- 11.Unicode
- 12.PrimaryStudent
- 二·面向对象编程
-
- 代码练习合集
-
- 1.Tax
- 2.Tax_abstract
- 3.Tax_interface
- 4.Static
- 5.Insert(StringBuilder)
- 6.Select(StringJoiner)
- 三.异常处理
-
- 代码练习合集
-
- 1.Exception_catch(捕获异常)
- 2.Exception_throw(抛出异常)
- 3.Exception_custom(自定义异常)
- 4.JDK Logging
- 5.Commons_logging(配合commons-logging-1.2.jar命令行执行)`
- 4.反射
-
- 代码练习合集
-
- 1.Reflect_field
- 2.Reflect_method
- 5.注解
-
- 代码练习合集
-
- 1. Annotation_range_check
- 6.泛型
-
- 代码练习合集
-
- 1.List
- 2.复写equals方法
- 3.Map
- 4.Set
- 5.Priority_queue
- 8.IO
-
- 9.时间与日期
-
- 10.单元测试
-
- 代码练习合集
-
- 1.Junit测试
- 2.Fixture
- 3.异常测试
- 11.正则表达式
-
- 12.加密与安全
-
- 代码练习合集
-
- 1.:使用BouncyCastle提供的RipeMD160
- 13.多线程
- 14.Maven基础
- 15.JDBC编程
-
一·Java程序基础
1.一个Java源码只能定义一个public类型的class,并且class名称和文件名要完全一致;
2.使用javac可以将.java源码编译成.class字节码;
3.使用java可以运行一个已编译的Java程序,参数是类名。
4.类用大写英文字母开头
5.方法用小写英文字母开头
6.
7.快速格式化代码 Ctrl+Shift+f
8.final(常量修饰符) int A(常量名大写);
9.变量类型太长,可使用var定义变量,编译器自动补全
10.超出范围的强制转型可能会得到错误的结果
11.\u#### 表示一个Unicode编码的字符
12.可以使用+连接任意字符串和其他数据类型
13.注意要区分空值null和空字符串"",空字符串是一个有效的字符串对象,它不等于null
【当String = ""+A+B,A,B均不是String类型,需要在前面加个空字符串】
14.例数组使用: int[] ns = new int[5](数组变量初始化必须使用new int[5]表示创建一个可容纳5个int元素的数组。)
也可以在定义数组时直接指定初始化的元素---int[] ns = new int[] {
68, 79, 91, 85, 62 };
还可以进一步简写为:int[] ns = {
68, 79, 91, 85, 62 };
15.要判断引用类型的变量内容是否相等,必须使用equals()方法:if (s1.equals(s2))
16.JAVA14 switch新语法: switch(A):
case "A" -> System.out.println("XXXX");
新语法使用->,如果有多条语句,需要用{
}括起来。不要写break语句,因为新语法只会执行匹配的语句,没有穿透效应。
还可以直接返回值:case "apple" -> 1; 或用yield返回一个值作为switch语句的返回值:int code = 1;
yield code;
17.for each循环能够遍历所有“可迭代”的数据类型-------------------------->for (int n : ns)
在for (int n : ns)循环中,变量n直接拿到ns数组的元素,而不是索引 System.out.println(n);
18.打印二维数组ns[][]------>for (int[] arr : ns) {
for (int n : arr) {
System.out.print(n);
System.out.print(', ');
}
System.out.println();
}
19.命令行参数类型是String[]数组;命令行参数由JVM接收用户输入并传给main方法;如何解析命令行参数需要由程序自己实现。
代码练习合集
1.Hello,world
public class Hello {
public static void main(String[] args) {
System.out.println("Helo,world!");
}
}
2.BMI
import java.util.Scanner;
public class BMI {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入体重(单位kg):");
float weight = scanner.nextFloat();
System.out.print("请输入身高(单位m):");
float high = scanner.nextFloat();
double yourBMI = weight/Math.pow(high,2);
if((float)yourBMI < 18.5) {
System.out.printf("BMI为:%1f,过轻!",yourBMI);
}
else if(yourBMI <= 25) {
System.out.printf("BMI为:%.1f,正常!",yourBMI);
}
else if(yourBMI <= 28) {
System.out.printf("BMI为:%.1f,过重!",yourBMI);
}
else if(yourBMI <= 32) {
System.out.printf("BMI为:%.1f,肥胖!",yourBMI);
}
else {
System.out.printf("BMI为:%.1f,非常肥胖!",yourBMI);
}
}
}
3.Grade
import java.util.Scanner;
public class Grade {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入去年的成绩:");
int lastgrade = scanner.nextInt();
System.out.print("请输入今年的成绩:");
int nowgrade = scanner.nextInt();
float up = (float)(nowgrade-lastgrade)/lastgrade*100;
System.out.printf(up >= 0 ? "成绩提高%.2f%%\n": "成绩下降%.2f%%\n",Math.abs(up));
}
}
4.InputNum
public class InputNum {
public static void main(String[] args) {
int x = 100;
System.out.println(x);
}
}
5.Gamecaiquan
import java.util.Scanner;
import java.util.Random;
public class Gamecaiquan {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
Random rand = new Random();
System.out.print("选择数字--》石头(1)or剪刀(2)or布(3):");
int own = scanner.nextInt();
int sys = rand.nextInt(3) + 1;
switch (own) {
case 1 -> {
if (sys == 1) {
System.out.println("平手!");
} else if (sys == 2) {
System.out.println("获胜!");
} else {
System.out.println("失败!");
}
}
case 2 -> {
if (sys == 2) {
System.out.println("平手!");
} else if (sys == 3) {
System.out.println("获胜!");
} else {
System.out.println("失败!");
}
}
case 3 -> {
if (sys == 3) {
System.out.println("平手!");
} else if (sys == 1) {
System.out.println("获胜!");
} else {
System.out.println("失败!");
}
}
default -> System.out.println("输入不合规范!");
}
}
}
6.Main
public class Main {
public static void main(String[] args) {
double a = 1.0,b = 3.0,c = -4.0;
double r1,r2;
System.out.println("r1 = " + (-b+Math.sqrt(b*b-4*a*c))/(2*a));
System.out.println("r2 = " + (-b-Math.sqrt(b*b-4*a*c))/(2*a));
}
}
7.Reserve_arrays
public class Reserve_arrays {
public static void main(String[] args) {
int[] ns = {
1,4,9,16,25};
for(int i = ns.length - 1;i >= 0;i-- ) {
System.out.println(ns[i]);
}
}
}
8.Sum_dowhile
public class Sum_dowhile {
public static void main(String[] args) {
int sum = 0,m = 20,n = 100;
do{
sum = sum + m;
m++;
}while(m <= n);
System.out.println(sum);
}
}
9.Sum
public class Sum {
public static void main(String[] args) {
int sum = 0,m = 20,n = 100;
while(m <= n) {
sum = sum + m;
m++;
}
System.out.println(sum);
}
}
10.Sumnum
public class Sumnum {
public static void main(String[] args) {
int x = 100;
int i, sum = 0;
for (i = 1; i <= x; i++) {
sum = sum + i;
}
System.out.println(sum);
}
}
11.Unicode
public class Unicode {
public static void main(String[] args) {
int a = 72,b = 105,c = 65281;
char a1 = '\u0048';
char b1 = '\u0069';
char c1 = '\uff01';
String s ="" + a1 + b1 + c1;
System.out.println(s);
}
}
12.PrimaryStudent
public class PrimaryStudent {
public static void main(String[] args) {
int age = 7;
boolean isPrimaryStudent = (age >= 6) && (age <= 12);
System.out.println(isPrimaryStudent ? "Yes" : "No");
}
}
二·面向对象编程
1.定义private方法的理由是内部方法是可以调用private方法,不允许外部调用;
在方法内部,可以使用一个隐含的变量this,它始终指向当前实例。
2.调用构造方法,必须用new操作符;
可以定义多个构造方法,在通过new操作符调用的时候,编译器通过构造方法的参数数量、位置和类型自动区分;
一个构造方法可以调用其他构造方法,这样做的目的是便于代码复用。调用其他构造方法的语法是this(…)。
3.方法重载是指多个方法的方法名相同,但各自的参数不同;
重载方法应该完成类似的功能,参考String的indexOf();
重载方法返回值类型应该相同。
4.为了让子类可以访问父类的字段,我们需要把private改为protected;
protected关键字可以把字段和方法的访问权限控制在继承树内部,一个protected字段和方法可以被其子类,以及子类的子类所访问。
5.从Java 14开始,判断instanceof后,可以直接转型为指定变量,避免再次强制转型
public class Main {
public static void main(String[] args) {
Object obj = "hello";
if (obj instanceof String s) {
System.out.println(s.toUpperCase());
}
}
}
6.Java只允许单继承,所有类最终的根类是Object;
子类的构造方法可以通过super()调用父类的构造方法;
可以安全地向上转型为更抽象的类型;
可以强制向下转型,最好借助instanceof判断;
子类和父类的关系是is,has关系不能用继承。
7.final 可以实现
不允许修改方法
不允许修改字段
父类加final则不允许继承
8.通过abstract定义的方法是抽象方法,它只有定义,没有实现。抽象方法定义了子类必须实现的接口规范;
定义了抽象方法的class必须被定义为抽象类,从抽象类继承的子类必须实现抽象方法;
如果不实现抽象方法,则该子类仍是一个抽象类;
面向抽象编程使得调用者只关心抽象方法的定义,不关心子类的具体实现。
9.Java的接口(interface)定义了纯抽象规范,一个类可以实现多个接口;
接口也是数据类型,适用于向上转型和向下转型;
接口的所有方法都是抽象方法,接口不能定义实例字段;
接口可以定义default方法(JDK>=1.8)。
10.包没有父子关系。java.util和java.util.zip是不同的包,两者没有任何继承关系。
11. 静态字段属于所有实例“共享”的字段,实际上是属于class的字段;
调用静态方法不需要实例,无法访问this,但可以访问静态字段和其他静态方法;
静态方法常用于工具类和辅助方法。
12.Java内建的package机制是为了避免class命名冲突;
JDK的核心类使用java.lang包,编译器会自动导入;
JDK的其它常用类定义在java.util.*,java.math.*,java.text.*,……;
包名推荐使用倒置的域名,例如org.apache。
13.Java内建的访问权限包括public、protected、private和package权限;
Java在方法内部定义的变量是局部变量,局部变量的作用域从变量声明开始,到一个块结束;
final修饰符不是访问权限,它可以修饰class、field和method;
一个.java文件只能包含一个public类,但可以包含多个非public类
14.JVM通过环境变量classpath决定搜索class的路径和顺序;
不推荐设置系统环境变量classpath,始终建议通过-cp命令传入;
jar包相当于目录,可以包含很多.class文件,方便下载和使用;
MANIFEST.MF文件可以提供jar包的信息,如Main-Class,这样可以直接运行jar包。
15.Java 9引入的模块目的是为了管理依赖;
使用模块可以按需打包JRE;
使用模块对类的访问权限有了进一步限制。
16.Java字符串String是不可变对象;
字符串操作不改变原字符串内容,而是返回新字符串;
常用的字符串操作:提取子串、查找、替换、大小写转换等;
Java使用Unicode编码表示String和char;
转换编码就是将String和byte[]转换,需要指定编码;
转换为byte[]时,始终优先考虑UTF-8编码。
17.StringBuilder是可变对象,用来高效拼接字符串;
StringBuilder可以支持链式操作,实现链式操作的关键是返回实例本身;
StringBuffer是StringBuilder的线程安全版本,现在很少使用。
18.Java核心库提供的包装类型可以把基本类型包装为class;
自动装箱和自动拆箱都是在编译期完成的(JDK>=1.5);
装箱和拆箱会影响执行效率,且拆箱时可能发生NullPointerException;
包装类型的比较必须使用equals();
整数和浮点数的包装类型都继承自Number;
包装类型提供了大量实用方法。
19.JavaBean是一种符合命名规范的class,它通过getter和setter来定义属性;
属性是一种通用的叫法,并非Java语法规定;
可以利用IDE快速生成getter和setter;
使用Introspector.getBeanInfo()可以获取属性列表
20.Java使用enum定义枚举类型,它被编译器编译为final class Xxx extends Enum {
… };
通过name()获取常量定义的字符串,注意不要使用toString();
通过ordinal()返回常量定义的顺序(无实质意义);
可以为enum编写构造方法、字段和方法
enum的构造方法要声明为private,字段强烈建议声明为final;
enum适合用在switch语句中。
21.从Java 14开始,提供新的record关键字,可以非常方便地定义Data Class:
使用record定义的是不变类;
可以编写Compact Constructor对参数进行验证;
可以定义静态方法。
22.BigInteger用于表示任意大小的整数;
BigInteger是不变类,并且继承自Number;
将BigInteger转换成基本类型时可使用longValueExact()等方法保证结果准确
23.BigDecimal用于表示精确的小数,常用于财务计算;
比较BigDecimal的值是否相等,必须使用compareTo()而不能使用equals()。
24.Java提供的常用工具类有:
Math:数学计算
Random:生成伪随机数
SecureRandom:生成安全的随机数
代码练习合集
1.Tax
public class Tax {
public static void main(String[] args) {
Income[] incomes = new Income[] {
new Income(7000), new Gaofei(12000) };
System.out.println(totalTax(incomes));
}
public static double totalTax(Income... incomes) {
double total = 0;
for (Income income : incomes) {
total = total + income.getTax();
}
return total;
}
}
class Income {
protected double income;
public Income(double income) {
this.income = income;
}
public double getTax() {
return (income - 5000) * 0.3;
}
}
class Gaofei extends Income {
public Gaofei(double income) {
super(income);
}
@Override
public double getTax() {
if (income < 4000)
return (income - 800) * 0.7 * 0.2;
return income * 0.8 * 0.2 * 0.7;
}
}
2.Tax_abstract
public class Tax_abstract {
public static void main(String[] args) {
Income1[] incomes = new Income1[] {
new Gongzi(3000), new Gaofei1(12000) };
System.out.println(totalTax(incomes));
}
public static double totalTax(Income1... incomes) {
double total = 0;
for (Income1 income : incomes) {
total = total + income.getTax();
}
return total;
}
}
abstract class Income1{
protected double income;
public Income1(double income) {
this.income = income;
}
public abstract double getTax();
}
class Gongzi extends Income1{
public Gongzi(double income) {
super(income);
}
@Override
public double getTax() {
if(income < 5000)
return 0;
return (income - 5000)*0.3;
}
}
class Gaofei1 extends Income1{
public Gaofei1(double income) {
super(income);
}
@Override
public double getTax() {
if (income < 4000)
return (income - 800) * 0.7 * 0.2;
return income * 0.8 * 0.2 * 0.7;
}
}
3.Tax_interface
public class Tax_interface {
public static void main(String[] args) {
Income2[] incomes = new Income2[] {
new Gongzi2(6000), new Gaofei2(3333) };
System.out.println(totalTax(incomes));
}
public static double totalTax(Income2... incomes) {
double total = 0;
for (Income2 income : incomes) {
total = total + income.getTax();
}
return total;
}
}
interface Income2{
double getTax();
}
class Gongzi2 implements Income2{
protected double income;
public Gongzi2(double income) {
this.income = income;
}
public double getTax() {
if(income < 5000)
return 0;
return (income - 5000)*0.3;
}
}
class Gaofei2 implements Income2{
protected double income;
public Gaofei2(double income) {
this.income = income;
}
public double getTax() {
if (income < 4000)
return (income - 800) * 0.7 * 0.2;
return income * 0.8 * 0.2 * 0.7;
}
}
4.Static
public class Static {
public static void main(String[] args) {
person p1 = new person("A");
System.out.println(person.getCount());
person p2 = new person("B");
System.out.println(person.getCount());
person p3 = new person("C");
System.out.println(person.getCount());
}
}
class person{
public static int count = 0;
String name;
public person(String name) {
this.name = name;
count += 1;
}
public static int getCount() {
return count;
}
}
5.Insert(StringBuilder)
public class Insert {
public static void main(String[] args) {
String[] fields = {
"name", "position", "salary" };
String table = "employee";
String insert = buildInsertSql(table, fields);
System.out.println(insert);
String s = "INSERT INTO employee (name, position, salary) VALUES (?, ?, ?)";
System.out.println(s.equals(insert) ? "测试成功" : "测试失败");
}
static String buildInsertSql(String table, String[] fields) {
StringBuilder sb = new StringBuilder(1024);
sb.append("INSERT INTO " + table + " (");
for (int i = 0; i < 2; i++) {
sb.append(fields[i] + ", ");
}
sb.append(fields[2] + ") VALUES (?, ?, ?)");
return sb.toString();
}}
6.Select(StringJoiner)
import java.util.StringJoiner;
public class Select {
public static void main(String[] args) {
String[] fields = {
"name", "position", "salary"};
String table = "employee";
String select = buildSelectSql(table,fields);
System.out.println(select);
System.out.println("SELECT name, position, salary FROM employee".equalsIgnoreCase(select) ? "测试成功" : "测试失败");
}
static String buildSelectSql(String table,String[] fields) {
var sj = new StringJoiner(", ","SELECT "," FROM " + table);
for(String field : fields) {
sj.add(field);
}
return sj.toString();
}
}
三.异常处理
1.Java使用异常来表示错误,并通过try ... catch捕获异常;
Java的异常是class,并且从Throwable继承;
Error是无需捕获的严重错误,Exception是应该捕获的可处理的错误;
RuntimeException无需强制捕获,非RuntimeException(Checked Exception)需强制捕获,或者用throws声明;
不推荐捕获了异常但不进行任何处理。
2.使用try ... catch ... finally时:
多个catch语句的匹配顺序非常重要,子类必须放在前面;
finally语句保证了有无异常都会执行,它是可选的;
一个catch语句也可以匹配多个非继承关系的异常。
3.调用printStackTrace()可以打印异常的传播栈,对于调试非常有用:
捕获异常并再次抛出新的异常时,应该持有原始异常信息;
通常不要在finally中抛出异常。如果在finally中抛出异常,应该原始异常加入到原有异常中。调用方可通过Throwable.getSuppressed()获取所有添加的Suppressed Exception。
4.抛出异常时,尽量复用JDK已定义的异常类型;
自定义异常体系时,推荐从RuntimeException派生“根异常”,再派生出业务异常;
自定义异常时,应该提供多种构造方法。
5.NullPointerException是Java代码常见的逻辑错误,应当早暴露,早修复;
可以启用Java 14的增强异常信息来查看NullPointerException的详细错误信息。
6.断言是一种调试方式,断言失败会抛出AssertionError,只能在开发和测试阶段启用断言;
对可恢复的错误不能使用断言,而应该抛出异常;
断言很少被使用,更好的方法是编写单元测试。
7.日志是为了替代System.out.println(),可以定义格式,重定向到文件等;
日志可以存档,便于追踪问题;
日志记录可以按级别分类,便于打开或关闭某些级别;
可以根据配置文件调整日志,无需修改代码;
Java标准库提供了java.util.logging来实现日志功能
8.Commons Logging是使用最广泛的日志模块;
Commons Logging的API非常简单;
Commons Logging可以自动检测并使用其他日志模块。
9.通过Commons Logging实现日志,不需要修改代码即可使用Log4j;
使用Log4j只需要把log4j2.xml和相关jar放入classpath;
如果要更换Log4j,只需要移除log4j2.xml和相关jar;
只有扩展Log4j时,才需要引用Log4j的接口(例如,将日志加密写入数据库的功能,需要自己开发)
10.SLF4J和Logback可以取代Commons Logging和Log4j;
始终使用SLF4J的接口写入日志,使用Logback只需要配置,不需要修改代码。
代码练习合集
1.Exception_catch(捕获异常)
public class Exception_catch{
public static void main(String[] args) {
String a = "12";
String b = "x9";
try {
int c = stringToInt(a);
int d = stringToInt(b);
System.out.println(c * d);
}catch(NumberFormatException e) {
System.out.println("Bad input");
}catch(Exception e){
System.out.println("Bad input");
}finally {
System.out.println("END");
}
}
static int stringToInt(String s){
return Integer.parseInt(s);
}
}
2.Exception_throw(抛出异常)
public class Exception_throw {
public static void main(String[] args) {
try {
System.out.println(tax(2000, 0.1));
System.out.println(tax(-200, 0.1));
System.out.println(tax(2000, -0.1));
} catch (IllegalArgumentException e) {
e.printStackTrace();
} finally {
System.out.println("END");
}
}
static double tax(int salary, double rate) {
if (salary < 0 || rate < 0) {
throw new IllegalArgumentException("传入参数不能为负");
}
return salary * rate;
}
}
3.Exception_custom(自定义异常)
public class Exception_custom {
public static void main(String[] args) {
try {
String token = login("admin", "pass");
System.out.println("Token: " + token);
} catch (UserNotFoundException e1) {
e1.printStackTrace();
System.out.println("测试失败:用户名异常");
} catch (LoginFailedException e2) {
e2.printStackTrace();
System.out.println("测试失败:密码异常");
} finally {
System.out.println("测试结束");
}
}
static String login(String username, String password) {
if (username.equals("admin")) {
if (password.equals("password")) {
return "测试成功";
} else {
throw new LoginFailedException("Bad username or password.");
}
} else {
throw new UserNotFoundException("User not found.");
}
}
}
class BaseException extends RuntimeException {
public BaseException() {
super();
}
public BaseException(String message, Throwable cause) {
super(message, cause);
}
public BaseException(String message) {
super(message);
}
public BaseException(Throwable cause) {
super(cause);
}
}
class LoginFailedException extends BaseException {
public LoginFailedException(String password) {
super(password);
}
}
class UserNotFoundException extends BaseException {
public UserNotFoundException(String username) {
super(username);
}
}
4.JDK Logging
import java.io.UnsupportedEncodingException;
import java.util.logging.Logger;
public class Logging {
public static void main(String[] args) {
Logger logger = Logger.getLogger(Logging.class.getName());
logger.info("Start process...");
try {
"".getBytes("invalidCharsetName");
} catch (UnsupportedEncodingException e) {
logger.severe(e.toString());
e.printStackTrace();
}
logger.info("Process end.");
}
}
5.Commons_logging(配合commons-logging-1.2.jar命令行执行)`
package Exception;
import java.io.UnsupportedEncodingException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class Main {
static final Log log = LogFactory.getLog(Main.class);
public static void main(String[] args) {
log.info("Start process...");
try {
"".getBytes("invalidCharsetName");
} catch (UnsupportedEncodingException e) {
log.error("UnsupportedEncodingException",e);
e.printStackTrace();
}
log.info("Process end.");
}
}
4.反射
1.JVM为每个加载的class及interface创建了对应的Class实例来保存class及interface的所有信息;
获取一个class对应的Class实例后,就可以获取该class的所有信息;
通过Class实例获取class信息的方法称为反射(Reflection);
JVM总是动态加载class,可以在运行期根据条件来控制加载class。
2.Java的反射API提供的Field类封装了字段的所有信息:
通过Class实例的方法可以获取Field实例:getField(),getFields(),getDeclaredField(),getDeclaredFields();
通过Field实例可以获取字段信息:getName(),getType(),getModifiers();
通过Field实例可以读取或设置某个对象的字段,如果存在访问限制,要首先调用setAccessible(true)来访问非public字段。
通过反射读写字段是一种非常规方法,它会破坏对象的封装。
3.Java的反射API提供的Method对象封装了方法的所有信息:
通过Class实例的方法可以获取Method实例:getMethod(),getMethods(),getDeclaredMethod(),getDeclaredMethods();
通过Method实例可以获取方法信息:getName(),getReturnType(),getParameterTypes(),getModifiers();
通过Method实例可以调用某个对象的方法:Object invoke(Object instance, Object... parameters);
通过设置setAccessible(true)来访问非public方法;
通过反射调用方法时,仍然遵循多态原则。
4.Constructor对象封装了构造方法的所有信息;
通过Class实例的方法可以获取Constructor实例:getConstructor(),getConstructors(),getDeclaredConstructor(),getDeclaredConstructors();
通过Constructor实例可以创建一个实例对象:newInstance(Object... parameters); 通过设置setAccessible(true)来访问非public构造方法。
5.通过Class对象可以获取继承关系:
Class getSuperclass():获取父类类型;
Class[] getInterfaces():获取当前类实现的所有接口。
通过Class对象的isAssignableFrom()方法可以判断一个向上转型是否可以实现。
6.Java标准库提供了动态代理功能,允许在运行期动态创建一个接口的实例;
动态代理是通过Proxy创建代理对象,然后将接口方法“代理”给InvocationHandler完成的。
代码练习合集
1.Reflect_field
import java.lang.reflect.Field;
public class Main {
public static void main(String[] args) throws Exception {
String name = "Xiao Ming";
int age = 20;
Person p = new Person();
Class c = p.getClass();
Field f1 = c.getDeclaredField("name");
f1.setAccessible(true);
f1.set(p, name);
Field f2 = c.getDeclaredField("age");
f2.setAccessible(true);
f2.set(p, age);
System.out.println(p.getName());
System.out.println(p.getAge());
}
}
public class Person {
private String name;
private int 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;
}
}
2.Reflect_method
package method;
import java.lang.reflect.*;
public class Main {
public static void main(String[] args) throws Exception {
String name = "Xiao Ming";
int age = 20;
Person p = new Person();
Method m1 = p.getClass().getDeclaredMethod("setName", String.class);
m1.setAccessible(true);
m1.invoke(p, name);
Method m2 = p.getClass().getDeclaredMethod("setAge",int.class);
m2.setAccessible(true);
m2.invoke(p, age);
System.out.println(p.getName());
System.out.println(p.getAge());
}
}
package method;
public class Person {
private String name;
private int 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;
}
}
5.注解
1.注解(Annotation)是Java语言用于工具处理的标注:
注解可以配置参数,没有指定配置的参数使用默认值;
如果参数名称是value,且只有一个参数,那么可以省略参数名称。
2.Java使用@interface定义注解:
可定义多个参数和默认值,核心参数使用value名称;
必须设置@Target来指定Annotation可以应用的范围;
应当设置@Retention(RetentionPolicy.RUNTIME)便于运行期读取该Annotation。
3.可以在运行期通过反射读取RUNTIME类型的注解,注意千万不要漏写@Retention(RetentionPolicy.RUNTIME),否则运行期无法读取到该注解。
可以通过程序处理注解来实现相应的功能:
对JavaBean的属性值按规则进行检查;
JUnit会自动运行@Test标记的测试方法。
代码练习合集
1. Annotation_range_check
import java.lang.reflect.Field;
public class Main {
public static void main(String[] args) throws Exception {
Person p1 = new Person("Bob", "Beijing", 20);
Person p2 = new Person("", "Shanghai", 20);
Person p3 = new Person("Alice", "Shanghai", 199);
for (Person p : new Person[] {
p1, p2, p3 }) {
try {
check(p);
System.out.println("Person " + p + " checked ok.");
} catch (IllegalArgumentException e) {
System.out.println("Person " + p + " checked failed: " + e);
}
}
}
static void check(Person person) throws IllegalArgumentException, ReflectiveOperationException {
for (Field field : person.getClass().getFields()) {
Range range = field.getAnnotation(Range.class);
if (range != null) {
Object value = field.get(person);
if(value instanceof String) {
String s = (String) value;
if(s.length() < range.min() ||s.length() > range.max()) {
throw new IllegalArgumentException("Invalid field:" + field.getName());
}
}
if(value instanceof Integer) {
int t = (int) value;
if(t < range.min() ||t > range.max()) {
throw new IllegalArgumentException("Invalid field:" + field.getName());
}
}
}
}
}
}
public class Person {
@Range(min = 1, max = 20)
public String name;
@Range(max = 10)
public String city;
@Range(min = 1, max = 100)
public int age;
public Person(String name, String city, int age) {
this.name = name;
this.city = city;
this.age = age;
}
@Override
public String toString() {
return String.format("{Person: name=%s, city=%s, age=%d}", name, city, age);
}
}
import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Range {
int min() default 0;
int max() default 255;
}
6.泛型
1.泛型就是编写模板代码来适应任意类型;
泛型的好处是使用时不必对类型进行强制转换,它通过编译器对类型进行检查;
注意泛型的继承关系:可以把ArrayList<Integer>向上转型为List<Integer>(T不能变!),但不能把ArrayList<Integer>向上转型为ArrayList<Number>(T不能变成父类)。
2.编写泛型时,需要定义泛型类型<T>;
静态方法不能引用泛型类型<T>,必须定义其他类型(例如<K>)来实现静态泛型方法;
泛型可以同时定义多种类型,例如Map<K, V>。
3.Java的泛型是采用擦拭法实现的;
擦拭法决定了泛型<T>:
不能是基本类型,例如:int;
不能获取带泛型类型的Class,例如:Pair<String>.class;
不能判断带泛型类型的类型,例如:x instanceof Pair<String>;
不能实例化T类型,例如:new T()。
型方法要防止重复定义方法,例如:public boolean equals(T obj);
子类可以获取父类的泛型类型<T>。
4.使用类似<? extends Number>通配符作为方法参数时表示:
方法内部可以调用获取Number引用的方法,例如:Number n = obj.getFirst();;
方法内部无法调用传入Number引用的方法(null除外),例如:obj.setFirst(Number n);。
即一句话总结:使用extends通配符表示可以读,不能写。
使用类似<T extends Number>定义泛型类时表示:
泛型类型限定为Number以及Number的子类。
5.使用类似<? super Integer>通配符作为方法参数时表示:
方法内部可以调用传入Integer引用的方法,例如:obj.setFirst(Integer n);;
方法内部无法调用获取Integer引用的方法(Object除外),例如:Integer n = obj.getFirst();。
即使用super通配符表示只能写不能读。
使用extends和super通配符要遵循PECS原则。
无限定通配符<?>很少使用,可以用<T>替换,同时它是所有<T>类型的超类。
6.部分反射API是泛型,例如:Class<T>,Constructor<T>;
可以声明带泛型的数组,但不能直接创建带泛型的数组,必须强制转型;
可以通过Array.newInstance(Class<T>, int)创建T[]数组,需要强制转型;
同时使用泛型和可变参数时需要特别小心。
# 7.集合
1.List是按索引顺序访问的长度可变的有序表,优先使用ArrayList而不是LinkedList;
可以直接使用for each遍历List;
List可以和Array相互转换。
2.在List中查找元素时,List的实现类通过元素的equals()方法比较两个元素是否相等,
因此,放入的元素必须正确覆写equals()方法,Java标准库提供的String、Integer等已经覆写了equals()方法;
编写equals()方法可借助Objects.equals()判断。
如果不在List中查找元素,就不必覆写equals()方法
3.Map是一种映射表,可以通过key快速查找value。
可以通过for each遍历keySet(),也可以通过for each遍历entrySet(),直接获取key-value。
最常用的一种Map实现是HashMap。
4.要正确使用HashMap,作为key的类必须正确覆写equals()和hashCode()方法;
一个类如果覆写了equals(),就必须覆写hashCode(),并且覆写规则是:
如果equals()返回true,则hashCode()返回值必须相等;
如果equals()返回false,则hashCode()返回值尽量不要相等。
实现hashCode()方法可以通过Objects.hashCode()辅助方法实现
5.如果Map的key是enum类型,推荐使用EnumMap,既保证速度,也不浪费空间。
使用EnumMap的时候,根据面向抽象编程的原则,应持有Map接口。
6.SortedMap在遍历时严格按照Key的顺序遍历,最常用的实现类是TreeMap;
作为SortedMap的Key必须实现Comparable接口,或者传入Comparator;
要严格按照compare()规范实现比较逻辑,否则,TreeMap将不能正常工作。
7.Java集合库提供的Properties用于读写配置文件.properties。.properties文件可以使用UTF-8编码。
可以从文件系统、classpath或其他任何地方读取.properties文件。
读写Properties时,注意仅使用getProperty()和setProperty()方法,不要调用继承而来的get()和put()等方法。
8.Set用于存储不重复的元素集合:
放入HashSet的元素与作为HashMap的key要求相同;
放入TreeSet的元素与作为TreeMap的Key要求相同;
利用Set可以去除重复元素;
遍历SortedSet按照元素的排序顺序遍历,也可以自定义排序算法。
9.队列Queue实现了一个先进先出(FIFO)的数据结构:
通过add()/offer()方法将元素添加到队尾;
通过remove()/poll()从队首获取元素并删除;
通过element()/peek()从队首获取元素但不删除。
要避免把null添加到队列。
10.PriorityQueue实现了一个优先队列:从队首获取元素时,总是获取优先级最高的元素。
PriorityQueue默认按元素比较的顺序排序(必须实现Comparable接口),也可以通过Comparator自定义排序算法(元素就不必实现Comparable接口)。
11.Deque实现了一个双端队列(Double Ended Queue),它可以:
将元素添加到队尾或队首:addLast()/offerLast()/addFirst()/offerFirst();
从队首/队尾获取元素并删除:removeFirst()/pollFirst()/removeLast()/pollLast();
从队首/队尾获取元素但不删除:getFirst()/peekFirst()/getLast()/peekLast();
总是调用xxxFirst()/xxxLast()以便与Queue的方法区分开;
避免把null添加到队列。
12.栈(Stack)是一种后进先出(LIFO)的数据结构,操作栈的元素的方法有:
把元素压栈:push(E);
把栈顶的元素“弹出”:pop(E);
取栈顶元素但不弹出:peek(E)。
在Java中,我们用Deque可以实现Stack的功能,注意只调用push()/pop()/peek()方法,避免调用Deque的其他方法。
最后,不要使用遗留类Stack。
13.Iterator是一种抽象的数据访问模型。使用Iterator模式进行迭代的好处有:
对任何集合都采用同一种访问模型;
调用者对集合内部结构一无所知;
集合类返回的Iterator对象知道如何迭代。
Java提供了标准的迭代器模型,即集合类实现java.util.Iterable接口,返回java.util.Iterator实例。
14.Collections类提供了一组工具方法来方便使用集合类:
创建空集合;
创建单元素集合;
创建不可变集合;
排序/洗牌等操作
代码练习合集
1.List
import java.util.*;
public class Main {
public static void main(String[] args) {
final int start = 10;
final int end = 20;
List<Integer> list = new ArrayList<>();
for (int i = start; i <= end; i++) {
list.add(i);
}
Collections.shuffle(list);
int removed = list.remove((int) (Math.random() * list.size()));
int found = findMissingNumber(start, end, list);
System.out.println(list.toString());
System.out.println("missing number: " + found);
System.out.println(removed == found ? "测试成功" : "测试失败");
}
static int findMissingNumber(int start, int end, List<Integer> list) {
int sum = 0;
for (Integer i : list) {
sum += i;
}
return (start + end)*(end - start + 1) / 2 - sum;
}
}
2.复写equals方法
import java.util.List;
import java.util.Objects;
public class Equals {
public static void main(String[] args) {
List<Person> list = List.of(new Person("Xiao", "Ming", 18), new Person("Xiao", "Hong", 25),
new Person("Bob", "Smith", 20));
boolean exist = list.contains(new Person("Bob", "Smith", 20));
System.out.println(exist ? "测试成功!" : "测试失败!");
}
}
class Person {
String firstName;
String lastName;
int age;
public Person(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
public boolean equals(Object o) {
if (o instanceof Person) {
Person p = (Person) o;
return Objects.equals(this.firstName, p.firstName) && Objects.equals(this.lastName, p.lastName)
&& this.age == p.age;
}
return false;
}
}
3.Map
import java.util.*;
public class Map {
public static void main(String[] args) {
List<Student> list = List.of(new Student("Bob", 78), new Student("Alice", 85), new Student("Brush", 66),
new Student("Newton", 99));
var holder = new Students(list);
System.out.println(holder.getScore("Bob") == 78 ? "测试成功!" : "测试失败!");
System.out.println(holder.getScore("Alice") == 85 ? "测试成功!" : "测试失败!");
System.out.println(holder.getScore("Tom") == -1 ? "测试成功!" : "测试失败!");
}
}
class Students {
List<Student> list;
Map<String, Integer> cache;
Students(List<Student> list) {
this.list = list;
cache = new HashMap<>();
}
int getScore(String name) {
Integer score = this.cache.get(name);
if (score == null) {
score = this.findInList(name);
if(score != null) {
cache.put(name,score);
}
}
return score == null ? -1 : score.intValue();
}
Integer findInList(String name) {
for (var ss : this.list) {
if (ss.name.equals(name)) {
return ss.score;
}
}
return null;
}
}
class Student {
String name;
int score;
Student(String name, int score) {
this.name = name;
this.score = score;
}
}
4.Set
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Message> received = List.of(
new Message(1, "Hello!"),
new Message(2, "发工资了吗?"),
new Message(2, "发工资了吗?"),
new Message(3, "去哪吃饭?"),
new Message(3, "去哪吃饭?"),
new Message(4, "Bye")
);
List<Message> displayMessages = process(received);
for (Message message : displayMessages) {
System.out.println(message.text);
}
}
static List<Message> process(List<Message> received) {
List<Message> newReceived = new ArrayList<>();
Set<Integer> set = new TreeSet<>();
for (Message ss : received) {
if(set.add(ss.sequence)) {
newReceived.add(ss);
}
}
return newReceived;
}
}
class Message {
public final int sequence;
public final String text;
public Message(int sequence, String text) {
this.sequence = sequence;
this.text = text;
}
}
5.Priority_queue
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;
public class Main {
public static void main(String[] args) {
Queue<User> q = new PriorityQueue<>(new UserComparator());
q.offer(new User("Bob", "A10"));
q.offer(new User("Alice", "A2"));
q.offer(new User("Boss", "V1"));
System.out.println(q.poll());
System.out.println(q.poll());
System.out.println(q.poll());
System.out.println(q.poll());
}
}
class UserComparator implements Comparator<User> {
public int compare(User u1, User u2) {
if (u1.number.charAt(0) == u2.number.charAt(0)) {
int u1Num = Integer.parseInt(u1.number.substring(1));
int u2Num = Integer.parseInt(u2.number.substring(1));
return Integer.compare(u1Num,u2Num);
}
if (u1.number.charAt(0) == 'V') {
return -1;
} else {
return 1;
}
}
}
class User {
public final String name;
public final String number;
public User(String name, String number) {
this.name = name;
this.number = number;
}
public String toString() {
return name + "/" + number;
}
}
8.IO
1.iO流是一种流式的数据输入/输出模型:
二进制数据以byte为最小单位在InputStream/OutputStream中单向流动;
字符数据以char为最小单位在Reader/Writer中单向流动。
Java标准库的java.io包提供了同步IO功能:
字节流接口:InputStream/OutputStream;
字符流接口:Reader/Writer。
2.ava标准库的java.io.File对象表示一个文件或者目录:
创建File对象本身不涉及IO操作;
可以获取路径/绝对路径/规范路径:getPath()/getAbsolutePath()/getCanonicalPath();
可以获取目录的文件和子目录:list()/listFiles();
可以创建或删除文件和目录。
3.Java标准库的java.io.InputStream定义了所有输入流的超类:
FileInputStream实现了文件流输入;
ByteArrayInputStream在内存中模拟一个字节流输入。
使用try(resource)来保证InputStream正确关闭。
4.Java标准库的java.io.OutputStream定义了所有输出流的超类:
FileOutputStream实现了文件流输出;
ByteArrayOutputStream在内存中模拟一个字节流输出。
某些情况下需要手动调用OutputStream的flush()方法来强制输出缓冲区。
总是使用try(resource)来保证OutputStream正确关闭。
5.Java的IO标准库使用Filter模式为InputStream和OutputStream增加功能:
可以把一个InputStream和任意个FilterInputStream组合;
可以把一个OutputStream和任意个FilterOutputStream组合。
Filter模式可以在运行期动态增加功能(又称Decorator模式)。
6.ZipInputStream可以读取zip格式的流,ZipOutputStream可以把多份数据写入zip包;
配合FileInputStream和FileOutputStream就可以读写zip文件
7.把资源存储在classpath中可以避免文件路径依赖;
Class对象的getResourceAsStream()可以从classpath中读取指定资源;
根据classpath读取资源时,需要检查返回的InputStream是否为null。
8.可序列化的Java对象必须实现java.io.Serializable接口,类似Serializable这样的空接口被称为“标记接口”(Marker Interface);
反序列化时不调用构造方法,可设置serialVersionUID作为版本号(非必需);
Java的序列化机制仅适用于Java,如果需要与其它语言交换数据,必须使用通用的序列化方法,例如JSON。
9.Reader定义了所有字符输入流的超类:
FileReader实现了文件字符流输入,使用时需要指定编码;
CharArrayReader和StringReader可以在内存中模拟一个字符流输入。
Reader是基于InputStream构造的:可以通过InputStreamReader在指定编码的同时将任何InputStream转换为Reader。
总是使用try (resource)保证Reader正确关闭。
10.Writer定义了所有字符输出流的超类:
FileWriter实现了文件字符流输出;
CharArrayWriter和StringWriter在内存中模拟一个字符流输出。
使用try (resource)保证Writer正确关闭。
Writer是基于OutputStream构造的,可以通过OutputStreamWriter将OutputStream转换为Writer,转换时需要指定编码。
11.PrintStream是一种能接收各种数据类型的输出,打印数据时比较方便:
System.out是标准输出;
System.err是标准错误输出。
PrintWriter是基于Writer的输出
代码练习合集
1.File
import java.io.File;
public class Main {
public static void main(String[] args) {
File file = new File("C:\\Users\\74886\\Desktop\\宇宙机");
print(file, 0);
}
private static void print(File file, int level){
for(int i = 0; i < level; ++i)
System.out.print(" ");
if(file.isDirectory()){
System.out.println(file.getName() + "/");
}else if(file.isFile())
System.out.println(file.getName());
File[] fList = file.listFiles();
if(fList != null){
for (File f:fList)
print(f, level+1);
}
}
}
9.时间与日期
1.在编写日期和时间的程序前,我们要准确理解日期、时间和时刻的概念。
由于存在本地时间,我们需要理解时区的概念,并且必须牢记由于夏令时的存在,同一地区用GMT/UTC和城市表示的时区可能导致时间不同。
计算机通过Locale来针对当地用户习惯格式化日期、时间、数字、货币等。
2.计算机表示的时间是以整数表示的时间戳存储的,即Epoch Time,Java使用long型来表示以毫秒为单位的时间戳,通过System.currentTimeMillis()获取当前时间戳。
Java有两套日期和时间的API:
旧的Date、Calendar和TimeZone;
新的LocalDateTime、ZonedDateTime、ZoneId等。
分别位于java.util和java.time包中。
3.Java 8引入了新的日期和时间API,它们是不变类,默认按ISO 8601标准格式化和解析;
使用LocalDateTime可以非常方便地对日期和时间进行加减,或者调整日期和时间,它总是返回新对象;
使用isBefore()和isAfter()可以判断日期和时间的先后;
使用Duration和Period可以表示两个日期和时间的“区间间隔”。
4.-[ZonedDateTime是带时区的日期和时间,可用于时区转换;
ZonedDateTime和LocalDateTime可以相互转换。
5.对ZonedDateTime或LocalDateTime进行格式化,需要使用DateTimeFormatter类;
DateTimeFormatter可以通过格式化字符串和Locale对日期和时间进行定制输出。
6.Instant表示高精度时间戳,它可以和ZonedDateTime以及long互相转换。
7.处理日期和时间时,尽量使用新的java.time包;
在数据库中存储时间戳时,尽量使用long型时间戳,它具有省空间,效率高,不依赖数据库的优点。
练习代码合集
1.Flight_time
import java.time.*;
public class Main {
public static void main(String[] args) {
LocalDateTime departureAtBeijing = LocalDateTime.of(2019, 9, 15, 13, 0, 0);
int hours = 13;
int minutes = 20;
LocalDateTime arrivalAtNewYork = calculateArrivalAtNY(departureAtBeijing, hours, minutes);
System.out.println(departureAtBeijing + " -> " + arrivalAtNewYork);
if (!LocalDateTime.of(2019, 10, 15, 14, 20, 0)
.equals(calculateArrivalAtNY(LocalDateTime.of(2019, 10, 15, 13, 0, 0), 13, 20))) {
System.err.println("测试失败!");
} else if (!LocalDateTime.of(2019, 11, 15, 13, 20, 0)
.equals(calculateArrivalAtNY(LocalDateTime.of(2019, 11, 15, 13, 0, 0), 13, 20))) {
System.err.println("测试失败!");
}
}
static LocalDateTime calculateArrivalAtNY(LocalDateTime bj, int h, int m) {
ZonedDateTime zbj = bj.atZone(ZoneId.of("Asia/Shanghai"));
ZonedDateTime zny = zbj.withZoneSameInstant(ZoneId.of("America/New_York"));
LocalDateTime lny = zny.toLocalDateTime();
LocalDateTime lny2 = lny.plusHours(13).plusMinutes(20);
return lny2;
}
}
10.单元测试
1.JUnit是一个单元测试框架,专门用于运行我们编写的单元测试:
一个JUnit测试包含若干@Test方法,并使用Assertions进行断言,注意浮点数assertEquals()要指定delta。
2.编写Fixture是指针对每个@Test方法,编写@BeforeEach方法用于初始化测试资源,编写@AfterEach用于清理测试资源;
必要时,可以编写@BeforeAll和@AfterAll,使用静态变量来初始化耗时的资源,并且在所有@Test方法的运行前后仅执行一次。
3.测试异常可以使用assertThrows(),期待捕获到指定类型的异常;
对可能发生的每种类型的异常都必须进行测试。
4.条件测试是根据某些注解在运行期让JUnit自动忽略某些测试。
5.使用参数化测试,可以提供一组测试数据,对一个测试方法反复测试。
参数既可以在测试代码中写死,也可以通过@CsvFileSource放到外部的CSV文件中。
代码练习合集
1.Junit测试
public class Main {
public static long fact(long n) {
long r = 1;
for (long i = 1; i <= n; i++) {
r = r * i;
}
return r;
}
}
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
public class MainTest {
@Test
void testFact() {
assertEquals(1, Main.fact(1));
assertEquals(2, Main.fact(2));
assertEquals(6, Main.fact(3));
assertEquals(362800, Main.fact(10));
assertEquals(2432902008176640000L, Main.fact(20));
}
}
2.Fixture
public class Calculator {
private long n = 0;
public long add(long x) {
n = n + x;
return n;
}
public long sub(long x) {
n = n - x;
return n;
}
}
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class CalculatorTest {
Calculator calculator;
@BeforeEach
public void setUp() {
this.calculator = new Calculator();
}
@AfterEach
public void tearDown() {
this.calculator = null;
}
@Test
void testAdd() {
assertEquals(100, this.calculator.add(100));
assertEquals(150, this.calculator.add(50));
assertEquals(130, this.calculator.add(-20));
}
@Test
void testSub() {
assertEquals(-100, this.calculator.sub(100));
assertEquals(-150, this.calculator.sub(50));
assertEquals(-130, this.calculator.sub(-20));
}
}
public class CalculatorTest {
}
3.异常测试
public class Factorial {
public static long fact(long n) {
if (n < 0) {
throw new IllegalArgumentException();
}
if(n > 20) {
throw new ArithmeticException();
}
long r = 1;
for (long i = 1; i <= n; i++) {
r = r * i;
}
return r;
}
}
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
public class FactorialTest {
@Test
void testFact() {
assertEquals(1, Factorial.fact(0));
assertEquals(1, Factorial.fact(1));
assertEquals(2, Factorial.fact(2));
assertEquals(6, Factorial.fact(3));
assertEquals(3628800, Factorial.fact(10));
assertEquals(2432902008176640000L, Factorial.fact(20));
}
@Test
void testNegative() {
assertThrows(IllegalArgumentException.class, new Executable() {
@Override
public void execute() throws Throwable {
Factorial.fact(-1);
}
});
assertThrows(IllegalArgumentException.class, () -> {
Factorial.fact(-1);
});
}
@Test
void testLargeInput() {
assertThrows(ArithmeticException.class, () -> {
Factorial.fact(21);
});
}
}
11.正则表达式
1.正则表达式是用字符串描述的一个匹配规则,使用正则表达式可以快速判断给定的字符串是否符合匹配规则。
Java标准库java.util.regex内建了正则表达式引擎。
2.单个字符的匹配规则如下:
正则表达式 规则 可以匹配
A 指定字符 A
\u548c 指定Unicode字符 和
. 任意字符 a,b,&,0
\d 数字0~9 0~9
\w 大小写字母,数字和下划线 a~z,A~Z,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
3.
复杂匹配规则主要有:
正则表达式 规则 可以匹配
^ 开头 字符串开头
$ 结尾 字符串结束
[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
4.正则表达式用(...)分组可以通过Matcher对象快速提取子串:
group(0)表示匹配的整个字符串;
group(1)表示第1个子串,group(2)表示第2个子串,以此类推。
5.正则表达式匹配默认使用贪婪匹配,可以使用?表示对某一规则进行非贪婪匹配。
注意区分?的含义:\d??。
6.使用正则表达式可以:
分割字符串:String.split()
搜索子串:Matcher.find()
替换字符串:String.replaceAll()
代码练习合集
1.电话匹配练习
import java.util.*;
public class Main {
public static void main(String[] args) throws Exception {
String re = "0\\d{2,3}-[1-9]\\d{6,7}";
for (String s : List.of("010-12345678", "020-9999999", "0755-7654321")) {
if (!s.matches(re)) {
System.out.println("测试失败: " + s);
return;
}
}
for (String s : List.of("010 12345678", "A20-9999999", "0755-7654.321")) {
if (s.matches(re)) {
System.out.println("测试失败: " + s);
return;
}
}
System.out.println("测试成功!");
}
}
2.分组匹配
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Time {
public static int[] parseTime(String s) {
if (s == null) {
throw new IllegalArgumentException();
}
Pattern pattern = Pattern.compile("([0-1]\\d|2[0-3]):([0-5][0-9]):([0-5][0-9])");
Matcher matcher = pattern.matcher(s);
if (matcher.matches()) {
int hour = Integer.parseInt(matcher.group(1));
int minute = Integer.parseInt(matcher.group(2));
int second = Integer.parseInt(matcher.group(3));
return new int[] {
hour, minute, second };
} else {
throw new IllegalArgumentException();
}
}
}
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
class TimeTest {
@Test
public void testParseTime() {
assertArrayEquals(new int[] {
0, 0, 0 }, Time.parseTime("00:00:00"));
assertArrayEquals(new int[] {
1, 2, 3 }, Time.parseTime("01:02:03"));
assertArrayEquals(new int[] {
10, 20, 30 }, Time.parseTime("10:20:30"));
assertArrayEquals(new int[] {
12, 34, 56 }, Time.parseTime("12:34:56"));
assertArrayEquals(new int[] {
23, 59, 59 }, Time.parseTime("23:59:59"));
}
@Test
public void testParseTimeFailed() {
assertThrows(IllegalArgumentException.class, () -> {
Time.parseTime(null);
});
assertThrows(IllegalArgumentException.class, () -> {
Time.parseTime("");
});
assertThrows(IllegalArgumentException.class, () -> {
Time.parseTime("24:00:00");
});
assertThrows(IllegalArgumentException.class, () -> {
Time.parseTime("23:60:59");
});
assertThrows(IllegalArgumentException.class, () -> {
Time.parseTime("10:1:2");
});
}
}
3.搜索和替换
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Template {
final String template;
final Pattern pattern = Pattern.compile("\\$\\{(\\w+)\\}");
public Template(String template) {
this.template = template;
}
public String render(Map<String, Object> data) {
Matcher m = pattern.matcher(template);
StringBuilder sb = new StringBuilder();
while (m.find()) {
m.appendReplacement(sb, data.get(m.group(1)).toString());
}
m.appendTail(sb);
return sb.toString();
}
}
import static org.junit.jupiter.api.Assertions.*;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
class TemplateTest {
@Test
public void testIsValidTel() {
Template t = new Template("Hello, ${name}! You are learning ${lang}!");
Map<String, Object> data = new HashMap<>();
data.put("name", "Bob");
data.put("lang", "Java");`在这里插入代码片`
assertEquals("Hello, Bob! You are learning Java!", t.render(data));
}
}
12.加密与安全
1.URL编码和Base64编码都是编码算法,它们不是加密算法;
URL编码的目的是把任意文本数据编码为%前缀表示的文本,便于浏览器和服务器处理;
Base64编码的目的是把任意二进制数据编码为文本,但编码后数据量会增加1/3。
2.哈希算法可用于验证数据完整性,具有防篡改检测的功能;
常用的哈希算法有MD5、SHA-1等;
用哈希存储口令时要考虑彩虹表攻击。
3.BouncyCastle是一个开源的第三方算法提供商;
BouncyCastle提供了很多Java标准库没有提供的哈希算法和加密算法;
使用第三方算法前需要通过Security.addProvider()注册。
4.Hmac算法是一种标准的基于密钥的哈希算法,可以配合MD5、SHA-1等哈希算法,计算的摘要长度和原摘要算法长度相同。
5.对称加密算法使用同一个密钥进行加密和解密,常用算法有DES、AES和IDEA等;
密钥长度由算法设计决定,AES的密钥长度是128/192/256位;
使用对称加密算法需要指定算法名称、工作模式和填充模式。
6.PBE算法通过用户口令和安全的随机salt计算出Key,然后再进行加密;
Key通过口令和安全的随机salt计算得出,大大提高了安全性;
PBE算法内部使用的仍然是标准对称加密算法(例如AES)。
7.非对称加密就是加密和解密使用的不是相同的密钥,只有同一个公钥-私钥对才能正常加解密;
只使用非对称加密算法不能防止中间人攻击。
8.数字签名就是用发送方的私钥对原始数据进行签名,只有用发送方公钥才能通过签名验证。
数字签名用于:
防止伪造;
防止抵赖;
检测篡改。
常用的数字签名算法包括:MD5withRSA/SHA1withRSA/SHA256withRSA/SHA1withDSA/SHA256withDSA/SHA512withDSA/ECDSA等。
9.数字证书就是集合了多种密码学算法,用于实现数据加解密、身份认证、签名等多种功能的一种安全标准。
数字证书采用链式签名管理,顶级的Root CA证书已内置在操作系统中。
数字证书存储的是公钥,可以安全公开,而私钥必须严格保密。
代码练习合集
1.:使用BouncyCastle提供的RipeMD160
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class Main {
public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());
MessageDigest md = MessageDigest.getInstance("RipeMD160");
md.update("HelloWorld".getBytes("UTF-8"));
byte[] result = md.digest();
System.out.println(new BigInteger(1, result).toString(16));
}
}
13.多线程
1.Java用Thread对象表示一个线程,通过调用start()启动一个新线程;
一个线程对象只能调用一次start()方法;
线程的执行代码写在run()方法中;
线程调度由操作系统决定,程序本身无法决定调度顺序;
Thread.sleep()可以把当前线程暂停一段时间
2.Java线程对象Thread的状态包括:New、Runnable、Blocked、Waiting、Timed Waiting和Terminated;
通过对另一个线程对象调用join()方法可以等待其执行结束;
可以指定等待时间,超过等待时间线程仍然没有结束就不再等待;
对已经运行结束的线程调用join()方法会立刻返回。
3.对目标线程调用interrupt()方法可以请求中断一个线程,目标线程通过检测isInterrupted()标志获取自身是否已中断。如果目标线程处于等待状态,该线程会捕获到InterruptedException;
目标线程检测到isInterrupted()为true或者捕获了InterruptedException都应该立刻结束自身线程;
通过标志位判断需要正确使用volatile关键字;
volatile关键字解决了共享变量在线程间的可见性问题。
4.守护线程是为其他线程服务的线程;
所有非守护线程都执行完毕后,虚拟机退出;
守护线程不能持有需要关闭的资源(如打开文件等)。
5.多线程同时读写共享变量时,会造成逻辑错误,因此需要通过synchronized同步;
同步的本质就是给指定对象加锁,加锁后才能继续执行后续代码;
注意加锁对象必须是同一个实例;
对JVM定义的单个原子操作不需要同步。
6.用synchronized修饰方法可以把整个方法变为同步代码块,synchronized方法加锁对象是this;
通过合理的设计和数据封装可以让一个类变为“线程安全”;
一个类没有特殊说明,默认不是thread-safe;
多线程能否安全访问某个非线程安全的实例,需要具体问题具体分析。
7.Java的synchronized锁是可重入锁;
死锁产生的条件是多线程各自持有不同的锁,并互相试图获取对方已持有的锁,导致无限等待;
避免死锁的方法是多线程获取锁的顺序要一致
8.wait和notify用于多线程协调运行:
在synchronized内部可以调用wait()使线程进入等待状态;
必须在已获得的锁对象上调用wait()方法;
在synchronized内部可以调用notify()或notifyAll()唤醒其他等待线程;
必须在已获得的锁对象上调用notify()或notifyAll()方法;
已唤醒的线程还需要重新获得锁后才能继续执行。
9.ReentrantLock可以替代synchronized进行同步;
ReentrantLock获取锁更安全;
必须先获取到锁,再进入try {
...}代码块,最后使用finally保证释放锁;
可以使用tryLock()尝试获取锁
10.Condition可以替代wait和notify;
Condition对象必须从Lock对象获取。
11.使用ReadWriteLock可以提高读取效率:
ReadWriteLock只允许一个线程写入;
ReadWriteLock允许多个线程在没有写入时同时读取;
ReadWriteLock适合读多写少的场景。
12.StampedLock提供了乐观读锁,可取代ReadWriteLock以进一步提升并发性能;ThreadLocal表示线程的“局部变量”,它确保每个线程的ThreadLocal变量都是各自独立的;
ThreadLocal适合在一个线程的处理流程中保持上下文(避免了同一参数在所有方法中传递);
使用ThreadLocal要用try ... finally结构,并在finally中清除。
StampedLock是不可重入锁。
13.使用java.util.concurrent包提供的线程安全的并发集合可以大大简化多线程编程:
多线程同时读写并发集合是安全的;
尽量使用Java标准库提供的并发集合,避免自己编写同步代码。
14.使用java.util.concurrent.atomic提供的原子操作可以简化多线程编程:
原子操作实现了无锁的线程安全;
适用于计数器,累加器等。
15.JDK提供了ExecutorService实现了线程池功能:
线程池内部维护一组线程,可以高效执行大量小任务;
Executors提供了静态方法创建不同类型的ExecutorService;
必须调用shutdown()关闭ExecutorService;
ScheduledThreadPool可以定期调度多个任务。
16.对线程池提交一个Callable任务,可以获得一个Future对象;
可以用Future在将来某个时刻获取结果。
17.CompletableFuture可以指定异步处理流程:
thenAccept()处理正常结果;
exceptional()处理异常结果;
thenApplyAsync()用于串行化另一个CompletableFuture;
anyOf()和allOf()用于并行化多个CompletableFuture。
18.Fork/Join是一种基于“分治”的算法:通过分解任务,并行执行,最后合并结果得到最终结果。
ForkJoinPool线程池可以把一个大任务分拆成小任务并行执行,任务类必须继承自RecursiveTask或RecursiveAction。
使用Fork/Join模式可以进行并行计算以提高效率。
19.ThreadLocal表示线程的“局部变量”,它确保每个线程的ThreadLocal变量都是各自独立的;
ThreadLocal适合在一个线程的处理流程中保持上下文(避免了同一参数在所有方法中传递);
使用ThreadLocal要用try ... finally结构,并在finally中清除。
14.Maven基础
1.Maven是一个Java项目的管理和构建工具:
Maven使用pom.xml定义项目内容,并使用预设的目录结构;
在Maven中声明一个依赖项可以自动下载并导入classpath;
Maven使用groupId,artifactId和version唯一定位一个依赖。
2.Maven通过解析依赖关系确定项目所需的jar包,常用的4种scope有:compile(默认),test,runtime和provided;
Maven从中央仓库下载所需的jar包并缓存在本地;
可以通过镜像仓库加速下载。
3.Maven通过lifecycle、phase和goal来提供标准的构建流程。
最常用的构建命令是指定phase,然后让Maven执行到指定的phase:
mvn clean
mvn clean compile
mvn clean test
mvn clean package
通常情况,我们总是执行phase默认绑定的goal,因此不必指定goal。
4.Maven通过自定义插件可以执行项目构建时需要的额外功能,使用自定义插件必须在pom.xml中声明插件及配置;
插件会在某个phase被执行时执行;
插件的配置和用法需参考插件的官方文档。
5.Maven支持模块化管理,可以把一个大项目拆成几个模块:
可以通过继承在parent的pom.xml统一定义重复配置;
可以通过<modules>编译多个模块
6.使用Maven Wrapper,可以为一个项目指定特定的Maven版本。
15.JDBC编程
1.使用JDBC的好处是:
各数据库厂商使用相同的接口,Java代码不需要针对不同数据库分别开发;
Java程序编译期仅依赖java.sql包,不依赖具体数据库的jar包;
可随时替换底层数据库,访问数据库的Java代码基本不变。
2.JDBC接口的Connection代表一个JDBC连接;
使用JDBC查询时,总是使用PreparedStatement进行查询而不是Statement;
查询结果总是ResultSet,即使使用聚合查询也不例外。
3.使用JDBC执行INSERT、UPDATE和DELETE都可视为更新操作;
更新操作使用PreparedStatement的executeUpdate()进行,返回受影响的行数。
4.数据库事务(Transaction)具有ACID特性:
Atomicity:原子性
Consistency:一致性
Isolation:隔离性
Durability:持久性
JDBC提供了事务的支持,使用Connection可以开启、提交或回滚事务。
5.使用JDBC的batch操作会大大提高执行效率,对内容相同,参数不同的SQL,要优先考虑batch操作。
6.数据库连接池是一种复用Connection的组件,它可以避免反复创建新连接,提高JDBC代码的运行效率;
可以配置连接池的详细参数并监控连接池。
代码练习合集
1.