文章目录
一.java入门
1.cmd
cmd就是在window中利用命令行的方式去操作计算机
- 使用win+R键可以打开操作面板,然后输入cmd就可以打开了、
- Desktop就是桌面的文件夹
常见的cmd命令
- cd的使用
- cmd的使用(打开常用软件)
由于使用cmd时打开软件要切换盘符,进入多层文件夹,很麻烦,可以把想要打开的软件的路径记录在电脑的环境变量,就可以很方便的打开 - 步骤
先打开系统属性的高级选项
点击环境变量,找到系统变量中的path
点击编辑,在Path中新建常用软件的路径,就可以用cmd方便的打开软件了
2.入门的配置,使用的编译软件等
bin路径的内容
这五个模块了解就行
java程序的运行过程
第一次java程序的运行
注意:java文件名一定要和类名相同
环境变量(Path?)
配置的过程
Notepad++
使用notepad++来写java代码
(这jb辱华??)
不用了
vscode
还是要创建源文件和class文件,用javac
其他下载插件,然后运行就可以了
3.Java的介绍
- 使用平台
- JavaSe介绍
- javaME
- JavaEE
java的应用领域
java的主要特性
java跨平台的原理
4.jdk和jre
写(开发)代码需要的环境
JDK
- JVM
- 核心类库
java中提供的函数 - 开发工具
以上三个就是JDK
JRE
运行代码所需要的环境
二.java基础
1.注释和关键字
注释
注释就不解释了,要养成写注释的好习惯
注释的分类:
// 单行注释
/**/ 多行注释
/***/ 文档注释(暂时用不上)
以后nb了可以写文档注释
关键字(类class)
注:关键字的字母都是小写
第一个关键字
- class
用于创建/定义一个类,类是java的最基本组成单元
语法:
public(以后可能其他的内容) class 类名 {
//主入口
public static void main(){
具体内容
}
}
2.字面量
字面量的含义
字面量类型:
注意点:字符类型只有一个内容,也就是只能有一个字母或数字或文字
//输出语句
System.out.println(内容);
在cmd中可以用上下键来使用已经使用过的命令
空类型不能直接打印,想要打印的话,只能通过字符串的形式打印
public class Demo1 {
public static void main(String[] args){
System.out.println(111);
}
}
特殊字符
可以用来对齐数据
3.变量
变量的定义格式
与c是一样的
- 数据类型
-
- int: 整数
-
- double:小数(浮点数)
注意:定义的变量名不能重复,一个变量只能存储一个值,且使用之前一定要赋值
4.计算机的存储规则
java中进制的表现形式
在电路板中,大于3.3v的为1小于3.3v的为0
利用二进制和美国信息交换标准码表可以将数字与字母进行转换
用来计算机的汉字输入
5.基本数据类型
基本数据类型分为4类8种。
字符串类型是String
6.标识符
要求
起名方法:驼峰式
7.键盘录入
需要用到Scanner
这个类
即先要搞一个类似库之类的东西?
import java.util.Scanner;
即创建Scanner的语法为
Scanner 类名 = new Scanner(System.in);
int 变量名= sc.nextInt();
import java.util.Scanner;
public class Demo1 {
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
int abc=sc.nextInt();
System.out.println(abc);
}
}
现在的形式只能输入一个整数
int 变量名= sc.next();
上面的形式可以输入字符串
8.idea
即一个项目(project)中包含好几个模块(module),模块里才是包(package)也就是文件夹,再然后就是class类了
java类创建完成后,在类的使用上可以快速创建一个入口
即用psvm
例如
回车后就会直接创建好一个入口
同理,输出语句也有快捷方式
sout
如果忘记了创建的过程,重新看28集
9.运算符
- 运算符的分类
算数运算符
与c语言使用方式一样,
不同点
有小数参与计算时,结果可能是不精确的,与c类似,当只有整数参与运算时,结果也是整数,会舍弃掉所有的小数。
- %的应用场景
数值拆分的练习
package com.javaparctice.exercise;
import java.util.Scanner;
public class Basicexercise {
public static void main(String[] args) {
Scanner in=new Scanner(System.in);
int oin=in.nextInt();
System.out.println(oin%10);
System.out.println(oin/10%10);
System.out.println(oin/100%10);
}
}
强制转换和隐式转换
- 隐式转换,自动类型提升
类型取值范围的大小
隐式转换的规则
强制转换
语法:
目标数据类型 变量名=(目标数据类型)被转化的数据
由低范围转至高范围时,有可能会发生错误,,是由于数据的取值范围决定的, - 字符串的“+”操作(拼串)
例如:100+1+"100"="101100"
不论有什么类型,只要在+的过程中出现了字符串,最后都会有拼串的操作
- 字符相加
第一个是把‘a’
转化为int 形,再相加,结果为98
第二个就是拼串了
自增自减
++
,--
++
和--
会把变量的值改变,而前后位置会影响表达式的值
package com.javaparctice.exercise;
public class Basicexercise {
public static void main(String[] args) {
int a=10;
int c=a++;//这里a++表达式为10,即c为10,但是a为11
int b=++a;//这里++a表达式为12,b为12,a也为12
System.out.println(a);
System.out.println(b);
System.out.println(c);
}
}
赋值和关系运算符
- 赋值
与c一样 - +=
注
即使用+=,-=,*=,/=,%=时,会把数据强制转换成运算符左边的类型。
- 关系运算符
注意事项
逻辑运算符(与c有些不同)
这里是一个符号,像&(且),|(或)等,而c语言中,都是两个&&,||
且多出一个^
符号,逻辑异或,相同为flase,不同为true
package com.javaparctice.exercise;
public class Basicexercise {
public static void main(String[] args) {
System.out.println(true^true);
System.out.println(false^false);
System.out.println(true^false);
}
}
显示为:
false
false
true
注
短路逻辑运算符
&&
,||
这里又跟语言一样了,即&&
运算符有&&(且)
的特性,||
运算符有||(或)
并且有逻辑短路的特性
三元运算符
注:三元表达式的最终结果一定要使用,如果只是在代码中单独写一个三元表达式,程序会报错
用这样的形式来使用
快捷键
运算符的优先级
10.原码,反码,补码
- 原码
例如:
56转成二进制是00111000,第一个0就是符号位,后面的是数据
一个字节8个bit
在原码中
一个字节的最大值是01111111,十进制就是127
最小值是11111111十进制就是-127
但是一个字节的取值范围是-128到127
-0也是0
符号位决定了字节的正负,若符号位是0,证明这个字节是正数,对它进行加减操作时就是正常的加减
而若符号位是1,证明这个字节是负数,对它进行加减操作时就不是正常的加减了
例如:
00000000+1=00000001转成十进制就是1
10000000+1=10000001转成十进制就是-1
就是按照数轴移动的。
- 反码补码
原码转反码的过程中,1是符号位,不变,其余的0变1,1变0。
利用反码可以计算负数
利用反码时,如果计算结果跨越0,即计算结果超过0后,会出现误差,计算值比实际值少1。
原因是反码有两个0的表现形式00000000
和11111111
在计算机中,数字的存储和运算都是以补码的形式进行存储的
只有补码有-128,在计算机中所以一个字节的范围是-128到127
所以,隐式转换的本质就是将小范围的数据类型增加字节变成大范围的数据类型
强制转换的本质就是将大范围的数据类型减少字节变成小范围的数据类型
运算符<<左移
注:每左移一次就是乘以2,上面代码左移了两次,所以就是800
运算符>>右移
这里在符号位补的数与原来的正负要保持一致,如果原来是正,就补0,原来是负,就补1。
注:每右移一次就是除以2,上面代码右移了两次,所以就是50
11.流程控制
if语句
- 格式1
注意点
第三点可以看一下
两句代码
- 格式2
- 格式3
switch语句
- 格式
表达式会得到一个具体的结果,结果会依次与case值比较,有对应的值就会执行相应的语句,执行的过程中,遇到break就会结束。
如果所有的case值都不匹配,就会执行default里的语句体 - 注意
- switch拓展
defalut可以省略,但不建议这样做
且defalut可以写在任意位置,习惯写在最下面 - case穿透
switch新特性就jdk12的新特性,就没必要使用了
即
case 1,2,3,4,5:
与
case 1:
case 2:
case 3:
case 4:
case 5:
是一样的效果
for语句
例子
public class Basicexercise {
public static void main(String[] args) {
for(int i=0;i<5;i++){
int b=1;
System.out.println(b);//这里b的作用域是for循环内
}
}
}
快捷生成for循环的方式
循环次数.fori
例如
100.fori
生成
for (int i = 0; i < 100; i++) {
}
偶数累加练习
package com.javaparctice.exercise;
public class Basicexercise {
public static void main(String[] args) {
int s=0;
for (int i = 1; i <=100; i++) {
if(i%2==0){
s=s+i;
}
}
System.out.println(s);
}
}
统计范围练习
package com.javaparctice.exercise;
import java.util.Scanner;
public class Basicexercise {
public static void main(String[] args) {
Scanner in=new Scanner(System.in);
int sum=0;
System.out.println("请输入开始的范围");
int strat=in.nextInt();
System.out.println("请输入结束的范围");
int end=in.nextInt();
for(int i=strat;i<=end;i++){
if(i%3==0&&i%5==0){
sum++;
}
}
System.out.println(sum);
}
}
while循环
- for与while循环的区别
这个说的很对
可以用calc打开电脑计算器
do…while语句
算法练习
回文数字的判断
package com.javaparctice.exercise;
import java.util.Scanner;
public class Basicexercise {
public static void main(String[] args) {
int x;//定义输入的数字
int sum=0;//存储顺序改变后的顺序
System.out.println("请输入回文数字");
Scanner hui=new Scanner(System.in);
x=hui.nextInt();
int temp=x;//存储x的初始值,方便以后做对比
while(x!=0){
int ge=x%10;//获取个位数
x=x/10;//改变x的值,去掉刚才获取到的值
sum=sum*10+ge;//将获取的数字排到最左边
}
if(sum==temp) {//判断是否为回文数字
System.out.println(sum);
}else{
System.out.println("这个数不是回文数字");
}
}
}
12.高级循环
无限循环
do…while不常用,无限循环一般会用while循环的格式多一些
跳转控制语句
就是跳过一次或多次循环
这里代码执行到continue时,就会跳过此次循环,直接进入下次循环。
这里代码执行到break时,就会直接结束循环。
获取随机数
使用Random类
可以看到,跟使用Scanner是一样的
使用这样的随机数一定是从0开始的,如果想固定随机数的范围,可以在随机数的代码后再加上数字
13.数组
结合隐式转换。
注
数组的定义和静态初始化
- 数组的定义格式
- 数组的静态初始化
完整格式过于繁琐,一般用简写格式
数组一旦创建完毕后,数组的长度就不会发生变化了
例如,上面的范例的长度就是3,不会变化了
数组的地址值和元素访问
- 数组的地址值
直接打印数组名会打印出数组的地址值,与c语言是类似的
- 访问数组元素
与c语言是一样的
原来的数据就会被覆盖掉,就不存在了
数组的遍历
注:
与javascript是一样的
例如
package com.javaparctice.exercise;
public class exercise2 {
public static void main(String[] args) {
int a[]={1,1,1,1,1,1,1,1,1,1,1,1,1,11,1,2,3,45,1,};
for(int i=0;i<a.length;i++){
System.out.println(a[i]);
}
}
}
idea提供的快速创建数组遍历的方式
即数组名.fori
练习题
package com.javaparctice.exercise;
public class exercise2 {
public static void main(String[] args) {
int array[]={1,2,3,4,5,6,7,8,9,10};
int sum=0;
for (int i = 0; i < array.length; i++) {
if(array[i]%2==0){
sum=array[i]/2;
System.out.println(sum);
}else{
sum=array[i]*2;
System.out.println(sum);
}
}
}
}
数组的动态初始化和常见问题
数据类型[] 数组名=new 数据类型[数组长度]
上例的数组长度就是50
- 数组默认初始化类型
- 使用场景
- 常见问题
越界异常的报错
- 新格式
System.out.print(内容)
在循环中输出时,不会换行,而是会在一行显示数据
- 交换数据
package com.javaparctice.exercise;
public class exercise2 {
public static void main(String[] args) {
int array[]={1,2,3,4,5};
for(int i=0,j=array.length-1;i<j;i++,j--){
int temp=array[i];
array[i]=array[j];
array[j]=temp;
}
for (int i = 0; i < array.length; i++) {
System.out.print(array[i]+" ");
}
}
}
数组的内存图
- java的内存分配(虚拟机的内存分配)
在jdk7以前,方法区和堆是在一个区域内的
暂时把元空间叫做方法区
-
栈
方法运行时使用的内存,比如main方法就是在方法栈中执行的
栈内存运行的逻辑
先在内存里创建空间,再存储相应的值。 -
堆
new关键字创建的内容都存储在堆内存。
每一块空间所占有的地址都是不一样的
创建数组时,会先在栈内存创建一个数组变量,然后再堆内存中创建一个空间来存放数组,用地址值来链接数组
即数组名记录的是堆内存里的地址值。
打印数组名会打印出相应的地址。
数组的赋值会把之前的数组元素覆盖
两个数组指向同一个数组空间
因为改变的是同一个内存地址中的内容
14.方法
例如
类似于函数
- 实际开发中的应用场景
利用率高的代码打包成方法,以便于重复使用。
方法的定义与调用
把一些代码打包在一起,这个过程叫做方法定义
- 最简单的方法的定义
语法
public static void 方法名(){
方法体(被打包起来的代码)
}
方法名使用驼峰命名法
- 方法的调用
方法名()
方法可以在主方法(main)下面定义
- 带参数的方法的定义和调用
语法
public static void 方法名(参数1,参数2){
方法体
}
注意,参数要有定义
调用
方法名(参数1,参数2...)
- 形参和实参
例子
- 带返回值的方法的定义和调用
语法
public static 返回值类型 方法名(参数1,参数2){
方法体;
return 返回值;
}
例如:
- 调用
三种格式
比较长方形的面积(这个代码很简单,但是这个编程的思想可以关注一下,利用方法解决重复的代码)
package com.javaparctice.exercise;
public class exercise2 {
public static void main(String[] args) {
double a1=chang(1,2);
double a2=chang(3,5);
if(a1>a2){
System.out.println("第一个长方形的面积大");
}else{
System.out.println("第二个长方形的面积大");
}
}
// public static void yuan(int r){
// double a=3.14;
// double S=r*r*a;
// System.out.println(S);
// }
public static double chang(double width,double height){
double a=(width+height)*2;
return a;
}
}
方法定义的技巧
第一点决定了方法里的方法体,第二点决定了方法里的形参。
利用方法求圆的面积
package com.javaparctice.exercise;
public class exercise2 {
public static void main(String[] args) {
yuan(5);
}
public static void yuan(int r){
double a=3.14;
double S=r*r*a;
System.out.println(S);
}
}
方法实际上定义只有一种格式
注意事项
return关键字
方法的重载
定义
判断重载函数只看方法名是否相同和参数的个数与类型是否相同,与返回值无关(这个与返回值无关需要特别注意。)
上述代码就不是重载方法
上述代码是有两个类。
方法的练习
package com.javaparctice.exercise;
public class exercise2 {
public static void main(String[] args) {
int array[]={1,2,3,4,5};
arr(array);
}
public static int arr(int a[]){
for(int i=0;i<a.length;i++){
System.out.print(a[i]+" ");
}
return 0;
}
}
上述方法表明方法返回的是一个数组。
package com.javaparctice.exercise;
public class exercise2 {
public static void main(String[] args) {
int arr[]={1,2,3,4,5,6,7,8,9};
int copyArr[]=copyOfRange(arr,3,7);
for(int i=0;i<copyArr.length;i++){
System.out.println(copyArr[i]);
}
}
public static int[] copyOfRange(int a[],int from,int to){
int newArr[]=new int[to-from];
int index=0;
for(int i=from;i<to;i++){
newArr[index]=a[i];
index++;
}
return newArr;
}
}
方法的内存原理
- 方法调用的基本内存原理
上述程序刚开始运行时,main方法进栈,然后定义变量在栈内存中,等main方法运行完,main方法出栈,在main方法中定义的变量也会消失。
这里方法在运行完之后就会出栈,没运行之前是不会进栈的
方法出栈内存就像子弹上膛一样,先进后出
上述代码中,study方法会先进栈,在study方法出栈后,执行下面的语句,sleep方法才会进栈。
基本数据类型和引用数据类型
在基本数据类型中
现在来说,只要是new出来的,都是引用数据类型。
在引用数据类型中
- 方法的值的传递
格式化代码快捷键
ctrl alt l
自动抽取方法
ctrl alt m
变量的批量修改
shift f6
质数的判断
package com.javaparctice.exercise;
public class exercise3 {
public static void main(String[] args){
//判断101-200之间有几个质数,并且把指数打印出来
int count=0;
for(int i=101;i<=200;i++){
Boolean flag=true;
for(int j=2;j<i;j++){
if(i%j==0){
flag=false;
break;//这里停止的是内层循环
}
}
if(flag){
System.out.println(i+"是一个质数");
count++;
}
}
System.out.println("一共有"+count+"个质数");
}
}
二维数组
- 二维数组的静态初始化
例如
int [][] arr=new int [][]{{1,2},{2,3}}
简化的格式
int [][] arr={{1,1},{2,2}}
也可以这样写
int arr[][]={{1,1},{2,2}}
但是推荐使用第一种,根据阿里巴巴的java规范
package com.javaparctice.exercise;
public class exercise4 {
public static void main(String[] args) {
int [][]arr={
{1,2,3,5},
{1,2,3,4}
};//二维数组推荐这样定义,可以方便阅读
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 4; j++) {
System.out.print(arr[i][j]);
}
System.out.println();
}
}
}
获取二维数组的值
例如
package com.javaparctice.exercise;
public class exercise4 {
public static void main(String[] args) {
int [][]arr={
{1,2,3,5},
{1,2,3,4}
};//二维数组推荐这样定义,可以方便阅读
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 4; j++) {
System.out.print(arr[i][j]);
}
}
System.out.print(arr[0]);//表示获取的是二维数组的第一个一维数组(地址值)
System.out.print(arr[0][0]);//表示获取的是第一个一维数组中0索引的元素(数组元素)
}
}
在二维数组中,数组名.length
是有多少个一维数组,而数组名[i].length
则是这个arr[i]的一维数组的数组长度。
- 二维数组的动态初始化
例如
int [][]arr=new int[2][3];
这就表示该数组可以存放两个一维数组,每一个一维数组可以存放三个元素
- 二维数组的内存原理
特殊情况
省略一维数组的长度,不需要java来自动创建数组。(java自动创建的一维数组的长度都是相同的)
第二种情况赋值后,原本java自动创建的数组会被覆盖掉,从而消失
15.面向对象
面向对象的重点
面向对象的三大特征
封装,继承,多态
类和对象(对象的创建和使用)
public class 类名{
}
new 类名()//就是创建或使用了一个类
例如
public class Phone{
//属性(成员变量)
String brand;
double price;
//行为(成员方法)
public void call(){
}
public void playGame(){
}
}
创建对象
例如:
//类
package com.javaparctice.exercise;
public class game {
int a;
String b;
public void c(){
System.out.println("使用电脑");
}
}
//类的使用
package com.javaparctice.exercise;
public class game1 {
public static void main(String[] args) {
game d=new game();
d.a=10;
d.b="这是一个数";
System.out.println(d.a);
System.out.println(d.b);
d.c();
}
}
- 类的注意事项
注意事项
成员变量的初始值(默认值)
在idea中,按着鼠标滚轮不松可以竖着选中代码(或是跟vscode一样,按住alt不松),方便修改 - 开发中类的设计
小技巧:名词提炼法,把需求中的名词都提炼成类的属性
例如
这里的学生管理修改中的学号,姓名,性别等都被提炼成了属性,而验证学号和确认提交则是行为(方法)
类的创建
首先看需求中有几类事务,每一类事务都要提炼成一个单独的类,然后在类中观察,名词等描述一般都可以写成属性,动词,即类需要干啥则可以写成行为(方法)
封装
对象代表什么,就得封装相应的数据,并提供数据对应的行为
例如
人画圆
分为两个对象,人对象,圆对象。
画的方法就得封装在圆里,因为只有圆能提供相应的数据,例如半径。
同理
人关门
分为两个对象,人对象,门对象。
关的方法就得封装在门里。
对象代表什么,就得封装相应的数据,并提供数据对应的行为
这就是封装对象的原则
private关键字
主要是第三条
即只能在本类使用,不能再其他类中使用了。
如果想在其他类中实现赋值,还是要在本类中写方法
//类
package com.javaparctice.exercise;
public class GirlFriend {
private int age;
public void setAge(int a){//给成员变量age赋值
if(a>=18&&a<=40){
age=a;
}else{
System.out.println("数据错误");
}
}
public int getAge(){//对外提供age的值
return age;
}
}
//类的使用
package com.javaparctice.exercise;
public class GirlFriendText{
public static void main(String args[]){
GirlFriend gal=new GirlFriend();
int b=20;
gal.setAge(b);
int a=gal.getAge();
System.out.println(a);
}
}
this关键字
成员变量和局部变量
使用时依照就近原则,即哪个变量离使用的语句近,那个变量就会被使用
如果上述函数想要使用成员变量,就需要在方法中使用this关键字
package com.javaparctice.exercise;
public class thisPlay {
private int age;
public void setAge(){
int age=10;
System.out.println(this.age);
}
}
构造方法
构造方法的特点
构造方法的使用
不能手动调用。
空参构造和带参构造可以同时存在
//类的构造方法
package com.javaparctice.exercise;
public class thisPlay {
private int age;
public thisPlay() {
System.out.println(11111);
}
public thisPlay(int age){
this.age=age;
}
public void setAge() {
int age = 10;
System.out.println(this.age);
}
}
//构造方法的使用
package com.javaparctice.exercise;
public class game1 {
public static void main(String[] args) {
// Game d=new Game();
// d.a=10;
// d.b="这是一个数";
// System.out.println(d.a);
// System.out.println(d.b);
// d.c();
thisPlay one=new thisPlay(10);
}
}
注意事项
构造方法可以进行重载。
无参构造和有参构造都要书写
标准的JavaBean类
书写格式(标准)
构造方法的快捷键
alt+insert
或是
alt+fn+insert
这里可以生成get和set方法。
- 插件ptg
可以用ptg快速生成javabean
对象内存图
但我们运行一个类的时候,这个类的字节码文件就会在元空间(方法区)里运行
创建一个对象时
两个对象的内存图
两个引用指向一个对象
基本数据类型和引用数据类型(本质区别)
基本数据类型
就是在变量中存储的真实的数据值。
引用数据类型
在变量中存储的是一个地址值,地址指向一个空间,空间内存放着真实的数据
this的内存原理
this的作用:
区分局部变量和成员变量
this的本质:
所在方法调用者的地址值
//类
package com.javaparctice.exercise;
public class thisPlay {
public thisPlay() {
System.out.println(11111);
}
private int age=5;
public void setAge() {
int age = 10;
System.out.println(this.age);//this的本质就是所在方法调用者的地址值。
}
}
//方法的使用
package com.javaparctice.exercise;
public class thistext {
public static void main(String[] args) {
thisPlay a=new thisPlay();//这里a的地址是新创建的thisplay类,地址空间里存储着private int age=5;
a.setAge();//serAge方法调用者为a
}
}
this的本质:(这个很重要)
所在方法调用者的地址值
成员变量和局部变量的区别
面向对象的练习
ctrl
+p
在idea中可以显示新建对象的形参顺序,ctrl
+a
可以全选,ctrl
+d
可以向下复制一行
文字格斗游戏
人物类
package com.javaparctice1.exer;
import java.util.Random;
public class Role {
private String name;
private int blood;
public Role() {
}
public Role(String name, int blood) {
this.name = name;
this.blood = blood;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setBlood(int blood) {
this.blood = blood;
}
public int getBlood() {
return blood;
}
public void attack(Role role){//攻击方法
Random ran=new Random();
int r=ran.nextInt(20)+1;//攻击伤害,一个随机数
int s=role.blood-r;//剩余血量
s=s<=0?0:s;//检查剩余血量是否为负数,如果是,就变为零,如果不是,还是剩余的血量
role.setBlood(s);
System.out.println(this.getName()+"发动了一次帅爆的攻击,击中了"+role.getName()+"造成了"+r+"点伤害,"+role.getName()+"还剩"+s+"点血");
}
}
类的测试(使用)
package com.javaparctice1.exer;
public class gametest {
public static void main(String[] args) {
//创建人物
Role one=new Role("老八",100);
Role two=new Role("奥里给",100);
//循环,判断是谁胜利
while(true){
one.attack(two);
if(two.getBlood()==0){
System.out.println(one.getName()+"击败了"+two.getName());
break;
}
two.attack(one);
if(one.getBlood()==0){
System.out.println(two.getName()+"击败了"+one.getName());
break;
}
}
}
}
%s
占位符
适用于System.out.printf()
语句
与c语言类似
占位符中的数据就是,
后面的数据。
91集,有时间可以把升级版再做一遍
简单的对象数组
//类
package com.javaparctice1.exer;
public class Goods {
private String id;
private String name;
private double price;
private int count;
public Goods(){
}
public Goods(String id, String name, double price, int count) {
this.id = id;
this.name = name;
this.price = price;
this.count = count;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
//测试
package com.javaparctice1.exer;
public class GoodsTest {
public static void main(String[] args) {
Goods []arr=new Goods[3];
Goods g1=new Goods("001","华为mate40",2500.00,20);
Goods g2=new Goods("002","小米13pro",2100.00,30);
Goods g3=new Goods("003","iphone14promax",8000.00,10);
arr[0]=g1;
arr[1]=g2;
arr[2]=g3;
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i].getId()+" "+arr[i].getName()+" "+arr[i].getPrice()+" "+arr[i].getCount());
}
}
}
键盘录入
- 第一套体系的特性
当输入空格时
第二次输出时不会再重新输入,会使用内存中剩余的数据,上例就是第一句输出abc,第二句输出bcd。 - 第二套体系的特性
注:键盘录入的两套体系是不能混用的。
键盘录入的数组对象
与上面的数组对象一个类
package com.javaparctice1.exer;
import java.util.Scanner;
public class GoodsTest {
public static void main(String[] args) {
Goods []arr=new Goods[3];//注意,这里对象的创建一定要在循环里,这样才可以把每个对象的属性赋值给数组
Scanner sc=new Scanner(System.in);
for (int i = 0; i < arr.length; i++) {
Goods g=new Goods();
System.out.println("请输入id号");
String id=sc.next();
g.setId(id);
System.out.println("请输入名称");
String name=sc.next();
g.setName(name);
System.out.println("请输入价格");
double price=sc.nextDouble();
g.setPrice(price);
System.out.println("请输入剩余");
int count=sc.nextInt();
g.setCount(count);
arr[i]=g;
}
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i].getId()+" "+arr[i].getName()+" "+arr[i].getPrice()+" "+arr[i].getCount());
}
}
}
先写主干,再根据要求去写要求的代码。
//类
package com.javaparctice1.exer;
public class Student {
private int id;
private String name;
private int age;
public Student() {
}
public Student(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
//代码主体
package com.javaparctice1.exer;
public class StudentTest {
public static void main(String[] args) {
Student[] arr = new Student[3];
Student stu1=new Student(1,"李华",18);
Student stu2=new Student(2,"小明",19);
// Student stu3=new Student(3,"xxx",17);
arr[0]=stu1;
arr[1]=stu2;
// arr[2]=stu3;
Student stu4=new Student(4,"ccc",21);
boolean flag=contains(arr,stu4.getId());//判断数组id是否唯一
if(flag){//当flag为true时,证明数组已经存在相同的id,为false时,就会进行新的判断。
System.out.println("当前id已经存在");
}else{
int count=getCount(arr);//获取数组内元素的个数
if(arr.length==count){//当if为true时,证明数组已经存满,就会创建一个新的数组,为false时,会把对象直接加到数组里。
Student[]newArr=createNewArr(arr);
newArr[count]=stu4;
printArr(newArr);
}else{
arr[count]=stu4;
printArr(arr);
}
}
}
//创建方法时,思考一下这几个问题。
//我要干嘛?
//我干这件事,需要什么?
//调用处是否需要方法的结果?
//遍历数组
public static void printArr(Student []arr){
for (int i = 0; i < arr.length; i++) {
Student stu =arr[i];
if(stu!=null){
System.out.println(arr[i].getId()+" "+arr[i].getName()+" "+arr[i].getAge());
}
}
}
//创建新数组
public static Student[] createNewArr(Student []arr){
Student []newArr=new Student[arr.length+1];
for (int i = 0; i < arr.length; i++) {
newArr[i]=arr[i];
}
return newArr;
}
//判断数组元素是否已经存满
public static int getCount(Student []arr){
int count=0;
for (int i = 0; i < arr.length; i++) {
if(arr[i]!=null){
count++;
}
}
return count;
}
//判断数组id是否唯一
public static boolean contains(Student[] arr, int id){
for (int i = 0; i < arr.length; i++) {
if(arr[i]!=null){
int a=arr[i].getId();
if(id==a){
return true;
}
}
}
return false;
}
}
上述代码在写的时候的逻辑。
16.API&字符串
api
API:
例如:Java的随机数random
java.lang
包
使用java.lang
包时,不需要import进行导类
在这里可以寻找关键字
api的使用方法
String
注:字符串的内容是不会发生改变的,即字符串对象在创建后不能被修改
上述代码并没有改变第一个字符串,而是创建了一个新的字符串,把原来的字符串给代替了。
创建String对象的两种方式
- 直接赋值
- 用new去构造一个方法
字符串的创建方式
package javaparctice2.exer;
public class StringTest {
public static void main(String[] args) {
//直接赋值
String a="1111";
//空参构造
String b=new String();
System.out.println("1"+b+"2");
//传递字符串(了解)
String c=new String("abc");
//传递字符数组
//应用环境:修改字符串 例如把abc 改成qbc
//abc-->{'a','b','c'}-->{'q','b','c'}-->""qbc
char[] d={'w','s','s','b'};
String e=new String(d);
System.out.println(e);
//传递字节数组
//应用场景:在网络中传输的都是字节信息,把字节信息转换成字符串
byte[] f={97,98,99,100};
//这里不是把数字传递过去,而是把数字转成ASCLL表里的字符。
String g=new String(f);
System.out.println(g);
}
}
- 字符串在内存中的存储方式
字符串常量池
只有直接赋值的字符串才会在这个空间中
在堆内存中
当串池中已经存在一个abc字符串时,再次给字符串变量赋值相同的值时,会直接复用之前相同字符串的地址。
每new一次,就会在堆空间创建一个新的空间,可能会造成内存的浪费
所以,还是推荐使用直接赋值创建字符串。
字符串的比较
- ==号比较的原理
比较字符串的内容。
package javaparctice2.exer;
public class bijiaotest {
public static void main(String[] args) {
String a="abc";
String b=new String("abc");
boolean c=a.equals(b);
if(c){
System.out.println("a与b的内容相同");
}
}
}
package javaparctice2.exer;
public class bijiaotest {
public static void main(String[] args) {
String a="abc";
String b=new String("Abc");
boolean c=a.equals(b);
if(c){
System.out.println("a与b的内容相同");
}else{
System.out.println("a与b的内容不同");
}
boolean d=a.equalsIgnoreCase(b);
if(d){
System.out.println("a与b的内容相同,大小写可能不同");
}else {
System.out.println("a与b的内容绝对不同");
}
}
}
练习(包括一些新的字符串方法,CharAt,substring,replace)
- 用户登录
ctrl
+b
可以查看Java的源码
键盘录入得到的字符串是new出来的,所以,存储在堆地址中
package javaparctice2.exer;
import java.util.Scanner;
public class userLogin {
public static void main(String[] args) {
String user="zhangsan";
String password="123456";
for (int i=0;i<3;i++) {
Scanner sc=new Scanner(System.in);
System.out.println("请输入用户名");
String ouser=sc.next();
System.out.println("请输入密码");
String opassword=sc.next();
if(user.equals(ouser)&&password.equals(opassword)){
System.out.println("登录成功");
break;
}else{
System.out.println("登录失败");
}
}
}
}
把代码包起来后,用ctrl
+alt
+t
可以进行
- 遍历字符串(统计数据个数)
数组的长度是一个属性,而字符串的长度是一个对象。
package javaparctice2.exer;
import java.util.Scanner;
public class statisticsTest {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
System.out.println("请输入一个字符串");
String statistics=sc.next();
int bigCount=0;
int smallCount=0;
int numberCount=0;
for (int i = 0; i < statistics.length(); i++) {
char b=statistics.charAt(i);
if(b>='A'&&b<='Z'){
bigCount++;
} else if (b>='a'&&b<='z') {
smallCount++;
}else if(b>='1'&&b<='9'){
numberCount++;
}
}
System.out.println("大写字母的个数为:"+bigCount+" 小写字母的个数为:"+smallCount+" 数字的个数为:"+numberCount);
}
}
在idea中,forr是倒着遍历
shift
+f6
批量修改
- 金额转换
package javaparctice2.exer;
import java.util.Scanner;
/*
反推,由答案一步一步推向开始。
*/
public class AmountConversion {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int money;
while (true) {
System.out.println("请输入金额");
money = sc.nextInt();
if (money >= 0 && money <= 9999999) {
break;
} else {
System.out.println("输入的金额有误");
}
}
String numb = "";
while (true) {
int num = money % 10;
String chine = getMajuscule(num);
numb = chine + numb;
money = money / 10;
if (money == 0) {
break;
}
}
// System.out.println(numb);
int count = numb.length();
for (int i = 0; i < 7 - count; i++) {
numb = "零" + numb;
}
String result="";
// System.out.println(numb);
String[] arr = {"佰", "拾", "万", "千", "佰", "拾", "元"};
for (int i = 0; i < numb.length(); i++) {
char a=numb.charAt(i);
result=result+a+arr[i];
}
System.out.println(result);
}
public static String getMajuscule(int number) {
String[] chinese = {"零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "", "捌", "玖"};//数字表
return chinese[number];
}
}
可以从一个大的字符串中把字符截取出来
两个参数就是索引,表示从beginindex开始,到endindex结束
例如
a.substring(0,2);
表示从0开始,截取到2但是不包括2;
从参数截取到末尾
StringBuilder
使用StringBuilder的场景:1.字符串的拼接 2.字符串的反转
相当于一个字符串的工具
StringBuilder是一个对象
内容可以发生变化,极大的提高了字符串的操作效率
-
StringBuilder的创建
-
StringBuilder的常用方法
即打印StringBuilder是打印出来的是属性值不是地址值 -
sb.append(); 添加方法
package javaparctice2.exer;
public class stringBuilder {
public static void main(String[] args) {
StringBuilder sb=new StringBuilder("abc");//创建StringBuilder对象
sb.append(1);
sb.append(2.5);
sb.append("ccd");
System.out.println(sb);
}
}
- sb.reverse(); 反转方法
package javaparctice2.exer;
public class stringBuilder {
public static void main(String[] args) {
StringBuilder sb=new StringBuilder("abc");//创建StringBuilder对象
sb.append(1);
sb.append(2.5);
sb.append("ccd");
sb.reverse();
System.out.println(sb);
}
}
- sb.length(); 获取长度
- sb.toString(); 变回字符串,因为StringBuilder是一个对象,是容器,不是字符串,所以需要用toString方法来把StringBuilder内的东西变回字符串
链式编程
package javaparctice2.exer;
public class stringBuilder {
public static void main(String[] args) {
StringBuilder sb=new StringBuilder("abc");//创建StringBuilder对象
String c="abcdef";
System.out.println(c.substring(1).length());
}
}
StringJoiner
StringJoiner对象的创建
StringJoiner的构造
StringJoiner sj=new StringJoiner(",");//一个参数就是间隔符号
StringJoiner sj1=new StringJoiner(",","[","]")//三个参数就分别是间隔符号,开始符号,结束符号
StringJoiner的方法
StringJoiner sj=new StringJoiner("---");
sj.add("aaa").add("bbb").add("ccc");
显示为
这里的add是添加元素,即将要被分离的元素
这里的length返回的是所有字符的总个数
字符串的底层原理
- 字符串拼接的底层原理
当等号的左边没有变量时
当等号的左边有变量时
ctrl
+n
打开idea的搜索引擎
ctrl
+f12
也是搜索
即在jdk8之前,一个+号至少会在堆内存里添加两个对象
在jdk8之后,在编译时,会先预估字符串的长度并创建一个数组,再把数组变成一个字符串
只要有变量参与,就会建立一个新的字符串
练习
- 转换罗马数字
package javaparctice2.exer;
import java.util.Scanner;
public class luoma {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
String str;
while(true){
System.out.println("请输入字符串");
str=sc.next();
if(judgeNumber(str)){
break;
}else{
System.out.println("请输入正确的字符串");
}
}
System.out.println(conversion(str));
}
public static String conversion(String str){
StringBuilder sb=new StringBuilder();
String []arr={" ","I","II","III","IV","V","VI","VII","VIII","IX"};
for (int i = 0; i < str.length(); i++) {
int a=str.charAt(i)-48;
sb.append(arr[a]+" ");
}
return sb.toString();
}
public static boolean judgeNumber(String str){
if(str.length()>9){
return false;
}
for (int i = 0; i < str.length(); i++) {
char c=str.charAt(i);
if (c< '0' || c> '9') {
return false;
}
}
return true;
}
}
字符串的方法
toCharArray(),可以把一个字符串变成一个字符数组。
17.ArrayList(集合,还有一些方法)
集合是一个容器,可以用来存储数据,和数组不同的是,集合的长度可以发生变化。
当添加元素时,会自动扩容。
集合可以存储引用数据类型,不能直接存储基本数据类型,要把基本数据类型变成其对应的包装类才能存储。
- ArrayList集合的创建
package com.javaparctice3.exer;
import java.util.ArrayList;
public class ArrayListDemo1 {
public static void main(String[] args) {
ArrayList<String> list=new ArrayList<String>();
//这里的<>就是泛型。上面的代码时jdk7以前的写法,现在可以这样写
ArrayList<String> listOne=new ArrayList<>();
//即后面的类型可以不写,只写<>
}
}
注意
- ArrayList的成员方法
add()和remove的返回值都是布尔值,add默认返回true,不用管,而remove在删除成功时返回true,失败时返回false
同时,remove还可以根据索引进行删除,返回值是被删除的元素
set是修改指定索引上的值,返回被覆盖的值。
get就是获取相应索引的元素
size就是集合的长度,类似于length
package com.javaparctice3.exer;
import java.util.ArrayList;
public class ArrayListDemo1 {
public static void main(String[] args) {
ArrayList list=new ArrayList();
list.add("aaa");
list.add("aaa");
list.add("bbb");
list.add("ccc");
System.out.println(list);
if(list.remove("aaa")){
System.out.println("删除成功");
}else{
System.out.println("删除失败");
}
System.out.println(list.remove(2));
System.out.println(list.set(1,"ddd"));
System.out.println(list.get(1));
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
练习
package com.javaparctice3.exer;
import java.util.ArrayList;
public class ArrayListDemo2 {
public static void main(String[] args) {
ArrayList<String> list=new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
System.out.print("[");
for (int i = 0; i < list.size(); i++) {
if(i<(list.size()-1)){
System.out.print(list.get(i));
System.out.print(",");
}else{
System.out.print(list.get(i));
}
}
System.out.print("]");
}
}
基本数据类型的包装类
这里的包装类实际上就是泛型里的关键字
除了int和char变成Integer和Character,其余的都是首字母变成大写
ArrayList<Integer> list=new ArrayList<>();//泛型为Integer后,
//就可以直接添加int类型的数据了
list.add(1);
list.add(2);
//同理,Character也是如此
向集合中添加自定义对象
这里添加的对象值实际上是对象的地址值(除了java自带的类)
//student类
package com.javaparctice3.exer;
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;
}
}
//对象添加到集合的操作
package com.javaparctice3.exer;
import java.util.ArrayList;
public class ArrayListDemo3 {
public static void main(String[] args) {
ArrayList<student> list=new ArrayList<>();
student s1=new student("zhangsan",20);
student s2=new student("zhangsi",21);
student s3=new student("zhangwu",22);
list.add(s1);
list.add(s2);
list.add(s3);
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
//这里由于不是java本身提供的类,所以显示的是地址值
System.out.println(list.get(i).getName()+" "+list.get(i).getAge());
}
}
}
注意,这里代码不会执行for循环,因为集合的长度是可以发生变化的,刚开始集合的长度是0,所以,不会执行for循环
package com.javaparctice3.exer;
import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
ArrayList<student> list=new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
System.out.println("1111");
}
}
}
只要有new 就会创建一个新的对象,开辟一块新的空间,在循环里也是如此
package com.javaparctice3.exer;
import java.util.ArrayList;
public class Test2 {
public static void main(String[] args) {
ArrayList<User>list=new ArrayList<>();
User u1=new User(01,"zhangsi",111111);
User u2=new User(02,"zhangs",1111111);
User u3=new User(03,"zhang",11111111);
list.add(u1);
list.add(u2);
list.add(u3);
boolean flag=judgeId(list,01);
System.out.println(flag);
}
public static boolean judgeId(ArrayList<User>list, int id){
for (int i = 0; i < list.size(); i++) {
if(list.get(i).getId()==id){
//如果找到直接返回true
return true;
}
}
return false;
}
}
package com.javaparctice3.exer;
import java.util.ArrayList;
public class Test3 {
public static void main(String[] args) {
ArrayList<Phone>list=new ArrayList<>();
Phone p1=new Phone("小米",2000);
Phone p2=new Phone("华为",2999);
Phone p3=new Phone("iphone",8000);
list.add(p1);
list.add(p2);
list.add(p3);
ArrayList<Phone>PhoneList=judgePrice(list);//集合返回值的接受
//遍历新集合
for (int i = 0; i < PhoneList.size(); i++) {
System.out.println(PhoneList.get(i).getBrand()+" "+PhoneList.get(i).getPrice());
}
}
public static ArrayList<Phone> judgePrice(ArrayList<Phone> list){
//定义一个集合用于存储手机(Phone)对象
ArrayList<Phone>resultList=new ArrayList<>();
//for循环用于遍历集合中的每个元素,价格低于3000的对象添加到上面的集合中去
for (int i = 0; i < list.size(); i++) {
if(list.get(i).getPrice()<3000){
resultList.add(list.get(i));
}
}
return resultList;//返回集合
}
}
18.学生管理系统(大型练习,第一个项目)
- 学会阅读需求文档
以后真的要写项目的话,在了解需求文档后,自己画出需求的结构流程图(业务流程图)
不要只在脑子里想,一定要画出来,清除的了解到业务逻辑,和项目的结构
结构一定要清晰
- java版本换成jdk17了😆
完整代码
package com.FristProject;
import java.util.ArrayList;
import java.util.Scanner;
public class StudentSystem {
public static void main(String[] args) {
ArrayList<Student>list=new ArrayList<>();
loop:
while (true) {//给循环起名为loop
System.out.println("----------欢迎来到学生管理系统-----------");
System.out.println("请输入您的选择:");
System.out.println("1:添加学生");
System.out.println("2:删除学生");
System.out.println("3:修改学生");
System.out.println("4:查询学生");
System.out.println("5:退出");
Scanner sc = new Scanner(System.in);
String choose = sc.next();//用next的容错率高,当用户输入一些不相关的内容时也可以给予提示
switch (choose) {
case "1" -> addStudent(list);
case "2" -> delStudent(list);
case "3" -> updateStudent(list);
case "4" -> queryStudent(list);
case "5" -> {
System.out.println("退出");
break loop;//跳出loop循环
//第二种方法:System.exit(),执行这个语句后会停止虚拟机的运行
}
default -> System.out.println("请输入正确的数字");
}
}
}
//添加学生对象
public static void addStudent(ArrayList<Student> list) {
Scanner sc=new Scanner(System.in);
String id= null;
while (true) {
System.out.println("请输入学生的id");
id = sc.next();
if(judgeId(list,id)){
System.out.println("此id已经存在");
}else {
break;
}
}
System.out.println("请输入学生的姓名");
String name=sc.next();
System.out.println("请输入学生的年龄");
int age=sc.nextInt();
System.out.println("请输入学生的家庭住址");
String HomeDress= sc.next();
Student sd=new Student(id,name,age,HomeDress);
list.add(sd);
System.out.println("学生信息添加成功");
}
//删除学生对象
public static void delStudent(ArrayList<Student> list) {
Scanner sc=new Scanner(System.in);
System.out.println("请输入要删除学生的id");
String id=sc.next();
int sid=getIndex(list,id);
if(sid>=0){
System.out.println("id为"+list.get(sid).getId()+"的学生信息已被删除");
list.remove(sid);
}else{
System.out.println("所删除id的学生不存在");
}
}
//修改学生对象的信息
public static void updateStudent(ArrayList<Student> list) {
Scanner sc=new Scanner(System.in);
System.out.println("请输入要修改学生信息的学生id");
String id=sc.next();
int index=getIndex(list,id);
if(index<0){
System.out.println("您输入的学生id不存在,请重新输入");
return;
}
Student sd=list.get(index);
System.out.println("请输入要修改的学生姓名");
String newName=sc.next();
sd.setName(newName);
System.out.println("请输入要修改的学生年龄");
int newAge=sc.nextInt();
sd.setAge(newAge);
System.out.println("请输入要修改的学生家庭地址");
String newHomeDress=sc.next();
sd.setHomeDress(newHomeDress);
}
//查询所有学生对象的信息
public static void queryStudent(ArrayList<Student> list) {
if(list.size()==0){
System.out.println("请添加学生信息后再进行查询");
return;
}
System.out.println("id\t\t姓名\t年龄\t家庭住址");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i).getId()+"\t"+list.get(i).getName()+"\t"+list.get(i).getAge()+"\t"+list.get(i).getHomeDress());
}
}
//判断id是否存在
public static boolean judgeId(ArrayList<Student> list,String id){
/* for (int i = 0; i < list.size(); i++) {
if(list.get(i).getId().equals(id)){
return true;
}
}
return false;*/
return getIndex(list,id)>=0;//提高代码的复用性
}
//判断学生id,并返回对象在集合里的索引
public static int getIndex(ArrayList<Student> list,String id){
for (int i = 0; i < list.size(); i++) {
if(list.get(i).getId().equals(id)){
return i;
}
}
return -1;
}
}
19.面向对象进阶
static
static(静态变量)(静态变量被该类所有对象共享)
static
- 静态变量
- 特点:
-
- 静态变量被该类所有对象共享
- 调用方式:
-
- 类名调用(推荐)
-
- 对象调用
例如
//javabean类
package com.none.staticstudy;
public class Student {
private int age;
private String name;
private String gender;
public static String teacherName;
public Student() {
}
public Student(int age, String name, String gender) {
this.age = age;
this.name = name;
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public void study(){
System.out.println(age+","+name+","+gender+","+teacherName);
}
}
//测试使用
package com.none.staticstudy;
public class tset {
public static void main(String[] args) {
Student s1=new Student();
Student s2=new Student();
Student.teacherName="abc";
s1.study();
s2.study();
//这里s1与s2的teacher名都是abc,即teacherName这个属性的值对所有对象是共享的
}
}
当这里的teacherName被赋值后,会被Student类里所有对象共享(公共的一个属性)
静态变量是随着类的加载而加载的,优先于对象出现
静态变量在堆内存中有单独的存储空间
静态区里的变量是对象共享的,在内存中只有一份,谁用谁拿,非静态的变量是每一个对象所独有的,每一个对象里的非静态的变量都是单独存放的。
判断一个变量是否是静态变量,主要就是两个字,共享
需要共享的变量就是静态变量
static(静态方法)
- 特点和调用方式
工具类
目前一共学习了三种类
- javabean类
- 测试类
- 工具类
工具类的要求
这里的2
私有化构造方法就是
package com.none.staticstudy;
public class Student {
private public Student() {
}
}
一个类一旦被私有化构造方法后,就不能创建这个类的对象了。
工具类的创建和使用
//工具类的创建
package com.none.staticstudy;
public class ArrayUtil {
private ArrayUtil(){};
public static String printArr(int []arr){
StringBuilder sb=new StringBuilder();
sb.append("[");
for (int i = 0; i < arr.length; i++) {
if(i==arr.length-1){
sb.append(arr[i]);
}else{
sb.append(arr[i]+",");
}
}
sb.append("]");
String array=sb.toString();
return array;
}
public static double getAerage(double []arr){
double sum=0;
for (int i = 0; i < arr.length; i++) {
sum=sum+arr[i];
}
double aerage=sum/(arr.length-1);
return aerage;
}
}
//工具类的使用
package com.none.staticstudy;
public class tset {
public static void main(String[] args) {
int []arr1={1,2,3,4,5};
double[]arr2={1.0,1.1,1.9,4.7};
String sc=ArrayUtil.printArr(arr1);
double sg=ArrayUtil.getAerage(arr2);
System.out.println(sc);
System.out.println(sg);
}
}
static注意事项
即
在非静态方法中,有一个隐藏的this,这个this就表示当前方法调用者的地址值
在第一次调用show()方法时,虚拟机就会把第一次调用者的地址赋值给方法中的this,就像图中所示
第二次同理,虚拟机会把第二次调用者的地址赋值给方法中的this
this的值
this的这一特性就是区分不同对象的
即在调用成员变量时,变量前面有隐含的this,从而显示出不同变量的结果
上面sout里的this可以省略
而在静态方法中,是没有这个this关键字的
原因就是静态方法是公用的,而this一般是独立的对象进行使用,不符合公用的理念。
因为在使用静态方法时,通过类名调用会直接到静态存储位置去找变量,而非静态的变量是随着对象的创建而创建的,在静态存储位置找不到,所以静态方法不能调用非静态成员变量
因为非静态的方法需要调用者,而在静态方法中是没有this的,也就没有调用者,自然就不能使用了
综上,静态方法只能调用静态的内容
因为静态的是公用的,所以非静态既可以访问到静态,也可以访问非静态
static
重新认识main方法
JVM就是虚拟机
继承
继承的概述
封装
继承实际上就是类跟类之间的父子关系
使用继承的好处
继承的学习点
- 自己设计一个继承类
- 如何使用别人的继承类
什么时候时使用继承
小结
继承的特点
单继承:
即一个子类只能继承一个父类
不能多继承:
子类不能同时继承多个父类
支持多层继承:
即儿子继承父亲,父亲继承爷爷
即子类的父类是直接父类,父类的父类是子类的间接父类
java中有一个祖宗类就是Object,每一个类都直接或间接继承于Object
小结
设计继承体系时,尽量先搞清楚体系逻辑,画一张图,从子类到父类
设计的核心就是:共性的内容抽取,子类是父类的一种
书写代码时,从父类到子类
注意:当父类中的方法用private修饰时,子类就无法访问了,被private修饰的方法只能在本类中访问
子类继承父类的具体内容(内存图分析)
- 构造方法:不论私有(private)还是非私有(像public等)的构造方法,子类都是不能继承的
- 成员变量:不论私有(private,不能直接调用)还是非私有(像public等)的成员变量,子类都是可以继承的(相当于从父类哪里copy了一份过来)
- 成员方法:非私有的成员方法(虚方法)可以被继承,私有的成员方法就不能被继承下来
没有用private修饰的继承成员变量的内存图
-
这里new出来一个子类后,会把堆内存中开辟出来的空间分为两部分,第一部分是继承父类的成员变量,第二类是子类自己的成员变量
-
如果父类的成员变量使用private修饰后,子类对象在堆内存中就无法使用继承父类的成员变量。
-
父类会把非private,非static和非final的方法(这些方法统称为虚方法)抽取出来,放到虚方法表里,在继承的时候,父类会把虚方法表交给子类复制,子类可以添加新的虚方法加入到子类的虚方法表中,使后代继续复制
可以大大提高性能
继承中成员变量和成员方法的访问特点
- 继承的成员变量的访问特点:
就近原则:谁离我近,我就用谁
即优先级为:
方法内的成员变量>子类的成员变量>父类的成员变量
在使用一个成员变量时,会先在局部的方法内找,如果方法内没有,就会到本类(子类)中去找,本类中再没有,就会到父类中去找
在上述代码中,name就是“ziShow”,如果“ziShow”没有,name就是“Zi”,如果“Zi”再没有,那么name就是“Fu”
当然,也可以同时访问这三个成员变量
package com.inherit.study;
public class Test {
public static void main(String[] args) {
Zi zi=new Zi();
zi.ziShow();
}
}
class Fu{
String name="Fu";
}
class Zi extends Fu{
String name="Zi";
public void ziShow(){
String name="ziShow";
System.out.println(name);
System.out.println(this.name);
System.out.println(super.name);
}
}
即使用this关键字可以访问本类的成员变量,使用super关键字可以访问父类的成员变量。
语法:
this.变量名
super.变量名
注:在子类中最多使用一个super
- 继承的成员方法的访问特点:
直接调用:满足就近原则,谁近调用谁
super调用:直接访问父类
与上面成员变量的特点一样,都是就近原则,都是this,super
package com.inherit.study;
public class Test {
public static void main(String[] args) {
Zi zi=new Zi();
zi.show();
}
}
class Fu{
String name="Fu";
public void eat(){
System.out.println("chifan,父类被调用");
}
}
class Zi extends Fu{
String name="Zi";
//这里进行了方法的重写,由于父类的方法不能满足子类的需求,从而进行了方法的重写
@Override
public void eat(){
System.out.println("heshui,子类被调用");
}
public void show(){
eat();
this.eat();
super.eat();
}
}
-
方法的重写
当父类的方法不能满足子类现在的需求时,需要进行方法的重写
书写格式:
在继承中,子类出现和父类一样的方法,就称子类的这个方法为重写的方法
@Override:
重写的注解,给程序员和虚假机看的,可以用来看语法是否正确
方法重写的本质:新的方法覆盖旧的方法(虚方法表)
方法重写的注意事项:
重写方法时也可以调用父类的方法 -
小结
继承中构造方法的访问特点
- 特点
1.父类中的构造方法不会被子类继承
2.子类中所有的构造方法默认先访问父类中的无参构造,再执行自己
第二条出现的原因
所以,子类的构造方法的第一句都会是super()
,即使不写也会存在于第一行。
这里的super()
就是调用父类的空参构造方法
如果想调用父类的有参构造,就必须手动写super进行调用
- 小结
//父类
package com.inherit.study;
public class Person {
String name;
int age;
public Person() {
System.out.println("父类的空参构造");
}
public Person(String name,int age){
this.name=name;
this.age=age;
}
}
//子类
package com.inherit.study;
public class Student extends Person {
public Student() {
System.out.println("子类的空参构造");
}
public Student(String name, int age) {
super(name, age);
}
}
//测试类
package com.inherit.study;
public class Test2 {
public static void main(String[] args) {
Student s=new Student("zhangsan",16);
System.out.println(s.name+" "+s.age);
}
}
this和super的使用总结
- this
this就相当于一个局部变量,表示当前方法调用者的地址值 - super
super就代表父类的存储空间
this与super的区别
关键字 | 访问成员变量 | 访问成员方法 | 访问构造方法 |
---|---|---|---|
this | this.成员变量 访问本类成员变量 | this.成员方法 访问本类成员方法 | this(…) 访问本类构造方法 |
super | super.成员变量 访问父类成员变量 | super. 访问父类成员方法 | super(…) 访问父类构造方法 |
使用this方法来访问本类构造方法
package com.inherit.study;
public class Person {
String name;
int age;
String gender;
public Person() {
this(null,0,"男");
}
public Person(String name,int age,String gender){
this.name=name;
this.age=age;
this.gender=gender;
}
}
这里空参构造里的this(null,0,"男");
就是使用this来访问构造方法,并赋予初始值
且,使用this()
后,虚拟机就不会自动添加super()
了
例子:
//员工类
package com.inherit2.study;
public class Employee {
private String id;
private String name;
private String salary;
public Employee() {
}
public Employee(String id, String name, String salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSalary() {
return salary;
}
public void setSalary(String salary) {
this.salary = salary;
}
public void work(){
System.out.println("员工在摸鱼");
}
public void eat(){
System.out.println("吃饭");
}
}
//经理类
package com.inherit2.study;
public class Manager extends Employee{
private int bouns;
public Manager() {
}
public Manager(String id, String name, String salary, int bouns) {
super(id, name, salary);
this.bouns = bouns;
}
public int getBouns() {
return bouns;
}
public void setBouns(int bouns) {
this.bouns = bouns;
}
@Override
public void work() {
System.out.println("管理其他人");
}
}
//厨师类
package com.inherit2.study;
public class Cook extends Employee{
@Override
public void work() {
System.out.println("炒菜");
}
}
//测试类
package com.inherit2.study;
public class Test {
public static void main(String[] args) {
Manager m=new Manager("001","zhangsan","2000",1000);
Cook ck=new Cook();
ck.setId("002");
ck.setName("lisi");
ck.setSalary("3000");
System.out.println(m.getId()+" "+m.getName()+" "+m.getSalary()+" "+m.getBouns());
m.eat();
m.work();
System.out.println(ck.getId()+" "+ck.getName()+" "+ck.getSalary());
ck.eat();
ck.work();
}
}
多态
多态的概念
封装是多态的必要条件,继承是多态的前提条件
- 多态的含义
多种形态,同种类型的对象表现出的不同形态
可以以父类来创建子类对象,即使用父类型作为参数,可以接收所有子类对象
多态语法
父类类型 对象名称=子类对象
多态可以根据调用对象的不同,使用对应的方法
例如
Person man=new Student()
,这里Person是父类,Student是子类
但是多态的使用要满足以下前提
//父类Person
package com.inherit.study;
public class Person {
String name;
int age;
String gender;
public Person() {
}
public Person(String name,int age,String gender){
this.name=name;
this.age=age;
this.gender=gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public void show(){
System.out.println(name+" "+age);
}
}
//子类Student
package com.inherit.study;
public class Student extends Person {
@Override
public void show(){
System.out.println("学生的信息为:"+getName()+" "+getAge());
}
}
//子类Teacher
package com.inherit.study;
public class Teacher extends Person{
@Override
public void show() {
System.out.println("老师的信息为:"+getName()+" "+getAge());
}
}
//测试类
package com.inherit.study;
public class test3 {
public static void main(String[] args) {
Person pr=new Person("abc",16,"nan");
Student st=new Student();
st.setName("acc");
st.setAge(18);
st.setGender("nv");
Teacher tc=new Teacher();
tc.setName("acb");
tc.setAge(19);
tc.setGender("nv");
regrster(pr);
regrster(st);
regrster(tc);
}
public static void regrster(Person m){
m.show();
}
}
多态调用成员的特点
父类 对象名=new 子类()
遵循两个口诀
变量调用:编译看左边,运行也看左边
方法调用:编译看左边,运行看右边。
-
编译看左边的解释:
javac编译代码时,会看左边的父类有没有这个变量或方法,如果有,编译成功,如果没有,编译失败,即必须要有父类的方法 -
运行也看左边的解释:
javac运行代码的时候,实际获取的就是左边父类中成员变量的值。
即对象调用遵循以上两个规则。
同理,方法调用也是如此,只不过在运行时会运行子类的方法,即 右边
//父类Person
package com.inherit.study;
public class Person {
String name="人";
int age;
String gender;
public Person() {
}
public Person(String name,int age,String gender){
this.name=name;
this.age=age;
this.gender=gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public void show(){
System.out.println(name+" "+age);
}
}
//子类Student
package com.inherit.study;
public class Student extends Person {
String name="学生";
@Override
public void show(){
System.out.println("学生的信息为:"+getName()+" "+getAge());
}
}
//测试类
package com.inherit.study;
public class test3 {
public static void main(String[] args) {
Person pr=new Student();
System.out.println(pr.name);
pr.show();
}
}
显示为
可以看到,父类的成员变量显示出来,子类的成员变量没有显示,并且子类的方法被运行,父类的方法没有运行
子类的方法能运行的原因是子类重写的方法是虚方法,在虚方法表里覆盖了父类的虚方法。
如果是子类 对象名=子类()
这种形式调用成员变量时,会先在子类的内存空间寻找成员变量,子类的内存空间没有时,才会到父类的内存空间去找,这是继承的特性,而在多态中,父类 对象名=new 子类()
如果父类里没有相应的成员变量,会直接报错。
多态的优势和弊端
- 优势
- 在多态模式下,右边对象可以实现解耦合,便于扩展和维护
即只需把后面标红的代码修改就可以了 - 定义方法时,使用父类型作为参数,可以接受所有子类对象,体现多态的扩展性和便利。
- 弊端
不能调用子类的特有功能
因为子类的特有方法不是重写的,是子类独有的,父类里没有,所以不能调用,会报错。
解决方案:
用强制类型转换把父类型转换为子类型,不能随便转换,要对应着转换
这样转就会报错
可以用instanceof
关键字来判断是不是相应类型
对象名 instanceof 类
如果是相应类型,会返回true,否则返回false
java14的新特性
对象名 instanceof 类 变量名
如果是相应类型,会直接变成相应类型的变量,否则结果是false
Person a=new Student();
if(a intanceof Student b){
b.Student的独有方法
}else{
sout("这个对象不是Student类型")
}
小结
练习
可以利用多态简化代码
package com.inherit3.study;
public class Animal {
private int age;
private String color;
public Animal() {
}
public Animal(int age, String color) {
this.age = age;
this.color = color;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public void eat(String something){
System.out.println("吃"+something);
}
}
------------------------------
package com.inherit3.study;
public class Dog extends Animal{
public Dog() {
}
public Dog(int age, String color) {
super(age, color);
}
@Override
public void eat(String something){
System.out.println("狗吃"+something);
}
public void lookHome(){
System.out.println("狗在看家");
}
}
------------------------------
package com.inherit3.study;
public class Cat extends Animal{
public Cat() {
}
public Cat(int age, String color) {
super(age, color);
}
@Override
public void eat(String something){
System.out.println("猫吃"+something);
}
}
------------------------------
package com.inherit3.study;
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void keepPet(Animal animal,String something){
if(animal instanceof Dog d){
System.out.println("年龄为"+age+"的"+name+"养了一只"+d.getColor()+"的"+d.getAge()+"的狗");
d.eat(something);
}else if(animal instanceof Cat c){
System.out.println("年龄为"+age+"的"+name+"养了一只"+c.getColor()+"的"+c.getAge()+"的狗");
c.eat(something);
}
}
}
------------------------------
package com.inherit3.study;
public class test1 {
public static void main(String[] args) {
Person ph=new Person("老刘",30);
Person pr=new Person("老i",39);
Animal dog=new Dog(3,"hei");
Animal cat=new Cat(2,"white");
ph.keepPet(dog,"???");
pr.keepPet(cat,"...");
}
}
包和final关键字
-
包
包就是文件夹,用来管理各种不同功能的java类,方便后期代码维护
包的起名规则:
公司域名反写+包的作用
包名加上类名才是全部的类名
如果想在一个包里使用其他包的类名,就需要引用使用的那个包的名字加上类名
import 包名.类名
这样就可以使用其他包的类了
注意事项:
使用两个包的同名类时,不需要导包了,直接使用同名类
小结
-
final关键字(与static类似的关键字)
在代码中,一旦被final修饰,就表示被修饰的内容不能改变了,有点像JavaScript的const。
final可以修饰方法,类,和变量
修饰方法时:
表示该方法是最终方法,不能被重写
修饰类时:
表示该类是最终类,不能被继承
修饰变量时:
这个变量就会变为一个常量,只能被赋值一次
-
使用final的场景:
如果定义的方法是一种规则时,可以用final
主要还是用final来修饰变量 -
常量(final定义的变量):
实际开发中,常量一般作为系统的配置信息,方便维护,提高可读性 -
常量的命名规范:
-
注意事项
主要是第二点,对象的内部可以改变。
字符串不可变的原因就是final和private
ieda快捷键
crtl+shift+u可以让字母变成大写
小结
权限修饰符(private等)和代码块(静态代码块)
- 权限修饰符
作用:
是用来控制一个成员能够被访问的范围。
可以修饰成员变量,方法,构造方法,内部类。
权限修饰符的分类:
有四种类,被访问的范围从小到大分为:private<空着不写(默认情况)<protected<public
修饰符 | 同一个类中的作用范围 | 同一个包里的其他类的作用范围 | 不同包下的子类 的作用范围 | 不同包下的无关类 的作用范围 |
---|---|---|---|---|
private | 可以 | 不行 | 不行 | 不行 |
空着不写 (默认) | 可以 | 可以 | 不行 | 不行 |
protected | 可以 | 可以 | 可以 | 不行 |
public | 可以 | 可以 | 可以 | 可以 |
实际开发中,一般来讲,只会用private和public,且遵守以下规则
- 代码块
可以分为三类
局部代码块,构造代码块,静态代码块 - 局部代码块(了解)
就是写在方法里边的一个单独的代码块
pubic class Test{
public static void mian(String[ ] args){
//这里下面的大括号就是一个局部代码块
{
int a=10;
sout(a);
}
}
}
作用:提前结束变量的生命周期(例如上面的a,代码块结束后,a就会消失)
这个技术现在已经用不到了
- 构造代码块(了解)
就是写在成员位置的代码块,可以优先于构造方法执行
作用:可以把多个构造方法中重复的代码抽取出来。
pubic class Person{
int age;
String name;
//下面这里就是一个构造代码块
{
sout("111111")
}
public Person(){
//sout("111111")
}
public Person(int age,String name){
//sout("111111")
this.age=age;
this.name=name;
}
}
这个技术现在也渐渐淘汰了
可以将重复的代码抽取成一个方法,构造方法需要使用时直接调用方法就行了。
- 静态代码块(重点)
就是在构造代码块的基础上加上static
格式:static{}
特点:随着类的加载而加载
,并且自动触发,且只执行一次
。(即使用这个类创建多个对象,也只会执行一次)
使用场景:在类加载的时候,做一些数据初始化的时候使用。
静态代码块里只能用静态
小结
抽象类和抽象方法
- 抽象关键字:abstract
- 抽象类:
如果一个类中存在抽象方法,那么该类就必须声明为抽象类。(由方法推向类,不是由类推向方法) - 抽象方法:
将共性的行为(方法)抽取到父类后,由于每个子类的执行结果不一样,所以在父类中不能定义确定的方法体,该方法就可以定义为抽象方法 - 抽象方法的定义格式:
public abstract 返回值类型 方法名(参数列表);
没有方法体
抽象类的定义同理:public abstract class 类名{}
- 抽象类和抽象方法的注意事项
1.抽象类不能创建对象
2.抽象类中不一定有抽象方法,有抽象方法的一定是抽象类
3.抽象类可以有构造方法(作用:当创建子类对象时给子类的属性进行赋值)
4.抽象类的子类分两种情况:
如果子类不是抽象类,则需要重写抽象类中的所有方法
如果子类是抽象类,则没事
ieda便捷键
alt+回车
可以查看报错的内容并且快速修改 - 抽象类和抽象方法的意义
让代码统一,方便代码的维护 - 小结
接口
接口就是一种规则。
是指父类与子类之间的关系。
上述图片很好的说明了为什么要有接口,
- 接口的应用
接口不代表一类事务,他代表一种规则,是对行为的抽象
接口与抽象的区别:
抽象更多是表示一类事物,而接口更多表示行为 - 接口的定义和使用
1.接口用关键字interface
定义
public interface 接口名{}
2.接口不能创建对象
3.接口和类是实现关系,通过inplements
关键字表示
public class 类名 inplements 接口名{}
4.接口的子类(接口的实现类)
分两种情况:
不是抽象类直接重写所有抽象方法(更多用这种)
是抽象类没事
5.接口里的方法都是抽象方法
注意事项:
注意,创建接口时,不能直接用iead中的class类吗,要用interface
接口中成员的特点
- 接口中的成员变量:
只能是常量
默认修饰符为:public static final
- 接口中的构造方法
没有 - 接口中的成员方法
只能是抽象方法
默认修饰符:public abstract
接口和类之间的关系
- 类和类之间的关系
继承关系,只能单继承,不能多继承,但是可以多重继承 - 类和接口的关系
实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口。
多个接口中有重名的方法,只需在类里重写一次就行了 - 接口与接口之间的关系
继承关系,可以单继承,也可以多继承。
如果实现类实现了子接口,那么就需要重写所有的抽象方法
//如果实现类实现了子接口,那么就需要重写所有的抽象方法
//接口1
public interface Inter1{
public abstract void method1();
}
//接口2
public interface Inter2{
public abstract void method2();
}
//接口3
public interface Inter3 extends Inter1,Inter2{
public abstract void method3();
}
//实现类
public class InterImpl implements Inter3{
@Override
public void method1(){
}
@Override
public void method2(){
}
@Override
public void method3(){
}
}
小结
接口练习
最优的结构图
package com.inherit4.study;
//因为直接创建人的对象是没有意义的,所以直接把person创建为抽象类
public abstract class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
---------------------------------------------------
package com.inherit4.study;
public abstract class athletes extends Person{
public athletes() {
}
public athletes(String name, int age) {
super(name, age);
}
public abstract void study();
}
---------------------------------------------------
package com.inherit4.study;
public abstract class instructor extends Person{
public instructor() {
}
public instructor(String name, int age) {
super(name, age);
}
public abstract void teach();
}
---------------------------------------------------
package com.inherit4.study;
public interface English {
public abstract void eng();
}
---------------------------------------------------
package com.inherit4.study;
public class ppathletes extends athletes implements English{
public ppathletes() {
}
public ppathletes(String name, int age) {
super(name, age);
}
@Override
public void eng() {
System.out.println("说英语");
}
@Override
public void study() {
System.out.println("学打乒乓球");
}
}
---------------------------------------------------
package com.inherit4.study;
public class bkathletes extends athletes{
public bkathletes() {
}
public bkathletes(String name, int age) {
super(name, age);
}
@Override
public void study() {
System.out.println("学打篮球");
}
}
---------------------------------------------------
package com.inherit4.study;
public class ppInstuctor extends instructor implements English{
public ppInstuctor() {
}
public ppInstuctor(String name, int age) {
super(name, age);
}
@Override
public void eng() {
System.out.println("说英语");
}
@Override
public void teach() {
System.out.println("教打乒乓球");
}
}
---------------------------------------------------
package com.inherit4.study;
public class bkInstucttor extends instructor{
public bkInstucttor() {
}
public bkInstucttor(String name, int age) {
super(name, age);
}
@Override
public void teach() {
System.out.println("教打篮球");
}
}
---------------------------------------------------
package com.inherit4.study;
public class test {
public static void main(String[] args) {
ppathletes pa=new ppathletes("zhangsan",18);
System.out.println(pa.getName()+" "+pa.getAge());
pa.eng();
pa.study();
bkathletes ba=new bkathletes("zhangsi",19);
System.out.println(ba.getName()+" "+ba.getAge());
ba.study();
ppInstuctor pi=new ppInstuctor("lisi",30);
System.out.println(pi.getName()+" "+pi.getAge());
pi.eng();
pi.teach();
bkInstucttor bi=new bkInstucttor("zhangliu",36);
System.out.println(bi.getName()+" "+bi.getAge());
bi.teach();
}
}
接口新增方法(jdk8开始)
jdk7以前,接口中只能定义抽象方法
jdk8以后,接口中可以定义有方法体的方法(默认,静态)
jdk9以后,接口中可以定义私有方法
即jdk8之后,接口里的方法可以有方法体了
-
jdk8以后接口中新增的方法(默认方法)
允许在接口中定义默认方法,需要使用关键字default
修饰
作用:解决接口升级的问题
接口默认方法的定义格式:
public default 返回值类型 方法名(参数列表){方法体}
例如
public default void show(){}
接口中默认方法的注意事项:
接口中的默认方法不是抽象方法,所以不强制重写。如果被重写,重写时去掉default
关键字。
public可以省略,default不能省略
如果实现了多个接口,多个接口中存在相同名字的默认方法,子类就必须对该方法进行重写。 -
jdk8以后接口中新增的方法(静态方法)
静态方法不能重写。
允许在接口中定义静态方法,需要使用关键字static
修饰
接口静态方法的定义格式:
public static 返回值类型 方法名(参数列表){方法体}
例如
public static void show(){}
接口中静态方法的注意事项:
接口中的静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
public可以省略,static不能省略
重写的概念:
- jdk9新增的方法
为什么在接口中增加私有方法
对于一些重复代码,是对单独一个接口中其他方法去服务的,不想被外类去访问,因为外类访问是没有意义的,因此在接口中出现了私有方法
接口中私有方法的定义格式:
格式1:private 返回值类型 方法名(参数列表){}
(不加default关键字,给默认方法服务,即默认方法中的重复代码)
例如:
private void show(){}
格式2:private static 返回值类型 方法名(参数列表){}
(给静态方法服务,即静态方法中的重复代码)
例如:
private static void method(){}
小结:
接口的应用
- 把多个类都有可能用到的规则定义成接口,对实现类来讲,想要要实现类拥有什么样的功能,就实现对应的接口就行了。
即接口代表规则,是行为的抽象。想要让哪个类拥有一个行为,就让这个类实现对应的接口就行了 - 当一个方法的参数是接口时,可以传递接口所有实现类的对象,这种方式称之为接口多态。
适配器设计模式
-
设计模式:
设计模式是一套被反复利用,多数人知晓的,经过分类编目的,代码设计经验的总结。使用设计模式是为了可重用代码,让代码更容易被他人理解,保证代码可靠性,程序的重用性。
简单理解:
-
适配器设计模式:
解决接口与接口实现类之间的矛盾问题
就是在接口和实现类中间加上一个单独的类,这个类把接口里的所有方法重写(空着),然后,实现类使用时只需要重写需要的方法就行了
注意,中间类的名字是XXXAdapder
-
接口
-
中间类
-
实现类
小结
内部类
- 类的五大成员:
属性,方法,构造方法,代码块,内部类
内部类就是在一个类里再定义的类
例如:
public class Person{
public class Student{
}
}
person类里的student就是内部类,person就是外部类
跟这两个类无关的其他所有类就是外部其他类
- 定义内部类的规则:
内部类表示的事物是外部类的一部分
内部类单独出现没有任何意义 - 内部类的访问特点:
内部类可以直接访问外部类的成员,包括私有的
外部类要访问内部类的成员,必须在外部类里创建对象
package com.inherit.study;
public class Car {
private String carName;
private int carAge;
private String carColor;
//外部类访问内部类时则需要创建对象,不能直接访问
eng neng=new eng();
public void show2(){
System.out.println(neng.engAge+" "+neng.engName+" "+neng.engColor);
}
class eng{
String engName;
int engAge;
String engColor;
public void show1(){
//内部类访问外部类可以直接访问,即使是私有的也可以访问
System.out.println(carName+" "+carAge+" "+carColor);
}
}
}
小结:
内部类的分类
分为:
成员内部类
静态内部类
局部内部类
匿名内部类
前三种了解一下哎,写代码时能看懂,会用就行,最后一种匿名内部类需要掌握
成员内部类
写在成员位置的内部类就是成员内部类
package com.inherit.study;
public class Car {
private String carName;
private int carAge;
private String carColor;
eng neng=new eng();
public void show2(){
System.out.println(neng.engAge+" "+neng.engName+" "+neng.engColor);
}
class eng{//这里就是成员内部类
String engName;
int engAge;
String engColor;
public void show1(){
System.out.println(carName+" "+carAge+" "+carColor);
}
}
}
- 成员内部类代码的书写
1.写在成员位置的,属于外部类的成员,有着成员的性质
2.成员内部类可以被一些修饰符所修饰,比如:private,默认,protected,public,static(用static修饰的内部类就不是成员内部类了,是静态内部类)
3.成员内部类里可以定义静态变量(jdk16以后) - 成员内部类的创建
方式1:(private修饰时会使用)
在外部类中编写方法,对外提供内部类的对象,可以用Object创建一个先对象来接收内部类对象的返回值,也可以直接使用
方式2:
直接创建格式:外部类名.内部类名 对象名=外部类对象(new 对象).内部类对象(new 对象);
方式1的例子:
package com.inherit.study;
public class Outer {
private String name;
private class Inner{
}
public Inner getInner(){//创建方法
return new Inner();
}
}
Outer o=new Outer();
o.getInner();//调用方法
方式2的例子:
//这里相当于内部类是外部类的成员,创建外部类对象后调用
Outer.Inner duixiang= new Outer().new Inner();
//这里Outer是外部类名,Inner是内部类名
- 成员内部类获取外部类的成员变量(使用
Outer.this.变量
,Outer是外部类名)
一个案例:
package com.inherit.study;
public class Outer {
private int a=10;
class Inner{
private int a=20;
public void show(){
int a=30;
//this是调用方法者的地址
/*这里想要调用外部类的a值,就使用外部类调用this,获得外部类的
地址,再调用a,就获得外部类a的值。*/
sout(Outer.this.a);//10
/*同理,这里获取到是调用者也就是内部类的地址值,再通过内部类
地址调用a,获得内部类a的值*/
sout(this.a);//20
//这里就直接调用,遵循就近原则
sout(a);//30
}
}
}
//调用
package com.inherit5.study;
public class test {
public static void main(String[] args) {
Outer.Inner oi =new Outer().new Inner();
oi.show();//oi是调用者,记录的是内部类的地址值(因为oi是内部类对象)
}
}
小结:
静态内部类
- 静态内部类是成员内部类的一种特殊情况,当成员内部类前面的修饰符为static时,就是静态内部类
- 静态内部类只能访问外部类中的静态变量和静态方法,如果想要访问非静态的需要创建对象。
- 例如:
package com.inherit.study;
public class Outer {
int a=10;
static String name="111";
//静态内部类
static class Inner{
public void show1(){
Outer oi=new Outer();
sout(oi.a);//外部类的非静态对象需要创建对象才能访问
sout(name);//外部类的静态变量可以直接访问
}
public static void show2(){
Outer oi=new Outer();
sout(oi.a);//外部类的非静态对象需要创建对象才能访问
sout(name);//外部类的静态变量可以直接访问
}
}
}
-
创建静态内部类的语法:
外部类名.内部类名 对象名=new 外部类名.内部类名();
例如:
Outer.Inner oi=new Outer.Inner();
(这里new的是Inner) -
用静态内部类对象调用静态内部类中的非静态方法:
先创建对象,用对象调用 -
调用静态内部类中的静态方法:
直接调用:Outer.Inner.方法名()
-
例如:
package com.inherit.study;
public class Outer {
//静态内部类
static class Inner{
public void show1(){
sout(非静态的方法被调用了);
}
public static void show2(){
sout(静态的方法被调用了);
}
}
}
//调用
//调用静态内部类里的非静态的方法,需要先创建一个静态内部类对象,然后用对象调用
Outer.Inner oi=new Outer.Inner();
oi.show1();
//调用静态内部类里的静态的方法,可以直接通过 外部类.内部类.方法名() 调用
Outer.Inner.show2();
小结
局部内部类
将内部类定义在方法里面就叫做局部内部类
,类似于方法里面的局部变量- 外界无法直接使用,需要再方法里创建对象并使用
- 该类可以直接访问外部类的成员,也可以访问方法内的局部变量
小结
匿名内部类(重点)
匿名内部类的本质就是隐藏了名字的内部类,可以写在成员位置,也可以写在局部位置
匿名内部类有三个特点:
1.实现或继承关系
2.方法的重写
3.创建对象
- 匿名内部类的语法:
new 类名或接口名(){
重写方法();//类或接口的抽象方法的重写
};
//这一段代码的返回值是一个对象,可以被对象变量接收
例如:
new Inter(){
public void show(){
}
};
详细解释:
即
new Swim(){//这里的new创建的是后面没有名字的类的对象
@Override
public void swim(){
sout("重写的游泳方法");
}
};
/*这一部分从语法上来解释,应该叫做匿名内部类的对象
new Swim(){
@Override
public void swim(){
sout("重写的游泳方法");
}
};
*/
/*这一部分才是没有名字类的主体,即匿名内部类,实现了Swim这个接口
{
@Override
public void swim(){
sout("重写的游泳方法");
}
};*/
- 以上代码有三个特点
1.实现关系
2.方法的重写
3.创建对象
同理,当Swim换成一个类名时,特点就变为了
1.继承关系
2.方法的重写
3.创建对象
此时匿名内部类的主题就是父类的子类
new Fulei(){//这里的new创建的是后面没有名字的类的对象
@Override
public void show(){
sout("重写的show方法");
}
};
/*这一部分从语法上来解释,应该叫做匿名内部类的对象
new Fulei(){
@Override
public void show(){
sout("重写的show方法");
}
};
*/
/*这一部分才是没有名字类的主体,即匿名内部类,继承了Fulei的子类
{
@Override
public void show(){
sout("重写的show方法");
}
};*/
应用场景
可以作为方法的实参,不用再写格外的类
//Animal类(父类)
public abstract class Animal{
public void eat(){
sout("吃东西")
}
}
//Dog类(子类)
public class Dog extends Animal{
@Override
public void eat(){
sout("狗吃骨头")
}
}
//测试类
psvm(String[] args){
//以前调用method方法
Dog d=new Dog();
method(d);
//使用匿名内部类调用method方法
method(
new Animal(){
@Override
public void eat(){
sout("狗吃骨头")
}
}
)
/*
new Animal(){
@Override
public void eat(){
sout("狗吃骨头")
}
}
这个整体就相当于:Animal a=子类对象 ,即多态
*/
public static void method(Animal a){
a.eat();
}
}
拓展:
可以直接用语法的形式调用对象
new Fulei(){//这里的new创建的是后面没有名字的类的对象
@Override
public void show(){
sout("重写的show方法");
}
}.show();
小结:
阶段综合项目
拼图小游戏
用到java图形化界面的知识,即GUI
ctrl+alt+m:快速将代码创建为方法
package com.sanchi.ui;
import javax.swing.*;
public class GameJFrame extends JFrame {
//跟游戏相关的逻辑都写在这个类
private void initJMenuBar() {
//初始化菜单
//创建菜单对象
JMenuBar jMenuBar=new JMenuBar();
//创建菜单选项
JMenu functionjMenu=new JMenu("功能");//功能
JMenu aboutjMenu=new JMenu("关于");//关于
//选项的具体内容
JMenuItem replayItem=new JMenuItem("重新游戏");
JMenuItem reLogin=new JMenuItem("重新登录");
JMenuItem closeItem=new JMenuItem("关闭游戏");
JMenuItem accountItem=new JMenuItem("作者信息");
//将选项的具体内容添加到选项中
functionjMenu.add(replayItem);
functionjMenu.add(reLogin);
functionjMenu.add(closeItem);
aboutjMenu.add(accountItem);
//将选项添加到菜单中
jMenuBar.add(functionjMenu);
jMenuBar.add(aboutjMenu);
//给当前的界面设置菜单
this.setJMenuBar(jMenuBar);
}
private void initGJFrame() {
//页面的宽高
this.setSize(603,680);
//页面标题
this.setTitle("拼图单机版-v1.0");
//页面置顶
this.setAlwaysOnTop(true);
//页面居中
this.setLocationRelativeTo(null);
//游戏的关闭模式,有4个模式,源码中有一个接口,定义了四个规则,0,1,2,3
this.setDefaultCloseOperation(3);
}
}
用继承的方法把游戏,登录和注册三个模块写成类的形式,分别创建对象,放到界面中。
import com.sanchi.ui.GameJFrame;
import com.sanchi.ui.LoginJFrame;
import com.sanchi.ui.RegisterJFrame;
public class app {
//表示程序的启动入口
public static void main(String[] args) {
/*new LoginJFrame();
new RegisterJFrame();*/
new GameJFrame();
}
}
使用Imageicon创建图片对象,用JLabel管理图片对象,再将JLabel添加到页面中
private void initImage() {
//创建图片对象
ImageIcon icon=new ImageIcon("C:\\Users\\86158\\IdeaProjects\\FirstProgect\\image\\animal\\animal3\\3.jpg");
//创建一个JLabel对象(管理图片)
JLabel jLabel=new JLabel(icon);
//将Jlabel加入到界面中
this.add(jLabel);
}
通过设置图片坐标,使图片排布在界面中
图片默认放在隐藏容器中,可以通过窗体.getContentPane()来获得,默认放在窗口最中间,通过setLayout(null)在初始化界面的时候来去除隐藏容器的默认机制
private void initGJFrame() {
//页面的宽高
this.setSize(603,680);
//页面标题
this.setTitle("拼图单机版-v1.0");
//页面置顶
this.setAlwaysOnTop(true);
//页面居中
this.setLocationRelativeTo(null);
//游戏的关闭模式,有4个模式,源码中有一个接口,定义了四个规则,0,1,2,3
this.setDefaultCloseOperation(3);
//取消默认的居中放置
this.setLayout(null);
}
图片的添加(二维数组)
private void initImage() {
int count=1;
//外循环,表示列
for (int j = 0; j <=3 ; j++) {
//内循环,表示行
for (int i = 0; i <=3; i++) {
//创建图片对象的同时创建一个JLabel对象(管理图片)
JLabel jLabel=new JLabel(new ImageIcon("C:\\Users\\86158\\IdeaProjects\\FirstProgect\\image\\animal\\animal3\\"+count+".jpg"));
//指定图片位置
jLabel.setBounds(105*i,105*j,105,105);
//将Jlabel加入到界面中
//this.add(jLabel);
this.getContentPane().add(jLabel);
count++;
}
}
}
处理数据时,长度确定的话,用数组,长度不确定,用集合
练习
private void initImage() {
//外循环,表示列
for (int j = 0; j < 4; j++) {
//内循环,表示行
for (int i = 0; i < 4; i++) {
int num=date[j][i];
//创建图片对象的同时创建一个JLabel对象(管理图片)
JLabel jLabel = new JLabel(new ImageIcon("C:\\Users\\86158\\IdeaProjects\\FirstProgect\\image\\animal\\animal3\\" + num + ".jpg"));
//指定图片位置
jLabel.setBounds(105 * i, 105 * j, 105, 105);
//将Jlabel加入到界面中
//this.add(jLabel);
this.getContentPane().add(jLabel);
}
}
}
- 事件
事件源,事件,绑定监听
事件练习:
package lainxi;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
public class Test1 {
public static void main(String[] args) {
JFrame jf=new JFrame();
Random rd=new Random();
//页面的宽高
jf.setSize(600,600);
//标题
jf.setTitle("呃呃呃呃呃");
//置顶
jf.setAlwaysOnTop(true);
//居中
jf.setLocationRelativeTo(null);
//关闭模式
jf.setDefaultCloseOperation(3);
jf.setLayout(null);
JButton jb1=new JButton("an1");
jb1.setSize(100,50);
jb1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int a=rd.nextInt(500);
jb1.setBounds(a,a,100,100);
}
});
jf.getContentPane().add(jb1);
//页面显示
jf.setVisible(true);
}
}
- 鼠标监听MouseListener
是一个接口。
JButton jb1=new JButton("an1");
jb1.setSize(100,50);
jb1.addMouseListener(new MouseListener() {
@Override
public void mouseClicked(MouseEvent e) {
System.out.println(1);
}
@Override
public void mousePressed(MouseEvent e) {
System.out.println(2);
}
@Override
public void mouseReleased(MouseEvent e) {
System.out.println(3);
}
@Override
public void mouseEntered(MouseEvent e) {
System.out.println(4);
}
@Override
public void mouseExited(MouseEvent e) {
System.out.println(5);
}
});
- 键盘监听KeyListener
同样是接口
第三个有局限性,不常用
可以通过KeyCode方法获取键盘字母的编码,每一个按键都有一个编码对应。
//e为键盘事件的对象
int code=e.KeyCode();
sout(code);
方框的美化
加背景图和边框
图片的移动
在初始化页面时给页面添加键盘监听,记录二维数组0值的坐标点,通过交换0和要被移动的二维数组值实现图片的移动
private void initGJFrame() {
//页面的宽高
this.setSize(603, 680);
//页面标题
this.setTitle("拼图单机版-v1.0");
//页面置顶
this.setAlwaysOnTop(true);
//页面居中
this.setLocationRelativeTo(null);
//游戏的关闭模式,有4个模式,源码中有一个接口,定义了四个规则,0,1,2,3
this.setDefaultCloseOperation(3);
//取消默认的居中放置
this.setLayout(null);
//给整个界面添加键盘监听事件
this.addKeyListener(this);
}
private void initDate() {
//一维数组的创立与随机
int[] tempArr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
Random rd = new Random();
for (int i = 0; i < tempArr.length; i++) {
int count = rd.nextInt(tempArr.length);
int temp = tempArr[i];
tempArr[i] = tempArr[count];
tempArr[count] = temp;
}
for (int i = 0; i < tempArr.length; i++) {
if(tempArr[i]==0){
//记录0值的坐标点
x=i/4;
y=i%4;
}else{
date[i / 4][i % 4] = tempArr[i];
}
}
}
private void initImage() {
//清空所有图片
this.getContentPane().removeAll();
//外循环,表示列
for (int j = 0; j < 4; j++) {
//内循环,表示行
for (int i = 0; i < 4; i++) {
int num=date[j][i];
//创建图片对象的同时创建一个JLabel对象(管理图片)
JLabel jLabel = new JLabel(new ImageIcon("..\\FirstProgect\\image\\animal\\animal3\\" + num + ".jpg"));
//指定图片位置
jLabel.setBounds(105 * i+83, 105 * j+134, 105, 105);
//将Jlabel加入到界面中
jLabel.setBorder(new BevelBorder(1));
//this.add(jLabel);
this.getContentPane().add(jLabel);
}
}
//先添加的图片在图层的上面,后添加的图片在图层的下面
JLabel backGround=new JLabel(new ImageIcon("..\\FirstProgect\\image\\background.png"));
backGround.setBounds(40,40,508,560);
this.getContentPane().add(backGround);
this.getContentPane().repaint();
}
@Override
public void keyReleased(KeyEvent e) {
//对上下左右进行判断
//左:37 上:38,右:39 下:40
int code=e.getKeyCode();
if(code==37){//左
if(y==3){//边界的限定
return;
}
date[x][y]=date[x][y+1];
date[x][y+1]=0;
y++;
initImage();
}else if(code==38){//上
if(x==3){
return;
}
//向上移动就是把空白下面方块的值赋值给空白方块,再将空白方块的坐标改变
date[x][y]=date[x+1][y];
date[x+1][y]=0;
x++;
//然后再重新加载一下图片
initImage();
} else if (code==39) {//右
if(y==0){
return;
}
date[x][y]=date[x][y-1];
date[x][y-1]=0;
y--;
initImage();
} else if (code==40) {//下
if(x==0){
return;
}
date[x][y]=date[x-1][y];
date[x-1][y]=0;
x--;
initImage();
}
}
}
查看完整图片
完整代码
//GameJFrame,游戏主页面
package com.sanchi.ui;
import javax.swing.*;
import javax.swing.border.BevelBorder;
import java.awt.event.*;
import java.util.Random;
public class GameJFrame extends JFrame implements KeyListener, ActionListener{
//成员变量
//x,y记录0索引的位置
int x=0;
int y=0;
//定义变量用来定义步数
int step=0;
Random rd=new Random();
int count=rd.nextInt(8)+1;
//选项的具体内容
JMenuItem replayItem = new JMenuItem("重新游戏");
JMenuItem reLogin = new JMenuItem("重新登录");
JMenuItem closeItem = new JMenuItem("关闭游戏");
JMenuItem women=new JMenuItem("美女");
JMenuItem animal=new JMenuItem("动物");
JMenuItem play=new JMenuItem("运动");
JMenuItem accountItem = new JMenuItem("作者信息");
//创建二维数组
int[][] date = new int[4][4];
int [][] win={
{1,2,3,4},
{5,6,7,8},
{9,10,11,12},
{13,14,15,0}
};
//定义一个值用来记录路径,方便更改
String path="..\\FirstProgect\\image\\animal\\animal"+count+"\\";
//跟游戏相关的逻辑都写在这个类
public GameJFrame() {
//初始化界面
initGJFrame();
//初始化菜单
initJMenuBar();
//初始化数据
initDate();
//初始化图片
initImage();
//页面的显示
this.setVisible(true);
}
//初始化数据,使数据打乱
private void initDate() {
//一维数组的创立与随机
int[] tempArr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
Random rd = new Random();
for (int i = 0; i < tempArr.length; i++) {
int count = rd.nextInt(tempArr.length);
int temp = tempArr[i];
tempArr[i] = tempArr[count];
tempArr[count] = temp;
}
for (int i = 0; i < tempArr.length; i++) {
if(tempArr[i]==0){
x=i/4;
y=i%4;
}
date[i / 4][i % 4] = tempArr[i];
}
}
//初始化图片
private void initImage() {
//清空所有图片
this.getContentPane().removeAll();
if(victory()){
//显示胜利
JLabel winJlabel=new JLabel(new ImageIcon("..\\FirstProgect\\image\\win.png"));
winJlabel.setBounds(203,283,197,73);
this.getContentPane().add(winJlabel);
}
JLabel stepContent=new JLabel("步数"+step);
stepContent.setBounds(50,30,100,20);
this.getContentPane().add(stepContent);
//外循环,表示列
for (int j = 0; j < 4; j++) {
//内循环,表示行
for (int i = 0; i < 4; i++) {
int num=date[j][i];
//创建图片对象的同时创建一个JLabel对象(管理图片)
JLabel jLabel = new JLabel(new ImageIcon(path + num + ".jpg"));
//指定图片位置
jLabel.setBounds(105 * i+83, 105 * j+134, 105, 105);
//将Jlabel加入到界面中
jLabel.setBorder(new BevelBorder(1));
//this.add(jLabel);
this.getContentPane().add(jLabel);
}
}
//先添加的图片在图层的上面,后添加的图片在图层的下面
JLabel backGround=new JLabel(new ImageIcon("..\\FirstProgect\\image\\background.png"));
backGround.setBounds(40,40,508,560);
this.getContentPane().add(backGround);
this.getContentPane().repaint();
}
//初始化菜单
private void initJMenuBar() {
//初始化菜单
//创建菜单对象
JMenuBar jMenuBar = new JMenuBar();
//创建菜单选项
JMenu functionjMenu = new JMenu("功能");//功能
JMenu aboutjMenu = new JMenu("关于");//关于
JMenu replaceImage=new JMenu("更换图片");
//将选项的具体内容添加到选项中
functionjMenu.add(replaceImage);
functionjMenu.add(replayItem);
functionjMenu.add(reLogin);
functionjMenu.add(closeItem);
aboutjMenu.add(accountItem);
//给选项添加绑定事件
replayItem.addActionListener(this);
reLogin.addActionListener(this);
closeItem.addActionListener(this);
accountItem.addActionListener(this);
women.addActionListener(this);
play.addActionListener(this);
animal.addActionListener(this);
//将选项添加到菜单中
jMenuBar.add(functionjMenu);
jMenuBar.add(aboutjMenu);
//将选项加到次级菜单中
replaceImage.add(women);
replaceImage.add(animal);
replaceImage.add(play);
//给当前的界面设置菜单
this.setJMenuBar(jMenuBar);
}
private void initGJFrame() {
//页面的宽高
this.setSize(603, 680);
//页面标题
this.setTitle("拼图单机版-v1.0");
//页面置顶
this.setAlwaysOnTop(true);
//页面居中
this.setLocationRelativeTo(null);
//游戏的关闭模式,有4个模式,源码中有一个接口,定义了四个规则,0,1,2,3
this.setDefaultCloseOperation(3);
//取消默认的居中放置
this.setLayout(null);
//给整个界面添加键盘监听事件
this.addKeyListener(this);
}
//判断游戏是否获胜
private boolean victory(){
for (int i = 0; i < date.length ; i++) {
for (int j = 0; j <date.length ; j++) {
if(date[i][j]!=win[i][j]){
return false;
}
}
}
return true;
}
@Override
public void keyTyped(KeyEvent e) {
}
//查看图像,快捷键a
@Override
public void keyPressed(KeyEvent e) {//按下不松时
int code=e.getKeyCode();
//System.out.println(code);
if(code==65){
//先删除所有图片
this.getContentPane().removeAll();
JLabel all=new JLabel(new ImageIcon(path+"all.jpg"));
all.setBounds(83,134,420,420);
this.getContentPane().add(all);
//添加背景
JLabel backGround=new JLabel(new ImageIcon("..\\FirstProgect\\image\\background.png"));
backGround.setBounds(40,40,508,560);
this.getContentPane().add(backGround);
this.getContentPane().repaint();
}
}
//图片的移动和一键完成,快捷键w
@Override
public void keyReleased(KeyEvent e) {
//先判断游戏是否胜利,若胜利,则此方法直接结束
if(victory()){
return;
}
//按下松开时
//对上下左右进行判断
//左:37 上:38,右:39 下:40
int code=e.getKeyCode();
if(code==37){//左
if(y==3){//边界的限定
return;
}
date[x][y]=date[x][y+1];
date[x][y+1]=0;
y++;
step++;
initImage();
}else if(code==38){//上
if(x==3){
return;
}
//向上移动就是把空白下面方块的值赋值给空白方块,再将空白方块的坐标改变
date[x][y]=date[x+1][y];
date[x+1][y]=0;
x++;
step++;
//然后再重新加载一下图片
initImage();
} else if (code==39) {//右
if(y==0){
return;
}
date[x][y]=date[x][y-1];
date[x][y-1]=0;
y--;
step++;
initImage();
} else if (code==40) {//下
if(x==0){
return;
}
date[x][y]=date[x-1][y];
date[x-1][y]=0;
x--;
step++;
initImage();
}else if(code==65){
initImage();
} else if (code==87) {
date=new int[][]{
{1,2,3,4},
{5,6,7,8},
{9,10,11,12},
{13,14,15,0}
};
initImage();
}
}
@Override
public void actionPerformed(ActionEvent e) {
Object obj=e.getSource();
if(obj==replayItem){
initDate();
step=0;
initImage();
} else if (obj==reLogin) {
this.setVisible(false);
new LoginJFrame();
} else if (obj==closeItem) {
System.exit(0);
} else if (obj==accountItem) {
JDialog jDialog=new JDialog();
JLabel jLabel=new JLabel(new ImageIcon("..\\FirstProgect\\image\\about.png"));
jLabel.setBounds(0,0,258,258);
jDialog.getContentPane().add(jLabel);
jDialog.setSize(344,344);
jDialog.setAlwaysOnTop(true);
jDialog.setLocationRelativeTo(null);
jDialog.setModal(true);
jDialog.setVisible(true);
} else if (obj==women) {
int count=rd.nextInt(13)+1;
path="..\\FirstProgect\\image\\girl\\girl"+count+"\\";
initDate();
step=0;
initImage();
} else if (obj==play) {
int count=rd.nextInt(10)+1;
path="..\\FirstProgect\\image\\sport\\sport"+count+"\\";
initDate();
step=0;
initImage();
}else if (obj==animal) {
int count=rd.nextInt(8)+1;
path="..\\FirstProgect\\image\\animal\\animal"+count+"\\";
initDate();
step=0;
initImage();
}
}
}
//LoginJFrame,登录
package com.sanchi.ui;
import javax.swing.*;
public class LoginJFrame extends JFrame {
//登录相关的逻辑都写在这里
public LoginJFrame(){
//页面的宽高
this.setSize(488,430);
//游戏的关闭模式
this.setDefaultCloseOperation(3);
//页面标题
this.setTitle("拼图登录");
//页面置顶
this.setAlwaysOnTop(true);
//页面居中
this.setLocationRelativeTo(null);
//页面的显示
this.setVisible(true);
}
}
//RegisterJFrame
package com.sanchi.ui;
import javax.swing.*;
public class RegisterJFrame extends JFrame {
//注册相关的逻辑
public RegisterJFrame(){
//页面的宽高
this.setSize(488,500);
//页面标题
this.setTitle("拼图注册");
//页面置顶
this.setAlwaysOnTop(true);
//页面居中
this.setLocationRelativeTo(null);
//游戏的关闭模式
this.setDefaultCloseOperation(3);
//页面的显示
this.setVisible(true);
}
}
//test测试类
import com.sanchi.ui.GameJFrame;
import com.sanchi.ui.LoginJFrame;
import com.sanchi.ui.RegisterJFrame;
public class app {
//表示程序的启动入口
public static void main(String[] args) {
/*new LoginJFrame();
new RegisterJFrame();*/
new GameJFrame();
}
}
20.常用API
Math
在源码中的定义
public final class Math extends Object
即Math是一个最终类,不能被继承
Math是一个帮助计算的工具类
私有化构造方法,所有方法都是静态的
类Math包含用于执行基本数字运算的方法,例如基本指数,对数,平方根和三角函数
Math中的方法基本上都是静态修饰,可以直接通过类名调用
Math. 方法名()
例如:
Math.abs(-11)
常用方法
方法名 | 作用 |
---|---|
abs(int a) | 返回参数绝对值 |
ceil(double a) | 向上取整(即向数轴的正方向取整) |
floor(double a) | 向下取整(同理,向数轴的负方向取整) |
round(float a) | 四舍五入 |
max(int a,intb) | 获取两个数的较大值 |
min(int a,intb) | 获取两个数的较小值 |
pow(double a,double b) | 返回a的b次幂的值 |
sqrt(int a) | 返回a的平方根 |
cqrt(int a) | 返回a的立方根 |
public static double random() | 返回值为double的随机值,范围[0.0,1.0) ,没random类好用 |
- abs的bug
以int为例
int类型的取值范围是-2147483648~2147483647
如果没有正数与负数对应,那么传递负数结果有误
sout(Math.abs(-2147483648))
//结果为-2147483648,出现bug
- 解决方案
使用absExact
方法,当取值超过数据类型范围时,会有报错
代码
package lainxi;
public class MathTest {
public static void main(String[] args) {
System.out.println(Math.abs(-100.5));
System.out.println(Math.ceil(-100.5));
System.out.println(Math.floor(-100.5));
System.out.println(Math.round(-100.5));
System.out.println(Math.max(-100,-99));
System.out.println(Math.pow(16,2));
System.out.println(Math.random());
}
}
有忘记的就去查api帮助文档
Math练习
//质数改进
package lainxi;
public class MathTest {
public static void main(String[] args) {
boolean a=judgmentPrime(7);
if(a){
System.out.println("是质数");
}else {
System.out.println("不是质数");
}
}
public static boolean judgmentPrime(int a){
double b=Math.ceil(Math.sqrt(a));
for (int i = 2; i < b ; i++) {
if(a%i==0){
return false;
}
}
return true;
}
}
//自幂数
package lainxi;
public class MathTest {
public static void main(String[] args) {
int count=0;
for (int i = 10000; i <100000 ; i++) {
int a=i%10;
int b=i/10%10;
int c=i/100%10;
int d=i/1000%10;
int e=i/10000%10;
double sum=Math.pow(a,5)+Math.pow(b,5)+Math.pow(c,5)+Math.pow(d,5)+Math.pow(e,5);
if(sum==i){
count++;
}
}
System.out.println(count);
}
}
System
常用方法
方法名 | 作用 |
---|---|
public static void exit(int status) | 终止当前运行的java虚拟机 |
public static long currentTimeMillis() | 返回当前时间与UTC时间1970年1月1日午夜之间的差异,以毫秒为单位。 |
public static void arraycopy(数据源数组, 起始索引, 目的地数组, 起始索引, 拷贝个数) | 数组拷贝 |
long l = System.currentTimeMillis();
System.out.println(l);
可以用currentTimeMillis()
来判断程序运行时间
package lainxi;
public class MathTest {
public static void main(String[] args) {
boolean a=judgmentPrime(7);
if(a){
System.out.println("是质数");
}else {
System.out.println("不是质数");
}
}
public static boolean judgmentPrime(int a){
double b=Math.ceil(Math.sqrt(a));
long start= System.currentTimeMillis();
for (int i = 2; i < b ; i++) {
if(a%i==0){
long end= System.currentTimeMillis();
return false;
}
}
long end= System.currentTimeMillis();
return true;
}
}
- arraycopy的参数具体解析
/*
第一个参数:数据源,表示被拷贝的数组
第二个参数:从数据原中的第几个索引开始拷贝
第三个参数:目的地,我要把数据拷贝到那个数组中
第四个参数:目的地数组的索引
第五个参数:拷贝的个数
*/
System.arraycopy(arr1,0,arr2,2,4);
for (int i = 0; i < arr2.length; i++) {
System.out.println(arr2[i]);
}
- arraycopy的细节
1.如果数据原数组和目的地数组的值都是基本数据类型,那么两者的类型必须保持一致,否则会报错
2.在拷贝的时候,需要考虑数组的长度,如果超出范围也会报错
3。如果数据原数组和目的地数组的值都是引用数据类型,那么,子类类型可以赋值给父类类型
//Person是Student的父类
Student s1=new Student("zhangsan",18);
Student s2=new Student("sanwu",18);
Student s3=new Student("lisan",18);
Student []arr1={s1,s2,s3};
Person []arr2=new Person[3];
//这里是可以赋值的,但在遍历arr2的时候,要把数据类型再进行转换,换成Student
System.arraycopy(arr1,0,arr2,0,3);
for(i=0;i<arr2.length.i++){
Student stu=(Student)arr2[i];
sout(stu.getname()+" "+stu.getage())
}
小结
Runtime
Runtime表示当前虚拟机的运行环境
主要可以用来监视虚拟机的内存
这个类的方法不是静态的,需要先获取对象才能调用方法
方法名 | 作用 |
---|---|
public static Runtime getRuntime() | 获取当前系统的运行环境对象 |
exit(int status) | 停止虚拟机 |
availableProcessors() | 获得cpu的线程数 |
maxMemory() | 返回Java虚拟机能获得的最大内存量(单位byte) |
totalMemory() | 返回Java虚拟机已经获得的最大内存量 (单位byte) |
freeMemory() | 返回Java虚拟机中剩余内存大小(单位byte) |
public Process exec(String command) | 运行cmd命令 |
package lainxi;
import java.io.IOException;
public class RuntimeTest {
public static void main(String[] args) throws IOException {
//获取Runtime对象
//java的Runtime对象只有一个
Runtime r1=Runtime.getRuntime();
Runtime r2=Runtime.getRuntime();
System.out.println(r1==r2);
//这个实际上就是System.exit(0)的底层源码
//Runtime.getRuntime().exit(0);
//获取cpu线程数
System.out.println(Runtime.getRuntime().availableProcessors());
//可以获得的总内存大小
System.out.println(Runtime.getRuntime().maxMemory()/1024/1024);
//已经获得的内存大小
System.out.println(Runtime.getRuntime().totalMemory()/1024/1024);
//虚拟机中剩余内存的大小
System.out.println(Runtime.getRuntime().freeMemory());
//以字符串的形式运行cmd命令
//notepad,打开记事本
//shutdown,关机
//加上参数才能运行
//-s,默认一分钟之后关机
//-s -t 指定关机时间
//-a 取消关机操作
//-r 关机并重启
Runtime.getRuntime().exec("shutdown -a");
}
}
Object
Object是java中的顶级父类,所有的类都直接或简介的继承于Object类
Object中的方法可以被所有子类访问
Object中没有成员变量,所以没有带参构造,只有空参构造方法
public Object
- Object的成员方法
方法名 | 说明 |
---|---|
toString() | 返回对象的字符串表现形式 |
equals(Object obj) | 比较两个对象是否相等 |
clone() | 对象克隆 |
- toString
返回对象的字符串表现形式
package ObjectStudy;
public class Test1 {
public static void main(String[] args) {
Object obj = new Object();
String str1 = obj.toString();
System.out.println(str1);//java.lang.Object@4eec7777 @之前是包名+类名,@后是对象的地址值
sout(obj);//打印出来也是java.lang.Object@4eec7777
}
}
System.out.println(参数)
语句的解析
System是类名,out是一个静态变量,System.out:获取被打印的对象,println则是一个方法,参数就表示要打印的内容
核心逻辑:
当我们打印一个对象时,底层会调用对象的toString方法,把对象变成字符串,然后再打印在控制台上,打印完成后换行处理
将toString()打印地址值改成打印对象属性值的解决方案:
重写父类Object中的toString方法
toString方法的结论:
如果打印一个对象,想要看到属性值的话,那么在子类中重写toString方法就行可以了
在重写的方法中,把对象属性值进行拼接返回
@Override
public String toString() {
return name+","+age;
}
- equals
比较两个对象是否相等
原本比较的是地址值,通过重写(直接用alt+回车的重写)可以比较对象里的属性值
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && name.equals(student.name);
}
//测试
Student stu1=new Student("zhangsan",18);
Student stu2=new Student("zhangsan",18);
// System.out.println(stu1);
boolean s1=stu1.equals(stu2);
System.out.println(s1);//结果为true
- 结论:
如果没有重写equals方法,那么默认使用Object中的方法进行比较,比较的是地址值是否相等
一般来讲地址值对我们意义不大,所以会进行方法的重写,将equals重写后比较的就是对象内部的属性值了
注:
String类中的equals方法会先判断参数是否为字符串,如果是字符串,再比较内部的属性,但是如果不是字符串,直接返回false
StringBuilder中没有equals方法,所以会直接使用Object中的equals方法,也就是比较地址值是否相等
clone方法(Object对象中的方法)
clone()对象克隆
protected Object clone()
把A对象的属性值完全拷贝给B对象,也叫对象拷贝,对象复制
clone()源码,被protected修饰,所以不能直接调用,需要在子类里重写,
ctrl+鼠标点击
就可以查看java源码
Cloneable接口
package java.lang;
public interface Cloneable {
}
Cloneable接口
如果一个接口中没有抽象方法
表示当前接口是一个标记性接口
当标记性接口(现在Cloneable表示)一旦实现,那么当前类的对象就可以被克隆
如果没有实现,当前类的对象就不能克隆
即,要想一个对象被克隆的话,就必须实现Cloneable这个接口,然后才能被克隆
- 浅克隆(浅拷贝)
拷贝时,先创建一个新的对象,然后
基本数据类型就拷贝其数值,
引用数据类型就拷贝其地址值 - 深克隆(深拷贝)
拷贝时,也会先创建一个新的对象,然后
基本数据类型就拷贝其数值,
引用数据类型在拷贝时,如果是new出来的,就会在堆内存中开辟出一块新的空间,然后把之前老空间里的数据复制到新空间里,再把新空间的新地址值
赋值给拷贝的变量,如果不是new出来的,像字符串,数据在串池里的,就会把之前的数据复用
小结:
即
浅克隆:不管对象内部的属性是基本数据类型还是引用数据类型,都完全拷贝过来,
深克隆:基本数据类型拷贝过来,字符串复用,引用数据类型还会重新创建新的
Object对象中的克隆是浅克隆
//User类
package ObjectStudy;
import java.util.StringJoiner;
//Cloneable
//如果一个接口中没有抽象方法
//表示当前接口是一个标记性接口
//现在Cloneable表示一旦实现,那么当前类的对象就可以被克隆
//如果没有实现,当前类的对象就不能克隆
public class User implements Cloneable{
private int id;
private String userName;
private String password;
private String path;
private int[] data;
public User() {
}
public User(int id, String userName, String password, String path, int[] data) {
this.id = id;
this.userName = userName;
this.password = password;
this.path = path;
this.data = data;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
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;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public int[] getData() {
return data;
}
public void setData(int[] data) {
this.data = data;
}
@Override
public String toString() {
return "角色编号为:" + "id=" + id + ", 用户名" + userName + ",密码'" + password + '\'' + ", 游戏图片" + path+",游戏进度"+arrString();
}
public String arrString(){
StringJoiner sj=new StringJoiner(",","[","]");
for (int i = 0; i < data.length; i++) {
sj.add(data[i]+" ");
}
return sj.toString();
}
@Override
protected Object clone() throws CloneNotSupportedException {
//调用java中的clone方法
//相当于让java帮我们克隆一个对象,并把克隆之后的对象返回出去
//这个对象是Object类,不是当前的子类User
//浅克隆
//return super.clone();
//深克隆的实现
//先获取原来对象中的数组
int []data=this.data;
//创建新数组
int []newData=new int[data.length];
//把老数组的值赋值给新数组
for (int i = 0; i < data.length; i++) {
newData[i]=data[i];
}
//调用父类的方法克隆对象
User u=(User)super.clone();
//由于父类(Object)的克隆是浅克隆,需要把newData的地址值赋值给data
u.data=newData;
return u;
}
}
//test类
package ObjectStudy;
public class Test2 {
public static void main(String[] args) throws CloneNotSupportedException {
int []data={1,2,3,4,5,6,7,8,9};
User u1=new User(1,"yier","123123qw","123wqesa",data);
//克隆对象
//方法在底层会帮我们创建一个对象,并把原对象中的数据拷贝过去
//书写细节:
//1.重写Object中的clone方法
//让JavaBean类实现Cloneable接口
//创建原对象并调用clone
User u2=(User)u1.clone();
int []arr=u1.getData();
//把第一个对象的数组值改变
arr[1]=100;
System.out.println(u1);
System.out.println(u2);
}
}
以后用到深克隆时,就会使用第三方的一个工具
使用步骤:
1.先创建一个lib(Library)包,将第三方的工具导入其中
这个深克隆的工具放到了
"D:\javastudy\新建文件夹\gson-2.6.2.jar"
导入后,再点击添加到库中,如果有问题,看160集
有问题了看
https://blog.csdn.net/qq_43842093/article/details/121276552
这篇文章
2.编写代码
//先创建一个工具的对象
Gson gson=new Gson();
//把对象变成一个字符串
String s= gson.toJson(u1);
User user=gson.fromJson(s,User.class);
//打印对象
System.out.println(user);
小结:
Objects
Objects是一个工具类,提供了一些方法去完成一些功能
Objects的成员方法
方法名 | 说明 |
---|---|
public static boolean equals(Object a, Object b) | 先做非空判断,比较两个对象 |
public static boolean isNull(Object obj) | 判断对象是否为null,如果是,返回true,否则返回false |
public static boolean nonNull(Object obj) | 判断对象是否为null,如果是,返回false,否则返回true |
boolean result=Objects.equals(s1,s2);
sout(Objects.isNull(s3));
sout(Objects.nonNull(s4));
直接使用Objects调用
细节:
1.Objects的equals方法的底层会先判断s1是否为null,如果时null,直接返回false
2.如果s1不为null,那么就利用s1再调用equals方法
3.此时的s1为Student类型,所以调用的还是Student的equals方法,如果没有重写,调用的就是Object的equals方法,比较地址值,如果重写了,就会比较属性值。
小结:
BigInteger(大的整数)
- BigInteger(大的整数)
整数在底层占用的字节数
byte一个字节,short两个字节,int四个字节,long八个字节
BigInteger可以表示超出long范围的整数,上限可以看作无限 - BigInteger的
构造方法
方法名 | 说明 |
---|---|
public BigInteger(int num,Random rnd) | 获取随机大范围整数,范围[0~2的num次方-1],包含0和2的num次方-1 |
public BigInteger(String val) | 获取指定的大整数,字符串里只能是整数 |
public BigInteger(String val,int radix) | 获取指定进制的大整数,字符串里只能是整数 |
valueOf(long val)
,BigInteger的静态
方法,可以获取BigInteger的对象,内部有优化
注:BigInteger对象的细节:对象一旦创建,内部记录的值就不能发生改变
- public BigInteger(String val,int radix) 获取指定进制的大整数
例如:
BigInteger bd1=new BigInter("100",10);//这里获取的就是10进制的100,打印出来就是100
BigInteger bd2=new BigInter("100",2);//这里获取的就是2进制的100,打印出来就是4
BigInteger bd3=new BigInter("123",2);//这里代码就会报错,因为二进制中没有2和3
- BigInteger(String val)与valueOf(long val)的不同之处
先来看相同的地方
他们都是创建一个大整数对象
BigInteger bd1=new BigInteger("100");
BigInteger bd1=BigInteger.valueOf(100);
不同:
1.valueOf能表示的范围比较小,只能在long的范围之内,不能超出long的范围
2.在内部,对常用的数字,-16到16进行了优化,提前把-16到16的BigInteger对象创建好了,多次获取时不会创建新的对象
BigInteger bd1=BigInteger.valueOf(16);
BigInteger bd2=BigInteger.valueOf(16);
sout(bd1==bd2);//==号比较的是地址值,结果是true,证明bd1和bd2是同一个对象
BigInteger bd3=BigInteger.valueOf(17);
BigInteger bd4=BigInteger.valueOf(17);
sout(bd3==bd4);//==号比较的是地址值,结果是false,证明bd3和bd4不是同一个对象.
如果不知道数据的范围有多大,就使用BigInteger(String val) 去获取,如果知道数据的范围在long范围内,就使用valueOf来获取
由于BigInteger对象一旦创建,内部记录的值就不能发生改变,所以,只要进行计算,像加减乘除在内的计算都会产生一个新的BigInteger对象
- 构造方法小结
BigInteger常见的成员方法
方法名 | 说明 |
---|---|
public BigInteger add(BigInteger val) | 加法 |
public BigInteger subtract(BigInteger val) | 减法 |
public BigInteger multiply(BigInteger val) | 乘法 |
public BigInteger divide(BigInteger val) | 除法,获取商 |
public BigInteger[] divideAndRemainder(BigInteger val) | 除法,获取商和余数 |
public boolean equals(Object x) | 比较是否相同,比较的是内部的属性值,不是对象地址 |
public BigInteger pow(int exponent) | 次幂 |
public BigInteger max/min(BigInteger val) | 返回较大值/较小值的BigInteger的对象 |
public int intValue() | 将BigInteger对象转为int类型整数,超出范围数据有误 |
因为BigInteger是一个对象,不能直接相加减,要通过方法来完成
一些方法的使用:
import java.math.BigInteger;
public class BigIntegerTest {
public static void main(String[] args) {
BigInteger bd1=BigInteger.valueOf(15);
BigInteger bd2=BigInteger.valueOf(5);
//加
BigInteger bd3=bd1.add(bd2);
System.out.println(bd3);
//减
BigInteger bd4=bd1.subtract(bd2);
System.out.println(bd4);
//乘
BigInteger bd5=bd1.multiply(bd2);
System.out.println(bd5);
//除
BigInteger bd6=bd1.divide(bd2);
System.out.println(bd6);
//除,获取商的同时获取余数
//返回值是一个数组,第一个值记录商,第二个值记录余数
BigInteger[] bd7=bd1.divideAndRemainder(bd2);
System.out.println(bd7);
System.out.println(bd7[0]);
System.out.println(bd7[1]);
//比较是否相同
Boolean a=bd1.equals(bd2);
System.out.println(a);
//次幂
BigInteger bd9=bd1.pow(3);
BigInteger bd10=bd2.pow(3);
System.out.println(bd9+ " "+bd10);
//返回较大值
BigInteger bd11=bd1.max(bd2);
System.out.println(bd11);
//返回较小值
BigInteger bd12=bd1.min(bd2);
System.out.println(bd12);
//转换类型
int b=bd1.intValue();
System.out.println(b);
}
}
- BigInteger底层存储方式
就是将一个大的整数分成多段存入数组中
1.先将数转化成补码,
2.将补码32位分成一段,然后分成多段
3.再将多段补码转化为多段十进制整数,
4.再将十进制整数存入数组
数组的最大长度就是int的最大值:2147483647(理论值)
- 小结
BigDecimal(大的小数)
计算机中的小数的存储方式
由于小数在计算机中用二进制表示时会很多,而每一种数据类型能够占用的空间是有限的,所以就会导致小数在计算机中的存储是不精确的
BigDecimal的作用:
用于小数的精确计算
用很大的小数
BigDecimal的一些构造方法
方法名 | 说明 |
---|---|
public BigDecimal(double val) | 将 double 转换为 BigDecimal,但是,此构造方法的结果有一定的不可预知性,即还是有可能是不精确的,所以不推荐使用 |
public BigDecimal(String val) | 将字符串转换为 BigDecimal。,它不会遇到 BigDecimal(double) 构造方法的不可预知问题。 |
与BigInteger一样,BigDecimal也有valueOf(long val)
的静态
方法,可以获取BigDecimal的对象,可以传递整数。
import java.math.BigDecimal;
public class BigDecimalTest {
public static void main(String[] args) {
//通过传递double类型的小数来创建对象
//这种方式有可能不精确,不推荐使用
BigDecimal bd1=new BigDecimal(0.01);
BigDecimal bd2=new BigDecimal(0.09);
System.out.println(bd1);
System.out.println(bd2);
//结果为
//0.01000000000000000020816681711721685132943093776702880859375
//0.0899999999999999966693309261245303787291049957275390625
//通过传递字符串表示的小数来创建对象
//常用的方式1
BigDecimal bd3=new BigDecimal("0.01");
BigDecimal bd4=new BigDecimal("0.09");
System.out.println(bd3);
System.out.println(bd4);
//结果为
//0.01
//0.09
//通过静态方法获取对象
//常用的方式2
BigDecimal bd6=BigDecimal.valueOf(10);
sout(bd6);
}
}
-
通过传递字符串表示的小数来创建对象
与valueOf静态方法
的差别
1.如果表示的数字没有超出double的取值范围,推荐使用静态方法
2.如果要表示的数字比较大,超出了double的取值范围,建议使用构造方法
3.在静态方法中,如果传递的是0~10之间的整数(包括0和10),那么,方法会返回已经创建好的对象,不会重新new,跟BigInteger是一样的原理,但是,如果传递的是小数的话,就不会有优化,会创建新的对象 -
Bigdecimal的常见方法
方法名 | 说明 |
---|---|
public static BigDecimal valueOf(double val) | 获取对象 |
public BigDecimal add(BigDecimal val) | 加法 |
public BigDecimal subtract(BigDecimal val) | 减法 |
public BigDecimal multiply(BigDecimal val) | 乘法 |
public BigDecimal divide(BigDecimal val) | 除法 |
public BigDecimal divide(BigDecimal val,精确几位(要保留几位小数),舍入模式(像四舍五入等)) | 除法 |
//前面几个跟BigInteger没什么差别
//主要是除法
BigDecimal bd1=BigDecimal.valueOf(10.0);
BigDecimal bd2=BigDecimal.valueOf(3.0);
//当除不尽时,public BigDecimal divide(BigDecimal val)这个方法就会报错,
//此时,就要使用
//public BigDecimal divide(BigDecimal val,精确几位,舍入模式)。这个方法
BigDecimal bd3=bd1.divide(bd1,2,RoundingMode.HALF_UP)//结果为3.33
其他的舍入模式可以在api文档中的RoundingMode类中可以找到
- BigDecimal的底层存储方式
会先得到字符串里的字符,将字符串分成一个个单独的字符,在将字符转化为ascll码表上的数值,再存储到数组里。
如下图
- 小结
一个编程思想:先把异常数据进行过滤,过滤后得到的就是满足要求的数据了
正则表达式
用于校验字符串的相关操作,可以很方便
字符串通过使用matches
方法来匹配相应的正则表达式
"abc".matches("[bcd]");//结果为false
- 正则表达式的作用:
1.校验字符串是否满足规则
2.在一段文本中查找满足要求的内容
正则表达式实际上就是一种规则
正则表达式的字符:
字符类(只匹配一个字符):
符号 | 作用 |
---|---|
[abc] | 字符串里字符的范围只能在a,b,c,之内 |
[^abc] | 除了abc之外的任何字符 |
[a-zA-Z] | a到z,A到Z |
[a-d[m-p]] | a到d或m到p |
[a-z&&[def]] | a-z和def的交集,为d,e,f |
[a-z&&[^bc]] | a-z 和非bc的交集。(等同于ad-z) |
[a-z&&[^m-p]] | a-z和除了m-p的交集(等同于a-lq-z) |
上面的方括号([ ]) 在正则表达式中表示一个字符的范围,字符串里出现的字符一定要在这个范围之内,不在范围之内的话会返回false
^
在正则表达式中表示取反的意思
&&
在正则表达式中表示取交集的意思
()
是分组
|
在方括号外表示并集
上述表格中 [abc]这个正则表达式 只表示一个字符的范围,正则表达式和字符会从左到右一个一个的去匹配
例如:
sout("a".metches("[abc]"));//true
sout("ab".metches("[abc]"));//false,因为这里的正则表达式只能表示一个字符的范围
//,如果想表示两个字符的范围的话,可以这样写
sout("ab".metches("[abc][abc]"));//这样就是表示两个字符
//取反
sout("a".metches("[^abc]")//false
sout("d".metches("[^abc]")//true
//a-z A-Z
sout("a".metches("[a-zA-Z]")//true
sout("0".metches("[a-zA-Z]")//false
sout("0".metches("[a-zA-Z0-9]")//ture
//[a-d[m-p]]
sout("a".metches("[a-d[m-p]]")//true
sout("m".metches("[a-d[m-p]]")//true
//[a-z&&[def]]
//这里如果只写了一个&,那么就不表示为一个交集了,就是单纯的一个&符号,没有任何含义
sout("a".metches("[a-z&&[def]]")//false
sout("d".metches("[a-z&&[def]]")//true
sout("&".metches("[a-z&[def]]")//true
sout("a".metches("[a-z&[def]]")//true
sout("e".metches("[a-z&[def]]")//true
//[a-z&&[^bc]
sout("a".metches("[a-z&&[^bc]")//ture
sout("b".metches("[a-z&&[^bc]")//false
//[a-z&&[^m-p]
sout("b".metches("[a-z&&[^m-p]")//true
sout("o".metches("[a-z&&[^m-p]")//false
- 预定义字符(只匹配一个字符)
符号 | 作用 |
---|---|
. | 任何字符 |
\d | 一个数字[0-9] |
\D | 非数字[^0-9] |
\s | 一个空白字符[\t\n\x0B\f\r] |
\S | 非空白字符:[^\s] |
\w | [a-zA-Z_0-9]英文数字下划线 |
\W | [^\w]一个非单词字符 |
在java中,\
表示转义字符,改变后面那个字符原本的含义
sout(" \" ")
打印出来的就是",sout(" " ")
这样写会报错
\\
就表示一个普通的\符号,不表示转义字符
例如
//.表示任意一个字符
sout("你".metches(".."))//false,因为有两个. ,所以需要两个字符
sout("你是".metches(".."))//true,
sout("你".metches("."))//true
// \d
因为\是转义字符,所以用\\来表示一个普通的\,则\\d就表示\d
sout("0".metches("\\d"))//true
sout("n".metches("\\d"))//false
sout("21".metches("\\d"))//false
// \D
sout("n".metches("\\D"))//true
sout("1".metches("\\D"))//false
// \w
sout("n".metches("\\w"))//true
sout("_".metches("\\w"))//true
sout("8".metches("\\w"))//true
sout("你".metches("\\w"))//false
// \W
sout("n".metches("\\W"))//false
sout("_".metches("\\W"))//false
sout("0".metches("\\W"))//false
sout("你".metches("\\W"))//true
正则表达式的数量词,可以一次表示多个字符
// x{6,} 至少6位
sout("234jnhs".matches("\\w{6,}"));//true
sout("23sx".matches("\\w{6,}"));//false
//x{4}必须是4位
sout("3424".matches("[a-zA-Z0-9]{4}"))//true
sout("3A424".matches("[a-zA-Z0-9]{4}"))//false
有忘记的可以看api文档Pattern类
正则表达式练习(字符串相关的练习)
正则表达式的一些使用技巧
先找到一个正确的数据,从左到右的去书写正则表达式的规则
package RegexDemo;
public class RegexDemo1 {
public static void main(String[] args) {
//手机号
String regex1="1[3-9]\\d{9}";//这里的1表示第一个数字只能是1
System.out.println("13949909592".matches(regex1));
//座机号
String regex2="0\\d{2,3}-?[1-9]\\d{4,9}";
//0表示第一位必须是0,\\d{2,3}表示0后面可以有2到3位的任意数
// ,-?表示-取一位或0位,[1-9]表示-后的第一个数字不能是0
//\\d{4,9}表示后面可以有4到9位任意数
//即regex2可以分为三部分“0\d{2,3}” “-?[1-9]” “\\d{4,9}”
System.out.println("0270-3241567".matches(regex2));
System.out.println("020-3231567".matches(regex2));
//邮箱号:1012622238@qq.com zhangsan123@163.com zhangsan123@163.com.cn
//分为三部分
//@的左边,@,@的右边
//第一部分:任意的数字 字母 下划线至少出现一次,即\\w+
//第二部分:@出现一次。即@
//第三部分:可以分为三部分
// 3.1 .的左边[\\w[^_]]{2,6}
// 3.2 .的表示 \\.
// 3.3 大小写字母 二到三次 [a-zA-Z]{2,3}
// 3.1和3.2看成一组可以出现一次或两次
//整体的表达式为:"\\w+@[\\w&&[^_]]{2,6}(\\.[a-zA-Z]{2,3}){1,2}"
String regex3="\\w+@[\\w&&[^_]]{2,6}(\\.[a-zA-Z]{2,3}){1,2}";
System.out.println("1012622238@qq.com".matches(regex3));
System.out.println("zhangsan123@163.com.cn".matches(regex3));
}
}
在实际开发中,使用anyrule插件可以快捷创建正则表达式
右键点击
然后把开头和末尾的符号删掉
String regex4="(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d";
以:
作为区分
package RegexDemo;
public class RegexDemo2 {
public static void main(String[] args) {
// 用户名
String regex1="\\w{4,16}";
// 身份证号码的简单校验
String regex2="\\d{17}[xX0-9]";
System.out.println("411124200409214511".matches(regex2));
//忽略大小写的方式
String regex4="(?i)abc";//忽略了abc的大小写
System.out.println("Abc".matches(regex4));
System.out.println("aBc".matches(regex4));
String regex5="a(?i)bc";//忽略了bc的大小写
System.out.println("ABc".matches(regex5));//false
System.out.println("aBC".matches(regex5));//true
String regex6="a((?i)b)c";//只忽略了b的大小写
System.out.println("aBc".matches(regex6));//true
System.out.println("aBC".matches(regex6));//false
}
}
小结(一些符号的作用)
爬虫(正则表达式作用2)
爬虫即在一段文本中查找满足的内容
两个类:
Pattern:表示正则表达式的类
Matcher:文本匹配器,作用按照正则表达式的规则去读取字符串,从头开始读取,在大字符串中去找符合匹配规则的字串
- 获取正则表达式的对象
通过Pattern的静态方法complie获取正则表达式的对象
Pattern p=Pattern.compile(“Java\d{0,2}”); - 通过Matcher获取文本匹配器的对象
Matcher m=p.matcher(str);(用m读取str,按照p的规则找里面的小串) - 通过循环获取每一个满足条件的值,循环里通过Matcher的group方法获取具体字符串,循环的条件是由Matcher对象的find方法来判断循环是否停止
while (m.find()){//这里的find找到相应的结果会返回true,
//然后进行打印,等到没有相应的结果了,自然就是false,结束循环
String s=m.group();
System.out.println(s);
}
package RegexDemo;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexDemo3 {
public static void main(String[] args) {
String str= "因为Java是全球排名第一的编程语言,Java8工程师也是市场需求最大的软件工程师,选择Java17,就是选择了高薪。";
// method1(str);
//获取正则表达式的对象
Pattern p=Pattern.compile("Java\\d{0,2}");
//获取文本匹配器的对象
//m,文本匹配器的对象
//str:大串
//p,规则
//m要在str中寻找符合规则的小串
Matcher m=p.matcher(str);
//获取find和group时使用循环获取
while (m.find()){//这里的find找到相应的结果会返回true,然后进行打印,等到没有相应的结果了,自然就是false,结束循环
String s=m.group();
System.out.println(s);
}
}
private static void method1(String str) {
//获取正则表达式的对象
Pattern p=Pattern.compile("Java\\d{0,2}");
//获取文本匹配器的对象
//m,文本匹配器的对象
//str:大串
//p,规则
//m要在str中寻找符合规则的小串
Matcher m=p.matcher(str);
//m会从大串从头开始读取,寻找是否有满足规则的字串
//没有返回false
//有返回true,在底层记录字串的起始索引和结束索引+1
Boolean b=m.find();
//底层会根据find找到的索引进行字符串的截取
//底层截取用的是substring(开始索引,结束索引)包头不包尾
//会把截取的小串进行返回
//用group方法截取
String s1=m.group();
System.out.println(s1);
}
}
带条件的数据爬取
带条件的数据爬取
Java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和Java11,因为这两个是长期支持版本,下一个长期支持版木是Java17,相信在未来不久Java17也会逐渐登上历史舞台
package RegexDemo;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexDemo4 {
public static void main(String[] args) {
String s="Java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和Java11," +
"因为这两个是长期支持版本,下一个长期支持版木是Java17,相信在未来不久Java17也会逐渐登上历史舞台";
//需求1
//1.定义正则表达式
String regex1="Java(?=8|11|17)";
//需求2
String regex2="Java(8|11|17)";//以前的方法
String regex3="Java(?:8|11|17)";//现在的方法
//需求3
String regex4="Java(?!8|11|17)";
//这里的?就表示括号前的Java,
// 后面的=表示要在Java后要跟随的数据,
// 但实际获取的还是Java这个数据
Pattern p1 = Pattern.compile(regex2);
Matcher m1 = p1.matcher(s);
while (m1.find()){
System.out.println(m1.group());
}
}
}
贪婪爬取和非贪婪爬取
贪婪爬取:在爬取时,尽可能多的爬取数据
非贪婪爬取:尽可能少爬取数据
Java默认为贪婪爬取
如果我们在数量词+
*
后加上问号,那么此时就是非贪婪爬取
package RegexDemo;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexDemo5 {
public static void main(String[] args) {
String s="Java自从95年问世以来,abbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa" +
"经历了很多版本,目前企业中用的最多的是Java8和Java11," +
"因为这两个是长期支持版本,下一个长期支持版木是Java17,相信在未来不久Java17也会逐渐登上历史舞台";
String regex1="ab+";
String regex2="ab+?";
Pattern p1=Pattern.compile(regex1);
Pattern p2=Pattern.compile(regex2);
Matcher m1=p1.matcher(s);
Matcher m2=p2.matcher(s);
//贪婪
while (m1.find()){
System.out.println(m1.group());
}
//非贪婪
while (m2.find()){
System.out.println(m2.group());
}
}
}
正则表达式在字符串方法的使用
方法名 | 说明 |
---|---|
public boolean matches(String regex) | 判断字符串是否满足正则表达式的规则 |
public String replaceAll(String regex,String replacement) | 按照正则表达式的规则进行替换 |
public String[] split(String regex) | 按照正则表达式的规则切割字符串 |
package RegexDemo;
public class RegexDemo11 {
public static void main(String[] args) {
String s="小实时jasndjasnoid1231小淡淡ndasjkldnan213131小灰灰";
// String regex="[\\w&&[^_]]+";
// String result=s.replaceAll(regex,"vs");//在源码中也是使用前面爬虫的方法来替换的
// //
// System.out.println(result);
String[]a= s.split("[\\w&&[^_]]+");
for (int i = 0; i <a.length ; i++) {
System.out.println(a[i]);
}
}
}
如果说字符串方法的形参名字为regex,那么就能识别正则表达式
分组
分组就是一个小括号
与数学的计算相似
每组是有组号的
在正则表达式中,\\x(组号)
就表示把第x组的数据再拿出来用一次
规则1;从1开始,连续不间断
规则2;以左括号为基准,最左边的是第一组,其次为第二组,依次类推
package RegexDemo;
public class RegexDemo6 {
public static void main(String[] args) {
//a123a b2324b c1234c d2131a(false)
String regex1="(.).+\\1";
System.out.println("a123a".matches(regex1));
System.out.println("b2324b".matches(regex1));
System.out.println("c1234c".matches(regex1));
System.out.println("d2131a".matches(regex1));
//abc213abc
String regex2="(.+).+\\1";
System.out.println("abc123abc".matches(regex2));
System.out.println("bc2324bc".matches(regex2));
System.out.println("csx1234csx".matches(regex2));
System.out.println("dz2131ac".matches(regex2));
//aaa123aaa
//这里的\\2:把首字母再拿出来使用
// *: \\2出现0次或多次
String regex3="((.)\\2*).+\\1";
}
}
- 捕获分组:
后续还要使用本组的数据时
正则内部用\\组号
正则外部用$组号
package RegexDemo;
public class RegexDemo7 {
public static void main(String[] args) {
String str="我要学编编编编编编编编程程程程程程程程程程程";
//(.)表示把重复内容的第一个字符看作第一组
// \\1表示第一字符再次出现
// +表示至少一次
// $1表示在正则表达式之外使用第一组的数据
System.out.println(str.replaceAll("(.)\\1+","$1"));
}
}
- 非捕获分组
分组之后不需要再用本组数据,仅仅是把数据括起来,不占用组号,不能用\\组号
来使用非捕获分组的数据
符号 | 含义 | 举例 |
---|---|---|
(?:正则) | 获取所有 | Java(?:8:11:17) |
(?=正则) | 获取前面部分 | Java(?=8:11:17) |
(?!正则) | 获取不是指定内容前面部分 | Java(?!8:11:17) |
上述表格就的具体案例就在带条件的数据爬取
- 分组小结
JDK7以前的时间相关类
有三个类
Date:时间
SimpleDateFormat: 格式化时间
Calender: 日历
Date
Date时间类
Date类是Jdk写好的一个JavaBean类,用来描述时间,精确到毫秒
利用空参构造创建的对象,默认表示系统当前时间
利用有参构造创建的对象,表示指定的时间
自己写一个时间类
package Date;
public class Date {
private long time;
public Date() {
this.time=System.currentTimeMillis();
}
public Date(long time) {
this.time = time;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
}
Date的常用方法
方法名 | 作用 |
---|---|
public Date() | 创建Date对象,表示当前时间 |
public Date(long date) | 创建Date对象,表示指定时间 |
public void setTime(long time) | 设置/修改毫秒值 |
public long getTime | 获取时间对象的毫秒值 |
package Date;
import java.util.Date;
public class datetest {
public static void main(String[] args) {
//创建时间对象表示时间
Date d1=new Date();
System.out.println(d1);
//创建时间对象表示指定时间
Date d2=new Date(0L);
System.out.println(d2);
//setTime修改时间
d2.setTime(1000L);
System.out.println(d2);
//getTime获取当前时间的毫秒值
long time=d2.getTime();
System.out.println(time);
}
}
package Date;
import java.util.Date;
import java.util.Random;
public class dateTest2 {
public static void main(String[] args) {
Date d1=new Date(0L);
long time=d1.getTime();
time=time+1000L*60*60*24*365;
d1.setTime(time);
System.out.println(d1);
Random r=new Random();
Date d2=new Date(Math.abs(r.nextInt()));
Date d3=new Date(Math.abs(r.nextInt()));
if(d2.getTime()>d3.getTime()){
System.out.println("d2>d3");
}else if(d2.getTime()< d3.getTime()){
System.out.println("d2<d3");
}else{
System.out.println("d2=d3");
}
}
}
小结
SimpleDateFormat
由于Date类只能按照默认方法取表示,所以出现了SimpleDateFormat类
- 格式化:
把时间变成我们喜欢的格式 - 解析:
把字符串表示的时间变成Date对象
构造方法 | 说明 |
---|---|
public SimpleDateFormat() | 构造一个SimpleDateFormat,使用默认格式 |
public SimpleDateFormat(String pattern) | 构造一个SimpleDateFormat,使用指定的格式 |
常用方法 | 说明 |
---|---|
public final String format(Date date) | 格式化(日期对象->字符串) |
public Date parse(String source) | 解析(字符串->日期对象) |
格式化时间格式的常用模式关系
在api帮助文档里有
- 格式化案例代码
package time;
import java.text.SimpleDateFormat;
import java.util.Date;
public class simpleDateFormatSet {
public static void main(String[] args) {
SimpleDateFormat sdf1=new SimpleDateFormat();
Date d1=new Date(0L);
String time=sdf1.format(d1);
System.out.println(time);
SimpleDateFormat sdf2=new SimpleDateFormat("yyyy-MM-dd HH-mm-ss");
System.out.println(sdf2.format(d1));
}
}
- 解析案例代码
package time;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class simpleDateFormatSet {
public static void main(String[] args) throws ParseException {
String time ="2023-04-08 17-10-50";
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH-mm-ss");
Date d1 = sdf.parse(time);
System.out.println(d1.getTime());
}
}
package time;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Test3 {
public static void main(String[] args) throws ParseException {
String ct="2002-11-09";
SimpleDateFormat sdf1=new SimpleDateFormat("yyyy-MM-dd");
Date d1 = sdf1.parse(ct);
SimpleDateFormat sdf2=new SimpleDateFormat("yyyy年MM月dd日");
System.out.println(sdf2.format(d1));
}
}
package time;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Test4 {
public static void main(String[] args) throws ParseException {
String t1="2023-11-11 0-01-00";
String t2="2023-11-11 0-11-00";
String count="2023-11-11 0-10-00";
//这里的转换和比值都应该写到方法里面
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH-mm-ss");
Date d1=sdf.parse(t1);
Date d2=sdf.parse(t2);
Date d3=sdf.parse(count);
if(panduan(d1,d3)){
System.out.println(11);
}else {
System.out.println(22);
}
}
public static Boolean panduan(Date date,Date date1){
if(date.getTime()<date1.getTime()){
return true;
}else{
return false;
}
}
}
Calendar
Calendar表示了系统当前时间的日历对象,可以单独修改,获取时间中的年,月,日。
应用场景:可以方便的修改时间
注:Calendar是一个抽象类,不能直接创建对象
- 获取Calendar日历类对象的方法
public static Calendar getInstance()
可以获取当前时间的日历对象 - Calendar对象的常用方法
方法名 | 说明 |
---|---|
public final Date getTime() | 获取日期对象,返回值Date |
public final setTime(Date date) | 给日历设置日期对象 |
public long getTimeInMillis() | 拿到时间毫秒值 |
public void setTimeInMillis(long millis) | 给日历设置时间毫秒值 |
public int get(int field) | 得到日历中某个字段(属性,成员变量)信息 |
public void set(int field, int value) | 修改日历中某个字段(属性,成员变量)的信息 |
public voidadd(int field,int amount) | 为某个字段添加/减少指定的值 |
在日历对象中,月份的范围是0-11,所以要在获取的月份值上+1,才表示真正的月份
星期的值是1,2,3,4,5,6,7,但是,在老外眼里,星期日是一周的开始,
所以1在日历对象中代表的是周日
即1(周日)2(周一)3(周二)4(周三)5(周四)6(周五)7(周六)
package time;
import java.util.Calendar;
import java.util.Date;
public class Test5 {
public static void main(String[] args) {
//Calendar是一个抽象类,不能直接new,而是通过一个静态方法获得子类对象
//底层原理:
//会根据系统的不同时区返回不同日历对象,默认表示当前时间
//会把年,月,日,时,分,秒等都放到Calendar对象的一个数组中
Calendar c=Calendar.getInstance();
System.out.println(c);
/*[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,
transitions=31,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,
ERA=1,YEAR=2023,MONTH=3,WEEK_OF_YEAR=15,WEEK_OF_MONTH=3,DAY_OF_MONTH=9,DAY_OF_YEAR=99,
DAY_OF_WEEK=1,DAY_OF_WEEK_IN_MONTH=2,AM_PM=1,HOUR=3,HOUR_OF_DAY=15,MINUTE=6,SECOND=44
,MILLISECOND=103,ZONE_OFFSET=28800000,DST_OFFSET=0]
*/
Date d=new Date(0L);
c.setTime(d);
System.out.println(c);
//在日历对象中,月份的范围是0-11,所以要在获取的月份值上+1,才表示真正的月份
//getTime
Date d1= c.getTime();
System.out.println(d1);
//getTimeInMillis()
System.out.println(c.getTimeInMillis());
//会把年,月,日,时,分,秒等都放到Calendar对象的一个数组中
//数组索引分别代表
//0:纪元
//1:年
//2:月
//3:一年中的第几周
//4:一个月中的第几周
//5:一个月中的第几天
//。。。以此类推,一直到16.
//然后在get,set,add方法中的field参数就是上面的索引值,通过索引来获取,或修改日历中得到字段
//当然,索引不易阅读,所以可以用相应的关键字来代替索引
//Calendar.YEAR就表示年,与索引1是相同的
c.set(Calendar.YEAR,2000);
//这里修改月份时可以超过11,超过的部分会继续往后加
//例如:2000 1 1的月份修改成13,那么就会变成2001 1 1
c.set(Calendar.MONTH,12);//要注意,这里的月份的范围还是0-11,现在表示的就是2001 1 1
//add就是对相应的字段相加减,正数加,负数减
c.add(Calendar.YEAR,10);//2011 1 1 周六
c.add(Calendar.YEAR,-10);//1991 1 1 周二
int year=c.get(Calendar.YEAR);//等同于int year=c.get(1)
int month=c.get(Calendar.MONTH)+1;
int date = c.get(Calendar.DAY_OF_MONTH);
int week=c.get(Calendar.DAY_OF_WEEK);
System.out.println(year+" "+month+" "+date+" "+getWeek(week));
}
public static String getWeek(int index){
//查表法
String[]arr={"","周日","周一","周二","周三","周四","周五","周六"};
return arr[index];
}
}
小结
JDK8新增的时间相关类
为何增加JDK8的时间相关类
1.jdk7的代码较为麻烦,jdk8的代码简单
2.jdk7在多线程环境下可能会导致数据安全的问题,jdk8的时间日期对象都是不可变的,解决了这个安全问题
Date类(ZoneId,Instant,ZoneDateTime)
- ZoneId类(时区)
方法名 | 说明 |
---|---|
static Set< String > getAvailableZoneIds() | 获取Java中支持的所有时区,会返回一个集合,集合里放的就是所有时区的名称 |
static ZoneId systemDefault() | 获得系统默认时区 |
static ZoneId of(String zoneId) | 获取一个指定时区 |
package time;
import java.time.ZoneId;
import java.util.Set;
public class ZoneIdtest {
public static void main(String[] args) {
//获取所有时区
Set<String> ZoneIds= ZoneId.getAvailableZoneIds();
System.out.println(ZoneIds);
//获取系统默认时区
ZoneId zoneId=ZoneId.systemDefault();
System.out.println(zoneId);
//指定时区
ZoneId zoneId1=ZoneId.of("Africa/Nairobi");
System.out.println(zoneId1);
}
}
- Instant类(时间戳)
方法名 | 说明 |
---|---|
static Instant now() | 获取当前时间的Instant对象(标准时间,获取中国的时间需要加上8个小时) |
static Instant ofXxxx() | 根据(秒/毫秒/纳秒)获取Instant对象 |
ZoneDateTime atZone(ZoneId zone) | 指定时区 |
boolean isXxx(Instant otherInstant) | 判断系列的方法 |
Instant minusXxx(long millisToSubtract) | 减少时间系列的方法 |
Instant plusXxx(long millisToSubtract) | 增加时间系列的方法 |
package time;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
public class InstantTest {
public static void main(String[] args) {
//now方法
Instant it=Instant.now();
System.out.println(it);
//ofXxx方法
//ofEpochMilli 获取的是指定毫秒值的时间戳
Instant it1=Instant.ofEpochMilli(0L);
//ofEpochMilli 获取的是指定秒值的时间戳
Instant it2=Instant.ofEpochSecond(1L);
//ofEpochMilli 获取的是指定秒加上纳秒值的时间戳
Instant it3=Instant.ofEpochSecond(1L,1000000000L);
//atZone方法
//这里atZone的参数是ZoneId的对象
ZonedDateTime time = Instant.now().atZone(ZoneId.of("Asia/Shanghai"));
System.out.println(time);
//isXxx
Instant it4=Instant.ofEpochMilli(0L);
Instant it5=Instant.ofEpochMilli(1000L);
//isBefore:判断调用者时间是否在参数时间的前面
boolean rs1=it4.isBefore(it5);
//isAfter:判断调用者时间是否在参数时间的后面
boolean rs2=it4.isAfter(it5);
//minusXxx:减少时间
Instant it7=Instant.ofEpochMilli(3000l);
System.out.println(it7.minusMillis(3000l));//minusMillis:减少毫秒
System.out.println(it7.minusSeconds(3l));//minusMillis:减少秒
System.out.println(it7.minusNanos(3000000000l));//minusMillis:减少纳秒
//同理,plusXxx与minusXxx方法相反
}
}
- ZoneDateTime类(带时区的时间)
主要是获取时间对象
方法名 | 说明 |
---|---|
static ZoneDateTime now() | 获取当前时间的ZoneDateTime对象(带时区) |
static ZoneDateTime ofXxxx() | 获取指定时间的ZoneDateTime对象 |
ZoneDateTime withXxx(Instant otherInstant) | 修改时间系列的方法 |
ZoneDateTime minusXxx(long millisToSubtract) | 减少时间系列的方法 |
ZoneDateTime plusXxx(long millisToSubtract) | 增加时间系列的方法 |
ofXxxx()有两种方法可以获取指定时间对象
- 方法1:
通过指定年月日时分秒纳秒+时区方式指定
ZoneDateTime time=ZoneDateTime.of(2023,4,7,16,13
,11,0,ZoneId.of("Asia/Shanghai"));
sout(time);
- 方法2
通过Instant+时区的方式指定
Instant it=Instant.ofEpochMilli(0L);
ZoneId zoneid=ZoneId.of("Asia/Shanghai"));
ZoneDateTime time=ZoneDateTime.ofInstant(it,zoneid);
sout(time);
其余方法都是创建完ZoneDateTime对象后直接调用就行,ieda会有提示
用法跟Instant相似
注:JDK8新增的时间对象都是不可变的
如果修改了一个时间后,调用者不会发生改变,但会产生一个新的时间
SimpleDateFormat类(日期格式化类)(DateTimeFormatter)
- DateTimeFormatter
用于时间的格式化和解析
方法名 | 说明 |
---|---|
static DateTimeFormatter ofPattern(格式) | 获取格式对象 |
String format(时间对象) | 按照指定方式格式化 |
第一个方法会指定时间的格式,返回DateTimeFormatter对象
调用第二个方法会按照指定的格式返回相应的字符串
package time;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
public class ZoneDateTimeTest {
public static void main(String[] args) {
ZonedDateTime z1=ZonedDateTime.now();
System.out.println(z1);
DateTimeFormatter d1=DateTimeFormatter.ofPattern("yyyy--MM--dd HH::mm::ss EE a");
System.out.println(d1.format(z1));
}
}
日历类(Calendar)(LocalDate,LocalTime,LocalDateTime)
- LocalDate(年 月 日)
- LocalTime(时 分 秒)(时间计算的很精确)
- LocalDateTime(年 月 日 时 分 秒)
这三个类常用的方法
方法名 | 说明 |
---|---|
static XXX now() | 获取当前时间的对象 |
static XXX of() | 获取指定时间的对象 |
get开头的方法 | 获取日历中的年月日,时分秒等信息 |
isBefore,isAfter | 比较两个LocalDate |
with开头的 | 修改时间系列的方法,不是在原有的基础上修改,而是创建一个新的对象 |
minus开头的 | 减少时间系列的方法 |
plus开头的 | 增加时间系列的方法 |
注:LocalDate和LocalTime用get开头的方法时,只能获得相对应的信息
即:LocalDate获得年月日,LocalTime获得时分秒
- 转换对象的方法
方法名 | 说明 |
---|---|
public LocalDate toLocalDate() | LocalDateTime转换成一个LocalDate对象 |
public LocalTime toLocalTime() | LocalDateTime转换成一个LocalTime对象 |
package time;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
public class LocalDateTest {
public static void main(String[] args) {
//LocalDate
LocalDate lo1=LocalDate.now();
//LocalTime
LocalTime lo2=LocalTime.now();
//LocalDateTime
LocalDateTime lo3=LocalDateTime.now();
System.out.println(lo1);
System.out.println(lo2);
System.out.println(lo3);
//of
LocalDate lo4=LocalDate.of(2000,10,1);
System.out.println(lo4);
//get,还有很多,就不一一展示了
lo4.getYear();
//注意,这里获得的是月的对象,直接打印会打印出对象的信息
//可以通过getValue方法来获取月份
//也可以直接通过getMonthValue方法来获取月的信息
System.out.println(lo4.getMonth().getValue());
System.out.println(lo4.getMonthValue());
System.out.println(lo4.getEra());
//is开头
System.out.println(lo4.isAfter(lo1));
System.out.println(lo4.isBefore(lo1));
//同理,with,minus,plus都是一样的使用方法
}
}
工具类(Duration,Period,ChronoUnit)
- Duration:用于计算两个
时间
间隔(秒和纳秒) - Period:用于计算两个日期间隔(年,月,日)
- ChronoUnit用于计算两个日期间隔
Period,计算年月日之间的时间间隔
package time;
import java.time.LocalDate;
import java.time.Period;
public class DPC {
public static void main(String[] args) {
LocalDate today=LocalDate.now();
System.out.println(today);
LocalDate oneDay=LocalDate.of(2004,8,9);
System.out.println(oneDay);
//第二个参数减去第一个参数
Period period=Period.between(oneDay,today);
//P18Y8M3D,这里period是一个对象,这里18Y就是相差18年,8M就是8个月,3D就是三天
System.out.println("相差的间隔时间对象"+period);
System.out.println(period.getYears());
System.out.println(period.getMonths());
System.out.println(period.getDays());
//toTotalMonths方法,可以获取的相差的总月数
System.out.println(period.toTotalMonths());
}
}
- Duration:用于计算两个
时间
间隔
代码基本同理,也是用
//第二个参数减去第一个参数
Duration duration=Duration.between(oneDay,today);
获取Duration对象,
获取相应的准确的时间间隔时,用to方法名
来获取
例如:
toDays()就是获取天数的时间差
toHours()就是获取小时的时间差
最小的单位可以达到纳秒
- ChronoUnit
是最常用的,因为他几乎包含了上面两个类的时间单位
使用
package time;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
public class ChronoUnitTset {
public static void main(String[] args) {
LocalDateTime ldt1=LocalDateTime.now();
LocalDateTime ldt2=LocalDateTime.of(2001,10,10,10,10,10);
System.out.println("相差的年"+ ChronoUnit.YEARS.between(ldt2,ldt1));
System.out.println("相差的月"+ ChronoUnit.MONTHS.between(ldt2,ldt1));
System.out.println("相差的周"+ ChronoUnit.WEEKS.between(ldt2,ldt1));
System.out.println("相差的天"+ ChronoUnit.DAYS.between(ldt2,ldt1));
System.out.println("相差的时"+ ChronoUnit.HOURS.between(ldt2,ldt1));
System.out.println("相差的分"+ ChronoUnit.MINUTES.between(ldt2,ldt1));
System.out.println("相差的秒"+ ChronoUnit.SECONDS.between(ldt2,ldt1));
System.out.println("相差的毫秒"+ ChronoUnit.MILLIS.between(ldt2,ldt1));
System.out.println("相差的纳秒"+ ChronoUnit.MICROS.between(ldt2,ldt1));
System.out.println("相差的半天"+ ChronoUnit.HALF_DAYS.between(ldt2,ldt1));
System.out.println("相差的十年"+ ChronoUnit.DECADES.between(ldt2,ldt1));
}
}
包装类
包装类:基本数据类型对应的引用数据类型(对象)
- 内存的角度理解包装类
即,用一个对象,把基本数据类型给包起来,就是包装类
所有基本数据类型的包装类
获取Integer对象(JDK5之前)
- 构造方法和静态方法获取Integer对象的区别
用静态方法获取Integer对象时,如果对象的数值在-128到127之间,就会使用已经创建好的对象,像字符串的串池那样,使用时直接调用,而不是重新创建
Integer ig1=Integer.valueof(127);
Integer ig2=Integer.valueof(127);
sout(ig1==ig2)//true,==比较的时地址值,说明ig1和ig2是同一个对象
jdk5之前,包装类的计算
Integer ig1=Integer.valueof(1);
Integer ig2=Integer.valueof(2);
//先把对象变成基本数据类型
//计算后再变回对象
int result=ig1.intValue()+ig2.intValue();
Integer ig3=new Integer(result);
jdk5之后的改进
即在jdk5以后,int和Integer可以看作是同一个东西,因为在内部可以自动转化
-
小结
-
Integer的成员方法
方法名 | 说明 |
---|---|
public static String toBinaryString(int i) | 得到二进制 |
public static String toOctalString(int i) | 得到八进制 |
public static String toHexString(int i) | 得到十六进制 |
public static int parselent(String s) | 将字符串类型的整数转化为int类型的整数 |
都是静态方法,可以用类名.
调用
package time;
public class IntegerTest {
public static void main(String[] args) {
String s1=Integer.toBinaryString(100);
String s2=Integer.toOctalString(100);
String s3=Integer.toHexString(100);
int i=Integer.parseInt("100");
boolean b=String.parseBoolean("true");
sout(b);//true
//在类型转换时,参数只能是数字
//8中包装类中,除了Character其他都有相应的parseXXx方法,进行类型转换
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
System.out.println(i);
}
}
public static int parselent(String s) 将字符串类型的整数转化为int类型的整数,
这个以后用的最多
以后如果想要键盘录入的话,统一使用nextLine,然后用相应包装类的转换方法把类型转换,这里接收nextLine的参数尽量是String类型
即
Scanner sc=new Scanner(System.in);
String line=sc.nexeLine();
练习
package ApiTest;
import java.util.ArrayList;
import java.util.Scanner;
public class one {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
ArrayList<Integer>ig=new ArrayList<>();
while (true){
System.out.println("请输入一个整数");
String numStr = sc.nextLine();
int i=Integer.parseInt(numStr);
if(i<0||i>100){
System.out.println("输入的数据有误");
continue;
}
//这里注意
//i是基本数据类型
//集合中只能存储引用数据类型
//所以在底层,这里的i已经自动装箱变成了Integer类型
ig.add(i);
int sum=getSum(ig);
if(sum>200){
System.out.println("计算结束");
break;
}
}
}
private static int getSum(ArrayList<Integer> ig) {
int sum=0;
for (int i = 0; i < ig.size(); i++) {
int a=ig.get(i);
sum=sum+a;
}
return sum;
}
}
package ApiTest;
public class two {
public static void main(String[] args) {
String s="12345678";
if(!s.matches("[1-9]\\d{0,9}")){
System.out.println("数据有误");
}else{
int sum=0;
for (int i = 0; i < s.length(); i++) {
char a=s.charAt(i);
int b=a-'0';
sum=sum*10+b;
}
System.out.println(sum+1);
}
}
}
package ApiTest;
public class three {
public static void main(String[] args) {
System.out.println(getBinaryString(6));
// System.out.println(Integer.toBinaryString(100));
}
public static String getBinaryString(int a){
StringBuilder sb=new StringBuilder();
while (true){
if (a==0){
break;
}
System.out.println(a);
int b=a%2;
sb.insert(0,b);
a=a/2;
}
return sb.toString();
}
}
jdk7
package ApiTest;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class four {
public static void main(String[] args) throws ParseException {
String s="2004年8月9日";
SimpleDateFormat sdf=new SimpleDateFormat("yyyy年MM月dd日");
Date d=sdf.parse(s);
long time=d.getTime();
// Date now=new Date();
// long time1=now.getTime();//这种方法不好
//可以直接获取当前时间的毫秒值
long time1=System.currentTimeMillis();
long time2=time1-time;
long day=time2/3600/1000/24;
System.out.println(day);
}
}
jdk8
package ApiTest;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
public class five {
public static void main(String[] args) {
LocalDateTime l1=LocalDateTime.now();
LocalDateTime l2=LocalDateTime.of(2004,8,9,0,0,0);
System.out.println(ChronoUnit.DAYS.between(l2,l1));
}
}
package ApiTest;
import java.time.LocalDate;
import java.util.Calendar;
public class six {
public static void main(String[] args) {
//jdk7
Calendar c=Calendar.getInstance();
c.set(2000,2,1);//月份的范围0-11
c.add(Calendar.DAY_OF_MONTH,-1);
int day1=c.get(Calendar.DAY_OF_MONTH);
System.out.println(day1);
//获取一年中的天数,判断天数是否为366天,若是,就是闰年,不是就不是闰年
//jdk8
//一个方法就直接搞定
//isLeapYear,判断是否为闰年,是就是true,否就是false
LocalDate l1=LocalDate.of(2000,2,1);
System.out.println(l1.isLeapYear());
}
}