个人主页:Hello Code.
本文专栏:Java零基础指南
如果觉得本文篇幅较长,请前往上方专栏查看Java基础知识分篇总结
如有问题,欢迎指正,一起学习~~
环境搭建(https://www.java.com/zh-CN/)
了解JRE和JDK
JDK的下载和安装
常用DOS命令
public class Hello{ //定义一个名为Hello的类(
public static void main(String[] args){ //main入口
System.out.println("Hello World"); //打印输出Hello World
}
}
目的:为了让数据更加灵活
步骤
import java.util.Scanner;
(需要写在class上面)Scanner sc = new Scanner(System.in);
(只有sc可以改变,其他属于固定格式)int i = sc.nextInt();
(只有i和sc变量可以改变,其他属于固定格式)实例
import java.util.Scanner; //导包
public class input{ //创建input类
public static void main(String[] args){ //主入口
Scanner scan = new Scanner(System.in); //创建对象
int a = scan.nextInt(); //使用变量接收数据
System.out.println(a); //打印输出数据
}
}
标识符:就是给类、方法、变量等起名字的符号
定义规则
常见命名约定
小驼峰命名法:方法、变量
约定1:标识符是一个单词的时候,首字母小写
范例1:name
约定2:标识符由多个单词组成的时候,第一个单词首字母小写,其他单词首字母大写
范例2:firstName
大驼峰命名法:类
约定1:标识符是一个单词的时候,首字母大写
范例1:Student
约定2:标识符由多个单词组成的时候,每个单词首字母都大写
范例2:GoodStudent
隐式转换:将数据类型中,取值范围小的数据,给取值范围大的类型赋值,可以直接赋值
int a = 10; //int占4字节
double b = a; //double占8字节
简单记:小的给大的,可以直接给
4升的油,倒入8升的桶,可以直接倒入
小的数据和大的数据类型运算,小的会先提升为大的数据类型再进行运算
byte short char 这三种数据在运算的时候,无论是否有更高的数据类型,都会提升为int,然后再进行运算
强制转换:把一个表示数据范围大的数值或者变量赋值给另一个表示数据范围小的变量
格式:目标数据类型 变量名 = (目标数据类型)值或者变量;
int a = 10; //int 4字节
byte b = (byte)a; //byte 1字节
int c = (int)88.88;
强制类型转换,有可能会发生精度损失
精度损失:简单理解,将容积为8升的水倒入容积为4升的桶中,多出的水会洒掉
类型转换案例
请判断下列代码是否存在问题,如果有,请指出并改正12
public class Test{
public static void main(String[] args){
byte a = 3; //①
byte b = 4; //②
byte c = a + b; //③
byte d = 3 + 4; //④
}
}
运算符:对常量或者变量进行操作的符号
表达式:用运算符把常量或者变量连接起来符合Java语法的式子就可以称为表达式。
不同运算符连接的表达式体现的是不同类型的表达式。
int a = 10;
int b = 20;
int c = a + b;
算术运算符:加(+)、减(-)、乘(*)、除(/)、取余(%)
加、减、乘都和小学一样,在用除法时,两整数相除只能得到整数,想要得到小数必须有浮点数参与运算
取余即指取结果的余数
字符之间相加时,字符会根据ASCII码值提升为int类型进行运算 (0–48 a–97 A–65)
int a = 1;
char b = 'a';
System.out.println(a + b); //输出98
字符串之间相加时,可以使用+和(任意数据类型)拼接
当‘+’操作中出现字符串时,这个‘+’是字符串连接符,而不是算术运算符
在‘+’操作中,如果出现了字符串,就是连接运算符,否则就是算术运算符,当连续进行‘+’操作时,从左到右逐个执行
System.out.println("java" + 666); //java666
System.out.println(1 + 99 +"java"); //100java
System.out.println("5+5=" + 5 + 5); //5+5=55
System.out.println("5+5=" + (5 + 5)); //5+5=10
自增自减运算符:自增(++)、自减(–)
赋值运算符:赋值(=)、加后赋值(+=)、减后赋值(-=)、乘后赋值(*=)、除后赋值(/=)、取余后赋值(%=)
int a = 10;
a += 20; //a = a + 20
System.out.println(a); //30
注意:扩展运算符底层会自带强制类型转换的功能
关系(比较)运算符:等于(==)、不等(!=)、大于(>)、大于等于(>=)、小于(<)、小于等于(<=)
注意:返回结果只有true或者false
逻辑运算符
&(与):并且,全真则真,一假则假
|(或):或者,全假则假,一真则真
!(非):取反,真假假真
^(异或):两同为假,不同为真
短路逻辑运算符
&&(短路与):作用和&相同,但有短路效果
||(短路或):作用和|相同,但有短路效果
逻辑短路:&&遇到false直接为false,不执行后面代码;||遇到true则直接为true,不执行后续代码
三元运算符
格式:关系表达式 ? 表达式1 : 表达式2;
执行流程
首先计算关系表达式的值
若为true则取表达式1的值
若为false则取表达式2的值
流程控制语句:通过一些语句,来控制程序的执行流程
顺序结构语句:程序中最简单最基本的流程控制,没有特定的语法结构,按照代码的先后顺序依次执行,程序中大多数代码都是这样的
if语句
格式1:
if(关系表达式){
语句体1;
}else{
语句体2;
}
执行流程:
首先计算关系表达式的值
如果关系表达式的值为true则执行语句体1
如果关系表达式为false就执行语句体2
继续执行后续代码
import java.util.Scanner;
public class a{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
System.out.println("请输入您的年龄:");
int age = sc.nextInt();
if(age >= 18){
System.out.println("您已成年,可以进入");
}else{
System.out.println("对不起,您未成年,不能进入");
}
}
}
如果if语句中的语句体只有一条,那么大括号{}可以省略不写,但是不建议,容易混淆
if语句小括号后面不要写分号;
格式2(多条件判断):
if(判断条件){
语句体1;
}else if(判断条件2){
语句体2;
}
...
else{
语句体n+1;
}
import java.util.Scanner;
public class a{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
System.out.println("请输入您的成绩:");
int score = sc.nextInt();
if(score >= 90 && score <= 100){
System.out.println("优秀");
}else if(score >= 80 && score < 90){
System.out.println("良好");
}else if(score >=60 && score < 80){
System.out.println("及格");
}else if(score <60 && score >= 0){
System.out.println("不及格");
}else{
System.out.println("请输入正确的成绩!");
}
}
}
switch语句
switch(表达式){
case 值1:
语句体1;
break;
case 值2;
语句体2;
break;
....
default:
语句体n+1;
break;
}
格式说明:
表达式==(将要被匹配的值)取值为byte、short、int、char、JDK5、以后还可以是枚举,JDK7以后可以是String
case:后面跟的是要和表达式比较的值(被匹配的值)==
break:表示中断,结束的意思,用来结束switch语句
default:表示所有的情况都不匹配的时候,就执行该处的内容,和if语句的else相似
case穿透:
现象:当开始case穿透,后续的case就不会具有匹配的效果,内部的语句都会执行
直到看见break,或者将整体switch语句执行完毕,才会结束
应用场景:当多个case语句出现重复现象,就可以考虑使用case穿透来优化代码
for循环(推荐明确循环次数时使用)
格式:
for(初始化语句;条件判断语句;条件控制语句){
循环体语句;
}
执行流程:
执行初始化语句
执行条件判断语句,看其结果是true还是false
如果是false,循环结束
如果是true,循环继续
执行循环体语句
执行条件控制语句
回到第2步继续循环
案例
//在控制台输出1-5
for(int i = 1;i <= 5;i++){
System.out.println(i);
}
//求1-5的数据和
int sum = 0;
for(int i = 1;i <= 5;i++){
sum += i;
}
System.out.println(sum);
//求1-100的偶数和
int sum = 0;
for(int i = 1;i <= 100;i++){
if(i % 2 == 0){
sum += i;
}
}
System.out.println(sum);
//输出所有的水仙花数
//水仙花数:是一个三位数,个位、十位、百位的数字立方和等于原数
for(int i = 100;i <= 999;i++){
int sum;
int a = i % 10;
int b = i / 10 % 10;
int c = i / 100;
sum = a * a * a + b * b * b + c * c * c;
if(sum == i){
System.out.println(i);
}
}
System.out.print()为同行打印,不换行
System.out.println()自带换行效果,类似于html中的块级元素,独占一行。括号内无内容时可以当作换行符来使用
while循环(不明确循环次数时推荐while)
格式:
while(条件判断语句){
循环体语句;
条件控制语句;
}
int i = 1;
while(int i <= 100){
System.out.println(i);
i++;
}
do…while循环(使用较少)
格式:
do{
循环体语句;
条件控制语句;
}while(条件判断语句);
三种循环的区别:
for循环和while循环先判断条件是否成立,然后决定是否执行循环体(先判断后执行)
do…while循环先执行一次循环体,然后判断条件是否成立,是否继续执行循环体(先执行后判断)
条件控制语句中所控制的自增变量,因为归属于for循环的语法结构中,在for循环结束后,就不能再次被访问到了
条件控制语句所控制的自增变量,对于while循环来说不归属其语法结构中,在while循环结束后,该变量还可以继续使用
死循环
//for
for(;;){
循环体语句;
}
//while
while(true){
循环体语句;
}
//do...while
do{
循环体语句;
}while(true);
例如:键盘录入一个1-100之间的整数(用户可能出现误操作现象)
while(true){
Scanner sc = new Scanner(System.in);
int a = sc.nextInt();
if(a >= 1 && a <= 100){
b
}
}
continue与break关键字
continue:跳过某次循环内容,继续开始下一层循环,只能在循环中使用
break:跳出整个循环,终止循环体内容的执行,只能在循环和switch中使用
标号:可以给语句块加标号赋予它们名称,标号位于语句之前。标号只能被continue和break引用。
public class Test{
public static void main(String[] args){
int n = 1;
lo: //标号
while(true){
switch(n){
case 1:
System.out.println("1");
break lo; //通过标号,这里的break将结束外层while循环
}
}
}
}
// 语句前只允许加一个标号,标号后面不能跟大括号。通过用break后加标号对处于标号中的语句进行控制。往往标号后是for.while.do-while等循环。
作用:用于产生一个随机数
使用步骤
导包
import java.util.Random;
创建对象
Random r = new Random();
获取随机数
int number = r.nextInt(10); //获取数据的范围:[0,10),包括0,不包括10
int number = r.nextInt(10) + 1; //获取数据的范围:[1,10],包括1,也包括10
猜数字案例
import java.util.Random;
import java.util.Scanner;
public class a{
public static void main(String[] args){
Random r = new Random();
int ran = r.nextInt(100) + 1;
while(true){
System.out.print("请输入您猜的数字:");
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
if(n > ran){
System.out.println("猜大了~~");
}else if(n < ran){
System.out.println("猜小了~~");
}else {
System.out.println("猜恭喜你,猜对了,答案就是" + ran);
break;
}
}
}
}
概述:IDEA全称IntelliJ IDEA,是用于Java语言开发的集成环境,它是业界公认的目前用于Java程序开发最好的工具
集成环境:就是把代码编写、编译、执行、调试等多种功能综合到一起的开发工具
下载和安装
下载:https://www.jetbrains.com/idea/
安装:傻瓜式安装,建议修改安装路径
IDEA项目结构
project-module-package-class
这些结构的划分,是为了方便管理文件
IDEA中的第一个代码
注意:最后代码一定要存放到src目录下,包名类似于com.baidu.demo1这样的格式
数组(array):是一种容器,用来存储同种数据类型(或者比它所占字节小的)的多个值
格式
//1.数据类型[] 变量名 【最常用】
int[] array;
//2.数据类型 变量名[]
int array[];
初始化:Java中的数组必须先初始化,才能使用
所谓初始化,就是在内存中,为数组容器开辟空间,并将数据存入容器的过程
动态初始化:初始化时只指定数组长度,由系统为数组分配初始值(只明确元素个数,不明确具体数值,推荐使用)
格式:数据类型[] 变量名 = new 数据类型[数组长度];
范例:int[] arr = new int[3];
注意:打印数组变量名,输出的是数组在内存中的地址值
数组元素访问
数组内存地址的访问:数组名
数组内部保存的数据的访问:数组名[索引]
索引从0开始,连续的,逐一增加
数组在创建完毕后,没有赋值也能取出,取出的值为0
默认值:
整数——0
浮点数——0.0
布尔——false
字符——空字符
引用数据类型——null
内存分配
Java程序在运行时,需要在内存中分配空间
为了提高效率,就对空间进行了不同区域的划分
每一片区域都有特定的处理数据的方式和内存管理方式
栈内存:方法运行时,进入的内存,局部变量都存放于这块内存当中
堆内存:new出来的内容就会进入堆内存,并且会存在地址值
方法区:字节码文件加载时进入的内存
本地方法栈:调用操作系统相关资源
寄存器:交给CPU去使用
静态初始化:初始化时,就可以指定数组要存储的元素,系统还会自动计算出该数组的长度(需求中明确了具体数据,直接静态初始化即可)
格式:数据类型[] 变量名 = new 数据类型[]{数据1,数据2,数据3,....};
范例:int[] arr = new int[]{1,2,3};
简化格式:int[] arr = {1,2,3};
数组遍历:将数组中所有的元素取出来
动态获取数组元素个数:数组名.length
int arr = {1,2,3,4,5,6,7,8,9};
for(int i = 0; i <= arr.length; i++){
System.out.println(arr[i]);
}
注意:遍历是指取出数据的过程,不要局限的理解为:遍历就是打印
数据常见操作
获取最值
思路:定义一个变量,用于保存最大值(或最小值)
取数组中的第一个值作为变量的初始值(假设第一个值就是最大(小)值)
与数组中的剩余数据逐个对比
int[] arr = {1, 2, 3, 4, 5, 6, 7000, 8, 919};
int max = arr[0];
for(int i = 0; i < arr.length; i++){
if(arr[i] > max) max = arr[i];
}
System.out.println("最大值为:" + max);
数组元素求和
import java.util.Scanner;
public class test{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int[] arr = new int[];
int sum = 0;
for(int i = 0; i < arr.length; i++){
System.out.print("请输入第" + (i+1) + "个数值:");
int n = sc.nextInt();
sum += n;
}
System.out.println("数组内的元素的和为:" + sum);
}
}
数组基本查找
int[] arr = {19, 28, 37, 46, 50};
Scanner sc = new Scanner(System.in);
System.out.print("请输入您要查找的数据:");
int n = sc.nextInt();
for(int i = 0; i < arr.length; i++){
if(arr[i] == n){
System.out.println("您要查找的数据索引为:" + i);
break;
}
}
概念:方法就是一段具有独立功能 的代码块,不调用就不执行
使用前提:
方法必须先创建才可以使用,该过程称为方法的定义
方法创建后并不是直接运行的,需要手动使用后才执行,该过程称为方法调用
方法定义:
public static void 方法名(){
//方法体
}
方法调用:方法名();
注意:方法必须先定义后调用,否则程序将会报错
方法与方法之间是平级关系,不能嵌套定义
在方法没有被调用的时候,都在方法区中的字节码文件(.class)中存储
方法被调用的时候,需要进入到栈内存中运行
带参方法的定义和调用
public static void 方法名(参数){
//方法体
}
单个参数:数据类型 变量名
多个参数:数据类型 变量名1 , 数据类型 变量名2 , .....
调用:方法名(参数);
方法名(变量名/常量值);
方法名(变量名1/常量值1 , 变量名2/常量值2 , ...);
方法调用时,参数的数量与类型必须与方法定义中的设置相匹配,否则程序将会报错
形参和实参
形参:全称形式参数,是指方法定义中的参数
实参:全称实际参数,是指方法调用中的参数
带参数方法练习
// 需求:设计一个方法(print)用于打印n到m之间的所有的奇数
public class a{
public static void main(String[] args){
print(10, 20);
}
public static void print(int n, int m){
for(int i = n; i <= m; i++){
if(i % 2 != 0) System.out.println(i);
}
}
}
带返回值方法的定义和调用
public static 数据类型 方法名(参数){
return 数据;
}
方法定义时,return后面的返回值与方法定义上的数据类型要匹配,否则就会报错
在执行代码时,return语句后面的语句都不会执行,属于无效代码
return;
可以用于结束方法,也就是方法从栈内存中弹出去,该过程称为方法的弹栈
调用:数据类型 变量名 = 方法名(参数);
方法通用格式
public static 返回值类型 方法名(参数){
方法体;
return 数据;
}
方法重载:
方法名相同,参数也完全相同,称为方法的重复定义,是一种冲突性的错误
在调用方法的时候,Java虚拟机会通过参数的不同来区分同名的方法
在同一个类中,定义了多个同名的方法,但每个方法具有不同的参数类型或参数个数,这些同名的方法,就构成了重载关系
注意:识别方法之间是否是重载关系,只看方法名和参数,和返回值无关
好处:不用记忆过多繁琐的方法名字
方法重载练习
// 需求:使用方法重载思想,设计比较两个整数是否相同的方法,兼容全整数类型(byte,short,int,long)
public static void main(String[] args){
System.out.println(compare(20, 30));
}
public static boolean compare(byte a, byte b){
return a == b;
}
public static boolean compare(short a, short b){
return a == b;
}
public static boolean compare(int a, int b){
return a == b;
}
public static boolean compare(long a, long b){
return a == b;
}
方法的参数传递
案例
// 需求:设计一个方法用于数组遍历,要求遍历的结果是在一行上的。例如:[11,22,33,44,55]
public static void main(String[] args) {
int[] arr = {11, 22, 33, 44, 55};
printArray(arr);
}
public static void printArray(int[] arr) {
System.out.print("[");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]);
if (i == arr.length - 1) System.out.print("]");
else {
System.out.print(", ");
}
}
}
// 需求:设计一个方法用于获取数组元素中的最大值
public static void main(String[] args){
int[] arr = {11,33,44,88,22};
System.out.println("数组中的最大值为:" + max(arr));
}
public static int max(int[] arr){
int max = arr[0];
for(int i = 0; i < arr.length; i++){
if(arr[i] > max) max = arr[i];
}
return max;
}
// 需求:设计一个方法,该方法中能够同时获取最大值和最小值
public static void main(String[] args){
int[] arr = {11,22,33,44,2,2393,55};
int[] res = get(arr);
System.out.println("最大值为:" + res[0]);
System.out.println("最小值为:" + res[1]);
}
public static int[] get(int[] arr){
int max = arr[0];
int min = arr[0];
for(int i = 0; i < arr.length; i++){
if(arr[i] > max) max = arr[i];
if(arr[i] < min) min = arr[i];
}
int[] arrMaxAndMin = {max, min};
return arrMaxAndMin;
}
注意:return语句只能同时返回一个值,需要返回多个值的话可以使用数组
进制:指进位制:是人们规定的一种进位方式
常见进制:二进制、八进制、十进制、十六进制
计算机数据在底层运算的时候,都是以二进制进行的,了解不同的进制,便于我们对数据的运算过程理解的更加深刻
十进制:逢十进一,借一当十
二进制:逢二进一,借一当二(只有0和1)
八进制:逢八进一,借一当八(0,1,2,3,4,5,6,7)
十六进制:0~9,a~f(其中a~f表示10~15)
在Java中,数值默认都是十进制,不需要加任何修饰
二进制:数值前面以0b开头,b大小写都可
八进制:数值前面以0开头
十六进制:数值前面以0x开头,x大小写都可
注意:书写的时候,虽然加入了进制的标识,但打印在控制台展示的时候都是十进制数据
进制转换
任意进制到十进制公式:系数*基数的权次幂 相加
系数:每一【位】上的数
基数:几进制,就是几
权:从数值的右侧,以0开始,逐个+1增加
十进制到任意进制的转换公式:除基取余
使用源数据,不断地除以基数(几进制就是除几)得到余数,直到商为0,再将余数倒序拼起来即可
快速进制转换法:8421码
8421码又称BCD码,是BCD代码中最常用的一种
BCD:(Binary-Coded Decimal)二进制码十进制数
在这种编码方式中,每一位二进制值的1都是代表一个固定数值,把每一位的1代表的十进制数加起来得到的结果就是他所代表的十进制数
原码反码补码
注意:计算机中的数据,都是以二进制补码的形式在运算,而补码则是通过反码和原码推算出来的
原码:(可直观的看出数据大小)就是二进制定点表示法,最高位为符号位,0正1负,其余位表示数值的大小
一个字节等于8个比特位,也就是8个二进制位
反码:(将原码转换为补码)正数的反码与其原码相同,负数的反码是对其原码逐位取反,但符号位除外
补码:(数据以该状态进行运算)正数的补码与其原码相同,负数的补码是在其反码的末位加1
正数的原反补都是相同的
负数的【反码】,是根据【原码】取反(0变1,1变0)得到的==(符号位不变)==
负数的【补码】,是根据【反码】的末尾+1得到的
位运算
位运算指的是二进制位的运算,先将十进制数转换成二进制后再进行运算
在二进制位运算中,1表示true,0表示false
&
位与:遇false则false,遇0则0
|
位或:遇true则true,遇1则1
^
位异或:相同为false,不同为true (a = a ^ b ^ b)
~
取反:全部取反,0变1,1变0(也包括符号位)
位移运算符:
<<
有符号左移运算,二进制位向左移动,左边符号位丢弃,右边补齐0
运算规律:向左移动几位,就是乘以2的几次幂
>>
有符号右移运算:二进制位向右移动,使用符号位进行补位
运算规律:向右移动几位,就是除以2的几次幂
>>>
无符号右移运算符,无论符号位是0还是1,都补0
案例:
// 需求:在不使用第三方变量的情况下,实现两数据交换
public static void main(String[] args){
int a = 10;
int b = 20;
a = a ^ b;
b = a ^ b;
a = a ^ b;
System.out.println(a);
System.out.println(b);
}
// 需求:实现数组元素的反转(交换数组中元素的值)
int[] arr = {11, 22, 33, 44, 55, 66, 77};
for(int start = 0, end = arr.length - 1; start < end; start++, end--){
int temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
}
for(int i = 0 ; i < arr.length; i++){
System.out.println(arr[i]);
}
概述:二维数组也是一种容器,不同于一维数组,该容器存储的都是一维数组容器
定义格式
格式1:数据类型[][] 变量名;
格式2:数据类型 变量名[];
格式3:数据类型[] 变量名[];
动态初始化:数据类型[][] 变量名 = new 数据类型[m][n];
m表示这个二维数组可以存放多少个一维数组==(行)==
n表示每一个一维数组,可以存放多少个元素==(列)==
拓展:将一个提前创建好的一维数组存储到二维数组中
int[] arr = {11, 22, 33};
int[][] arr2 = new int[1][3];
arr2[0] = arr;
System.out.println(arr2[0][2]);
静态初始化:
格式:数据类型[][] 变量名 = new 数据类型[][]{{元素1, 元素2,...}, {元素1, 元素2,...}...};
简化格式:数据类型[][] 变量名 = {{元素1, 元素2,...}, {元素1, 元素2,...}, ...};
二维数组遍历
// 需求:已知一个二维数组arr = {{11, 22, 33}, {33, 44, 55}}; 遍历数组,取出所有元素并打印
int[][] arr = {{11, 22, 33}, {33, 44, 55}};
for(int i = 0; i < arr.length; i++){
for(int j = 0; j < arr[i].length; j++){
System.out.println(arr[i][j]);
}
}
案例:
// 需求:二维数组求和
int sum = 0;
int[][] arr = {{22, 66, 44}, {77, 33, 88}, {25, 45, 65}, {11, 66, 99}};
for(int i = 0; i < arr.length; i++){
for(int j = 0; j < arr[i].length; j++){
sum += arr[i][j];
}
}
System.out.println(sum);
类是对对象的描述
对象是类的实体
一个类可以创建出多个对象
类的组成:属性和行为
类的定义步骤
定义类
编写类的成员变量
编写类的成员方法
public class 类名{
// 成员变量
变量1的数据类型 变量1;
String name; // 未赋值默认null
变量2的数据类型 变量2;
int age; // 未赋值默认0
......
// 成员方法
方法1;
public void study(){
System.out.println("学习");
}
方法2;
......
}
创建对象
格式:类名 对象名 = new 类名();
使用对象
使用成员变量:对象名.变量名
使用成员方法:对象名.方法名();
案例
// 需求:定义一个类,然后定义一个手机测试类,在手机测试类中通过对象完成成员变量和成员方法的使用
public class Phone{
// 成员变量:品牌、价格、....
String brand;
int price;
// 成员方法:打电话、发短信、....
public void call(String name){
System.out.println("给" + name + "打电话");
}
public void sendMessage(){
System.out.println("群发短信");
}
}
单个对象内存图
两个对象内存图
两个引用指向同一对象内存图
成员变量和局部变量
private关键字:权限修饰符,可以用来修饰成员,来提高数据的安全性
特点:只能在本类当中进行访问,外界需要访问可以定义方法来进行设置值和获取值
针对private修饰的成员变量,如果需要被其他类引用,提供相应的操作
提供get变量名();
方法,用于获取成员变量的值,方法用public修饰
提供set变量名();
方法,用于设置成员变量的值,方法用public修饰
// 新建Student类
public class Student{
private String name;
private int age;
public void setName(String n){
name = n;
}
public String getName(){
return name;
}
public void setAge(int a){
age = a;
}
public int getAge(){
return age;
}
public void show(){
System.out.println(name + age);
}
}
this关键字:可以调用本类的成员(变量,方法),解决局部变量和成员变量重名问题
局部变量和成员变量如果重名,Java使用的是就近原则
this
代表所在类的对象引用,方法被哪个对象调用,this就代表哪个对象
public class Student{
private int age;
public void method(int age){
this.age = age; // 添加this关键字,使前一个age成为成员变量,再将局部变量age赋值过去
}
}
封装
构建、创造对象的时候,所调用的方法
格式:
public class Student{
public Student(){
System.out.println("这是Student类的构造方法");
}
}
执行时机:
作用:用于给对象的数据**(属性)**进行初始化
class Student{
private int age;
public Student(int age){
this.age = age;
}
}
注意事项
构造方法的创建:
如果没有定义构造方法,系统将给出一个默认的无参数的构造方法
如果定义了构造方法,系统将不再提供默认的构造方法
标准类的代码编写和使用
/*
JavaBean类:封装数据的类
*/
public class Student{
// 私有变量
private String name;
private int age;
// 无参数构造方法
public Student(){}
//有参数构造方法
public Student(String name, int age){
this.name = name;
this.age = age;
}
//set/get方法
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age = age;
}
public String getAge(){
return age;
}
}
API(Application Programming Interface):应用程序编程接口
编写程序去控制踢足球,程序需要向机器人发出向前跑、向后跑、射门、抢球等各种命令。机器人厂商一定会提供一些用于控制机器人的接口类,这些类中定义好了操作机器人各种动作的方法。其实,这些接口类就是机器人厂商提供给应用程序编程的接口,大家把这些类称为API。
键盘录入字符串
Scanner sc = new Scanner();
System.out.print("请输入内容:");
String s = sc.nextLine(System.in); 遇到回车、换行结束
//String s = sc.next(System.in); 遇到空格、tab就停止
System.out.println(s);
// 在键盘录入接收数据的时候,如果是字符串和整数一起接收,建议使用next方法接收字符串
概述:String类在java.lang包下,使用时不需要导包。
String类代表字符串,Java程序中的所有字符串字面值(如:“abc”)都作为此类的方法实现
字符串是常量,它们的值在创建后不能更改
常见构造方法
public String()
创建一个空白字符串对象,不含有任何内容
public String(char[] chs)
根据字符数组的内容,来创建字符串
public String(String original)
根据传入的字符串内容,来创建字符串对象
String s = "abc";
直接赋值的方式创建字符串对象,内容就是abc
String 这个类比较特殊,打印其对象名的时候,不会出现内存地址,而是该对象所记录的真实内容
创建字符串对象的区别对比
以""方式给出的字符串,只要字符序列相同(顺序和大小写),无论在程序代码中出现几次,JVM都只会建立一个String对象,并在字符串常量池中维护
字符串常量池:当使用双引号创建字符串对象的时候,系统会检查该字符串是否在字符串常量池中存在。若不存在,则创建;存在则不会重新创建,直接使用
注意:字符串常量池从JDK7开始,从方法区挪到了堆内存
扩展:==
在比较基本数据类型时比较值,在比较引用数据类型时比较地址是否相同
通过new创建的字符串对象,每一次new都会申请一个内存空间,虽然内容相同,但是地址值不同
特点:
Java程序中所有双引号字符,都是String类的对象
字符串不可变,它们的值在创建后不能被更改
虽然String的值是不可变的,但是他们可以被共享
当字符串之间使用 + 拼接的时候,系统底层会自动创建一个StringBuilder对象
然后再调用其append方法完成拼接
拼接后,再调用toString方法转换为String类型
字符串比较
字符串是对象,他比较内容是否相同,是通过一个方法来实现的,这个方法叫:equals()
public boolean equals(Object anObject):将此字符串与指定对象进行比较。由于我们比较的是字符串对象,所以参数之间传递一个字符串
String s1 = "abc";
String s2 = "ABC";
s1.equals(s2); //false
//equalsIgnoreCase()方法比较字符串不区分大小写
s1.equalsIgnoreCase(s2); //true
案例
// 需求:已知用户名和密码,请用程序实现模拟用户登录,总共给三次机会,登录之后,给出相应的提示
import java.util.Scanner;
public class Test{
public static void main(String[] args){
String userName = "abcde";
String passWord = "abc666";
int count = 0;
Scanner sc = new Scanner(System.in);
for(int i = 0; i < 3; i++){
System.out.print("请输入用户名:");
String user = sc.nextLine();
System.out.print("请输入密码:");
String pass = sc.nextLine();
if(userName.equals(user) && passWord.equals(pass)){
System.out.println("恭喜您,登录成功!");
break;
}else{
System.out.println("账号或密码错误,请重试");
count++;
}
}
if(count >= 3) System.out.println("登录失败次数过多,请稍后重试");
}
}
// 需求:键盘录入一个字符串,使用程序实现在控制台遍历该字符串,将字符串拆分为字符数组
// public char charAt(int index):返回指定索引处的char值,字符串的索引也是从0开始的
// public int length():返回此字符串的长度
// public char[] toCharArray():将当前字符串拆分为字符数组并返回
import java.util.Scanner;
public class Test{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
System.out.print("请输入字符串:");
String s = sc.nextLine();
/*
for(int i = 0; i < s.length(); i++){
System.out.print(s.charAt(i) + " ");
}
*/
char[] chars = s.toCharArray();
for(int i = 0; i < chars.length; i++){
System.out.print(chars[i] + " ");
}
}
}
//需求: 键盘录入一个字符串,统计该字符串中大写字母字符,小写字母字符,数字字符出现的次数(不考虑其他字符)
import java.util.Scanner;
public class Test{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
System.out.print("请输入字符串:");
String s = sc.nextLine();
char[] chars = s.toCharArray();
int num = 0;
int english = 0;
int English = 0;
for(int i = 0; i < chars.length; i++){
if(chars[i] >= '0' && chars[i] <= '9') num++;
else if(chars[i] >= 'a' && chars[i] <= 'z') english++;
else if(chars[i] >= 'A' && chars[i] <= 'Z') English++;
}
System.out.println("数字:" + num + " 小写字母:" + english + " 大写字母:" + English);
}
}
// 需求:以字符串的形式从键盘接收一个手机号,将中间四位号码屏蔽
// 最终效果:183****4828
/*
截取字符串:
String substring(int beginIndex):
从传入的索引位置处,向后截取,一直截取到末尾,得到新的字符串并返回
String substring(int beginIndex, int endIndex):
从beginIndex索引位置开始截取,截取到endIndex索引位置,得到新的字符串并返回(含头不含尾)
*/
import java.util.Scanner;
public class Test{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
System.out.print("请输入手机号:");
String s = sc.nextLine();
String begin = s.substring(0, 3);
String end = s.substring(7, 11);
System.out.println("最终手机号为:" + begin + "****" + end);
}
}
// 需求:键盘录入一个字符串,如果字符串中包含(TMD),则使用***替换
/*
String replace(CharSequence target, CharSequence replacement)
将当前字符串中的target(被替换的旧值)内容,使用replacement(替换的新值)进行替换
返回新的字符串
*/
import java.util.Scanner;
public class Test{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
System.out.print("请输入内容:");
String s = sc.nextLine();
String result = s.replace("TMD", "***");
System.out.println("您输入的内容为:" + result);
}
}
// 需求:以字符串的形式从键盘录入学生信息,例如:"张三,23"从该字符串中切割出有效数据,封装为Student学生对象
// String[] split(String regex):根据传入的字符作为规则进行切割,将切割后的内容存入字符串数组中,并将字符串返回(数组)
11
11
// 新建一个Student类
public class Student{
private String name;
private String age;
public Student(){}
public Student(String name, String age){
this.name = name;
this.age = age;
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public String getAge(){
return age;
}
public void setAge(String age){
this.age = age;
}
}
//---------------------------下面为一个新的class文件----------------------------------------------
import java.util.Scanner;
public class Test{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
System.out.print("请输入您的信息:");
String stuInfo = sc.nextLine();
String[] sArr = stuInfo.split(",");
Student stu = new Student(sArr[0], sArr[1]);
System.out.println("姓名:" + stu.getName() + " 年龄:" + stu.get)
}
}
概述:StringBuilder 是一个可变的字符串类,我们可以把它看成是一个容器
作用:提高字符串的操作效率
构造方法:
public StringBuilder()
创建一个空白可变字符串对象,不含有任何内容
public StringBuilder(String str)
根据字符串的内容,来创建可变字符串对象
常用方法:
public StringBuilder append(任意类型)
添加数据,并返回对象本身
public StringBuilder reverse()
返回相反的字符序列
public int length()
返回长度(字符出现的个数)
public String toString()
通过toString()就可以实现把StringBuilder转换为String
StringBuilder sb = new StringBuilder();
//链式编程:如果一个方法返回的是对象类型,对象就可以继续向下调用方法
sb.append("red").append("blue").append("green");
StringBuilder 提高效率原理:
当String类型字符串以 + 拼接时,系统默认在堆内存中new一个StringBuilder类型对象,通过append()方法完成拼接,再通过toString()将StringBuilder类型转换为String类型。
而使用StringBuilder则可以省去不必要的步骤
案例
// 需求:键盘接收一个字符串,程序判断出该字符串是否是对称字符串,并在控制台打印是或不是
// 对称字符串:123321、111 非对称字符串:123123
import java.util.Scanner;
public class Test{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
System.out.print("请输入内容:");
String s = sc.nextLine();
StringBuilder ss = new StringBuilder(s);
String sss = ss.reverse().toString();
if(s.equals(sss)){
System.out.println("是");
}else{
System.out.println("不是");
}
}
}
// 需求:定义一个方法,把int数组中的数据按照指定的格式拼接成一个字符串返回,调用该方法,并在控制台输出结果。
// 例如:数组为:int[] arr = {1, 2, 3}; 执行方法后的输出结果为:[1,2,3]
public static void main(String[] args){
int[] arr = {1, 2, 3};
String s = arrayToString(arr);
System.out.println(s);
}
// 定义一个方法,返回值类型 String ,参数列表int[] arr
public static String arrayToString(int[] arr){
StringBuilder sb = new StringBuilder("【");
for(int i = 0; i < arr.length; i++){
if(i == arr.length - 1){
sb.append(arr[i]).append("】");
}else{
sb.append(arr[i]).append(",");
}
}
return sb.toString();
}
// 需求:将(张三,23)(李四,24)(王五,25)封装为3个学生对象并存入数组,随后遍历数组,将学生信息输出
// 新建一个Student类
public class Student{
private String name;
private String age;
public Student(){}
public Student(String name, String age){
this.name = name;
this.age = age;
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public String getAge(){
return age;
}
public void setAge(String age){
this.age = age;
}
}
//---------------------------下面为一个新的class文件----------------------------------------------
public class Test{
public static void main(String[] args){
Student[] arr = new Student[3];
Student stu1 = new Student("张三","23");
Student stu2 = new Student("李四","24");
Student stu3 = new Student("王五","25");
arr[0] = stu1;
arr[1] = stu2;
arr[2] = stu3;
for(int i = 0; i < arr.length; i++){
System.out.println("姓名:" + arr[i].getName() + " 年龄:" + arr[i].getAge());
}
}
}
构造方法:ArrayList()
构造一个初始容量为10的空列表
成员方法:
boolean add(E e)
将指定的元素添加到此列表的尾部
void add(int index, E element)
将指定的元素插入此列表中的指定位置
如果没有进行数据类型的限制,可以添加任何数据类型的数据
如果要进行限制,格式为:
ArrayList<数据类型> list = new ArrayList<数据类型>();
JDK7版本以后,可写为:
ArrayList<数据类型> list = new ArrayList<>();
:是一种特殊的数据类型,泛型。<>里面只能写引用数据类型,int、double等基本数据类型不行
public boolen remove(Object)
删除指定的元素,返回是否删除成功,只能删除第一个匹配到的元素
public E remove(int index)
删除指定索引处的元素,返回被删除的元素
public E set(int index, E element)
修改指定索引处的元素,返回被修改的元素
public E get(int index)
返回指定索引处的元素
public int size()
返回集合中的元素的个数
// 需求:创建一个存储字符串的集合,存储3个字符串元素,使用程序实现在控制台遍历该集合
import java.util.ArrayList;
public class Test{
public static void main(String[] args){
ArrayList<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
for(int i = 0; i < list.size(); i++){
System.out.println(list.get(i));
}
}
}
// 需求:创建一个存储学生对象的集合,存储3个学生对象,使用程序实现在控制台遍历该集合
// 新建一个Student类
public class Student{
// 成员变量
private String name;
private int age;
// 构造方法
public Student(){}
public Student(String name, int age){
this.name = name;
this.age = age;
}
//成员方法
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return age;
}
}
//---------------------------下面为一个新的class文件----------------------------------------------
import java.util.ArrayList;
public class Test{
public static void main(String[] args){
ArrayList<Student> list = new ArrayList<>();
Student stu1 = new Student("张三",23);
Student stu2 = new Student("李四",24);
Student stu3 = new Student("王五",25);
list.add(stu1);
list.add(stu2);
list.add(stu3);
for(int i = 0; i < list.size(); i++){
System.out.println("姓名:" + list.get(i).getName() + " 年龄:" + list(i).getAge());
}
}
}
// 需求:创建一个存储String的集合,内部存储(test,张三,李四,test,test)字符串,删除所有的test字符串,将删除后的集合元素打印到控制台
import java.util.ArrayList;
public class Test{
public static void main(String[] args){
ArrayList list = new ArrayList();
list.add("test");
list.add("张三");
list.add("李四");
list.add("test");
list.add("test");
for(int i = 0; i < list.size(); i++){
if("test".equals(list.get(i))){ // 变量和常量调用方法,尽量常量去调用,减少不必要的错误
list.remove(i);
i--;
}
}
System.out.println(list);
}
}
// 需求:定义一个方法,方法接收一个集合对象(泛型为Student),方法内部将年龄低于18的学生对象找出并存入新集合对象,方法返回新集合
// 新建student类步骤省略
import java.util.ArrayList;
public static void main(String[] args){
Student stu1 = new Student("张三",23);
Student stu2 = new Student("李四",16);
Student stu3 = new Student("王五",22);
Student stu4 = new Student("赵六",13);
Student stu5 = new Student("马七",18);
ArrayList<Student> list = new ArrayList<>();
list.add(stu1);
list.add(stu2);
list.add(stu3);
list.add(stu4);
list.add(stu5);
ArrayList<Student> newList = getList(list);
for(int i = 0; i < newList.size(); i++){
System.out.println("姓名:" + newList.get(i).getName() + " 年龄:" + newList.get(i).getAge());
}
}
public static ArrayList<Student> getList(ArrayList<Student> list){
ArrayList<Student> newList = new ArrayList<>();
for(int i = 0; i < list.size(); i++){
if(list.get(i).getAge() < 18){
newList.add(list.get(i));
}
}
return newList;
}
实现思路
定义学生类
public class Student{
// 成员变量
private int sid; //学号
private String name; //姓名
private int age; //年龄
private String birthday; //生日
// 构造方法
public Student(){}
public Student(int sid, String name, int age, String birthday){
this.sid = sid;
this.name = name;
this.age = age;
this.birthday = birthday;
}
// 成员方法
public void setSid(int sid){
this.sid = sid;
}
public int getSid(){
return sid;
}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return age;
}
public void setBirthday(String birthday){
this.birthday = birthday;
}
public String getBirthday(){
return birthday;
}
}
主界面代码编写
import java.util.Scanner;
import java.util.ArrayList;
public class studentManager {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
ArrayList<Student> list = new ArrayList<>(); // 创建一个容器
// 搭建主界面菜单
lo: //添加一个标号,配合break完成退出操作
while (true) {
System.out.println("-------欢迎来到学生信息管理系统-------");
System.out.println("1.添加学生");
System.out.println("2.删除学生");
System.out.println("3.修改学生");
System.out.println("4.查看学生");
System.out.println("5.退出");
System.out.println("---------------------------------");
System.out.print("请输入您的选择:");
int choice = sc.nextInt();
switch (choice) {
case 1:
addStudent(list);
break;
case 2:
delStudent(list);
break;
case 3:
System.out.println("修改");
break;
case 4:
queryStudent(list);
break;
case 5:
System.out.println("您已退出,感谢您的使用");
break lo;
default:
System.out.println("您的输入有误,请重新输入");
break;
}
}
}
}
添加学生的代码编写
// addStudent
public static void addStudent(ArrayList<Student> list){
Scanner sc = new Scanner(System.in);
int sid; // 写在while外面,方便整个addSt
while(true){
System.out.print("请输入学号:");
sid = sc.nextInt();
int index = getIndex(list, sid);
if(index == -1){
break;
}else{
System.out.println("该学号已存在,请重新输入");
}
}
System.out.print("请输入姓名:");
String name = sc.next();
System.out.print("请输入年龄:");
int age = sc.nextInt();
System.out.print("请输入生日:");
String birthday = sc.next();
Student stu = new Student(sid, name, age, birthday);
list.add(stu);
System.out.println("恭喜您,添加成功!");
}
查看学生的代码编写
// queryStudent
public static void queryStudent(ArrayList<Student> list){
if(list.size() == 0){
System.out.println("当前无数据,请添加数据后重试");
}
else{
System.out.println("学号\t\t\t姓名\t\t年龄\t\t生日");
for(int i = 0; i < list.size(); i++){
Student stu = list.get(i);
System.out.println(stu.getSid() + "\t\t\t" + stu.getName() + "\t\t" + stu.getAge() + "\t\t" + stu.getBirthday());
}
}
}
删除/修改学生学号不存在问题
// 定义一个方法(getIndex),用于从集合中查找【学号】在【集合】中出现的索引位置
public static int getIndex(ArrayList<Student> list, int sid){
int index = -1; //假设传入的学号不存在
for(int i = 0; i < list.size(); i++){
Student stu = list.get(i);
int id = stu.getSid();
if(id == sid){
index = i;
}
}
return index;
}
删除学生的代码编写
// delStudent
public static void delStudent(ArrayList<Student> list){
Scanner sc = new Scanner(System.in);
System.out.print("请输入您要删除的学生学号:");
int delSid = sc.nextInt();
int index = getIndex(list, delSid);
if(index == -1){
System.out.println("查无信息,请重新输入");
}else{
list.remove(index);
System.out.println("恭喜您,删除成功!");
}
}
修改学生的代码编写
// updateStudent
public static void updateStudent(ArrayList<Student> list){
Scanner sc = new Scanner(System.in);
System.out.print("请输入您要修改的学生学号:");
int updateSid = sc.nextInt();
int index = getIndex(list, updateSid);
if(index == -1){
System.out.println("查无信息,请重新输入");
}else{
System.out.print("请输入新的学生姓名:");
String name = sc.next();
System.out.print("请输入新的学生年龄:");
int age = sc.nextInt();
System.out.print("请输入新的学生生日:");
String birthday = sc.next();
Student stu = new Student(updateSid, name, age, birthday);
list.set(index, stu);
System.out.println("恭喜您,修改成功!");
}
}
工作目录---->暂存区---->本地历史仓库
可视化操作
分支:由每次提交的代码,串成的一条时间线
特点:两条时间线,并行工作,互不打扰,多条时间线可以合并
使用场景:
使用分支意味着你可以把你的工作从开发主线上分离开来,以免影响开发主线
工作流程
创建新分支:git branch 分支名
切换分支: git checkout 分支名
查看文件命令:
ls
查看分支列表命令:git branch
不同分支之间是平行关系,在工作时不会相互影响
合并分支::git merge 分支名
删除分支:git branch -d 分支名
GitHub:
域名:https://github.com
介绍:GitHub是全球最大的开源项目托管平台,俗称大型程序员社区化交友网站
各类好玩有趣的开源项目,只有想不到,没有找不到
码云:
域名:https://gitee.com
介绍:码云是全国最大的开源项目托管平台,良心平台,速度快,提供免费私有库
远程仓库平台操作
情况:
1.先有本地仓库,远程仓库为空
2.先有远程仓库,本地仓库为空
情况1
步骤:
创建远程仓库
将项目从本地仓库,推送到远程仓库
**注意:**在推送代码之前,需要先配置SSH公钥
生成SSH公钥步骤:
- 设置Git账户
查看git账户:git config user.name
查看git邮箱:git config user.email
设置全局账户名和邮箱:git config --global user.name "账户名"
git config --global user.email "邮箱"
- 生成SSH公钥
查看是否生成过SSH公钥:cd ~/.ssh
生成SSH:ssh-keygen -t rsa -C "邮箱"
这里需要敲击三次回车
查看SSH:cat ~/.ssh/id_rsa.pub
- 设置账户公钥(在远程仓库平台设置)
- 公钥测试:
ssh -T [email protected]
推送到远程平台
步骤:
git remote add 远程名称 远程仓库URL
git push -u 仓库名称 分支名
情况2:
步骤:
git clone 仓库地址
git pull 远程仓库名 分支名
<<<<<<<和>>>>>>>>>中间的内容,就是冲突部分
IDEA中配置Git
版本切换
Reset Current Branch to Here… 会抛弃原来的提交记录
Revert Commit 需要进行版本冲突处理,历史记录还保留提交记录
远程仓库操作
分包思想:如果将所有的类文件都放在同一个包下,不利于管理和后期维护
所以,对于不同功能的类文件,可以放在不同的包下进行管理
包:本质上就是文件夹
创建包:(单级包、多级包)
多级包之间使用"."进行分割
多级包的定义规范:公司的网站地址翻转(去掉www)
比如:黑马程序员的网站为www.itheima.com
后期所定义的包结构就是:com.itheima.其他的包名。
包的命名规则:英文字母都是小写
包的定义
package
关键字定义包package 包名;
如果是多级包,中间用"."进行分割包的注意事项
类与类之间的访问
注意事项
import、package、class三个关键字的摆放位置存在顺序关系
需求说明
环境搭建
创建模块:itheima-edu-info-manager
创建包、创建类
包 | 存储的类 | 作用 |
---|---|---|
com.itheima.edu.info.manager.domain | Student.java | 封装学生信息 |
com.itheima.edu.info.manager.dao | StudentDao.java | 访问存储数据的数组,进行增删改查(库管) |
com.itheima.edu.info.manager.service | StudentService.java | 业务的逻辑处理(业务员) |
com.itheima.edu.info.manager.controller | StudentController.java | 和用户打交道(客服接待) |
com.itheima.edu.info.manager.entry | InfoManagerEntry.java | 程序的入口类,提供一个main方法 |
思路分析:
添加学生
StudentController(客服接待):
1. 方法中接收用户输入的信息
2. 将学生信息封装为学生对象并传递给StudentService
3. 接收方法的boolean类型返回值,根据结果在控制台打印添加成功/添加失败
StudentService(业务员):
StudentDao(库管):
static关键字
static关键字是静态的意思,修饰符,可以修饰成员变量、成员方法
被static修饰的成员变量,一般叫做静态变量
被static修饰的成员方法,一般叫做静态方法
public class 子类名 extends 父类名{}
public class Zi extends Fu{}
优点
弊端
继承是侵入性的
降低了代码的灵活性
继承关系,导致子类必须拥有父类非私有属性和方法,让子类自由的世界中多了些约束
增强了代码的耦合性
耦合性:代码与代码之间存在的关联都可以称之为“耦合”。
应用场景
Java继承的特点
访问特点
注意:如果子父类中,出现了重名的成员变量,通过就近原则,会优先使用子类的
如果一定要使用父类的,可以通过super
关键字进行区分
super关键字
super
和this
关键字的用法相似概述:在继承体系中,子类出现了和父类中一模一样的方法声明
应用场景
区别
方法重写:在继承体系中,子类出现了和父类一模一样的方法声明(方法名,参数列表,返回值类型)
方法重载:在同一个类中,方法名相同,参数列表不同,与返回值无关
注意事项
父类中私有方法不能被重写
父类静态方法,子类必须通过静态方法进行重写,父类非静态方法,子类也必须通过非静态方法进行重写
静态方法不能被重写!如果子类中,也存在一个方法声明一模一样的方法,可以理解为,子类将父类中同名的方法,隐藏了起来,并非是方法重写!
子类重写父类方法时,访问权限必须大于等于父类
修饰符 | 同一个类中 | 同一个包中子类无关类 | 不同包的子类 | 不同包的无关类 |
---|---|---|---|---|
private | √ | |||
默认 | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
子类中所有的构造方法默认都会访问父类中无参的构造方法
子类在初始化的时候,有可能会使用到父类中的数据,如果父类没有完成初始化,子类将无法使用父类的数据(子类在初始化前,一定要先完成父类的初始化)
构造方法的第一条语句默认都是:super();
注意:如果我们编写的类,没有手动指定父类,系统也会自动继承Object(Java继承体系中的最顶层父类)
访问特点
public abstract 返回值类型 方法名(参数列表);
public abstract class 类名{}
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。
使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。
通俗来说就是写代码的风格
模板设计模式:把抽象整体就可以看做成一个模板,模板中不能决定的东西定义成抽象方法
让使用模板的类(继承抽象类的类)去重写抽象方法实现需求
优势:模板已经定义了通用结构,使用者只需要关心自己需要实现的功能即可
final修饰的特点
修饰方法:表明该方法是最终方法,不能被重写
修饰变量:表明该变量是常量,不能再次被赋值
常量的命名规范:如果是一个单词,所有字母大写,如果是多个单词,所有字母大写,但是中间需要使用下划线_分隔
修饰基本数据类型变量:其值不能被更改
修饰引用数据类型变量:地址值不能被更改,但是可以修改对象的属性值
final修饰成员变量,需要在创建的时候直接赋值或者在构造方法结束之前完成赋值
修饰类:表明该类是最终类,不能被继承
概述:当一个类中所有方法都是抽象方法的时候,我们就可以将其定义为接口
接口也是一种引用数据类型,它比抽象类还要抽象
意义
定义和特点
public interface 接口名{}
public class 类名 implements 接口名{}
public class 类名 implements 接口名1,接口名2{}
成员的特点
成员变量
只能是常量,系统会默认加入三个关键字:public
、static
、final
构造方法
接口中不存在构造方法
成员方法
只能是抽象方法(JDK7以前),系统会默认加入两个关键字:public
、abstract
JDK8中接口成员的特点
允许接口中定义非抽象方法,但是需要使用关键字
default
修饰,这些方法就是默认方法定义格式:
public default 返回值类型 方法名(参数列表){}
默认方法不是抽象方法,所以不强制重写,但是可以被重写,重写的时候去掉default关键字
public可以省略,但是default不能省略
如果实现了多个接口,多个接口中存在相同的方法声明,子类就必须对该方法进行重写
JKD9中接口成员的特点
接口中私有方法的定义格式:
private 返回值类型 方法名(参数列表){}
private static 返回值类型 方法名(参数列表){}
使用思路
类和接口的关系
变量名 instanceof 类型
位置:在类的成员位置
创建内部类对象的格式:外部类名.内部类名 对象名 = new 外部类对象(). new 内部类对象()
内部类的访问特点
也属于成员,可以被一些修饰符修饰
外部类名.内部类名 对象名 = new 外部类名.内部类名();
外部类名.内部类名.方法名();
概述:局部内部类是在方法中定义的类,所以外界无法直接使用,需要在方法内部创建对象并使用,该类可以直接访问外部类的成员,也可以访问方法内的局部变量
位置:类的局部位置
概述:本质上是一个特殊的局部内部类(定义在方法内部)[使用次数远远大于成员内部类和局部内部类]
前提:需要存在一个接口或类
格式
new 类名或者接口名(){
重写方法;
}
理解:将继承\实现,方法重写,创建对象三个步骤放在了一步进行
使用场景
当方法的形式参数是接口或者抽象类时,可以将匿名内部类作为实际参数进行传递
理解:对于Lambda表达式,是对匿名内部类进行了优化,代码更少,关注点更加明确
函数式编程思想概述
在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿数据做操作”
面向对象思想强调“必须通过对象的形式来做事情”
函数式思想则尽量忽略面向对象的复杂语法:“强调做什么,而不是以什么形式去做”
而我们要学习的Lambda表达式就是函数式思想的体现
标准格式
(形式参数) -> {代码块}
->
:由英文中画线和大于符号组成,固定写法,代表指向动作使用前提
练习
// 1.编写一个接口(ShowHandler)
// 2.在该接口中存在一个抽象方法(show),该方法是无参数无返回值
// 3.在测试类(ShowHandlerDemo)中存在一个方法(useShowHandler)
// 方法的参数是ShowHandler类型的
// 在方法内部调用了ShowHandler的show方法
public class TestLambda{
public static void main(String[] args){
// 匿名内部类实现
useShowHandler(new ShowHandler(){
@Override
public void show(){
System.out.println("我是匿名内部类中的show方法");
}
});
// Lambda实现
useShowHandler(() -> {System.out.println("我是匿名内部类中的show方法");});
}
public static void useShowHandler(ShowHandler showHandler){
showHandler.show();
}
}
interface ShowHandler{
void show();
}
// 1.首先存在一个接口(StringHandler)
// 2.在该接口中存在一个抽象方法(printMessage),该方法是有参数无返回值
// 3.在测试类(StringHandlerDemo)中存在一个方法(useStringHandler),方法的参数是StringHandler类型的,在方法内部调用了StringHandler的printMessage方法
public class StringHandlerDemo{
public static void main(String[] args){
// 匿名内部类实现
useStringHandler(new StringHandler(){
@Override
public void printMessage(String msg){
System.out.println("我是匿名内部类." + msg);
}
});
// Lambda实现
useStringHandler((String msg) -> {System.out.println("我是Lambda" + msg);});
}
public static void useStringHandler(StringHandler stringHandler){
stringHandler.printMessage("坚持学习!");
}
}
interface StringHandler{
void printMessage(String msg);
}
省略规则
Lambda表达式和匿名内部类的区别
所需类型不同
使用限制不同
实现原理不同
Math包含执行基本数字运算的方法
没有构造方法,所有成员都是静态的(static),可以直接通过类名调用
常用方法
方法名 | 说明 |
---|---|
public static int abs(int a) | 返回参数的绝对值 |
public static double ceil(double a) | 向上取整 |
public static double floor(double a) | 向下取整 |
public static int round(float a) | 四舍五入 |
public static int max(int a,int b) | 返回两个int值中的较大值 |
public static int min(int a,int b) | 返回两个int值中的较小值 |
public static double pow(double a,double b) | 返回a的b次幂的值 |
public static double random() | 返回值为double的随机值 [0.0 , 1.0) |
概述
不能被实例化(不能被创建对象)
常用方法
方法名 | 说明 |
---|---|
public static void exit(int status) | 终止当前运行的Java虚拟机,非0表示异常终止 |
public static long currentTimeMillis() | 返回当前时间(以毫秒为单位) |
arraycopy(数据源数组,起始索引,目的地数组,起始索引,拷贝个数) | 数组copy |
public Object();
方法名 | 说明 |
---|---|
public String toString() | 返回对象的字符串表示形式。建议所有子类重写该方法,自动生成 |
public boolean equals(另一个对象) | 比较对象是否相等。默认比较地址,重写可以比较内容,自动生成 |
public class InterviewTest{
public static void main(String[] args){
String s1 = "abc";
StringBuilder sb = new StringBuilder("abc");
System.out.println(s1.equals(sb)); // false
// 此时调用的是String类中的equals方法
// 会先判断是否是字符串,若不是字符串则不会比较属性值,直接返回false
System.out.println(sb.equals(s1)); // false
// 此时调用的是StringBuilder中的equals方法,StringBuilder并没有该方法,则继承Object中的equals方法
// 即比较地址值,返回false
}
}
无构造方法,但所有成员方法都是静态方法(static),可以直接根据类名调用
常用方法
方法名 | 说明 |
---|---|
public static String toString(对象) | 返回参数中对象的字符串表示形式 |
public static String toString(对象,默认字符串) | 返回对象的字符串表示形式,如果对象为空,返回默认字符串 |
public static Boolean isNull(对象) | 判断对象是否为空 |
public static Boolean nonNull(对象) | 判断对象是否不为空 |
方法名 | 说明 |
---|---|
BigDecimal(double val) | 参数为double |
BigDecimal(String val) | 参数为String |
作用:可以用来精确计算(如果想要精确运算,请使用字符串构造)
方法名 | 说明 |
---|---|
public BigDecimal add(另一个BigDecimal对象) | 加法 |
public BigDecimal subtract(另一个BigDecimal对象) | 减法 |
public BigDecimal multiply(另一个BigDecimal对象) | 乘法 |
public BigDecimal divide(另一个BigDecimal对象) | 除法 |
public BigDecimal divide(另一个BigDecimal对象,精确几位,舍入模式) | 除法 |
舍入模式:
进一法:ROUND_UP
去尾法:ROUND_FLOOR
四舍五入:ROUND_HALE_UP
将基本数据类型封装为对象的好处在于可以在对象中定义更多的功能方法操作该数据
常用的操作之一:用于基本数据类型与字符串之间的转换
基本数据类型 包装类 byte Byte short Short int Integer long Long float Float double Double char Character boolean Boolean
构造方法
public Integer(int value)
根据int创建Integer对象**(过时)**
public Integer(String s)
根据String值创建Integer对象**(过时)**
public static Integer valueof(int i)
返回表示指定的int值的Integer实例
public static Integer valueof(String s)
返回一个保存指定值的Integer对象String
自动装箱
Integer i1 = 100;
自动拆箱
int i - i1;
i1为Integer对象成员方法
static int parseInt(String s)
将字符串类型的整数变成int类型的整数
// int 转换为 String // 方式一:+ "" int i1 = 100; String s1 = i1 + ""; // 方式二:可以调用String类中的valueof方法 String s2 = String.valueof(i1);
练习
// 案例:字符串中数据的处理
// 需求:有一个字符串:"91 27 46 38 50",把其中的每一个数存到int类型的数组中
public class MyIntegerDemo{
public static void main(String[] args){
String s = "91 27 46 38 50";
// 获取字符串中的每一个数字
String[] strArr = s.split(" ");
// 创建一个int类型数组
int[] numberArr = new int[strArr.length];
// 把strArr中的数据进行类型转换并存入到int数组中
for(int i = 0; i < strArr.length; i++){
int number = Integer.parseInt(strArr[i]);
numberArr[i] = number;
}
// 遍历int类型的数组
for(int i = 0; i < numberArr.length; i++){
System.out.println(numberArr[i]);
}
}
}
前提:数组的元素按照大小、顺序排列
每次去掉一半的查找范围
步骤
定义两个变量,表示要查找的范围。默认min=0,max = 最大索引
循环查找,但是min <= max
计算出mid的值
判断mid位置的元素是否为要查找的元素,如果是直接返回对应索引
如果要查找的值在mid左半边,那么min值不变,max = mid - 1,继续下次循环查找
如果要查找的值在mid右半边,那么max值不变,max = mid + 1,继续下次循环查找
当min > max时,表示要查找的元素在数组中不存在,返回-1
public class MyBinarySearchDemo{
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int number = 10;
int index = binarySearchForIndex(arr, number);
System.out.println(index);
}
private static int binarySearchForIndex(int[] arr, int number) {
// 定义查找的范围
int min = 0;
int max = arr.length - 1;
// 循环查找 min <= max
while (min <= max) {
// 计算中间位置
int mid = (min + max) >> 1; // 右移一位,也是除以二的意思
// mid指向的元素 > number
if (arr[mid] > number) {
// 表示要查找的元素在左边
max = mid - 1;
}
// mid指向的元素 < number
else if (arr[mid] < number) {
// 表示要查找的元素在右边
min = mid + 1;
} else {
// mid指向的元素 == number
return mid;
}
}
// 如果min大于了max就表示元素不存在,返回-1
return -1;
}
}
public class MyBubbleSortDemo{
public static void main(String[] args){
int[] arr = {3, 5, 2, 1, 4};
bubbleSort(arr);
printArr(arr);
}
private static void bubbleSort(int[] arr){
// 循环排序
// 外层循环控制次数,比数组的长度少一次
for(int i = 0; i < arr.length - 1; i++){
// 内存循环就是实际比较的次数
for(int j = 0; j < arr.length - 1 - i; j++){
// -1为了让数组不越界,-i 是每轮结束后都会减少比较
if(arr[j] > arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
private static void printArr(int[] arr){
// 循环遍历
for(int i = 0; i < arr.length; i++){
System.out.print(arr[i] + " ");
}
System.out.println();
}
}
概述:以编程角度来看,递归指的是方法定义中调用方法本身的现象
解决问题的思路:把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算
递归解决问题要有两个内容
案例
// 求1~100的和(递归实现)
public class MyFactorialDemo1{
public static void main(String[] args){
int sum = getSum(100);
System.out.println(sum);
}
private static int getSum(int i){
if(i == 1){
return 1;
}else{
return i + getSum(i - 1); // i + (i - 1)的和
}
}
}
// 需求:用递归求5的阶乘,并把结果在控制台输出
public class MyFactorialDemo2{
public static void main(String[] args){
int result = getJc(5);
System.out.println(result);
}
private static int getJc(int i){
// 出口
if(i == 1){
return 1;
}else{
// 规则(递归下一次调用的,一定更接近出口)
return i * getJc(i - 1);
}
}
}
冒泡排序算法中,一次循环结束,就相当于确定了当前的最大值,也能确定最大值在数组中应存入的位置。
快速排序算法中,每一次递归时以第一个数为基准数,找到数组中所有比基准数小的。再找到所有比基准数大的。小的全部放左边,大的全部放右边,确定基准数的正确位置
public class MyQuiteSortDemo{
public static void main(String[] args) {
int[] arr = {6, 1, 2, 7, 9, 3, 4, 5, 10, 8};
quiteSort(arr, 0, arr.length - 1);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
private static void quiteSort(int[] arr, int left, int right) {
if (right < left) {
return;
}
// 记录两个值
int left0 = left;
int right0 = right;
// 计算出基准数
int baseNumber = arr[left0];
while (left != right) {
// 1.从右开始找比基准数小的
while (arr[right] >= baseNumber && right > left) {
right--;
}
// 2.从左开始找比基准数大的
while (arr[left] <= baseNumber && right > left) {
left++;
}
// 3.交换两个值的位置
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
}
// 4. 基准数归位
int temp = arr[left];
arr[left] = arr[left0];
arr[left0] = temp;
// 再次递归调用方法
quiteSort(arr, left0, left - 1);
quiteSort(arr, left + 1, right0);
}
}
Arrays类包含用于操作数组的各种方法
方法名 | 说明 |
---|---|
public static String toString(int[] a) | 返回指定数组的内容的字符串表示形式 |
public static void sort(int[] a) | 按照数字顺序排列指定的数组 |
public static int binarySearch(int[] a,int key) | 利用二分查找返回指定元素的索引 |
public static int binarySearch(int[] a,int key)
1.数组必须有序
2.如果查找的元素存在,那么返回的是这个元素实际的索引
3.如果要查找的元素不存在,那么返回的是(-插入点-1)
插入点:如果这个元素在数组中,他应该在的索引位置
Date代表了一个特定的时间,精确到毫秒
构造方法
方法名 | 说明 |
---|---|
public Date() | 创建一个Date对象,表示默认时间(电脑中的当前时间) |
public Date(long data) | 创建一个Date对象,表示指定时间(从计算机时间原点开始,过了指定毫秒后的时间) |
常用成员方法
方法名 | 说明 |
---|---|
public long getTime() | 获取时间对象的毫秒值 |
public void setTime(long time) | 设置时间,传递毫秒值 |
SimpleDateFormat可以对Date对象,进行格式化和解析
格式化:Date对象 ====》 2021年1月1日 00:00:00
解析:2021年1月1日 00:00:00 ====》 Date对象
常用的模式字母及对应关系
y ----- 年 M ----- 月 d ----- 日
H ----- 时 m ----- 分 s ----- 秒
2021-01-01 00:00:00 ------------- yyyy-MM-dd HH:mm:ss
构造方法
方法名 | 说明 |
---|---|
public SimpleDateFormat() | 构造一个SimpleDateFormat,使用默认格式 |
public SimpleDateFormat(String pattern) | 构造一个SimpleDateFormat,使用指定的格式 |
成员方法
方法名 | 说明 |
---|---|
public final String format(Date date) | 将日期格式化成日期/时间字符串 |
public Date parse(String source) | 从给定字符串的开始解析文本以生成日期 |
LocalDateTime创建方法
方法名 | 说明 |
---|---|
public static LocalDateTime now() | 获取当前的系统时间 |
public static LocalDateTime of(年,月,日,时,分,秒) | 使用指定年月日和时分秒初始化一个LocalDateTime对象 |
LocalDateTime获取方法
方法名 | 说明 |
---|---|
public int getYear() | 获取年 |
public int getMonthValue() | 获取月份(1-12) |
public int getDayOfMonth() | 获取月份中的第几天(1-31) |
public int getDayOfYear() | 获取一年中的第几天(1-366) |
public DayOfWeek getDayOfWeek() | 获取星期 |
public int getMinute() | 获取分钟 |
public int getHour() | 获取小时 |
LocalDateTime转换方法
方法名 | 说明 |
---|---|
public LocalDate toLocalDate() | 转换为一个LocalDate对象 |
public LocalTime toLocalTime() | 转换为一个LocalTime对象 |
LocalDateTime格式化和解析
方法名 | 说明 |
---|---|
public String format(指定格式) | 把一个LocalDateTime格式化成为一个字符串 |
public LocalDateTime parse(准备解析的字符串,解析格式) | 把一个日期字符串解析为一个LocalDateTime对象 |
public static DateTimeFormatter ofPattern(String pattern) | 使用指定的日期模板获取一个日期格式化器Date TimeFormatter对象 |
LocalDateTime增加或者减少时间的方法
方法名 | 说明 |
---|---|
public LocalDateTime plusYears(long years) | 添加(正数)或者减(负数)去年 |
public LocalDateTime plusMonths(long months) | 添加或者减去月 |
public LocalDateTime plusDays(long days) | 添加或者减去日 |
public LocalDateTime plusHours(long hours) | 添加或者减去时 |
public LocalDateTime plusMinutes(long minutes) | 添加或者减去分 |
public LocalDateTime plusSeconds(long seconds) | 添加或者减去秒 |
public LocalDateTime plusWeeks(long weeks) | 添加或者减去周 |
public LocalDateTime minusYears(long years)
减去或添加年,和上边方法相反
还有其他和上述方法类似的减去或添加方法
LocalDateTime修改方法
方法名 | 说明 |
---|---|
public LocalDateTime withYear(int year) | 直接修改年 |
public LocalDateTime withMonth(int month) | 直接修改月 |
public LocalDateTime withDayOfMonth(int dayOfMonth) | 直接修改日期(一个月中的第几天) |
public LocalDateTime withDayOfYear(int dayOfYear) | 直接修改日期(一年中的第几天) |
public LocalDateTime withHour(int hour) | 直接修改小时 |
public LocalDateTime withMinute(int minute) | 直接修改分钟 |
public LocalDateTime withSecond(int second) | 直接修改秒 |
Period类
方法名 | 说明 |
---|---|
public static Period between(开始时间,结束时间) | 计算两个“时间”的间隔 |
public int getYears() | 获得这段时间的年数 |
public int getMonths() | 获得次期间的月数 |
public int getDays() | 获得此期间的天数 |
public long toTotalMonths() | 获取此期间的总月数 |
Duration类
方法名 | 说明 |
---|---|
public static Durationbetween(开始时间,结束时间) | 计算两个“时间”的间隔 |
public long toSeconds() | 获得此时间间隔的秒 |
public int toMillis() | 获得此时间间隔的毫秒 |
public int toNanos() | 获得此时间间隔的纳秒 |
如果程序出现了问题,我们没有做任何处理,最终JVM会做默认的处理
格式:throws 异常类名;
注意:这个格式是写在方法的定义处,表示声明一个异常。
指告诉调用者,在调用的时候可能出现这样的异常,如果方法中没有出现异常,正常执行。如果真的出现了异常,则是交给调用者处理,若调用者没有处理,则最终还是交给JVM虚拟机处理。
如果声明的异常是一个运行时异常,那么声明的代码可以省略
如果声明的异常是一个编译时异常,则必须要在方法后面进行显示声明
格式:throw new 异常();
注意:这个格式是在方法内,表示当前代码手动抛出一个异常,下面的代码不再执行了。
throw和throws的区别
throws | throw |
---|---|
用在方法声明后面,跟的是异常类名 | 用在方法内,跟的是异常对象名 |
表示声明异常,调用该方法有可能会出现这样的异常 | 表示手动抛出异常对象,由方法体内的语句处理 |
意义
格式:
try{
可能出现异常的代码;
}catch(异常类名 变量名){
异常的处理代码;
}
优点:可以让程序继续向下执行。
常见问题
方法名 | 说明 |
---|---|
public String getMessage() | 返回此throwbale的详细消息字符串 |
public String toString() | 返回此可抛出的简短描述 |
public void printStackTrace() | 把异常的信息输出在控制台(红色字体) |
抛出 throw throws
捕获 try…catch…
案例:键盘录入数据
// 需求:
// 键盘录入学生的姓名和年龄,其中年龄为18-25岁
// 超出这个范围是异常数据不能赋值,需要重新录入,一直录入到正确为止
public class ExceptionDemo{
public static void main(String[] args){
Student s = new Student();
Scanner sc = new Scanner(System.in);
System.out.print("请输入姓名:");
String name = sc.nextLine();
String name = sc.nextLine();
while(true){
System.out.print("请输入年龄:");
String ageStr = sc.nextLine();
try{
int age = Integer.parseInt(ageStr);
s.setAge(age);
break;
}catch(NumberFormatException e){
System.out.println("请输入一个整数");
continue;
}catch(RuntimeException e){
System.out.println("请输入一个符合范围的年龄");
continue;
}
}
System.out.println(s);
}
}
// Student类
public class Student{
private String name;
private int age;
public Student();
public void setName(String name){
this.name = name;
}
public void setAge(int age){
if(age >= 18 && age <= 25){
this.age = age;
}else{
throw new RuntimeException("年龄超出了范围");
}
}
public String getName(){
return name;
}
public int getAge(){
return age;
}
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
方法名 | 说明 |
---|---|
boolean add(E e) | 添加元素 |
boolean remove(Object o) | 从集合中移除指定的元素 |
boolean removeif(Object o) | 根据条件进行删除 |
void clear() | 清空集合 |
boolean contains(Object o) | 判断集合中是否存在指定的元素 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 集合的长度,也就是集合中元素的个数 |
boolean hasNext()
:判断当前位置是否有元素可以被取出E next()
:获取当前位置的元素,并将迭代器对象移向下一个索引位置Iterator it = list.iterator();
增强for:简化数组和Collection集合的遍历
格式
for(元素数据类型 变量名 : 数组或者Collection集合){
// 在此处使用变量即可,该变量就是元素
}
// 数据类型一定是集合或者数组元素的类型
// 变量名在循环的过程中,依次表示集合或者数组中的每一个元素
注意点:在增强for中,修改第三方变量的值不会影响到集合中的元素
三种循环的使用场景
// 需求:创建一个Collection集合存储学生对象的集合,存储3个学生对象,使用程序实现在控制台遍历该集合
// ----------Student类--------------------
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 String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
// ----------Test类--------------------
import java.util.ArrayList;
import java.util.Iterator;
public class test {
public static void main(String[] args) {
ArrayList<Student> list = new ArrayList<>();
Student s1 = new Student("张三", 23);
Student s2 = new Student("李四", 16);
Student s3 = new Student("王五", 31);
list.add(s1);
list.add(s2);
list.add(s3);
// 迭代器遍历
Iterator<Student> it = list.iterator();
while (it.hasNext()) {
Student s = it.next();
System.out.println(s);
}
System.out.println("----------------------");
// 增强for遍历
for (Student s : list) {
System.out.println(s);
}
}
}
方法名 | 说明 |
---|---|
void add(int index, E element) | 在此集合中的指定位置插入指定的元素 |
E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
E set(int index, E element) | 修改指定索引处的元素,返回被修改的元素 |
E get(int index) | 返回指定索引处的元素 |
数组是一个查询快、增删慢的模型
查询数据通过地址值和索引定位,查询任意数据耗时相同,查询速度快
删除数据时,要将原始数据删除,同时后面每个数据前移,删除效率低
添加数据时,添加位置后的每个数据后移,再添加元素,添加效率极低
ArrayList:底层数据结构是数组,查询快、增删慢
LinkedList:底层数据结构是链表,查询慢、增删快
特有功能
方法名 | 说明 |
---|---|
public void addFirst(E e) | 在该列表开头插入指定的元素 |
public void addLast(E e) | 将指定的元素追加到此列表的末尾 |
public E getFirst() | 返回此列表中的第一个元素 |
public E getLast() | 返回此列表中的最后一个元素 |
public E removeFirst() | 从此列表中删除并返回第一个元素 |
public E removeLast() | 从此列表中删除并返回最后一个元素 |
如果一个类后面有
,表示这个类是一个泛型类
在创建泛型类对象时,必须要给这个泛型确定具体的数据类型
泛型的定义格式:
<类型>
:指定一种类型的格式、、、
泛型类的定义格式:修饰符 class 类名<类型>{ }
public class MyGenericityClass<E> {
private E element;
public E getElement() {
return element;
}
public void setElement(E element) {
this.element = element;
}
@Override
public String toString() {
return "MyGenericityClass [element=" + element + "]";
}
}
定义格式:修饰符 <类型> 返回值类型 方法名(类型 变量名){ }
范例:public
// 定义一个泛型方法,传递一个集合和四个元素,将元素添加到集合中并返回
public class GenericityMethod{
public static void main(String[] args){
addElement(new ArrayList<String>, "a", "b", "c", "d");
}
public static <T> ArrayList<T> addElement(ArrayList<T> list, T t1, T t2, T t3, T t4){
list.add(t1);
list.add(t2);
list.add(t3);
list.add(t4);
return list;
}
}
使用方式
泛型接口的定义格式:修饰符 interface 接口名<类型>{ }
public class GenericityInterface{
public static void main(String[] args){
GenericityImpl<String> genericity = new GenericityImpl<>();
genericity.method("ccccccc");
}
}
interface Genericity<E> {
public abstract void method(E e);
}
class GenericityImpl<E> implements Genericity<E>{
public void method(E e){
System.out.println(e);
}
}
类型通配符:>
ArrayList>:表示元素类型未知的ArrayList,它的元素可以匹配任何的类型
类型通配符上限: extends 类型>
ArrayList extends Number>
:它表示的类型是Number或者其子类型类型通配符下限: super 类型>
ArrayList super Number>
:他表示传进来的类型可以是Number类型,也可以是Number的父类类型import java.util.ArrayList;
public class tongpeifu {
public static void main(String[] args) {
// TODO Auto-generated method stub
ArrayList<Integer> list1 = new ArrayList<>();
ArrayList<String> list2 = new ArrayList<>();
list1.add(11);
list2.add("ssacs");
printList(list1);
printList(list2);
}
private static void printList(ArrayList<?> list) {
// TODO Auto-generated method stub
System.out.println(list);
}
}
Set集合概述和特点
Set集合练习
public class MySet{
public static void main(String args[]){
Set<String> set = new TreeSet<>();
set.add("ccc");
set.add("aaa");
set.add("aaa");
set.add("bbb");
// 迭代器遍历
Iterator<String> it = set.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
System.out.println("---------------------------");
// 增强for遍历
for(String s:set){
System.out.println(s);
}
}
}
特点
自然排序Comparable的使用
使用空参构造创建TreeSet集合
自定义的Student类实现Comparable接口
重写里面的compareTo方法
@Override
public int compareTo(Student o){
int result = this.age - o.age;
return result;
}
原理
比较器排序Comparator的使用
TreeSet的带参构造方法使用的是比较器排序对元素进行排序的
比较器排序,就是让集合构造方法接收Comparator的实现类对象,重写compare(T o1, T o2)方法
重写方法时,一定要注意排序规则必须要按照要求的主要条件和次要条件来写
@Override
public int compare(Teacher o1, Teacher o2){
// o1表示现在要存入的那个元素
// o2表示已经存入到集合中的元素
// 主要条件
int result = o1.getAge() - o2.getAge();
// 次要条件
result = result == 0 ? o1.getName().compareTo(o2.getName()) : result;
return result;
}
两种比较方式小结
红黑树是一种自平衡的二叉查找树,是计算机科学中用到的一种数据结构,又叫平衡二叉B树
它是一种特殊的二叉查找树,红黑树的每一个节点上都有存储位表示节点的颜色
每一个节点可以是红或者黑;红黑树不是高度平衡的,它的平衡是通过“红黑规则”进行实现的
红黑规则
添加节点
练习:创建3个学生对象,属性为姓名,语数英成绩,按照总分从小到大顺序打印到控制台
// Student类
public class Student implements Comparable<Student>{
private String name;
private int chinese;
private int english;
private int math;
public Student(String name, int chinese, int english, int math) {
super();
this.name = name;
this.chinese = chinese;
this.english = english;
this.math = math;
}
public Student() {
super();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getChinese() {
return chinese;
}
public void setChinese(int chinese) {
this.chinese = chinese;
}
public int getEnglish() {
return english;
}
public void setEnglish(int english) {
this.english = english;
}
public int getMath() {
return math;
}
public void setMath(int math) {
this.math = math;
}
public int getSum(){
int res;
res = chinese + math + english;
return res;
}
@Override
public String toString() {
return "Student [name=" + name + ", chinese=" + chinese + ", english=" + english + ", math=" + math + "]" + "总分为:" + getSum();
}
@Override
public int compareTo(Student o) {
// TODO Auto-generated method stub
int res = this.getSum() - o.getSum();
res = res == 0 ? this.getChinese() - o.getChinese() : res;
res = res == 0 ? this.getMath() - o.getMath() : res;
res = res == 0 ? this.getName().compareTo(o.getName()) : res;
return res;
}
}
// 测试类
import java.util.TreeSet;
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
TreeSet<Student> stu = new TreeSet<>();
Student s1 = new Student("李四", 80, 80, 80);
Student s2 = new Student("张三", 80, 80, 80);
Student s3 = new Student("王五", 80, 80, 80);
stu.add(s1);
stu.add(s2);
stu.add(s3);
for(Student student : stu){
System.out.println(student);
}
}
}
HashSet集合特点
基本使用
import java.util.HashSet;
import java.util.Iterator;
//import java.util.TreeSet;
public class Hash {
public static void main(String[] args) {
// TODO Auto-generated method stub
HashSet<String> hs = new HashSet<>();
hs.add("hello");
hs.add("world");
hs.add("java");
hs.add("java");
hs.add("java");
hs.add("java");
Iterator<String> it = hs.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
}
}
哈希值(哈希码值):是JDK根据对象的地址或者属性值,算出来的int类型的整数
public int hashCode()
)对象的哈希值特点
Map集合概述和使用
创建Map集合的对象
多态的方式
具体的实现类HashMap
import java.util.HashMap;
import java.util.Map;
public class Base {
public static void main(String[] args) {
// TODO Auto-generated method stub
Map<String, String> stu = new HashMap<>();
stu.put("2020033001", "张三");
stu.put("2020033002", "李四");
stu.put("2020033003", "王五");
System.out.println(stu);
}
}
Map集合的基本功能
方法名 | 说明 |
---|---|
V put(K key, V value) | 添加元素 |
V remove(Object key) | 根据键删除值对元素 |
void clear() | 移除所有的键值对元素 |
boolean containsKey(Object key) | 判断集合是否包含指定的键 |
boolean containsValue(Object value) | 判断集合是否包含指定的值 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 集合的长度,也就是集合中键值对的个数 |
put方法中,如果要添加的键不存在,则会把键值对都添加到集合中;如果键存在,则会把原先的值覆盖,并当作返回值返回
遍历Map集合
Map集合的获取功能
方法名 | 说明 |
---|---|
Set keySet() | 获取所有键的集合 |
V get(Object key) | 根据键获取值 |
Set |
获取所有键值对对象的集合 |
K getKey() | 获得键 |
V getValue() | 获得值 |
HashMap是Map里面的一个实现类。
没有额外需要学习的特有方法,直接使用Map里面的方法就可以了
HashMap和HashSet的底层原理都是哈希表结构
依赖hashCode方法和equals方法保证键的唯一
如果键要存储的是自定义对象,需要重写hashCode和equals方法
案例
需求:创建一个HashMap集合,键是学生对象(Student),值是籍贯(String)。存储三个键值对元素,并遍历
// Student类
public class Student {
private String name;
private int age;
public Student() {
super();
}
public Student(String name, int age) {
super();
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 String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
// 实现类
import java.util.HashMap;
import java.util.Set;
import java.util.Map;
public class HashDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
HashMap<Student,String> hm = new HashMap<>();
Student s1 = new Student("张三",23);
Student s2 = new Student("李四",22);
Student s3 = new Student("王五",22);
hm.put(s1, "江苏");
hm.put(s2, "西安");
hm.put(s3, "北京");
// 遍历方式一:获取所有的键,再找对应的值
Set<Student> keys = hm.keySet();
for(Student key : keys){
String value = hm.get(key);
System.out.println(key + "------" + value);
}
System.out.println("====================");
// 遍历方式二:先获取到所有的键值对对象,再获取到里面的每一个键和值
Set<Map.Entry<Student, String>> entries = hm.entrySet();
for(Map.Entry<Student, String> entry: entries){
Student key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "------" + value);
}
System.out.println("====================");
// 遍历方式三:
hm.forEach(
(Student key, String value)->{
System.out.println(key + "-----" + value);
}
);
}
}
TreeMap是Map里面的一个实现类
没有额外需要学习的特有方法,直接使用Map里面的方法就可以了
TreeMap和TreeSet一样,底层都是红黑树结构的
依赖自然排序或者比较器排序,对键进行排序
如果键存储的是自定义对象,需要实现Comparable接口或者在创建TreeMap对象时侯给出比较器排序规则
练习:创建一个TreeMap集合,键是学生对象(Student),值是籍贯(String)
学生属性姓名和年龄,按照年龄进行排序并遍历
// Student类
public class Student implements Comparable<Student>{
private String name;
private int age;
public Student() {
super();
}
public Student(String name, int age) {
super();
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 String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
@Override
public int compareTo(Student o){
// 按照年龄进行排序
int result = this.getAge() - o.getAge();
// 次要条件,按照姓名排序
result = result == 0 ? this.getName().compareTo(o.getName()) : result;
return result;
}
}
// 实现类
import java.util.TreeMap;
public class TreeMapTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
TreeMap<Student, String> tm = new TreeMap<>();
Student s1 = new Student("张三",23);
Student s2 = new Student("李四",22);
Student s3 = new Student("王五",22);
tm.put(s1, "江苏");
tm.put(s2, "北京");
tm.put(s3, "天津");
tm.forEach(
(Student key, String value)->{
System.out.println(key + "----" + value);
}
);
}
}
// 需求:定义一个方法求N个数的和
// 在JDK5之前,会把所有的数据都先放到一个数组中,自定义方法形参只要写一个数组就可以了
public static void main(String[] args){
int[] arr = {1, 2, 3, 4, 5};
int sum1 = getSum(arr);
System.out.println(sum1);
}
public static int getSum(int[] arr){
int sum = 0;
for(int i = 0; i < arr.length; i++){
sum += arr[i];
}
return sum;
}
可变参数:就是形参的个数是可以变化的
格式:修饰符 返回值类型 方法名(数据类型...变量名){ }
注意事项:
public static void main(String[] args){
int sum = getSum(1, 2, 3, 4, 5, 6, 7);
System.out.println(sum);
}
public static int getSum(int...arr){
int sum = 0;
for(int i = 0; i < arr.length; i++){
sum += arr[i];
}
return sum;
}
创建不可变集合
方法名 | 说明 |
---|---|
static List of(E…elements) | 创建一个具有指定元素的List集合对象 |
static Set of(E…elements) | 创建一个具有指定元素的Set集合对象 |
static |
创建一个具有指定元素的Map集合对象 |
ArrayList list = new ArrayList<>(List.of("a", "b", "c", "d"));
集合对象.stream();
default Stream stream()
集合对象.keySet().stream();
集合对象.entrySet().stream();
Arrays.stream(数组名);
在Stream流中无法直接修改集合,数组等数据源中的数据
package Stream;
import java.util.ArrayList;
import java.util.stream.Stream;
public class Demo{
public static void main(String[] args){
ArrayList<String> manList = new ArrayList<>();
manList.add("张国立");
manList.add("张晋");
manList.add("刘烨");
manList.add("郑尹健");
manList.add("徐峥");
manList.add("王宝强");
ArrayList<String> womanList = new ArrayList<>();
womanList.add("郑爽");
womanList.add("杨紫");
womanList.add("关晓彤");
womanList.add("张天爱");
womanList.add("杨幂");
womanList.add("赵丽颖");
Stream<String> stream1 = manList.stream().filter(name -> name.length() == 3).limit(2);
Stream<String> stream2 = womanList.stream().filter(name -> name.startsWith("杨")).skip(1);
Stream.concat(stream1, stream2).forEach(name -> System.out.println(name));;
}
}
File:它是文件和目录路径名的抽象表示
方法名 | 说明 |
---|---|
File(String pathname) | 通过将给定的路径名字符串转换为抽象路径名来创建新的File实例 |
File(String parent, String child) | 从父路径名字符串和子路径名字符串创建新的File实例(路径拼接) |
File(File parent, String child) | 从父抽象路径名和子路径名字符串创建新的File实例(路径拼接) |
Q:为什么要把字符串表示形式的路径变为File对象?
A:就是为了使用File类里面的方法
File类创建功能
方法名 | 说明 |
---|---|
public boolean createNewFile() | 创建一个新的空的文件 |
public boolean mkdir() | 创建一个单级文件夹(了解) |
public boolean mkdirs() | 创建一个多级文件夹 |
createNewFile 只能创建文件; 如果文件存在,创建失败,返回false; 如果文件不存在,创建文件,返回true; 要求文件所在文件夹必须存在
mkdir 只能创建单级文件夹; 只能创建文件夹;
mkdirs 可以创建单击文件夹,也可以创建多级文件夹
创建文件夹的方法,mkdir只需要了解,掌握mkdirs即可
File类删除功能:public boolean delete()
删除由此抽象路径名表示的文件或目录
删除后不进回收站,不能恢复; 可以删除文件,也可以删除文件夹; 如果删除的是文件,直接删除;如果删除的是文件夹,只能删除空文件夹
File类判断和获取功能
方法名 | 说明 |
---|---|
public boolean isDirectory() | 测试此抽象路径名表示的File是否为目录 |
public boolean isFile() | 测试此抽象路径名表示的File是否为文件 |
public boolean exists() | 测试此抽象路径名表示的File是否存在 |
public String getName() | 返回由此抽象路径名表示的文件或目录的名称 |
getName方法:如果调用者是文件,那么获取的是文件名和后缀名;如果调用者是文件夹,那么获取的是文件夹的名字
File类高级获取功能:public File[] listFiles()
:返回此抽象路径名表示的目录中的文件和目录的File对象数组
当调用者是一个文件或不存在时,会返回一个null
当调用者是一个空文件夹时,会返回一个长度为0的数组
当调用者是一个有权限才能进入的文件夹时,会返回一个null
练习一:在当前模块下的aaa文件夹中创建一个a.txt文件
import java.io.File;
import java.io.IOException;
public class Demo1{
public static void main(String[] args) throws IOException{
File file = new File("File\\aaa");
if(!file.exists()){ // 如果文件夹不存在,创建文件夹
file.mkdirs();
}
File newFile = new File(file, "a.txt");
newFile.createNewFile(); // 要保证文件所在的文件夹必须存在
}
}
练习二:删除一个多级文件夹
import java.io.File;
public class Demo2{
public static void main(String[] args){
File src = new File("C:\\Users\\lihao\\Desktop\\cs");
deleteDir(src);
}
private static void deleteDir(File src){
// 递归思路
// 套路:
// 1.进入----得到src文件夹里面所有内容的File对象
File[] files = src.listFiles();
// 2.遍历----得到src文件夹里面每一个文件和文件夹的File对象
for(File file:files){
// 3.判断----如果遍历到的File对象是一个文件就直接删除
if(file.isFile()){
file.delete();
}else{ // 4.判断----如果遍历到的File对象是一个文件夹,递归
deleteDir(file); // 参数一定要是src文件夹里面的文件夹File对象
}
}
src.delete();
}
}
练习三:统计一个文件夹中每种文件的个数并打印
打印格式如下:
txt:3个
doc:4个
…
public class Demo3{
public static void main(String[] args){
File file = new file("File");
HashMap<String, Integer> hm = new HashMap<>();
getCount(hm, file);
System.out.println(h)
}
private static void getCount(HashMap<String, Integer> hm, File file){
File[] files = file.listFiles();
for(File f:files){
if(f.isFile()){
String fileName = f.getName();
String[] fileNameArr = fileName.split("\\.");
if(fileNameArr.length == 2){
String fileEndName = fileNameArr[1];
if(hm.containsKey(fileEndName)){
// 已经存在
// 将已经出现的次数获取出来
Integer count = hm.get(fileEndName);
// 该文件再次出现
count++;
hm.put(fileEndName, count);
}else{
// 不存在
// 当前文件是第一次出现
hm.put(fileEndName, 1);
}
}
}else{
getCount(hm, f);
}
}
}
}
步骤
FileOutputStream fos = new FileOutputStream("a.txt")
)fos.write(数据)
)fos.close();
)注意点
字节流写数据的3种方式
方法名 | 说明 |
---|---|
void write(int b) | 一次写一个数据 |
void write(byte[] b) | 一次写一个字节数组数据 |
void write(byte[] b, int off, int len) | 一次写一个字节数组的部分数据 |
两个小问题
字节流写完数据如何实现换行
写完数据后,加换行符:\r
\n
getBytes()
字节流写数据如何实现追加写入呢(保留原数据)?
在public FileOutputStream(String name, boolean append)
true表示续写开关,保留原数据;默认为false
try{
FileOutputStream fos = new FileOutputStream("a.txt");
fos.write(97);
fos.close();
}catch (IOException e){
e.printStackTrace();
}
上述方法在
fos.write(97);
出异常后,close方法将无法执行。那么如何操作才能让close方法一定执行呢?
finally:在异常处理时提供finally块来执行所有清除操作。比如说IO流中的释放资源
特点:被finally控制的语句一定会执行,除非JVM退出
异常处理标准格式:try...catch...finally
FileOutputStream fos = null;
try{
fos = new FileOutputStream("a.txt");
fos.write(97);
}catch (IOException e){
e.printStackTrace();
}finally{
//finally 中的代码一定会执行
if(fos != null){
try{
fos.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
步骤
FileInputStream fis = new FileInputStream("a.txt");
)
int read = fis.read();
)
fis.close();
)读取多个字节数据
当读取到文件结束时,会返回-1
int b;
while((b = fis.read()) != -1){
System.out.println((char)b);
}
fis.close();
需求:把"E:\\study\\a.txt" 复制到当前模块下
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class Demo2 {
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
FileInputStream fis = new FileInputStream("E:\\study\\test.txt");
FileOutputStream fos = new FileOutputStream("test.txt");
int b;
while((b = fis.read()) != -1){
fos.write(b);
}
fis.close();
fos.close();
}
}
如果操作的文件过大,那么速度就会很慢
为了解决速度问题,字节流通过创建字节数组,可以一次读写多个数据
一次读一个字节数组的方法:
public int read(byte[] b)
:从输入流读取最多b.length个字节的数据import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class Demo3 {
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
FileOutputStream fos = new FileOutputStream("a.test");
FileInputStream fis = new FileInputStream("E:\\study\\test.txt");
byte[] bytes = new byte[1024];
int len; // 本次读取到的有效字节个数-----这次读了几个字节
while((len = fis.read(bytes)) != -1){
fos.write(bytes, 0, len);
}
fis.close();
fos.close();
}
}
字节缓冲输出流:BufferedOutputStream(OutputStream out)
字节缓冲输入流:BufferedInputStream(InputStream in)
字节缓冲流仅仅提供缓冲区(数组),而真正的读写数据还得依靠基本的字节流对象进行操作
import java.io.*;
public class Demo1 {
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
// 创建字节缓冲输入流
// 在底层创建了一个默认长度为8192的字节数组
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\study\\test.txt"));
// 创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("test.txt"));
int b;
while((b = bis.read()) != -1){
bos.write(b);
}
// f
bis.close();
bos.close();
}
}
基础知识
ASCII字符集
GBK:windows系统默认的码表。兼容ASCII码表,也包含了21003个汉字,并支持繁体汉字以及部分日韩文字。
注意:GBK是中国的码表,一个中文以两个字节的形式存储。但不包含世界上所有国家的文字。
Unicode码表:由国际组织ISO制定,是统一的万国码,计算机科学领域里的一项业界标准,容纳世界上大多数国家的所有常见文字和符号
但是因为表示的字符太多,所有Unicode码表中的数字不是直接以二进制的形式存储到计算机的
会先通过UTF-7,UTF-7.5,UTF-8,UTF-16,以及UTF-32进行编码,再存储到计算机,其中最常见的就是UTF-8
注意:Unicode是万国码,以UTF-8编码后一个中文以三个字节的形式存储
重点:Windows默认使用码表为GBK,一个字符两个字节
idea和以后工作默认使用Unicode的UTF-8编解码格式,一个中文三个字节
byte[] getBytes()
:使用平台默认字符集将该String编码为一系列字节,将结果存储到新的字节数组中byte[] getBytes(String charsetName)
:使用指定的字符集将该String编码为一系列字节,将结果存储到新的字节数组中String(byte[] bytes)
:通过使用平台的默认字符集解码指定的字节数组来构造新的StringString(bytes,String charsetName)
:通过指定的字符集解码指定的字节数组来构造新的Stringimport java.io.UnsupportedEncodingException;
import java.util.Arrays;
public class Demo1 {
public static void main(String[] args) throws UnsupportedEncodingException {
// TODO Auto-generated method stub
String s = "好好学习,天天向上";
// 利用默认的GBK将中文编码为一系列的字节
byte[] bytes1 = s.getBytes();
System.out.println(Arrays.toString(bytes1));
// 指定为UTF-8编码格式
byte[] bytes2 = s.getBytes("UTF-8");
System.out.println(Arrays.toString(bytes2));
byte[] bytes3 = {-70, -61, -70, -61, -47, -89, -49, -80, -93, -84, -52, -20, -52, -20, -49, -14, -55, -49};
byte[] bytes4 = {-27, -91, -67, -27, -91, -67, -27, -83, -90, -28, -71, -96, -17, -68, -116, -27, -92, -87, -27, -92, -87, -27, -112, -111, -28, -72, -118};
String s1 = new String(bytes3);
String s2 = new String(bytes4,"UTF-8");
System.out.println(s1);
System.out.println(s2);
}
}
步骤
字符流写数据的5种方式
方法名 | 说明 |
---|---|
void write(int c) | 写一个字符 |
void write(char[] cbuf) | 写出一个字符数组 |
void write(char[] cbuf, int off, int len) | 写出字符数组的一部分 |
void write(String str) | 写一个字符串 |
void write(String str, int off, int len) | 写一个字符串的一部分 |
import java.io.FileWriter;
import java.io.IOException;
public class Demo2 {
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
// 创建字符输出流对象
FileWriter fw = new FileWriter("src\\CharStream\\a.txt");
// FileWriter fw = new FileWriter(new File("CharStream\\a.txt"));
// 写出数据(一次一个字符)
// method1(fw);
// 写一个字符数组
// method2(fw);
// 写出字符数组的一部分
// method3(fw);
// 写出一个字符串[重点]
// method4(fw);
// 写出一个字符串的一部分(了解)
// method5(fw);
// 释放资源
fw.close();
}
private static void method5(FileWriter fw) throws IOException {
fw.write("好好学习,天天向上", 3, 4);
}
private static void method4(FileWriter fw) throws IOException {
String line = "好好学习,";
fw.write(line);
fw.write("天天向上");
}
private static void method3(FileWriter fw) throws IOException {
char[] chars = {97, 98, 99, 100, 101, 102, 103};
fw.write(chars, 3, 4);
}
private static void method2(FileWriter fw) throws IOException {
char[] chars = {97, 98 ,99, 100, 101};
fw.write(chars);
}
private static void method1(FileWriter fw) throws IOException {
fw.write(97);
fw.write(98);
fw.write(99);
}
}
方法名 | 说明 |
---|---|
flush() | 刷新流,还可以继续写数据 |
close() | 关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据 |
步骤
FileReader fr = new FileReader("a.txt");
FileReader fr = new FileReader(new File("a.txt"));
fr.read();
fr.close();
一次读取多个字符
import java.io.FileReader;
import java.io.IOException;
public class Demo3 {
public static void main(String[] args) throws IOException{
// 创建对象
FileReader fr = new FileReader("src\\CharStream\\a.txt");
// 创建数组
char[] chars = new char[1024];
int len;
while((len = fr.read(chars)) != -1){
System.out.println(new String(chars, 0, len));
}
// 释放资源
fr.close();
}
}
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Scanner;
public class Demo4 {
public static void main(String[] args) throws IOException{
Scanner sc = new Scanner(System.in);
System.out.println("【用户注册】");
System.out.print("请输入用户名(6位):");
String userName = sc.next();
System.out.print("请输入密码(6位):");
String passWord = sc.next();
FileWriter fw = new FileWriter("user.txt");
fw.write(userName + "\n" + passWord);
fw.close();
System.out.println("注册成功!");
System.out.println("【用户登录】");
System.out.print("请输入用户名:");
String user = sc.next();
System.out.print("请输入密码:");
String pass = sc.next();
FileReader fr = new FileReader("user.txt");
char[] chars = new char[1024];
int len;
while((len = fr.read(chars)) != -1){
userName = new String(chars, 0, 6);
passWord = new String(chars, 7 , 6);
}
if(user.equals(userName) && pass.equals(passWord)){
System.out.println("登录成功!");
}else{
System.out.println("密码错误,登录失败!");
}
}
}
void newLine()
:写一行行分隔符,行分隔符字符串由系统属性定义(回车换行)public String readLine()
:读一行文字。结果包含行内的内容的字符串,不包括任何行终止字符,如果流的结尾已经到达,则为null需求:读取文件中的数据,排序后再次写到本地文件
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Arrays;
public class Demo5 {
public static void main(String[] args) throws IOException{
// 创建字符缓冲输入流对象
BufferedReader br = new BufferedReader(new FileReader("sort.txt"));
String s = br.readLine(); // 8 7 5 2 1 3 4 9 10 6
String[] n = s.split(" ");
int[] num = new int[n.length];
for(int i = 0; i < n.length; i++){
int number = Integer.parseInt(n[i]);
num[i] = number;
}
Arrays.sort(num);
br.close();
// 写入
BufferedWriter bw = new BufferedWriter(new FileWriter("sort.txt"));
for(int i = 0; i < num.length; i++){
bw.write(num[i] + " ");
bw.flush();
}
bw.close();
}
}
InputStreamReader("a.txt","utf-8");
FileReader fr = new FileReader("a.txt",charset.forName("UTF-8"));
特点:可以把对象以字节的形式写到本地文件,直接打开文件,是读不懂的,需要再次用对象操作流读到内存中
对象操作流分为两类
对象操作输出流(对象序列化流):ObjectOutputStream,就是将对象写到本地文件中,或者在网络中传输对象
对象操作输入流(对象反序列化流):ObjectInputStream,把写到本地文件中的对象读到内存中,或者接收网络中传输的对象
// User类
package Streamio;
import java.io.Serializable;
// 如果想要这个类的对象能被序列化,那么这个类就必须实现一个接口
public class User implements Serializable{
// 成员变量
private String userName;
private String passWord;
// 构造方法
public User() {
super();
}
public User(String userName, String passWord) {
super();
this.userName = userName;
this.passWord = passWord;
}
// 成员方法
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
@Override
public String toString() {
return "User [userName=" + userName + ", passWord=" + passWord + "]";
}
}
Serializable接口的意义:标记性接口,里面没有任何抽象方法,只要一个类实现类这个Serializable接口,那么就表示这个类的对象可以被序列化
// 实现类 对象操作输出流
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class Demo1 {
public static void main(String[] args) throws FileNotFoundException, IOException {
User user = new User("zhangsan","qwer");
// 需求:把这个用户信息保存到本地文件
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt"));
oos.writeObject(user);
oos.close();
}
}
// 实现类 写:对象操作输入流
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
public class Demo2 {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
// TODO Auto-generated method stub
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt"));
User o = (User) ois.readObject();
System.out.println(o);
ois.close();
}
}
private static final long serialVersionUID = 1L;
transient
修饰,该关键字标记的成员变量不参与序列化过程案例:用对象操作流读写多个对象
// 需求:创建多个JavaBean类对象写到文件中,再次读取到内存
// Student类
import java.io.Serializable;
public class Student implements Serializable{
private String name;
private int age;
private static final long serialVersionUID = 1L;
public Student() {
super();
}
public Student(String name, int age) {
super();
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 String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
}
// 实现类
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Demo3 {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException{
Student s1 = new Student("张三",23);
Student s2 = new Student("李四",24);
Student s3 = new Student("王五",25);
// 写入
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("b.txt"));
oos.writeObject(s1);
oos.writeObject(s2);
oos.writeObject(s3);
oos.close();
// 读出
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("b.txt"));
while(true){
try{
Student stu = (Student) ois.readObject();
System.out.println(stu);
} catch(IOException e){
break;
}
}
ois.close();
}
}
概述
练习:Properties作为Map集合的使用
package Properties;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
public class Demo1 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Properties prop = new Properties();
// 增
prop.put("唐三", "小舞");
prop.put("戴沐白", "朱竹青");
prop.put("奥斯卡", "宁荣荣");
// 删
prop.remove("戴沐白");
// 改
prop.put("奥斯卡", "朱竹青");
// 查
Object value = prop.get("唐三");
// 遍历
Set<Object> keys = prop.keySet();
for(Object key : keys){ // 所有的键
Object r = prop.get(key);
System.out.println(key + "=" + r);
}
// 所有的键值对对象
Set<Entry<Object, Object>> entries = prop.entrySet();
for(Entry<Object, Object> entry :entries){
Object key = entry.getKey();
Object val = entry.getValue();
System.out.println(key + "---" + val);
}
}
}
作为集合的特有方法
方法名 | 说明 |
---|---|
Object setProperty(String key, String value) | 设置集合的键和值,都是String类型,底层调用Hashtable方法 |
String getProperty(String key) | 使用此属性列表中指定的键搜索属性 |
Set stringPropertyNames() | 从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串 |
Properties和IO流结合的方法
方法名 | 说明 |
---|---|
void load(InputStream inStream) | 从输入字节流读取属性列表(键和元素对) |
void load(Reader reader) |
从输入字符流读取属性列表(键和元素对) |
void store(OutputStream out, String comments) | 将此属性列表(键和元素对)写入此Properties表中,以适合于使用load(InputStream)方法的格式写入输出字节流 |
void store(Writer writer, String comments) |
将此属性列表(键和元素对)写入此Properties表中,以适合使用load(Reader)方法的格式写入输出字符流 |
// 读取
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
public class Demo2 {
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
Properties prop = new Properties();
FileReader fr = new FileReader("src\\Properties\\prop.properties");
// 调用完了load方法之后,文件中的键值对数据已经在集合中了
prop.load(fr);
fr.close();
System.out.println(prop);
}
}
// 保存
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
public class Demo3 {
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
Properties prop = new Properties();
prop.put("zhangsan", "123");
prop.put("lisi", "456");
prop.put("wangwu", "789");
FileWriter fw = new FileWriter("src\\Properties\\prop.properties");
prop.store(fw, "注释"); // 注释不填可以填写null
fw.close();
}
}
继承Thread类的方式进行实现
定义一个MyThread继承Thread类
在MyThread类中重写run()方法
创建MyThread类的对象
启动线程
// MyThread类
public class MyThread extends Thread{
@Override
public void run{
// 代码就是线程在开启之后执行的代码
for(int i = 0; i < 100; i++){
System.out.println("线程开启了" + i);
}
}
}
// 测试类
public class Demo{
public static void main(String[] args){
// 创建两个线程对象
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
// 开启一条线程
t1.start();
// 开启第二条线程
t2.start();
}
}
多线程程序,线程交替进行执行,是随机的,所以每次执行的结果可能都不一样
两个小问题
为什么要重写run()方法?
因为run()是用来封装被线程执行的代码run()方法和start()方法的区别?
run():封装线程执行的代码,直接调用,相当于普遍方法的调用,并没有开启线程start():启动线程,然后由JVM调用此线程的run()方法
实现Runnable接口的方式进行实现
定义一个类MyRunnable实现Runnable接口
在MyRunnable类中重写run()方法
创建MyRunnable类的对象
创建Thread类的对象,把MyRunnable对象作为构造方法的参数
启动线程
// MyRunnable类
public class MyRunnable implements Runnable{
@Override
public void run(){
// 线程启动后执行的代码
for(int i = 0; i < 100; i++){
System.out.println("线程开启了" + i);
}
}
}
// 测试类
public class Demo{
public static void main(String[] args){
// 创建了一个参数的对象
MyRunnable mr = new MyRunnable();
// 创建了一个线程对象,并把参数传递给这个线程
// 在线程启动之后,执行的就是参数里面的run方法
Thread t = new Thread(mr);
t.start();
MyRunnable mr2 = new MyRunnable();
Thread t2 = new Thread(mr2);
t2.start();
}
}
利用Callable和Future接口方式实现
定义一个MyCallable实现Callable接口
在MyCallable类中重写call()方法
创建Mycallable类的对象
创建Future的实现类FutureTask对象,把Mycallable对象作为构造方法的参数
创建Thread类的对象,把FutureTask对象作为构造方法的参数
启动线程
// MyCallable类
public class MyCallable implements Callable<String>{
@Override
public String call() throws Exception{
for(int i = 0; i < 100; i++){
System.out.println("线程启动了" + i);
}
// 返回值就表示线程运行完毕之后的结果
return "完成";
}
}
// 测试类
public class Demo{
public static void main(String[] args){
// 线程开启之后执行里面的call方法
MyCallable mc = new MyCallable();
// 可以获取线程执行完毕之后的结果,也可以作为参数传递给Thread对象
FutureTack<String> ft = new FutureTask<>(mc);
// 创建线程对象
Thread t = new Thread(ft);
// 开启线程
ft.start();
// 获取线程执行完毕返回的结果
String s = ft.get(); // get方法不能在start方法前执行
System.out.println(s);
}
}
优点 | 缺点 | |
---|---|---|
实现Runnable、Callable接口 | 扩展性强,实现该接口的同时还可以继承其他的类 | 编程相对复杂,不能直接使用Thread类中的方法 |
继承Thread类 | 编程比较简单,可以直接使用Thread类中的方法 | 可扩展性较差,不能再继承其他的类 |
String getName()
:返回此线程的名称void setName(String name)
:将此线程的名称更改为等于参数namepublic static Thread currentThread()
:返回对当前正在执行的线程对象的引用public static void sleep(long time)
:让线程休眠指定的时间,单位为毫秒public final void setPriority(int Priority)
:设置线程的优先级,默认优先级为5,范围为[1,10]public final int getPriority()
:获取线程的优先级public final void setDaemon(boolean on)
:设置为守护线程案例:卖票
需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
思路
private int ticketCount = 100;
// Ticket类
public class Ticket implements Runnable{
private int ticket = 100;
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
if(ticket == 0){
// 卖完了
break;
}else{
ticket--;
System.out.println(Thread.currentThread().getName() + "正在卖票,当前还剩余:" + ticket + "张票");
}
}
}
}
// Demo类
public class Demo {
public static void main(String[] args){
// 为了多个线程共享一个类中的数据,所以只需要创建一个ticket对象
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
Thread t3 = new Thread(ticket);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
卖票案例的思考
在实际生活中,售票时出票也是需要时间的,所以在出售一张电影票的时候,需要一点时间的延迟,假定每次出票时间为100毫秒,可使用sleep()
方法实现
卖票案例数据安全问题的解决
为什么出现问题?(这也是判断多线程程序是否会有数据安全问题的标准)
如何解决多线程安全问题呢?
锁多条语句操作共享数据,可以使用同步代码块来实现
格式
synchronized(任意对象){ // 锁的对象一定要是唯一的
多条语句操作共享数据的代码
}
锁默认情况是打开的,只要有一个线程进去执行代码了,锁就会关闭
当线程执行完毕出来了,锁才会自动打开
同步的好处和弊端
修饰符 synchronized 返回值类型 方法名(方法参数){ }
this
类名.class
表示字节码文件的对象void lock()
:获得锁void unlock()
:释放锁ReentrantLock()
概述:生产者消费者模式是一种十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻
等待和唤醒的方法
为了体现生产和消费过程中的等待和唤醒,Java提供了几个方法供我们使用,这几个方法在Object类中
方法名 | 说明 |
---|---|
void wait() | 导致当前线程等待,直到另一个线程调用该对象的notify()方法或notifyAll()方法 |
void notify() | 唤醒正在等待对象监视器的单个线程 |
void notifyAll() | 唤醒正在等待对象监视器的所有线程 |
代码实现
套路:
/*消费者步骤:
1.判断桌子上是否有汉堡包
2.如果没有就等待
3.如果有就开吃
4.吃完之后,桌子上的汉堡包就没有了
叫醒等待的生产者继续生产
汉堡包的总数量减一*/
/*生产者步骤:
1.判断桌子上是否有汉堡包
如果有就等待,没有就生成
2.把汉堡包放在桌子上
3.叫醒等待的消费者开吃*/
// Desk类
package ThreadDemo;
package ThreadDemo;
public class Desk {
// 状态:桌上有无汉堡包
private boolean flag;
// 数量
private int count;
// 锁对象
private final Object lock = new Object();
public Desk(boolean flag, int count) {
super();
this.flag = flag;
this.count = count;
}
public Desk() {
super();
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public Object getLock() {
return lock;
}
@Override
public String toString() {
return "Desk [flag=" + flag + ", count=" + count + ", lock=" + lock + "]";
}
}
// 吃货类
package ThreadDemo;
public class Foodie extends Thread{
private Desk desk;
public Foodie(Desk desk) {
super();
this.desk = desk;
}
public void run(){
while(true){
synchronized(desk.getLock()){
if(desk.getCount() == 0){
break;
}else{
if(desk.isFlag()){ // 桌上有就开吃
System.out.println("吃货正在吃汉堡包");
desk.setCount(desk.getCount() - 1);
desk.setFlag(false);
desk.getLock().notifyAll(); // 叫醒厨师继续生产
}else{ // 如果没有就等待
try {
desk.getLock().wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
}
}
// 厨师类
package ThreadDemo;
public class Cooker extends Thread{
private Desk desk;
public Cooker(Desk desk) {
super();
this.desk = desk;
}
public void run(){
while(true){
synchronized(desk.getLock()){
if(desk.getCount() == 0){
break;
}else{
if(!desk.isFlag()){ // 桌上没有汉堡包就生产
System.out.println("厨师正在生产第" + (11 - desk.getCount()) + "个汉堡包");
desk.setFlag(true);
desk.getLock().notifyAll(); // 叫醒吃货来吃汉堡包
}else{ // 有就等待
try {
desk.getLock().wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
}
}
// 测试类
package ThreadDemo;
public class Demo {
public static void main(String[] args){
Desk desk = new Desk(false, 10);
Foodie fd = new Foodie(desk);
Cooker ck = new Cooker(desk);
fd.start();
ck.start();
}
}
阻塞队列实现等待唤醒机制
阻塞队列继承结构
Iterable(接口) -> Collection(接口) -> Queue(接口) -> BlockingQueue(接口) -> ArrayBlockingQueue(实现类) / LinkedBlockingQueue(实现类)
BlockingQueue的核心方法:
put(anObject)
:将参数放入队列,如果放不进去会阻塞
take()
:取出第一个参数,取不到会阻塞
常见BlockingQueue:
ArrayBlockingQueue
:底层是数组,有界
LinkedBlockingQueue
:底层是链表,无界。但不是真正的无界,最大为int的最大值
代码实现
// 吃货类
package ThreadDemo2;
import java.util.concurrent.ArrayBlockingQueue;
public class Foodie extends Thread{
private ArrayBlockingQueue<String> list;
public Foodie(ArrayBlockingQueue<String> list) {
super();
this.list = list;
}
public void run(){
while(true){
try {
System.out.println("吃货吃了一个" + list.take());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
// 厨师类
package ThreadDemo2;
import java.util.concurrent.ArrayBlockingQueue;
public class Cooker extends Thread{
private ArrayBlockingQueue<String> list;
public Cooker(ArrayBlockingQueue<String> list) {
super();
this.list = list;
}
@Override
public void run() {
while(true){
try {
list.put("汉堡包");
System.out.println("厨师放了一个汉堡包");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
// 测试类
package ThreadDemo2;
import java.util.concurrent.ArrayBlockingQueue;
public class Demo {
public static void main(String[] args) {
// 创建一个阻塞队列,容量为1
ArrayBlockingQueue<String> list = new ArrayBlockingQueue<>(1);
// 创建相应的生产者和消费者
Foodie fd = new Foodie(list);
Cooker ck = new Cooker(list);
// 开启对应的线程
ck.start();
fd.start();
}
}
以前写多线程的弊端
解决方案
创建一个池子(线程池),池子是空的-------创建Executors中的静态方法
有任务需要执行时,才会创建线程对象
当任务执行完毕,线程对象归还给池子----------submit方法
所有任务全部执行完毕,关闭连接池---------shutdown方法
池子会自动的帮我们创建对象,任务执行完毕,也会自动把线程对象归还池子
Executors----------可以帮助我们创建线程池对象
ExecutorService---------可以帮助我们控制线程池
代码实现
static ExecutorService newCachedThreadPool()
:创建一个默认的线程池,池子中默认是空的,默认最多可容纳int类型的最大值
static newFixedThreadPool(int nThreads)
:创建一个指定最多线程数量的线程池
package Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
// 创建一个线程池对象
ExecutorService executorService = Executors.newCachedThreadPool();
// ExecutorService executorService = Executors.newFixedThreadPool()
// 提交任务
executorService.submit(()->{
System.out.println(Thread.currentThread().getName() + "执行了");
});
Thread.sleep(2000);
executorService.submit(()->{
System.out.println(Thread.currentThread().getName() + "执行了");
});
executorService.shutdown();
}
}
ThreadPoolExecutor
核心元素
代码实现
package ThreadPool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MyThreadPoolDemo {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2,
5,
2,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
pool.submit(()->{
System.out.println(Thread.currentThread().getName() + "在执行");
});
pool.submit(()->{
System.out.println(Thread.currentThread().getName() + "在执行");
});
pool.shutdown();
}
}
所谓原子性是指在一次或多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行,多个操作是一个不可分割的整体
count++不是一个原子性操作,也就是说他在执行的过程中,有可能被其他线程打断操作
volatile关键字不能保证原子性,只能保证每次线程共享数据的时候是最新值
synchronized同步代码块可以保证原子性,但是速度相对比较慢
方法名 | 说明 |
---|---|
public AtomicInteger() | 初始化一个默认值为0的原子型Integer |
public AtomicInteger(int initialValue) | 初始化一个指定值的原子型Integer |
int get() | 获取值 |
int getAndIncrement() | 以原子方式将当前值加1,注意,这里返回的是自增前的值 |
int incrementAndGet() | 以原子方式将当前值加1,注意,这里返回的是自增后的值 |
int addAndGet(int data) | 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果 |
int getAndSet(int value) | 以原子方式设置为newValue的值,并返回旧值 |
// MyAtomThread类
public class MyAtomThread implements Runnable{
AtomicInteger ac = new AtomicInteger();
public void run(){
for(int i = 0; i < 100; i++){
int count = ac.incrementAndGet();
System.out.println("已经送了" + count + "个冰淇淋");
}
}
}
// 测试类
public class Demo{
public static void main(String[] args){
MyAtomThread atom = new MyAtomThread();
for(int i = 0; i < 100; i++){
new Thread(atom).start();
}
}
}
Hashtable
HashMap是线程不安全的(多线程环境下可能会存在问题)
为了保证数据的安全性我们可以使用Hashtable,但是Hashtable的效率低下
ConcurrentHashMap
CountDownLatch
使用场景:让某一条线程等待其他线程执行完毕后再执行
方法名 | 说明 |
---|---|
public CountDownLatch(int count) | 参数传递线程数,表示等待线程数量。并定义了一个计数器 |
public void await() | 让线程等待,当计数器为0时,会唤醒等待的线程 |
public void countDown() | 当前线程执行完毕,会将计数器减一 |
Semaphore
new Semaphore(int n)
----n表示最多可发放通行证的数量IP:全称“互联网协议地址”,也称IP地址。是分配给上网设备的数字标签。常见的IP分类为:ipv4和ipv6
通过域名访问服务器->域名通过DNS服务器解析为IP地址传递回去->计算机再通过解析好的IP地址访问相应的服务器->服务器返回数据展示在浏览器上
IPv4
IPv6
由于互联网的蓬勃发展,IP地址的需求量愈来愈大,而IPv4的模式下IP的总数是有限的。
采用128位地址长度,分成8组,每16位分为一组
冒分十六进制表示法:2001:0DB8:0000:0023:0008:0800:200C:417A
省略前面的0:2001:DB8:0:23:8:800:200C:417A
特殊情况:如果计算出的16进制表示形式中间有多个连续的0(FF01:0:0:0:0:0:0:1101)
采用0位压缩表示法:FF01::1101
常用命令
ipconfig
:查看本机IP地址ping IP地址
:检查网络是否连通static InetAddress getByName(String host)
:确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址String getHostName()
:获取此IP地址的主机名String getHostAddress()
:返回文本显示中的IP地址字符串public class Demo{
public static void main(String[] args) throws IOException{
// 创建发送端对象
DatagramSocket ds = new DatagramSocket();
// 数据打包 DatagramPacket(byte[] buf, int length, InetAddress, int port)
String s = "需要发送的数据";
byte[] bytes = s.getBytes();
InetAddress address = InetAddress.getByName("127.0.0.1");
int port = 10000;
DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
// 发送数据
ds.send(dp);
// 释放资源
ds.close();
}
}
public class Demo{
public static void main(String[] args) throws{
// 创建接收端对象------参数表示从10000端口接收数据
DatagramSocket ds = new DatagramSocket(10000);
// 创建箱子
byte[] bytes = new bytes[1024];
DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
// 接收数据
ds.receive(dp);
// 获取数据
byte[] data = dp.getData();
System.out.println(new String(data));
// 释放资源
ds.close();
}
}
在这里运行时必须先运行接收端再运行发送端(否则都发送完了才运行接收端就接收不到了)
如果接收端在启动之后没有接收到数据,会阻塞
在接收数据的时候,需要调用一个getLength方法,表示接收到了多少字节
// 发送端
package UDP;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Scanner;
public class ClientDemo {
public static void main(String[] args) throws IOException{
Scanner sc = new Scanner(System.in);
DatagramSocket ds = new DatagramSocket();
while(true){
String s = sc.nextLine();
if("886".equals(s)) break;
byte[] bytes = s.getBytes();
InetAddress address = InetAddress.getByName("127.0.0.1");
int port = 10000;
DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
ds.send(dp);
}
ds.close();
}
}
// 接收端
package UDP;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class ServerDemo {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket(10000);
while(true){
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
ds.receive(dp);
byte[] data = dp.getData();
int length = dp.getLength();
System.out.println(new String(data, 0, length));
}
// ds.close();
}
}
// 组播的发送端
public static void main(String[] args) throws IOException{
DatagramSocket ds = new DatagramSocket();
String s = "Hello Word";
byte[] bytes = s.getBytes();
InetAddress address = InetAddress.getByName("224.0.1.0");
int port = 10000;
DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
ds.send(dp);
ds.close();
}
// 接收端
public static void main(String[] args) throws IOException{
MulticastSocket ms = new MulticastSocket(10000);
DatagramPacket dp = new DatagramPacket(new byte[1024], 1024);
// 把当前计算机绑定一个组播地址
ms.joinGroup(InetAddress.getByName("224.0.1.0"));
// 接收数据
ms.receive(dp);
byte[] data = dp.getData();
int length = dp.getLength();
System.out.println(new String(data, 0, length));
ms.close();
}
// 发送端
public static void main(String[] args) throws IOException{
DatagramSocket ds = new DatagramSocket();
String s = "广播发送端";
byte[] bytes = s.getBytes();
InetAddress address = InetAddress.getByName("255.255.255.255");
int port = 10000;
DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
ds.sent(dp);
ds.close();
}
// 接收端
public class ServerDemo {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket(10000);
while(true){
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
ds.receive(dp);
byte[] data = dp.getData();
int length = dp.getLength();
System.out.println(new String(data, 0, length));
}
ds.close();
}
}
TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket对象
发送端:Socket
接收端:ServerSocket
通信之前要保证连接已经建立
通过Socket产生IO流来进行网络通信
创建客户端的Socket对象(Socket)与指定服务端连接
Socket(String host, int port)
获取输出流,写数据
OutputStream getOutputStream()
释放资源
void close()
代码实现
// 客户端
public static void main(String[] args) throws IOException{
Socket socket = new Socket("127.0.0.1", 10000);
OutputStream os = socket.getOutputStream();
os.write("hello".getBytes());
os.close();
socket.close();
}
创建服务端的Socket对象(ServerSocket)
ServerSocket(int port)
监听客户端连接,返回一个Socket对象
Socket accept()
获得输入流,读数据,并把数据显示在控制台
InputStream getInputStream()
释放资源
void close()
代码实现
// 服务端
public static void main(String[] args){
ServerSocket ss = new ServerSocket(10000);
Socket accept = ss.accept(); // 没有客户端连接的话,就会死等,不执行后续代码,即阻塞
InputStream is = accept.getInputStream();
int b;
while((b = is.read()) != -1){
System.out.print((char) b);
}
is.close();
ss.close();
}
客户端创建对象并连接服务器,此时是通过三次握手协议保证服务器之间的连接
针对客户端,是往外写的,所以是输出流;而服务端是往进读的,所以是输入流
read方法也是阻塞的
在关流的时候,还多了一个往服务器写结束标记的动作
最后一步断开连接,会通过四次挥手协议保证连接终止
保证客户端与服务器之间建立连接
第一次:客户端向服务器发出连接请求(等待服务器确认)
第二次:服务器向客户端返回一个响应(告诉客户端收到了请求)
第三次:客户端向服务端再次发出确认信息(连接建立)
练习一
客户端:发送数据,接收服务器反馈
服务器:接收数据,给出反馈
代码
// 客户端
package TCP;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.io.OutputStream;
public class ClientDemo {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 10000);
OutputStream os = socket.getOutputStream();
os.write("hello".getBytes());
socket.shutdownOutput(); // 仅仅关闭输出流,并写一个结束标记,对socket没有任何影响
InputStream is = socket.getInputStream();
int b;
while((b = is.read()) != -1){
System.out.print((char)b);
}
is.close();
os.close();
socket.close();
}
}
// 服务端
package TCP;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.io.InputStream;
public class ServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10000);
Socket accept = ss.accept();
InputStream is = (InputStream) accept.getInputStream();
int b;
while((b = is.read()) != -1){
System.out.print((char)b);
}
OutputStream os = accept.getOutputStream();
os.write("who?".getBytes());
os.close();
is.close();
accept.close();
ss.close();
}
}
练习二
客户端:将本地文件上传到服务器,接收服务器的反馈
服务器:接收客户上传的文件,上传完毕之后给出反馈
代码
// 客户端
package TCP;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
public class ClientDemo2 {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 10000);
// 本地的流,读取本地文件
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("1.png"));
OutputStream os = socket.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(os);
int b;
while((b = bis.read()) != -1){
bos.write(b); // 通过网络写到服务器中
}
// 给服务器一个结束标记
socket.shutdownOutput();
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
while((line = br.readLine()) != null){
System.out.println(line);
}
socket.close();
bis.close();
}
}
// 服务器
package TCP;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerDemo2 {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10000);
Socket accept = ss.accept();
// 网络中的流,从客户端读取数据的
BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
// 本地的IO流,把数据写到本地中,实现永久化存储
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("src\\copy.png"));
int b;
while((b = bis.read()) != -1){
bos.write(b);
}
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
bw.write("上传成功!");
bw.newLine();
bw.flush();
accept.close();
ss.close();
bos.close();
}
}
UUID uuid = UUID.randomUUID();
生成一个随机且唯一的uiduuid.toString();
转换为字符串概述:负责将.class文件(存储的物理文件)加载到内存中
加载时机
用到就加载,不用不加载
加载过程
加载—验证—准备—解析—初始化
又将验证—准备—解析这三步称为链接
分类
双亲委派模型
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
----可以获得系统类加载器
systemClassLoader.getParent()
-------------得到父类加载器
常用方法
方法名 | 说明 |
---|---|
loadClass(String name, boolean resolve) | 加载指定名称(包括包名)的二进制类型 |
findClass(String name) | 当loadClass方法中父类加载失败时,调用自己的findClass方法来完成类加载 |
defineClass(byte[] b, int off, int len) | 将byte字节流解析成JVM能够识别的Class对象 |
resolveClass(Class> c) | 让使用类的Class对象创建完成也同时被解析 |
Java反射机制
在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法
对于任意一个对象,都能够调用它的任意属性和方法
这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制
利用反射调用它类中的属性和方法时,无视修饰符。反射操作,程序比较灵活,动态的
先获取配置文件中的信息,动态获取信息并创建对象和调用方法
获取Class对象
以前调用一个类中的方法 | 反射调用一个类中的方法 |
---|---|
创建这个类的对象(new) | 反射方式:创建对象(利用Class对象) |
用对象调用方法 | 反射方式:调用方法 |
Class.forName(String className)
通过Class类静态方法传递全类名获取Class对象类名.class
对象.getClass()
获取Constructor对象
Constructor>[] getConstructors()
:返回所有公共构造方法对象的数组Constructor>[] getDeclaredConstructors()
:返回所有构造方法对象的数组(包括私有)Constructor getConstructor(Class>... parameterTypes)
:返回单个公共构造方法对象Constructor getDeclaredConstructor(Class>... parameterTypes)
:返回单个构造方法对象(包括私有)利用Constructor创建对象
T newInstance(Object...initargs)
:根据指定的构造方法创建对象
package fanshe;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Demo1 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
// 获取字节码文件
Class clazz = Class.forName("fanshe.Student");
// 获取构造方法
Constructor constructor = clazz.getConstructor(String.class, int.class);
// 利用newInstance 创建对象
Student student = (Student) constructor.newInstance("张三", 11);
System.out.println(student);
}
}
在Class类中,有一个newInstance 方法,可以利用空参直接创建一个对象
Class clazz = Class.forName("fanshe.Student");
Student stu = (Student)clazz.newInstance();
不过这个方法现在已经过时了,了解即可
// 获取一个私有的构造方法并创建对象
package fanshe;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Demo2 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
// 获取字节码文件
Class clazz = Class.forName("fanshe.Student");
// 获取构造方法
Constructor constructor = clazz.getConstructor(String.class, int.class);
// 被private修饰的成员,不能直接使用
// 如果用反射强行获取并使用,需要临时取消访问检查
constructor.setAccessible(true);
// 利用newInstance 创建对象
Student student = (Student) constructor.newInstance("张三", 11);
System.out.println(student);
}
}
在获取到的构造方法创建对象时,如果是public,可以直接创建对象
如果是非public的,需要临时取消检查,然后再创建对象
setAccessible(boolean)
(暴力反射)
反射获取成员变量
步骤
获得class对象
获得Field对象
方法名 | 说明 |
---|---|
Field[] getFields() | 返回所有公共成员变量对象的数组 |
Field[] getDeclaredFields() | 返回所有成员变量对象的数组(包括私有) |
Field[] getField(String name) | 返回单个公共成员变量对象 |
Field[] getDeclaredField(String name) | 返回单个公共成员变量对象(包括私有) |
赋值或者获取值
void set(Object obj, Object value)
:给指定对象的成员变量赋值
Object get(Object obj)
:返回指定对象的Field的值
代码实现
package fanshe;
import java.lang.reflect.Field;
public class Demo2 {
public static void main(String[] args) throws NoSuchFieldException, SecurityException, ClassNotFoundException, InstantiationException, IllegalAccessException {
// 获取class对象
Class clazz = Class.forName("fanshe.Student");
// 获取Field对象
Field field = clazz.getDeclaredField("name");
// 赋值
// 创建一个对象
Student stu = (Student)clazz.newInstance();
// 关闭访问检查(私有变量)
field.setAccessible(true);
// 赋值
field.set(stu, "张三");
System.out.println(stu);
}
}
获取Method对象
步骤
获取class对象
获取Method对象
方法名 | 说明 |
---|---|
Method[] getMethods() | 返回所有公共成员方法对象的数组,包括继承的 |
Method[] getDeclaredMethods() | 返回所有成员方法对象的数组,不包括继承的,包括私有的 |
Method[] getMethod(String name, Class>…parameterTypes) | 返回单个公共成员方法对象 |
Method[] getDeclaredMethod(String name, Class>…parameterTypes) | 返回单个成员方法对象(包括私有) |
运行方法
Object invoke(Object obj, Object...args)
:运行方法
参数一:表示用obj对象调用该方法
参数二:调用方法的传递的参数(如果没有就不写)
返回值:方法的返回值(没有就不写)
代码演示
package fanshe;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Demo3 {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
// 获取class对象
Class clazz = Class.forName("fanshe.Student");
// 获取Method对象
Method method = clazz.getDeclaredMethod("function", String.class, int.class);
// 创建对象
Student stu = (Student)clazz.newInstance();
// 运行
method.setAccessible(true);
method.invoke(stu, "张三",23);
}
}
标签规则
语法规则
演示
<students>
<student id="1">
<name>张三name>
<age>23age>
<info>学生 < >信息info>
<message>容]]>message>
student>
<student id="2">
<name>李四name>
<age>24age>
<info>info>
student>
students>
解析就是从xml中获取到数据
DOM(Document Object Model)文档对象模型:就是把文档的各个组成部分看做成对应的对象
会把xml文件全部加载到内存。在内存中形成一个树形结构,再获取到对应的值
常见的解析工具
DOM4J
下载地址:https://dom4j.github.io/
常用方法
SAXReader saxReader = new SAXReader();
:获取一个解析器对象
Document document = saxReader.read(new File("xml文件路径"))
:利用解析器把xml文件加载到内存中,并返回一个文档对象
Element rootElement = document.getRootElement()
:获取到根标签
List list = rootElement.elements()
:获取调用者所有的子标签,并加入到集合,最终返回这个集合
List list = rootElement.elements("标签名")
:获取调用者所有的指定的子标签,会把这些子标签放到一个集合中并返回
Attribute attribute = element.attribute("属性名")
:获取标签对应的属性
String id = attribute.getValue()
:获取对应属性的值
Element nameElement = element.element("标签名");
:获取调用者指定的子标签
String name = nameElement.getText()
:获取这个标签的标签体内容
代码
package MyXml;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
public class Demo {
public static void main(String[] args) throws DocumentException {
// 获取一个解析器对象
SAXReader saxReader = new SAXReader();
// 利用解析器把xml文件加载到内存中
Document document = saxReader.read(new File("src\\MyXml\\Students.xml"));
// 获取到根标签
Element rootElement = document.getRootElement();
// 获取子标签
List<Element> list = rootElement.elements("student");
// 遍历获取每个student信息
ArrayList<Student> s = new ArrayList();
for(Element element : list){
// 获取属性
Attribute attribute = element.attribute("id");
String id = attribute.getText();
// 获取name
Element nameElement = element.element("name");
String name = nameElement.getText();
// 获取age
Element ageElement = element.element("age");
int age = Integer.parseInt(ageElement.getText());
Student stu = new Student(id,name,age);
s.add(stu);
}
System.out.println(s);
}
}
步骤
引入DTD约束的三种方法
DTD语法规则
定义一个元素:
元素类型
简单元素:
EMPTY:表示标签体为空ANY:表示标签体可以为空也可以不为空
PCDATA:表示该标签的内容部分为字符串
复杂元素:
直接写子元素名称
多个子元素可以用","或者’|'隔开;
","表示定义子元素的顺序
"|“表示子元素只能出现任意一个
(?表示零次或多次;”+“表示一次或多次;”*"表示零次或多次;如果不写默认表示出现一次)
定义一个属性:
属性类型:CDATA类型(普通的字符串)
属性约束
#REQUIRED:必需的
#IMPLIED:属性不是必需的
#FIXED value:属性值是固定的
schema和DTD的区别
schema约束文件也是一个xml文件,符合xml语法,后缀名为.xsd
一个xml中可以引用多个schema约束文件,多个schema使用名称空间区分(名称空间类似于java包名)
dtd里面元素类型的取值比较单一常见的是PCDATA类型,但是在schema里面可以支持很多个数据类型
schema语法更加复杂
schame文件用来约束一个xml文件,因为其自身也是一个xml文件,所以同时也被别的文件约束着
步骤
xmlns=http://www.w3.org/2001/XMLSchema
(声明这个schema文件是一个约束文件,同时也被约束)targetNamespace=唯一的url地址
(指定当前这个schema文件的名称空间)elementFormDefault="qualified"
(表示当前schema文件是一个质量良好的文件)代码实现
xml version="1.0" encoding="UTF-8" ?>
<schema
xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.baidu.com/javase"
elementFormDefault="qualified"
>
<element name="persons">
<complexType>
<sequence>
<element name="person">
<complexType>
<sequence>
element>
element>
sequence>
complexType>
element>
sequence>
complexType>
element>
schema>
引入schema文件约束
xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance"
(表示当前文件被别人约束)xmlns = "约束文件的名称空间"
xmlns:标识 = "名称空间地址"
xsi:schemaLocation
指定名称空间所对应的约束文件路径xsi:schemaLocation = "名称空间url 文件路径"
Schema定义属性
为了间接的表示一些固定的值,Java提供了枚举
枚举:是指将变量的值一一列出来,变量值只限于列举出来的值的范围内
格式
public enum s{
枚举项1,枚举项2,枚举项3;
}
// 定义一个枚举类,用来表示春、夏、秋、冬
public enum Season{
SPRING,SUMMER,AUTUMN,WINTER;
}
public enum Season{
SPRING("春"){
// 枚举类中有抽象方法,必须重写
public void show(){
System.out.println(this.name);
}
},
SUMMER("夏"){
public void show(){
System.out.println(this.name);
}
},
AUTUMN("秋"){
public void show(){
System.out.println(this.name);
}
},
WINTER("冬"){
public void show(){
System.out.println(this.name);
}
};
// 成员变量
public String name;
// 有参构造
private Season(String name){
this.name = name;
}
// 抽象方法
public abstract void show();
}
方法名 | 说明 |
---|---|
String name() | 获取枚举项的名称 |
int ordinal() | 返回枚举项在枚举类中的索引值 |
int compareTo(E o) | 比较两个枚举项,返回的是索引值的差值 |
String toString() | 返回枚举常量的名称 |
static T valueOf(Class type, String name) | 获取指定枚举类中的指定名称的枚举值 |
values() | 获得所有的枚举项 |
注解名 | 说明 |
---|---|
@Override | 概述子类重写父类的方法 |
@Deprecated | 概述方法过时 |
@SuppressWarnings | 压制警告 |
@Retention(value = RetentionPolicy.RUNTIME) | 表示这个注解的存活时间 |
注释和注解
注释:给程序员看
注解:给编译器看(让虚拟机看到程序中的注解,注解代表程序的一些特殊功能)
自定义注解
public @interface 注解名称{
public 属性类型 属性名 () default 默认值; //这里只能是public,可省略
}
属性类型可以为基本数据类型、String、Class、注解、枚举以及以上类型的一维数组
在使用注解的时候如果注解里面的属性没有指定默认值,那么就需要手动给出注解属性的设置值
特殊属性value:在使用注解时如果只给value赋值,那么可以直接设置
isAnnotationPresent(Class extends Annotation> annotationClass)
判断当前方法上是否有指定的注解
参数:注解的字节码文件对象
返回值:布尔结果 true存在;false不存在
元注解名 | 说明 |
---|---|
@Target | 指定了注解能在哪里使用 |
@Retention | 可以理解为保留时间(生命周期) |
@Inherited | 表示修饰的自定义注解可以被子类继承 |
@Documented | 表示该自定义注解,会出现在API文档里面 |
@Target
:成员变量(ElementType.FIELD),类(ElementType.TYPE),成员方法(ElementType.METHOD)
@Retention
:不写默认只能存活在java文件(不能存活在class文件中),括号写上RetentionPolicy.RUNTIME
就可以存活到字节码中
注解 | 含义 |
---|---|
@Test | 表示测试该方法 |
@Before | 在测试的方法前运行 |
@After | 在测试的方法后运行 |
日志:程序中的日志可以用来记录程序在运行时的点点滴滴,并可以进行永久存储
区别
输出语句 | 日志技术 | |
---|---|---|
取消日志 | 需要修改代码,灵活性比较差 | 不需要修改代码,灵活性比较好 |
输出位置 | 只能是控制台 | 可以将日志信息写入到文件或者数据库中 |
多线程 | 和业务代码处于一个线程中 | 多线程方式记录日志,不影响业务代码的性能 |
Log4j
是Apache的一个开源项目,通过Log4j,可以控制日志信息输送的=目的地是控制台、文件等位置
也可以控制每一条日志输出格式
通过定义每一条日志信息的级别,能够更细致的控制日志的生成过程
这些可以通过一个配置文件来灵活的进行配置,不需要修改应用的代码
Log4j开发流程
导入log4j的相关jar包
编写log4j配置文件(log4j.properties/log4j.xml)
在代码中获取日志的对象
log4j自己的api(不推荐使用)
弊端:如果以后要更换日志的实现类,那么下面的代码就要跟着改
private static final Logger LOGGER = Logger.getLogger(.class字节码文件);
使用slf4j里面的api来获取日志的对象
好处:如果更换日志的实现类,下面的代码不需要更改
private static final Logger LOGGER = LoggerFactory.getLogger(clss字节码文件)
按照级别设置记录日志信息
Log4j组成
Loggers(记录器):日志的级别
常用级别:
DEBUG 打印基本信息
INFO 打印重要信息
WARN 打印可能出现问题的信息
ERROR 出现错误的信息,不影响程序运行
FATAL 重大错误,程序可以停止
DEBUGLog4j有一个规则:只输出级别不低于设定级别的日志文件
Appenders(输出源):日志要输出的地方,如控制台(Console)、文件(Files)等。
org.apache.log4j.ConsoleAppender
(控制台)
org.apache.log4j.FileAppender
(文件)
log4j.appender.ca = org.apache.log4j.ConsoleAppender
log4j.appender.ca.设置1 = 值1
log4j.appender.ca.设置2 = 值2
...........
Layouts(布局):日志输出的格式
常用布局管理器:
org.apache.log4j.PatternLayout
(可以灵活的指定布局模式)【最常用】
org.apache.log4j.SimpleLayout
(包含日志信息的级别和信息字符串)
org.apache.log4j.TTCCLayout
(包含日志产生的时间、线程、类别等信息)
log4j.appender.ca.layout = org.apache.log4j.PatternLayou
log4j.appender.ca.layout.设置1 = 值1
log4j.appender.ca.layout.设置2 = 值2
...........
配置文件详解
配置根Logger
格式:
log4j.rootLogger = 日志级别, appenderName1, appenderName2, ...
日志级别:OFF FATAL ERROR WARN INFO DEBUG ALL或者自定义的级别
appenderName1:就是指定日志信息要输出到哪里。可以同时指定多个输出目的地,用逗号隔开
例如:log4j.rootLogger = INFO, ca, fa
配置文件
ConsoleAppender常用的选项
ImmediateFlush = true 表示所有消息都会被立即输出,设置为false则不输出,默认为true
Target = System.err 默认值是System.out
FileAppender常用的选项
ImmediateFlush = true 表示所有消息都会立即被输出。设为false则不输出,默认值是true
Append = false true表示将消息添加到指定文件中,原来的消息不覆盖
false则将消息覆盖指定的文件内容,默认值为true
File = D:/logs/logging.log4j 指定消息输出到logging.log4j文件中
配置Layout
PatternLayout常用的选项
ConversionPattern=%m%n 设定以怎样的格式显示消息
【格式化符号说明可以查看网上的资料做一个了解】
Log4j应用
本文篇幅较长,分篇阅读请前往 Java基础知识 专栏查阅
③中错误,在byte short char运算时,会直接提升为int,然后再进行运算; ↩︎
④正确:Java存在常量优化机制,3和4是两个常量,会在编译的时候让3和4进行相加,然后判断7是否在byte的取值范围内 ↩︎