一、数学

(一)几何

两圆相交(重叠)的条件:

  1. 圆心距小于两圆半径的和,而大于两圆半径的差的绝对值。

考虑两圆相交两点中的一点P,与两圆心成三角形:由”两边之和大于第三边“可得。

已知三角形三点,求三角形面积


S=| \frac{x_1(y_2-y_3)+x_2(y_3-y_1)+x_3(y_1-y_2)}{2} |

由向量的叉积公式得来,其中 $sin(\theta)$由点积中的$cos(\theta)$转换而来

已知三角形三边长,求三角形面积


S = \sqrt{\lambda \cdot (\lambda - e_1)(\lambda-e_2)(\lambda - e_3)}

以上为海伦公式。其中,$\lambda=\frac{e_1+e_2+e_3}{2}$

已知两点坐标,得出线段所在直线方程的一般式

设$p_1=(x_1,y_1),p_2=(x_2,y_2)$

两点式:$\frac{y-y_1}{x-x_1} = \frac{y_2-y_1}{x_2-x_1}$

一般式:$x(y_2-y_1) - y(x_2-x_1)+y_1(x_2-x_1)-x_1(y_2-y_1)=0$

即对于$ Ax+By+C=0$,有$A=(y_2-y_1), B=(x_1-x_2),C=y_1(x_2-x_1)-x_1(y_2-y_1)$

注意两点式要考虑$x_2=x_1 或y_1=y_2$的边界情况,但一般式的各系数表达式不用考虑这一边界(尽管是由两点式而来)

  1. //由两点得出直线方程的一般式Ax+By+C=0
  2. public double[] getEquation(){
  3. double[] coefficient = new double[3];
  4. coefficient[0] = p2.getY()-p1.getY(); //A
  5. coefficient[1] = p1.getX()-p2.getX(); //B
  6. coefficient[2] = p1.getY()*(p2.getX()-p1.getX()) - p1.getX()*(p2.getY()-p1.getY()); //C
  7. return coefficient;
  8. }

如何判断一点在线的哪一侧?

向量 $v{12}\times v{13}$若为正,说明点$p3$在线段向量$v{12}$的左边,为负则在右,为$0$表示向量平行,点$p_3$在线段或其延长线上

  1. //判断点与线段向量的位置:返回0表示在线段上,返回+1表示在向量左侧,返回-1表示在向量右侧,返回2表示在延长线上
  2. public int onWhichSide(MyPoint p3){
  3. double[] vector12 = {p2.getX()-p1.getX(), p2.getY()-p1.getY()};
  4. double[] vector13 = {p3.getX()-p1.getX(), p3.getY()-p1.getY()};
  5. double cross12To13 = vector12[0]*vector13[1] - vector13[0]*vector12[1];
  6. if(cross12To13 == 0){
  7. if( ( p3.getX() > p1.getX() && p3.getX() < p2.getX() ) ||
  8. p3.getX() > p2.getX() && p3.getX() < p1.getX() )
  9. return 0;
  10. else
  11. return 2;
  12. }
  13. else if( cross12To13 > 0)
  14. return 1;
  15. else return -1;
  16. }

已知端点坐标,如何判断两线段相交

方法一:

  1. 判断线段是否平行?若平行是否有重合?
  2. 不平行,解出直线的交点
    若两条直线给出的一般式方程,如:
    $$
    \begin{equation}
    \left{
    \begin{aligned}
    A_1x+B_1y+C_1=0 \
    A_2x+B_2y+C_2=0\
    \end{aligned}
    \right.
    \end{equation}
    $$
    则由克拉默法则可得$x= \left |\begin{array}{}
    (-C_1) &B_1 \
    (-C_2) &B_2 \
    \end{array}\right| / ParallelFactor$,$y=\left |\begin{array}{}
    A_1 &(-C_1) \
    A_2 &(-C_2) \
    \end{array}\right|/ ParallelFactor$,
    其中若$ ParallelFactor =\left |\begin{array}{}
    A_1 &B_1 \
    A_2 &B_2 \
    \end{array}\right| $为0,则表示两直线平行,方程无解。
  3. 判断该交点是否都在两条线段上
  1. public boolean intersection(LineSegment2D line2){
  2. double[] coLine1 = this.getEquation();
  3. double[] coLine2 = line2.getEquation();
  4. double parallelFactor = getDet2D(coLine1[0],coLine1[1],coLine2[0],coLine2[1] );
  5. //判断线段是否平行?若平行是否有重合?
  6. if( parallelFactor == 0){
  7. if (this.onWhichSide(line2.p1)==0 || this.onWhichSide(line2.p2)==0)
  8. return true;
  9. else return false;
  10. }
  11. //不平行,解出直线的交点
  12. else{
  13. double cpX = getDet2D(-coLine1[2],coLine1[1],-coLine2[2],coLine2[1] )/parallelFactor;
  14. double cpY = getDet2D(coLine1[0],-coLine1[2],coLine2[0],-coLine2[2] )/parallelFactor;
  15. MyPoint crossPoint = new MyPoint(cpX,cpY);
  16. //判断交点是否都在两条线段上
  17. if( this.inTheRecZoneOfLineSegment(crossPoint) && line2.inTheRecZoneOfLineSegment(crossPoint))
  18. return true;
  19. else return false;
  20. }
  21. }
  22. //其中 inTheRecZoneOfLineSegment 方法用于判断交点是否在两条线段上

已知顶点坐标,如何判断点是否在三角形中?

  • 面积相加是否相等
  • 该点与三点所成的三个夹角之和是否为360°
  • 该点与三点连线所在的三条直线是否和三点的对边相交
  • 用凸包

已知顶点坐标,如何判断两个三角形是否相交?

已知顶点坐标,如何判断两个矩形是否相交?

方法一:至少一个点在对方内部

方法二:取每个矩形的对角线的两个顶点(如左上顶点 left-top 和右下顶点 right-bottom

  1. //判断两矩形是否重叠,若重叠,则至少一个点在彼此内部(contains方法考虑了点在矩形边上的情况)
  2. public boolean overlapsByContain(Rectangle2D rec2){
  3. return( this.contains(rec2.left_top) || this.contains(rec2.right_top) ||
  4. this.contains(rec2.left_bottom) || this.contains(rec2.right_bottom));
  5. }
  6. //判断两矩形是否重叠,若重叠,则一个矩形在另一个的左边往左或者上边往上
  7. public boolean overlapsByVertex(Rectangle2D rec2){
  8. if ( this.right_bottom.getX() < rec2.left_top.getX() || rec2.right_bottom.getX() < this.left_top.getX())
  9. return false;
  10. if ( this.left_top.getY() < rec2.right_bottom.getY() || rec2.left_top.getY() < this.right_bottom.getY())
  11. return false;
  12. return true;
  13. }

求凸多边形面积

已知各点坐标,顺时针排列为:$(x_1,y_1),(x_2,y_2)…(x_n,y_n)$

Area=\frac{1}{2}\left |\begin{array}{}
x_1 & y_1 \
x_2 & y_2 \
x_3 & y_3 \
.. & .. \
x_n & y_n
\end{array}\right|=\frac{1}{2}{(x_1y_2+x_2y_3+…+x_ny_1)-(y_1x_2+y_2x_3+…+y_nx_1)}

(二)代数

判断是否是素数

  • 如果在2到根号N之内(闭区间)都没有数整除N,则N是素数。
  • 对于BigInteger,采用实例方法 bigNum.isProbablePrime(int certatinty)
    certainty参数可以以 $1 - 1/2^{certatinty}$的可能性返回结果
    例子见习题10.18

素数分解

  1. public static ArrayList<Integer> primeFactorization(long num){
  2. ArrayList<Integer> list = new ArrayList<>();
  3. //step1: divide num by 2 till num can't be divided by 2
  4. while( num % 2 == 0){
  5. list.add(2);
  6. num /= 2;
  7. }
  8. //step2: i start at 3, increment by 2 when i < sqr(num) ;
  9. // divide num by every i till num can't be divided by i
  10. for(int i = 3; i < Math.sqrt(num); i+=2){
  11. while ( num % i == 0){
  12. list.add(i);
  13. num /= i;
  14. }
  15. }
  16. //The above two steps is to find prime factors more quickly
  17. //step3: corner-case: now num itself is a prime number BUT it's greater than 2
  18. if( num > 2 )
  19. list.add((int)num);
  20. return list;
  21. }

寻找完全平方数

对于整数m,找到整数n使得 m*n是某个数的完全平方

把m解析为最小因数的乘积,出现了奇数次的最小因数各相乘一次,就是n

例如,m= 90 =2 3 3 5, n = 2 5 = 10,m * n = 900 是完全平方数。

求最大公约数

  1. public static int greatestCommonDivisor( int n1,int n2) {
  2. int gcd = 0;
  3. int test = 1;
  4. while (test <= n1 && test <= n2) {
  5. if ( n1%test==0 && n2%test==0)
  6. gcd=test;
  7. test++;
  8. }
  9. return gcd;
  10. }
  11. //不能把循环条件设置成test <= n1/2 && test <= n2/2,这样当n1或n2就是最大公约数时则被忽略了,例如n1=144,n2=48,这种方法求出来的最大公约数是24而不是48

判断是否回文数

  1. public static boolean isPalindrome ( int number ) {
  2. String numString = ""+number;
  3. for ( int i = 0 ; i < (numString.length()/2) ; i++) {
  4. if ( numString.charAt(i) != numString.charAt( numString.length() - i -1 ) ) {
  5. System.out.println("第 "+(i+1)+" 位与第 "+(numString.length() - i )+" 位不同。 ");
  6. return false; //只要发现由不同的位,用return 返回false 并直接跳出方法
  7. }
  8. else
  9. System.out.println("第 "+(i+1)+" 位与第 "+(numString.length() - i )+" 位相同,继续判断");
  10. }
  11. return true;
  12. }

模运算

先除1000再模60;与直接模60000是不一样的

  1. long totalSeconds = System.currentTimeMillis() / 1000;
  2. long totalMinutes = totalSeconds % 60;
  3. long wrongTotalMinutes = System.currentTimeMillis() % 60000

浮点数的表示精度

为什么10.03的double在计算时会变成10.029999999,而10.25可以被精确表达?

这是因为IEEE 754标准下的浮点数使用二进制计数,即0.25=1×2;但0.03则不能用二进制计数法表达。因此754标准所说的表示范围不是一个连续实数集,中间有很多小数无法被精确表达。

如何避免精度损失?

将小数位提到整数部分计算;采用bigDecimal类;

产生随机数

  • 使用 int random = System.out.currentTimeMillis() % 10;产生[0,9] 区间的整数;
  • 使用double random = Math.random();产生 [0.0,1.0)区间的浮点数;

    1. double k = 10 + Math.random()*10; //产生[10,20)的随机浮点数;
    2. int m = 10 + (int)(Math.random()*21); // 产生[10,20]的整数
  • 能否用此方法产生 [10,20]的浮点数? (10,20]的浮点数或整数?

  • 使用 Random类待补充 ```java import java.util.Random; Random random = new Random();

int k = random.nextInt(6) ; // 产生 [0.6)的整数

  1. <a name="9a37f36d"></a>
  2. #### 进制转换
  3. 给定某数值型字符串,返回给定进制数
  4. <a name="2e3fe4d9"></a>
  5. ##### 十进制 到 其他进制
  6. - 用静态方法`String.format`
  7. ```java
  8. return String.format("%x",26);
  9. //-> 1A (字符串)
  • 自定义

其他进制 到 十进制
  • 用包装对象的静态方法 Integer.parseIntInteger.parseDouble

    1. return Integer.parseInt("1A",16);
    2. //-> 26
  • 自定义

    1. //args:String s
    2. //uppercase
    3. String hexString = s.toUpperCase();
    4. //compute
    5. long decResult = 0;
    6. for(int i = 0; i < hexString.length(); i++){
    7. char thisChar = hexString.charAt(i);
    8. if( thisChar>='0'&& thisChar <='9'){
    9. decResult = decResult*16 + (thisChar-'0') ;
    10. }
    11. else
    12. decResult = decResult*16 + (thisChar - 'A' + 10);
    13. }
    14. return decResult;

字符串

Java允许没有引用变量直接使用字符串字面值来调用该实例对象的方法,那么这种没有引用变量的实例对象是如何保存、以及内存回收的呢?

break语句

随机产生0/1值

  1. int i = (int)Math.rint(Math.random());

计算某天星期几

  • 泽勒一致性算法:输入年份、月份、某月的第几天;得到该天是星期几。P97

计算多边形面积

已知各点坐标,计算各个三角形面积再相加:

  1. s = (e1+e2+e3)/2;
  2. area = Math.sqt( s*(s-e1)*(s-e2)*(s-e3) );

正n边形且已知外接圆的半径r:

  1. area = (r*r*n)/( 4*Math.tan(Math.PI/n) );

计算球面上两点距离(最大圆距)

  1. d = 半径 * Math.acos( Math.sin(x1)*Math.sin(x2)+Math.cos(x1)*Math.cos(x2)*Math.cos(y1-y2) );

输入一个字符

一般方法

  1. char c = reader.next().charAt(0);
  2. char c = reader.getLine().charAt(0);
  3. //reader是一个Scanner对象实例

To consume exactly one character you could use:

  1. char c = reader.findInLine(".").charAt(0);

To consume strictly one character you could use:

  1. char c = reader.next(".").charAt(0);

常用字符的ASCII值

  1. 'a'=97; 'A'=65; '0'=48; '9'=57;

十进制转十六进制

  • 自定义方法一
  1. public static String decTohex(int dec) {
  2. char hexChar[] = {'0','1','2','3','4','5','6','7','8','9',
  3. 'A','B','C','D','E','F'};
  4. String hex = "";
  5. if (dec==0)
  6. hex = "0";
  7. while ( dec != 0 ) {
  8. hex += hexChar[ dec%16 ];
  9. dec /= 16;
  10. }
  11. return hex;
  12. }
  • 自定义方法二

    1. public static String decToHex(int dec) {
    2. String hex = "";
    3. int hexDigitValue = -1;
    4. if( dec==0 )
    5. hex = "0";
    6. while(dec!=0) {
    7. hexDigitValue = dec % 16;
    8. if (hexDigitValue >= 0 && hexDigitValue <=9)
    9. hex = (char)(hexDigitValue+'0')+hex;
    10. else
    11. hex = (char)(hexDigitValue-10+'A')+hex;
    12. dec /= 16;
    13. }
    14. return hex;
  • library method

  1. String hex = Integer.toHexString(dec);

字符与数值类型之间的转换

  • 字符=>数字
    字符的Unicode在一个字节范围内,自动隐式转换;超过一个字节须强制显示转换。
  • 数字=>字符
    整型值只能转换其低16位的值对应的Unicode字符;浮点值须先(内部)转为整型再转为字符。

十六进制(字符)转换为十进制

  1. public static int hexChar2Dec(char hexChar) {
  2. if ( hexChar >= 'a' && hexChar <= 'z')
  3. return 10+hexChar-'a';
  4. else return hexChar - '0';
  5. }
  6. //特别注意 “10+hexChar-'a';”

选择流程

注意当判断条件含有某方法语句时,方法会重复运行,导致选择分支无法被选中,例如

  1. outcome = judge();
  2. if (outcome =="Win")
  3. winCount++;
  4. else (outcome =="Lose")
  5. loseCount++;
  6. 而不是以下这样:
  7. if ( judge( )=="Win")
  8. winCount++;
  9. else ( judge( ) =="Lose")
  10. loseCount++;

数组下标的类型是什么

foreach循环

  1. for (double e: mylist){
  2. ....
  3. }
  4. // 对于mylist数组中的每个e元素执行动作

将一连串空格符间隔的输入读取为数组

https://stackoverflow.com/questions/14635136/read-integers-separated-with-whitespace-into-int-array

输入控制:连续输入以空格间断的一组数字,以0为输入作为结束

  1. public static String inputControl() {
  2. Scanner input = new Scanner(System.in);
  3. String userInput = input.next()+" ";
  4. String user = "";
  5. while( !userInput.equals("0 ") ) {
  6. user += userInput;
  7. userInput = input.next() + " ";
  8. }
  9. System.out.println( user );
  10. return user;
  11. }
  12. 输入: 1 2 3 2 1 0
  13. 返回user: "1 2 3 2 1"
  14. // !!注意,循环条件中,字符串不能与0直接相等来比较;并且,注意"0 "有一个空格符;
  15. 也可讲条件改为 while( !userInput.trim().equals("0") )
  16. 类似的,要求以负数作为结束输入的标志,循环条件改为:
  17. while( Integer.parseInt( userInput.trim()) >= 0 )

二分查找务必确保数组是按顺序排列

将两个有序数组合并为一个有序数组:交替比较法,Time O(length 1+length 2)

  1. public static int[] mergeSorted(int nums1[], int nums2[]) {
  2. int length1 = nums1.length, length2 = nums2.length;
  3. int length = length1 + length2;
  4. int index = 0, index1 = 0, index2 = 0;
  5. while( index < length ) {
  6. if ( nums1[index1] > nums2[index2]) {
  7. nums[index] = nums2[index2] ;
  8. index2++;
  9. index++;
  10. }
  11. else {
  12. nums[index] = nums1[index1] ;
  13. index1++;
  14. index++;
  15. }
  16. // if one of this two arrays has been filled,then copy leftover of the other
  17. if ( index2 == length2 ) {
  18. int leftInterval = length - index1 + 2;
  19. for( int j = index; j < leftInterval; j++, index1++) {
  20. nums[j] = nums1[index1];
  21. }
  22. break;
  23. }
  24. if ( index1 == length1 ) {
  25. int leftInterval = length - index2 + 2;
  26. for( int j = index; j < leftInterval ; j++, index2++) {
  27. nums[j] = nums2[index2];
  28. }
  29. break;
  30. }
  31. }
  32. return nums;
  33. }

利用while循环和自增符来简化代码

  1. public static int[] mergeSorted(int nums1[], int nums2[]) {
  2. int length1 = nums1.length, length2 = nums2.length;
  3. int length = length1 + length2;
  4. int [] nums = new int[length];
  5. int index = 0, index1 = 0, index2 = 0;
  6. while (index1 < length1 && index2 < length2)
  7. {
  8. if (nums1[index1] < nums2[index2])
  9. nums[index++] = nums1[index1++];
  10. else
  11. nums[index++] = nums2[index2++];
  12. }
  13. while (index1 < length1)
  14. nums[index++] = nums1[index1++];
  15. // Store remaining elements of second array
  16. while (index2 < length2)
  17. nums[index++] = nums2[index2++];
  18. return nums;
  19. }

分区

  1. public static int[] partitionByElement(int nums[], int thisNum, int thisIndex) {
  2. int length = nums.length;
  3. int[] thisNums = new int[length];
  4. int index = 0, lowIndex = 0, highIndex = length-1;
  5. while( lowIndex < highIndex) {
  6. if(nums[index] < thisNum) {
  7. thisNums[lowIndex++] = nums[index];
  8. }
  9. else if( nums[index] > thisNum ) {
  10. thisNums[highIndex--] = nums[index];
  11. }
  12. else if(nums[index] == thisNum && index != thisIndex) {
  13. thisNums[lowIndex++] = nums[index];
  14. }
  15. index++;
  16. }
  17. thisNums[lowIndex] = thisNum;
  18. return thisNums;
  19. }
  1. public static void partitionByElement(int[] nums, int pivotIndex) {
  2. int pivot = nums[pivotIndex];
  3. int length = nums.length;
  4. //设计两个index位于两端,向pivot靠拢
  5. int highIndex = length - 1;
  6. int lowIndex = 0;
  7. //检查两个index在循环体更新之后是否交叉
  8. while(lowIndex < highIndex){
  9. //找到不比pivot小的、最左边的元素的位置;包括等于pivot的元素或者就是pivot自身元素的位置
  10. for(lowIndex = 0 ; nums[lowIndex] < pivot; lowIndex++);
  11. //找到不比pivot大的、最右边的元素的位置;包括等于pivot的元素或者就是pivot自身元素的位置
  12. for(highIndex = length - 1; nums[highIndex] > pivot; highIndex--);
  13. if (lowIndex < highIndex ) {
  14. int temp = nums[lowIndex];
  15. nums[lowIndex] = nums[highIndex];
  16. nums[highIndex] = temp;
  17. }
  18. }
  19. }
  20. // test case: the last 7 is the pivot
  21. // test case1 {*,*,*,7,*,*,1,*} pass
  22. // test case2 {*,7,*,*,7,1,*} pass

冒泡排序

  1. public static int[] bubbleSort(int[] list) {
  2. int[] arr = new int[list.length];
  3. System.arraycopy(list, 0,arr, 0, list.length);
  4. int n = arr.length;
  5. boolean swapped;
  6. for (int i = 0; i < n-1; i++) {
  7. swapped = false;
  8. //每个第i次外循环后,arr[n-1-i]及其后的元素都是升序排列,
  9. //因为内循环能保证aar[n-i-1]比前面所有元素都大
  10. //例如i=0的内循环完成后,arr[n-1]比前面所有元素都大
  11. for (int j = 0; j < n-i-1; j++) {
  12. if (arr[j] > arr[j+1]) {
  13. int temp = arr[j];
  14. arr[j] = arr[j+1];
  15. arr[j+1] = temp;
  16. swapped = true;
  17. }
  18. }
  19. //如果没有交换过,每个元素都比前一个大,也就是已经排好序
  20. if (swapped == false)
  21. break;
  22. }
  23. return arr;
  24. }
  25. //test case
  26. **i = 0** , j < 6
  27. 1, 5, 4, 3, 9, 2, 1
  28. j = 0 **1, 5**, 4, 3, 9, 2, 1
  29. j = 1 1, **4, 5**, 3, 9, 2, 1
  30. j = 2 1, 4, **3, 5**, 9, 2, 1
  31. j = 3 1, 4, 3, **5, 9,** 2, 1
  32. j = 4 1, 4, 3, 5, **2, 9,** 1
  33. j = 5 1, 4, 3, 5, 2, **1, 9**

选择排序

  1. public static void selectionSort(int[] nums){
  2. for(int i = 0 ; i < nums.length-1; i++){
  3. for(int j = i+1; j < nums.length; j++){
  4. if( nums[i] > nums[j]){
  5. int temp = nums[j];
  6. nums[j] = nums[i];
  7. nums[i] = temp;
  8. }
  9. }
  10. }
  11. }

二分查找

  1. public static int binarySearch( int[] nums, int thisNum){
  2. // the nums array should be sorted
  3. int lowIndex = 0, highIndex = nums.length-1;
  4. int mid = lowIndex;
  5. while(highIndex > lowIndex){
  6. mid = (highIndex + lowIndex) / 2;
  7. if(nums[mid] > thisNum){
  8. highIndex = mid-1; // 为什么要去mid-1 和 mid +1? 因为要确保查不到时highIndex能够大于lowIndex
  9. }
  10. else if(nums[mid] < thisNum){
  11. lowIndex = mid+1;
  12. }
  13. else
  14. return mid;
  15. }
  16. return -lowIndex-1; // 为什么还要减1? 因为lowIndex可能为0,不减1可能误认为nums[0]就是要找的数
  17. }

循环

for-each循环

能够方便的遍历地选中每一个元素,但在循环体操作中不能改变选中的这个元素

字符串

字符串连接:
  • 性能:在多次连接时(例如在循环中)将字符串创建为StringBuilder类的对象,而不是String;这样可以避免多次建立String对象。

二、时间

(一)日期

1. 获取并打印年月日、时分秒

  • 使用 Date 类 ```java Date date = new Date(); //获取当前日期及时间的Date实例 SimpleDateFormat formatter= new SimpleDateFormat(“yyyy-MM-dd ‘at’ HH:mm:ss z”); System.out.println(formatter.format(date));

test:

2020-02-05 at 10:11:33 UTC #

  1. - **注意**如何使用 `SimpleDateFormat` 类的 `format()`方法 格式化打印时间
  2. - 使用 `Calendar`
  3. ```java
  4. Calendar calendar = Calendar.getInstance();//获取当前日期及时间的Calendar实例
  5. SimpleDateFormat formatter= new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z");
  6. System.out.println(formatter.format(calendar.getTime())); //使用该实例的getTime()方法
  • 使用GregorianCalendar类 ```java GregorianCalendar gCal = new GregorianCalendar(); elapsedMillis = System.currentTimeMillis(); gCal.setTimeInMillis(elapsedMillis); int year = gCal.get(GregorianCalendar.YEAR); int month = gCal.get(GregorianCalendar.MONTH); int day = gCal.get(GregorianCalendar.DAY_OF_MONTH); System.out.println( year+” “+(month+1)+” “+day )

//注意:month的0表示一月;

test:

2020 7 30 #

  1. - 使用 `DateTime` API
  2. - `LocalDate`类,只获取日期
  3. ```java
  4. LocalDate date = LocalDate.now(); // 静态方法 now()获取当前日期
  5. DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy");
  6. System.out.println(date.format(formatter));
  7. #test
  8. 05-02-2020
  9. #
  • 注意如何使用 DateTimeFormattter 类的 ofPattern构造方法, format()方法 格式化打印时间
    now()方法还可以指定时区:

    1. LocalDate date = LocalDate.now(ZoneId.of("Europe/Paris"));
  • 其中 ZoneId的获取方式如下:

    1. System.out.println(ZoneId.getAvailableZoneIds());
  • localTime 类,只获取时间

    1. LocalTime time = LocalTime.now();
  • 格式化打印和指定时区的方式与上类似

  • localDateTime

    1. LocalDateTime dateTime = LocalDateTime.now();
  • 指定时区则是 ZonedDateTime

    1. ZonedDateTime dateTime = ZonedDateTime.now();
  • 这两个 DateTime 类的格式化打印方式与上类似

  • 对于整个 DateTimeAPI,可以使用Clock类的实例作为now()方法的参数,进而指定某个时区(系统)下的日期时间 ``` //建立指定时区下的Clock实例 Clock clock = Clock.systemDefaultZone(); 指定系统的时区 Clock clock = Clock.systemUTC(); 指定UTC时区 Clock clock = Clock.system(ZoneId.of(“Europe/Paris”)); //用ZoneId指定时区

//用clock来建立如下四个类的实例 LocalDateTime localDateTime = LocalDateTime.now(clock); LocalTime localTime = LocalTime.now(clock); LocalDate localDate = LocalDate.now(clock); ZonedDateTime zonedDateTime.now(clock);

Instant instant = Instant.now(clock);

  1. - Clock类还可以用实例方法获取当前毫秒数
  2. ```java
  3. System.out.println(clock.millis());
  4. //与如下两个语句相同
  5. System.out.println(System.currentTimeMillis());
  6. System.out.println(date.getTime());
  1. - Clock类还可以用实例方法建立`Instant`类的对象
  1. Instant instant = Instant.now(clock);
  2. //或
  3. Instant instant = clock.instant();
  4. //打印instant的效果:
  5. System.out.println(instant);
  6. #
  7. 2020-04-16T17:05:43.867063Z
  8. #

数值计算

数值表示范围溢出

  1. Line1: BigDecimal thisNum = new BigDecimal(Long.MAX_VALUE+""+1);
  2. Line2: BigDecimal thisNum = new BigDecimal(Long.MAX_VALUE+1+"");

数值计算是从左往右的,Line1中先变为字符串在做字符串的连接;Line2先加1会导致Long数值类型的范围溢出。

抵消错误

当处理一个较大数和一个较小数时,可能会把较小数略去。

例如在计算 $1+\frac{1}{2}+\frac{1}{3}+…+\frac{1}{n}$时,从右到左计算更精确。

零除异常

  1. 0除一个整型数(`int` `long` )会抛出零除异常;
  2. 但是用0(或 0.0)除一个浮点数( `float` `double`)不会有问题,因为 `IEEE 754`标准为浮点数设计了 `NaN` `+0` `-0` `-Infinity` `+Infinity`五个特殊数值,其中 `NaN`则用来表示 零除等浮点计算的结果。

类与对象

为什么要有类和对象?

  1. 程序中,常常会有多组数据产生联系,如果将各组数组抽象成数据域的各个”维度“,将各组数据的联系互动概括为方法域中的各个方法,那么这样的数据域和方法域就抽象成了一个类,而实际的数据及其互动就成了该类的实例,也即对象。
  2. 这样的抽象的好处在于:
  1. 实现了数据之间的紧密耦合,
  2. 实现了数据及其动作的紧密耦合,
    紧密耦合就使得程序更便于设计和维护。

一、类的概念和描述

1. 概念

  1. 类是一种**引用类型**(区别于基本类型数据),意味着该类引用类型的变量都可引用该类的一个实例(对象)。
  2. 也即,引用变量沟通了了类和对象:
  • 引用变量的类型表达“对象属于哪个类”;
  • 引用变量的值表达“对象内容的起始位置”。(初始值为null,调用初始为null的对象内容发生NullPointerException

    1. public class Test{
    2. public static void main(){
    3. java.util.Date[] dates = new java.util.Date[40]; //40个Date对象的数组,但此时只有40个Date类型的引用
    4. System.out.println(dates[0]); //OK!只是打印第一个对象的引用值,并不涉及该对象的使用;
    5. System.out.println(dates[0].toString()); //Error:NullPointerException. 该引用变量没有引用任何对象
    6. }
    7. }
  • 也存在不被引用的对象,创建之后立即使用,称为匿名对象;匿名对象会在随后被垃圾回收(garbage collecting),若要让一个对象被回收,则要更改(引用别的对象 或者 改为null)所有引用该对象的引用变量的值。

2. 类图

+号表示public,-号表示private

下划线表示static

二、类的成员的可变性

  1. 按照是否被所有该类对象共享(都可访问、都可修改)的特点,数据域和方法可各自分为静态和实例两类。
  2. 静态数据域(**静态变量**)保存在公共的内存地址,其更改会涉及到该类所有的对象;
  3. **静态方法**是无需创建类的实例就可调用的方法。
  4. 对于常量而言,一般将其设为静态常量。
  • 静态与实例的权限:

    1. 静态方法和静态数据域不能调用实例方法、不能访问实例数据域,因为它们不属于具体的某个对象;
    2. 实例方法和实例数据域都可访问静态数据域、调用静态方法。

三、类及其成员的可见性:

1. 四种权限

  • 默认类可以被同一包中其他类访问;
  • public表示还可以被任意的,即别的包中的类访问
  • private表示只能被在自己的类中访问;只能修饰类的成员,不能修饰类名。
  • protected

被某类访问,意思是在某类中访问此类的数据域和方法。

因此某类要访问别的类的数据域\方法,必须要考虑类本身和该数据域\方法两个层面的可见性。

如果要防止用户创建类的对象(例如此类的成员都是静态的),可以将构造方法修饰为 private,例如

  1. private Math(){
  2. }

2. 数据域封装

  1. 为了**保护数据域安全**、**便于使用该类的程序维护**,通常将数据域修饰为 `private`,这样在定义该数据域的类以外,都不能对数据域直接修改。
  2. 若要修改,可以设置非 `private`可见性的设置方法(setter)和访问方法(getter)。

四、设置不可变的类和对象

  1. 什么是不可变:一旦创建之后其数据域和方法都不改变。
  2. 怎么才能满足不可变的要求:
  • 所有数据域都是私有的:这样保证数据域在定义类之外都不能访问;
  • 没有设置方法(修改器方法)
  • 没有能够返回指向可变数据域的引用的访问方法

    见P 306:

    • ”返回引用“:在别的类中访问Student类的Date型数据的引用,然后就可以通过该引用值对该数据域做更改。
    • “指向的数据域不能是可变的”:若访问方法指向的是String这种不可变数据域,即便访问到了也无法修改这种数据域;

五、类范围内的变量作用域

1. 回顾:方法中的变量(局部变量)作用域

  1. 方法中定义的变量称为局部变量,同一方法中的局部变量可以同名,但必须位于不同的块`{···}`中,这是因为局部变量是**块作用域**的:
  • 局部变量的作用域大小与声明位置有关
    方法签名中的变量(即参数)在整个方法块中都有效;
    循环块头部的变量在整个循环中都有效;
    循环块体中的变量在单次循环中才有效。
  • 局部变量的初始化位置
    初始化必须在一俩 该变量的方法 和 其他变量初始化 之前。

2. 类的变量(数据域)的作用域

  1. 类的变量即数据域,包含实例变量和静态变量。
  • 类的变量的作用域大小始终是整个类,与声明位置无关
  • 类的变量初始化位置
    初始化可以在依赖该变量的方法之后,但必须在依赖此变量的其他变量初始化之前。

3. 局部变量与类数据域的优先级

  1. 由于类变量作用于整个类,因此不允许同名的类变量;
  2. 但允许在方法中局部变量与类变量同名,这是因为在方法中,局部变量优先于类变量,类变量被**隐藏**(而不是跟随局部变量改动)。
  3. 但注意:此时局部变量的优先仍然是基于块的。加入局部变量在某方法的某块中声明,那么在”方法内 块外“的位置,该局部变量无效,此时是类变量有效。见复习题9.13.1
  4. 一般,为了便于阅读,会将“方法的参数”这种局部变量与类变量同名;但别的情况下,要避免同名的局部变量出现。

六、this引用

  1. `this`是用来引用**对象**自身的引用名。为了便于阅读,可以用 `this`显式表明数据域或方法所属于哪个对象。**注意**:当数据域或方法是静态的时候,最好依然是使用类名`Circle.angle`
  2. 但有两种情况必须要使用 `this`
  • 在类的方法(包括构造方法)中访问数据域时,若参数与类变量同名,会导致要被赋值的类变量会被隐藏
  1. public class Circle{
  2. private double radius = 1;
  3. private static double angle;
  4. public void setRadius(double radius){
  5. radius = radius;
  6. }
  7. }
  1. 因此用`this`来引用**对象**自身,把构造方法的赋值语句改为 `this.radius = radius;`
  • 在构造方法中调用别的(重载)构造方法
    此时 this(arg-list);语句应放在构造方法体的首行;

类与类之间的关系:


  • public声明下的方法(含构造方法)称为公共方法,可以从不同包的其它类访问。
  • private只能修饰方法和数据域,表示只能在自己类中访问

类与对象之间的关系:
  • 引用:
    • 引用变量的类型表达“对象属于哪个类”;
    • 引用变量的值表达“对象内容的起始位置”。(初始值为null,调用初始为null的对象内容发生NullPointerException)
      1. public class Test{
      2. public static void main(){
      3. java.util.Date[] dates = new java.util.Date[40]; //40个Date对象的数组,但此时只有40个Date类型的引用
      4. System.out.println(dates[0]); //OK!只是打印第一个对象的引用值,并不涉及该对象的使用;
      5. System.out.println(dates[0].toString()); //Error:NullPointerException. 该引用变量没有引用任何对象
      6. }
      7. }

七、类之间的关系

UML图的表示

以Student类和Course类为例,

  • 图标:关联用一般实线连接,聚集在实线的被聚集一端加空菱形,组合在某一端加实心菱形。
  • 数量表示:在类旁可以加上数量关系
  • 角色表示:可以在类旁加上角色名称,动作名称以及表示该动作方向的箭头。

1. 关联

2. 聚集 与 组合

  1. 聚集表示了“类A有类B”的关系,例如StudentAddress:一个学生可以有多个地址,但一个地址只能对应一个学生;
  2. 组合表示聚集的基础上,被聚集对象不能单独存在的关系:例如StudentName,二者一一对应,可以说前者聚集于后者,或者后者聚集于前者,但Name类不能单独存在。
  3. 一般可统称为组合。

3. 继承

八、类的编译

  • 类都能被编译,但有main方法的类(主类)才能被解释器运行
  • public声明下的类称为公共类,可以从不同包的其他类访问。
    • 一个Java文件中只能有一个公共类,文件内的类可以互相访问,这里的公共类不包括pubic的内部类
    • Java文件名必须与公共类同名
    • Java文件编译后形成多个.class文件,涉及到的Java文件外的公共类也会被编译。

时间

使用 GregorianCalendar类来打印年月日

  1. GregorianCalendar d1 = new GregorianCalendar();
  2. System.out.println(d1.get(GregorianCalendar.YEAR)+" "+d1.get(GregorianCalendar.MONTH)+
  3. " "+d1.get(GregorianCalendar.DAY_OF_MONTH) );

常用类的笔记

(1)BigInteger和BigDecimal类

  • 算术方法本身不能单独作为可执行语句,因为该方法是返回一个对象,而不是在原对象上更改
    因此 step.add(BigDecimal.ONE); 不能执行,而是 step = step.add(BigDecimal.ONE);
    因此 BigDecimal nowNum = num.add(step);执行后 num所指向的对象没有改变

(2)String类

  • 快速创建一个给定长度的重复字符的字符串

    1. String s ="*".repeat(x.length());
  • 给定正则匹配模式,得到匹配的字串
    三种匹配程度:

    • matches完全匹配:整个字符序列完全匹配成功,匹配成功了不再继续匹配。
    • lookingAt首位开始部分匹配:总是从第一个字符开始进行匹配,匹配成功了不再继续匹配。匹配失败了,也不继续匹配。
    • find灵活部分匹配:从当前位置开始匹配,找到一个匹配的子串,将移动下次匹配的位置。 ```java String s1 = “Chapter303..Chapter3_03..”; String regex1 = “Chapter[\d][\d]{2,}”; Pattern pattern1 = Pattern.compile(regex1); Matcher matcher1 = pattern1.matcher(s1); while(matcher1.find()) System.out.println(“find() Ok:”+matcher1.group()); while(matcher1.matches()) System.out.println(“matches() Ok:”+matcher1.group()); while(matcher1.lookingAt()) System.out.println(“lookingAt() Ok:”+matcher1.lookingAt());

//输出如下 find() Ok:Chapter3_03 find() Ok:Chapter3_03 lookingAt() Ok:Chapter3_03 lookingAt() Ok:Chapter3_03 lookingAt() Ok:Chapter3_03 lookingAt() Ok:Chapter3_03 //…(lookingAt的死循环)

  1. - 如何多次实现字符串的部分替换<br />由于字符串是不可变的,要借助于`StringBuilder` `StringBuffer`,每次替换字符串时将引用原字符串对应的`StringBuilder`对象改为引用一个新的由新字符串创建的 `StringBuilder`对象。
  2. ```java
  3. String name = "Chapter1_1/Chapter1_2/Chapter1_1.txt";
  4. StringBuilder newName = new StringBuilder(name);
  5. while(continueModify){
  6. String mod = modify(name);
  7. newName = new StringBuilder(mod);
  8. }
  9. return newName;

(3)Scanner 和 PrintWriter类

  • 这两类对象的方法在循环中要特别注意重复抛出异常导致死循环:
  1. //用InputMismatchException来测试
  2. public static void Test(){
  3. //随便定义一个5大小的数组
  4. int[] nums = {0,1,2,3,4};
  5. //设计循环
  6. boolean inputAgain = true;
  7. while (inputAgain) {
  8. try {
  9. //**如果把Scanner这个user对象放在循环之外,当user的nextInt()方法抛出一次异常,在以后的每次循环中的try{}块,user对象都会抛出该异常。因为该对象未更新。
  10. Scanner user = new Scanner(System.in);
  11. System.out.println(nums[user.nextInt()]);
  12. }
  13. catch (InputMismatchException ex) {
  14. System.out.println("input a index(integer)");
  15. inputAgain = true;
  16. }
  17. }
  18. }
  • 建立一个文件的printWriter对象后,只要这个对象对应的文件是真实存在的( file.exists() == true ),文本原内容会被擦除
  • Scanner类的实例方法hasNext():当文本中只有空白字符(回车符、换行符、换页符、Tab符)时,返回false.

基于标记和基于行的读取方法
  • 基于标记的读取方法规则,以 input.next() 为例:
    默认' ' 空格字符为分隔符。该方法会首先跳过任意数量的分隔符(甚至没有分隔符),然后从第一个非分隔符的字符开始读取,一直到以第一个分隔符作为结束,称为标记。方法所返回的内容会去除标记中的这第一个分隔符。接着开始第二次读取与返回。
    对于多行文本(包括键盘的多行输入)来说,该方法会忽略换行符

    1. Scanner input = new Scanner(System.in);
    2. String s1 = input.next();
    3. String s2 = input.next();
    4. System.out.print(s1+"!"+s2);
    5. ------------------------------如下以·代表输入中的空格分隔符
    6. ··s1··
    7. ··s2
    8. >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>输出
    9. s1!s2
    10. //所输入文本相当于 ··s1····s2,s1后面两空格作为s2前的“任意数量分隔符”被跳过了
  • 基于行的读取方法规则,以 input.nextLine()为例
    该方法的读取由当前位置开始,由换行符结束。所返回的内容不包含作为结束的此换行符。

    1. Scanner input = new Scanner(System.in);
    2. String s1 = input.next();
    3. String s2 = input.nextLine();
    4. System.out.print(s1+"!"+s2);
    5. ------------------------------如下以·代表输入中的空格分隔符
    6. ··s1··
    7. (··s2)来不及输入第二行
    8. >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>输出
    9. s1!··
    10. //当输入第一行后按下回车,输入文本相当于··s1··'\n'。nextLine()开始读取时“当前位置”就在s1后的第一个空格符,一直到按下回车。
  • 因此不应在基于标记的输入后使用基于行的输入,不管这两次输入间隔了多少其他代码。

(4)File类

1. 目录操作

  • mkdir()方法要保证父目录是存在的,否则既不会输出true也不会输出 false;若不管父目录存在与否,用 mkdirs()方法。
  • isFile()方法判断为 true 只需要所给路径是在系统中是真实存在的:
    • 可以识别父级目录甚至其他系统盘,例如 c:/。(在任何目录中, /不是必须但对于系统盘 :是必须的)
    • 也可以识别子目录,例如 Chapter
    • 表示当前目录,用 .
  • 在保持目录下不变的情况下,也可以用thisDir.reNameTo(File destDir)方法重命名该目录;但不能通过改文件名重设路径来改变目录名。
  • File[] files = dir.listFiles();在对files的使用中可能会产生 NullPointerException。这是因为dir可能不是一个目录型 File对象,或者在使用时可能产生IO异常。一般解决办法是用 if(files == null)排除,目录为空不是files == null,一个大小为空的 File[]

2. 文件操作

  • thisFile.reNameTo(File destFile)方法需要 thisFile对象必须真实存在(即建立这个对象所用的 Path 是真实正确的);而对于 dest对象,它所代表的 path可以带别的目录(即移动此文件),也可以更改文件属性名;但是当该目录下有同名同属性文件则重命名失败。

异常

异常处理的结构,即:异常声明+异常处理(异常抛出+异常捕获+尾部处理)

  1. public class Test {
  2. public static void main(String[] args) throws Exception {
  3. try {
  4. method();
  5. System.out.println("After the method call:");
  6. }
  7. catch (RuntimeException ex){
  8. System.out.println("RuntimeException in main");
  9. }
  10. catch (Exception ex){
  11. System.out.println("Exception in main");
  12. }
  13. }
  14. static void method() throws Exception{
  15. try{
  16. String s ="abc";
  17. System.out.println(s.charAt(3));
  18. System.out.println("ah ha");
  19. }
  20. catch (RuntimeException ex){
  21. System.out.println("RuntimeException in method");
  22. }
  23. catch (Exception ex){
  24. System.out.println("Exception in method");
  25. }
  26. finally {
  27. System.out.println("Finally");
  28. }
  29. }
  30. }

异常的种类

Throwable类派生出 Error(系统错误)类 和 Exception 类;而 RuntimeException类继承自 Exception 类。这三类异常都发生在系统运行时;而 ErrorRuntimeException称为 免检异常Exception及其子类称为必检异常

异常声明

  1. 若某方法可能会产生异常,就会在方法头声明该异常类 其父异常类;对于只可能产生免检异常的,不必显式声明。
  2. 若异常在某方法中抛出但未被处理 处理了但未被捕获,异常将传递给调用该方法的方法;因此,调用该方法的方法也应做异常声明。
  3. 若异常在父类方法中未作异常声明,则当子类在可继承该方法甚至重写、重载时也不能作异常声明。

异常处理

  • try{} catch{} finally{}语句块顺序构成,且语句块之间不能插入其他语句。
  • try块是为了显示告知编译器哪些语句会抛出异常,但是当这些语句仅抛出免检异常,可以省去 try块,编译器依然可以自动识别出是哪种异常,然后创建该类异常并抛出给 catch块们匹配。
  • catch块的设置要注意,子类异常在前父类异常在后,避免异常捕获(即异常对象参数的匹配)中出现歧义,导致错误。
  • finally块和 catch块至少要有一个,作为 try块的handler
  1. try{
  2. statement1;
  3. statement2;
  4. }
  5. catch(RuntimeException ex){
  6. handle1_1;
  7. handle1_2;
  8. }
  9. catch(Exception ex){
  10. handle2_1;
  11. throw ex;
  12. handle2_2;
  13. }
  14. finally{
  15. endOperation;
  16. }
  17. restStatemnt;

执行顺序

  • 没有出现异常
    依次执行 try块语句,若没有抛出异常,直接执行 fianally块及余下语句。
  • 出现异常,并捕获
    依次执行 try块语句,当 statement1抛出异常(可能自身抛出,也可能所调用的方法抛出),会立即匹配 catch块,之后都不再执行 try块余下语句;
    而此时例如被第二个 catch块捕获并执行 handle2_1handle 2,最后再执行 fianally块 及余下语句。
    此时可以抛出异常
    • 可抛出的异常类型(原类型 Exception):
      • 可再抛出捕获的这个异常throw ex
      • 也可重新抛出同类的异常 throw new Exception("exception message",ex),第二个参数 ex表示此异常与ex是链式关系,若没有这个参数就是独立的新的异常对象
      • 或者重新抛出其派生类的异常 throw new RuntimeException("exception mesage"),此时不能设置ex参数,即不能与原异常构成链式关系
    • handle 2被跳过,直接执行 finally块,余下语句也被跳过,直接返回到调用该方法的方法。
    • catch块 只能抛出捕获的这个异常,不能创建某个异常并抛出。
  • 出现异常,未捕获
    依次执行 try块语句,当 statement1抛出异常,但没有匹配任何 catch块,也会执行 fianally块,之后会省略余下语句直接将异常传递给本方法的调用者,一直到 main方法,若最后仍未被捕获,会导致系统中断。
    可见:
    1. try语句块中,抛出异常的语句后余下的都不执行;
    2. 无论如何,即使在其之前有return语句,finally块都会执行;
    3. 当且仅当”出现异常但未捕获“ 或 “捕获并继续抛给所调用方法”时,catch-finally后的语句会被省略

正则表达式

Trick

  • 判断数组列表或字符串中某元素的唯一性:
    用`if( indexOf(e) == lastIndexOf(e) )