JavaSE基础知识
1、Java应用与特性
jdk8 常用
jdk11 常用
jdk17 推荐使用
java之父高斯林
1、常用的java程序分为JavaSE、JavaEE、Java ME三个版本
2、J2SE:定位在服务端的开发(WEB网页)
3、JavaME:定位在消费行电子产品的应用上。
特性和优势:
1、面向对象
2、可移植性
3、高性能
4、动态性
5、多线程
6、安全性
7、健壮性
2、java软件组成
1、JDK:java development kit java开发工具包
2、jre: java runtime envirment java运行环境(核心类库)
3、jdk=jre+开发工具(java javac...)+java api文档
4、jvm:Java Virtual Mechinal java虚拟机 实现java跨平台机制,在不同的操作系统上运行java程序。
注意:jre包括jvm
3、配置环境变量
目的:为了能在任何环境下使用java命令。
在环境变量选项中找到系统变量,新建JAVA_HOME变量名,变量值是jdk的地址。
在path路径中添加 %JAVA_HOME%bin
在cmd中使用java -version查看JAVA版本
java配置路径在path路径中要放在第一行
4、常用DOS命令
1、cd 路径 进入文件目录路径 cd.. 回到上一级目录
2、dir 展示当前目录的内容
3、java -version 查看java版本
4、d:切换到D盘目录
5、exit 退出
键盘快捷键 tab shift ctrl alt
5、文件编写第一个程序
1、用记事本编写打印HelloWord,取名为类名.java文件。
2、cmd命令找到java文件目录路径。
3、执行命令javac *.java把java文件编译成class文件。
4、执行命令java 类名 运行编译后的字节码文件。
5、一个java文件中可以有多个class文件和多个类文件。
6、如果类名加上public修饰符,源码名字必须与类名一致。
7、注意关键词大小写严格区分。
6、Java基本数据类型
程序最核心的目的就是为了操作数据,但是数据都在内存中,也就是我们的JVM,而且这个内存区域是循环使用的。
程序中一般使用变量来操纵存储空间中的数据!
java是一种强类型语言,每个变量使用前必须提前声明,可以合理利用空间。
java变量要素包括变量名,变量类型和作用域。
简单来说:变量=数据类型+标识符
可以先声明后赋值,也可以边声明边赋值。比如:int a; a=10 int a=10;
7、标识符的命名规则
1、不能以数字开头,不能包含%,只用包含字母、数字、_、字母、$、
2、必须以字母、下划线、$ 开头 。
3、不可以是java关键字,严格区分大小写。
4、java不采用通常语言使用的ASCII字符集,而是采用unicode这样的标准的国际字符集。因此这里的字符的含义:英文、汉字等等。
8、进制与转换
1、进制就是进位制,是人们规定的一种进位方法。
2、二进制就是逢二进一,八进制是逢八进一....
3、java进制分为二进制、八进制、十进制,但是计算机只能处理二进制的数据和指令。
二进制:0 1
十六进制:0-9 A—F
八进制:0 - 7
十进制转换为二进制:短除法除以2取余,从下往上写。
二进制转为十进制:基数*2的权位次方相加,从右往左算起(权位0 1 2……)。
二进制转为十六进制:从右往左,4个数字为一组,基数*进制数^权位相加,从右往左算起(权位0 1 2 3)。
二进制转为八进制:从右往左,3数字为一组,基数*8^权位相加,从右往左算起(权位0 1 2)。
八进制、十六进制转二进制:八进制把每一位除以2拆成3位,十六进制把每一位除以2拆成4位。
9、补码、原码、反码
前提是在二进制的数字下:
1、解决负数的存储问题。
2、原码:正数的原码是它本身。
3、负数用最高位是1表示负数,反码是负数的原码按位取反(0变1,1变0),符号位不变。
4、正数的反码与原码是一样的。
5、负数的补码是负数的反码加1。
原因:因为在使用原码、补码在计算是时不准确、使用补码计算时才准确。而且计算机没有减法,只有加法。
计算机的容量:
电脑的容量单位最小的是bit,也就是位
而8位为一个字节,也就是Byte.
10、存储单位
1、最小的存储单位是bit 一个二进制单位就是一个bit
2、最基本的存储单位:byte 进制转换单位是1024
KB Byte MB GB TB
工业标准按照1000进行计算
科学标准按照1024进行计算
11、数据类型
1、java定义数据类型比较主要的一个操作是为了分配不同内存空间
2、对内存占用分类:
基本数据类型:
占用内存大小是固定的
1、整数类型:long(8字节)、int(4字节)、short(2字节)、byte(1字节)
2、浮点类型:float(4字节)、double(8字节)
3、字符类型:char(2字节)
4、布尔类型:boolean(1位)
引用数据类型:
引用数据类型占用的内存大小是未知的。
类、接口类型、数组类型、枚举类型、注解类型、字符串型。
除了基本数据类型,就是引用数据类型,但是引用数据类型都是由基本数据类型构成的。
引用数据类型的大小统一为4个字节,记录的是其引用对象的地址。
转义符 | 含义 |
---|---|
\b |
退格 |
\n |
换行 |
\r |
回车 |
\t |
制表符 |
\" |
双引号 |
\' |
单引号 |
\ |
反斜杠 |
12、运算符
算术运算符:+,-,*,、,%,++,--
赋值运算符:=
关系运算符:>,<,>=,=<,==,!=,instanceof
逻辑运算符:&&,||,!
位运算符:&,|,^,~,>>,<<,>>>(了解!!!)
条件运算符:? :
扩展赋值运算符:+=,-=,=,/=
三元运算符:条件表达式?结果1:结果2
&&与&区别?&&短路运算前面的表达式结果确定最终的结果。 &两边都要参与运算(位运算)
重点:
一元运算符:++(自增),--(自减)
1、单独使用++在前后值并无区别。
2、参与运算,若++在前先加1再赋值,若++在后先赋值再一(--原理一样)
特别注意:1<<3=>0001->1000=8 8>>3=>1000->0001=1
>>是算术右移操作符,右移时正数补0,负数补1。如-16=1111,1111,1111,1111,1111,1111,1111,0000;(4个字节)
16=0000,0000,0000,0000,0000,0000,0001,0000;
>>>是逻辑右移操作符,右移时无论正负数都补0。
如:-16=0000,0000,0000,0000,0000,0000,0001,0000;(4个字节)
16=0000,0000,0000,0000,0000,0000,0001,0000;
13、类型转换
自动类型转换:容量小的数据类型可以自动转换为容量大的数据类型。
特例:可以将整型常量直接赋值给byte,short,char等类型变量,而不需要进行强制类型转换,只要不超出其表示范围。
强制类型转换:强制类型转换,又称为造型,用于显式的转换一个数值的类型。在有可能丢失信息的情况下进行的转换是通过造型来完成的,但可能造成精度降低或溢出。
强制类型转换的语法格式:(type)var,运算符“()”中的type表示将值var想要转换成的目标数据类型。
当将一种类型强制转换成另一种类型,而又超出了目录类型的表示范围,就会被截断成为一个完全不同的值。
不能在布尔类型和任何数值类型之间做强制转换。
整型数值型默认是转换为int型,若是带有小数类型的默认转换为浮点型。 转为了float后面用大写L。
int a=200;//先将200转换为二进制,注意符号位,最高位为0正数位原码反码补码不变
byte b=(byte) a;//若是最高位是1负数,应将负数二进制转为补码,再转为十进制存储进b
System.out.println(b);//byte最多存储一个字节(8位)
14、随机数值包
随机数 Math.random()表示[0,1)的double数值型随机数,Math是lang包下的不用导包。
15、while和for、swtich循环区别
循环可以减少重复的代码语句。
while用于不知道循环次数情况下使用的。
for用于知道循环次数的情况下使用的。
switch中的case判断常量,要使用break,防止程序跳级。
case可以判断byte short long int double char String 枚举,不能使用float。
16、break和continue、return的区别
break:退出一层循环
continue:退出本轮循环,继续下一轮循环
return:结束方法,循环中遇到了return,不管有几层循环,都会退出。
注意:三个语句后面都不能跟任何代码,break和continue只能在循环中使用。
循环嵌套代码
int randomNum= (int)(Math.random()*100+1);
// System.out.println(randomNum);
int count=1;
while (true){
System.out.println("请输入0--100的数字!");
Scanner scanner=new Scanner(System.in);
int i = scanner.nextInt();
if(randomNum==i){
System.out.println("恭喜你猜对了!");
System.out.println("共猜了"+count+"次");
break;
}
if(i>randomNum){
System.out.println("数字猜大了!");
}
if(i<randomNum){
System.out.println("数字猜小了!");
}
count++;
}
17、逻辑运算符
&&(与)||(或)!(非) &(与 位运算) |(或 位运算)
&&:两边运算结果为true 最终结果就为true。
||:两边运算结果有一个true,最终就是true。
&&与&区别?
&&:短路运算 前面的表达式结果确定最终的结果,后面的表达式就不参与运算了
&:两边都需要参与运算
18、方法
概念:方法就是具有特定功能的独立的代码块。
方法定义在类中,方法与方法之间是平行的。不能在方法中定义方法只能调用方法。
语法:
[修饰符] 返回值类型 方法名称(形参){
方法体
}
修饰符:public static
返回类型:没有返回值用void,有返回值需要考虑类型。
方法名称:遵循驼峰命名法,不能有同名的方法。
形参:参数的类型、个数
方法体:方法内的功能代码。
方法签名,确保一个类中的唯一性,方法的签名只看方法名和形参(类型、个数和顺序),与修饰符返回类型和形参名无关。
方法的重载的特征:
重载:在同一个类中方法名相同,参数列表不同。
仅仅方法返回值不同不能构成重载
19、递归
递归是一种常见的解决问题的方法,即把问题逐渐简单化。递归的思想就是自己调自己。利用递归可以简单的程序来解决一些复杂的问题。
定义递归头:什么时候不调用自身方法。如果没有头,就将陷入死循环。
递归体:什么时候需要调用自身方法自身需要做什么操作。
递归的缺陷:调用递归会占用大量的系统堆栈,内存耗用多,在递归调用层次多时,一层调用一层,直到调用递归头,然后从递归头向上执行到main方法为止。如果没有递归头就会出现栈溢出的错误。
递归使用案例
//用递归打印n!值
public static void main(String[] args) {
int i = printSum(4);
System.out.println(1);
}
public static int printSum(int num){
if(num==0||num==1){
return 1;
}else{
return num*printSum(num-1);
}
}
20、数组
数组在java里面属于一种引用数据类型概念,数组有长度定义。 数组最大的特点就是可以直接解决一组相关变量的定义与管理问题。
语法:数据类型[]数组名称=new 数据类型[长度]
数组类型 数组名称[]=new 数据类型[长度]
数据类型[] 数组名称=null
数组名称=new 数据类型[长度]
数组动态初始化,开辟空间后,数组默认为数据类型的默认值。如果数组中的个数超过数组下标,则会出现数组越界异常。
数组静态初始化:数据类型 数组名称[]={数值1,数值2,...}
数据类型[]数组名称=new 数据类型[]{数值1,数值2,...}(推荐使用).
- 遍历数组
1、使用for循环
数组长度固定,下标从0开始到数组长度减一下标。
语法:
int[] arr=new int[10]{1,2,3,4,5,67,8,9,7};
for(int i=0;i<arr.length;i++){
System.out.println(arr[i]);
}
- foreach输出
这种语法结构是由.NET语法衍生而来的,利用这种结构可以非常轻松的实现数组或集合内容的输出。
for(数据类型 临时变量:数组){
System.out.println(临时变量);
}
- 数组引用传递
数组引用传递是改变堆内存中数组下标中的数值,对象引用传递和数组引用传递原理是一样的。通过change()改变堆内存的数组值,temp数组变量栈内存断开联系。数组中多个栈内存对象可以指向同个堆内存地址。
- 测试代码如下:
int data[] = new int[] { 10, 20, 30 }; // 数组静态初始化
int temp[] = data; // 引用传递
temp[0] = 99; // 修改数组内容
for (int x = 0; x < data.length; x++) { // 循环输出数组数据
System.out.print(data[x] + "、");// 根据索引访问数组元素
}
- 数组排序案例代码
1、采用两两比较交换位置操作。
2、采用面向对象的思想进行代码编写。
public class ArraySortDemo {
public static void main(String[] args) {
int[] arrays=new int[]{10,5,15,0,20,18,30,25};
ArrayUtil util = new ArrayUtil(arrays);
util.sort();
util.printArrays();
}
}
class ArrayUtil{
private int[] arrays=null;
public ArrayUtil(int[] arrays){
this.arrays=arrays;
}
//冒泡排序 从小到大排序
public void sort(){
for(int i=0;i<this.arrays.length;i++){
for(int j=0;j<this.arrays.length-i-1;j++){
if(this.arrays[j]>this.arrays[j+1]){
int temp=arrays[j];
arrays[j]=arrays[j+1];
arrays[j+1]=temp;
}
}
}
}
public void printArrays(){
for (int i=0;i<this.arrays.length;i++){
System.out.print(arrays[i]+", ");
}
}
}
- 二分法查找算法
//前提是数组的值是有序的
int search=60;
int[] nums=new int[]{10,20,30,40,50,60,70,80,90,100};
int head=0;
int button=nums.length-1;
boolean flag=true;
while (head<=button){
int mid=(head+button)/2; //每循环一次就要改变中间下标一次
if(search==nums[mid]){
System.out.println("恭喜你!数字已经找到了!");
System.out.println("下标是"+mid);
flag=false;
break;
}else if(nums[mid]>search){
button=mid-1;
}else {
head=mid+1;
}
}
if(flag){
System.out.println("很遗憾,没有找到这个数字!");
}
- 选择排序
//选择排序
int[] nums=new int[]{10,6,25,85,52,32,89,20,56};
int min=0;
for (int i=0;i<nums.length-1;i++){
min=i;
for (int j=i+1;j<nums.length;j++){
if(nums[j]<nums[min]){
min=j;
}
}
if(i!=min){
int temp=nums[i];
nums[i]=nums[min];
nums[min]=temp;
for (int n:nums){
System.out.println(n);
}
- 数组的转置
数组的转置实际上就是第一个和最后一个交换位置,第二个和倒数第二个交换位置以此类推。
- 测试代码
public class ArraySortDemo {
public static void main(String[] args) {
int[] arrays=new int[]{10,5,15,0,20,18,30,25};
ArrayUtil util = new ArrayUtil(arrays);
// util.sort();
util.reverse();
util.printArrays();
}
}
class ArrayUtil{
private int[] arrays=null;
public ArrayUtil(int[] arrays){
this.arrays=arrays;
}
//数组排序
public void sort(){
for(int i=0;i<this.arrays.length;i++){
for(int j=0;j<this.arrays.length-i-1;j++){
if(this.arrays[j]>this.arrays[j+1]){
int temp=arrays[j];
arrays[j]=arrays[j+1];
arrays[j+1]=temp;
}
}
}
}
//数组的转置
public void reverse(){
int center=this.arrays.length/2;
int head=0;
int tail=this.arrays.length-1;
for(int i=0;i<center;i++){
int temp=this.arrays[head];
this.arrays[head]=this.arrays[tail];
this.arrays[tail]=temp;
head++;
tail--;
}
}
public void printArrays(){
for (int i=0;i<this.arrays.length;i++){
System.out.print(arrays[i]+", ");
}
}
}
- 二维数组的转置
二维数组转置实际上是二维立体上,除了行列相同数字外的其他位置进行交换,比如位置[2][4]值交换到位置[4][2]上
public class ArraySortDemo {
public static void main(String[] args) {
// int[] arrays=new int[]{10,5,15,0,20,18,30,25};
int[][] arrs=new int[][]{{1,2,3},{4,5,6},{7,8,9}};
ArrayUtil util = new ArrayUtil(arrs);
// util.sort();
util.reverseTwo();
util.printArrays();
// util.printArraysTwo();
}
}
class ArrayUtil{
private int[] arrays=null;
private int[][] arr=null;
public ArrayUtil(int[] arrays){
this.arrays=arrays;
}
public ArrayUtil(int[][] arr){
this.arr=arr;
}
//冒泡排序 两两交换 升序排序
public void sort(){
boolean b=false;
if(!b) {
for (int i = 0; i < this.arrays.length; i++) {
for (int j = 0; j < this.arrays.length - i - 1; j++) {
if (this.arrays[j] > this.arrays[j + 1]) {
int temp = arrays[j];
arrays[j] = arrays[j + 1];
arrays[j + 1] = temp;
}
}
}
b=true;
}
}
//转置一维数组
public void reverse(){
int center=this.arrays.length/2;
int head=0;
int tail=this.arrays.length-1;
for(int i=0;i<center;i++){
int temp=this.arrays[head];
this.arrays[head]=this.arrays[tail];
this.arrays[tail]=temp;
head++;
tail--;
}
}
//转置二维数组
public void reverseTwo(){
boolean flage=false;
if(!flage) {
for (int i = 0; i < this.arr.length; i++) {
for (int j = 0; j < i; j++) {
if (i != j) {
int t = this.arr[i][j];
this.arr[i][j] = this.arr[j][i];
this.arr[j][i] = t;
}
}
}
flage=true;
}
}
//打印二维数组
public void printArrays(){
for (int i=0;i<this.arr.length;i++){
for(int j=0;j<arr[i].length;j++)
System.out.print(arr[i][j]+", ");
}
}
//打印一维数组
public void printArraysTwo(){
for (int i=0;i<this.arrays.length;i++){
System.out.print(arrays[i]+", ");
}
}
}
- 二维数组的增强for循环遍历
int[][] arrs=new int[][]{{1,2,3},{4,5,6},{7,8,9}};
for (int[] a:arr){
for(int array:a){
System.out.print(array+",");
}
}
21、面向对象
1、面向对象与面向过程的区别。
面向对象把整个过程看成一个对象,不注重具体每个过程的活动,而面向过程则注重 每步过程的活动具体方案。
对应到程序设计中,以面向对象的思想来进行程序设计
重点就是:找具有对应功能的对象,使用对象,没有对象自己造对象的过程,面向对象思想好处:极大提高开发效率
2、面向对象三大特征
封装、继承、多态
3、什么是类?
类是对某一类事物的具体描述,如何描述(找共有的特征和行为)
描述学生 ( 特性:学号 姓名 性别 婚否 行为:吃饭,睡觉,学习,游戏..... )
描述老师(特征: 编号 姓名 性别 婚否 籍贯... 行为:管理老师,授课....)
描述笔记本(特征:品牌 型号 价格 颜色... 行为:开机,关机,打游戏,看电影....)
4.类和对象的区别
类是抽象的概念,理解为一种模板,是一种泛指/统称
对象是类的具体化/实例化,具体存在的。
5、类的定义
生活中先有对象,才有类
程序中先有类,才有对象
语法:
[修饰符] class 类名{
类中成员;
属性表示特征;
方法表示行为;
}
类名的命名:遵循大驼峰命名规范 Student Teacher
6.语法:
设置值:
对象名称.属性名称 = 值;
获取值:
对象名称.属性名称;
7.类中属性/成员变量/实例变量
特点:作用范围是在整个类有效的
都有默认的初始值 int 0 ,double 0.0 ,boolean false ,char '\u0000', 引用数据类型 null。
- 构造器
1.作用
类中成员,对对象进行初始化
2.语法
[修饰符] 构造方法名称(形参列表){
方法体;
}
2.特点
1.1构造器的名称和类名一致!!!
1.2构造器默认有空构造器,一旦添加的带参构造,空构造就没有,如果需要使用则手动添加。
1.3构造器是一个特殊的方法,支持重载,可以添加不同参数的构造器。
1.4构造器没有返回值,不能声明void。
1.5方法需要手动调用,构造器不需要手动调用,new的时候调用构造器,调用哪一个构造器,由传入的参数来决定。
- 代码块/构造块
作用:
完成对对象的初始化
什么时候使用?
在每一个构造器中,都有重复的代码,可以将重复的代码提取出来,定义在构造块中
执行顺序:
先执行构造块,再执行构造器。 对象实例化后与构造器一样只会执行一次。
- 对象在内存中的分析
1、对象实例化后会在堆内存开辟一块空间,然后加载变量。
2、类的字节码文件加载在内存的方法区,只加载一次。
3、对象实例化在堆内存开辟一块区域
默认初始化(系统默认值)
显示初始化(类中赋值)
构造块初始化(代码块)
构造器初始化
4、栈内存的变量值指向堆内存地址进行变量赋值。
5、多个栈内存可以指向一个堆内存,反之不行。
6、栈内存保存对象的堆内存地址,堆内存保存对象的真实数据。
- 成员方法
方法重载:同一类中,方法名相同,参数列表(参数类型,参数个数,参数顺序)必须有一项不同。
优点:可以让一个程序段尽量减少代码和方法的种类
仅仅改变方法返回数据类型不会构成方法重载。
方法调用的时候,值传递都是以值拷贝的形式传递(重点理解)
* 传递的值类型为基本数据类型,不会对数据进行修改
* 传递的值为引用数据类型,在方法中会对堆内存中的数据做修改。
可变参数:参数的类型相同,个数不确定,使用可变参数
注意:
1.x表示一个可变参数 2.本质是一个数组 3.可变参数必须位于参数列表的最后一个位置
- this关键词
this:表示当前对象
1.普通方法使用this: 表示谁调用这个对象,指的就是谁
2.在构造器中使用this,区分局部变量和成员变量,指的也是当前对象。
3.在构造器中通过this(参数)调用重载的构造器,必须位于第一行,
4.在main()方法中不能使用this关键字
5.在静态构造块和静态方法都不能使用this。
- static关键字
static 静态的
特点:
跟随类的加载而加载,生命周期和类的生命周期一样。
使用场景:
1.static修饰成员变量,称为类变量,跟随类的加载而加载,在内存中只有一份,数据共享
2.static修饰成员方法 称为类方法/静态方法,跟随类的加载而加载,
不能访问非静态的成员,只能访问静态成员,不能使用this关键字。(工具类中方法都为静态方法,方便调用)
3.static修饰构造块,称为静态代码块,对类进行初始化,只会执行一次。比如:数据库连接的工具类加载驱动定义在静态代码块中。
调用静态成员:
类名.静态成员
- package
包作用:管理类
命名:公司名称.项目名称.模块名称
- 访问权限修饰符
public:权限最高,所有都可以调用。
protect:在不同包下的类不能调用,只能通过继承父类,引用其属性。
default:在不同包下的类不能调用,继承父类也不能使用其修饰的属性。
private:权限最低,只能在同一类中使用,其他方法都无法调用。
权限修饰符注意事项:
* 1.public修饰类,类名和文件名称一致
* 2.public 和default可以修饰类
* 3.四种访问权限符可以修饰构造器
* 4.不能修饰局部变量
* 5.四种访问权限修饰符都可以修饰成员变量和成员方法
- 继承(Extends)
优点:提高代码的复用性,减少重复的代码,让类与类之间有继承关系,才有多态。
如何实现继承:
提取多个类中共有的属性和方法,定义到另一个类中,让其他的类继承该类。
class AA extends BB{
}
AA 是子类/派生类
BB 是父类/超类/基类
注意:
1、继承关系一定是所属关系,比如狗继承于动物。
2、子类继承所有父类中的成员方法和成员属性,私有的由于访问权限限制,可以通过set和get访问父类中的私有成员;
3、子类只能继承一个父类,单继承。
4、一个继承体系中,使用最底层的类,因为拥有的功能最多。
5、子类的构造器第一行默认有一个super() 访问父类中的空构造。
6、在一个继承体系中,当所有的类都有相同的属性,创建子类对象的时候,先查找子类是否有值,如果有直接访问子类,如果子类没有,查找父类是否有,如果父类也没有,再向上查找,一直查找到Object类为止。
- 方法重写(Override)
1.什么是重写?
在继承中,父类的方法不能满足子类的需要,子类可以对父类方法进行重新定义。
2.重写要求
a.方法名,形参列表,返回值类型必须保持一致。
b.重写方法不能使用比被重写方法更严格的访问权限 (重新的访问权限不能比被重新的访问权限小)
c.final,static,private修饰的方法不能被重写。
d、两个类有继承关系。
e、子类重写方法的异常类型要小于父类被重写的异常类型。
注意:子类重写的时候,子类返回值类型必须是父类方法的返回值类型,或者是父类的子类。
- super关键字
super指的是父类对象的引用。
super使用场景:
1、子类的构造器中默认第一行super()访问父类中含参构造,也可通过super(参数)访问父类中的构造。
注意:
1、在构造器中使用super()必须位于第一行;
2、如果已经使用了super(),就不能使用this().
3、在子类中通过super.成员变量/成员方法,访问父类中的属性值或者方法。
4、在子类的重写方法中,通过super.方法名()访问父类中被重写的方法。
- 多态
有继承关系,子类重写父类方法,子类实例化指向父类对象。
父类的引用指向子类对象。编译时看返回引用数据类型,运行时看实例化对象类型。
调用重写方法取决于运行类型,属性没有多态,取决于编译类型。
注意:
向上转型只能调用编译类型的方法和属性
如果想要调用子类特有的属性和方法,必须通过类型转换(向下转型)才能使用子类特有的方法。
转型的两种方式:
1、向上转型 Animal a=new Cat();cat提升为Animal。
2、向下转型 Animal cat=(Animal)a
注意:只能转型为指向的子类对象,否则会出现类型转换异常(ClassCastException)
instanceof:判断对象所属类型,只能判断对象类型是否是继承关系。
动态绑定机制:
1、多态前提下,方法在运行时期,会和对象的运行类型进行绑定,具体调用哪个方法,由运行类型决定。
2、属性没有动态绑定,属性只看编译类型。
- 源码阅读
public class Exercise10 {
static int x, y, z;//类变量
static {
int x = 5; //局部变量
x--; //执行局部变量 4
}
static {
x--; //-1 执行类变量
}
public static void main(String[] args) {
System.out.println("x=" + x);//-1 打印类变量
z--; //-1 //执行类变量
method();//0
System.out.println("result:" + (z + y + ++z));//1+0+2=3 ++0=1
}
public static void method() {
y = z++ + ++z; //-1++ + ++-1=0 执行类变量
}
}
- final关键字
1、final表示最终最后
2、final修饰成员变量,一旦赋值不可更改,必须有初始值,可以在构造器中赋 值操作,只能赋值一次。
3、修饰局部变量 final int x,不能修改值。
4、修饰成员方法,该方法不能被子类重写,可以被重载,方法可以被子类继承。
5、修饰的类不能被继承,没有子类,比如java.lang.Math类。
6、final修饰成员变量、常量通常定义时加上static修饰。
常量: public static final 数据类型 常量名称=值。常量名称一般全部字母大写。(只能赋值一次,无法更改)
注意:final修饰的是引用变量时只是引用值不能改变,对象内部属性还是可以改变。
- 抽象类(abstract)
一种模板模式,抽象类为所有子类提供一个通用模板,子类可以在这个模板基础上进行扩展,通过抽象类,可以避免子类设计的随意性。
在一个继承体系中,需要规范子类的定义,必须让子类重写父类中的方法,需要将该方法声明为抽象方法,abstract关键字声明。
1、在继承体系中,子类必须重写父类的抽象方法,抽象类只能被继承。
2、类中声明抽象方法,该类必须是抽象类。
3、抽象方法没有方法体。
4、抽象类可以有普通方法和静态方法、成员变量。
5、抽象类可以有构造器,默认有无参构造,可以手动添加有参构造,不能用来初始化,目的只是为了让子类访问。
6、抽象类中可以没有抽象方法。
- 接口(Interface)
1、为什么要使用接口
实现了规范与具体实现的分离
2、用Interface 接口名字 定义接口。
3、接口使用细节:
a、接口中都是抽象方法,不能有普通方法,JDK8.0之后可以添加静态方法和默认方法。
默认方法是public default void 方法名(){}。
void 方法名();是抽象方法,修饰符和abstract可以省略。
b、接口中的成员变量默认都是常量,都有public static final int x=20。
c、JDK9.0以后接口中可以有私有方法,声明静态方法可以在接口内部中默认方法直接通过接口.方法调用,普通方法可以直接在接口内默认方法中直接调用,实现类不能使用私有方法。
d、接口不能进行抽象声明。
4、接口中所有权限修饰符全部是public或者默认,没有构造器、普通方法。
5、一个类可以同时实现若干个接口,可以避免单继承的局限。
6、 子类不但可以实现多个接口,还可以同时继承一个父类,则需要采用先继承后实现进行定义。
7、接口是不能继承任何父类,不能做子类,但是所有的接口对象都是可以通过Object来进行接收。(Object可以接收一切引用类型,可以实现最终参数的统一。)
8、一个接口可以继承多个父接口,也就是接口的多继承,实现类要把所有父接口的方法重写,通俗点讲就是接口只能与接口之间实现继承。
9、子类实现不同的接口和接口的父接口,以及继承父类或抽象父类和父类的父类,接口与父类没有任何联系,但是子类可以向上转型任何一个父接口或者父类。
注意:接口类和抽象方法属于实现关系,抽象方法可以不全部实现接口抽象方法。
实现类实现接口的方法必须要加修饰符public,接口的方法可以不加修饰符默认是public。
22、内部类(InnerClass)
内部类开发常用的是匿名内部类,其他内部类学习是辅助读源码。
内部类定义在外部类成员位置上成员内部类。
在方法中定义内部类叫局部内部类。
用static修饰的内部类叫静态内部类。
1、成员内部类
a、成员内部类可以直接访问外部类中的成员变量和成员方法。
b、可以添加权限修饰符(private、public、default、protect)
c、外部类访问成员内部类中的方法要通过实例化进行访问。new outer_().new Inner_(),并且成员内部类必须为public修饰。
2、局部内部类(属性与局部代码块是一致)
a、只能被final修饰,不能被权限修饰符和静态修饰,跟局部变量一样。
b、方法中不能访问局部内部类,局部内部类可以直接访问外部类。
c、外部其他类不能访问局部内部类。
d、外部其他类要访问局部内部类,使用外部类.this.方法();
3、静态内部类
a、用static声明的内部类。
b、外部类实例化访问普通方法和属性,可以直接访问内部类中的静态方法和属性。
c、作用域:整个外部类区域。
4、匿名内部类(重点)
a、传统的方法在没有匿名类,如果想要实现接口类的方法时,需要定义一个实现类,然后重写抽象方法,然后实例化实现类调用抽象方法。
b、如果接口中的方法只实现一次,可以采用匿名内部类来简化代码,不需要定义实现类。
c、本质上还是jvm创建一个匿名实现类实现接口,然后实例化调用方法,然后释放实例化对象,减少了空间。
- 匿名内部类的测试代码
InterfaceDemo id=new InterfaceDemo() {
@Override
public void look() {
System.out.println("look……");
}
@Override
public void run() {
System.out.println("run……");
}
@Override
public void jump() {
System.out.println("jump……");
}
};
id.user();
id.look();
23、String类对象
1、实例化对象的两种方式:
直接赋值:String 字符串对象="字符串";
构造方法实例化:String s=new String("字符串");
[注意]c、c++对于字符串是通过数组的形式来实现的。jdk1.8以前String对象的构造方法是字符数组,jdk9以后变成字节数组,主要是字符数组的长度不可变,不利于灵活存储,浪费空间。jdk13开始可以用块的形式进行定义了。(String str="abc"+"bcd"+"def"),字符串本身是由字符数组所组成。
2、jdk13新特性可以用文本块来赋值。
String str="""
你好,你在哪?
abc
""";
【注意】jdk13新特性使用前提要开启预览编译,打印出换行符自动保留。
3、字符串比较。
a、编程语言内部会提供关系运算符,基本数据类型的数值型可以通过‘==’进行比较。两个字符串对象进行“==”比较本质是比较两个字符串对象的堆内存地址数值(所有引用类型的比较都是这种原理)。
b、想要比较两个字符串内容要使用String内置方法:字符对象1.equals(字符对象2).
4、字符串常量。
a、字符串常量一旦定义不可改变。
b、可以直接调用equals()进行字符串内容比较,是String的匿名对象,字符串常量存放在堆内存中。
c、为了防止出现NullPointerException异常,可以直接调用常量.equals()作为判断条件,因为字符串常量本身是匿名对象,可以处理Null。
5、String对象实例化方式比较
a、String类直接赋值里面采用一个共享设计模式,也就是多个栈内存指向同一个常量池,不同对象相同值不会开辟新的内存空间,而是多个对象指向同一个常量池地址进行引用关联,减少无用堆内存的开辟。
b、String类实例化对象,会开辟两块内存空间(堆内存和方法区常量池),对象只会指向一个堆内存,一块内存空间会成为垃圾空间。
c、通过new调用构造方法进行对象初始化,创建实例化对象不会自动入池,该对象堆内存数据只能被一个String类所使用,可以调用对象.intern()手工入池返回常量池的地址引用,只会指向常量池。
6、字符串常量
a、静态常量池:程序加载会自动将此程序的方法、属性等资源进行分配。
String strA="www.baidu.com";
String strB="www."+"baidu."+"com";
strA=strB (true)
b、动态常量池:在java程序执行的时候有一些字符串的数据是需要通过计算得来的(不是以字符串常量的形式进行的定义)。
String strA="www.baidu.com";
String tem="baidu";
String strB="www."+tem+".com";
strA=strB (false);
7、字符串修改分析
String a="www.";
a+="baidu";
a=a+".com";
System.out.printn(a); //不会入池 会在堆内存存放baidu、.com、www.baidu.com 常量池:www.
以上代码会产生多个字符串对象,而最终是指向最后拼接完整字符串的对象地址,这样会产生大量的字符串对象内存垃圾,大大影响程序性能,有变量拼接就在堆内存中,常量直接拼接在常量池中(包括final修饰的变量名也在常量池中)。
8、主方法组成分析
a、所有程序都是从主方法开始运行的
b、public:公开修饰权限。static:静态类方法,可以直接类调用。void:空返回值。main:系统内置方法名称,不可更改。 String args[]:方法形式参数,可以动态传入初始化参数。
- String类常用方法(Java Doc)
通过查看javaDoc文档去查看java类中的方法,进行方法的测试。尽量查看英文文档。
- 字符常用方法
public String(char[] value):将一个字符数组的全部内容直接转为字符串,能很好处理中文。
public String(char[] value,int offset,int count):将一个字符数组的部分内容转为字符串,使用offset设置转换的开始位置,count定义转换的长度。
public char charAt(int index):获取指定索引位置的字符。
public char[] toCharArray():将字符串转为一个新的字符数组。
- 字符串与字节常用方法
public String(bytes[] bytes):将全部字节数组转为字符串。
public String(byte[] bytes,int offset,int length):将部分字节数组转为字符串,offset表示转换的开始点,length表示要转换的个数。
public byte[] getBytes(String charsetName):实现编码的转换,参数是描述编码。
public byte[] getBytes():使用默认的编码将字符串转为字符数组与toCharArray()是一样的。
jdk1.9以后,String类内部保存数据的数组类型统一设置为了byte
- 字符串比较常用方法
//String重写了Object的equals()方法,进行字符串内容比较,会区分大小写。
public boolean equals(Object anObject):字符串内容比较,会区分大小写。
public boolean equalsIgnore(String anotherString):字符串比较,不区分大小写。
public int comparaTo(String anotherString):判断两个字符串大小比较,如果大于关系返回大于0的正数,如果相等返回0,如果小于返回小于0的负数,调用类字符串与传入字符串编码差值。
public int comparaToIgnoreCase(String str):不区分大小写进行大小关系的比较。
- 字符串查找常用方法
public boolean contains(CharSequence s):判断当前的字符串之中是否包含有子字符串(CharSequence理解为String的含义),该方法是从JDK1.5以后提供的。
public int indexOf(String str):从字符串的第0个索引开始,检查指定子字符串的开始位置,如果存在返回位置的索引号(字符串第一个字符的位置),如果不存在返回-1.
public int indexOf(String str,int fromIndex):从指定索引位置开始检查子字符串的位置,如果存在返回位置的索引(字符串第一个字符的位置),如果不存在返回-1.
public int lastIndexOf(String str):从最后一个索引位置从后往前查找(字符串第一个字符的位置 ),如果找不到返回-1.
public boolean startsWith(String prefix):判断是否以指定的子字符串开头。
public boolean startsWith(String prefix,int offset):从指定的索引位置处判断是否以指定的子字符串开头。
public boolean endsWith(String suffix):判断是否以指定的子字符串结尾。
- 字符串替换常用方法
public String replace(String oldChar,char newChar):替换单个字符串
public String replaceAll(String oldChar,String newChar):替换全部字符串
- 字符串拆分常用方法
public String[] split(String regex):将字符串按照指定的子字符串全部拆分。
public String[] split(String regex,int limit):将字符串按照指定的字符串拆分为指定长度的数组。如果设置的拆分次数超过了原来长度则表示全部拆分(不会出现任何的错误)。
[注意]如果拆不了的字符,统一转义处理,参数默认是正则,比如"\\"表示\ "\\." 表示" ." "\\|"表示"|"。
- 字符串截取常用方法
public String substring(int beginIndex):从指定的索引位置截取到结尾。
public String substring(int beginIndex,int endIndex):设置开始索引和结束索引截取字符串(索引范围:[beginIndex,endIndex))。
- 字符串格式化常用方法
//JDK1.5以后String提供了静态方法format()方法
public static String format(String format,Object... args).
String result=String.format("%s,%d,%5.2f",name,age,score)//沿用了c语言的格式化输出格式。
- 其他常用方法
public String concat(String str):进行字符串的连接,但是连接后的字符串不会进常量池,需要调用intern()方法入池。
public String repeat(int count):字符串重复定义 String a="hello".repeat(2)//打印hellohello
public static String copyValueOf(char[] data):将一个字符数组转为字符串。
public boolean isBlank():判断字符串是否是空或者由空白字符所组成,JDk11提供
public boolean isEmpty():判断是否为空字符串。(字符串长度为0,[注意]换行符\n空格\u000长度不为0)
public int length():计算字符串长度,与数组的length不同,数组length是属性。
public String strip():删除所有空白字符,但是保留Unicode空白字符("\t")jdk11
public String trim():删除字符串前后两边的空格。
public String toLowerCase():将字符串中的字母全部转为小写。
public String toUpperCase():将字符串中的字母全部转为大写。
- StringBuilder和StringBuffer的区别?
1、String是不可变字符序列,如果需要频繁对字符串进行添加、修改,会产生大量的副本,原来的字符串一点都没有改变,造成大量垃圾空间产生,影响程序效率。
2、StringBuilder和StringBuffer都可用于可变字符序列,用法都是一样的。
a、String:不可变字符序列,对字符串查询相关,建议使用String。
b、StringBuffer:如果考虑线程安全优先使用此对象中的方法操作字符串,但效率低,默认开辟存储字符串长度是16,如果字符串长度超过16则会通过字节数组进行扩容,会产生字符串垃圾,所以最好使用前先定义好初始化大小。
c、StringBuilder:如果考虑效率高,可以使用此对象中的方法拼接字符串,但线程不安全。
d、StringBuffer与StringBuilder区别?
一、StringBuffer与StringBuilder都可以实现字符串内容的修改处理操作。
二、StringBuffer属于线程同步的处理过程,多个线程进行修改的时候可以自动实现同步机制,保证数据修改的正确性,效率低。
三、StringBuilder属于非线程安全的处理,没有进行同步的操作,多个线程同时修改的时候一定会有数据的修改的错误,效率高,推荐使用。
四、单线程环境下两者没有任何区别。
3、常用方法(StringBuilder一样)
public StringBuffer():实例化一个空的StringBuffer对象。
public StringBuffer(String str):使用特定的字符串实现StringBuffer的定义。
public StringBuffer(int capaC ity):定义StringBuffer字符串大小。
public StringBuffer append(E b):向StringBuffer中保存数据。
public StringBuffer delete(int start,int end):删除指定索引中的内容,字符串没有的方法。
public StringBuffer insert(int offset,E b):在指定位置处增加数据,字符串没有的方法。
public StringBuffer reverse():字符串反转,字符串没有的方法。
public StringBuffer replace(int start,int end,String str):替换指定范围的字符串。
24、异常类型(Exception)
1、异常概念:程序在运行时出现的问题,不会出现程序的执行。
2、异常分类:
a、Error:程序错误,程序员无法解决,不用重点关注。[例如:]`StackOverflowError`
b、Exception:程序异常,可以处理。
1、运行时异常:程序运行后jvm抛出的异常。比如:`ArithmeticException`等.
2、编译时异常(CheckedException):程序编译时出现的异常,如果不解决无法运行程序。比如:`IOException`,`SQLException`,`NumberFormatterException`等.
3、处理异常的代码格式:
a、捕捉异常格式:
try{
}catch(RuntimeException e){
//捕获抛出的异常,需要定义异常对象 接收抛出的异常
}finally{
}
b、抛出异常格式
public void a() throws 异常对象{
}
4、语法上可以使用的格式
try{}catch(){}
try{}finally{}
try{}catch(){}catch(){}
[注意]只有一个catch()会执行,然后一定会走finally代码块,异常发生时后面的代码不会执行,子类异常类型放前面catch()父类异常必须放后面catch(),否则编译不能通过。
当catch中有return,finally中有return时,只会执行finally中的return,如果finally中没有return,先执行完finally中的代码再执行catch的return,要是进行运算时变量赋值叠加。
5、抛出异常类型
如果不想处理异常,在方法名旁边用throws异常类型抛出异常,可以抛出多个异常,可以一直抛出到main()上交予jvm处理。在方法中用throw实例化异常对象,抛出异常。
6、自定义异常
定义一个类,去继承Exception类或者他的子类(RuntimeException等),因为异常必须直接或者间接继承Exception类,可以使用Throwable(String message)构造器传入自定义异常信息。
throw和throws区别?
1、使用位置不同:throw是在方法内抛出异常throws是在方法上声明异常。
2、后面跟的内容不同:throw异常对象,throws异常类型。
25、Object类
1、object是java的一个系统类,是所有类的父类(包括系统类以及系统定义的类),所有的类可以向上转型到object类实现对象的统一。
2、获取对象信息
任何类实例化对象,如果直接进行输出时候默认返回一个对象编码数据,是由Object类中的toString()决定的,不同对象实例化输出的对象编码数据不一样,只要是打印对象不管有没调用都默认调用toString()进行输出。
Object有一个无参构造,由于Object是所有类的父类,便于子类访问Object的属性方法。
可以通过重写toString()方法打印对象的属性信息。
3、对象比较
a、将类的每一个属性进行判断,但是传统的比较方法将类的全部细节都暴露在外,应该比较类本身具备的功能,应该将这个功能直接在类中定义,也就是重写equals()方法进行比较,equals()方法默认比较对象地址数值,String重写了Object中的equals()方法,比较字符是否一致。
b、HashSet实现类的去重复性 必须实现Hashcode和equals方法的覆写,如果使用hashmap的键和值都是对象的话,这个对象类要是覆写equals和hashCode方法,先比较地址数值地址数值相同再比较hashCode。
4、equals()和==区别是什么?
equals()只能比较引用数据类型,是由Object提供的,而==既可以比较引用数据类型的地址数值又可以比较基本数据类型值大小。
- 对象比较和重新toString的测试代码
public class ObjDemo {
private String name;//书名
private String author;//作者
private float price; //价格
//没重写toString时默认实例化对象进行对象输出,
// 都会自动调用toString()方法,打印对象编码数据
public void setName(String name){
this.name=name;
}
public String getName(){
return this.name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
//重写Object中toString方法打印对象信息
public String toString(){
return "[图书信息]书名:"+this.name+",作者:"+this.author+",价格:"+this.price;
}
//重写equals方法进行对象的比较,判断是不是同一个对象
public boolean equals(Object obj){
//如果是同一个对象返回true,提升系统的性能
if(obj==this){ //比较两个对象的地址数值
return true;
}
//为了防止出现NullPointerException异常,判断对象是不是属于本类的
if(obj instanceof ObjDemo){
//向下转型
ObjDemo objDe=(ObjDemo) obj;
return objDe.getName().equals(this.getName())&&objDe.getAuthor().equals(this.getAuthor())&&objDe.getPrice()==this.getPrice();
}
return false;
}
public static void main(String[] args) {
// System.out.println(new ObjDemo().toString()); 打印对象编码数据
ObjDemo objDemo = new ObjDemo();
objDemo.setName("《java从入门到精通》");
objDemo.setAuthor("李兴华");
objDemo.setPrice(99.8f);
ObjDemo objDemo1 = new ObjDemo();
objDemo1.setName("《java从入门到精通》");
objDemo1.setAuthor("李兴华");
objDemo1.setPrice(99.6f);
// System.out.println(objDemo.toString()); //打印对象基本信息。
if(objDemo.equals(objDemo1)){
System.out.println("[√]两个对象相同。");
}else {
System.out.println("[×]两个对象不相同。");
}
}
}
- 对象克隆
a、对象克隆实际上就是对对象进行赋值,对象创建完成后实际上会自动开辟堆内存空间,每个堆内存空间都会保存对象的相关属性内容,所谓的对象克隆就是一个属性的赋值,这个方法实际上是利用jvm底层内存空间复制处理,通俗点讲就是一个开辟一个对象复制原来对象的属性方法内容。
b、要克隆对象的类如果没有实现Cloneable接口,就会出现CloneNotSupportedException异常,Cloneable接口并没有提供任何的处理方法,此接口属于一种标识性接口,表示一种能力。
c、深克隆(深拷贝)
所有与对象有关的基本类型全部进行克隆(基本数据类型、引用数据类型)。
d、浅克隆(浅克隆)
只克隆当前类中的基本属性内容。
- 对象克隆测试代码
public class CloneDemo implements Cloneable {
//必须实现Cloneable接口
private String ename;
private int age;
private String job;
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
@Override
public String toString() {
return "[对象编码+"+super.toString()+"]"+"姓名:"+this.ename+"年龄:"+this.age+"职位:"+this.job;
}
//克隆
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
//对象拷贝
public static void main(String[] args) throws CloneNotSupportedException {
CloneDemo cloneDemo = new CloneDemo();
cloneDemo.setEname("zs");
cloneDemo.setAge(25);
cloneDemo.setJob("学生");
Object clone = cloneDemo.clone();
CloneDemo c=(CloneDemo)clone;
System.out.println(cloneDemo.toString());
System.out.println(c.toString());
}
}
- 浅拷贝测试代码
public class CloneDemo implements Cloneable {
private String ename;
private int age;
private String job;
private Dept dept;
public Dept getDept() {
return dept;
}
public void setDept(Dept dept) {
this.dept = dept;
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
@Override
public String toString() {
return "[对象编码+"+super.toString()+"]"+"姓名:"+this.ename+"年龄:"+this.age+"职位:"+this.job;
}
//克隆
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
//对象拷贝
public static void main(String[] args) throws CloneNotSupportedException {
CloneDemo cloneDemo = new CloneDemo();
cloneDemo.setEname("zs");
cloneDemo.setAge(25);
cloneDemo.setJob("学生");
Dept dept=new Dept();
dept.setDname("教学部");
dept.setSal(5000);
cloneDemo.setDept(dept);
Object clone = cloneDemo.clone();//对象拷贝
CloneDemo c=(CloneDemo)clone;
System.out.println(cloneDemo.toString());
System.out.println(c.toString());
System.out.println("*************************************");
//对象未拷贝,只是类中的基本数据类型进行了拷贝,为浅拷贝。
System.out.println(cloneDemo.getDept().toString());
System.out.println(c.getDept().toString());
}
}
class Dept{
private String dname; //部门名称
private int sal; //工资
public String getDname() {
return dname;
}
public void setDname(String dname) {
this.dname = dname;
}
public int getSal() {
return sal;
}
public void setSal(int sal) {
this.sal = sal;
}
@Override
public String toString() {
return "[对象编码+"+super.toString()+"]"+"部门名:"+this.dname+"工资:"+this.sal;
}
}
- 代码执行结果
//类中的基本数据类型地址指向不同地址
[对象编码+com.JackMa.javase.CloneDemo@b4c966a]姓名:zs年龄:25职位:学生
[对象编码+com.JackMa.javase.CloneDemo@4769b07b]姓名:zs年龄:25职位:学生
*************************************
//类中的引用数据类型地址指向同一个地址
[对象编码+com.JackMa.javase.Dept@cc34f4d]部门名:教学部工资:5000
[对象编码+com.JackMa.javase.Dept@cc34f4d]部门名:教学部工资:5000
- 注意观察对象编码数据,对象编码数据未改变,基本属性对象编码进行了改变,只是拷贝了类中的基本属性,类中的对象未进行拷贝,还是指向同一个地址。
26、包装类
1、在进行集合操作时,需要传入引用数据类型,为了存储基本数据类型,就要把基本数据类型转换为对象进行存储,包装类均位于java.lang包下。
2、自动装箱和拆箱:基本数据类型转化引用数据类型(比如int-->Integer)为装箱;
引用数据类型转化基本数据类型为拆箱(比如Integer-->int)。
3、编译器蜜糖
Integer i9=null;
int x2=i9;//空指针异常 i9没有任何指向。
4、装箱值比较
Integer大小范围:-128----127
Integer num1=100;
Integer num2=100;
Integer num3=130;
Integer num4=130;
num1=num2;//true 值在Integer大小范围内转化为基本数据类型进行比较
num3=num4;//false 值超出Integer范围大小比较对象地址
[注意]只有自动装箱时才会执行以上规律,手动装箱只会比较对象地址值。
25、时间处理类
1、初始时间是东八区1970年1月1日8点整开始计时。
2、获取当前时间
//获取当前系统时间的毫秒数
long l = System.currentTimeMillis();
System.out.println(l);
//当前时间格式化 年月日
Date date = new Date();
SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String s = format.format(date);
System.out.println(s);
3、日期字符串转换为date日期型,字符串格式一定要与设置的格式一致,否则就会报错。
SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
// 字符串转化为Date
Date ntext = format.parse(s);
System.out.println(ntext);
- 最新时间类测试代码(jdk8以后出现的)
ZoneId id=ZoneId.systemDefault(); //获取系统当前时区
System.out.println(id);
LocalDate date=LocalDate.now(id);
LocalTime localTime=LocalTime.now(id);
System.out.println("当前时间是"+localTime);
//获取当前日期输出格式
System.out.println(String.format("%s-%s-%s",date.getYear(),date.getMonthValue(),date.getDayOfMonth()));
System.out.println("获取一周的时间(星期几)"+date.getDayOfWeek().getValue());
System.out.println("今天是本月的第几周"+date.get(ChronoField.ALIGNED_WEEK_OF_MONTH));
System.out.println("今年是一年的第几天"+date.get(ChronoField.DAY_OF_YEAR));
System.out.println("今天是几月"+date.getMonthValue());
System.out.println("###############################################");
LocalDate time=LocalDate.parse("2000-01-16");
boolean b = time.isLeapYear();//是否是闰年
System.out.println(b);
int i = time.get(ChronoField.DAY_OF_MONTH); //是本月的几号
System.out.println(i);
System.out.println("周几"+time.getDayOfWeek());
System.out.println("本月的第一天"+time.with(TemporalAdjusters.firstDayOfMonth()));
System.out.println("本月的第二天"+time.withDayOfMonth(2));
System.out.println("300年后是"+time.plusYears(300));
System.out.println("300月后是"+time.plusMonths(300));
System.out.println("日期所处第一个周一"+time.with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)));
System.out.println("2000年的第一个周一"+LocalDate.parse("2000-01-01").with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)));
- 代码执行结果
Asia/Shanghai
当前时间是11:48:41.697977400
2023-7-18
获取一周的时间(星期几)2
今天是本月的第几周3
今年是一年的第几天199
今天是几月7
###############################################
true
16
周几SUNDAY
本月的第一天2000-01-01
本月的第二天2000-01-02
300年后是2300-01-16
300月后是2025-01-16
日期所处第一个周一2000-01-03
2000年的第一个周一2000-01-03
26、数组
1、类比较器
1、通过实现接口comparator<Object>编写实现类,重写compara(Object o1,Object o2)方法,要是第一个参数属性减第二个参数属性大于0,对象根据属性值进行升序排序,小于0降序排序,等于0对象相等。对外排序通过传入comparator接口实现方法在Arrays.sort(Comparator<T> comparator)方法中排序。
2、通过实现接口comparable<Object>编写实现类,重写comparaTo(Object o)方法,this.实现类属性>o.实现类属性,大于0,对对象根据属性进行升序排序,小于0降序排序,等于0对象相等。对内排序直接调用Arrays.sort()方法进行排序。
- 测试代码如下
public class BooKDemo implements Comparable<BooKDemo> {
private String name;
private float price;
public BooKDemo() {
}
public BooKDemo(String name, float price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public float getPrice() {
return price;
}
@Override
public int compareTo(BooKDemo o1) {
if (o1.getPrice() > this.getPrice()) {
return 1;
} else if (o1.getPrice() < this.getPrice()) {
return -1;
}
return 0;
}
@Override
public String toString() {
return "[[图书信息]]书名:"+this.getName()+"价格:"+this.getPrice();
}
public static void main(String[] args) {
BooKDemo[] demo=new BooKDemo[]{
new BooKDemo("《java从入门到精通》",19.8f),
new BooKDemo("《python从入门到精通》",10.5f),
new BooKDemo("《go从入门到精通》",99.8f),
new BooKDemo("《c#从入门到精通》",58.6f)
};
//接口Comparator的匿名内部类重写方法
Comparator<BooKDemo> comparator=new Comparator<BooKDemo>() {
@Override
public int compare(BooKDemo o1, BooKDemo o2) {
if (o1.getPrice() > o2.getPrice()) {
return -1;
} else if (o1.getPrice() < o2.getPrice()) {
return 1;
}
return 0;
}
};
//根据price属性实现升序排列
// Arrays.sort(demo,comparator); 实现接口Comparator接口的排
Arrays.sort(demo); //实现接口comparable接口的排序 升序排序
System.out.println(Arrays.toString(demo));
}
}
27、Math数学类
1、java中提供数学计算的程序功能类,没有子类,final修饰的类,提供的都是类方法(静态方法),构造器私有化。
2、常用静态常量:
E:以e为底的对数常量值。
PI:圆周周期率 3.1415926
3、常用方法
round(double num):四舍五入 只保留整数部分不保留小数部分,要通过自定义编写保留几位小数。
abs(double num):绝对值
pow(double num,int count):乘方 如pow(2,2)=4
cos(double x):三角余弦
max(double x):最大值
min(double x):最小值
log(double x):对数
28、File类
1、File类的作用
对磁盘中的文件或者目录的封装,在程序中操作磁盘中的文件或者目录,需要使用File对象。
2、文件类型:文本文件(txt)、二进制文件(图片、声音、视频、字节码文件、doc、ppt等)。
3、文件操作:打开 关闭 复制 粘贴 查看文件大小 改变文件类型等
4、文件路径要是使用"\\"或者"/",推荐使用File.separator,创建文件时,如果文件夹不存在应该先创建文件夹(父路径文件),否则会找不到路径.
//a、创建File对象
public File(String pathName);//传入完整文件路径
public File(String parent,String child);//传入父路径和子路径
public File(File file);//传入文件对象
public File(File file,String child);//传入文件夹文件对象和子文件名
//b、常用方法
public boolean canRead();判断文件路径是否可读。
public boolean canWrite();判断文件路径是否可写。
public boolean canExecute():判断文件路径是否可执行。
public boolean exists();判断文件是否存在。
public boolean isDirectory();判断文件是否是文件夹。
public boolean createNewFile() throws IOException:创建一个文件。
public boolean delete():删除文件 应该事先判断文件是否存在。
public boolean mkdir():创建单级目录
public boolean mkdirs():创建多级目录(更加实用)
public String getParent():获取父路径
public File getParentFile():获取父路径对应的文件对象
public File getAbsoluteFile():获取绝对路径实例
public String getAbsolutePath():获取文件绝对路径
public String getName():获取文件名称(不包括目录的路径)
public boolean isAbsolute():是否为绝对路径
public boolean isFile():是否为文件
public boolean isHidden():是否为隐藏文件或者目录
public long lastModified():获取最后一次修改日期(毫秒数)要通过格式化转换
public long length():获取文件大小(返回字节单位数值 Byte)
public String[] list():列出所有的子路径名称(不包括文件夹路径)。
public File[] listFiles():列出所有子路径的File对象数组。(完整的文件路径)
public File[] listFiles(FileFilter filter):列出目录组成的时候设置一个过滤条件。
public boolean renameTo(File file):实现文件更名同时移动文件到file对象路径下
29、枚举(Enumeration)
1、什么是枚举
jdk1.5之后出现的新的类型,为了限制对象的创建,将有可能出现的情况罗列出来,定义在枚举中。
2、枚举定义
enum 名称{
对象名1,对象名2,……
}
可以定义属性、方法、构造器,构造器只能由private修饰,默认都是private。
3、常用方法
Enumeration.name()/Enumeration.toString:打印枚举对象名。
Enumeration.ComparaTo(E o):调用对象的位置数值减去传入对象的位置数值。
Enumeration.values():返回对象数组
Enumeration.equals(E o):比较两个枚举对象的地址数值。
Enumeration.ordinal():返回枚举对象位置数值。
Enumeration.hashCode():返回对象哈希码
- 测试代码
//1、传统定义方式
class Season{
//传统的enum枚举定义
public static final Season SPRING=new Season("春天","春暖花开");
public static final Season SUMMER=new Season("夏天","夏日炎炎");
public static final Season AUTOMN=new Season("秋天","秋高气爽");
public static final Season winter=new Season("冬天","冰天雪地");
//季节名
private String name;
//描述
private String desc;
private Season(String name,String desc){
this.name=name;
this.desc=desc;
}
public String toString(){
return "季节名:"+this.name+"\t描述:"+this.desc;
}
}
--------------------------------------------------------------------
//2、现在的定义方式
enum Week{
//在枚举内部定义实例化对象 在外部直接调用
MONDAY(),TUESDAY("周二"),WEDNEDAY("周三"),THURSDAY("周四"),FRIDAY("周五"),SATURDAY("周六"),SUNDAY("周日");
private String name;
private static final String MESSAGE="枚举";
//不能定义抽象方法
// private static abstract String test();
//构造器的修饰符只能是私有,默认就是私有方法。
private Week(){}
private Week(String name){
this.name=name;
}
public void printEnum(){
System.out.println(this.name);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//3、对象调用都是
//枚举名.对象常量名.方法
30、集合(Collection)
1、链表是解决数组长度最佳解决方案,java类集框架帮助用户实现了链表、数组结构、二叉树、队列、栈等信息。
2、Collection接口下定义的常用抽象方法
public boolean add(E e):向集合中追加单个数据。
public boolean addAll(Collection<?extends E> c):向集合中追加一组数据。
public void clear():清空集合数据
public boolean contains(Object o):判断集合中是否存在指定的数据内容,需要equals()支持。
public boolean containsAll(Collection<?> c):判断某一个集合的内容是否存在
public boolean isEmpty():判断是否为空集合(没有保存任何数据)
public Iterator<E> iterator():获取Iterator接口对象实例。
public boolean remove(Object o):从集合中删除数据,需要equals()支持
public int size():返回集合中保存的元素个数。
public Object[] toArray():将集合中的数据转为对象数组。
[注意]collection接口并不是无限制的进行数据的存储,它最大数据个数是Integer.MAX_VALUE(整型最大值)。一般不会使用接口,而是使用子接口中的方法。
iterator进行遍历的时候要有个hasNext()方法判断,防止指向空,出现NullPointerException异常。
3、collection常用的子接口
List(允许保存重复数据) Set(不允许保存重复数据) SortedSet(不允许重复且排序保存) Queue(队列)。
[注意]iterator获取的迭代器对象,遍历时,应该使用hasNext()判断下一个位置是否有值,否则很有可能出现NoSuchElementException异常。
在迭代循环的时候移除元素应该用迭代器自带的remove()方法,否则很容易出现ConcurrentModificationException并发修改问题。
List接口
1、继承关系
2、常见方法
public void add(int index,E element):在指定索引位置添加元素。
public E get(int index):根据索引获取保存的数据
public int indexOf(Object o):获取指定数据的索引位置。
public ListIterator<E> listIterator():获取ListIterator接口实例。
public E set(int index,E element):修改指定索引位置的数据。
public default void sort(Comparator<?super E> c):使用特定比较器实现排序操作。
public List<E> subList(int fromIndex,int toIndex):截取子集合。
public static <E> List<E> of(E... elements):通过给定元素创建List集合。
[注意]List.of()创建的对象不能再通过,List.add()去添加否则出现异常,List.of()创建的对象是固定的数值,也可以通过toArray()转化为对象数组返回Object型。
- ArrayList实现类
1、ArrayList可以加入null,并且可以多个。
2、ArrayList是由数组实现数据存储的。
3、ArrayList基本等同于Vector,除了ArrayList是线程不安全,在多线程的情况下,不建议使用ArrayList。
4、底层结构:
a、存储数据底层结构使用的对象数组 transient Object[] elementData;
b、ArrayList()扩容机制
无参构造实例化ArrayList对象,初始化对象数组长度为0,经过grow()方法扩容,默认对象数组长度为10,超过长度10,Object对象数组长度扩容原来的1.5倍变成15,超过15扩容成22等。
有参构造实例化ArrayList对象,初始化为赋值长度,超过赋值长度就进行初始化长度1.5倍扩容。
5、对ArrayList遍历
迭代器
增强for
普通for
6、删除和查询自定义类:必须要重写equals()方法,否则无法使用remove()方法删除自定义类。
- LinkedList实现类
1、LinkedList底层实现了双向链表和双端队列的特点。
2、可以添加任意元素,元素可以重复,包括null。
3、没有实现线程同步。
4、底层分析
a、LinkedList底层使用的双向链表结构,维护了Node First Next Last两个节点,分别执行首尾节点。
b、每一个Node中又有Node pre,Node next分别指向上一个节点和下一个节点。
5、分析LinkedList和ArrayList特点
a、如果需要频繁的进行添加和删除,建议使用LinkedList,底层使用的是双向链表,添加元素不需要考虑扩容,添加和删除元素效率更高。
b、如果需要频繁进行查询操作,建议使用ArrayList,ArrayList底层是数组,数组有下标,查询效率更高。
c、一般项目中大多数查询更多,所以选择ArrayList比较多点。
6、为什么ArrayList构造器有初始值而LinkedList构造器没有初始值?
主要是因为ArrayList底层是数组,数组有长度限制,而LinkedList底层是链表,链表没有长度。
- Vector实现类
1、特点
Vector底层也是一个对象数组,protected Object[] elementData;
Vector是线程同步的,即线程安全,Vector操作带有synchronized修饰,在开发中,需要线程同步安全时,考虑使用Vector。
2、Vector和ArrayList区别?
Vector和ArrayList底层都是对象数组,扩容机制使用方法都差不多,唯一不同的是Vector操作可以实现线程安全但是效率低,而ArrayList操作效率高线程不安全。
Set接口
无序不可重复的存取数据,最多只能包含一个null,元素没有顺序,有个实现类HashSet,底层就是HashMap实现的(看了HashMap后再回来看HashSet的实现),因此,查询效率高。由于采用HashCode算法直接确定元素得到内存地址,增删效率也高。
- HashSet底层结构以及扩容机制(重点)
1、hashSet中添加元素,调用add()方法,通过调用对象的hashcode()计算hash值,从而确定元素在table表中位置,如果位置没有元素,直接添加,如果表中对应的位置已经有元素,调用equals()比较值是否相等,如果equals()不相等,则挂载到链表后,如果equals()相等,则不添加,生成hash值源码:(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)。(去重原理)
2、hash值是一种以hashcode为属性进行一定算法处理的数值(hash值不等同地址值),基本数据类型值相等,hash值相等,引用数据类型hash值不同,重写hashcode()方法比较hash值。
3、HashMap底层:数组+单向链表+红黑树 (jdk8.0以后)
创建HashMap对象,第一次扩容的table表长度为16,设定临界值为16*0.75=12,当添加数值长度大于临界值就会再次扩容,扩容到原来的两倍(16*2=32),临界值就为16*2*0.75=24以此类推。
当链表的个数大于8,并且table表长度大于64的时候,进行树化(Node---->treeNode红黑树),(jdk17当单向链表个数大于等于11,table长度大于64,进行树化。同一位置链表节点个数大于8时,table表长度扩容到16*2=32,节点个数大于9时,table表长度扩容到32*2=64,同一位置链表节点个数达到11个进行树化(treeNode))。
- 测试代码
public class HashSetDemo {
public static void main(String[] args) {
Employee_D ed=new Employee_D("zs",25);
Employee_D ed1=new Employee_D("zs",25);
Employee_D ed2=new Employee_D("ls",26);
Set<Employee_D> set=new HashSet<>();
set.add(ed);
set.add(ed1);
set.add(ed2);
Iterator<Employee_D> iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next().toString());
}
}
}
class Employee_D{
private String name;
private int age;
public Employee_D(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;
}
//重写equals和hashcode方法在set集合中去重复自定义对象,equals和hashcode方法重写方式相同。
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee_D that = (Employee_D) o;
return age == that.age && this.name.equals(that.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "[雇员信息]名字:"+this.name+"\t年龄:"+this.age;
}
}
- TreeSet
1、底层是二叉树数据结构,可以自动对基本数据类型进行排序,同时去重复数值。
2、不能存放Null值,会报NullPointerException异常。
3、不能直接对引用数据类型自动排序,必须实现Comparable重写comparaTo(Object o)方法或者传入Comparator接口的匿名内部类,否则会出现ClassCastException异常,尽量对所有属性进行比较,否则会出现比较不准确。
4、所有带Tree关键字的类都是排序的。
5、此类间接实现了SortedSet,所以拥有排序功能,基本数据类型按照顺序存储。
- linkedHashSet
此类是HashSet的子类,有序不存重复数据,可以有序存储和读取数据。
ListIterator接口
1、此接口是Iterator的子接口,Iterator并没有提供此接口的方法,是通过List中的方法引出此接口的方法,可以实现从前往后迭代输出,也可以从后往前迭代输出,但是由于考虑是指针输出,所以先从前往后迭代输出,再从后往前迭代输出。
2、常用方法:add(E e):向集合中增加数据;hasPrevious():判断是否有前一个元素;previous():获取前一个元素;set(E e):修改当前元素数据。
Enumeration枚举输出
只有Vector对象才可以获取Enumration对象,通过elemens()静态方法实现类的调用,此对象实现枚举输出与迭代器输出方式一致。
常用方法:hasMoreElements():判断是否有下一个元素;NextElement():获取当前元素;
Map接口
- HashMap实现类
1、Map与collection并列存在,用于保存具有映射关系的数据:key-Value
2、Map中的Key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中。
3、Map中的Key不允许重复,原因和HashSet一样,前面分析过源码。
4、Map中的value可以重复。
5、Map的key可以为null,value也可以为null,注意Key为null,只能有一个,value为null,可以有多个,并且存储数据是无序(所有的hash对象都是存储无序)。
6、常用String类作为Map的key。
7、Key和value之间存在单向一对一关系,即通过指定的key总能找到value。
8、Map中of()重复key会报异常,不能进行增删改操作,只能查询。
- hashMap底层实现原理
1、hashMap有三个构造器:public hashMap();public HashMap(int initialCapacity, float loadFactor);public HashMap(int initialCapacity),可以实现指定初始化大小和阈值加载因子数,默认初始化大小为16,阈值加载因子为0.75,最大的初始化大小为Integer最大值。
2、通过初始化大小16,如果添加的元素值超过16*0.75=12,初始化大小将扩容到32(扩容一倍),jdk8以前hashMap底层数组+链表,链表长度超过8树化,jdk8以后通过位运算左移1<<3(存储元素个数大于8),并且数组长度达到64,原始的链表结构转为红黑树结构,目的是平衡二叉树的时间复杂度。
- 常用方法
get(Object key):通过key获取value。
containsKey(Object key):判断是否包含Key键。
containsValue(Object value):判断是否存在这个值
size():获取HashMap大小。
KeySet():获取所有的Key值。
remove(Object key):删除指定Key所在的值。
entrySet():获取所有Map中的key和value键值对
replace(Object key,Object oldValue,Object newValue):替换原值。
clear():清空Map集合
put(K key,V value):向集合中添加数据。
//【注意】put()方法添加新值时,由于之前没有值,返回null,如果根据key对value进行覆盖时,返回覆盖前的值。
- 遍历方式
//第一种遍历方式
Set k= maps.keySet();
Iterator iterator1 = k.iterator();
while (iterator1.hasNext()){
System.out.println(iterator1.next());
}
//第二种遍历
Set<Map.Entry<String, Integer>> entries = maps.entrySet();
Iterator<Map.Entry<String, Integer>> iterator2 = entries.iterator();
while (iterator2.hasNext()){
//获取value值
System.out.println(iterator2.next().getValue());
//获取key值
System.out.println(iterator2.next().getKey());
}
- HashTable实现类
HashTable与HashMap基本差不多,不同的是HashTable中的key和value都不能存放null,初始化大小是11,否则会出现NullpointerException,并且HashTable线程安全,有同步锁(Synchronized),效率低,HashMap线程不安全效率高,没有同步锁,初始化大小是16。
- Properties子类
Properties类是 Hashtable类的子类,用于加载配置文件,在框架和项目开发中大量使用。
- 常用方法
setProperty(String key,String value):设置属性内容。
getProperty(String key,String defaultValue):根据key获取属性,不存在返回默认值。
store(OutputStream out,String comments):将属性内容通过字节输出流输出。
store(Writer writer,String comments):将属性内容通过字符输出流输出
load(InputStream in):通过字节输入流读取。
load(Reader reader):通过字符输入流读取。
31、泛型
1、java泛型是JDK1.5中引入的一个新特性,其本质是参数化类型,也就是说所操作的数据类型被指定为一个参数(type parameter)这种参数类型可以用在类、接口、方法和属性的创建中,分别称为泛型类、泛型接口、泛型方法。
2、为什么使用泛型?
避免重复类型转换,便于编译器检查类型是否正确,可以避免运行时产生ClassCastException异常。
[注意]泛型中,只能使用引用类型。
3、T、E、K、V只是一个符号,一个占位符而已,不存在任何意义,我们习惯使用一个大写的字母T,E,K,V.
4、泛型的作用是:可以在类声明通过一个标识表示类中某个属性的类型,或者是某个方法的返回的类型,或者是参数类型。
5、在static方法中不可以使用泛型,泛型变量也不可以用static关键字来修饰;如果一个静态方法需要使用泛型的话,该方法需要定义成泛型方法。
6、使用泛型的数组,不能初始化。 应该这样定义:T[] arr; arr=new Integer[];
泛型类的类型,是在创建对象时确定的(因为创建对象时,需要执行确定类型)。
如果在创建对象时,没有指定类型,默认为Object。
父类的泛型可以传递给子类。
- 泛型通配符<?>
1、作用:本质上从同一个泛型类生出来的类实例化的对象,类型都是一样泛型类,同一个泛型类衍生出来的多个类之间没有任何关系,需要对每个泛型实例进行分别处理。
2、<? extends List>:泛型传入类型只能是List类型及其List的子类,泛型上边界。
3、<? super List>:泛型传入类型只能是List类型及其List的父类,泛型下边界。
- 类型擦除
泛型目的是让编译器在编译的时候检查类型是否正确,防止在运行时出现异常转换异常,运行的时候jvm统一按照Object类型进行处理,同一个对象不同的泛型,他们的类加载地址是一样的。
32、IO流
程序中的流指的是数据的处理方向,在应用程序中需要和外部数据源进行数据交互,需要用到IO流,java编程之中实现输入输出提供了两种不同的操作流:
1、OutputStream:字节输出流,InputStream:字节输入流(8位)
2、Writer:字符输出流,Reader:字符输入流 (16位)
3、类使用模板:
通过File类定义一个要操作文件的路径(此操作是在进行文件输入输出的时候所需要采用的方式)。
通过字节流或字符流的子类为父类对象实例化。
实现数据的读、写操作。
流属于宝贵的资源,操作完毕后一定要进行关闭(close())。
4、数据源:data source提供原始数据的原始媒介。常见的有数据库、文件、其他程序、内存、网络、IO设备、其他程序。数据源就像水箱,流就像水管中流着的水流,程序就是我们最终的用户。流是一个抽象、动态的概念,是一连串连续动态的数据集合。
5、字节流和字符流的区别?
字节流不会经过内存缓冲区进行数据的暂存,而是直接与目标介质进行输出控制,而所有的字符流都要需要经过内存处理才能实现读写,所以字符流要调用close()方法进行缓存区刷新,或者调用flush()方法强制刷新才能成功实现读写操作。
6、由于字节流字符流父接口实现了Closeable接口,因此可以把其实现类放入try()中,会自动关闭流,不用调用close()方法。
- 功能分类
节点流(低级流):直接和外部数据源进行关联。
处理流(包装流):在节点流的基础上,提示了操作性能,通过对其他流的处理提高程序的性能。
节点流有哪些?
文件节点流:FileInputStream FileOutputStream FileReader FileWriter
数组节点流:ArrayDataInputStream ArrayDataOutputStream
管道节点流:pipedOutputStream pipedInputStream
处理流有哪些?
缓冲流:BufferedInputStream BufferedOutputStream(没有多态)
数组流:ByteArrayOutputStream ByteArrayInputStream
打印流:PrintWriter printStream
数据流:DataInputStream DataOutputStream
使用缓冲流来实现文件的拷贝,缓冲流在节点流的基础上进一步提升读写性能。
- 乱码分析与解决
乱码问题原因:默认编码方式utf-8,读取文件的编码为ANSI(GBK)编码和解码不匹配,就出现乱码问题。
操作的文件类型为文本文件,就有可能出现中文乱码问题,为了读取过程中,避免出现中文乱码问题,就需要使用转换流。
InputStreamReader:字节流---->字符流
InputStreamReader(InputStream in,Charset cs):通过指定的字符集读取文件。
OutputStreamWriter(OutPutStream out):通过指定的字符输出流写入文件。
- 转换流
目的是处理中文字符,防止读取中文字符时乱码,可以通过转换流指定字符编码,默认字符编码是GBK,一般指定为UTF-8编码。
InputStreamReader(InputStream in,String charSet):转换输入流 OutputStreamWriter(OutputStream out,String charSet):转换输出流
父类是转换流,字符编码默认是UTF-8.
- 对象流
以前我们学过的流都只能读写字节,字符形式的数据,而java中非常重要并且常见的对象类型的数据,如果想要存储到文件中应该怎么操作呢?这个时候就使用到了对象流。
序列化:是一个用于将对象状态转换为字节流的过程,可以将其保存到磁盘文件中或通过网络发送到任何其他程序。
反序列化:从字节流创建对象的相反的过程称为反序列化。
用对象流写入对象时,javaBean要是实现Serializable接口,否则会出现NotSerializableException异常。
写入顺序和读取顺序要一致,否则会出现EOFException异常!
transient和static修饰的属性不能序列化,如果使用反序列化读取数据时返回默认值。
- 测试代码
// C:\Users\Administrator\Desktop 序列化测试代码
Annimal annimal = new Annimal("旺财", "dog", 5);
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Administrator\\Desktop\\data.dat"));
oos.writeObject(annimal);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
class Annimal implements Serializable {
private String name;
transient private String type;//此属性不需要序列化 读取时返回默认值null
private int age;
public Annimal(String name, String type, int age) {
this.name = name;
this.type = type;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "[动物信息]名字:"+this.name+"\t类型:"+this.type+"\t年龄:"+this.age;
}
}
---------------------------------反序列化代码如下----------------------
//文件反序列化 把文件中的对象读取出来
ObjectInputStream inputStream = null;
try {
inputStream = new ObjectInputStream(new BufferedInputStream(new FileInputStream("C:\\Users\\Administrator\\Desktop\\data.dat")));
//反序列化读取的顺序要与写入的顺序一致
Annimal a = (Annimal) inputStream.readObject();
String s = a.toString();
System.out.println(s);
char word=inputStream.readChar();
System.out.println(word);
String s1 = inputStream.readUTF();
System.out.println(s1);
int i1 = inputStream.readInt();
System.out.println(i1);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
- serialVersionUid
序列化时,实体类中未定义serialVersionUID号,jvm会自动生成一个SerialVersionUID号,只有同一次编译成class文件时serialVersionUID才会相同,反序列化读取数据时,会匹配SerialVersionUID号是否一致,一致时才能进行反序列化读取数据,如果不一致出现版本不一致的异常。因此我们要在实体类中自定义serialVersionUID号,防止反序列化失败。
定义方式:private static final long serialVersionUID=100212012L;
- 字节数组流(内存流)
a、没有异常,不需要关闭资源,从始至终都是跟内存打交道。读写效率高。
b、ByteArrayInputStream 包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪read 方法要提供的下一个字节。
c、说白了,FileInputStream是把文件当做数据源。ByteArrayInputStream则是把内存中的某个数组当做数据源。
ByteArrayOutputStream类实现了一个输出流,其中的数据被写入一个 byte 数组。缓冲区会随着数据的不断写入而自动增长。可使用 toByteArray() 和 toString() 获取数据。 ByteArrayOutputStream将一个输出流指向一个Byte数组,但这个Byte数组是ByteArrayOutputStream内部内置的,不需要我们来定义。
d、由于经过内存缓存区,所以内存流即使不用关闭,也要调用flush()方法进行缓冲区刷新,否则无法写入。
- 字节数组流测试代码
OutputStream out = null;//写文件
ByteArrayInputStream in = null;
try {
out = new FileOutputStream("src\\picture\\ab1.txt");
String str = "测试内存流!!@@@@@@@@@@@@@@@";
byte[] bytes = str.getBytes();
in = new ByteArrayInputStream(bytes);
// out.writeTo();
int len = 0;
while ((len = (in.read(bytes))) != -1) {
out.write(bytes, 0, len);
}
//写入指定文件中 用内存流写入不会保留在文本文件中 会立即消失 只是暂时
// out.writeTo(new BufferedOutputStream(new FileOutputStream("src\\picture\\ab1.txt")));
System.out.println("程序执行完毕!!");
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
//字节数组流因为是直接从内存中读取缓存字节数组所以可以不用关闭流
if (out != null) {
try {
out.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
- 打印流
获取从控制台输出的流,printStream printWriter。
PrintStream stream = null;
try {
//打印流
stream = new PrintStream(new BufferedOutputStream(new FileOutputStream(new File("C:\\Users\\Administrator\\Desktop\\ab.txt"))));
stream.println("你好!!!!啊");
stream.append("aaasss");
stream.append("aaasss1121213");
stream.append("aaasss1121213");
System.out.println("已写入!!");//PrintStream ps=System.out;
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (stream != null) {
stream.close();
}
}
33、JDk8新特性
- lambda表达式
lambda表达式必须函数式接口支持,所谓的函数式接口就是接口中只有一个抽象方法,最终目的是实现接口中的唯一的抽象方法,前面的接口规定了类型(后面的对象只能是该接口的实现类对象);接口中只有一个抽象方法(所有的功能都是对该方法的重写,即方法名等已经固定了),最终提升代码表达能力。
- 基本语法如下
(形参列表)->{操作集合};
- 测试代码
//无参无返回值,只有一行代码时,可以省略{}
A1 a1=()-> System.out.println("lambda表示式……");
//调用方法
a1.test();
//有参无返回值
A2 a2=(num,num1)->{
System.out.println("两数之和"+(int)(num+num1));
};
a2.test1(10,20);
//有参有返回值
// A3 a3=(num, num1) ->{
// return num+num1;
// } ;
//一行代码情况下 可以省略{}并且可以省略return;完整代码下必须写reture
A3 a3=(num,num1)->num+num1;
a3.test2(10,20);
}
}
//函数式接口
interface A1{
void test();
}
interface A2{
void test1(int num,int num1);
}
interface A3{
int test2(int num,int num1);
}
- 四大内置型函数接口
消费型接口Consumer<T> 一个形参无返回值
提供方法:void accept(T t);
供给型接口 Supplier<T> 无形参有返回值
提供方法:T get();
函数型接口 Function<T,R> 一个形参一个返回值
提供方法:R apply(T t);
断定型接口 Predicate<T> 一个形参布尔返回值
提供方法:boolean test(T t);
BiFunction<T,R,E> 两个参数,一个返回值
提供方法:E apply(T t,R r);
- 消费型接口测试代码
//消费型接口
List<Integer> list = new ArrayList<>();
list.add(1121);
list.add(1125);
list.add(589745);
list.add(12578);
// list.forEach(System.out::println);
//PrintStream ps=System.out;
list.forEach(t-> System.out.println(t));
- 供给型接口测试代码
List<Integer> list = getArray(10, () -> (int) (Math.random() * 100 + 1));
list.forEach(t -> {
System.out.println(t);
});
//供给性方法
public static List<Integer> getArray(int num, Supplier<Integer> sup) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < num; i++) {
list.add(sup.get());
}
return list;
}
- 函数型接口测试代码
public static String getStr(String str,Function<String,String> fun){
reture fun.apply(str);
}
String str=getStr(" sddfggQwer ",(str)->{
return str.trim();
});
System.out.println(str);
- 断言型接口测试方法
public static StringBuilder getBuilder(String[] strs,Predicate<String> pre){
StringBuilder builder=new StringBuilder();
for(int i=0;i<strs.length;i++){
if(pre.test(strs[i])){
builder.append(strs[i]);
}
}
return builder;
}
String[] strs=new String[]{"asdfr","asd","asdfrt","asert","dfrtg","sderd","asder"};
StringBuilder sb=getBuilder(strs,(str)->str.length()>3);
System.out.println(sb);
- 方法引用
Lambda表达式的一种表示形式
语法形式:
实例化对象名::实例方法名(普通方法)
类名::静态方法名(静态方法)
类名::实例方法名(比如系统自带String类调用boolean equals(Object anthorObject));
[注意]引用的方法名必须与返回接口类型中的抽象方法中形参和返回值一致,使用方法引用格式时要省略"(参数)->"。
- 普通方法测试代码
Work w=new Work("程序员");
Supplier<String> sup=w::toString; //Supplier接口中get()方法与toString()方法参数、返回类型一致
String str=sup.get();
System.out.println(str);
class Work{
private String name;
public Work(String name){
this.name=name;
}
public work(){}
public void setName(String name){
this.name=name;
}
public String get(){
return this.name;
}
public String toString(){
return "工作名:"+this.name;
}
}
- 静态方法测试代码
Supplier<String> sup=Work::getName;
String name=sup.get();
System.out.println(name);
class Work{
private static String name;
public Work(String name){
this.name=name;
}
public work(){}
public void setName(String name){
this.name=name;
}
public static String getName(){
return this.name;
}
public String toString(){
return "工作名:"+this.name;
}
}
- 类名实例方法测试代码
BiFunction<String,String,Boolean> ba=String::equals;//注意泛型只能用引用数据类型不能用基本数据类型 由于equals可以用String匿名对象直接调用。
boolean flage=ba.apply("aaa","aaa");
System.out.println(flage);
- 构造器引用(测试代码)
//构造器引用
//Function只能传入一个参数 调用一个参数的构造器
Function<String,Work> e=(name)->{return new Work(name)};
Function<String,Work> e=Work::new;
Work w1 = e.apply("电子厂工人");
System.out.println(w1.toString());
//调用多个参数的构造器
BiFunction<String,String,Work> bi=(name,type)->new Work(name,type);
BiFunction<String,String,Work> bi=Work::new;
Work apply = bi.apply("服务员", "服务业");
System.out.println(apply.toString());
//调用无参的构造器
Supplier<Work> su=Work::new;
Work w2=su.get();
System.out.println(w2.toString());
class Work{
private String workName;
private static String type;
public Work(String workName, String type) {
this.workName = workName;
this.type = type;
}
public Work(String workName) {
this.workName = workName;
this.type = "默认类型";
}
public Work(){}
@Override
public String toString() {
return "工作名称:"+this.workName+"\t\t\t工作类型:"+this.type;
}
}
- Stream流
1、什么是流?
这个接口是JDk1.8开始引入的,主要目的是通过函数式编程的结构实现集合数据的分析。
2、Stream(流)是一个来自数据源的元素队列并支持聚合操作。
3、Java中的Stream并不会存储元素,而是按需计算。数据源:流的来源, 可以是集合,数组。
4、Stream操作步骤:
一、创建Stream源
一个数据源(如:数组、集合、自定义类),获取一个流。
二、中间操作
一个中间操作链,对数据源的数据进行处理。
三、终止操作
一个终止操作,执行中间操作链,并产生结果。
5、Stream特点:
a、stream不存储数据,而是按照待定的规则对数据进行计算,一般会输出结果。
b、stream不会改变数据源,通常情况下会产生一个新的集合和一个值。
c、stream具有延迟执行特性,只会调用终端操作,中间操作才会执行。
- 获取Stream的三种常用方式
List<String> list=new ArrayList<>();
Collections.addAll(list,"hello","hi","java","go","javaScript","c","c++");
//通过集合获取流
Stream<String> stream = list.stream();
//查询含有j的小写字母
List list2 = stream.filter((ele) -> ele.toLowerCase().contains("j")).collect(Collectors.toList());
System.out.println(list2);
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//通过数组获取Stream
String[] strs=new String[]{"hello","hi","java","go","javaScript","c","c++"};
Stream<String> stream1= Arrays.stream(strs);
long count = stream1.count();
System.out.println(count); //获取数组长度
++++++++++++++++++++++++++++++++++++++++++++++++=+++++=++=+=++++
//自定义类,集合存储对象 来获取Stream流来对对象集合进行处理。
List<Emp> generate = Emp.generate();
//薪资大于2000员工的对象收集在一个新的List集合
List<Emp> collect = generate.stream().filter(sala -> sala.getSal() > 2000).collect(Collectors.toList());
collect.forEach(System.out::println);
System.out.println("******************************");
//获取名字中含有个s的员工信息
List<Emp> s = generate.stream().filter(o -> o.getEname().toUpperCase().contains("S")).collect(Collectors.toList());
s.forEach(System.out::println);
System.out.println("*******************************");
List<Emp> collect1 = generate.stream().sorted(((o1, o2) -> (int) (o2.getSal() - o1.getSal()))).findFirst().stream().collect(Collectors.toList());
collect1.forEach(System.out::println);
System.out.println("*******************************************************************");
List<Emp> emps = generate.stream().max(((o1, o2) -> o1.getSal().compareTo(o2.getSal()))).stream().collect(Collectors.toList());
System.out.println("工资最高的员工!");
emps.forEach(System.out::println);
System.out.println("薪资高于2000的人数:");
long count = generate.stream().filter((e) -> e.getSal() > 2000).count();
System.out.println(count);
System.out.println("**********************操作字母*****************************************");
String[] strs = new String[]{"hello", "word", "program", "java"};
Stream<String> stream = Arrays.stream(strs);
stream.map((s1) -> s1.toUpperCase()).collect(Collectors.toList());
//自定义实体类
public class Emp implements Serializable ,Comparable{
private Integer empno;
private String ename;
private String job;
private Integer mgr;
private Double sal;
private Double comm;
private Integer deptno;
public Emp() {
}
public Emp(Integer empno, String ename, String job, Integer mgr, Double sal, Double comm, Integer deptno) {
this.empno = empno;
this.ename = ename;
this.job = job;
this.mgr = mgr;
this.sal = sal;
this.comm = comm;
this.deptno = deptno;
}
public static List<Emp> generate() {
List<Emp> empList = new ArrayList<>();
empList.add(new Emp(7369, "SMITH", "CLERK", 7902, 800.00, null, 20));
empList.add(new Emp(7499, "ALLEN", "SALESMAN", 7698, 1600.00, 300.00, 30));
empList.add(new Emp(7521, "WARD", "SALESMAN", 7698, 1250.00, 500.00, 30));
empList.add(new Emp(7566, "JONES", "MANAGER", 7839, 2975.00, null, 20));
empList.add(new Emp(7654, "MARTIN", "SALESMAN", 7698, 1250.00, 1400.00, 30));
empList.add(new Emp(7698, "BLAKE", "MANAGER", 7839, 2850.00, null, 30));
empList.add(new Emp(7782, "CLARK", "MANAGER", 7839, 2450.00, null, 10));
empList.add(new Emp(7788, "SCOTT", "ANALYST", 7566, 3000.00, null, 20));
empList.add(new Emp(7839, "KING", "PRESIDENT", null, 5000.00, null, 10));
empList.add(new Emp(7844, "TURNER", "SALESMAN", 7698, 1500.00, 0.00, 30));
empList.add(new Emp(7876, "ADAMS", "CLERK", 7788, 1100.00, null, 20));
empList.add(new Emp(7900, "JAMES", "CLERK", 7698, 950.00, null, 30));
empList.add(new Emp(7902, "FORD", "ANALYST", 7566, 3000.00, null, 20));
empList.add(new Emp(7934, "MILLER", "CLERK", 7782, 1300.00, null, 10));
return empList;
}
public Integer getEmpno() {
return empno;
}
public void setEmpno(Integer empno) {
this.empno = empno;
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
public Integer getMgr() {
return mgr;
}
public void setMgr(Integer mgr) {
this.mgr = mgr;
}
public Double getSal() {
return sal;
}
public void setSal(Double sal) {
this.sal = sal;
}
public Double getComm() {
return comm;
}
public void setComm(Double comm) {
this.comm = comm;
}
public Integer getDeptno() {
return deptno;
}
public void setDeptno(Integer deptno) {
this.deptno = deptno;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Emp emp = (Emp) o;
return Objects.equals(empno, emp.empno) &&
Objects.equals(ename, emp.ename) &&
Objects.equals(job, emp.job) &&
Objects.equals(mgr, emp.mgr) &&
Objects.equals(sal, emp.sal) &&
Objects.equals(comm, emp.comm) &&
Objects.equals(deptno, emp.deptno);
}
@Override
public int hashCode() {
return Objects.hash(empno, ename, job, mgr, sal, comm, deptno);
}
@Override
public String toString() {
return "Emp{" +
"empno=" + empno +
", ename='" + ename + '\'' +
", job='" + job + '\'' +
", mgr=" + mgr +
", sal=" + sal +
", comm=" + comm +
", deptno=" + deptno +
'}';
}
@Override
public int compareTo(Object o) {
return 0;
}
}
34、多线程
1、进程:进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间,当我们使用迅雷,又启动一个进程,操作系统将为迅雷分配新的内存空间。
进程是程序的一次执行过程,或者是正在运行的一个程序,是动态的产生,存在和消亡的过程。
2、线程:线程是由进程创建的,是进程的一个实体;一个进程可以拥有多个线程。
3、单线程:同一个时刻,只允许执行一个线程。
4、多线程:同一个时刻,可以执行多个线程,比如:一个QQ进程,可以同时打开多个聊天窗口
5、并发:多个线程同时交替执行,看似同一个时刻,单核cpu多任务执行。
6、并行:同一时刻,多个任务同时执行,多核cpu可以同时实行并行,也可以同时进行并发。
7、start()是native修饰,native是JNI(java Native Interface)技术提供的,主要用在安卓开发,若重复调用start()会出现异常。
8、将耗时操作代码交给子线程。
- 线程基本创建
//类继承Thread这种方式会造成单继承的局限性,所以推荐采用实现接口编程。 创建一个线程运行时会产生两个线程一个main方法线程一个子线程,交错执行,由操作系统底层内核决定的。
public class TestThread extends Thread{
public void run(){
}
public static void main(String[] args){
TestThread test= new TestThread();
test.start(); //启动线程
//匿名实现多线
new Thread(){
public void run(){
}
}.start();
}
}
//实现Runnable接口创建线程
Public class TestRunnable implements Runnable{
public void run(){
}
public static void main(String[] args){
Thread t=new Thread(new TestRunnable());
t.start();
}
}
//实现Callable<T> 接口创建线程 JUC提供的方法 实行异步返回call方法中的值。
public class TestCallale implements Callable<String>{
public String call(){
return "hello";
}
public static void main(String[] args){
TestCallable callable=new TestCallable();
//异步任务类。
FutureTask<String> task=new FutureTask(callable);
Thread t=new Thread(task);
t.start();
System.out.println(task.get());//获取call()方法中的返回值
}
}
- Runnable和Thread创建线程的区别?
1、Thread与Runnable用法一样,但是Thread本身实现Runnable接口。
2、实现Runnable接口能更好创建多个线程共享同一个资源,推荐使用实现Runnable接口创建。
3、由于Thread本身实现Runnable接口,自定义类可以通过实现Runnable接口来重写run()方法,然后把自定义类传入Thread类中,调用Runnable中的抽象方法run()并启动线程,自定义类负责实现程序的具体执行,而Thread负责处理调用并启动线程,形成了一种代理模式。
- 线程终止
应该采用变更程序布尔值来结束程序运行最终实现线程终止,否则容易产生线程死锁,而不是用stop()强制停止线程,该方法已经过时!
- 测试代码
private boolean flage=true;//设置线程信息方向标
public static void main(String[] args) {
ThreadHomeWork1 tw=new ThreadHomeWork1();
Thread thread = new Thread(tw);
thread.start();
Scanner scanner=new Scanner(System.in);
String next = scanner.nextLine();
if("Q".equalsIgnoreCase(next)){
tw.setFlage(false);
}
}
public void setFlage(boolean flage) {
this.flage = flage;
}
@Override
public void run() {
System.out.println("打印100以内的整数:按Q键盘停止");
try {
while (flage){
Thread.sleep(1000);
for (int i=0;i<=10;i++){
System.out.println(i);
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
- 中断休眠状态下的线程
Sleep(long millis):设置休眠时间,开启休眠,单位是毫秒,若是调用interrupt()方法,会抛出InterruptedException异常,唤醒子线程到可运行状态!。
Thread.interrupted():获取线程是否被终止。
线程对象名.interrupt():进行线程终止操作。
若线程在wait()、join()、sleep()状态下被终止,会出现异常,然后清理终止状态再继续执行线程,若是真正的终止线程,需要在捕获异常是再一次调用interrupt()进行线程终止。
- 线程礼让和插队
join():线程调用该方法,该线程要插队优先执行,直到该线程全部执行完毕后,再释放线程,继续运行下一个线程,可以通过调用Interrupt()方法来终止线程,抛出InterruptedException异常(插队),另外线程处于等待(wait)。
yield():把到手的资源让给礼让给其他线程(礼让只在一瞬间执行),如定义子线程并调用此方法,main线程先执行,另外线程处于就绪(ready)!(礼让)
[注意]main方法线程不需要启动,它会自动启动,如果main方法线程调用start()方法启动,会出现IllegalThreadStateException异常。
private static int title=10;
@Override
public void run() {
//程序插队和礼让测试代码
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
System.out.println("[" + Thread.currentThread().getName() + "]" + "票贩子卖了" + --title + "张票。");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public static void main(String[] args) throws InterruptedException {
//主线程
ThreadYeildAndJoin join = new ThreadYeildAndJoin();
Thread thread = new Thread(join,"子线程");
//启动子线程
thread.start();
Thread mainThread = Thread.currentThread();
mainThread.setName("主线程");
//主线不需要启动,它会自动启动,如果主线程调用start()方法启动,会出现IllegalThreadStateException异常
// mainThread.start();
for (int i = 0; i < 100; i++) {
Thread.sleep(500);
System.out.println("[" + Thread.currentThread().getName() + "]" + "车站窗口卖了" + --title + "张票。");
if(i%2==0){
//子线程进行插队
thread.join();
//子线程礼让
//[主线程]车站窗口卖了9张票。[子线程]票贩子卖了8张票。[主线程]车站窗口卖了7张票。[主线程]车站窗口卖了6张票。[子线程]票贩子卖了5张票。[主线程]车站窗口卖了4张票。[主线程]车站窗口卖了3张票。[子线程]票贩子卖了2张票。 执行结果 主线程运行更多
thread.yield();
}
}
}
- 守护线程
守护线程为工作线程而存在,守护线程一般都是工作在后台的线程,主线程先执行,守护线程随后执行,当工作线程执行完毕以后,守护线程也就结束,守护线程要在线程启动之前设置。(例如:JVM中的垃圾回收机制,服务器心跳检测机制)
Thread.setDaemon(true):设置守护线程,谁调用谁就是守护线程,另外一个就是主线程
- 守护线程使用案例
//电脑搬运
public class Computer_Move {
public static void main(String[] args) {
Site site=new Site();//实例化运行守护线程!
new Thread(()->{
for (int i=0;i<50;i++){
site.make();
}
},"生产商").start();
new Thread(()->{
for (int i=0;i<50;i++){
site.move();
}
},"搬运商").start();
}
}
class Site{
private Computer computer=null;
private int count=0;
public Site() {
Thread daemonThread=new Thread(()->{
while (true) {
try {
Thread.sleep(200);
System.out.println("[守护线程]正在生产第" + count + "台电脑!");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
daemonThread.setDaemon(true);
daemonThread.start();
}
public synchronized void make() {
if (this.computer != null) {
try {
super.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
if (count % 2 == 0) {
this.computer = new Computer("笔记本电脑", "华硕", 4369);
} else {
this.computer = new Computer("台式电脑", "惠普", 5689);
}
count++;
//由于程序运行太快 要设置休眠 否则容易出现线程打印重复!
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在生产电脑……");
super.notify();
}
public synchronized void move() {
if(this.computer==null){
try {
super.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//由于程序运行太快 要设置休眠 否则容易出现线程打印重复!
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
String s = this.computer.toString();
System.out.println(Thread.currentThread().getName() + "正在搬运电脑:" + s);
this.computer = null;
super.notify();
}
}
//电脑实体类
class Computer{
private String typeName; //类型
private String brand; //品牌
private double price;//价格
public Computer(String typeName, String brand, double price) {
this.typeName = typeName;
this.brand = brand;
this.price = price;
}
@Override
public String toString() {
return "[电脑信息]电脑类型:"+this.typeName+"\t\t电脑品牌:"+this.brand+"\t\t电脑价格"+this.price;
}
}
- 线程的生命周期
new->runnable->blocked->waiting->timed_waiting->terminated
new:尚未启动的线程处于此状态。
Runnable:在java虚拟机中执行的线程处于此状态,细分为Ready(准备阶段,等待调度器调度)和Running(线程运行阶段)。
blocked:被阻塞等待监视器锁定的线程处于此状态。
waiting:正在等待另一个线程执行特定动作的线程处于此状态。
timed_waiting:正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
terminated:已退出的线程处于此状态。
[基本思路]
1、通过实例化Thread()创建新的线程,然后调用start(),并不是立刻进行多线程的执行,只是进入就绪状态,等待cpu调度,当线程分配了相关硬件资源,运行线程的核心业务,执行不是一直执行的,而是需要进行资源的抢占,运行一段时间让出当前资源,重新等待调度。
2、当一个线程让出当前资源后,那么该线程对象将进入一种阻塞状态(此时线程之中未完成的代码不执行了),其他的线程继续完成自己先前未完成的任务。除了自动系统控制之外,可以利用Thread类中的一些方法进行线程的阻塞操作,比如Thread.seleep();Thread.join())
3、当线程的方法全部执行完毕之后,该线程将释放掉所占用的全部资源,并且结束全部执行。
- 线程优先级
理论上优先级越高,有可能先执行,执行顺序也是不确定,最低优先级为1,最高优先级为10,主方法默认优先级是5!
setPriority(int priopri):为线程设置优先级。
getPriority():获取线程优先级。
- 线程同步锁
a、线程不同步导致线程执行异常,主要原因是多个线程共享同一个资源,多个线程抢占的速度特别快,导致资源更新来不及,最终程序执行结果出现错乱,加上同步锁可以解决这个问题,但是运行效率会降低。
b、通过同步代码块、或者同步方法来解决线程不安全问题。
c、多个线程更新同一资源的时候必须考虑到同步,而同步所带来的问题就是线程的死锁。
d、如果使用static修饰,默认锁对象是当前类.class。
e、同步方法如果没有使用static,默认锁对象就是this。
f、哪些情况会释放同步锁?
1、当前线程的同步方法,同步代码块正常执行结束。
2、当前线程在同步代码块,同步方法中遇到break,return。
3、当前线程在同步代码块,同步方法中出现了未处理的Error或者Exception,导致异常 结束。
4、当前线程在同步代码块,同步方法中执行了线程对象的wait()方法,当前线程暂停,并 释放锁。
e、yield()、sleep(),锁嵌套、死锁、无限循环情况下不会释放锁。
d、同步锁必须是对同一个对象上锁,否则会锁不住。
//同步代码块
synchronized(this){
}
//同步方法
public synchronized String getThread(){
}
- 生产者和消费者模型
1、生产者生产产品,消费者进行消费,若是生产者生产能力强,消费者消费能力弱,则生产者需要等待消费者消费完以后再生产,若是消费者消费能力强,生产者生产能力弱,则消费者等待生产者生产完再进行消费,就会出现一个线程等待和唤醒的过程!
2、如果只是简单的生产者和消费者,没有经过同步锁处理、线程等待和唤醒处理,就会出现过度消费、过度生产、以及获取生产的半成品。
3、常用方法:object.notify();唤醒单个线程。
object.notifyAll();唤醒全部线程。
object.wait();线程等待。
- 测试代码
public class ProducerDemo implements Runnable {
private Message message = null;
public ProducerDemo(Message message) {
this.message = message;
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
if (i % 2 == 0) {
this.message.set("生产一轮", "生产了第一轮包子!");
} else {
this.message.set("生产二轮", "生产了第二轮包子!");
}
System.out.println("[生产者]------>已生产"+(i+1)+"轮。");
}
}
public static void main(String[] args) {
Message ms = new Message();
//生产者
ProducerDemo producerDemo = new ProducerDemo(ms);
//消费者
ConsumerDemo consumerDemo=new ConsumerDemo(ms);
new Thread(producerDemo).start(); new Thread(consumerDemo).start();
}
}
//消费者
class ConsumerDemo implements Runnable {
private Message message=null;
public ConsumerDemo(Message message) {
this.message = message;
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
String s = this.message.get();
System.out.println("[消费者进行消费]--------->" + s);
}
//消费完以后 继续生产
}
}
//信息存储类
class Message{
private String title;//标题
private String content;//内容
private boolean flag = true;
//true时,生产者可以生产,false时,消费者可以消费。
//加上同步锁 防止线程不安全 出现数据混乱 生产数据
public synchronized void set(String title,String content){
if(!flag){
try {
super.wait(); //停止生产
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
if(flag) {
try {
this.title = title;
Thread.sleep(100);
this.content = content;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
this.flag=false; //生产结束
super.notify();//唤醒消费者
}
}
//加上同步锁 获取数据时 防止出现线程不安全 消费数据
public synchronized String get(){
if (flag){ //true 停止消费
try {
super.wait(); //停止消费
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
if(!flag) {
try {
Thread.sleep(100);
return "[信息]标题:" + this.title + "\t\t内容:" + this.content;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
this.flag=true;//消费者消费完毕
super.notify(); //唤醒生产者
}
}
return null;
}
}
- volatile关键字
1、此关键字只能作用在属性上,不具有同步的操作特点,但是它一般与同步代码块或同步方法搭配使用。
2、传统的程序变量进行修改时,通过主内存将某一个变量内容拷贝到线程内存之中,此时为一个变量副本,随后在线程内存里面针对变量的副本数据进行处理操作(赋值、修改),当所有的线程内存变量修改完成后则需要与主内存中的变量进行同步处理,所以线程内存与主内存之间的变量会存在有同步延迟,volatile关键字不再进行副本的定义,直接操作主内存的变量,提高了同步效率。
- ThreadLocal关键字
1、传统自定义类,在多个线程中实例化多个对象并对类的属性进行赋值,最终会导致先执行线程的对象被后来执行线程实例化对象所覆盖,最终不同的线程都调用了最后实例化的对象,没有实现每个线程调用各自实例化的对象。ThreadLocal系统类就是解决多个线程下类被覆盖的问题。
2、ThreadLocal以线程为key,对象为value进行存储,通过线程为key来获取特定的对象,避免了多个线程调用对象被覆盖的问题。
3、常用方法:
void set(T t):添加不同对象 T get():获取对象 void remove():移除对象
- 测试代码
//定义对象常量
public static final ThreadLocal<Message_> MESSAGE=new ThreadLocal<>();
private Message_ message_;
public static void main(String[] args) {
String[] values=new String[]{"JAVA","C","C++","Python","golang","C#","Rubble"};
//通过创建多个线程,存储不同的对象,以线程为key,对象为value
for (String v:values){
new Thread(()->{
MESSAGE.set(new Message_());
MESSAGE.get().setContent(v); //每个对象传入不同的对象属性值
System.out.println(Thread.currentThread().getName()+"打印值------->"+MESSAGE.get().getContent());
}).start();
}
}
}
class Message_{
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
35、反射
1、反射机制允许程序在执行期借助于ReflectionAPI取得任何类的内部信息(比如成员变量,构造器,成员方法等等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到。
2、加载完类之后,在堆中就产生了一个class类型的对象(一个类只有一个class对象),这个对象包括了类的完整结构信息。
3、1.优点:可以动态的创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架技 术失去底层支撑。
2.缺点:使用反射基本是解释执行,对执行速度有影响。
[注意]注意:类的字节码文件在加载期间,只会在堆内存中创建一次对象(一个字节码文件,只会产生一个类对象)
- 测试代码
//获取配置文件的
Properties properties=new Properties();
properties.load(new FileInputStream("resources/reflect.properties"));
//获取配置文件中的类名
String className = properties.getProperty("className");
String methods = properties.getProperty("methods");
//加载类
Class<?> aClass = Class.forName(className);
//类名实例化
Object o = aClass.getDeclaredConstructor().newInstance();
//调用方法 后面是方法参数
Method method=aClass.getDeclaredMethod(methods);
method.invoke(o);
- Class关键字
1、Class也是类,因此也继承Object类。
2、Class类对象不是new出来的,而是系统创建的。
3、对于某个类的class类对象,在内存中只有一份,因为类只加载一次。
4、每个类的实例都会记得自己是由哪个class实例所生成。
5、通过class对象可以完整地得到一个类的完整结构,通过一系列API。
6、class对象是存放在堆里。
7、类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括方法代码,变量名,方法名,访问权限等等)。
8、常用方法:
static Class forName(String name):返回指定类名name的class对象。
Object newInstance():调用缺省构造函数,返回该class对象的一个实例。
String getName():返回此class对象所表示的实体(类、接口、数组类、基本类型)名称
Class[] getInterfaces():获取当前Class对象的接口。
ClassLoader getClassLoader():返回该类的类加载器。
Class getSuperclass():返回表示此Class所表示的实体的超类的class。
Constructor[] getConstructors():返回一个包含某些Constructor对象的数组。
Field[] getDeclaredFields():返回Field对象的一个数组。
Method getMethod(String name,Class ...paramTypes)
- 获取Class对象的四个重点方式
//1、已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName
Class<?> clazz=Class.forName("全类名");
//2、类进行new实例化,通过实例名.getClass获取类对象
Cat c=new Cat();
Class<?> clazz=c.getClass();
//3、直接通过类名.class获取类对象
Class<?> clazz=Cat.class;
//4、通过类加载载入对象
ClassLoad c1=对象名.getClass().getClassLoad();
Class c4=c1.loadClass("类的全名");
- 类加载
1、反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载。
2、静态加载:编译时加载相关的类,如果没有则报错,依赖性太强了。
3、动态加载:运行时期加载需要的类,如果运行时不用该类,即使不存在该类,也不报错,降低了依赖。
- Class类中的常用方法
getName:获取全类名(包括包名)。
getSimpleName:获取简单类名。
getFields:获取所有public修饰的属性,包括本类以及父类的。
getDeclaredFields:获取本类中所有属性。
getMethods:获取所有public修饰的方法,包括本类以及父类的。
getDeclaredMethods:获取本类中所有方法。
getConstructors:获取所有public修饰的构造器。
getDeclaredConstructors:获取本类中所有构造器。
getPackage:以Package形式返回包的信息。
getSuperClass:以Class形式返回父类信息。
getInterfaces:以class[]形式返回接口信息。
getAnnotations:以Annotation[]形式返回注解信息。
- Field类
1、getModifiers:以int形式返回修饰符
[说明:默认修饰符是0,public是1,private是2,protected是4,static是8,final是16],public(1)+static(8)=9;
2、getType:以Class形式获取返回类型。
3、getName:返回属性名。
- Method类
1、getModifiers:以int形式返回修饰符
[说明:默认修饰符是0,public是1,private是2,protected是4,static是8,final是16],public(1)+static(8)=9;
2、getReturnType:以Class形式获取返回类型。
3、getName:返回方法名
4、getParameterTypes:以Class[]返回参数类型数组。
- Constructor类
1、getModifiers:以int形式返回修饰符。
2、getName:返回构造器名(全类名)。
3、getParameterTypes:以Class[]返回参数类型数组。
- 通过反射获取属性值
//1、根据属性名获取Field对象
Field f=Clazz对象.getDeclaredField(属性名);
//2、暴力破解
f.setAccessible(true);
//3、设置私有属性值和访问属性值
f.set(o,值)//o为实例化对象
f.get(o)//o为实例化对象
- 通过反射实例化对象
//1、调用public无参构造
类名.Class.getDeclaredConstructor().newInstance();//可以调用所有构造器
类名.Class.Constructor().newInstance();//只能调用public修饰的构造器
//2、调用有参构造器并传值
类名.Class.getDeclaredConstructor(参数数据类型.class,...).newInstance(值1,....);
//3、只能调用public修饰的类
类名.Class.getConstructor(参数数据类型.class,...).newInstance(值1,....);
36、网络编程
- 网络OSI七层模型
1、网络编程更多的是针对于“TCP/UDP”两种协议进行网络开发,实际上网络协议非常复杂,为了解决这个问题,针对各种网络协议进行了一种抽象性管理,而这种抽象性的程序管理逻辑就是socket编程,socket编程内部针对网络程序的引用模式也分为两种形式:
a、C/S结构:需要开发两套程序,一套是服务器端程序,另外一套是客户端程序,在进行维护的时候服务器端程序和客户端程序都需要进行更新,使用非公开的端口,程序的安全性比较高。
b、B/S结构:基于浏览器实现客户端应用,只需要开发一套服务器端的程序即可,如果需要维护只需要修改服务器端的程序代码即可完成,维护与开发的成本降低了。
2、网络编程属于C/S程序模型,WEB开发属于B/S程序模型,C/S程序模型也分两种开发模型:
TCP程序:使用三次握手和四次挥手的方式保证所有的数据可靠的进行传输。
UDP程序:发送数据报,而接收数据报的一方不一定可以接收到信息。
3、语义、语法、时序三要素通信协议。
- 网络开发类
1、java.net包中提供了Socket和ServerSocket两个类,进行通信。
2、服务器端数据输出(OutputStream)实际上就属于客户端数据的输入(InputStream),服务端网络通信是通过字节流进行传输,不能使用字符流传输。
- 测试代码
//模拟服务器端 并设置监听端口号9999
try {
ServerSocket socket=new ServerSocket(9999);
//监听客户端
Socket client = socket.accept();
//获取客户端输出流,并写入客户端
PrintStream stream=new PrintStream(client.getOutputStream());
stream.println("你好!客户端请求!!!!!");
stream.println("你好!客户端请求!!!!!");
stream.println("你好!客户端请求!!!!!");
stream.println("你好!客户端请求!!!!!");
stream.println("你好!客户端请求!!!!!");
client.shutdownOutput(); //写入完成后 进行流的关闭
if(stream!=null){
stream.close();
}
if(socket!=null){
socket.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
//模拟客户端
try {
Socket socket = new Socket("localhost", 9999);
//获取服务器传来的数据
InputStream inputStream = socket.getInputStream();
//把从服务器端获取的字节流转化为字符流
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream,"UTF-8"));
Stream<String> lines = reader.lines(); //转化为Stream
lines.forEach(System.out::println);
} catch (IOException e) {
throw new RuntimeException(e);
}
- Echo模型
客户端发送数据给服务器端,服务器端接收到此数据之后直接进行回应,并且这种回应可以持续进行,当客户端确定不再继续进行交互的时候则断开整个的服务器连接,属于单线程开发!
- 测试代码
//模拟服务端
Socket client = null;
try {
ServerSocket socket = new ServerSocket(8080);
//开启服务器监听
client = socket.accept();
//获取客户端输出流
OutputStream out = client.getOutputStream();//获取服务器端输出流,写入服务器端。
//读取服务器输入的值 再返回到服务器端
InputStream in = client.getInputStream();
//控制台输入流
Scanner scanner = new Scanner(in);
//打印输出流
PrintStream ps = new PrintStream(out);
while (scanner.hasNext()) {
String s = scanner.nextLine(); //获取客户端输入的值
if("q".equalsIgnoreCase(s)){
ps.println("程序正在退出,欢迎你下次光临!");
}
ps.println(s); //返回服务器端
}
client.shutdownOutput();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (client != null) {
try {
client.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
*******************************************************************
//模拟客户端
InputStream inputStream = null;
OutputStream out = null;
try {
Socket socket = new Socket("localhost", 8080);
//读取服务器返回的值
inputStream = socket.getInputStream();
BufferedReader rd = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
//键盘输入发送到服务器
Scanner scanner = new Scanner(System.in);
//获取服务器的输出流发送消息
out = socket.getOutputStream();
PrintStream ps = new PrintStream(out);//使用打印流
boolean flag = true;
while (flag) {
System.out.println("请输入要发送的消息!输入q程序结束:");
String s = scanner.nextLine();
ps.println(s);
if ("q".equalsIgnoreCase(s)) {
flag = false;
System.out.println(rd.readLine());
break; //停止循环
}
System.out.println("[服务器回应的消息]:" + rd.readLine());
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
BBIOIO }
}
- BIO模型
BIO(Blocking IO、阻塞IO处理)是最为传统的一种网络通讯模型的统一描述,这种通讯模型主要是为了解决服务器的并发处理问题,属于多线程开发,可以多个客户端同时连接。
- 测试代码
//其他不变 只在echo模型的 服务器端加上一个线程!
//模拟服务区端
ServerSocket socket = null;
try {
socket = new ServerSocket(6666);
//加上一个线程
while (true) {
//开启服务器监听
Socket client = socket.accept();
new Thread(new EchoHandle(client)).start();
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
class EchoHandle implements Runnable {
private Socket client;
public EchoHandle(Socket client) {
this.client = client;
}
@Override
public void run() {
try {
//获取客户端输出流
OutputStream out = client.getOutputStream();//获取服务器端输出流,写入服务器端。
//读取服务器输入的值 再返回到服务器端
InputStream in = client.getInputStream();
//控制台输入流
Scanner scanner = new Scanner(in);
//打印输出流
PrintStream ps = new PrintStream(out);
while (scanner.hasNext()) {
String s = scanner.nextLine(); //获取客户端输入的值
if ("q".equalsIgnoreCase(s)) {
ps.println("程序正在退出,欢迎你下次光临!");
}
ps.println(s); //返回服务器端
}
client.shutdownOutput(); //写入客服端后进行关闭连接
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
- URL(统一资源位置)
URL指统一资源位置,是一种特殊的URI,它除了标识一个资源,还会为资源提供一个特殊的网络位置,客户端可以通过它来获取URL对应的资源。
URL所表示的网络资源位置通常包括用于访问服务器的协议(如http、ftp等)、服务器的主机名或者IP地址、以及资源文件在该服务器上的路径。
URL的语法表示形式为:
protocol://userInfo@host:port/path?query#fragment
协议://用户信息@主机名:端口/路径?查询#锚点
IP地址唯一标识了Internet上的计算机,而URL则标识了这些计算机上的资源。
- IP地址
( IP) 是 Internet Protocol 的外语缩写, 网络之间互连的协议也就是为计算机网络相互连接进行通信
而设计的协议。 在因特网中,它是能使连接到网上的所有计算机网络实现相互通信的一套规则,规定了
计算机在因特网上进行通信时应当遵守的规则。任何厂家生产的计算机系统,只要遵守 IP 协议就可以
与因特网互连互通。
端口:区分数据流向的软件 0-65535 不要使用 1024 以下的端口 ,每一个协议拥有自己的端口,在同
一个协议下端口不能重复 FTP:21 HTTP:80
InetAddress:封装计算机的ip地址和DNS(没有端口信息!)
InetSocketAddress:包含IP和端口,常用于SOCKET通信。
- TCP和UDP协议
TCP:TCP(transfer control protocol) 打电话 面向连接、安全、可靠,效率低
UDP:UDP(UserDatagramProtocol ) 发送短信 非面向连接、不安全、数据可能丢失 、效率高。
UDP编程:使用这两个类:DatagramSocket设置端口号,DatagramPacke 套数据包。
- UDP编程测试代码
public class UDPDemo {
public static void main(String[] args) {
//模拟服务器端
try {
//创建数据包 工具
DatagramSocket socket=new DatagramSocket(7777);
//准备接收数据的容器空间
byte[] b=new byte[1024];
//数据报
DatagramPacket packet=new DatagramPacket(b,b.length);
socket.receive(packet); //阻塞式方法 接收数据包
String str=new String(packet.getData(),0,packet.getLength());
System.out.println(str);
socket.close();
} catch (SocketException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
//模拟UDP客户端
class UDPClient{
public static void main(String[] args) {
//创建要发送数据报的工具 客户端的端口9000
try {
DatagramSocket ds=new DatagramSocket(9000);
byte[] b="hello word".getBytes();
//告诉数据包要发送到哪里
DatagramPacket dp=new DatagramPacket(b,b.length, InetAddress.getLocalHost(),7777);
ds.send(dp); //发送数据包
ds.close(); //关闭流
} catch (SocketException e) {
throw new RuntimeException(e);
} catch (UnknownHostException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
37、注解
- 基本介绍:
1、jdk1.5引入注解,目前已被广泛用于各种java框架,如Hibernate、spring。注解相当于是一种嵌入在程序中的元数据,可以使用注解解析工具或者编译器对其进行解析,也可以指定注解编译器或运行期有效。
Annotation就是java提供了一种元程序中的元素关联任何信息和任何元数据的途径和方法。annotation是一个接口,程序可以通过反射来获取指定程序元素的annotation对象,然后通过annotation对象来获取注解里面的元数据。注解API非常强大,被广泛用于各种java框架。
2、jdk内置注解
@Override:方法可以覆写。
@Deprecated:标记已过时。
@SuppressWarnings:压制警告!(all)参数压制所有警告
@Target:描述注解的使用范围。
@Retention:描述注解的生命周期。
@Documented:该注解元素被javadoc文档化。
@Inherited:被子类继承的注解,该注解可以被子类使用。
- 自定义注解并解析注解
@Documented
@Retention(RetentionPolicy.RUNTIME) //运行时生效
@Target(ElementType.TYPE) //作用在类上
public @interface Table_ {
String[] name() default "";
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Column{
String columnName() default "";
}
@Table_(name = "student_")
class Student_Data{
@Column(columnName = "t_name")
private String name; //姓名
@Column(columnName = "t_age")
private int age; //年龄
@Column(columnName = "t_gender")
private String gender; //性别
@Column(columnName = "t_id")
private int id; //主键
public Student_Data() {
}
public Student_Data(String name, int age, String gender, int id) {
this.name = name;
this.age = age;
this.gender = gender;
this.id = id;
}
//setter getter修饰
}
class AnnotationDemo{
public static void main(String[] args) {
Student_Data data = new Student_Data("张三", 23, "男", 10020);
String s = insertSql(data);
System.out.println(s);
}
public static String insertSql(Object obj){
String sql="insert into ";
//获取类上的注解
Class<?> aClass = obj.getClass();
Table_ tab = aClass.getAnnotation(Table_.class);
String[] name = tab.name();//调用注解中的方法
for (String table:name){
sql+=table+"(";
}
//获取属性上的注解值
Field[] fields = aClass.getDeclaredFields();
for (Field f:fields){
Column column = f.getAnnotation(Column.class);
f.setAccessible(true);//私有属性要暴力破解
String s = column.columnName();
sql+=s+",";
}
sql=sql.substring(0,sql.length()-1)+") values (";
for (Field f1:fields){
try {
f1.setAccessible(true);
Object o = f1.get(obj);
sql+=o+",";
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
sql=sql.substring(0,sql.length()-1)+");";
return sql;
}
}
38、正则表达式
不加量词的单个符号匹配
x:表示正则表达式的任意字符
\t:表示单个制表符
\n:表示单个换行符
\\:表示单斜杠
\b:边界符号
\A:匹配字符开头
\Z:匹配字符结尾
不加量词的字符匹配
[abc]:表示匹配是否含有a或b或c
[a-z]:表示匹配是否含有小写字母a-z
[A-Z]:表示匹配是否含有大写字母A-Z
[^a-z]:表示匹配是否含有除了a-z
[a-zA-Z]:表示匹配不区分大小写的字母
不加量词的简化正则:
.:表示任意字符(除了换行符 \n)
\d:表示匹配数字,等价于[0-9]
\D:表示匹配除了数字,等价于[^0-9]
\w:表示匹配数字、字母(不区分大小写)、下划线。等价于[a-z_A-Z0-9]
\W:表示匹配除数字、字母、下划线,等价于[^a-zA-Z_0-9]
\s:表示匹配空格 等价于[ \t\n\x0B\f\r]
\S:表示匹配除空格之外,等价于[^\s]
边界正则匹配
^:一行的开始, 例如:^cat 以cat开头的字符串
$:一行的结束 例如:cat$ 以cat结尾的字符串
量词的匹配
正则表示式?:匹配出现一次或者0次
正则表达式*:匹配出现0次或者多次
正则表达式+:匹配出现一次或者多次
正则表达式{n}:匹配出现n次
正则表达式{n,}:匹配出现最少n次或者n次以上
正则表达式{n,m}:匹配出现n次到m次
正则逻辑运算
正则表达式A紧跟正则表达B,正则表达式B紧跟正则表达A.
正则表达式A|正则表达式B,正则表达式B|正则表达式A. 正则表达式A或者正则表达式B
(正则表达式):多个正则表达式变为一组
常用的正则表达式
一、校验数字的表达式
数字:^[0-9]*$
n位的数字:^\d{n}$
至少n位的数字:^\d{n,}$
m-n位的数字:^\d{m,n}$
零和非零开头的数字:^(0|[1-9][0-9]*)$
非零开头的最多带两位小数的数字:^([1-9][0-9]*)+(\.[0-9]{1,2})?$
带1-2位小数的正数或负数:^(\-)?\d+(\.\d{1,2})$
正数、负数、和小数:^(\-|\+)?\d+(\.\d+)?$
有两位小数的正实数:^[0-9]+(\.[0-9]{2})?$
有1~3位小数的正实数:^[0-9]+(\.[0-9]{1,3})?$
非零的正整数:^[1-9]\d*$ 或 ^([1-9][0-9]*){1,3}$ 或 ^\+?[1-9][0-9]*$
非零的负整数:^\-[1-9][]0-9"*$ 或 ^-[1-9]\d*$
非负整数:^\d+$ 或 ^[1-9]\d*|0$
非正整数:^-[1-9]\d*|0$ 或 ^((-\d+)|(0+))$
非负浮点数:^\d+(\.\d+)?$ 或 ^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$
非正浮点数:^((-\d+(\.\d+)?)|(0+(\.0+)?))$ 或 ^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$
正浮点数:^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$ 或 ^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$
负浮点数:^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$ 或 ^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$
浮点数:^(-?\d+)(\.\d+)?$ 或 ^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$
--------------------------------------------------------------------------------
校验字符的表达式
汉字:^[\u4e00-\u9fa5]{0,}$
英文和数字:^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$
长度为3-20的所有字符:^.{3,20}$
由26个英文字母组成的字符串:^[A-Za-z]+$
由26个大写英文字母组成的字符串:^[A-Z]+$
由26个小写英文字母组成的字符串:^[a-z]+$
由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$
由数字、26个英文字母或者下划线组成的字符串:^\w+$ 或 ^\w{3,20}$
中文、英文、数字包括下划线:^[\u4E00-\u9FA5A-Za-z0-9_]+$
中文、英文、数字但不包括下划线等符号:^[\u4E00-\u9FA5A-Za-z0-9]+$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$
可以输入含有^%&',;=?$\"等字符:[^%&',;=?$\x22]+
禁止输入含有~的字符:[^~\x22]+
----------------------------------------------------------------------------------
三、特殊需求表达式
Email地址:^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$
域名:[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+\.?
InternetURL:[a-zA-z]+://[^\s]* 或 ^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$
手机号码:^(13[0-9]|14[5|7]|15[0|1|2|3|4|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$
电话号码("XXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"和"XXXXXXXX):^(\(\d{3,4}-)|\d{3.4}-)?\d{7,8}$
国内电话号码(0511-4405222、021-87888822):\d{3}-\d{8}|\d{4}-\d{7}
电话号码正则表达式(支持手机号码,3-4位区号,7-8位直播号码,1-4位分机号): ((\d{11})|^((\d{7,8})|(\d{4}|\d{3})-(\d{7,8})|(\d{4}|\d{3})-(\d{7,8})-(\d{4}|\d{3}|\d{2}|\d{1})|(\d{7,8})-(\d{4}|\d{3}|\d{2}|\d{1}))$)
身份证号(15位、18位数字),最后一位是校验位,可能为数字或字符X:(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)
帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$
密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):^[a-zA-Z]\w{5,17}$
强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在 8-10 之间):^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])[a-zA-Z0-9]{8,10}$
强密码(必须包含大小写字母和数字的组合,可以使用特殊字符,长度在8-10之间):^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$
日期格式:^\d{4}-\d{1,2}-\d{1,2}
一年的12个月(01~09和1~12):^(0?[1-9]|1[0-2])$
一个月的31天(01~09和1~31):^((0?[1-9])|((1|2)[0-9])|30|31)$
钱的输入格式:
有四种钱的表示形式我们可以接受:"10000.00" 和 "10,000.00", 和没有 "分" 的 "10000" 和 "10,000":^[1-9][0-9]*$
这表示任意一个不以0开头的数字,但是,这也意味着一个字符"0"不通过,所以我们采用下面的形式:^(0|[1-9][0-9]*)$
一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号:^(0|-?[1-9][0-9]*)$
这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧。下面我们要加的是说明可能的小数部分:^[0-9]+(.[0-9]+)?$
必须说明的是,小数点后面至少应该有1位数,所以"10."是不通过的,但是 "10" 和 "10.2" 是通过的:^[0-9]+(.[0-9]{2})?$
这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样:^[0-9]+(.[0-9]{1,2})?$
这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样:^[0-9]{1,3}(,[0-9]{3})*(.[0-9]{1,2})?$
1到3个数字,后面跟着任意个 逗号+3个数字,逗号成为可选,而不是必须:^([0-9]+|[0-9]{1,3}(,[0-9]{3})*)(.[0-9]{1,2})?$
备注:这就是最终结果了,别忘了"+"可以用"*"替代如果你觉得空字符串也可以接受的话(奇怪,为什么?)最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里
xml文件:^([a-zA-Z]+-?)+[a-zA-Z0-9]+\\.[x|X][m|M][l|L]$
中文字符的正则表达式:[\u4e00-\u9fa5]
双字节字符:[^\x00-\xff] (包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1))
空白行的正则表达式:\n\s*\r (可以用来删除空白行)
HTML标记的正则表达式:<(\S*?)[^>]*>.*?|<.*? /> ( 首尾空白字符的正则表达式:^\s*|\s*$或(^\s*)|(\s*$) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式)
腾讯QQ号:[1-9][0-9]{4,} (腾讯QQ号从10000开始)
中国邮政编码:[1-9]\d{5}(?!\d) (中国邮政编码为6位数字)
IPv4地址:((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}
39、xml文件配置
1、XML(可扩展的标记语言)是一种可以实现跨平台、跨网络并且不受程序开发平台的数据描述语言,在实际的项目开发中可以通过XML数据方便的实现数据交换、系统配置功能。
2、如果要想HTML和XML的作用 :
相比较之前的HTML代码来讲,此时给出XML文件结构上更加的清晰,但是显示的功能不是那么强大(因为HTML标签的所有目的就是为了进行页面展示),XML中的标签没有所谓的显示功能,仅仅是能够提供数据标记功能,所以XML结构上会更加的强大,但是显示的方式确实不足。
- 语法
1、前导声明:<?xml version="XML版本编号" encoding="中文显示编码" standalone="是否独立运行"?>
a、version="XML版本编号":规定了XML语法版本,现在有且只有"1.0"版本语法;
b、encoding="中文显示编码":如果当前的XML文件中包含有中文数据,则必须设置编码,否则无法正常显示;
c、standalone="是否独立运行":XML仅仅可以描述数据结构,但是有些时候需要通过一些其他的语法(css、xslt)让其显示,此时就需要通过"standalone"明确的告诉当前的浏览器是否需要其他的语言支持,如果要有其他的相关文件引入,则此时的内容必须设置"no",如果是独立运行则设置"yes"。
2、数据主体:描述XML所要保存的数据信息。
在XML中数据可以通过元素和属性两种形式进行描述,实际上常见的做法就是通过元素描述数据;
每一个XMl文件里面都必须存在一个根节点,所有的其他数据节点都必须在根元素中定义;
每一个元素中可以存在有若干个属性定义,多个属性要通过空格分割,采用"属性名称"="内容",所有的属性内容必须使用双引号封装(HTML之中的元素属性可以加上引号也可以不加引号,但是XML是描述数据结构的,所以语法要求必须严格)。
3、可引入外部文件,把standalone="false",文件头加上 <?xml-stylesheet type="text/css" href="css/style.css"?>;
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<tree>
<contact id="&">
<name>张三</name>
<age>25</age>
<address>天安门</address>
</contact>
<contact>
<name>李四</name>
<age>23</age>
<address>南池子</address>
</contact>
<contact>
<name>王五</name>
<age>26</age>
<address>北池子</address>
</contact>
</tree>
- 常见的转义字符
No | 实体参照 | 对应字符 |
---|---|---|
1 | & |
& |
2 | < |
< |
3 | > |
> |
4 | " |
" |
5 | ' |
' |
- DOM树
DOM树是在内存中完成的,要实现DOM树要考虑大小问题,在每一个元素之中保存的文本内容,从严格意义上来讲都是保存在文本节点里的,也就是说元素下首先要包含有文本节点,而后在文本节点下才有具体的文本内容。
- 使用java中DOM内置方法解析xml文件
//获取文档构建工厂
DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();
//获取工厂中的实例化对象
try {
DocumentBuilder builder = factory.newDocumentBuilder();
//解析自定义xml文件
Document document = builder.parse(new File("C:\\Users\\Administrator\\Desktop\\test.xml"));
//获取文档节点
Element element = document.getDocumentElement();
//获取members节点
NodeList members = element.getElementsByTagName("member");
//由于有多个member 所以要执行循环
for (int i=0;i<members.getLength();i++){
//获取每个member的id属性值
System.out.println(members.item(i).getAttributes().item(0).getNodeValue());
//获取Name节点的值
String name = document.getElementsByTagName("name").item(i).getTextContent();
//获取age节点的值
String age = document.getElementsByTagName("age").item(i).getTextContent();
//获取gender节点的值
String gender = document.getElementsByTagName("gender").item(i).getTextContent();
System.out.printf("姓名:%s,年龄:%s,性别:%s\n",name,age,gender);
}
} catch (ParserConfigurationException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (SAXException e) {
throw new RuntimeException(e);
}
- DOM4J解析XML文件
1、java中DOM原生态方法便于删除修改xml文件节点,而SAX便于查询xml文件节点,DOM4j是将修改和查询封装为一体的工具。
2、导入DOM4jjar包,添加到项目librate。
- xml源文件
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<members>
<member id="one">
<name>张三</name>
<age>25</age>
<gender>男</gender>
</member>
<member id="two">
<name>李四</name>
<age>26</age>
<gender>女</gender>
</member>
</members>
- 测试代码
//DOM4j解析xml文件 实例化SAXReader对象
SAXReader reader=new SAXReader();
try {
//读取本地xml文件
Document document = reader.read(new File("C:\\Users\\Administrator\\Desktop\\test.xml"));
Element rootElement = document.getRootElement();//获取根节点
System.out.println("根节点名称:"+rootElement.getName());
//获取根节点下的所有member子节点
List<Element> members = rootElement.elements("member");
//获取根节点下的属性
for (Element m:members){
System.out.println("子节点所有的id属性值"+m.attribute("id").getValue());
Element name = m.element("name");
System.out.println(name.getText());
Element age = m.element("age");
System.out.println(age.getText());
Element gender = m.element("gender");
System.out.println(gender.getText());
}
} catch (DocumentException e) {
throw new RuntimeException(e);
}
标签:String,int,笔记,学习,线程,new,JAVASE,public,out
From: https://www.cnblogs.com/smallzengstudy/p/17615996.html