JAVA SE 学习笔记

/**
 * Learn Java from https://www.liaoxuefeng.com/
 * 
 * @author liaoxuefeng
 */

一·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]表示创建一个可容纳5int元素的数组。)
				也可以在定义数组时直接指定初始化的元素---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; // switch语句返回值
	
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

//计算体质指数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); // 创建Scanner对象
		Random rand = new Random(); // 创建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

//倒序遍历数组bingdyin
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

//使用do while计算从m到n的和
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

//使用while计算从m到n的和
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

//计算前n个自然数的和
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

//用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改为protectedprotected关键字可以把字段和方法的访问权限控制在继承树内部,一个protected字段和方法可以被其子类,以及子类的子类所访问。
  
5.从Java 14开始,判断instanceof后,可以直接转型为指定变量,避免再次强制转型
	public class Main {
		public static void main(String[] args) {
			Object obj = "hello";
			if (obj instanceof String s) {
				// 可以直接使用变量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内建的访问权限包括publicprotectedprivatepackage权限;
   Java在方法内部定义的变量是局部变量,局部变量的作用域从变量声明开始,到一个块结束;
   final修饰符不是访问权限,它可以修饰class、field和method;
   一个.java文件只能包含一个public类,但可以包含多个非public14.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,字段强烈建议声明为finalenum适合用在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) {
		/*
		 * 个人所得税=(工资-5000)*0.3 稿费税收 = (《4000)---》(稿费-800)*0.2*0.7 or
		 * (>4000)---->稿费*0.8*0.2*0.7
		 */
		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) {
		/*
		 * 个人所得税=(工资-5000)*0.3 稿费税收 = (《4000)---》(稿费-800)*0.2*0.7 or
		 * (>4000)---->稿费*0.8*0.2*0.7
		 */
		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) {
		/*
		 * 个人所得税=(工资-5000)*0.3 稿费税收 = (《4000)---》(稿费-800)*0.2*0.7 or
		 * (>4000)---->稿费*0.8*0.2*0.7
		 */
		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("传入参数不能为负");
		} // TODO: 如果传入的参数为负,则抛出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 {
				// 抛出LoginFailedException:
				throw new LoginFailedException("Bad username or password.");
			}
		} else {
			// 抛出UserNotFoundException:
			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());// TODO: 使用logger.severe()打印异常
			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();// TODO: 使用log.error(String, Throwable)打印异常
		}
		log.info("Process end.");
	}
}

4.反射

1.JVM为每个加载的classinterface创建了对应的Class实例来保存classinterface的所有信息;
  获取一个class对应的Class实例后,就可以获取该class的所有信息;
  通过Class实例获取class信息的方法称为反射(Reflection);
  JVM总是动态加载class,可以在运行期根据条件来控制加载class2.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();// TODO: 利用反射给name和age字段赋值:
		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()); // "Xiao Ming"
		System.out.println(p.getAge()); // 20
	}
}

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();
		// TODO: 利用反射调用setName和setAge方法:
		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()); // "Xiao Ming"
		System.out.println(p.getAge()); // 20
	}
}

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);
				// TODO:
				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通配符表示只能写不能读。
  使用extendssuper通配符要遵循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) {
		// 构造从start到end的序列:
		final int start = 10;
		final int end = 20;
		List<Integer> list = new ArrayList<>();
		for (int i = start; i <= end; i++) {
			list.add(i);
		}
		// 洗牌算法shuffle可以随机交换List中的元素位置:
		Collections.shuffle(list);
		// 随机删除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<>();
		}
	
		/**
		 * 根据name查找score,找到返回score,未找到返回-1
		 */
		int getScore(String name) {
			// 先在Map中查找:
			Integer score = this.cache.get(name);
			//Map没有直接去List查找,找到后存到cache并输出
			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<>();
	    // TODO: 按sequence去除重复消息
	    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());
        // 添加3个元素到队列:
        q.offer(new User("Bob", "A10"));
        q.offer(new User("Alice", "A2"));
        q.offer(new User("Boss", "V1"));
        System.out.println(q.poll()); // Boss/V1
        System.out.println(q.poll()); // Bob/A1
        System.out.println(q.poll()); // Alice/A2
        System.out.println(q.poll()); // null,因为队列为空
    }
}

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') {
            // u1的号码是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);
        // test:
        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 {

	/**
	 * 从"21:05:19"中提取时,分,秒,否则抛出IllegalArgumentException
	 */
	public static int[] parseTime(String s) {
		// FIXME:
		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;

/**
 * Learn Java from https://www.liaoxuefeng.com/
 * 
 * @author liaoxuefeng
 */
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);
		// TODO:
		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/32.哈希算法可用于验证数据完整性,具有防篡改检测的功能;
  常用的哈希算法有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 {
		// 注册BouncyCastle:
		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.

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值