计算机编程基础 人机交互方式
图形化界面(GUI),简单直观,使用者易于接受且容易上手。
命令行方式(CLI),需要有一个控制台,输入特定的指令,让计算机完成一些操作。
常用的一些命令行指令
dir: 列出当前目录下的文件以及文件夹
md: 创建目录(文件夹)
rd: 删除目录(需要保证目录是空的)
cd: 进入指定目录
cd..:退回到上一级目录
cd\: 退回到根目录
del: 删除文件
exit:退出dos命令行
del后面也可以跟文件夹名,作用是把该文件夹内所有文件都删除。
Java的两种核心机制
Java虚拟机(JVM)
Ⅰ.JVM是一个虚拟的计算机,具有指令集并使用不同的存储区域。负责执行指令,管理数据、内存、寄存器。 Ⅱ.对于不同平台,有不同的虚拟机。 Ⅲ.只有某平台提供了对应的Java虚拟机,Java程序才能在此平台运行。 Ⅳ.Java虚拟机机制屏蔽了底层运行平台的差别,实现了“一次编译,到处运行”。
垃圾回收
Ⅰ.不再使用的内存空间应回收,在C/C++等语言中,由程序员负责回收无用内存。 Ⅱ.垃圾回收在Java程序运行过程中自动进行,程序员无法精确控制和干预。 Ⅲ.还是有可能出现内存泄露和内存溢出的问题。
JDK,JRE
JDK(Java Decelopment Kit)Java开发工具包
JDK是提供给Java开发人员使用的,其中包含了Java的开发工具,也包括了JRE,所以安装了JDK就不用单独安装JRE了。
JRE(Java Runtime Environment)Java运行环境
包括Java虚拟机和Java程序所需的核心类库等,如果想要运行一个开发好的Java程序,计算机只需要安装JRE即可
了解Java 注释
单行注释
1 2 3 4 5 6 Class Hello{ public static void main (String args[]) { System.out.println("Hello, World!" ); } }
多行注释
1 2 3 4 5 6 7 8 9 Class Hello{ public static void main (String args[]) { System.out.println("Hello, World!" ); } }
文档注释(Java特有)
1 2 3 4 5 6 7 8 9 10 Class Hello{ public static void main (String args[]) { System.out.println("Hello, World!" ); } }
Java基本语法 关键字与保留字
用于定义数据类型的关键字
class
interface
enum
byte
short
int
long
float
double
char
boolean
void
用于定义流程控制的关键字
if
else
switch
case
default
while
do
for
break
continue
return
用于定义访问权限修饰符的关键字
用于定义类、函数、变量修饰符的关键字
abstract
final
static
synchronized
用于定义类与类之间的关系
用于定义建立实例及引用实例,判断实例的关键字
new
this
super
instanceof
用于异常处理的关键字
try
catch
finally
throw
throws
用于包的关键字
其他修饰符关键字
native
strictfp
transient
volatile
assert
用于定义数据类型值的字面值
保留字:现有Java版本尚未使用,但是以后的版本可能会作为关键字使用的,命名标识符时要避免使用这些保留字。 goto、const
Java中级名称命名规范
包名:多单词组成时所有字母都小写:xxxyyyzzz
类名、接口名:多单词组成时,所有单词的首字母大写:XxxYyyZzz
变量名、方法名:多单词组成时,第一个单词首字母小写,第二个单词开始每个单词首字母发泄:xxxYyyZzz
常量名:所有字母都大写。多单词组成时用下划线连接:XXX_YYY_ZZZ
Java的几种数据类型
变量
整型:byte(1字节=8bit),short(2字节),int(4字节),long(8字节)
1 2 3 4 5 6 7 8 9 10 Class Hello{ public static void main (String args[]) { long a = 151234L ; System.out.println("Hello, World!" ); } }
浮点型:float(4字节)、double(8字节)
1 2 3 4 5 6 7 8 9 10 Class Hello{ public static void main (String args[]) { double a = 15.1 ; float f1 = 12.3f ; System.out.println("Hello, World!" ); } }
字符型:char(2字节)
1 2 3 4 5 6 7 8 9 10 11 12 13 Class Hello{ public static void main (String args[]) { char c1 = 'a' ; char c2 = '\n' ; char c3 = '\u0043' ; System.out.println("Hello, World!" ); } }
布尔型:boolean(1字节)
自动类型提升和强制类型转换
自动类型提升
当容量小的数据类型的变量与容量大的数据类型的变量做运算时,结果自动提升为容量大的类型。 byte 、 short 、 char -> int -> long -> float -> double 特别的,当byte、char、short三种类型的变量做运算时(包括与自身相同的类型),结果为int型。 变量与整形、浮点型常量做运算时,整型常量默认为int型,浮点型常量默认为double型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class VariableTest { public static void main (String args[]) { byte b1 = 2 ; int i1 = 129 ; int i2 = b1 + i1; long l1 = b1 + i1; System.out.println(i2); float f = b1 + i1; System.out.println(f); short s1 = 123 ; double d1 = s1; System.out.println(d1); char c1 = 'a' ; int i3 = 10 ; int i4 = c1 + i3; System.out.println(i4); short s2 = 10 ; byte b2 = 10 ; } }
强制类型转换
即自动类型提升运算的逆运算 需要使用强转符:() 注意点:强制类型转换可能导致精度损失
1 2 3 4 5 6 7 8 class VariableTest { public static void main (String args[]) { double d1 = 12.3 ; int i1 = (int )d1; System.out.println(i1); } }
String类型变量的使用
String不是 基本数据类型,属于引用数据类型
声明String类型变量时,使用一对双引号:””
String可以和8种基本数据类型做运算,且运算只能是连接运算:+,运算的结果仍是String类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class StringTest { public static void main (String args[]) { String s1 = "Hello World" ; System.out.println(s1); int number = 1001 ; String numberStr = "学号:" ; String info = numberStr + number; boolean b1 = true ; String info2 = info + b1; System.out.println(info2); System.out.println(info); char c = 'a' ; int num = 10 ; String str = "Hello" ; System.out.println(c + num + str); System.out.println(c + str + num); System.out.println(c + (num + str)); System.out.println((c + num) + str); System.out.println(str + num + c); System.out.println("* *" ); System.out.println('*' +"\t" +'*' ); } }
运行结果:
四种进制
二进制(binary):以0b或者0B开头
十进制(decimal)
八进制(octal):以数字0开头
十六进制(hex):以0x或者0X开头
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class System1 { public static void main (String[] args) { int num1 = 0b110 ; int num2 = 110 ; int num3 = 0127 ; int num4 = 0x110A ; System.out.println("num1 = " +num1); System.out.println("num2 = " +num2); System.out.println("num3 = " +num3); System.out.println("num4 = " +num4); } }
补充组原知识:原码、反码、补码
运算符 算术运算符
取模运算的结果正负性与被模数保持一致 逻辑运算符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 package logic;public class LogicTest { public static void main (String[] args) { boolean b1 = true ; b1 = false ; int num1 = 10 ; if (b1 & (num1++ > 0 )) { System.out.println("我现在在北京" ); } else { System.out.println("我现在在南京" ); } System.out.println("num1 = " + num1); boolean b2 = true ; b2 = false ; int num2 = 10 ; if (b2 && (num2++ > 0 )) { System.out.println("我现在在北京" ); } else { System.out.println("我现在在南京" ); } System.out.println("num2 = " + num2); boolean b3 = false ; b3 = true ; int num3 = 10 ; if (b3 | (num3++ >0 )) { System.out.println("我现在在北京" ); } else { System.out.println("我现在在南京" ); } System.out.println("num3 = " + num3); boolean b4 = false ; b4 = true ; int num4 = 10 ; if (b4 || (num4++ >0 )) { System.out.println("我现在在北京" ); } else { System.out.println("我现在在南京" ); } System.out.println("num4 = " + num4); } }
位运算符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package bit_operator;public class Bit_operator { public static void main (String[] args) { int i = 21 ; System.out.println("i << 2 :" + (i << 2 )); System.out.println("i << 3 :" + (i << 3 )); int m = 12 ; int n = 5 ; System.out.println("m & n :" + (m & n)); System.out.println("m | n :" + (m | n)); System.out.println("m ^ n :" + (m ^ n)); } }
运算符的优先级
Scanner类的使用 具体的实现步骤: 1.导包:import java.util.Scanner; 2.Scanner的实例化 3.调用Scanner类的相关方法,来获取指定类型的变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package scanner;import java.util.Scanner;public class Scanner1 { public static void main (String[] args) { Scanner scan = new Scanner (System.in); int num = scan.nextInt(); System.out.println(num); } }
分支结构(if-else, switch-case) 注意点:switch中的表达式,只能是如下的六种数据类型之一: byte, short, char, int, 枚举类型, String
循环结构(for, do while, while) 嵌套循环的应用:打印出100以内所有的质数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package cycle1;public class Cycle { public static void main (String[] args) { boolean isFlag = true ; for (int i=2 ;i<=100 ;i++) { for (int j=2 ;j<i;j++) { if (i%j==0 ) { isFlag = false ; break ; } } if (isFlag==true ) { System.out.println(i); } isFlag = true ; } } }
对以上算法进行有优化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public static void main (String[] args) { long start = System.currentTimeMillis(); boolean isFlag = true ; label:for (int i=2 ;i<=10000 ;i++) { for (int j=2 ;j<=Math.sqrt(i);j++) { if (i%j==0 ) { isFlag = false ; break label; } } if (isFlag==true ) { System.out.println(i); } isFlag = true ; } long end = System.currentTimeMillis(); System.out.println(end-start); }
小项目一-家庭收支管理系统
AccountController.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 package accountcon;public class AccountController { public static void main (String[] args) { boolean isFlag = true ; String details = "" ; int balance = 10000 ; while (isFlag) { System.out.println("----------家庭收支记账软件----------\n" ); System.out.println(" 1.收支明细" ); System.out.println(" 2.登记收入" ); System.out.println(" 3.登记支出" ); System.out.println(" 4.退 出\n" ); System.out.println(" 请输入1-4来执行对应的功能" ); char selection = Utility.readMenuSelection(); switch (selection) { case '1' : System.out.println("1.收支明细" ); System.out.println("----------家庭收支记账软件----------\n" ); System.out.println("收支\t账户金额\t收支金额\t说 明\n" ); System.out.println(details); System.out.println("-----------------------------------\n" ); continue ; case '2' : System.out.println("本次收入金额:" ); int money = Utility.readNumber(); System.out.println("本次收入说明:" ); String info = Utility.readString(); balance += money; details += "收入\t" + balance + "\t+" + money + "\t" + info + "\n" ; System.out.println("--------------登记完成--------------\n" ); continue ; case '3' : System.out.println("本次支出金额:" ); int moneyout = Utility.readNumber(); System.out.println("本次支出说明:" ); String infoout = Utility.readString(); if (balance >= moneyout) { balance -= moneyout; } else { System.out.println("支付失败" ); } details += "支出\t" + balance + "\t-" + moneyout + "\t" + infoout + "\n" ; System.out.println("--------------登记完成--------------\\n" ); continue ; case '4' : System.out.println("确定要退出吗(Y/N)\n" ); char isExit = Utility.readConfirmSelection(); if (isExit == 'Y' ) { isFlag = false ; } break ; } } } }
Utility.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 package accountcon; import java.util.Scanner;public class Utility { private static Scanner scanner = new Scanner (System.in); public static char readMenuSelection () { char c; for (; ; ) { String str = readKeyBoard(1 ); c = str.charAt(0 ); if (c != '1' && c != '2' && c != '3' && c != '4' ) { System.out.print("选择错误,请重新输入:" ); } else break ; } return c; } public static int readNumber () { int n; for (; ; ) { String str = readKeyBoard(4 ); try { n = Integer.parseInt(str); break ; } catch (NumberFormatException e) { System.out.print("数字输入错误,请重新输入:" ); } } return n; } public static String readString () { String str = readKeyBoard(8 ); return str; } public static char readConfirmSelection () { char c; for (; ; ) { String str = readKeyBoard(1 ).toUpperCase(); c = str.charAt(0 ); if (c == 'Y' || c == 'N' ) { break ; } else { System.out.print("选择错误,请重新输入:" ); } } return c; } private static String readKeyBoard (int limit) { String line = "" ; while (scanner.hasNext()) { line = scanner.nextLine(); if (line.length() < 1 || line.length() > limit) { System.out.print("输入长度(不大于" + limit + ")错误,请重新输入:" ); continue ; } break ; } return line; } }
数组 数组属于引用数据类型,所以需要用new进行实例化,其元素可以是基本数据类型,也可以是引用数据类型。 创建数组对象会在内存中开辟一块连续的内存空间,而数组名引用的是这块连续空间的首地址。 数组的长度一旦确定,就不可以更改。
1 2 3 4 5 6 7 int [] arr1 = new int [3 ];int [] arr2 = new int []{1 ,2 ,3 };String arr3 = new String [3 ];
二维数组 对于二维数组,可以简单理解为arr1作为另一个数组arr2的数组元素而存在。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int [][] arr = new int arr[11 ][3 ];int [] arr1[] = new int arr1[11 ][3 ];int arr2[][] = new int arr2[11 ][3 ];int [][] arr3 = new int arr3[][]{{1 ,2 ,3 },{2 ,3 ,6 },{7 ,8 ,9 }};for (int i=0 ;i<arr.lenght;i++){ for (int j=0 ;j<arr[i].length;j++){ System.out.println(arr[i][j]+" " ); } } System.out.println(arr); System.out.println(arr[1 ]);
数组操作 杨辉三角 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class YangHui { public static void main (String[] args) { int [][] arr = new int [10 ][]; for (int i=1 ;i<=arr.length;i++) { arr[i-1 ]=new int [i]; } for (int i=0 ;i<arr.length;i++) { arr[i][0 ]=1 ; arr[i][i]=1 ; for (int j=0 ;j<=i;j++) { if (i>1 &&j>0 &&j<i) { arr[i][j]=arr[i-1 ][j-1 ]+arr[i-1 ][j]; } System.out.print(arr[i][j]+" " ); } System.out.println(); } } }
运行结果
二分搜索 二分搜索相比于顺序查找,效率更高,但是要求数组中的元素必须有序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package com.geo.exer2;public class BinarySearchTest { public static void main (String[] args) { int [] arr = new int []{2 , 4 , 5 , 8 , 12 , 15 , 19 , 26 , 37 , 49 , 51 , 66 , 89 , 100 }; int target = 5 ; int head = 0 ; int end = arr.length - 1 ; boolean flag = false ; while (head <= end) { int middle = (head + end) / 2 ; if (arr[middle] == target) { flag = true ; System.out.println("找到了" + target + ",在第" + (middle + 1 ) + "位上" ); break ; } else if (arr[middle] < target) { head = middle + 1 ; } else if (arr[middle] > target) { end = middle - 1 ; } } if (!flag) { System.out.println("该数组中不存在" + target); } } }
数组的一些工具类
ArraysUtils.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 package com.geo.ArrayUtils;import java.util.Arrays;public class ArrayUtils { public static void main (String[] args) { int [] arr1 = new int []{1 , 2 , 3 , 4 , 5 }; int [] arr2 = new int []{1 , 2 , 3 , 4 , 5 }; System.out.println(arr1 == arr2); boolean isEquals = Arrays.equals(arr1, arr2); System.out.println(isEquals); System.out.println(arr1); System.out.println(Arrays.toString(arr1)); Arrays.fill(arr1, 10 ); System.out.println(Arrays.toString(arr1)); int [] arr3 = new int []{1 , 6 , 5 , 3 , 9 , 11 , 54 , 43 , 99 }; Arrays.sort(arr3); System.out.println(Arrays.toString(arr3)); int index = Arrays.binarySearch(arr3, 5 ); System.out.println(index); int arr5[][] = new int [3 ][]; System.out.println(arr5[0 ][1 ]); } }
面向对象编程
学习面向对象内容的三条主线
Java类及类的成员:(重点)属性、方法、构造器;(熟悉)代码块、内部类 面向对象的特征:封装、继承、多态、(抽象) 其他关键字的使用:this、super、package、import、static、final、interface、abstract等
面向过程的程序设计思想
关注的焦点是过程:过程就是操作数据的步骤,如果某个过程的实现代码重复出现,那么就可以把这个过程抽取为一个函数,这样就可以大大简化代码,便于维护。 典型的语言:C语言 代码结构:以函数为组织单位 是一种“执行者思维”,适合解决简单问题。扩展能力差、后期维护难度较大。
面向对象的程序设计思想
关注的焦点是类:在计算机程序设计过程中,参照现实中事物,将事物的属性特征、行为特征抽象出来,用类来表示。 典型的语言:Java、C#、C++、Python、Ruby和PHP等 代码结构:以“类”为组织单位,每种事物都具备自己的属性和方法。 是一种“设计者思维”,适合解决复杂问题。代码扩展性强、可维护性高。
类和对象的概述 类(Class)和对象(Object)是面向对象的核心概念。
1.什么是类:具有相同特征额事物的抽象描述,是抽象的、概念上的定义。 2.什么是对象:实际存在的该类事物 每个个体,是具体的,因而也被成为实例(instance)。
类的声明和使用 设计类,实际上就是设计类的成员。 Class Person{
}
类的内部成员一、二 成员之一:属性、成员变量、field(字段、域) 成员之二:(成员)方法、函数、method
类的实例化剖析,内存解析 1.栈(Stack):方法内定义的变量,存储在栈中。 2.堆(Heap):new出来的结构(数组的实体、对象的实体)。包括对象中的属性。 3.方法区(method area):存放类的模板。
局部变量和成员变量 不同点: 1.声明的位置不同 属性:声明在类内,方法外的变量 局部变量:声明方法、构造器内部的变量 2.在内存中分配的位置不同 属性:随着对象的创建,存储在堆空间当中 局部变量:存储在栈空间中 3.是否可以使用权限修饰符进行修饰 属性:Public、Private、缺省、Protected 局部变量:不能使用任何的权限修饰符进行修饰 4.是否有默认初始化值 属性:都有默认初始化值 局部变量:都没有默认初始化值
对象数组的使用
StudentTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package com.Learning04.ObjArr;public class StudentTest { public static void main (String[] args) { Student[] student = new Student [20 ]; for (int i = 0 ; i < student.length; i++) { student[i] = new Student (); student[i].number = i + 1 ; student[i].state = (int ) (Math.random() * 6 + 1 ); student[i].score = (int ) (Math.random() * 101 ); } for (int i = 0 ; i < student.length; i++) { if (student[i].state == 3 ) { Student stu = student[i]; System.out.println(stu.number); } } } }
Student.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.Learning04.ObjArr;public class Student { int number; int state; int score; }
方法的重载 概念及特点
在同一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可。
参数列表不同,意味着参数个数或参数类型的不同。
满足这样特征的多个方法,彼此之间构成方法的重载。
方法的重载与形参的名、权限修饰符、返回值类型都没有关系。
在同一个类中不允许定义两个相同的方法
overload.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package com.Learning05.method_more;public class overload { public static void main (String[] args) { overload over = new overload (); over.add(1 , "33" ); } public void add (int i, int j) { } public void add (int i, int j, int k) { } public void add (String s1, String s2) { } public void add (int i, String s) { } public void add (String s, int i) { } }
可变个数形参的方法
使用场景:可变参数使用类型,在调用方法时,可能会出现形参的类型是确定的,但是参数的个数不确定。此时就可以使用可变个数形参。
格式(参数类型 … 参数名)
可变个数形参的方法在调用时,针对于可变的形参赋的实参个数可以为0个、1个或多个。 可变个数形参的方法与同一个类中,同名的方法之间可以构成重载,但是优先级最低。 特例:可变个数形参的方法与同一个类中,同名的参数为数组的方法不构成重载,会报错。
可变个数形参必须声明在参数列表的最后。
递归 递归实际上就是在函数体内部再次调用自己,效率不高,能使用循环迭代的尽量使用循环。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package com.Learning05.recursion.exer1;public class RecursionExer1 { public static void main (String[] args) { RecursionExer1 res = new RecursionExer1 (); System.out.println(res.f(10 )); } public int f (int n) { if (n == 20 ) { return 1 ; } else if (n == 21 ) { return 4 ; } else { return f(n + 2 ) - 2 * f(n + 1 ); } } }
面向对象的特征之一:封装性
为什么需要封装性?
高内聚:类的内部数据操作细节自己完成,不允许外部干涉。 低耦合:仅暴露少量的方法给外部使用,尽量方便外部调用。
通俗的说就是把该暴露的暴露出来,该隐藏的隐藏起来。
如何实现数据封装?
使用权限修饰符:private、缺省、protected、public
四种权限修饰符的权限如下所示:
其中,类只能使用public、缺省修饰 类的内部成员可以使用四种修饰符修饰
封装性的体现:
私有化(private)类的属性,提供公共(public)的get和set方法,对此属性进行获取或修改。
将类中不需要对外暴露的方法,设置为private。
在单例模式中构造器声明为private,避免在类的外部创建实例。
构造器 1 2 person p1 = new Person ()
构造器的作用
搭配new关键字,创建类的对象
在创建对象的同时,可以给对象的相关属性进行赋值
构造器的使用说明
构造器的声明格式:权限修饰符 类名(形参列表) {}
创建类以后,在没有显式提供任何构造器的情况下,系统会默认提供一个空参的构造器,且构造器的权限与类的权限相同
一旦类中显示声明了构造器,则系统不再提供默认的空参的构造器
一个类中可以声明多个构造器,彼此之间构成重载
Person.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package com.Learning02.memory;public class Person { String name; int age; char gender; public void eat () { System.out.println("吃饭" ); } public void sleep () { System.out.println("睡觉" ); } public void interest (String hobby) { System.out.println("爱好:" + hobby); } public Person (String s) { name = s; } }
PersonTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.Learning02.memory;public class PersonTest { public static void main (String[] args) { Person p1 = new Person ("小杰" ); p1.age = 23 ; p1.gender = '男' ; System.out.println("姓名:" + p1.name + "年龄:" + p1.age + "性别:" + p1.gender); p1.eat(); p1.sleep(); p1.interest("打游戏" ); } }
实例变量赋值过程 在类的属性中,可以怎样实现赋值?
默认赋值
显式赋值
构造器赋值
通过“对象.方法”赋值
通过“对象.属性”赋值
执行的先后顺序? 1->2->3->4/5
以上操作在执行次数上的限制 只能执行一次:1、2、3 可以多次执行:4、5
JavaBean JavaBean的理解:是一种由Java语言编写的可复用组件
所谓JaveBean,是指符合下列标准的java类:
类是公共的
有一个无参的公共构造器
有属性,且有对应的get和set方法
JavaBeanTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package com.Learning06.JavaBean;import java.sql.Date;public class customer { public customer () { } private int id; private String name; private Date birth; public int getId () { return id; } public void setId (int id) { this .id = id; } public String getName () { return name; } public void setName (String name) { this .name = name; } public Date getBirth () { return birth; } public void setBirth (Date birth) { this .birth = birth; } }
UML类图 UML(Unified Modeling Language,统一建模语言),用来描述软件模型和架构的图形化语言
this关键字的使用
引出问题:当我们在声明一个属性对应的set方法时,通过形参给对应的属性赋值,如果形参名和属性名同名了,那么该如何在方法内部区分这两个变量呢?
解决方案:使用this。具体来讲,使用this修饰的是属性,没有用this修饰的是形参。
this可以调用的结构:成员变量、方法、构造器
对this的一个理解:当对象(在方法中调用时),或当前正在创建的对象(在构造器中调用时)
this调用构造器
格式:this(形参列表)
我们可以在类的构造器中,调用当前类中指定的其他构造器
this(形参列表)必须声明在当前构造器的首行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package com.Learning06.JavaBean;import java.sql.Date;public class customer { public customer () { } public customer (String name) { this (); } public customer (String name, int id) { this (name); this .id = id; } private int id; private String name; }
电商项目练习(增删改查)
首先创建好一个客户类,用于构造客户,即JavaBean。
在其中,定义好私有属性之后,使用alt+insert创建get和set方法,还需要一个空参和有参的构造器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 package com.Project01.mangement;public class customer { private String name; private char gender; private int age; private String phone; private String email; public customer () { } public customer (String name, char gender, int age, String phone, String email) { this .name = name; this .gender = gender; this .age = age; this .phone = phone; this .email = email; } public String getName () { return name; } public void setName (String name) { this .name = name; } public char getGender () { return gender; } public void setGender (char gender) { this .gender = gender; } public int getAge () { return age; } public void setAge (int age) { this .age = age; } public String getPhone () { return phone; } public void setPhone (String phone) { this .phone = phone; } public String getEmail () { return email; } public void setEmail (String email) { this .email = email; } }
用于存放客户记录的数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 package com.Project01.mangement;public class CustomerList { private Customer[] customers; private int total = 0 ; public CustomerList (int totalCustomer) { customers = new Customer [totalCustomer]; } public boolean addCustomer (Customer customer) { if (total < customers.length) { customers[total] = customer; total++; return true ; } return false ; } public boolean replaceCustomer (int index, Customer customer) { if (index >= 0 && index < total) { customers[index] = customer; return true ; } return false ; } public boolean deleteCustomer (int index) { if (index >= 0 && index < total) { for (int i = index; i < total - 1 ; i++) { customers[i] = customers[i + 1 ]; } customers[total - 1 ] = null ; total--; return true ; } return false ; } public Customer[] getAllCustomers() { Customer[] customers1 = new Customer [total]; for (int i = 0 ; i < total; i++) { customers1[i] = customers[i]; } return customers1; } public Customer getCustomer (int index) { if (index >= 0 && index < total) { return customers[index]; } return null ; } public int getTotal () { return total; } }
用于视图展示的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 package com.Project01.mangement;public class CustomerView { CustomerList customerList = new CustomerList (10 ); public void enterMainMenu () { boolean isFlag = true ; while (isFlag) { System.out.println("\n-----------------客户信息管理软件-----------------\n" ); System.out.println(" 1 添 加 客 户" ); System.out.println(" 2 修 改 客 户" ); System.out.println(" 3 删 除 客 户" ); System.out.println(" 4 客 户 列 表" ); System.out.println(" 5 退 出\n" ); System.out.print(" 请选择(1-5):" ); char key = CMUtility.readMenuSelection(); switch (key) { case '1' : addNewCustomer(); break ; case '2' : modifyCustomer(); break ; case '3' : deleteCustomer(); break ; case '4' : listAllCustomers(); break ; case '5' : System.out.println("确认是否退出(Y/N):" ); char isExit = CMUtility.readConfirmSelection(); if (isExit == 'Y' ) { isFlag = false ; } break ; } } } public static void main (String[] args) { CustomerView customerView = new CustomerView (); customerView.enterMainMenu(); } private void addNewCustomer () { System.out.println("---------------------添加客户---------------------" ); System.out.print("姓名:" ); String name = CMUtility.readString(10 ); System.out.print("性别:" ); char gender = CMUtility.readChar(); System.out.print("年龄:" ); int age = CMUtility.readInt(); System.out.print("电话:" ); String phone = CMUtility.readString(13 ); System.out.print("邮箱:" ); String email = CMUtility.readString(30 ); Customer customer = new Customer (name, gender, age, phone, email); boolean isSuccess = customerList.addCustomer(customer); if (isSuccess) { System.out.println("---------------------添加完成---------------------" ); } else { System.out.println("-------------------客户目录已满,添加失败---------------" ); } } private void modifyCustomer () { System.out.println("---------------------修改客户---------------------" ); Customer cust; int number; for (; ; ) { System.out.print("请选择待修改客户编号(-1退出):" ); number = CMUtility.readInt(); if (number == -1 ) { return ; } cust = customerList.getCustomer(number - 1 ); if (cust == null ) { System.out.println("无法找到指定客户!" ); } else { break ; } } System.out.print("姓名(" + cust.getName() + "):" ); String name = CMUtility.readString(10 , cust.getName()); System.out.print("性别(" + cust.getGender() + "):" ); char gender = CMUtility.readChar(cust.getGender()); System.out.print("年龄(" + cust.getAge() + "):" ); int age = CMUtility.readInt(cust.getAge()); System.out.print("电话(" + cust.getPhone() + "):" ); String phone = CMUtility.readString(13 , cust.getPhone()); System.out.print("邮箱(" + cust.getEmail() + "):" ); String email = CMUtility.readString(30 , cust.getEmail()); Customer newCust = new Customer (name, gender, age, phone, email); boolean isRepalaced = customerList.replaceCustomer(number - 1 , newCust); if (isRepalaced) { System.out.println("---------------------修改完成---------------------" ); } else { System.out.println("---------------------修改失败---------------------" ); } } private void deleteCustomer () { System.out.println("---------------------删除客户---------------------" ); int number; for (; ; ) { System.out.print("请选择待删除客户编号(-1退出):" ); number = CMUtility.readInt(); if (number == -1 ) { return ; } Customer customer = customerList.getCustomer(number - 1 ); if (customer == null ) { System.out.println("无法找到指定客户!" ); } else { break ; } } System.out.print("确认是否删除(Y/N):" ); char isDelete = CMUtility.readConfirmSelection(); if (isDelete == 'Y' ) { boolean deleteSuccess = customerList.deleteCustomer(number - 1 ); if (deleteSuccess) { System.out.println("---------------------删除完成---------------------" ); } else { System.out.println("---------------------删除失败---------------------" ); } } else { return ; } } private void listAllCustomers () { System.out.println("---------------------------客户列表---------------------------\n" ); int total = customerList.getTotal(); if (total == 0 ) { System.out.println("没有客户记录!" ); } else { System.out.println("编号\t姓名\t性别\t年龄\t电话\t\t邮箱" ); Customer[] custs = customerList.getAllCustomers(); for (int i = 0 ; i < custs.length; i++) { Customer cust = custs[i]; System.out.println((i + 1 ) + "\t" + cust.getName() + "\t" + cust.getGender() + "\t" + cust.getAge() + "\t" + cust.getPhone() + "\t" + cust.getEmail()); } } System.out.println("-------------------------客户列表完成-------------------------" ); } }
工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 package com.Project01.mangement;import java.util.Scanner;public class CMUtility { private static Scanner scanner = new Scanner (System.in); public static char readMenuSelection () { char c; for (; ; ) { String str = readKeyBoard(1 , false ); c = str.charAt(0 ); if (c != '1' && c != '2' && c != '3' && c != '4' && c != '5' ) { System.out.print("选择错误,请重新输入:" ); } else break ; } return c; } public static char readChar () { String str = readKeyBoard(1 , false ); return str.charAt(0 ); } public static char readChar (char defaultValue) { String str = readKeyBoard(1 , true ); return (str.length() == 0 ) ? defaultValue : str.charAt(0 ); } public static int readInt () { int n; for (; ; ) { String str = readKeyBoard(2 , false ); try { n = Integer.parseInt(str); break ; } catch (NumberFormatException e) { System.out.print("数字输入错误,请重新输入:" ); } } return n; } public static int readInt (int defaultValue) { int n; for (; ; ) { String str = readKeyBoard(2 , true ); if (str.equals("" )) { return defaultValue; } try { n = Integer.parseInt(str); break ; } catch (NumberFormatException e) { System.out.print("数字输入错误,请重新输入:" ); } } return n; } public static String readString (int limit) { return readKeyBoard(limit, false ); } public static String readString (int limit, String defaultValue) { String str = readKeyBoard(limit, true ); return str.equals("" ) ? defaultValue : str; } public static char readConfirmSelection () { char c; for (; ; ) { String str = readKeyBoard(1 , false ).toUpperCase(); c = str.charAt(0 ); if (c == 'Y' || c == 'N' ) { break ; } else { System.out.print("选择错误,请重新输入:" ); } } return c; } private static String readKeyBoard (int limit, boolean blankReturn) { String line = "" ; while (scanner.hasNextLine()) { line = scanner.nextLine(); if (line.length() == 0 ) { if (blankReturn) return line; else continue ; } if (line.length() < 1 || line.length() > limit) { System.out.print("输入长度(不大于" + limit + ")错误,请重新输入:" ); continue ; } break ; } return line; } }
面向对象的特征之二:继承性
如上图所示,Student类继承了父类Person的所有属性和方法,并增加了一个属性school。Person中的属性和方法,Student都可以使用。
继承的优点
继承的出现减少了代码冗余,提高了代码的复用性。
继承的出现,更有利用功能的扩展。
继承的出现让类与类之间产生了is-a的关系,为多态的使用提供了前提。 注意:不要仅仅为了获取其他类中的功能而去继承!
继承的语法 class A{
}
class B extends A{
}
继承中的基本概念 类A:父类、superClass、超类、基类 类B:子类、subClass、派生类
有了继承之后
子类可以获取到父类所有的属性和方法。
但是,由于封装性的影响,可能子类不能直接调用父类中声明的属性或者方法(私有)。
同时,子类还可以扩展自己特有的属性和方法。
默认的父类:Java中声明的类,如果没有显式的声明其父类时,则默认继承于java.lang.Object
一个父类可以有多个子类。
一个子类只能有一个父类。
Java支持多层继承机制,即一个类可以同时是父类与子类。
重写 为什么需要方法的重写? 子类在继承父类之后,就获取到了父类中声明的所有的方法。但是,父类中的方法可能不太适合子类,换句话说,子类需要对继承过来的方法进行覆盖、重写的操作。
具体规则
父类被重写的方法与子类被重写的方法的方法名和形参列表必须相同。
子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符。
子类不能重写父类中声明为private权限修饰的方法。
当返回值是void和基本数据类型,子类的返回值类型必须和父类相同。
当返回值是引用数据类型,子类要与父类保持一致,或者是被重写方法的返回值类型的子类。
super关键字的使用 为什么要使用super eg:子类继承父类之后,对父类的方法进行了重写,那么在子类中,是否还可以对父类被重写额方法进行调用? eg:子类继承父类之后,发现子类和父类中定义了同名的属性,是否可以在子类中区分?
解决办法:使用super关键字即可,理解为父类的。
super可以调用的结构:属性、方法、构造器
使用规范
子类继承父类时,不会继承父类的构造器,只能通过super(“形参列表”)的方式调用父类指定的构造器。
规定super必须声明在构造器的首行,同时,this和super只能二选一。
如果在子类的构造器中首行既没有调用this()也没有调用super(),则此构造器默认调用super(),即调用父类的空参构造器。
面向对象的特征之三:多态性 如何理解多态性? 理解为一个事物的多种形态
Java中多态性的体现 父类的引用指向子类的对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.Learning09.polymorphism;public class PersonTest { public static void main (String[] args) { Person person = new Person (); Man man = new Man (); Person person1 = new Man (); } }
多态性的使用前提
要有类的继承关系
要有方法的重写
多态的适用性 适用于方法,不适用属性
多态的好处与弊端
好处 极大地减少了代码的冗余,不需要定义多个重载的方法。
弊端 在多态的场景下,创建了子类的对象,也加载了子类特有的属性和方法。但是由于声明了父类的引用,导致我们没有办法直接调用子类特有的属性和方法。
apply.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 package com.Learning09.polymorphism.apply;public class AnimalTest { public static void main (String[] args) { AnimalTest animalTest = new AnimalTest (); animalTest.adopt(new Dog ()); animalTest.adopt(new Cat ()); } public void adopt (Animal animal) { animal.eat(); animal.jump(); } } class Animal { public void eat () { System.out.println("动物吃饭" ); } public void jump () { System.out.println("动物跳跃" ); } } class Dog extends Animal { public void eat () { System.out.println("狗吃骨头" ); } public void jump () { System.out.println("狗跳" ); } public void watchDoor () { System.out.println("狗能看家" ); } } class Cat extends Animal { public void eat () { System.out.println("猫吃鱼" ); } public void jump () { System.out.println("猫跳" ); } public void catchMouse () { System.out.println("猫能抓老鼠" ); } }
子类的强制转型 强制转型的目的:可以访问到子类特有的属性或者方法。
如下的代码片段中使用instanceof进行了判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.Learning09.polymorphism;public class PersonTest1 { public static void main (String[] args) { Person person = new Man (); if (person instanceof Man) { Man man = (Man) person; man.earnMoney(); System.out.println(man.isSmoke); } } }
Object类 如何理解跟父类
类java.lang.Object是类层次结构的根类,即所有它的类的父类。每个类都是用Object作为超类。
任何一个Java类(除Object类)都直接或间接的继承与Object类
Object类称为java类的根父类
Object类中声明的结构(属性、方法等)具有通用性 Object类中没有声明属性 Object类提供了一个空参的构造器
常用方法 重点方法:equals() \ toString() 了解方法:clone() \ finalize()
equals()方法的使用
适用情况:任何引用数据类型都可以使用
子类使用说明: 如果自定义的类没有重写Object类中equals()方法的情况下,调用的就是Object类中的equals(),比较两个对象的引用地址是否相同。(或比较两个对象是否指向了堆空间中的同一个对象实体)
对于像String、File、Date这些包装类等,它们都重写了Object类当中的equals()方法,用于比较两个对象的实体内容是否相等。
手动重写equals()方法,比较内容是否相同
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package com.Learging10.object.equals;import java.util.Objects;public class UserTest { public static void main (String[] args) { User user = new User ("小红" , 23 ); User user1 = new User ("小红" , 23 ); System.out.println(user.equals(user1)); } } class User { String name; int age; public User () { } public User (String name, int age) { this .name = name; this .age = age; } 重写equals() @Override public boolean equals (Object obj) { if (this == obj) { return true ; } if (obj instanceof User) { User user = (User) obj; if (user.age == this .age && user.name.equals(this .name)) { return true ; } } return false ; } }
但是在实际开发当中,可以使用效率更高的方法,使用IDEA自动生成equals()方法
按下alt+ins键,然后点击equals()和hashcode()
下一步
下一步
经过IDEA自动生成的equals方法也可以达到相同的效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package com.Learging10.object.equals;import java.util.Objects;public class UserTest { public static void main (String[] args) { User user = new User ("小红" , 23 ); User user1 = new User ("小红" , 23 ); System.out.println(user.equals(user1)); } } class User { String name; int age; public User () { } public User (String name, int age) { this .name = name; this .age = age; } @Override public boolean equals (Object o) { if (this == o) return true ; if (o == null || getClass() != o.getClass()) return false ; User user = (User) o; return age == user.age && Objects.equals(name, user.name); } }
equals和==的区别
==:运算符 适用范围:基本数据类型、引用数据类型 (1)基本数据类型:判断数据值是否相等 (2)引用数据类型:判断两个对象的地址值是否相等
equals()方法 适用范围:引用数据类型 不重写equals()方法的话默认比较两个对象的地址值是否相等 一般我们在自定义的类中会将equals()重写为比较数值是否相同的方法
toString()方法的使用 1.Object类中toString()的定义:
1 2 3 4 public String toString () { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
根据源码可以分析出:返回的是一个类名+地址
2.开发中的使用场景:
平时我们在使用System.out.println()这个方法的时候就调用了toString()方法
3.子类使用说明 自定义的类:在没有重写Object类下的toString()的情况下,默认返回的是当前对象的地址值 String、File、Date等包装类:都重写了toString()方法,在调用toString()时,返回当前对象的实体内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package com.Learging10.object.tostring;public class toStringTest { public static void main (String[] args) { User user = new User ("小红" , 23 ); System.out.println(user.toString()); String s1 = new String ("666" ); System.out.println(s1.toString()); } } class User { String name; int age; public User () { } public User (String name, int age) { this .name = name; this .age = age; } }
4.开发中使用说明:
习惯上:开发中对于自定义方法的类在调用toString()时,也希望显示其对象的实体内容,这时候就需要重写toString()方法了。
手动重写toString()以及IDEA生成toString()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package com.Learging10.object.tostring;public class toStringTest { public static void main (String[] args) { User user = new User ("小红" , 23 ); System.out.println(user.toString()); String s1 = new String ("666" ); System.out.println(s1.toString()); } } class User { String name; int age; public User () { } public User (String name, int age) { this .name = name; this .age = age; } @Override public String toString () { return "User{" + "name='" + name + '\'' + ", age=" + age + '}' ; } }
static关键词的使用 1.static:静态的 2.static用来修饰的结构:属性、方法、代码块、内部类 3.对比静态变量和实例变量 (1)个数 静态变量:在内存空间中只有一份,被类的多个对象所共享 实例变量:类的每一个实例都保存着一份实例变量 (2)内存位置 静态变量:jdk6及之前存放在方法区,jdk7及之后存放在堆空间中 实例变量:存放在对空间的对象实体中 (3)加载时机 静态变量:随着类的加载而加载,由于类只会加载一次,所以静态变量也只有一份 实例变量:随着对象的创建而加载 (4)调用者 静态变量:可以被类直接调用,也可以使用对象调用 实例变量:只能使用对象进行调用 (5)消亡时机 静态变量:随着类的卸载而消亡 实例变量:随着对象的消亡而消亡
4.static修饰方法: (1)随着类的加载而加载 (2)可以通过“类.静态方法”的方式,直接调用静态方法 (3)静态方法内可以调用静态的属性或静态的方法,不可以调用非静态的结构 (4)在类的非静态方法中,可以调用当前类中的静态结构或非静态结构 (5)static修饰的方法内,不能使用this和super
单例(Singleton)设计模式 何为单例模式? 所谓类的单例设计模式,就是采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类提供一个取得其对象实例的方法。
实现思路 如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private ,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法 以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义为静态的
饿汉式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package com.Learning12.singleton;public class BankTest { public static void main (String[] args) { Bank bank = Bank.getBank(); } } class Bank { private Bank () { } private static Bank bank = new Bank (); public static Bank getBank () { return bank; } }
懒汉式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package com.Learning12.singleton;public class GirlFriendTest {} class GirlFriend { private GirlFriend () { } private static GirlFriend girlFriend = null ; public static GirlFriend getGirlFriend () { if (girlFriend == null ) { girlFriend = new GirlFriend (); } return girlFriend; } }
对比两种模式的优缺点 1.特点 饿汉式:“立即加载”,随着类的加载而记载,当前的唯一实例就创建了 懒汉式:“延迟记载”,在需要使用的时候进行创建
2.优点 饿汉式:写法简单,由于内存中较早加载,使用更方便、更快。线程安全。 懒汉式:在需要的时候进行创建,节省内存空间
3.缺点 饿汉式:内存中占用时间较长 懒汉式:线程不安全
单例模式的一些应用场景 1.Windows中的任务管理器和回收站 2.手机上的App应用 3.数据库连接池的设计一般也是采用单例模式
类的成员之四:代码块 1.代码块的作用: 用来初始化类或对象的信息(即初始化类或者对象的成员变量)
2.代码块的修饰 只能使用static进行修饰,或者没有修饰词
3.代码块的分类 静态代码块:使用static进行修饰 非静态代码块:没有使用static进行修饰
4.具体使用 静态代码块的使用:随着类的加载而执行;由于类的加载只会执行一次,进而静态代码块的执行也只有一次。 用来初始化类的信息 静态代码块的内部只能调用静态的结构(即静态的属性、方法),不能调用非静态的结构 非静态代码块的使用:随着对象的创建而执行,每创建当前类的一个实例,就会执行一次非静态代码块。 用来初始化对象的信息 非静态代码块的内部既能调用静态的结构(即静态的属性、方法),也能调用非静态的结构
final关键字的使用 1.final的理解:最终的
2.final可以修饰的结构:类、方法、变量
3.具体说明:
(1)final修饰类:表示此类不能被继承,即将来也不再需要进行额外的功能扩展 (2)final修饰方法:表示此方法不能被重写 (3)final修饰变量:既可以修饰成员变量也可以修饰局部变量;此时的“变量”其实就变成了常量 final修饰成员变量有哪些位置可以给成员变量赋值 显式赋值 代码块中赋值 构造器中赋值 final修饰局部变量:一旦赋值就不能更改,且在调用局部变量之前一定要进行赋值
4.final和static进行搭配:修饰成员变量时,称为全局常量
抽象类
代码展示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 package com.Learning16._abstract;public class AbstractTest { public static void main (String[] args) { Student student = new Student (); student.eat(); } } abstract class Person { String name; int age; public Person () { } public Person (String name, int age) { this .name = name; this .age = age; } public abstract void eat () ; public void sleep () { System.out.println("人睡觉" ); } } class Student extends Person { String school; public Student () { } public Student (String name, int age, String school) { super (name, age); this .school = school; } @Override public void eat () { System.out.println("学生吃饭" ); } @Override public void sleep () { System.out.println("学生睡觉" ); } }
1.abstract的概念:抽象的 2.abstract可以修饰:类、方法 3.具体的使用 3.1abstract修饰类: 此类称为抽象类;不可以实例化; 抽象类是包含构造器的,因为子类对象实例化时,需要直接或者间接的调用到父类的构造器; 抽象类中可以不包含抽象方法;抽象方法所在的类一定是抽象类 3.2abstract修饰方法: 此方法即为抽象方法; 抽象方法只有方法的声明没有方法体; 抽象方法其功能是确定的(通过方法的声明即可确定) 子类必须重写父类中所有的抽象方法之后才可以实例化,否则此子类仍然是一个抽象类; 4.abstract不能修饰哪些结构:属性、构造器、代码块; 5.abstract不能与哪些关键字同时使用:不能修饰私有方法、静态方法、final的方法、final的类; 原因:私有方法不能被重写;避免静态方法使用类进行调用;final修饰的方法不能被重写;final修饰的类不能喝有子类;
接口 1.接口的理解:接口的本质是契约、标准、规范,就像法律一样,制定好之后都要遵守。 2.定义接口的关键字:interface 3.接口内部结构的说明: 可以声明:属性:必须使用public、static、final修饰;方法:JDK8之前只能声明抽象方法,修饰为public abstract;JDK8:声明静态方法、默认方法;JDK9:可以声明私有方法 不可以声明:构造器、代码块等 4.接口与类的关系:实现关系 5.格式:class A implements B,C{} 6.满足此关系之后,说明: 类可以实现多个接口。 类针对于接口的多实现,一定程度上弥补了类的单继承的局限性。 类必须将实现的接口中的所有的抽象方法都重写,方可实例化,否则此实现类必须声明为抽象类。 7.接口与接口之间的关系:继承关系,且可以多继承。 8.接口的多态性:接口名 变量名 = new 实现类对象 9.区分抽象类和接口 共性:都可以有抽象方法;都不能实例化; 不同:抽象类一定有构造器,接口没有构造器;类与类之间是继承关系,接口与接口之间是多继承关系,类与接口之间是实现关系;
接口使用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 package com.Learning17._interface.apply;public class USBTest { public static void main (String[] args) { Computer computer = new Computer (); Printer printer = new Printer (); computer.transferData(printer); computer.transferData(new Camera ()); USB usb = new USB () { @Override public void start () { System.out.println("U盘开始工作了" ); } @Override public void stop () { System.out.println("U盘结束工作了" ); } }; computer.transferData(usb); computer.transferData(new USB () { @Override public void start () { System.out.println("匿名实现类的匿名对象开始工作了" ); } @Override public void stop () { System.out.println("匿名实现类的匿名对象结束工作了" ); } }); } } interface USB { public abstract void start () ; void stop () ; } class Computer { public void transferData (USB usb) { System.out.println("设备连接成功..." ); usb.start(); System.out.println("数据传输的细节操作" ); usb.stop(); } } class Printer implements USB { @Override public void start () { System.out.println("打印机开始工作" ); } @Override public void stop () { System.out.println("打印机结束工作" ); } } class Camera implements USB { @Override public void start () { System.out.println("照相机开始工作" ); } @Override public void stop () { System.out.println("照相机结束工作" ); } }
声明静态方法、默认方法、私有方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package com.Learning17._interface.jdk8;public interface CompareA { public static void method () { System.out.println("接口中的静态方法,只能接口自己调用,实现类无法调用" ); } public default void method1 () { System.out.println("接口中的默认方法,实现类可以进行重写" ); } private void method2 () { System.out.println("供接口自己使用" ); } }
类的成员之五:内部类 1.什么是内部类:将一个类A定义在另一个类B里面,类A就称为内部类 ,类B则称为外部类 。 2.为什么要声明内部类:当一个事物A的内部,还有一个部分需要一个完整的结构B进行描述,而这个内部的完整结构B又只为外部事物A提供服务,不在其他地方单独使用,那么整个内部的完整结构最好使用内部类。 总体来说,遵循高内聚低耦合的面向对象开发原则。 3.内部类使用举例: Thread类内部声明State类,用于表示线程的生命周期。 HashMap类中,声明了一个Node类,用来表示封装的key和value 4.内部类的分类:(参考变量的分类) 成员内部类:直接声明在外部类的里面;使用static称为静态的成员内部类、反之是非静态的成员内部类 局部内部类:声明在方法内、构造器内、代码块内;匿名的局部内部类、非匿名的局部内部类
5.关于成员内部类的理解: 从类的角度看:内部可以声明属性、方法、构造器、代码块、内部类等结构;此内部类可以声明父类、可以实现接口;可以使用final修饰,表示不能有子类;可以使用abstract进行修饰。
从外部类的成员的角度看:在内部可以调用外部类的结构,如:属性、方法等;除了使用public、缺省权限修饰之外,还可以使用private和protected进行修饰;可以使用static进行修饰。
内部类的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 package com.Learning18.inner;public class OuterClass { public static void main (String[] args) { Person.Dog dog = new Person .Dog(); dog.eat(); Person person = new Person (); Person.Bird bird = person.new Bird (); bird.eat(); bird.show("jack" ); bird.show1(); } } class Person { String name = "Tom" ; public void eat () { System.out.println("人吃饭" ); } static class Dog { public void eat () { System.out.println("狗可以啃骨头" ); } } class Bird { String name = "Jerry" ; public void eat () { System.out.println("鸟吃虫子" ); } public void show (String name) { System.out.println("形参中的name" + name); System.out.println("Bird类中的name" + this .name); System.out.println("Person类中的name" + Person.this .name); } public void show1 () { eat(); this .eat(); Person.this .eat(); } } public void method () { class InnerClass1 { } } public Person () { class InnerClass2 { } } }
局部内部类的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package com.Learning18.inner;public class OuterClassTest1 { public void method1 () { class A { } } public Comparable getInstance () { return new Comparable () { @Override public int compareTo (Object o) { return 0 ; } }; } }
枚举类 枚举类的理解 枚举类型本质上也是一种类,只不过这个类的对象是有限的、固定的几个、不能让用户随意创建。
开发中的建议 1.如果针对某个类,其实例个数是确定的。则推荐将此类声明为枚举类。 2.如果枚举类的实例只有一个,则可以看做单例的实现方式。
JDK5.0之前与之后如何定义枚举类
JDK5.0之前
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package com.Learning19._enum;public class SeasonTest { public static void main (String[] args) { System.out.println(Season.autumn); } } class Season { private final String seasonName; private final String seasonDesc; private Season (String seasonName, String seasonDesc) { this .seasonName = seasonName; this .seasonDesc = seasonDesc; } public String getSeasonName () { return seasonName; } public String getSeasonDesc () { return seasonDesc; } public static final Season spring = new Season ("春天" , "春暖花开" ); public static final Season summer = new Season ("夏天" , "夏日炎炎" ); public static final Season autumn = new Season ("秋天" , "秋高气爽" ); public static final Season winter = new Season ("冬天" , "白雪皑皑" ); @Override public String toString () { return "Season{" + "seasonName='" + seasonName + '\'' + ", seasonDesc='" + seasonDesc + '\'' + '}' ; } }
JDK5.0之后的枚举类实现方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package com.Learning19._enum;public class SeasonTest1 {} enum Season1 { Season1("春天" , "春暖花开" ), summer1("夏天" , "夏日炎炎" ), autumn1("秋天" , "秋高气爽" ), winter1("冬天" , "白雪皑皑" ); private final String seasonName; private final String seasonDesc; private Season1 (String seasonName, String seasonDesc) { this .seasonName = seasonName; this .seasonDesc = seasonDesc; } public String getSeasonName () { return seasonName; } public String getSeasonDesc () { return seasonDesc; } @Override public String toString () { return "Season1{" + "seasonName='" + seasonName + '\'' + ", seasonDesc='" + seasonDesc + '\'' + '}' ; } }
Enum中的常用方法 1.使用enum关键字定义的枚举类,默认父类是java.lang.Enum类,不能再显式定义其父类,会报错。 2.熟悉Enum类中常用的方法: String toString():默认返回的是常量名(对象名),可以手动重写该方法。 static 枚举类型[] values():返回枚举类型的对象数组。该方法可以很方便的遍历所有的枚举值,是一个静态方法。 static 枚举类型 valueof(String name),可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举
3.枚举类实现接口的操作: 情况1:枚举类实现接口,在枚举类中重写接口的抽象方法。当通过不同的枚举类对象调用此方法时,执行的是同一个方法。 情况2:让枚举类的一个对象,去重写接口中的抽象方法。当不同的枚举类对象调用此方法时,执行的是各自重写的方法。
如下具体代码所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 package com.Learning19._enum;public class SeasonTest1 { public static void main (String[] args) { System.out.println(Season1.Spring); System.out.println(Season1.Spring.name()); Season1[] values = Season1.values(); String objName = "winter" ; Season1 season1 = Season1.valueOf(objName); System.out.println(season1); System.out.println(Season1.Spring.ordinal()); Season1.Spring.show(); } } enum Season1 implements Info { Spring("春天" , "春暖花开" ) { @Override public void show () { System.out.println("这个季节是" + getSeasonName()); } }, summer("夏天" , "夏日炎炎" ) { @Override public void show () { System.out.println("这个季节是" + getSeasonName()); } }, autumn("秋天" , "秋高气爽" ) { @Override public void show () { System.out.println("这个季节是" + getSeasonName()); } }, winter("冬天" , "白雪皑皑" ) { @Override public void show () { System.out.println("这个季节是" + getSeasonName()); } }; private final String seasonName; private final String seasonDesc; private Season1 (String seasonName, String seasonDesc) { this .seasonName = seasonName; this .seasonDesc = seasonDesc; } public String getSeasonName () { return seasonName; } public String getSeasonDesc () { return seasonDesc; } } interface Info { void show () ; }
Annotation注解 什么是注解 注解(Annotation)是JDK5.0引入的,以“@注解名”在代码中存在。例如: @Override ->重写 @Deprecated ->已弃用 @SuppressWarnings(value=”unchecked”) ->抑制编译器警告
注解与注释 对于单行注释和多行注释,是给程序员看的 注解则是可以被编译器或其它程序读取的。程序还可以根据注解的不同,作出相应的处理。
常见的Annotation作用 @author:表明开发该模块的作者,多个作者之间使用,分割 @version:表明该类模块的版本 @see:参考转向,也就是相关主题 @since:从哪个版本开始增加的 @param:对方法中参数的说明,如果没有参数就不能写 @return:对方法返回值的说明,如果方法的返回值类型是void就不能写 @exception:对方法可能抛出的异常进行说明,如果方法没有用throws显式抛出异常就不能写
自定义注解 参考@SuppressWarnings为参照,进行定义即可。
元注解 对现有的注解进行解释说明的注解。 1.@Target:用于描述注解的使用范围 可以通过枚举类型ElementType的10个常量对象来指定 TYPE、METHOD、CONSTRUCTOR、PACKAGE… 2.@Retention:用于描述注解的生命周期 可以通过枚举类型RetentionPolicy的3个常量对象来指定 SOURCE(源代码)、CLASS(字节码)、RUNTIME(运行时) 唯有RUNTIME阶段才能被反射读取到 3.@Documented:表明这个注解应该被javadoc工具记录 4.@Inherited:允许子类继承父类中的注解
单元测试 下载好junit和hamcrest-core这两个jar包之后导入到项目库当中,然后使用注解@Test就可以执行对应的方法,而不必将其它方法都注释掉。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package com.Learning20.annotation.junit;import org.junit.Test;public class JUnitTest { int number = 10 ; @Test public void test () { System.out.println("这是JUnit的单元测试" ); } @Test public void test2 () { System.out.println("number = " + number); } }
要想能正确的编写单元测试方法,需要满足 所在的类必须是public的,非抽象的,包含唯一的无参构造器 @Test标记的方法本身必须是public的,非抽象的,非静态的,void无返回值的,无参数的
默认情况下,单元测试方法中使用Scanner会失效,解决方法 在IDEA上方的帮助中选择“编辑自定义虚拟机选项”,然后在最后一行加上 -Deditable.java.test.console=true 就可以让Scanner恢复正常功能
定义test测试模板
其中模板内容中的$$的意思是光标停在的位置,点击回车之后会跳到下一个$$的位置
包装类 为什么需要包装类 为了使基本数据类型的变量具有引用数据类型变量的相关特征(比如:封装性、继承性、多态性),给各个基本数据类型的变量提供了对应的包装类。
自定义包装类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package com.Learning21.wrapper;public class WrapperTest { public static void main (String[] args) { MyInteger myInteger = new MyInteger (33 ); System.out.println(myInteger); } } class MyInteger { int value; public MyInteger () { } public MyInteger (int value) { this .value = value; } public int getValue () { return value; } public void setValue (int value) { this .value = value; } @Override public String toString () { return "MyInteger{" + "value=" + value + '}' ; } }
掌握基本数据类型和以后用数据类型之间的转换 为什么需要转换 一方面,在有一些场景下,需要使用到基本数据类型对用的包装类对象,此时就需要将基本数据类型的变量转换为包装类的对象。 比如:ArrayList的add(Object obj);Object类的equals(Object obj) 对于包装类来讲:既然使用的是对象,对象是不能进行加减乘除等运算的。为了能够进行运算就需要将包装类对象转换为基本数据类型对象。
jdk5.0新特性:自动装箱、自动拆箱
(装箱)基本数据类型->包装类 (拆箱)包装类->基本数据类型
转换方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 package com.Learning21.wrapper;import com.Learning14.block.BlockTest;import org.junit.Test;public class WrapperTest2 { @Test public void test1 () { int i1 = 10 ; Integer i = new Integer (i1); System.out.println(i.toString()); float f1 = 12.3F ; Float ff1 = new Float (f1); System.out.println(ff1.toString()); boolean b1 = true ; Boolean bb1 = new Boolean (b1); System.out.println(bb1.toString()); Integer i2 = Integer.valueOf(i1); Boolean b = Boolean.valueOf(b1); Float v = Float.valueOf(f1); System.out.println(i2.toString() + b.toString() + v.toString()); } @Test public void test2 () { Integer i = new Integer (11 ); int i1 = i.intValue(); System.out.println(++i1); Float f = new Float (12.3F ); float f1 = f.floatValue(); System.out.println(++f1); Boolean b = new Boolean (true ); boolean b1 = b.booleanValue(); System.out.println(b1 + "123" ); } @Test public void test3 () { int i1 = 10 ; Integer ii1 = i1; System.out.println(ii1.toString()); Boolean bb1 = true ; Float f1 = 12.3F ; int ii2 = ii1; System.out.println(ii2); } }
String类型与包装类、基本数据类型的转换 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package com.Learning21.wrapper;import org.junit.Test;public class WrapperTest1 { @Test public void test () { int i1 = 10 ; String s = String.valueOf(i1); System.out.println(s); boolean b1 = true ; String s1 = String.valueOf(b1); System.out.println(s1); Float f1 = 12.3f ; String s2 = String.valueOf(f1); System.out.println(s2); } @Test public void test1 () { String s1 = "123" ; int i1 = Integer.parseInt(s1); System.out.println(i1); } }
异常 什么是程序的异常 在使用计算机语言进行项目开发的过程中,即使把代码写的尽善尽美,在系统运行的过程当中仍然会遇到一些问题,比如:客户输入数据的格式不对,读取文件是否存在,网络是否保持通畅等等。
异常:指的是程序在执行过程中,出现的非正常情况,如果不处理最终会导致JVM的非正常停止。
异常的抛出机制 Java中把不同的异常用不同的类表示,一旦发生某种异常,就创建该异常类的对象,并且抛出(throw)。然后程序员可以捕获(catch)到这个异常对象,并处理。如果没有捕获这个异常对象,那么这个异常对象将导致程序终止。
编译时异常和运行时异常 编译时期异常(即checked异常、受检异常):在代码编译阶段,编译器就能明确警示当前代码可能发生的xx异常。 运行时期异常(即runtime异常、unchecked异常、非受检异常):在代码编译阶段,编译器完全不做任何检查,无论该异常是否发生,编译器都不给出任何提示,只有等代码运行起来并且发生了异常,编译器才会发现。
异常的体系结构 java.lang.Throwable:异常体系的根父类: java.lang.Error:Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。一般不编写针对性的代码进行处理。 java.lang.Exception:其他因编程错误或者偶然的外在因素导致的一般性问题,需要使用针对性的代码进行处理,使程序继续运行。例如:空指针访问、试图读取不存在的文件、网络连接中断、数组下标越界。
常见的一些异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 package com.Learning01.throwable;import org.junit.Test;import org.w3c.dom.ls.LSOutput;import java.io.File;import java.io.FileInputStream;import java.util.Date;import java.util.Scanner;public class ExceptionTest { @Test public void test () { int arr[] = new int [10 ]; System.out.println(arr[10 ]); } @Test public void test1 () { int [][] arr1 = new int [10 ][]; System.out.println(arr1[0 ][0 ]); } @Test public void test2 () { Object obj = new String (); Date date = (Date) obj; } @Test public void test3 () { String str = "123" ; str = "abc" ; int i = Integer.parseInt(str); System.out.println(i); } @Test public void test4 () { Scanner scanner = new Scanner (System.in); int num = scanner.nextInt(); System.out.println(num); } @Test public void test5 () { int num = 10 ; System.out.println(num / 0 ); } @Test public void test6 () { Class clz = Class.forName("java.lang.String" ); } @Test public void test7 () { File file = new File ("hello.txt" ); FileInputStream fileInputStream = new FileInputStream (file); } }
异常的处理 在编写程序时,经常要在可能出现的错误的地方加上检测的代码,如运算x/y的时候,要检测分母是否为0,数据为空,输入的不是数据而是字符等。因此要采用异常处理机制。
Java异常处理 Java采用额异常处理机制,是将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁、优雅、并易于维护。
方式一:try-catch-finally
过程1:抛:程序在执行的过程中, 一旦出现异常,就会出现在异常的代码处,生成对应异常类的对象,并将此对象抛出。 一旦抛出,此程序就不执行其后的代码。
过程2:抓:针对过程1中抛出的异常对象进行捕获处理。 一旦将出现的异常处理掉,代码就可以继续执行。
基本结构
try{ 可能产生异常的代码 } catch(异常类型1 e){ 当产生异常类型1异常时的处理措施 } catch(异常类型2 e){ 当产生异常类型2异常时的处理措施 } finally{ 无论是否发生异常,都无条件执行的语句 }
使用细节
如果声明了多个catch结构,不同的异常类型不存在父子类的关系,谁声明在上,或者在下都无所谓。 但如果多个类型满足父子类的关系,则必须将子类的结构声明在父类的上方。否则报错,因为先执行父类的异常处理,就不会执行子类异常的处理了
catch中异常处理的方式
1.自己编写输出的语句 2.printStackTrace():打印异常的详细信息 3.getMessage():获取发生异常的原因
开发体会
对于运行时异常:开发中,通常不进行显式处理,一旦在程序运行中出现按照提示进行修改代码即可。 对于编译时异常:一定要进行处理,否则编译不通过。
处理异常代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package com.Learning01.throwable;import org.junit.Test;import org.w3c.dom.ls.LSOutput;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.util.Date;import java.util.InputMismatchException;import java.util.Scanner;public class ExceptionTest { @Test public void test4 () { try { Scanner scanner = new Scanner (System.in); int num = scanner.nextInt(); System.out.println(num); } catch (InputMismatchException e) { e.printStackTrace(); } System.out.println("异常处理结束,代码继续执行" ); } @Test public void test7 () { try { File file = new File ("hello.txt" ); FileInputStream fileInputStream = new FileInputStream (file); } catch (FileNotFoundException e) { e.printStackTrace(); } System.out.println("读取数据结束" ); } }
finally的使用
1.关于finally的理解:
将一定要执行的代码声明在finally结构中。 无论try中或catch中是否存在仍未被处理的异常或者存在return语句,finally中的代码都会被执行。
唯一的例外就是使用System.exit(0)终止当前运行的虚拟机。
finally语句和catch语句是可选的,但是finally不能单独使用,try也不能单独使用。
2.什么样的代码需要写在finally当中
在开发中,有一些资源:输入流、输出流、数据库连接、Socket连接等资源,在使用完之后必须显式的进行关闭操作,否则GC不会自动回收这些资源,进而导致内存泄露。 必须将这些操作声明在finally当中。
方式二:throws+异常类型
1.格式:在方法的声明处使用throws+异常类型1,异常类型2,…
2.基本结构: public void test() throws 异常类型1,异常类型2,..{ 可能产生异常的代码 }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package com.learning03._throws;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;public class ThrowTest { public static void main (String[] args) { method2(); } public static void method2 () { try { method1(); } catch (FileNotFoundException e) { e.printStackTrace(); } } public static void method1 () throws FileNotFoundException { File file = new File ("hello.txt" ); FileInputStream fileInputStream = new FileInputStream (file); System.out.println("读取数据结束" ); } }
3.是否真正处理了异常?
从编译是否能通过的角度看,看成是给出了异常的解决方案,继续向上抛出。 但是throws的方式仅是将可能出现的异常抛给了此方法的调用者。调用者仍然需要考虑如何处理相关异常。 从这个角度来看,throws不算真正意义上处理了异常。
4.方法重写的要求(针对于编译型异常)
子类重写的方法抛出的异常类型可以与被重写的方法抛出的异常类型相同,或者是父类被重写的方法抛出的异常类型的子类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.learning03._throws;import java.io.FileNotFoundException;import java.io.IOException;public class OverrideTest { public static void main (String[] args) { Father f = new Son (); try { f.method1(); } catch (IOException e) { e.printStackTrace(); } } } class Father { public void method1 () throws IOException { } } class Son extends Father { @Override public void method1 () throws FileNotFoundException { } }
5.开发中,如何选择处理两种异常方式:
涉及到资源的调用(流、数据库连接、网络连接等),使用try-catch-finally处理,防止发生内存泄漏。 如果父类被重写的方法没有throws异常类型,子类重写的方法中出现异常,只能使用try-catch-finally。 开发中,方法a中依次调用了b,c,d等方法,方法b,c,d是递进关系。此时如果方法b,c,d中有异常,通常选择使用throws,然后在a中使用try-catch-finally处理。
手动抛出异常(throw) 为什么需要手动抛出异常对象
在实际开发中,如果出现不满足具体问题的情况就有必要手动抛出一个指定类型的异常对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package com.learning04._throw;public class ThrowTest { public static void main (String[] args) { Student student = new Student (); try { student.regist(-12 ); } catch (RuntimeException e) { e.printStackTrace(); } System.out.println("程序结束" ); } } class Student { int id; public void regist (int id) { if (id > 0 ) { System.out.println(this .id = id); } else { throw new RuntimeException ("id不能为负" ); } } }
自定义异常类 1.继承于现有的异常体系,通常继承于RuntimeException\Exception 2.提供几个重载的构造器 3.提供一个全局常量,声明为:static final long serialVersionUID
多线程 相关概念 1.程序、进程与线程
程序(program):为完成特定任务,用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
进程(process):程序的一次执行过程,或是正在内存中运行的应用程序,如:运行中的QQ,运行中的音乐播放器。 每个进程都有一个独立的内存空间,系统运行一个程序即是一个进程从创建、运行到消亡的过程。(声明周期) 程序是静态的,进程是动态的。 进程作为操作系统调度和分配资源的最小单位(亦是系统运行程序的基本单位),系统在运行时会为每个进程分配不同的内存区域。 现代的操作系统,大多数是支持多进程的,支持同时运行多个程序。
线程(thread):进程可进一步细分为线程,是程序内部的一条执行路径。一个进程中至少有一个线程。 一个进程同一时间若并行多个线程,就是支持多线程的。 线程作为CPU调度和执行的最小单位。 一个进程中的多个线程共享相同的内存单元,它们从同一个堆中分配对象,可以访问相同的变量和对象。这就使得线程间的通信更简便、高效。但多个线程操作共享的系统资源可能带来安全隐患。 不同进程之间不共享内存。 进程之间的数据交换和成本很高。
线程调度 分时调度:所有线程轮流使用CPU的使用权,并且平均分配每个线程占用CPU的时间
抢占式调度:让优先级高的线程以较大概率优先使用CPU、如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
多线程程序的优点 1.提高应用程序的响应,对图形化界面更有意义,可增强用户体验。 2.提高计算机系统的CPU利用率。 3.改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改。
并行与并发 并行(parallel):指两个或多个事件在同一时刻发生(同时发生)。指在同一时刻,有多条指令在多个CPU上同时执行。
并发(concurrency):指两个或多个事件在同一时间段内发生。即在一段时间内,有多条指令在单个CPU上快速轮换、交替执行,使得在宏观上具有多个进程同时执行的效果。
创建和启动线程 Java语言的JVM实例允许程序运行多个线程,使用java.lang.Thread类代表线程,所有线程的对象都必须是Thread类或其子类的实例。 Thread类的特性:每个线程都是通过某个特定Thread对象的run()方法来完成操作的,因此把run()方法体称为线程执行体;通过该Thread对象的start()方法来启动这个线程,而非直接调用run();要想实现多线程,必须在主线程中创建新的线程对象。
线程的创建方式一:继承Thread类 1.创建一个继承于Thread类的子类 2.重写Thread类的run()方法 —>将此线程要执行的操作声明在此方法体中 3.创建当前Thread的子类的对象 4.通过对象调用start():启动线程;调用当前线程的run方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 package com.learning01.create.exer1;public class PrintNumberTest { public static void main (String[] args) { new Thread () { @Override public void run () { for (int i = 1 ; i <= 100 ; i++) { if (i % 2 == 0 ) { System.out.println(Thread.currentThread().getName() + ":" + i); } } } }.start(); new Thread () { @Override public void run () { for (int i = 1 ; i <= 100 ; i++) { if (i % 2 != 0 ) { System.out.println(Thread.currentThread().getName() + ":" + i); } } } }.start(); } } class EvenNumberPrint extends Thread { @Override public void run () { for (int i = 1 ; i <= 100 ; i++) { if (i % 2 == 0 ) { System.out.println(Thread.currentThread().getName() + ":" + i); } } } } class OddNumberTest extends Thread { @Override public void run () { for (int i = 1 ; i <= 100 ; i++) { if (i % 2 != 0 ) { System.out.println(Thread.currentThread().getName() + ":" + i); } } } }
线程的创建方式二:实现Runnable接口 1.创建一个实现Runnable接口的类 2.实现接口中的run()方法 —>将此线程要执行的操作声明在此方法体中 3.创建当前实现类的对象 4.将此对象作为参数传递到Thread构造器中,创建Thread类的实例 5.Thread类的实例调用start():启动线程;调用当前线程的run方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package com.learning01.create.runnable;public class EvenNumberTest { public static void main (String[] args) { EvenNumberPrint p = new EvenNumberPrint (); new Thread (p).start(); for (int i = 1 ; i < 100 ; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); } } } class EvenNumberPrint implements Runnable { @Override public void run () { for (int i = 1 ; i < 100 ; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); } } }
对比两种方式 共同点:1.启动线程,使用的都是Thread类中定义的start();2.创建的线程对象,都是Thread类或其子类的实例。 不同点:一个是类的继承,一个是接口的实现。 建议:建议使用实现Runnable接口的方式,好处是实现的方式避免了类的单继承的局限性,更适合来处理有共享数据的问题。
线程的常用结构 1.线程中的构造器
public Thread():分配一个新的线程对象 public Thread(String name):分配一个指定名字的新的线程对象 public Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run方法 public Thread(Runnable target, String name):分配一个带有指定目标新的线程对象并指定名字
2.线程中的常用方法
start():1.启动线程;2.调用线程的run(); run():将线程要执行的操作声明在run()方法中 currentThread():获取当前执行代码对应的线程 getName():获取线程名 setName():设置线程名 sleep():静态方法,让线程睡眠指定的毫秒数 yield():静态方法,一旦执行此方法,就释放CPU的执行权 join():在线程a中通过线程b调用join()方法,意味着线程a进入阻塞状态,直到线程b结束,线程a才结束阻塞状态继续执行 isAlive():判断当前线程是否存活
过时方法:
stop():强行结束一个线程的执行,直接进入死亡状态 void suspend() / void resume():暂停/恢复线程,可能造成死锁,不建议使用
3.线程的优先级
getPriority():获取线程的优先级 setPriority():设置线程的优先级,范围[1,10]
Thread类内部声明的三个常量 MAX_PRIOTITY(10):最高优先级 MIN_PRIOTITY(1):最低优先级 NORM_PRIOTITY(5):普通优先级,默认情况下main线程具有普通优先级
多线程的生命周期 JDK1.5之前:五种状态 线程的声明周期有五种状态:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(Dead)
JDK1.5及之后
线程的安全问题与同步机制 当我们使用多个线程访问同一资源 (可以是同一个变量、同一个文件、同一条记录等等)的时候,若多线程只有读的操作,那么不会有线程安全的问题。但是如果多个线程对同一个资源有读和写的操作 ,就容易出现线程安全的问题。
线程不安全的方法,使用实现Runnable接口的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package com.learning03.threadsafe.notsafe;public class WindowTest { public static void main (String[] args) { SaleTicket s = new SaleTicket (); Thread t1 = new Thread (s); Thread t2 = new Thread (s); Thread t3 = new Thread (s); t1.setName("窗口1" ); t2.setName("窗口2" ); t3.setName("窗口3" ); t1.start(); t2.start(); t3.start(); } } class SaleTicket implements Runnable { int ticket = 100 ; @Override public void run () { while (true ) { if (ticket > 0 ) { System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket); ticket--; } else { break ; } } } }
线程不安全的方法,使用继承Thread类的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package com.learning03.threadsafe.notsafe;public class WindowTest1 { public static void main (String[] args) { SaleTicket1 s1 = new SaleTicket1 (); SaleTicket1 s2 = new SaleTicket1 (); SaleTicket1 s3 = new SaleTicket1 (); s1.setName("窗口1" ); s2.setName("窗口2" ); s3.setName("窗口3" ); s1.start(); s2.start(); s3.start(); } } class SaleTicket1 extends Thread { int ticket = 100 ; @Override public void run () { while (true ) { if (ticket > 0 ) { System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket); ticket--; } else { break ; } } } }
1.多线程卖票,出现的问题:出现了重票、错票 2.什么原因导致的?
线程1要操作ticket的过程中尚未结束的情况下,其他的线程也参与进来对ticket进行操作,导致线程出现安全问题。
3.如何解决?
必须保证一个线程在操作ticket的过程中,其他线程必须等待,直到当前线程操作ticket结束以后其他线程才可以继续进行操作ticket。
4.Java是如何解决线程的安全问题的?
方法一:同步代码块
synchronized(同步监视器){ 需要被同步的代码 }
说明:需要被同步的代码即为操作共享数据的代码。 共享数据:即多个线程都需要操作的数据。比如ticket。 同步监视器:俗称锁,哪个线程获取了锁,哪个线程就能执行被同步的代码。可以使用任意一个类的对象充当,但是,多个线程必须共用同一个同步监视器。
注意:在实现Runnable接口的方式中,同步监视器可以考虑使用this;而在继承Thread类的方式中,同步监视器可以使用类名.class。
线程安全的,实现Runnable接口的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package com.learning03.threadsafe.runnablesafe;public class WindowTest { public static void main (String[] args) { SaleTicket s = new SaleTicket (); Thread t1 = new Thread (s); Thread t2 = new Thread (s); Thread t3 = new Thread (s); t1.setName("窗口1" ); t2.setName("窗口2" ); t3.setName("窗口3" ); t1.start(); t2.start(); t3.start(); } } class SaleTicket implements Runnable { int ticket = 100 ; Object obj = new Object (); @Override public void run () { while (true ) { synchronized (obj) { if (ticket > 0 ) { System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket); ticket--; } else { break ; } } } } }
线程安全的,继承Thread类的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 package com.learning03.threadsafe.threadsafe;public class WindowTest1 { public static void main (String[] args) { SaleTicket1 s1 = new SaleTicket1 (); SaleTicket1 s2 = new SaleTicket1 (); SaleTicket1 s3 = new SaleTicket1 (); s1.setName("窗口1" ); s2.setName("窗口2" ); s3.setName("窗口3" ); s1.start(); s2.start(); s3.start(); } } class SaleTicket1 extends Thread { static int ticket = 100 ; @Override public void run () { synchronized (SaleTicket1.class) { while (true ) { if (ticket > 0 ) { try { Thread.sleep(10 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket); ticket--; } else { break ; } } } } }
方法二:同步方法
如果操作共享数据的代码完整声明在了一个方法中,那么就可以将此方法声明为同步方法即可。 非静态的同步方法,默认的同步监视器是this 静态的同步方法,默认同步监视器默认是当前类本身
synchronized的好处:解决了线程的安全问题 弊端:在操作共享数据时,多线程其实是串行执行的,性能较低。
使用实现Runnable接口的方式使用同步方法保证线程的安全性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 package com.learning03.threadsafe.runnablesafe;public class WindowTest1 { public static void main (String[] args) { SaleTicket1 s = new SaleTicket1 (); Thread t1 = new Thread (s); Thread t2 = new Thread (s); Thread t3 = new Thread (s); t1.setName("窗口1" ); t2.setName("窗口2" ); t3.setName("窗口3" ); t1.start(); t2.start(); t3.start(); } } class SaleTicket1 implements Runnable { int ticket = 100 ; Boolean isFlag = true ; @Override public void run () { while (isFlag) { show(); } } public synchronized void show () { if (ticket > 0 ) { try { Thread.sleep(10 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket); ticket--; } else { isFlag = false ; } } }
使用继承Thread类的方式,使用同步方法实现线程安全
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 package com.learning03.threadsafe.threadsafe;public class WindowTest2 { public static void main (String[] args) { SaleTicket2 s1 = new SaleTicket2 (); SaleTicket2 s2 = new SaleTicket2 (); SaleTicket2 s3 = new SaleTicket2 (); s1.setName("窗口1" ); s2.setName("窗口2" ); s3.setName("窗口3" ); s1.start(); s2.start(); s3.start(); } } class SaleTicket2 extends Thread { static int ticket = 100 ; static Boolean isFlag = true ; @Override public void run () { while (isFlag) { show(); } } public void show () { synchronized (SaleTicket2.class) { try { Thread.sleep(10 ); } catch (InterruptedException e) { e.printStackTrace(); } if (ticket > 0 ) { System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket); ticket--; } else { isFlag = false ; } } } }
单例之懒汉式的线程安全问题 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 package com.learning05.threadsafemore.singleton;public class BankTest { static Bank b1 = null ; static Bank b2 = null ; public static void main (String[] args) { Thread t1 = new Thread () { @Override public void run () { b1 = Bank.getInstance(); } }; Thread t2 = new Thread () { @Override public void run () { b2 = Bank.getInstance(); } }; t1.start(); t2.start(); try { t1.join(); } catch (InterruptedException e) { throw new RuntimeException (e); } try { t2.join(); } catch (InterruptedException e) { throw new RuntimeException (e); } System.out.println(b1); System.out.println(b2); System.out.println(b1 == b2); } } class Bank { private Bank () { } private static Bank instance = null ; public static Bank getInstance () { synchronized (Bank.class) { try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } if (instance == null ) { instance = new Bank (); } return instance; } } }
线程同步机制带来的问题:死锁 1.如何看待死锁?
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。 编写程序时,要避免出现死锁。
2.诱发死锁的原因
互斥条件、占用且等待、不可抢占、循环等待
3.解决死锁
死锁一旦出现,基本上很难进行人为干预,只能尽量规避。可以考虑打破上面的诱发条件。 针对条件一:互斥条件基本上无法打破,因为线程需要互斥条件解决安全问题。 针对条件二:可以考虑一次性申请所有资源,这样就不存在等待的问题。 针对条件三:占用部分资源的进程在进一步申请其他资源时,如果申请不到就主动释放自身已经占用的资源。 针对条件四:可以将资源改为线性顺序,申请资源时,先申请序号较小的,这样避免循环等待问题。
Lock锁的使用 步骤: 1.创建lock的实例,需要确保多个线程共用一个Lock,需要将这个对象声明为static 2.执行lock方法,锁定对共享资源的调用 3.调用unlock,释放对共享数据的锁定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 package com.learning05.threadsafemore.singleton.lock;import java.util.concurrent.locks.ReentrantLock;public class LockTest { public static void main (String[] args) { SaleTicket1 s1 = new SaleTicket1 (); SaleTicket1 s2 = new SaleTicket1 (); SaleTicket1 s3 = new SaleTicket1 (); s1.setName("窗口1" ); s2.setName("窗口2" ); s3.setName("窗口3" ); s1.start(); s2.start(); s3.start(); } } class SaleTicket1 extends Thread { static int ticket = 100 ; private static ReentrantLock lock = new ReentrantLock (); @Override public void run () { while (true ) { try { lock.lock(); if (ticket > 0 ) { System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket); ticket--; } else { break ; } } finally { lock.unlock(); } } } }
Synchronized与Lock的对比 Synchronized不管是同步代码块还是同步方法,都需要在结束一对{}之后,释放对同步监视器的调用。 Lock是通过两个方法来控制需要同步的代码,相比较而言更灵活一些。 Lock作为接口提供了多种实现类,适合更多的、更复杂的场景、效率也会更高一些。
线程之间的通信 为什么需要处理线程之间的通信? 当我们需要多个线程来完成同一件任务,并且我们希望他们可以有规律的执行,那么多线程之间需要一些通信机制,可以协调它们的工作,以此实现多线程共同操作一份数据。
涉及到三个方法的使用 wait():线程一旦执行此方法,就进入等待状态。同时会释放对同步监视器的调用。wait()方法需要进行抛异常的操作。 notify():一旦执行此方法,就会唤醒被wait的线程中优先级最高的那个。 notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
案例1:使用两个线程交替打印1-100之间的数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 package com.learning06.communication;public class PrintNumberTest { public static void main (String[] args) { PrintNumber p1 = new PrintNumber (); Thread t1 = new Thread (p1, "线程一" ); Thread t2 = new Thread (p1, "线程二" ); t1.start(); t2.start(); } } class PrintNumber implements Runnable { private static int number = 1 ; @Override public void run () { while (true ) { synchronized (this ) { try { Thread.sleep(100 ); } catch (InterruptedException e) { e.printStackTrace(); } notify(); if (number <= 100 ) { System.out.println(Thread.currentThread().getName() + ":" + number); number++; try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { break ; } } } } }
三个注意点 此三个方法的使用,必须是在同步代码块或者同步方法中。 Lock需要配合Condition配合线程之间的通信。 此三个方法的调用者必须是同步代码块或是同步方法中的同步监视器。否则会报错。
wait()和sleep()的区别 相同点: 两个方法一旦执行都会导致当前线程进入阻塞的状态
不同点: 声明的位置:wait()声明在Object类中,sleep()声明在Thread类中; 使用的场景不同:wait()只能使用在同步代码块或同步方法中,sleep()可以使用在任意位置; 使用在同步代码块或同步方法中:wait()会释放同步监视器,sleep()不会释放同步监视器; 结束阻塞的方式:wait():到达指定时间自动结束阻塞或通过被notify()唤醒结束阻塞,sleep():到达时间自动结束阻塞。
案例2:生产者和消费者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 package com.learning06.communication;public class ProducerConsumerTest { public static void main (String[] args) { Clerk clerk = new Clerk (); Producer producer = new Producer (clerk); Consumer consumer = new Consumer (clerk); producer.setName("生产者1" ); consumer.setName("消费者1" ); producer.start(); consumer.start(); } } class Clerk { private static int ProductNumber = 0 ; public synchronized void addProduct () { if (ProductNumber >= 20 ) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { ProductNumber++; System.out.println(Thread.currentThread().getName() + "生产者生产了第" + ProductNumber + "个产品" ); notifyAll(); } } public synchronized void minusProduct () { if (ProductNumber <= 0 ) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { System.out.println(Thread.currentThread().getName() + "消费者消费了第" + ProductNumber + "个产品" ); ProductNumber--; notifyAll(); } } } class Producer extends Thread { private Clerk clerk; public Producer (Clerk clerk) { this .clerk = clerk; } @Override public void run () { while (true ) { System.out.println("生产者开始生产产品" ); try { Thread.sleep(50 ); } catch (InterruptedException e) { e.printStackTrace(); } clerk.addProduct(); } } } class Consumer extends Thread { private Clerk clerk; public Consumer (Clerk clerk) { this .clerk = clerk; } @Override public void run () { while (true ) { System.out.println("消费者开始消费产品" ); try { Thread.sleep(50 ); } catch (InterruptedException e) { e.printStackTrace(); } clerk.minusProduct(); } } }
新增两种创建线程的方式 创建多线程的方式之三:实现Callable(jdk5.0新增) 与Runnable作对比: 优点:call()方法可以有返回值;call()方法可以使用throws的方式处理异常,更灵活;Callable使用了泛型参数,可以指明具体的返回值类型,更灵活。 缺点:如果在主线程中需要获取分线程call()的返回值,则此时的主线程是阻塞状态的。
创建多线程的方式之四:使用线程池 现有问题:如果并发的线程有很多,并且每一个线程都是执行一个时间很短的任务就结束了,这样频繁创建、销毁线程就会大大降低系统的效率。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁销毁创建、实现重复利用。
此方法的好处:提高了程序执行的效率;提高了资源的复用率;可以设置相关的参数,对线程池中的线程的使用进行管理。
常用类与基础API String类
类的声明:
public final class String implements java.io.Serializable, Comparable, CharSequence, Constable, ConstantDesc
final:表示String是不可被继承的。 Serializable:是可序列化的接口,凡是实现此接口的对象都可以通过网络或本地流进行数据的传输。 Comparable:凡是实现此接口的类,其对象都可以比较大小。 CharSequence:实现字符序列的接口。
内部声明的属性
jdk8中: private final char[] value;
存储字符串数据的容器。 final:指明此value数组一旦初始化,其地址就不可变。
jdk9及之后: private final byte[] value; 替换成byte是为了节省内存空间。
字符串常量的存储位置
字符串常量都存储在字符串常量池(StringTable)中 字符串常量池不允许存放两个相同的字符串常量 字符串常量池在不同的jdk版本中,存放位置不同 jdk7之前存放在方法区 jdk7及之后存放在堆空间
String的不可变性的理解 (1)当字符串变量重新赋值时,需要重新指定一个字符串常量的位置进行赋值,不能在原有的地方修改。 (2)当对现有的字符串进行拼接操作时,需要重新开辟空间保存拼接之后的字符串,不能在原有的地方修改。 (3)当调用字符串的replace()方法替换某个现有的字符时,仍需要重新开辟空间保存新的字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 package com.learning01.string;import org.junit.Test;public class StringDemo { @Test public void test1 () { String s1 = "hello" ; String s2 = "hello" ; System.out.println(s1 == s2); } @Test public void test2 () { String s1 = "hello" ; String s2 = "hello" ; s2 = "h1" ; System.out.println(s1); System.out.println(s2); } @Test public void test3 () { String s1 = "hello" ; String s2 = "hello" ; String s3 = s2.replace('l' , 'w' ); System.out.println(s1); System.out.println(s2); System.out.println(s3); } } 5. String实例化的两种方式String s1 = "hello" ;String s2 = new String ("hello" );其中第二种方式创建了几个对象? 两个 一个是堆空间中new 的对象。另外一个是在字符串常量池中生成的字面量。 6. String的连接操作情况1 :常量 + 常量:结果仍然存储在字符串常量池中。 情况2 :常量 + 变量 或 变量 + 变量:会通过new 的方式创建新的字符串,返回堆空间中字符串对象的地址。 情况3 :调用intern()方法,返回的是字符串常量池中字面量的地址。 情况4 :concat():不管是常量调用此方法,还是变量调用此方法,同样不管参数是常量还是变量,总之,调用完之后都返回一个新new 的对象。 ``` java package com.learning01.string;import org.junit.Test;public class StringDemo1 { @Test public void test () { String s1 = "hello" ; String s2 = "hello" ; String s3 = new String ("hello" ); String s4 = new String ("hello" ); System.out.println(s1 == s2); System.out.println(s1 == s3); System.out.println(s3 == s4); } @Test public void test1 () { String s1 = "hello" ; String s2 = "world" ; String s3 = "helloworld" ; String s4 = "hello" + "world" ; String s5 = s1 + "world" ; String s6 = "hello" + s2; String s7 = s1 + s2; System.out.println(s3 == s4); System.out.println(s3 == s5); System.out.println(s3 == s6); System.out.println(s3 == s7); System.out.println(s5 == s6); System.out.println(s3 == s7); String s8 = s5.intern(); System.out.println(s3 == s8); } @Test public void test2 () { final String s1 = "hello" ; String s2 = "world" ; String s3 = "helloworld" ; String s4 = "hello" + "world" ; String s5 = s1 + "world" ; String s6 = "hello" + s2; String s7 = s1 + s2; System.out.println(s3 == s5); } @Test public void test3 () { String s1 = "hello" ; String s2 = "world" ; String s3 = s1.concat(s2); String s4 = "hello" .concat("world" ); String s5 = s1.concat("world" ); System.out.println(s3 == s4); System.out.println(s3 == s5); System.out.println(s4 == s5); } }
String的构造器
public String():初始化新创建的String对象,以使其表示空字符序列
public String(String original):初始化一个新创建’String’对象,使其表示一个与参数相同的字符序列;换句话说
public String(char[] value):通过当前参数中的字符数组来构造新的String
public String(char[] value, int offset, int count):通过字符数组的一部分来构造新的String
public String(byte[] bytes):通过使用平台的默认字符集 解码当前参数中的字节数组来构造新的String
public String(byty[] bytes, String charsetName):通过使用指定的字符集解码当前参数中的字节数组来构造新的String
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 package com.learning01.string;import org.junit.Test;import java.io.UnsupportedEncodingException;public class StringMethodTest { @Test public void test1 () { String s1 = new String (); String s2 = new String ("" ); String s3 = new String (new char []{'a' , 'b' , 'c' }); System.out.println(s3); } @Test public void test2 () { String str = "hello" ; char [] charArray = str.toCharArray(); System.out.println(charArray); String str1 = new String ((charArray)); System.out.println(str1); } @Test public void test3 () throws UnsupportedEncodingException { String str = new String ("hello" ); byte [] bytes = str.getBytes(); for (int i = 0 ; i < bytes.length; i++) { System.out.println(bytes[i]); } byte [] arr1 = str.getBytes("gbk" ); for (int i = 0 ; i < arr1.length; i++) { System.out.println(arr1[i]); } String s = new String (arr1); System.out.println(s); } }
String的常用方法
boolean isEmpaty():字符串是否为空 int length():返回字符串的长度 String concat(xx):拼接 boolean equals(Object obj):比较字符串是否相等,区分大小写 boolean equalsIgnoreCase(Object obj):比较字符串是否相等,不区分大小写 int compareTo(String other):比较字符串的大小,区分大小写,按照Unicode编码值比较大小 int compareToIgnore(String other):比较字符串的大小,不区分大小写 String toLowerCase():将字符串中的大写字母转换为小写字母 String toUpperCase():将字符串中的小写字母转换为大写字母 String trim():去掉字符串前后的空白符 public String intern():结果在常量池中共享
boolean contains(xx):是否包含xx int indexOf(xx):从前往后找当前字符串当中的xx,如果有,则返回第一次出现的下标,如果没有,则返回-1 int indexOf(String str, int fromIndex):返回指定字符串在此字符串中第一次出现处的下标,从指定索引开始 int lastIndexOf(xx):从后往前找当前字符串当中的xx,如果有,则返回最后一次出现的下标,如果没有,则返回-1 int lastIndexOf(String str, int fromIndex):返回指定字符串在此字符串中最后一次出现的下标,如果没有,则返回-1
String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取,一直取到最后 String substring(int beginIndex, int endIndex):返回一个新的字符串,从此字符串的beginIndex开始截取,一直到endIndex(不包括)
char charAt(index):返回[index]位置的字符 char[] toCharArray():将此字符串转换为一个新的字符数组并返回 static String valueOf(char[] data):返回指定数组中表示给字符序列的String static String valueOf(char[] data, int offset, int count):返回指定数组中表示该字符序列的String static String copyValueOf(char[] data):返回指定数组中表示该字符序列的String static String copyValueOf(char[] data, int offset, int count):返回指定数组中表示该字符序列的String
boolean startsWith(xx):测试此字符串是否以指定的前缀开始 boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子串是否以指定的前缀开始 boolean endWith(xx):测试此字符串是否以指定的后缀结束
String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用newChar替换此字符串中所有的oldChar得到的,不支持正则 String replace(CharSequence target, CharSequence replacement):使用指定的字面值序列替代此字符串所有匹配字面值目标序列的子字符串 String replace(String regex, String replacement):使用给定的replacement替换此字符串中所有匹配给定的正则表达式的子字符串 String replaceFirst(String regex, String replacement):使用给定的replacement替换此字符串中所有匹配给定的正则表达式的第一个子字符串
StringBuffer和StringBuilder
这三个类有什么区别?
String:不可变的字符序列; StringBuffer:可变的字符序列;JDK1.0声明,线程安全的,效率低; StringBuilder:可变的字符序列;JDK5.0声明,线程不安全,效率高;
StringBuffer和StringBuilder的可变性分析
针对于String: String s1 = new String(); //char[] value = new char[0]; String s2 = new String(“abc”); //char[] value = new char[3]{‘a’,’b’,’c’};
针对于StringBuilder: 内部的属性有: char[] value; //用来存储字符序列 int count; //实际存储的字符的个数
StringBuilder sBuilder = new StringBuilder(); //char[] value = new Char[16]; StringBuilder sBuilder = new StringBuilder(“abc”); //char[] value = new Char[16+”abc”.length];
不断添加新的元素,一旦count超过value.length时,就需要扩容,默认扩容为原有容量的2倍+2,并将原有value数组中的元素复制到新的数组当中。
源码启示
如果开发中需要频繁的针对于字符串进行增删改查操作,建议使用StringBuffer或StringBuilder。
如果开发中,不涉及到线程安全问题,建议使用StringBuilder替换StringBuffer。
如果开发中大体确定要操作的字符的个数,建议使用带int capacity参数的构造器,可以避免底层多次扩容操作,性能更高。
StringBuffer和StringBuilder中常用的方法
StringBuffer append(xx):提供了很多的append()方法,用于进行字符串追加的方式拼接 StringBuffer delete(int start, int end):删除[start,end)之间的字符 StringBuffer deleteCharAt(int index):删除[index]位置字符 StringBuffer replace(int start, int end, String str):替换[start,end)范围的字符序列 void setCharAt(int index, char c):替换[index]位置字符 char charAt(int index):查找指定index位置上的字符 StringBuffer insert(int index, xx):在[index]位置上插入xx int length():返回存储字符数据的长度 StringBuffer reverse():反转
对比三者的执行效率
StringBuilder > StringBuffer > String
日期时间的API使用 JDK8之前的API
System类的currentTimeMillis()
获取当前时间对应的毫秒数,long类型,时间戳 当前时间与1970年1月1日0时0分0秒之间的毫秒数 常用来计算时间差
两个Date类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.learning03.date.before8;import org.junit.Test;import java.util.Date;public class DateTimeTest { @Test public void test () { Date date1 = new Date (); System.out.println(date1.toString()); long time = date1.getTime(); System.out.println("对应的毫秒数为:" + time); Date date2 = new Date (1675087138561L ); System.out.println(date2.toString()); } @Test public void test1 () { java.sql.Date date = new java .sql.Date(1675087138561L ); System.out.println(date.toString()); } }
SimpleDateFormat类
java.text.SimpleDateFormat类是一个不与语言环境有关的方式来格式化和解析日期的具体类。
格式化:将日期转换成指定格式的字符串 解析:将字符串转换为一个日期
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Test public void test2 () throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat (); Date date = new Date (); String format = sdf.format(date); System.out.println(format); Date parse = sdf.parse("2023/9/19 09:53" ); System.out.println(parse); } @Test public void test3 () throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat ("yyyyy.MMMMM.dd GGG hh:mm aaa" ); Date date = new Date (); String format = sdf.format(date); System.out.println(format); Date parse = sdf.parse("02023.九月.19 公元 10:01 上午" ); System.out.println(parse); }
java.util.Calendar(日历)
Date类中大部分的API被废弃了,替换为Calendar Calendar是一个抽象类,主要用于完成日期字段之间相互操作的功能 获取Calendar实例的方法:由于Calendar是一个抽象类,所以需要通过Calendar的静态方法getInstance()即可获取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Test public void test4 () { Calendar instance = Calendar.getInstance(); System.out.println(instance.getClass()); System.out.println(instance.get(Calendar.DAY_OF_MONTH)); System.out.println(instance.get(Calendar.DAY_OF_WEEK)); instance.set(Calendar.DAY_OF_MONTH, 23 ); System.out.println(instance.get(Calendar.DAY_OF_MONTH)); instance.add(Calendar.DAY_OF_MONTH, 3 ); System.out.println(instance.get(Calendar.DAY_OF_MONTH)); Date date = instance.getTime(); System.out.println(date); Date date1 = new Date (); instance.setTime(date1); System.out.println(instance.get(Calendar.DAY_OF_MONTH)); }
JDK8新增的日期API 可变性:像日期时间这样的类应该是不可变的 偏移性:Date中的年份是从1900年开始的,而月份都从0开始 格式化:格式化只对Date有用,Calendar则不行 此外,它们的线程都是不安全的;不能处理闰秒等
本地日期时间LocalDate、LocalTime、LocalDateTime 实例化:now()/of() 方法:get、with、plus、minus等等
瞬时:Instant 实例化:now()/ofEpochMilli() 方法:toEpochMilli()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 package com.learning03.date.jdk8;import org.junit.Test;import java.time.*;import java.time.format.DateTimeFormatter;import java.time.temporal.TemporalAccessor;public class DateTimeTest { @Test public void test1 () { LocalDate localDate = LocalDate.now(); LocalTime localTime = LocalTime.now(); LocalDateTime localDateTime = LocalDateTime.now(); System.out.println(localDate); System.out.println(localTime); System.out.println(localDateTime); LocalDate localDate1 = LocalDate.of(2021 , 5 , 3 ); LocalDateTime localDateTime1 = LocalDateTime.of(2022 , 12 , 5 , 11 , 23 , 45 ); System.out.println(localDate1); System.out.println(localDateTime1); LocalDate localDate2 = LocalDate.now(); System.out.println(localDate2.getDayOfMonth()); LocalDate localDate3 = localDate2.withDayOfMonth(15 ); System.out.println(localDate2); System.out.println(localDate3); } @Test public void test2 () { Instant now = Instant.now(); System.out.println(now); OffsetDateTime offsetDateTime = now.atOffset(ZoneOffset.ofHours(8 )); System.out.println(offsetDateTime); Instant instant = Instant.ofEpochMilli(24123123312L ); System.out.println(instant); } @Test public void test3 () { DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss" ); LocalDateTime localDateTime = LocalDateTime.now(); String format = dateTimeFormatter.format(localDateTime); System.out.println(format); TemporalAccessor parse = dateTimeFormatter.parse("2023-09-21 10:49:44" ); System.out.println(parse); } }
使用Comparable接口实现自然排序 实现对象的排序,可以考虑两种方法:自然排序、定制排序
Product类,里面必须实现Comparable接口并且实现对应的抽象方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 package com.learning04.compare;public class Product implements Comparable { private String name; private double price; @Override public int compareTo (Object o) { if (o == this ) { return 0 ; } if (o instanceof Product) { Product p = (Product) o; return Double.compare(this .price, p.price); } throw new RuntimeException ("类型不匹配" ); } public Product (String name, double price) { this .name = name; this .price = price; } public Product () { } public String getName () { return name; } public void setName (String name) { this .name = name; } public double getPrice () { return price; } public void setPrice (double price) { this .price = price; } @Override public String toString () { return "Product{" + "name='" + name + '\'' + ", price=" + price + '}' ; } }
方式一:采用实现Comparable接口的方式
实现步骤:
具体的类A实现Comparable接口。
重写Comparable接口中的compareTo(Object obj)方法,在此方法中指明比较类A的对象的大小的标准。
创建类A的多个实例,进行大小的比较、排序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package com.learning04.compare.comparable;import com.learning04.compare.Product;import org.junit.Test;import java.util.Arrays;public class ComparableTest { @Test public void Test1 () { String[] arr = new String []{"Tom" , "Jerry" , "Tony" , "Rose" , "Jack" , "Lucy" }; Arrays.sort(arr); for (int i = 0 ; i < arr.length; i++) { System.out.println(arr[i]); } } @Test public void Test2 () { Product[] arr = new Product [5 ]; arr[0 ] = new Product ("HuaWeiMate60Pro" , 6999 ); arr[1 ] = new Product ("Xiaomi13Pro" , 4999 ); arr[2 ] = new Product ("VivoX90" , 5999 ); arr[3 ] = new Product ("iPhone14ProMax" , 9999 ); arr[4 ] = new Product ("HonorMagic4" , 6299 ); Arrays.sort(arr); for (int i = 0 ; i < arr.length; i++) { System.out.println(arr[i]); } } }
方式二:实现Comparator接口的方式
实现步骤:
创建了一个实现了Comparator接口的实现类。
实现类要求重写Comparator接口中的抽象方法compare(Object o1, Object o2),在此方法中指明要比较大小的对象的大小关系。
创建此实现类的对象,并将此对象传入到Arrays.sort()的参数位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 package com.learning04.compare.comparator;import com.learning04.compare.Product;import org.junit.Test;import java.nio.DoubleBuffer;import java.util.Arrays;import java.util.Comparator;public class ComparatorTest { @Test public void test1 () { Product[] arr = new Product [5 ]; arr[0 ] = new Product ("HuaWeiMate60Pro" , 6999 ); arr[1 ] = new Product ("Xiaomi13Pro" , 4999 ); arr[2 ] = new Product ("VivoX90" , 5999 ); arr[3 ] = new Product ("IPhone14ProMax" , 9999 ); arr[4 ] = new Product ("HonorMagic4" , 6299 ); Comparator comparator = new Comparator () { @Override public int compare (Object o1, Object o2) { if (o1 instanceof Product && o2 instanceof Product) { Product p1 = (Product) o1; Product p2 = (Product) o2; return Double.compare(p1.getPrice(), p2.getPrice()); } throw new RuntimeException ("类型不匹配" ); } }; Arrays.sort(arr, comparator); for (int i = 0 ; i < arr.length; i++) { System.out.println(arr[i]); } Comparator comparator1 = new Comparator () { @Override public int compare (Object o1, Object o2) { if (o1 instanceof Product && o2 instanceof Product) { Product p1 = (Product) o1; Product p2 = (Product) o2; return p1.getName().compareTo(p2.getName()); } throw new RuntimeException ("类型不匹配" ); } }; Arrays.sort(arr, comparator1); for (int i = 0 ; i < arr.length; i++) { System.out.println(arr[i]); } } @Test public void test2 () { String[] arr = new String []{"Tom" , "Jerry" , "Tony" , "Rose" , "Jack" , "Lucy" }; Arrays.sort(arr, new Comparator <String>() { @Override public int compare (String o1, String o2) { if (o1 instanceof String && o2 instanceof String) { String s1 = (String) o1; String s2 = (String) o2; return -s1.compareTo(s2); } throw new RuntimeException ("类型不匹配" ); } }); for (int i = 0 ; i < arr.length; i++) { System.out.println(arr[i]); } } }
对比两种方式:
角度一: 自然排序:单一的、唯一的; 定制排序:灵活的、多样的;
角度二: 自然排序:一劳永逸的; 定制排序:临时的;
角度三: 自然排序:对应的接口是Comparable,对应的方法是compareTo(Object obj); 定制排序:对应的接口是Comparator,对应的方法是compare(Object obj1, Object obj2);
其他常用类的使用
System类
属性:out、in、err 方法:currentTimeMillis()、gc()、exit(status)、getProperty(String property)
Runtime类
对应着Java进程的内存使用的运行时环境,是单例的。
Math类
凡是与数学有关的操作,都可以找到相关的方法。 ceil、floor、round、pow、sqrt、random…
BigInteger类和BigDecimal类
BigInteger可以表示不可变的任意精度的整数,主要用于表示long类型都存不下的长度的数值。 BigDecimal可以表示任意精度的浮点数,要求的数字精度比较高,常用来科学计算或工程计算。
集合框架 数组的特点、弊端
内存层面需要针对于多个数据进行存储。此时,可以考虑的容器有:数组、集合类
数组存储多个数据方面的特点:
数组一旦初始化,其长度就是确定的。 数组中的多个元素是紧密排列的、有序的、可重复的。 数组一旦初始化完成,其元素类型就是确定的。不是此类型的元素,就不能添加到此数组中。 数组元素的类型既可以是基本数据类型,也可以是引用数据类型。
数组存储多个数据方面的弊端:
数组一旦初始化,长度就不可变了。 数组中存储数据特点的单一性。对于无序的、不可重复的场景的多个数据就无能为力了。 数组中可用的方法、属性都极少。具体的需求都需要自己组织相关的代码逻辑。 针对于数组中元素的删除、插入操作,性能较差。
Java集合框架体系
java.util.Collection:存储一个一个的数据 子接口: List:存储有序的、可重复的数据(动态数组) ArrayList(主要实现类)、LinkedList、Vector Set:存储无序的、不可重复的数据 HashSet、LinkedHashSet、TreeSet
java.util.Map:存储一对一对的数据(key-value键值对) HashMap、LinkedHashMap、TreeMap、Hashtable、Properties
Collection接口
添加 add(Object obj):添加元素对象到当前集合当中;如果添加的是一个集合的话,那么不管添加的集合中有多少个元素,添加到当前集合当中之后,算做一个整体,总长度只加一。 addAll(Collection other):添加other集合中的所有元素对象到当前集合中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package com.learning01.collection;import org.junit.Test;import java.util.ArrayList;import java.util.Collection;public class CollectionTest { @Test public void test1 () { Collection coll = new ArrayList (); coll.add("AA" ); coll.add(123 ); coll.add("扫黄大队" ); coll.add(new Object ()); coll.add(new Person ("Tom" , 33 )); System.out.println(coll); Collection coll1 = new ArrayList (); coll1.addAll(coll); System.out.println(coll1); System.out.println(coll.size()); coll1.add(coll); System.out.println(coll1); System.out.println(coll1.size()); } }
运行截图:
int size():获取当前集合中实际存储的元素的个数
boolean isEmpty():判断当前集合是否为空集合
boolean contains(Object obj):判断当前集合是否存在一个与obj对象equals返回true的元素
boolean containsAll(Collection coll):判断coll集合中的元素是否都在当前集合中存在
boolean equals(Object obj):判断当前集合与obj是否相等
1 2 3 4 5 6 7 8 9 10 11 12 13 @Test public void test2 () { Collection collection = new ArrayList (); collection.add("Aa" ); System.out.println(collection.isEmpty()); System.out.println(collection.contains("Aa" )); Collection collection1 = new ArrayList (); collection1.add("cc" ); System.out.println(collection.containsAll(collection1)); }
删除
void clear():清空集合 boolean remove(Object obj):从当前集合当中删除第一个找到的与obj对象equals返回true的元素 boolean removeAll(Collection coll):从当前集合中删除两个集合中不同的元素,仅保留两个集合相同的元素
集合与数组的相互转换
集合 —> 数组:toArray() 数组 —> 集合:Arrays.asList(T…)
1 2 3 4 5 6 @Test public void test3 () { String arr[] = new String []{"AA" , "BB" , "CC" }; List<String> list = Arrays.asList(arr); System.out.println(list); }
向Collection中添加元素的要求
要求元素所属的类一定要重写equals() 因为Collection中的相关方法(contains、remove等)在使用时,要调用元素所在类的equals()
Iterator迭代器
(1). 迭代器的作用:用来遍历集合元素 (2). 如何获取迭代器对象:Iterator iterator = collection.iterator(); (3). 如何实现遍历:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Test public void test1 () { Collection collection = new ArrayList (); collection.add("AA" ); collection.add("BB" ); collection.add("CC" ); Iterator iterator = collection.iterator(); System.out.println(iterator.next()); System.out.println(iterator.next()); System.out.println(iterator.next()); for (int i = 0 ; i < collection.size(); i++) { System.out.println(iterator.next()); } while (iterator.hasNext()) { System.out.println(iterator.next()); } }
增强for循环(foreach)jdk5.0新特性
(1). 作用:用来遍历数组、集合 (2). 格式:for(要遍历的集合或数组元素的类型 临时变量名 : 要遍历的集合或数组名){针对临时变量的一些操作} (3). 说明:针对于集合来讲,foreach底层仍然使用的是迭代器;该循环执行过程中,是将集合或数组的元素依次赋值给临时变量,循环体内对临时变量进行修改,不会对原有数组或集合元素造成影响。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Test public void test1 () { Collection collection = new ArrayList (); collection.add("AA" ); collection.add("BB" ); collection.add("CC" ); for (Object obj : collection) { System.out.println(obj); } } @Test public void test2 () { int [] arr = new int []{1 , 2 , 3 }; for (int a : arr) { System.out.println(a); } }
Collection的子接口List
List接口中存储数据的特点
用于存储有序的、可以重复的数据。 —> 使用List替代数组,“动态”数组
List中的常用方法
(1). Collection中声明的15个方法 (2). 因为List是有序的,进而增加了一些索引操作的方法 插入元素: void add(int index, Object ele):在index位置插入ele元素 boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来 获取元素: Object get(int index):获取指定index位置的元素 List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合 获取元素索引: int indexOf(Object obj):返回obj在集合中首次出现的位置 int lastIndexOf(Object obj):返回obj在当前集合最后一次出现的位置 删除和替换元素: Object remove(int index):移除指定index位置的元素,并返回此元素 Object set(int index, Object ele):设置指定index位置的元素为ele
List及其实现类的特点 java.util.Collection:存储一个一个的数据 子接口:List:存储有序的、可重复的数据(“动态”数组) ArrayList:List的主要实现类;线程不安全的,效率高;底层使用Object[]数组存储;在添加数据和查找数据时,效率较高,在删除和插入数据时,效率较低 LinkedList:底层使用双向链表的方式进行存储;在删除和插入数据时,效率较高,在添加数据和查找数据时,效率较低;在对集合中的数据频繁进行删除、插入操作时,建议使用此类 Vector:List的古老实现类;线程安全的,效率低,底层使用Object[]数组存储。
练习: 题目要求:案例:键盘录入学生信息,保存到集合List当中。
1.定义学生类,属性姓名、年龄,提供必要的getter、setter方法,构造器,toString(),equals()方法。
2.使用ArrayList集合,保存录入多个学生对象。
3.循环录入方式:1:继续录入 2:结束录入。
4.录入结束后,用foreach遍历集合。
代码1:JavaBean设计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 package com.learning03.list.exer1;import java.util.Objects;public class Student { private String name; private int age; public Student () { } public Student (String name, int age) { this .name = name; this .age = age; } public String getName () { return name; } public void setName (String name) { this .name = name; } public int getAge () { return age; } public void setAge (int age) { this .age = age; } @Override public boolean equals (Object o) { if (this == o) return true ; if (o == null || getClass() != o.getClass()) return false ; Student student = (Student) o; return age == student.age && Objects.equals(name, student.name); } @Override public String toString () { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}' ; } }
代码2:实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package com.learning03.list.exer1;import java.util.ArrayList;import java.util.Collection;import java.util.List;import java.util.Scanner;public class StudentTest { public static void main (String[] args) { List list = new ArrayList (); System.out.println("请输入数字以进行操作:1.继续录入 2.结束录入" ); Scanner scanner = new Scanner (System.in); while (scanner.nextInt() == 1 ) { System.out.println("继续录入..." ); System.out.println("请输入学生姓名:" ); String name = scanner.next(); System.out.println("请输入学生年龄:" ); int age = scanner.nextInt(); list.add(new Student (name, age)); System.out.println("请输入数字以进行操作:1.继续录入 2.结束录入" ); } System.out.println("结束录入。" ); System.out.println("开始遍历数据..." ); for (Object o : list) { System.out.println(o); } scanner.close(); } }
Collection的子接口Set
Set及其实现类特点 java.util.Collection:存储一个一个的数据 子接口:Set:存储无序的、不可重复的数据 HashSet:主要实现类;底层使用的是HashMap,即使用数组+单向链表+红黑树的结构进行存储。 LinkedHashSet:HashSet的子类,在HashSet的基础上,又添加了一组双向链表,用于记录添加元素的先后顺序。就可以根据添加元素的顺序实现遍历操作。便于频繁的查询操作。 TreeSet:底层使用红黑树存储。可以按照添加的元素指定的属性的大小顺序进行遍历。
开发中的使用频率及场景: 较List、Map来说,Set使用的频率比较少。 用来过滤重复数据
Set中常用方法:Collection中声明的15个抽象方法,没有新增的方法。
Set中无序性、不可重复性的理解(以HashSet及其子类为例说明)
无序性:不等于随机性;与添加的元素位置有关,不像ArrayList一样是一次紧密排列的。 - 不可重复性:添加到Set中的元素是不能相同的。 比较的标准:需要判断hashCode()得到的哈希值以及equals()得到的boolean型的结果。 哈希值相同且equals()返回true,则认为元素是相同的。
添加到HashSet/LinkedHashSet中元素的要求: 要求元素所在的类要重写两个方法:equals()和hashCode() 同时,要求equals()和hashCode()要保持一致性,只需要在IDEA中自动生成就可以保证一致性。
TreeSet的使用:
底层的数据结构:红黑树
添加数据后的特点:可以按照添加的元素的指定的属性的大小顺序进行遍历。
向TreeSet中添加元素的要求:
添加到TreeSet中的元素必须是同一个类型的对象,否则会报错。
判断数据是否相同的标准:
不再是考虑hashCode()和equals()方法了,也就意味着添加到TreeSet中的元素所在的类不需要重写这两个方法了。
比较元素大小的或比较元素是否相等的标准就是考虑自然排序或定制排序中compareTo()和compare()的返回值;如果返回值为0,则认为两个对象是相等的。
SetTest
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 package com.learning04.set; import org.junit.Test;import java.util.HashSet;import java.util.Iterator;import java.util.LinkedHashSet;import java.util.Set;public class SetTest { @Test public void test1 () { Set set = new HashSet (); set.add("aa" ); set.add("bb" ); set.add("cc" ); set.add("dd" ); set.add(new Person ("小红" , 23 )); Iterator iterator = set.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } System.out.println(set.contains(new Person ("小红" , 23 ))); } @Test public void Test2 () { Set set = new LinkedHashSet (); set.add("aa" ); set.add("bb" ); set.add("cc" ); set.add("dd" ); set.add(new Person ("小红" , 23 )); Iterator iterator = set.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } } }
TreeSetTest
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 package com.learning04.set;import org.junit.Test;import java.util.Comparator;import java.util.Iterator;import java.util.TreeSet;public class TreeSetTest { @Test public void Test1 () { TreeSet treeSet = new TreeSet (); treeSet.add("AA" ); treeSet.add("BB" ); treeSet.add("CC" ); Iterator iterator = treeSet.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } } @Test public void Test2 () { TreeSet treeSet = new TreeSet (); treeSet.add(new User ("小杰" , 23 )); treeSet.add(new User ("小红" , 23 )); treeSet.add(new User ("小民" , 23 )); Iterator iterator = treeSet.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } } @Test public void Test3 () { Comparator comparator = new Comparator () { @Override public int compare (Object o1, Object o2) { if (o1 instanceof User && o2 instanceof User) { User user1 = (User) o1; User user2 = (User) o2; int value = user1.getName().compareTo(user2.getName()); if (value != 0 ) { return value; } return -(user1.getAge() - user2.getAge()); } throw new RuntimeException ("类型不匹配" ); } }; TreeSet treeSet = new TreeSet (comparator); treeSet.add(new User ("小杰" , 23 )); treeSet.add(new User ("小红" , 22 )); for (Object o : treeSet) { System.out.println(o); } } }
Map接口
java.util.Map:存储一对一对的数据(key-value键值对) HashMap:主要实现类;线程不安全的;效率高;可以添加null的key和value值;底层使用了数组+单向链表+红黑树结构存储 LinkedHashMap:是HashMap的子类;在HashMap数据结构基础上增加了一对双向链表,用于记录添加的元素的先后顺序; 进而在遍历元素时就可以按照添加的顺序显示,对于频繁的遍历操作,建议使用此类。 TreeMap:底层使用红黑树存储;可以按照添加的元素的指定的属性的大小进行遍历,需要考虑使用自然排序或定制排序 Hashtable:古老实现类;线程安全的;效率低;不可以添加null的key和value值;底层使用了数组+单向链表结构存储 properties:存储的键值对都是String类型的,常用来处理属性文件
HashMap的特点: HashMap中元素的key彼此之间是不可重复的、无序的。所有的key就构成一个Set集合;key所在的类要重写hashCode()和equals() HashMap中的所有的value彼此之间是可重复的、无序的。所有的value构成了一个Collection集合;value所在的类要重写equals() HashMap中的一个key-value,就构成了一个entry HashMap中的所有的entry是不可重复的、无序的。所有的entry构成了一个Set集合
Map中的常用方法:
添加、修改操作: Object put(Object kry, Object value):将指定的key-value添加到(修改)当前map对象当中。 void putAll(Map m):将m中所有的key-value存放到当前map中。 删除操作: Object remove(Object key):移除指定key的key-value对,并返回value。 void clear():清空当前map中的所有数据。 元素查询的操作: Object get(Object key):获取指定key对应的value。 boolean containsKey(Object key):是否包含指定的key。 boolean containsValue(Object value):是否包含指定的value。 int size():返回map中key-value对个数。 boolean isEmpty():判断当前map是否为空。 boolean equals(Object obj):判断当前map和参数对象obj是否相等。 元视图操作方法: Set keySet():返回所有key构成的set集合。 Collection values():返回所有value构成的Collection集合。 Set entrySet():返回所有key-value对构成的set集合。
TreeMap的使用:
底层使用红黑树进行存储,可以按照添加的key-value中的key元素的指定的属性大小顺序进行遍历,需要考虑使用自然排序、定制排序。 要求:向TreeMap中添加的key必须是同一个类型的
Hashtable与Properties的使用
Properties是Hashtable的一个子类,key和value都是String类型的,常用来处理属性文件。
TreeMap测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 package com.learning05.map;import org.junit.Test;import java.util.Comparator;import java.util.TreeMap;public class TreeMapTest { @Test public void Test1 () { TreeMap map = new TreeMap (); map.put("CC" , 89 ); map.put("BB" , 77 ); map.put("AA" , 84 ); map.put("DD" , 62 ); for (Object o : map.entrySet()) { System.out.println(o); } } @Test public void Test2 () { TreeMap map = new TreeMap <>(); map.put(new User ("小杰" , 23 ), 63 ); map.put(new User ("小红" , 22 ), 62 ); map.put(new User ("效民" , 21 ), 61 ); for (Object o : map.keySet()) { System.out.println(o); } } @Test public void Test3 () { Comparator comparator = new Comparator () { @Override public int compare (Object o1, Object o2) { if (o1 instanceof User && o2 instanceof User) { User u1 = (User) o1; User u2 = (User) o2; int value = u1.getName().compareTo(u2.getName()); if (value != 0 ) { return value; } return u1.getAge() - u2.getAge(); } throw new RuntimeException ("类型不匹配" ); } }; TreeMap map = new TreeMap (comparator); map.put(new User ("小杰" , 23 ), 63 ); map.put(new User ("小红" , 22 ), 62 ); map.put(new User ("效民" , 21 ), 61 ); for (Object o : map.keySet()) { System.out.println(o); } } }
Properties测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package com.learning05.map;import org.junit.Test;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.util.Properties;public class PropertiesTest { @Test public void Test1 () throws IOException { File file = new File ("info.properties" ); System.out.println(file.getAbsoluteFile()); FileInputStream fileInputStream = new FileInputStream (file); Properties properties = new Properties (); properties.load(fileInputStream); String name = properties.getProperty("name" ); String pwd = properties.getProperty("password" ); System.out.println(name); System.out.println(pwd); } }
Collections工具类
Collections概述: Collections是一个操作Set、List、Map等集合的工具类。
常用方法:
排序操作: reverse(List):反转List中元素的顺序 shuffle(List):对List集合元素进行随机排序 sort(List):根据元素的自然顺序对指定List集合元素按升序排序 sort(List, Comparator):根据Comparator指定的顺序对List中集合元素进行排序 swap(List list, int i, int j):将list集合当中i处元素和j处元素进行交换 查找操作: Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素 Object max(Collection, Comparator):根据Comparator指定的顺序,返回给定集合中的最大元素(程序认为排完序之后在最右侧的为最大值) Object min(Collection):根据元素的自然顺序,返回给定集合中的最小元素 Object min(Collection, Comparator):根据Comparator指定的顺序,返回给定集合中的最小元素(程序认为排完序之后在最左侧的为最小值) int binarySearch(List list, T key):在List集合中查找某个元素的下标,但是List的元素必须是T或T的子类对象,而且必须是可比较大小的,即支持自然排序的。而且集合事先也必须是有序的,否则结果不确定。 int binarySearch(List list, T key, Comparator c):在List集合中查找某个元素的下标,但是List的元素必须是T或T的子类对象,而且必须是可比较大小的,即支持自然排序的。而且集合事先按照比较器c进行排序过的,否则结果不确定。 复制、替换: void copy(List dest, List src):将src中的内容复制到dest中 boolean replaceAll(List list, Object oldVal, Object newVal):使用新值替换List对象的所有旧值 提供了多个unmodifiableXxx()方法,该方法返回指定Xxx的不可修改的视图。 添加: boolean addAll(Collection c, T… elements):将所有指定元素添加到指定的collection中 同步: Collections类中提供了多个synchronizedXxx()方法,该方法可以使指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题。
区分Collection和Collections
Collection:用于存储一个一个元素的接口,又分为List和Set等子接口。 Collections:用于操作集合框架当中的一个工具类,此时的集合框架包括了Set、List、Map。
Collections工具类测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 package com.learning06.collections;import org.junit.Test;import java.util.*;public class CollectionsTest { @Test public void test1 () { List<Integer> list = Arrays.asList(45 , 12 , 33 , 87 , 56 ); Collections.reverse(list); System.out.println(list); Collections.shuffle(list); System.out.println(list); Collections.sort(list); System.out.println(list); Collections.sort(list, new Comparator <Integer>() { @Override public int compare (Integer o1, Integer o2) { if (o1 instanceof Integer && o2 instanceof Integer) { Integer i1 = (Integer) o1; Integer i2 = (Integer) o2; return -(i1.intValue() - i2.intValue()); } throw new RuntimeException ("类型不匹配" ); } }); System.out.println(list); Collections.swap(list, 0 , 1 ); System.out.println(list); } @Test public void test2 () { List<Integer> list = Arrays.asList(45 , 12 , 33 , 87 , 56 ); Integer max = Collections.max(list); System.out.println(max); Integer max1 = Collections.max(list, new Comparator <Integer>() { @Override public int compare (Integer o1, Integer o2) { if (o1 instanceof Integer && o2 instanceof Integer) { Integer i1 = (Integer) o1; Integer i2 = (Integer) o2; return -(i1.intValue() - i2.intValue()); } throw new RuntimeException ("类型不匹配" ); } }); System.out.println(max1); Integer min = Collections.min(list); System.out.println(min); int i = Collections.binarySearch(list, 23 ); System.out.println(i); int frequency = Collections.frequency(list, 55 ); System.out.println(frequency); } @Test public void test3 () { List<Integer> list = Arrays.asList(45 , 12 , 33 , 87 , 56 ); List<Object> dest = Arrays.asList(new Object [list.size()]); Collections.copy(dest, list); System.out.println(dest); } @Test public void test4 () { List list = new ArrayList (); list.add(34 ); list.add(22 ); list.add(12 ); List list1 = Collections.unmodifiableList(list); list1.add(38 ); } @Test public void test5 () { List list = new ArrayList (); List list1 = Collections.synchronizedList(list); } }
泛型
什么是泛型: 所谓泛型,就是允许在定义类、接口时通过一个“标识”表示类中某个“属性的类型”或者是某个方法的“返回值或参数类型”。这个类型参数将在使用时(继承或实现这个接口、创建对象或者调用方法时)确定(即传入实际的类型参数,也称为类型实参)。
在集合中使用泛型之前可能存在的问题 问题1:类型不安全,因为add()的参数是Object类型,意味着任何类型的对象都可以添加成功。 问题2:需要使用强转操作,繁琐。还有可能导致ClassCastException异常。
在集合、比较器中使用泛型(重点)
使用说明
集合框架在声明接口和其实现类时,使用泛型(jdk5.0),在实例化集合对象时,如果没有使用泛型,则认为操作的是Object类型的数据/ 如果使用了泛型,则需要指明泛型的具体类型。一旦指明了泛型的具体类型,则在集合的相关方法中,凡是使用类的泛型的位置,都替换为具体的泛型类型。
CollectionMapTest泛型测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 package com.learning01.use;import org.junit.Test;import java.util.*;public class CollectionMapTest { @Test public void test1 () { List arrayList = new ArrayList (); arrayList.add(123 ); arrayList.add(13 ); arrayList.add(12 ); arrayList.add("AA" ); Iterator iterator = arrayList.iterator(); while (iterator.hasNext()) { Integer i = (Integer) iterator.next(); int score = i; System.out.println(score); } } @Test public void test2 () { List<Integer> list = new ArrayList <Integer>(); Iterator<Integer> iterator = list.iterator(); while (iterator.hasNext()) { Integer i = iterator.next(); } } @Test public void test3 () { Map<String, Integer> map = new HashMap <String, Integer>(); Map<String, Integer> map1 = new HashMap <>(); map1.put("tom" , 33 ); map1.put("Jerry" , 63 ); map1.put("Rose" , 52 ); var entrySet = map1.entrySet(); var iterator = entrySet.iterator(); while (iterator.hasNext()) { Map.Entry<String, Integer> next = iterator.next(); String key = next.getKey(); Integer value = next.getValue(); System.out.println(key + "--->" + value); } } }
泛型练习
题目要求: 1.定义一个Employee类。 该类包含:private成员变量name,age,birthday,其中birthday为MyDate类型。 并为每一个属性定义定义getter和setter方法
2.MyDate类包含: private成员变量包含year,month,day;并为每一个属性定义getter和setter方法
3.创建该类的5个对象,并把这些对象放入TreeSet集合当中(需要使用泛型定义)
4.分别按照以下两种方式对集合中的元素进行排序,并遍历输出
使Employee实现Comparable接口,并按name排序 创建TreeSet时传入Comparator对象,按生日日期的先后排序
Employee类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 package com.learning01.use.exer;import java.util.Comparator;import java.util.EnumMap;public class Employee implements Comparable <Employee> { private String name; private int age; private MyDate birthday; public Employee () { } public Employee (String name, int age, MyDate birthday) { this .name = name; this .age = age; this .birthday = birthday; } 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; } public MyDate getBirthday () { return birthday; } public void setBirthday (MyDate birthday) { this .birthday = birthday; } @Override public int compareTo (Employee o) { return this .name.compareTo(o.getName()); } @Override public String toString () { return "Employee{" + "name='" + name + '\'' + ", age=" + age + ", birthday=" + birthday + '}' ; } }
MyDate类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 package com.learning01.use.exer;public class MyDate { private int year; private int month; private int day; public MyDate () { } public MyDate (int year, int month, int day) { this .year = year; this .month = month; this .day = day; } public int getYear () { return year; } public void setYear (int year) { this .year = year; } public int getMonth () { return month; } public void setMonth (int month) { this .month = month; } public int getDay () { return day; } public void setDay (int day) { this .day = day; } @Override public String toString () { return "MyDate{" + "year=" + year + ", month=" + month + ", day=" + day + '}' ; } }
Employee测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 package com.learning01.use.exer;import org.junit.Test;import java.util.Comparator;import java.util.Iterator;import java.util.Set;import java.util.TreeSet;public class EmployeeTest { @Test public void test1 () { Employee e1 = new Employee ("小红" , 23 , new MyDate (2000 , 2 , 8 )); Employee e2 = new Employee ("小杰" , 23 , new MyDate (2000 , 4 , 30 )); Employee e3 = new Employee ("效民" , 22 , new MyDate (2001 , 8 , 22 )); Employee e4 = new Employee ("佳诚" , 23 , new MyDate (2000 , 6 , 20 )); Employee e5 = new Employee ("小菜" , 22 , new MyDate (2001 , 3 , 23 )); Set<Employee> set = new TreeSet <>(); set.add(e1); set.add(e2); set.add(e3); set.add(e4); set.add(e5); Iterator<Employee> iterator = set.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } } @Test public void test2 () { Comparator<Employee> comparator = new Comparator <Employee>() { @Override public int compare (Employee o1, Employee o2) { int year = o1.getBirthday().getYear() - o2.getBirthday().getYear(); if (year != 0 ) { return year; } int month = o1.getBirthday().getMonth() - o2.getBirthday().getMonth(); if (month != 0 ) { return month; } int day = o1.getBirthday().getDay() - o2.getBirthday().getDay(); if (day != 0 ) { return day; } return 0 ; } }; Set<Employee> set = new TreeSet <>(comparator); Employee e1 = new Employee ("小红" , 23 , new MyDate (2000 , 2 , 8 )); Employee e2 = new Employee ("小杰" , 23 , new MyDate (2000 , 4 , 30 )); Employee e3 = new Employee ("效民" , 22 , new MyDate (2001 , 8 , 22 )); Employee e4 = new Employee ("佳诚" , 23 , new MyDate (2000 , 6 , 20 )); Employee e5 = new Employee ("小菜" , 22 , new MyDate (2001 , 3 , 23 )); set.add(e1); set.add(e2); set.add(e3); set.add(e4); set.add(e5); Iterator<Employee> iterator = set.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } } }
自定义泛型
自定义泛型类\接口
格式: class A{
}
interface B<T1, T2>{
}
使用说明:
我们在声明完自定义泛型类之后,可以在类的内部(比如:属性、方法、构造器中)使用类的泛型。
我们在创建自定义泛型类的对象时,可以指明泛型参数类型。一旦指明,内部凡是使用类的泛型参数的地方,都具体化为指定的泛型类型。
如果在创建自定义泛型类的对象时,没有指明泛型参数类型,那么泛型将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。
泛型的指定中必须使用引用数据类型,不能使用基本数据类型,此时只能用包装类进行替换。
除创建泛型类对象之外,子类继承泛型类、实现泛型类接口时,也可以确定泛型结构中的泛型参数。
如果我们在给泛型类对象提供子类时,子类也不确定泛型类的类型,则可以继续使用泛型参数。
我们还可以在现有的父类的泛型参数的基础上,新增泛型参数。
注意点:
泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如<E1,E2,E3>。
JDK7.0开始,泛型的简化操作:ArrayList flist = new ArrayyList();
如果泛型结构是一个接口或者抽象类,则不可创建泛型类的对象。
不能使用new E[]。但是可以E[] elements = (E[])new Object[capacity];
在类\接口上声明的泛型,在本类或者本接口中即代表某种类型,但不可以在静态方法中使用类的泛型。
异常类是不能带泛型的。
自定义泛型方法
问题:在泛型类的方法中,使用了类的泛型参数。那么此方法是泛型方法吗
格式:权限修饰符 返回值类型 方法名 (形参列表){ //通常在形参列表或返回值类型的位置会出现泛型参数T
} 3. 举例: public method(E e){} 4. 说明:
声明泛型方法时,一定要添加泛型参数。
泛型参数在方法调用时,指明其具体类型。
泛型方法可以根据需要声明为static。
泛型方法所属的类是否是一个泛型类,都可以。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package com.learning02.selfdefine;public class Order <T> { T t; int orderId; public Order () { } public Order (T t, int orderId) { this .t = t; this .orderId = orderId; } public T getT () { return t; } public void setT (T t) { this .t = t; } public int getOrderId () { return orderId; } public void setOrderId (int orderId) { this .orderId = orderId; } @Override public String toString () { return "Order{" + "t=" + t + ", orderId=" + orderId + '}' ; } }
泛型在继承上的体现
类SuperA是类A的父类,则G< SuperA >和G< A >的关系:两个是并列的类,并没有任何的子父类的关系。 比如:ArrayList< Object > 和 ArrayList< String >没有子父类的关系。
类SuperA是类A的父类或接口,SuperA< G > 和 A< G >的关系是:有继承或实现的关系。 比如:List< String > 和 ArrayList< String >有子父类的关系。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 package com.learning03.more;import org.junit.Test;import java.io.ObjectStreamException;import java.util.ArrayList;import java.util.Arrays;import java.util.List;public class GenericTest { @Test public void test1 () { Object obj = null ; String str = null ; obj = str; } @Test public void test2 () { ArrayList<Object> list1 = null ; ArrayList<String> list2 = null ; } @Test public void test4 () { List<String> list1 = null ; ArrayList<String> list2 = null ; list1 = list2; } }
通配符的使用
通配符:?
使用说明:举例 ArrayList>
G>可以看作是G类型的父类,即可以将G 的对象赋值给G<?>类型的引用(或变量)。
读写数据的特点: 允许读取数据,读取值的类型是Object类型;不允许写入数据(特例:null)。
有限制条件的通配符: List<? extends A>:可以将List或List赋值给List<? extends A>,其中B类是A类的子类。 List<? super A>:可以将List或List赋值给List<? super A>,其中B类是A类的父类。
数据结构与集合源码 数据结构
概念:数据结构,就是一种程序设计优化的方法论,研究数据的“逻辑结构”和“物理结构”,以及他们之间的关系,并对这种结构定义相应的运算,目的是加快执行速度,减少内存占用空间。
数据结构的研究对象:
研究对象一:
集合结构:数据结构中的元素之间除了“同属于一个集合”的相互关系之外,别无其他关系。集合元素之间没有逻辑关系。
线性结构:数据结构中的元素存在一对一的相互关系。比如:排队,结构中必须存在唯一的首元素和唯一的尾元素。体现为:一维数组、链表、栈、队列。
树形结构:数据结构中的元素存在一对多的相互关系。比如:家谱、文件系统、组织架构。
图形结构:数据结构中的元素存在多对多的相互关系。比如:铁路网、地铁图。
研究对象二:数据的存储结构(或物理结构)
顺序结构
链式结构
索引结构
散列结构
开发中,更习惯如下方式理解存储结构:
线性表(一对一的关系):一维数组、单向链表、双向链表、栈、队列。
树(一对多的关系):二叉树、B+树。
图(多对多)
哈希表:HashMap、HashSet
研究对象三:运算结构
分配资源、建立结构、释放资源
插入和删除
获取和遍历
修改和排序
常见存储结构之:数组 略…
常见存储结构之:链表 链表中的基本单位是:节点(Node)
单向链表 class Node{ Objcet data; Node next; public Node(Object data, Node data){ this.data = data; } }
创建对象: Node node1 = new Node(“AA”); Node node2 = new Node(“BB”); node1.next = node2;
双向链表 class Node{ Node prev; Object data; Node next; public Node(Node prev, Object data, Node next){ this.prev = prev; this.data = data; this.next = next; } }
创建对象: Node node1 = new Node(null, “AA”, null); Node node1 = new Node(node1, “BB”, null); Node node1 = new Node(node2, “CC”, null);
node1.next = node2; node2.next = node3;
常见存储结构之:二叉树 class TreeNode{ TreeNode left; Object data; TreeNode right; public TreeNode(Object data){ this.data = data; } public TreeNode(TreeNode left, Object data, TreeNode right){ this.left = left; this.data = data; this.right = right; } } 创建对象: TreeNode node1 = new TreeNode(null, “AA”, null); TreeNode leftNode = new TreeNode(null, “BB”, null); TreeNode rightNode = new TreeNode(null, “CC”, null); node1.left = leftNode; node1.right = rightNode; 或 class TreeNode{ TreeNode parent; TreeNode left; Object data; TreeNode right; public TreeNode(Object data){ this.data = data; } public TreeNode(TreeNode left, Object data, TreeNode right){ this.parent = parent; this.left = left; this.data = data; this.right = right; } } 创建对象: TreeNode node1 = new TreeNode(null, null, “AA”, null); TreeNode leftNode = new TreeNode(node1, null, “BB”, null); TreeNode rightNode = new TreeNode(node1, null, “CC”, null); node1.left = leftNode; node1.right = rightNode;
常见存储结构之:栈(stack、先进先出、FILO)
属于抽象数据结构(ADT)
可以使用数组或链表来构建 class Stack{ Object[] values; int size; //记录存储的元素的个数
public Stack(int length){ values = new Object[length]; }
//入栈 public void push(Object ele){ if(size >= values.length){ throw new RuntimeException(“栈空间已满,入栈失败”); } values[size] = ele; size++; }
//出栈 public Object pop(){ if(size <= 0){ throw new RuntimeException(“栈空间已空,出栈失败”); } return values[–size]; }
}
常见存储结构之:队列(queue、先进先出、FIFO)
属于抽象数据类型(ADT)
可以使用数组或链表来构建 class Queue{ Object[] values; int size;//记录存储的元素的个数
public Queue(int length){ values = new Object[length]; }
public void add(Object ele){ if(size >= values.length){ throw new RuntimeException(“队列已满,添加失败”); } values[size] = ele; size++; }
public Object get(){ if(size <= 0){ throw nnew RuntimeException(“队列已空,获取失败”); } Object obj = values[0]; //数据前移 for(int i = 0;i < size-1;i++){ values[i] = values[i+1]; } //最后一个元素置空 values[size-1] = null; size–; return obj; } }
File类与IO流 File类
File类的理解:
File类位于java.io包下,其他涉及到的相关流也声明在java.io包下。
File类的一个对象,对应于操作系统下的一个文件或一个文件目录。
File类中声明了新建、删除、获取名称、重命名等方法,并没有涉及到内容的读写操作,要想实现文件内容的读写,就必须要使用io流。
File类的对象,通常是作为io流操作文件的端点出现的。
代码层面,将File类的对象作为参数传递到io流相关类的构造器中。
内部API的使用:
构造器:
public File(String pathname):以pathname为路径创建File对象,可以是绝对路径也可以是相对路径。
public File(String parent, String child):以parent为父路径,child为子路径创建File对象。
public File(File parent, String child):根据一个File父对象和子文件路径创建File对象。
方法:
获取文件和目录基本信息:
public String getName():获取名称
public String getPath():获取路径
public String getAbsolutePath():获取绝对路径
public File getAbsoluteFile():获取绝对路径表示的文件
public String getParent():获取上层文件目录路径,若无返回null
public long length():返回文件长度(即:字节数),不能返回目录的长度
public long lastModified():获取最后一次修改时间,时间戳
列出目录的下一级
public String[] list():返回一个String类型的数组,表示该File目录中所有的子文件或者目录
public File[] listFiles():返回一个File数组,表示该File目录中的所有子文件或目录
File类的重命名功能
public boolean renameTo(File dest):把文件重命名为指定的文件路径
判断功能的方法
public boolean exists():此File表示的文件或目录是否实际存在
public boolean isDirectory():此File类表示的是否为目录
public boolean isFile():此File类表示的是否为文件
public boolean canRead():判断是否可读
public boolean canWrite():判断是否可写
public boolean isHidden():判断是否隐藏
创建、删除功能
public boolean createNewFile():创建文件、若文件已经存在,则不创建,返回false
public boolean mkdir():创建文件目录,如果文件目录已经存在,则不创建。如果此文件目录的上层目录不存在,也不创建
public boolean mkdirs():创建文件目录,如果上层文件目录不存在,则一并创建
public boolean delete():删除文件或文件夹
删除注意事项:
Java中的删除不走回收站
要删除一个文件目录,请注意该文件目录不能包含文件或文件目录
概念:
绝对路径:包括盘符在内的文件或文件目录的完整路径。
相对路径:相对于某一个文件目录来讲的相对位置。
在IDEA当中,如果使用单元测试方法,相当于当前module来讲。
如果使用main()方法,相当于当前project来讲。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 package com.learning01.file;import org.junit.Test;import java.io.File;import java.io.IOException;import java.util.Arrays;public class FileTest1 { @Test public void test1 () { File file1 = new File ("hello.txt" ); System.out.println(file1.getName()); System.out.println(file1.getPath()); System.out.println(file1.getAbsolutePath()); System.out.println(file1.getAbsoluteFile()); System.out.println(file1.getParent()); System.out.println(file1.length()); System.out.println(file1.lastModified()); } @Test public void test3 () { File file = new File ("D:\\Spring6.0.0" ); System.out.println(Arrays.toString(file.list())); System.out.println(Arrays.toString(file.listFiles())); } @Test public void test4 () { File file1 = new File ("hello.txt" ); } @Test public void test5 () throws IOException { File file = new File ("d:/hello.txt" ); if (!file.exists()) { boolean isSucceed = file.createNewFile(); if (isSucceed) { System.out.println("创建成功" ); } } else { System.out.println("此文件已存在" ); } } @Test public void test6 () throws IOException { File file = new File ("d:/io" ); if (!file.exists()) { boolean newFile = file.createNewFile(); if (newFile) { System.out.println("文件目录创建成功" ); } } else { System.out.println("文件目录已存在" ); } } }
IO流 IO流原理
Java程序中,对于数据的输入/输出操作以“流(stream)”的方式进行,可以看做是一种数据的流动。
IO流中的I/O是Input和Output的缩写,I/O技术是非常实用的技术,用于处理设备之间的数据传输。如读/写文件、网络通讯等等。
输入Input:读取外部数据(磁盘、光盘中存储的数据)到程序(内存)当中。
输出Output:将程序(内存)中的数据输出到磁盘、光盘等设备当中。
流的分类:
java.io包下提供了各种“流”类和接口,用以处理不同种类的数据,并通过“标准”的方法输入和输出数据。
按数据的流向不同分为:输入流和输出流
输入流:把数据从其他设备读取到内存中的流:以InputStream、Reader结尾
输出流:把数据从内存写出到其他设备上的流:以OutputStream、Writer结尾
按操作数据单位不同分为:字节流(8bit)和字符流(16bit)
字节流:以字节为单位,读写数据中的流:以InputStream、OutputStream结尾
字符流:以字符为单位,读写数据中的流:以Reader、Writer结尾
根据IO流的角色不同分为:节点流和处理流
节点流:直接从数据源或目的地读写数据
处理流:不直接连接到数据源或目的地,而是“连接”在已存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能
流的API
Java中的IO流涉及40多个类,实际上非常规则,都是从如下4个抽象基类派生的。
由这四个类派生出来的子类名称都是以其父类名作为子类名后缀
FileReader和FileWriter的使用
执行步骤
第一步:创建读取或写出的File类的对象
第二步:创建输入流或输出流
第三步:具体的写入或写出的过程
读入:read(char[] cbuffer)
写出:write(String str) / write(char[] cbuffer, 0, len)
第四步:关闭流资源,避免内存泄漏
注意点:
因为涉及到流资源的关闭操作,所以出现异常的话,需要使用try-catch-finally的方式来处理异常
对于输入流来讲,要求File类的对象对应的物理磁盘上的文件必须存在。否则,回报FileNotFoundException
对于输出流来讲,File的对象对应的物理磁盘上的文件可以不存在
如果此文件不存在,则在输出的过程中,会自动创建该文件,并写出数据到此文件当中
如果此文件存在,如果使用FileWriter(File file)或FileWriter(File file, false)会对现有文件中的数据覆盖;如果使用FileWriter(File file, true),则会将现有的数据追加到末尾。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 package com.learning02.filestream;import org.junit.Test;import java.io.File;import java.io.FileReader;import java.io.FileWriter;import java.io.IOException;public class FileReaderWriterTest { @Test public void test1 () throws IOException { File file = new File ("hello.txt" ); FileReader fileReader = new FileReader (file); int data; while ((data = fileReader.read()) != -1 ) { System.out.println((char ) data); } fileReader.close(); } @Test public void test2 () { FileReader fileReader = null ; try { File file = new File ("hello.txt" ); fileReader = new FileReader (file); int data; while ((data = fileReader.read()) != -1 ) { System.out.println((char ) data); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (fileReader != null ) { fileReader.close(); } } catch (IOException e) { e.printStackTrace(); } } } @Test public void Test3 () { FileReader fileReader = null ; try { File file = new File ("hello.txt" ); fileReader = new FileReader (file); char [] cbuffer = new char [5 ]; int len; while ((len = fileReader.read(cbuffer)) != -1 ) { for (int i = 0 ; i < len; i++) { System.out.print(cbuffer[i]); } } } catch (IOException e) { e.printStackTrace(); } finally { try { if (fileReader != null ) { fileReader.close(); } } catch (IOException e) { e.printStackTrace(); } } } @Test public void Test4 () throws IOException { FileWriter fileWriter = null ; try { File file = new File ("info.txt" ); fileWriter = new FileWriter (file); fileWriter.write("avc" ); } catch (IOException e) { e.printStackTrace(); } finally { try { if (fileWriter != null ) { fileWriter.close(); } } catch (IOException e) { throw new RuntimeException (e); } } } @Test public void Test5 () { FileReader fr = null ; FileWriter fw = null ; try { File src = new File ("hello.txt" ); File dest = new File ("hello_copy.txt" ); fr = new FileReader (src); fw = new FileWriter (dest); char [] cbuffer = new char [5 ]; int len; while ((len = fr.read(cbuffer)) != -1 ) { fw.write(cbuffer, 0 , len); } } catch (IOException e) { e.printStackTrace(); } finally { try { fw.close(); } catch (IOException e) { e.printStackTrace(); } try { fr.close(); } catch (IOException e) { e.printStackTrace(); } } } }
执行步骤
第一步:创建读取或写出的File类的对象
第二步:创建输入流或输出流
第三步:具体的读入或写出的过程
读入:read(byte[] buffer)
写出:write(byte[] buffer, 0, len)
第四步:关闭流资源,避免内存泄露
注意点:
因为涉及到流资源的关闭操作,所以出现异常的话,需要使用try-catch-finally的方式来处理异常
对于输入流来讲,要求File类的对象对应的物理磁盘上的文件必须存在。否则,回报FileNotFoundException
对于输出流来讲,File的对象对应的物理磁盘上的文件可以不存在
如果此文件不存在,则在输出的过程中,会自动创建该文件,并写出数据到此文件当中
FileInputStream(File file)或FileOutputStream(File file, false)会对现有文件中的数据覆盖;如果使用FileWriter(File file, true),则会将现有的数据追加到末尾。
对于字符流,只能用来操作文本文件,不能用来处理非文本文件。
对于字节流,通常用来处理非文本文件。但是涉及到文本文件的复制操作,也可以使用字节流。
说明:
文本文件:.txt、.java、.c、.cpp、.py等
非文本文件:.doc、.xls、.jpg、.png、.pdf、.mp3等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 package com.learning02.filestream;import org.junit.Test;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;public class FileStreamTest { @Test public void Test1 () { FileInputStream fis = null ; FileOutputStream fos = null ; try { File srcFile = new File ("player.jpg" ); File destFile = new File ("player_copy.jpg" ); fis = new FileInputStream (srcFile); fos = new FileOutputStream (destFile); byte [] buffer = new byte [1024 ]; int len; while ((len = fis.read(buffer)) != -1 ) { fos.write(buffer, 0 , len); } } catch (IOException e) { throw new RuntimeException (e); } finally { try { if (fis != null ) { fis.close(); } } catch (IOException e) { e.printStackTrace(); } try { if (fos != null ) { fos.close(); } } catch (IOException e) { e.printStackTrace(); } } } }
处理流之一:缓冲流
基础IO流的框架
BufferedInputStream
BufferedOutputStream
BufferedReader
BufferedWriter
缓冲流的作用:提升文件读写的效率,文件比较大的时候推荐使用这个方法
4个缓冲流所使用的方法
处理非文本文件的字节流:
BufferedInputStream:read(byte[] buffer)
BufferedOutputStream:write(byte[] buffer, 0, len)、flush()
处理文本文件的字符流:
BufferedReader:read(char[] cbuffer) / String readLine()
BufferedWriter:write(char[] cbuffer, 0, len) / write(String)、flush()
实现的步骤
第一步:创建File对象、流的对象(包括文件流、缓冲流)
第二步:使用缓冲流实现读取数据或写出数据的过程
读取:int read(char[] cbuf/byte[] buffer):每次将数据读入到cbuf/buffer数组中,并返回读入到数组中的数据
写出:void write(String str)/write(char[] cbuf):将str或cbuf写入到文件中;void write(byte[] buffer)将byte[]写出到文件中
第三步:关闭资源
BufferedStream
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package com.learning03.buffered;import org.junit.Test;import java.io.*;public class BufferedStreamTest { @Test public void Test1 () throws IOException { File srcFile = new File ("player.jpg" ); File destFile = new File ("player_copy1.jpg" ); FileInputStream fis = new FileInputStream (srcFile); FileOutputStream fos = new FileOutputStream (destFile); BufferedInputStream bis = new BufferedInputStream (fis); BufferedOutputStream bos = new BufferedOutputStream (fos); byte [] buffer = new byte [1024 ]; int len; while ((len = bis.read(buffer)) != -1 ) { bos.write(buffer, 0 , len); } bos.close(); bis.close(); } }
BufferedReaderWriter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 package com.learning03.buffered;import org.junit.Test;import java.io.*;public class BufferedReaderWriterTest { @Test public void Test1 () throws IOException { File file = new File ("hello.txt" ); BufferedReader br = new BufferedReader (new FileReader (file)); String data; while ((data = br.readLine()) != null ) { System.out.println(data + "\n" ); } br.close(); } @Test public void Test2 () throws IOException { File file = new File ("hello.txt" ); File file1 = new File ("hello_copy1.txt" ); BufferedReader br = new BufferedReader (new FileReader (file)); BufferedWriter bw = new BufferedWriter (new FileWriter (file1)); String data; while ((data = br.readLine()) != null ) { bw.write(data); bw.newLine(); bw.flush(); } System.out.println("复制成功" ); bw.close(); br.close(); } }
处理流之二:转换流
复习
字符编码:字符、字符串、字符数组—>字节、字节数组(人们看懂的->人们看不懂的)
字符解码:(看不懂的->看懂的)
如果希望程序在读取文本文件时,不出现乱码,需要注意什么
解码时使用的字符集必须与当初编码时使用的字符集相同。
解码集必须和编码集兼容。比如:文件编码是GBK,解码时使用UTF-8。如果文件中只有英文字母,此文件不会出现乱码,但是中文就会乱码了。
作用:实现字节与字符之间的转换
API:
InputStreamReader:将一个输入型的字节流转换为输入型的字符流
OutputStreamWriter:将一个输出型的字符流转换为输出型的字节流
关于字符集的理解
存储在文件中的字符:
ascii:主要用来存储英文字符、数字、标点符号等,每个字符占用一个字节,向下兼容ascii。
iso8859-1:用与欧洲地区的使用。每个字符占用一个字节,向下兼容ascii。
gbk:用来存储包括中文、英文字符、标点符号等字符,每个字符占用两个字节,向下兼容ascii。
utf-8:可以用来存储世界范围内主要语言的所有字符,使用1-4个不等的字节表示一个字符。中文字符采用三个字节存储。
在内存中的字符:
一个字符(char)占用两个字节,在内存中使用的字符集称为Unicode字符集
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package com.learning04.inputstreamreader;import org.junit.Test;import java.io.*;public class InputStreamReaderTest { @Test public void Test1 () throws IOException { File file = new File ("dbcp_utf-8.txt" ); FileInputStream fis = new FileInputStream (file); InputStreamReader isr = new InputStreamReader (fis); char [] cBuffer = new char [1024 ]; int len; while ((len = isr.read(cBuffer)) != -1 ) { String str = new String (cBuffer, 0 , len); System.out.println(str); } isr.close(); } }
处理流之三:对象流
数据流及其使用(了解):
DataOutputStream:可以将内存中的基本数据类型的变量、String类型的变量写出到具体文件当中。
DataInputStream:将文件中保存的数据还原为内存中的基本数据类型的变量、String类型的变量。
对象流及其作用:
API:
ObjectInputStream:
ObjectOutputStream:
作用:
可以读写基本数据类型的变量、引用数据类型的变量。
对象的序列化机制是什么:
对象的序列化机制允许把内存中的Java对象转换称平台无关的二进制流,从而允许把这种二进制流持久的保持在磁盘上,或通过网络将这种二进制流传输到另一个网络节点,当其他程序获取了这种二进制流,就可以恢复成原来的Java对象。
如下两个过程使用的流:
序列化过程:使用ObjectOutputStream流实现。将内存中的Java对象保存在文件中或通过网络传输出去。
反序列化过程:使用ObjectInputStream流实现。将文件中的数据或网络传输过来的数据还原成内存中的Java对象。
自定类要想实现序列化机制,需要满足:
自定义类需要实现接口:Serializable
要求自定类声明一个全局常量:static final long serialVersionUID = 42234234L;
要求自定义类的各个属性也必须是可序列化的
对于基本数据类型的属性:默认就是可以序列化的
对于引用数据类型的属性:要求实现Serializable接口
注意点:
如果不声明全局常量,系统会自动生成一个针对当前类的serialVersionUID ,如果修改此类的话,就会导致serialVersionUID修改,进而导致反序列化时,出现InvalidClassException。
类中的属性如果声明为transient或static,则不会实现序列化。
ObjectInputOutputStreamTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 package com.learning05.objectstream;import org.junit.Test;import java.io.*;public class ObjectInputOutputStreamTest { @Test public void test1 () throws IOException { File file = new File ("Object.txt" ); ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream (file)); oos.writeUTF("三三四四" ); oos.flush(); oos.writeObject("123456" ); oos.flush(); oos.close(); } @Test public void test2 () throws IOException, ClassNotFoundException { File file = new File ("Object.txt" ); ObjectInputStream ois = new ObjectInputStream (new FileInputStream (file)); String s = ois.readUTF(); System.out.println(s); String str = (String) ois.readObject(); System.out.println(str); ois.close(); } @Test public void test3 () throws IOException { File file = new File ("Object1.dat" ); ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream (file)); Person p1 = new Person ("Tom" , 12 ); oos.writeObject(p1); oos.flush(); oos.close(); } @Test public void test4 () throws IOException, ClassNotFoundException { File file = new File ("Object1.dat" ); ObjectInputStream ois = new ObjectInputStream (new FileInputStream (file)); Person person = (Person) ois.readObject(); System.out.println(person); ois.close(); } }
Person.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package com.learning05.objectstream;import java.io.Serializable;public class Person implements Serializable { private static final long serialVersionUID = 42234234L ; String name; int age; public Person (String name, int age) { this .name = name; this .age = age; } public Person () { } public String getName () { return name; } public void setName (String name) { this .name = name; } public int getAge () { return age; } public void setAge (int age) { this .age = age; } @Override public String toString () { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}' ; } }
其他流的使用
标准输入、输出流
System.in:标准的输入流,默认从键盘键入
System.out:标准的输出流,默认从显示器输出
通过调用如下的方法,修改输入流和输出流的位置
setIn(inputStream is)
setOut(printStream ps)
网络编程 InetAddress相关方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package com.geo.learning01.net;import java.net.InetAddress;import java.net.UnknownHostException;public class InetAddressTest { public static void main (String[] args) { try { InetAddress inet1 = InetAddress.getByName("192.168.23.32" ); System.out.println(inet1); InetAddress inet2 = InetAddress.getByName("www.baidu.com" ); System.out.println(inet2); System.out.println(InetAddress.getLocalHost()); System.out.println(inet2.getHostName()); System.out.println(inet2.getHostAddress()); } catch (UnknownHostException e) { throw new RuntimeException (e); } } }
运行截图:
TCP
Socket,一个简单的TCP/IP通信程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 package com.geo.learning02.tcpudp;import org.junit.Test;import java.io.IOException;import java.io.OutputStream;import java.net.InetAddress;import java.net.Socket;public class TCPTest1 { @Test public void client () { Socket socket = null ; OutputStream os = null ; try { InetAddress inetAddress = InetAddress.getByName("192.168.21.107" ); int port = 8989 ; socket = new Socket (inetAddress, port); os = socket.getOutputStream(); os.write("我是客户端。" .getBytes()); } catch (IOException e) { e.printStackTrace(); } finally { try { if (socket != null ) { socket.close(); } } catch (IOException e) { e.printStackTrace(); } try { if (os != null ) { os.close(); } } catch (IOException e) { e.printStackTrace(); } } } @Test public void server () throws IOException { ServerSocket serverSocket = null ; Socket socket = null ; InputStream is = null ; try { int port = 8989 ; serverSocket = new ServerSocket (port); socket = serverSocket.accept(); System.out.println("服务器端已开启。。。" ); is = socket.getInputStream(); byte [] buffer = new byte [1024 ]; int len; ByteArrayOutputStream baos = new ByteArrayOutputStream (); while ((len = is.read(buffer)) != -1 ) { baos.write(buffer, 0 , len); System.out.println(baos); System.out.println("收到了来自:" + socket.getInetAddress().getHostAddress() + "的信息。" ); } System.out.println("数据接受完毕。。。" ); } catch (IOException e) { e.printStackTrace(); } finally { if (socket != null ) { socket.close(); } if (serverSocket != null ) { serverSocket.close(); } if (is != null ) { is.close(); } } } }
运行截图:
稍微复杂一点的TCP/IP通信:客户端给服务端发送一张图片,服务端接收到之后保存在本地
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 package com.geo.learning02.tcpudp;import org.junit.Test;import java.io.*;import java.net.InetAddress;import java.net.ServerSocket;import java.net.Socket;import java.net.UnknownHostException;public class TCPTest2 { @Test public void client () { Socket socket = null ; FileInputStream fis = null ; OutputStream os = null ; try { InetAddress address = InetAddress.getByName("192.168.0.125" ); int port = 9090 ; socket = new Socket (address, port); File file1 = new File ("pic.jpg" ); fis = new FileInputStream (file1); os = socket.getOutputStream(); byte [] buffer = new byte [1024 ]; int len; while ((len = fis.read(buffer)) != -1 ) { os.write(buffer, 0 , len); } } catch (IOException e) { e.printStackTrace(); } finally { if (os != null ) { try { os.close(); } catch (IOException e) { e.printStackTrace(); } } if (fis != null ) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } if (socket != null ) { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } } @Test public void server () { ServerSocket serverSocket = null ; Socket socket = null ; InputStream is = null ; FileOutputStream fos = null ; try { int port = 9090 ; serverSocket = new ServerSocket (port); socket = serverSocket.accept(); is = socket.getInputStream(); File file = new File ("pic_copy.jpg" ); fos = new FileOutputStream (file); byte [] buffer = new byte [1024 ]; int len; while ((len = is.read(buffer)) != -1 ) { fos.write(buffer, 0 , len); } System.out.println("数据接收完毕" ); } catch (IOException e) { e.printStackTrace(); } finally { try { if (fos != null ) { fos.close(); } } catch (IOException e) { e.printStackTrace(); } try { if (is != null ) { is.close(); } } catch (IOException e) { e.printStackTrace(); } try { if (socket != null ) { socket.close(); } } catch (IOException e) { e.printStackTrace(); } try { if (serverSocket != null ) { serverSocket.close(); } } catch (IOException e) { e.printStackTrace(); } } } }
运行截图:
TCP/IP测试三:客户端发送文件到服务端,服务端保存到本地,并给客户端发送消息说文件发送成功
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 package com.geo.learning02.tcpudp;import org.junit.Test;import java.io.*;import java.net.InetAddress;import java.net.ServerSocket;import java.net.Socket;public class TCPTest3 { @Test public void client () throws IOException { InetAddress address = InetAddress.getByName("192.168.0.125" ); int port = 8891 ; Socket socket = new Socket (address, port); File file = new File ("aaa.txt" ); FileInputStream fis = new FileInputStream (file); OutputStream os = socket.getOutputStream(); byte [] buffer = new byte [1024 ]; int len; while ((len = fis.read()) != -1 ) { os.write(buffer, 0 , len); } System.out.println("客户端发送文件成功。" ); socket.shutdownOutput(); InputStream is = socket.getInputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream (); byte [] buffer1 = new byte [1024 ]; int len1; while ((len1 = is.read(buffer1)) != -1 ) { baos.write(buffer1, 0 , len1); } System.out.println(baos); baos.close(); is.close(); os.close(); fis.close(); socket.close(); } @Test public void server () throws IOException { int port = 8891 ; ServerSocket serverSocket = new ServerSocket (port); Socket socket = serverSocket.accept(); InputStream is = socket.getInputStream(); File file = new File ("aaa_copy.txt" ); FileOutputStream fos = new FileOutputStream (file); byte [] buffer = new byte [1024 ]; int len; while ((len = is.read(buffer)) != -1 ) { fos.write(buffer, 0 , len); } System.out.println("服务端接收文件成功,并保存在本地。" ); OutputStream os = socket.getOutputStream(); os.write("服务端已接收到文件。" .getBytes()); os.close(); fos.close(); is.close(); socket.close(); serverSocket.close(); } }
UDP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package com.geo.learning02.tcpudp;import org.junit.Test;import java.net.DatagramPacket;import java.net.DatagramSocket;import java.net.InetAddress;public class UDPTest { @Test public void sender () throws Exception { DatagramSocket ds = new DatagramSocket (); InetAddress address = InetAddress.getByName("192.168.0.125" ); int port = 9090 ; byte [] bytes = "我是发送端" .getBytes("utf-8" ); DatagramPacket dp = new DatagramPacket (bytes, 0 , bytes.length, address, port); ds.send(dp); ds.close(); } @Test public void receiver () throws Exception { DatagramSocket ds = new DatagramSocket (9090 ); byte [] buffer = new byte [64 ]; DatagramPacket dp = new DatagramPacket (buffer, 0 , buffer.length); ds.receive(dp); String str = new String (dp.getData(), 0 , dp.getLength()); System.out.println(str); ds.close(); } }
URL
作用:一个具体的url就对应着互联网上的某一个资源。
URL格式:应用层协议-ip地址-端口号-资源地址-参数列表
URL类的实例化及常用方法
public String getProtocol():获取该URL的协议名
public String getHost():获取该URL的主机名
public String getPort():获取该URL的端口号
public String getPath():获取该URL的文件路径
public String getFile():获取该URL的文件名
public String getQuery():获取该URL的查询名
下载指定的URL的资源到本地
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 package com.geo.learning03.url;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.net.HttpURLConnection;import java.net.MalformedURLException;import java.net.URL;public class URLTest { public static void main (String[] args) { FileOutputStream fos = null ; InputStream is = null ; HttpURLConnection httpURLConnection = null ; try { URL url = new URL ("https://www.bilibili.com/video/BV1PY411e7J6?p=186&vd_source=b13a0c249f074a81f967680e2c948d52" ); try { httpURLConnection = (HttpURLConnection) url.openConnection(); is = httpURLConnection.getInputStream(); File file = new File ("aaa.jpg" ); fos = new FileOutputStream (file); byte [] buffer = new byte [1024 ]; int len; while ((len = is.read(buffer)) != -1 ) { fos.write(buffer, 0 , len); } } catch (IOException e) { e.printStackTrace(); } finally { if (fos != null ) { fos.close(); } if (is != null ) { is.close(); } if (httpURLConnection != null ) { httpURLConnection.disconnect(); } } } catch (IOException e) { e.printStackTrace(); } } }
反射 反射的概念
反射出现的背景: Java程序中,所有的对象都有两种类型:编译时类型和运行时类型,而很多时候对象的编译时类型和运行时类型不一致(多态)。 例如:某些变量或形参的声明类型是Object类型,但是程序却需要调用该对象运行时类型的方法,该方法不是object中的方法,那么如何解决呢? 解决这个问题,有两种方案: 方案1:在编译和运行时都完全知道类型的具体信息,在这种情况下,我们可以直接先使用 instanceof 运算符进行判断,再利用强制类型转换符将其转换成运行时类型的变量即可。 方案2:编译时根本无法预知该对象和类的真实信息,程序只能依靠 运行时信息 来发现该对象和类的真实信息这就必须使用反射。
反射概述:
Reflection(反射)是被视为动态语言的关键,反射机制允许程序在运行期间借助Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
加载完类之后,在堆内存的方法区就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子可以看到类的结构,所以形象的成为反射。
ReflectionTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 package com.geo.learning01.example;import org.junit.Test;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;public class ReflectionTest { @Test public void test2 () throws Exception { Class<Person> person = Person.class; Person person1 = person.newInstance(); System.out.println(person1); Field ageField = person.getField("age" ); ageField.set(person1, 10 ); System.out.println(ageField.get(person1)); Method showMethod = person.getMethod("show" ); showMethod.invoke(person1); } @Test public void test3 () throws Exception { Class clazz = Person.class; Constructor declaredConstructor = clazz.getDeclaredConstructor(String.class, int .class); declaredConstructor.setAccessible(true ); Person p1 = (Person) declaredConstructor.newInstance("Tom" , 12 ); Field declaredField = clazz.getDeclaredField("name" ); declaredField.setAccessible(true ); declaredField.set(p1, "jerry" ); Method showName = clazz.getDeclaredMethod("showName" , String.class); showName.setAccessible(true ); String info = (String) showName.invoke(p1, "李晓杰" ); System.out.println(info); } }
Person.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 package com.geo.learning01.example;public class Person { public String name; public int age; public Person () { } private Person (String name, int age) { this .name = name; this .age = age; } public String getName () { return name; } public void setName (String name) { this .name = name; } public int getAge () { return age; } public void setAge (int age) { this .age = age; } public void show () { System.out.println("展示" ); } private String showName (String name) { return "我的名字是" + name; } @Override public String toString () { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}' ; } }
通过使用反射前后的例子对比:
面向对象中创建对象,调用指定结构(属性、方法)等功能,可以不使用反射,也可以使用反射。有什么区别?
不使用反射,需要考虑封装性,出了Person类之后就不能调用Person类中的私有结构;
使用反射,我们可以调用运行时类中任意的构造器、属性、方法,包括私有的构造器、属性和方法。
以前创建对象并调用方法的方式,与现在通过反射创建对象并调用方法的方式对比的话,哪种用的多?场景是什么?
从我们作为开发者的角度:开发中主要完成业务代码,对于相关的对象和方法的调用都是确定的,所以使用非反射的方式更多。
因为反射体现了动态性,可以在运行时动态的获取对象所属的类,动态的调用相关的方法。所以在设计框架的时候,会大量的使用反射。
单例模式的饿汉式和懒汉式中,私有化类的构造器了,通过反射可以创建多个类的对象吗?
可以。
Java反射机制研究及应用
Java反射机制提供的功能:
在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时判断任意一个类所具有的成员变量和方法
在运行时获取泛型信息
在运行时调用任意一个对象的成员变量和方法
在运行时处理注解
生成动态代理
反射相关的主要API
java.lang.Class:代表一个类
java.lang.reflect.Meethod:代表类的方法
java.lang.reflect.Field:代表类的成员变量
java.lang.reflect.Constructor:代表类的构造器
反射的优缺点
优点:
提高了Java程序的灵活性和扩展性,降低了耦合性,提高自适应能力。
允许程序创建和控制任何类的对象,无需提前硬编码目标类。
缺点:
反射的性能较低。
反射会模糊程序的内部逻辑,可读性很差。
Class类 Class类的理解
针对编写好的.java源文件进行编译(使用javac.exe),会生成一个或多个.class字节码文件。接着使用java.exe对指定的.class文件进行解释运行。这个解释运行的过程中,需要将.class字节码文件加载到内存中。加载到内存中的.class文件对应的结构即为Class的一个实例。
运行时类在内存中会缓存起来,在整个执行期间,只会加载一次。
体会:Class看作是反射的源头。
获取Class实例的几种方式:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package com.geo.learning02._class;import org.junit.Test;public class ClassTest { @Test public void Test1 () throws ClassNotFoundException { Class<User> userClass = User.class; System.out.println(userClass); User u1 = new User (); Class user2 = u1.getClass(); String className = "com.geo.learning02._class.User" ; Class<?> aClass = Class.forName(className); System.out.println(userClass == user2); System.out.println(userClass == aClass); Class<?> aClass1 = ClassLoader.getSystemClassLoader().loadClass("com.geo.learning02._class.User" ); System.out.println(aClass1 == userClass); } }
Class的实例都可以指向哪些结构呢?(所有java类型)
class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
interface:接口
[]:数组
enum:枚举
annotation:注解@interface
primitive type:基本数据类型
void
类的加载过程
过程一:类的装载(loading)
将类的class文件读入内存,并为之创建一个java.lang.Class对象。此过程由类加载器完成
过程二:链接(linking)
验证(Verify):确保加载的类信息符合JVM规范,例如:以cafebabe开头,没有安全方面的问题。
准备(Prepare):证实为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法去中进行分配。
解析(Resolve):虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
过程三:初始化(initialization)
执行类构造器< clinit>()方法的过程。
类构造器< clinit>()方法是由编译期自动收集类中所有类变量赋值动作和静态代码块中的语句合并产生的。
关于类的加载器
作用:负责类的加载,并对应于一个Class实例。
分类:
BootstrapClassLoader:引导类加载器、启动类加载器
使用C/C++语言编写的,不能通过Java代码获取其实例
负责加载Java的核心库
继承于ClassLoader的类加载器
ExtensionClassLoader:扩展类加载器
负责加载从java.ext.dirs系统属性所指定的目录中加载类库
SystemClassLoader\ApplicationClassLoader:系统类加载器、应用程序类加载器
自定义的类默认使用的类的加载器
用户自定义类的加载器
以上的类的加载器是否存在继承关系?
无继承关系。
ClassLoaderTest.java,通过类加载器读取属性文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 package com.geo.learning02._class;import org.junit.Test;import java.io.*;import java.util.Properties;public class ClassLoaderTest { @Test public void test1 () throws IOException { Properties prop = new Properties (); InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("info1.properties" ); prop.load(is); String name = prop.getProperty("name" ); String password = prop.getProperty("password" ); System.out.println(name + ":" + password); } @Test public void test2 () throws IOException { Properties prop = new Properties (); FileInputStream fis = new FileInputStream (new File ("info.properties" )); prop.load(fis); String name = prop.getProperty("name" ); String password = prop.getProperty("password" ); System.out.println(name + ":" + password); } }
反射的应用 应用一:创建运行时类的对象
如何实现
通过Class的实例调用newInstatce()方法即可。
要想创建对象成功,需要满足:
条件1:要求运行时类中必须提供一个空参构造器。
条件2:要求提供的空参构造器的权限足够。
在JDK9中标记为过时,替换成什么结构
调用Constructor.newInstance()
应用二:获取运行时类的内部结构
获取运行时类的内部结构1:所有属性、所有方法、所有构造器
FieldTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 package com.atguigu.java2;import com.atguigu.java1.Person;import org.junit.Test;import java.lang.reflect.Field;import java.lang.reflect.Modifier;public class FieldTest { @Test public void test1 () { Class clazz = Person.class; Field[] fields = clazz.getFields(); for (Field f : fields){ System.out.println(f); } System.out.println(); Field[] declaredFields = clazz.getDeclaredFields(); for (Field f : declaredFields){ System.out.println(f); } } @Test public void test2 () { Class clazz = Person.class; Field[] declaredFields = clazz.getDeclaredFields(); for (Field f : declaredFields){ int modifier = f.getModifiers(); System.out.print(Modifier.toString(modifier) + "\t" ); Class type = f.getType(); System.out.print(type.getName() + "\t" ); String fName = f.getName(); System.out.print(fName); System.out.println(); } } }
MethodTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 package com.atguigu.java2;import com.atguigu.java1.Person;import org.junit.Test;import java.lang.annotation.Annotation;import java.lang.reflect.Method;import java.lang.reflect.Modifier;public class MethodTest { @Test public void test1 () { Class clazz = Person.class; Method[] methods = clazz.getMethods(); for (Method m : methods){ System.out.println(m); } System.out.println(); Method[] declaredMethods = clazz.getDeclaredMethods(); for (Method m : declaredMethods){ System.out.println(m); } } @Test public void test2 () { Class clazz = Person.class; Method[] declaredMethods = clazz.getDeclaredMethods(); for (Method m : declaredMethods){ Annotation[] annos = m.getAnnotations(); for (Annotation a : annos){ System.out.println(a); } System.out.print(Modifier.toString(m.getModifiers()) + "\t" ); System.out.print(m.getReturnType().getName() + "\t" ); System.out.print(m.getName()); System.out.print("(" ); Class[] parameterTypes = m.getParameterTypes(); if (!(parameterTypes == null && parameterTypes.length == 0 )){ for (int i = 0 ;i < parameterTypes.length;i++){ if (i == parameterTypes.length - 1 ){ System.out.print(parameterTypes[i].getName() + " args_" + i); break ; } System.out.print(parameterTypes[i].getName() + " args_" + i + "," ); } } System.out.print(")" ); Class[] exceptionTypes = m.getExceptionTypes(); if (exceptionTypes.length > 0 ){ System.out.print("throws " ); for (int i = 0 ;i < exceptionTypes.length;i++){ if (i == exceptionTypes.length - 1 ){ System.out.print(exceptionTypes[i].getName()); break ; } System.out.print(exceptionTypes[i].getName() + "," ); } } System.out.println(); } } }
Reflection.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 package com.atguigu.java2;import com.atguigu.java1.Person;import org.junit.Test;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;public class ReflectionTest { @Test public void testField () throws Exception { Class clazz = Person.class; Person p = (Person) clazz.newInstance(); Field id = clazz.getField("id" ); id.set(p,1001 ); int pId = (int ) id.get(p); System.out.println(pId); } @Test public void testField1 () throws Exception { Class clazz = Person.class; Person p = (Person) clazz.newInstance(); Field name = clazz.getDeclaredField("name" ); name.setAccessible(true ); name.set(p,"Tom" ); System.out.println(name.get(p)); } @Test public void testMethod () throws Exception { Class clazz = Person.class; Person p = (Person) clazz.newInstance(); Method show = clazz.getDeclaredMethod("show" , String.class); show.setAccessible(true ); Object returnValue = show.invoke(p,"CHN" ); System.out.println(returnValue); System.out.println("*************如何调用静态方法*****************" ); Method showDesc = clazz.getDeclaredMethod("showDesc" ); showDesc.setAccessible(true ); Object returnVal = showDesc.invoke(Person.class); System.out.println(returnVal); } @Test public void testConstructor () throws Exception { Class clazz = Person.class; Constructor constructor = clazz.getDeclaredConstructor(String.class); constructor.setAccessible(true ); Person per = (Person) constructor.newInstance("Tom" ); System.out.println(per); } }
获取运行时类的内部结构2:父类、接口、包、带泛型的父类、父类的泛型等。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 package com.atguigu.java2;import com.atguigu.java1.Person;import org.junit.Test;import java.lang.annotation.Annotation;import java.lang.reflect.Constructor;import java.lang.reflect.ParameterizedType;import java.lang.reflect.Type;public class OtherTest { @Test public void test1 () { Class clazz = Person.class; Constructor[] constructors = clazz.getConstructors(); for (Constructor c : constructors){ System.out.println(c); } System.out.println(); Constructor[] declaredConstructors = clazz.getDeclaredConstructors(); for (Constructor c : declaredConstructors){ System.out.println(c); } } @Test public void test2 () { Class clazz = Person.class; Class superclass = clazz.getSuperclass(); System.out.println(superclass); } @Test public void test3 () { Class clazz = Person.class; Type genericSuperclass = clazz.getGenericSuperclass(); System.out.println(genericSuperclass); } @Test public void test4 () { Class clazz = Person.class; Type genericSuperclass = clazz.getGenericSuperclass(); ParameterizedType paramType = (ParameterizedType) genericSuperclass; Type[] actualTypeArguments = paramType.getActualTypeArguments(); System.out.println(((Class)actualTypeArguments[0 ]).getName()); } @Test public void test5 () { Class clazz = Person.class; Class[] interfaces = clazz.getInterfaces(); for (Class c : interfaces){ System.out.println(c); } System.out.println(); Class[] interfaces1 = clazz.getSuperclass().getInterfaces(); for (Class c : interfaces1){ System.out.println(c); } } @Test public void test6 () { Class clazz = Person.class; Package pack = clazz.getPackage(); System.out.println(pack); } @Test public void test7 () { Class clazz = Person.class; Annotation[] annotations = clazz.getAnnotations(); for (Annotation annos : annotations){ System.out.println(annos); } } }
应用三:调用指定的结构:指定的属性、方法、构造器
调用指定的属性的步骤
通过Class实例调用getDeclaredField(String fieldName),获取运行时类指定名的属性
setAccessible(true),确保此属性是可访问的
通过Field类的实例调用get(Object obj)(获取的操作)或set(Object obj, Object value)(设置的操作)进行操作
调用指定的方法的步骤
通过Class实例调用getDeclaredMethod(String methodName, Class …args),获取指定的方法
setAccessible(true),确保此方法是可访问的
通过Method实例调用invoke(Object obj, Object …args),即为对Method对应的方法进行调用,invoke()的返回值即为Method对应的方法的返回值,如果对应的方法返回值类型为void,则invoke的返回值为null。
调用指定的构造器步骤
通过Class实例嗲敖勇getDeclaredConstructor(Class …args),获取指定参数类型的构造器
setAccessible(true),确保此构造器是可访问的
通过Constructor实例调用newInstance(Object …args),返回一个运行时类的实例
反射的动态性体现
Apple.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.geo.learning03.reflectapply.exer;public class Apple implements Fruit { @Override public void squeeze () { System.out.println("榨一杯苹果汁。" ); } }
Fruit.interface
1 2 3 4 5 6 7 8 9 10 11 12 package com.geo.learning03.reflectapply.exer;public interface Fruit { void squeeze () ; }
Juicer.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.geo.learning03.reflectapply.exer;public class Juicer { public void run (Fruit f) { f.squeeze(); } }
FruitTeest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package com.geo.learning03.reflectapply.exer;import org.junit.Test;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;import java.util.Properties;public class FruitTest { @Test public void test1 () throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Properties prop = new Properties (); File file = new File ("src/config.properties" ); FileInputStream fis = new FileInputStream (file); prop.load(fis); String property = prop.getProperty("fruitName" ); Class aClass = Class.forName(property); Constructor con = aClass.getDeclaredConstructor(); con.setAccessible(true ); Fruit f = (Fruit) con.newInstance(); Juicer juicer = new Juicer (); juicer.run(f); } }
JDK8-17新特性 JDK8的新特性-Lambda表达式
Lambda表达式的使用举例
(o1, o2) -> Integer.compare(o1, o2);
Lambda表达式的格式举例
Lambda形参列表->Lambda体
Lambda表达式的格式
->:lambda操作符或箭头操作符
->的左边:lambda形参列表,对应着要重写的接口中的抽象方法的形参列表。
->的右边:lambda体,对应着接口的实现类要重写的方法的方法体。
Lambda表达式的本质
一方面,lambda表达式作为接口的实现类的对象。
另一方面,lambda表达式就是一个匿名函数。
函数式接口
什么是函数式接口?为什么需要函数式接口?
如果接口中只声明有一个抽象方法,则此接口就成为函数式接口。
因为只有给函数式接口提供实现类的对象时,我们才可以使用lambda表达式。
api中函数式接口所在的包
声明在java.util.function包下。
4个基本的函数式接口
消费型接口:Consumer< T> void accept(T t);
供给型接口:Supplier< T> T get();
函数型接口:Function< T, R> R apply(T t);
判断型接口:Predicate< T> boolean test(T t);
Lambda表达式的语法规则总结: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 package com.geo.learning01.lambda;import org.junit.Test;import java.util.Comparator;import java.util.function.Consumer;public class LambdaTest1 { @Test public void Test1 () { Runnable r1 = new Runnable () { @Override public void run () { System.out.println("123" ); } }; r1.run(); System.out.println("******************" ); Runnable r2 = () -> { System.out.println("123456" ); }; r2.run(); } @Test public void Test2 () { Consumer<String> con = new Consumer <String>() { @Override public void accept (String s) { System.out.println(s); } }; con.accept("123456" ); System.out.println("****************" ); Consumer<String> con1 = (String s) -> { System.out.println(s); }; con1.accept("54163415" ); } @Test public void Test3 () { Consumer<String> con1 = (String s) -> { System.out.println(s); }; con1.accept("zxcv" ); System.out.println("****************" ); Consumer<String> con2 = (s) -> { System.out.println(s); }; con2.accept("bbb" ); } @Test public void Test4 () { Consumer<String> con1 = s -> { System.out.println(s); }; con1.accept("asdasd" ); } @Test public void test5 () { Comparator<Integer> com1 = new Comparator <Integer>() { @Override public int compare (Integer o1, Integer o2) { System.out.println(o1); System.out.println(o2); return o1.compareTo(o2); } }; System.out.println(com1.compare(22 , 33 )); System.out.println("*********************" ); Comparator<Integer> com2 = (o1, o2) -> { System.out.println(o1); System.out.println(o2); return o1.compareTo(o2); }; System.out.println(com2.compare(22 , 33 )); } @Test public void test6 () { Comparator<Integer> com1 = new Comparator <Integer>() { @Override public int compare (Integer o1, Integer o2) { return o1.compareTo(o2); } }; System.out.println("*************************" ); Comparator<Integer> com2 = (o1, o2) -> o1.compareTo(o2); System.out.println(com2.compare(32 , 26 )); } }
方法引用、构造器引用、数组引用 方法引用
举例:
Integer :: compare;
方法引用的理解:
可以看做是基于lambda表达式的进一步刻画。
当需要提供一个函数式接口的实例时,我们可以使用lambda表达式提供此实例。
当满足一定的条件的情况下,我们还可以使用方法引用或构造器引用去替换lambda表达式。
方法引用的本质:
方法引用作为了函数式接口的实例。
格式:
情况1:对象::实例方法
要求:函数式接口中的抽象方法a与内部实现时调用的某个对象的某个方法b的形参列表和返回值类型都相同,此时可以考虑使用方法b实现对方法a的替换、覆盖。此替换和覆盖就是方法引用,且方法a和方法b是非静态方法,所以需要对象进行调用。
情况2:类::静态方法
要求:函数式接口中的抽象方法a与内部实现时调用的某个类的某个静态方法b的形参列表和返回值类型都相同,此时可以考虑使用方法b实现对方法a的替换、覆盖。此替换和覆盖就是方法引用,且方法b是静态方法,所以需要类进行调用。
情况3:类::实例方法
要求:函数式接口中的抽象方法a与其内部实现时调用的对象的某个方法b的返回值类型相同。同时,抽象方法a中有n个参数,方法b中有n-1个参数,且抽象方法a的第一个参数作为方法b的调用者,且抽象方法a的后n-1个参数与方法b的后n-1个参数类型相同,则可以考虑使用方法b对方法a进行替换或覆盖。
具体代码展示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 package com.geo.learning02.reference;import com.geo.learning02.reference.data.Person;import org.junit.Test;import java.util.Comparator;import java.util.function.BiPredicate;import java.util.function.Consumer;import java.util.function.Function;import java.util.function.Supplier;public class MethodRefTest { @Test public void test1 () { Consumer<String> con1 = new Consumer <String>() { @Override public void accept (String s) { System.out.println(s); } }; con1.accept("hello" ); System.out.println("*************************" ); Consumer<String> con2 = s -> System.out.println(s); Consumer<String> con3 = System.out::println; } @Test public void test2 () { Person person = new Person ("tom" , 23 ); Supplier<String> sup1 = new Supplier <String>() { @Override public String get () { return person.getName(); } }; System.out.println(sup1.get()); System.out.println("*************************************" ); Supplier<String> sup2 = () -> person.getName(); System.out.println(sup2.get()); System.out.println("*************************************" ); Supplier<String> sup3 = person::getName; System.out.println(sup3.get()); } @Test public void test3 () { Comparator<Integer> com1 = new Comparator <Integer>() { @Override public int compare (Integer o1, Integer o2) { return o1.compareTo(o2); } }; System.out.println(com1.compare(23 , 25 )); System.out.println("***************************" ); Comparator<Integer> com2 = (o1, o2) -> o1.compareTo(o2); System.out.println(com2.compare(22 , 21 )); System.out.println("***************************" ); Comparator<Integer> com3 = Integer::compare; System.out.println(com3.compare(0 , 3 )); } @Test public void test4 () { Function<Double, Long> fun1 = new Function <Double, Long>() { @Override public Long apply (Double aDouble) { return Math.round(aDouble); } }; Function<Double, Long> fun2 = aDouble -> Math.round(aDouble); Function<Double, Long> fun3 = Math::round; } @Test public void test5 () { Comparator<String> com1 = new Comparator <String>() { @Override public int compare (String o1, String o2) { return o1.compareTo(o2); } }; Comparator<String> com2 = (o1, o2) -> o1.compareTo(o2); Comparator<String> com3 = String::compareTo; System.out.println(com3.compare("abc" , "abc" )); } @Test public void test6 () { BiPredicate<String, String> bipre1 = new BiPredicate <String, String>() { @Override public boolean test (String s1, String s2) { return s1.equals(s2); } }; BiPredicate<String, String> bipre2 = (s1, s2) -> s1.equals(s2); BiPredicate<String, String> bipre3 = String::equals; } @Test public void test7 () { Person person = new Person ("xiaojie" , 23 ); Function<Person, String> fun1 = new Function <Person, String>() { @Override public String apply (Person person) { return person.getName(); } }; fun1.apply(new Person ()); Function<Person, String> fun2 = person1 -> person1.getName(); fun2.apply(person); Function<Person, String> fun3 = Person::getName; System.out.println(fun3.apply(person)); } }
构造器引用
构造器引用
格式:类名::new
说明:
调用了类名对应的类中的某一个确定的构造器。
具体调用的类中的构造器取决于函数式接口的抽象方法的形参列表。
数组引用
格式:数组名[]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 package com.geo.learning02.reference;import com.geo.learning02.reference.data.Person;import org.junit.Test;import java.util.function.BiFunction;import java.util.function.Function;import java.util.function.Supplier;public class ConstructorRefTest { Person person = new Person ("xiaohong" , 24 ); @Test public void test1 () { Supplier<Person> sup1 = new Supplier <Person>() { @Override public Person get () { return new Person (); } }; sup1.get(); Supplier<Person> sup2 = () -> new Person (); sup2.get(); Supplier<Person> sup3 = Person::new ; } @Test public void test2 () { Function<Integer, Person> fun1 = new Function <Integer, Person>() { @Override public Person apply (Integer id) { return new Person (id); } }; Function<Integer, Person> fun2 = Person::new ; } @Test public void test3 () { BiFunction<String, Integer, Person> bi1 = new BiFunction <String, Integer, Person>() { @Override public Person apply (String name, Integer age) { return new Person (name, age); } }; System.out.println(bi1.apply("li" , 23 )); BiFunction<String, Integer, Person> bi2 = Person::new ; System.out.println(bi2.apply("er" , 15 )); } @Test public void test4 () { Function<Integer, Person[]> fun1 = new Function <Integer, Person[]>() { @Override public Person[] apply(Integer length) { return new Person [length]; } }; Function<Integer, Person[]> fun2 = Person[]::new ; fun2.apply(10 ); } }
StreamAPI
StreamAPI和集合框架的区别
StereamAPI关注的是多个数据的计算(排序、查找、过滤、映射、遍历等),面向CPU。
集合关注的是数据的存储,面向内存。
使用说明
Stream自己不会存储元素。
Stream不会改变源对象。相反,它们会返回一个持有结果的新Streamm
Stream操作是延迟执行的。这意味着它们会等到需要结果的时候才执行,一旦终止操作,就执行中间操作链,并产生结果。
Stream一旦执行了终止操作,就不能再调用其他中间操作或终止操作了。
Stream的执行流程
Stream的实例化
一系列的中间操作
执行终止操作
Stream创建的三种方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package com.geo.learning03.streamapi;import com.geo.learning03.streamapi.data.Employee;import com.geo.learning03.streamapi.data.EmployeeData;import org.junit.Test;import java.util.Arrays;import java.util.List;import java.util.stream.IntStream;import java.util.stream.Stream;public class StreamAPITest { @Test public void test1 () { List<Employee> list = EmployeeData.getEmployees(); Stream<Employee> stream = list.stream(); Stream<Employee> employeeStream = list.parallelStream(); System.out.println(stream); System.out.println(employeeStream); } @Test public void test2 () { Integer[] arr = new Integer []{1 , 2 , 3 , 4 , 5 }; Stream<Integer> stream = Arrays.stream(arr); int [] arr1 = new int []{1 , 2 , 3 , 4 , 5 }; IntStream stream1 = Arrays.stream(arr1); } @Test public void test3 () { Stream<Integer> integerStream = Stream.of(1 , 2 , 3 , 4 , 5 ); } }
..
...
...