一·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.D eque实现了一个双端队列(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 ( 2432902008176640000 L, 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 ( 2432902008176640000 L, 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- 9 xy] 指定范围的字符 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.F ork/ 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.