how2j学习总结-未完
Markdown
标题
#+空格+ 一级标题
字体
粗体 %两边两个*
斜体 %两边一个*
斜体+加粗 %两边三个*
删除线 %两边两个~
引用
文本引用效果 %用>+内容
分割线
%三个- 或者三个~
图片
% ! + [] + ()
超链接
点击跳转
%[] + ()
列表
A
B %数字+点+空格
- A
- B %减号+空格
表格
名字|性别|生日
–|--|–|
张三|男|7.2 %源代码模式删除空格
名字 | 性别 | 生日 |
---|---|---|
张三 | 男 | 7.2 |
代码
public
%三个`
预科
冯诺依曼体系结构
系统软件
DOS(磁盘操作系统)、windows、Linux、Unix、Mac、Android、iOS
快捷键
alt + F4 %关闭当前窗口
win + E %打开我的电脑
win + D %最小化所有页面
DOS
- win + R 输入cmd
- 任意文件夹下面,按住shift+鼠标右键,打开powershell
- 资源管理器地址栏前面加cmd
D: %盘符+冒号进入磁盘dir %显示目录cd + 地址 %change directory进入cd + /d + D:\电影 %跨盘符切换cd.. %返回上一级cls %clean screen清理屏幕ipconfig %查看电脑ip config配置#打开应用calcmspaintnotepad#ping 命令ping www.google.com#创建文件夹md#文件cd 空格 >a.txtdel a.txtrd
摩尔定律
第三代语言
C语言时典型的面向过程的语言,C++、Java时典型的面向对象的语言。
Java入门
高可用 高性能 高并发
特性
简单性、面向对象、可移植性、高性能、分布式、动态性、多线程、安全性、健壮性
跨平台
write once、run anywhere
版本
JavaSE:标准版(桌面程序,控制台开发)
JavaME:嵌入式开发(几乎淘汰)
JavaEE:企业级开发(web端,服务器开发)
JDK、JRE、JVM
JDK:Java Development Kit %开发者工具箱
JRE:Java Runtine Environment %运行环境
JVM:Java Virtual Machine %虚拟机
安装开发环境
- 配置环境变量JAVA_HOME
- 配置path变量
测试安装成功
cmd–> java -version
目录
bin:可执行程序
include:引入C语言中的一些头文件
jre:运行环境
lib:库文件
src:资源文件 类
hello world
记事本编辑editplus配置:关闭文件类型及语法自动完成;关闭自动保存创建备份
public class hello{public static void main(String[] args){System.out.print("hello world");}
}
编译javac ().java
运行java ()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-olU8pZWX-1633015148278)(F:\学习\code\hello.png)]
Java程序运行机制
compile编译型 解释型
IDEA
集成开发环境IDE(Integrated Development Environment ):用于提供程序开发环境的应用程序,一般包括代码编辑器、编译器、调试器和图形用户界面等工具。
Java基础
注释
- 单行注释://
- 多行注释:/* ··· */
- 文档注释:/** … */
有趣的代码注释
标识符
Java所有的组成部分都需要名字。类名、变量名以及方法名都被称为标识符。
标识符以字母、美元符$、下划线开始。首字符之后可以是任意组合(字母、下划线、美元符、数字)。(变量可以中文)
大小敏感。
关键字列表:
数据类型
强类型语言:要求变量使用严格符合规定,所有变量必须先定义才使用。
弱类型语言:VB、JS
八大基本数据类型
byte:Java中最小的数据类型,在内存中占8位(bit),即1个字节,取值范围-2^7 ~ 2^7-1(-128~127),默认值0
short:短整型,在内存中占16位,即2个字节,取值范围-2^15 ~ 2^15-1(-32768~32767),默认值0
int:整型,用于存储整数,在内在中占32位,即4个字节,取值范围-2^31 ~ 2^31-1 (-2147483648~2147483647)约21亿,默认值0
long:长整型,在内存中占64位,即8个字节-263~263-1,默认值0L
Java语言的整型常数默认为int型,声明long型常量可以后加‘ l ’或‘ L ’ 。
float:浮点型,在内存中占32位,即4个字节,用于存储带小数点的数字(与double的区别在于float类型有效小数点只有6~7位),默认值0
double:双精度浮点型,用于存储带有小数点的数字,在内存中占64位,即8个字节,默认值0
注意: 默认的小数值是double类型的
注意: 默认的小数值是double类型的
所以 float f = 54.321会出现编译错误,因为54.321的默认类型是 double,其类型 长度为64,超过了float的长度32
在数字后面加一个字母f,直接把该数字声明成float类型
float f2 = 54.321f,
这样就不会出错了当以f或者F结尾的时候,就表示一个float类型的浮点数,否则就是double类型(以d或者D结尾,写不写都可以)。
浮点数还可以用E或者e表示(科学计数法)
e2表示10的二次方,即100
1.234e2 = 1.234x100
char:字符型,用于存储单个字符,占16位,即2个字节,取值范围0~65535,默认值为空
字符的字面值放在单引号中
字符串的字面值放在双引号中
需要注意的是,\表示转义,比如需要表示制表符,回车换行,双引号等就需要用 \t \r \n " 的方式进行
public static void main(String[] args) {String name = "盖伦";char a= 'c';//以下是转义字符char tab = '\t'; //制表符char carriageReturn = '\r'; //回车char newLine = '\n'; //换行char doubleQuote = '\"'; //双引号char singleQuote = '\''; //单引号char backslash = '\\'; //反斜杠} }
boolean:布尔类型,占1个字节,用于判断真或假(仅有两个值,即true、false),默认值false
byte short int long
float double
char
boolean
public class Helloword {public static void main(String[] args) {long val = 26L; //以L结尾的字面值表示long型int decVal = 26; //默认就是int型int hexVal = 0x1a; //16进制int oxVal = 032; //8进制int binVal = 0b11010; //2进制System.out.println(oxVal+val);}}
引用类型
String:String类型其实并不是基本类型,但是它是如此广泛的被使用,常常被误以为是一种基本类型。
String类型是Immutable的,一旦创建就不能够被改变
类
接口
数组
数据类型拓展
System.out.println(Integer.toBinaryString(i2)); //查看一个整数对应的二进制的方法
整数拓展:二进制0b、十进制、八进制0、十六进制0x(0-9,A-F)
浮点数拓展:float有限、离散、舍入误差、大约、接近但不等于(最好不要使用float);银行常用BigDecimal
System.out.println((int)c); //强制转码
所有字符本质还是数字
编码Unicode表:(97=a、65=A) 2字节 0-65536(2^16)
转义字符
字符 | 作用 |
---|---|
\n | 换行 \n 换行符,使光标定位到下一行。 |
\r | 回车 \r 回车符,使光标回到当前行的行首。如果之前该行有内容,则会被覆盖; |
\t | 制表 (相当于tab) |
\f | 换页 |
\t 制表符(相当于键盘上按下TAB键之间的距离,一般为8个空格。)
使用制表符是把输出的切入点移动到下一个能被8整除的位置上。
即当打印小于八格的结果,用空格补足八格再打印下一个结果;当大于等于八格,小于十六格,补足十六格;以此类推。
\n换行符
public class text02 {public static void main(String[] args) {System.out.println("1234567 12345678 0.1234567 0.12345678");System.out.println("1234567\t12345678\t0.12345\t0.12345678");System.out.println("1234567 \n12345678\n0.1234567\n0.12345678");}
}
类型转换
小数的优先级大于整数
byte,short,char->int->long->float->double
//强制转换 (类型)变量名 高到低 要避免内存溢出
//自动转换 低到高
运算时如果有一个参数是L或者double,结果位L或者double
运算时如果比int低,结果均为int
注意
- 不能对布尔值转换
- 不能把对象类型转换为不相干类型
- 高到低时,强制转换
- 可能出现内存溢出或者精度问题
int money=10_0000_0000; JDK7新特性,数字之间可以用下划线分割
变量
type varName [=value] [{,varName[=value]}];//数据类型 变量名 =值;可以使用逗号隔开来声明多个同类型变量。
当一个变量被声明在类下面
变量就叫做字段 或者属性、成员变量、Field
比如变量i,就是一个属性。
那么从第2行这个变量声明的位置开始,整个类都可以访问得到
所以其作用域就是从其声明的位置开始的整个类
声明在方法内的变量,叫做局部变量
其作用域在声明开始的位置,到其所处于的块结束位置
声明在方法内的变量,叫做局部变量
其作用域在声明开始的位置,到其所处于的块结束位置
public class HelloWorld {public void method1() {int i = 5; //其作用范围是从声明的第4行,到其所处于的块结束12行位置System.out.println(i);{ //子块System.out.println(i); //可以访问iint j = 6;System.out.println(j); //可以访问j}System.out.println(j); //不能访问j,因为其作用域到第10行就结束了}}
变量作用域
- 类变量:从属类
- 实例变量:在类里,从属于对象,如果不初始化,会变成默认值(基本类型0 0.0 Boolean默认false,此外都是null
- 局部变量:在方法内,必须声明和初始化
public class Variable{static int salary = 2500; //类变量String str = "hello"; //实例变量int age;public void method(){int i = 0; //局部变量System.out.println(i);//实例变量 变量类型 变量名字 = new Variable();Variable variable = new Variable();System.out.println(variable.str); //输出nullSystem.out.println(variable.age); //输出0//类变量 staticSystem.out.println(salary);}
}
常量
初始化(initialize)后不能再改变值
final 常量名=值;//一般使用大写字母
如果在声明的时候未赋值,那么可以在后面代码进行唯一的一次赋值
inal 除了修饰变量,还可以修饰类,修饰方法
final修饰的类不能被继承 final定义的方法不能被重写 final定义的常量不能被重写赋值
final int[] a = {1,2,3} 这种栈里a变量空间存放的是堆内存的地址。地址是不可变的,但是数组里的值是可以变
final 修饰的变量在方法中,可以先初始化再赋值。 但是如果是全局变量,必须在初始化的时赋值,不然会报错。
当final修饰形参时,不能在方法里赋值,因为在调用方法的时候,就一定会第一次赋值了,后面不能再进行多次赋值
public class text03 {//修饰符,不存在先后顺序final static double PI = 3.14;//static final double PI = 3.14;public static void main(String[] args) {System.out.println(PI);}
}
表达式
表达式是由变量、操作符以及方法调用所构成的结构。
; 也是一个完整的表达式
块
从**{** 开始 到对应的**}** 结束,即一个块
public class HelloWorld { //类对应的块public static void main(String[] args) { //主方法对应的块System.out.println("abc");}
}
命名规范
类成员变量:首字母小写和驼峰原则:monthSalary 除了第一个单词以外,后面的单词首字母大写
局部变量:首字母小写和驼峰原则
常量:大写字母和下划线:MAX_VALUE
类名:首字母大写和驼峰原则:GoodMan
方法名:首字母小写和驼峰原则:runRun()
运算符
算术运算符:+、-、*、/、%(模运算,取余)、++、–
赋值运算符:=
关系运算符:>、<、>=、<=、==、!=、instanceof
逻辑运算符:&&、||、!
& && 长路与 和 短路与 | || 长路或 和 短路或 ! 取反 ^ 异或^
public static void main(String[] args){//长路与 无论第一个表达式的值是true或者false,第二个的值,都会被运算 //短路与 只要第一个表达式的值是false的,第二个表达式的值,就不需要进行运算了//长路或 无论第一个表达式的值是true或者false,第二个的值,都会被运算 //短路或 只要第一个表达式的值是true的,第二个表达式的值,就不需要进行运算了 int i = 2; System.out.println( i== 1 | i++ ==2 ); //无论如何i++都会被执行,所以i的值变成了3 System.out.println(i);}
位运算符:&、|、^、~、>>、<<、>>>(了解)
Integer.toBinaryString() 一个整数的二进制表达 | 位或 & 位与 ^ 异或 ~ 取非 << >> 左移 右移 >>> 带符号右移与无符号右移
public class HelloWorld {public static void main(String[] args) {int i =5;int j = 6;System.out.println(Integer.toBinaryString(i)); //5的二进制是 101System.out.println(Integer.toBinaryString(j)); //6的二进制是110System.out.println(i^j); //所以 5^6 对每一位进行或运算,得到 011->3System.out.println(i^0); //任何数和自己进行异或 都等于 0System.out.println(i^i); //任何数和0 进行异或 都等于自己} }
条件运算符 ?、:
扩展赋值运算符:+=、-=、*=、/=
Ctrl+D 在IDEA中复制当前行到下一行
//运算时如果有一个参数是L或者double,结果位L或者double
//运算时如果比int低,结果均为int,包括char
public class Text04 {public static void main(String[] args) {long a = 123456789L;int b = 12356;short c = 100;byte d = 5;double e = 3.14;char f = 'g';System.out.println(a+b);//LongSystem.out.println(b+c);//IntSystem.out.println(c+d);//IntSystem.out.println(d+e);//DoubleSystem.out.println(d+f);//Int}
}
一元运算符
int b = a++; //执行完这段代码后,先给b赋值,再自增
int c = ++a; //执行完这段代码前,先自增,再赋值
幂运算需要使用工具类
Math.pow(3,2); //9
逻辑与会造成短路运算
public class Text05 { public static void main(String[] args) { boolean a = true; boolean b = false; System.out.println("a && b: "+ (a && b)); //逻辑与运算:两个都为真,结果真;若第一个为假,会造成短路运算System.out.println("a || b: "+ (a || b)); System.out.println("!(a && b): "+ !(a && b)); //短路运算 int c = 5; boolean d = (c<4) && (c++<4); System.out.println(d); System.out.println(c); //5 }
}
位运算是对二进制位的运算
A&B 与
A|B 或
A^B 异或(相同为0,不同为1)
~B 非
最快速度运算2*8(位运算,效率高)
0000 0000 00000 0001 10000 0010 20000 0100 40000 1000 8
<< 左移 (*2) >>右移(/2)
>>:带符号右移。正数右移高位补0,负数右移高位补1。比如:
4>>1,结果为2;
-4>>1,结果为-2.
>>>:无符号右移。无论正数还是负数,高位通通补0.
对于正数而言,>>和>>>没有区别。
负数转换为二进制,就是将其相反数(正数)的补码的每一位变反(1变0,0变1)最后将变完了的数值加1,就完成了负数的补码运算。这样就变成了二进制。
对于负数而言,-2>>>1,结果是2147483647(Integer.MAX_VALUE)
-1>>>1,结果是2147483647(Integer.MAX_VALUE)
a+=b; //a=a+b
a-=b; //a=a-b
+=即自加
i+=2;
等同于
i=i+2;
其他的 -= , *= , /= , %= , &= , |= , ^= , >>= , >>>= 都是类似
public class Text06 { public static void main(String[] args) { int a = 10; int b = 20; //字符串连接符 + , String System.out.println(a+b); //30 System.out.println(""+a+b); //1020 System.out.println(a+b+""); //30 }
}
三元运算符 条件运算符
x ?y :z //如果x==ture,则结果为y,否则结果为z
public class Text07 { public static void main(String[] args) { int score = 80; String type = score < 60 ? "不及格" : "及格"; System.out.println(type); }
}
优先级
Java 语言中运算符的优先级共分为 14 级,其中 1 级最高,14 级最低。在同一个表达式中运算符优先级高的先执行。
**结合性:**当一个运算对象两侧的运算符优先级别相同时,则按运算符的结合性来确定表达式的运算顺序。
优先级 | 运算符 | 结合性 |
---|---|---|
1 | ()、[]、{} | 从左向右 |
2 | !、+、-、~、++、– | 从右向左 |
3 | *、/、% | 从左向右 |
4 | +、- | 从左向右 |
5 | «、»、>>> | 从左向右 |
6 | <、<=、>、>=、instanceof | 从左向右 |
7 | ==、!= | 从左向右 |
8 | & | 从左向右 |
9 | ^ | 从左向右 |
10 | | | 从左向右 |
11 | && | 从左向右 |
12 | || | 从左向右 |
13 | ?: | 从右向左 |
14 | =、+=、-=、*=、/=、&=、|=、^=、~=、«=、»=、>>>= | 从右向左 |
包机制
包的本质就是一个文件夹
一般使用公司域名倒置作为包名:com.baidu.www
为了使用某一个包的成员,我们需要再Java程序中明确导入该包。使用“import”语句可以完成此功能。
import package1.package2.classname;
import package1.package2.*; //导入这个包下所有的类
JavaDoc(/**… */)
加在类上面就是类的注释,加在方法上面就是方法的注释
@author
@version 版本号
@since 指明需要最早使用的jdk版本
@param 参数名
@return 返回值情况
@throws 异常抛出情况
打开cmd,进入该类所在的目录,然后输入
javadoc -encoding UTF-8 -charset UTF-8 Text08.java
- -encoding UTF-8:表示设置编码。
- -charset UTF-8:也表示设置编码。
IDEA
Java流程控制
Scanner对象
注意: 使用Scanner类,需要在最前面加上
import java.util.Scanner;
实现程序和人的交互,java.util.Scanner是Java5的新特征,可以通过Scanner类来获取用户的输入。
Scanner s = new Scanner(System.in);
通过Scanner类的next()与nextLine()方法获取输入的字符串
String str = scanner.nextLine();
读取前,需要使用hasNext()与hasNextLine()判断是否还有输入数据。
凡是属于IO流的类如果不关闭会一直占用资源 即输入输出流
scanner.close();
package com.wang.operator;import java.util.Scanner;public class Demo01 {public static void main(String[] args) {//创建一个扫描器对象,用于接收键盘数据Scanner scanner = new Scanner(System.in);System.out.println("使用next方式接收:");//判断用户有没有输入字符串if(scanner.hasNext()){//使用next方式接收String str = scanner.next();System.out.println("输入的内容为:"+str); //+为连接符}//凡是属于IO流的类如果不关闭会一直占用资源 即输入输出流scanner.close();}
}
next()读取到空格结束;
nextLine()读取到enter结束;
next() 与 nextLine() 区别
next():
- 1、一定要读取到有效字符后才可以结束输入。
- 2、对输入有效字符之前遇到的空白,next() 方法会自动将其去掉。
- 3、只有输入有效字符后才将其后面输入的空白作为分隔符或者结束符。
- next() 不能得到带有空格的字符串。
nextLine():
- 1、以Enter为结束符,也就是说 nextLine()方法返回的是输入回车之前的所有字符。
- 2、可以获得空白。
需要注意的是,如果在通过nextInt()读取了整数后,再接着读取字符串,读出来的是回车换行:"\r\n",因为nextInt仅仅读取数字信息,而不会读取回车换行"\r\n".
所以,如果在业务上需要读取了整数后,接着读取字符串,那么就应该连续执行两次nextLine(),第一次是取走回车换行,第二次才是读取真正的字符串
import java.util.Scanner;public class HelloWorld {public static void main(String[] args) {Scanner s = new Scanner(System.in);int i = s.nextInt();System.out.println("读取的整数是"+ i);String rn = s.nextLine();String a = s.nextLine();System.out.println("读取的字符串是:"+a);} }
判断是否是整数与小数数据
package com.wang.operator;import java.sql.SQLOutput;
import java.util.Scanner;public class Demo02 {public static void main(String[] args) {Scanner scanner = new Scanner(System.in); //new Scanner(System.in);int i = 0;float f = 0.0f;System.out.println("请输入整数:");if (scanner.hasNextInt()){i =scanner.nextInt();System.out.println("整数数据:"+i);}else{System.out.println("输入的不是整数数据!");}System.out.println("请输入小数:");if (scanner.hasNextFloat()){f =scanner.nextFloat();System.out.println("小数数据:"+f);}else{System.out.println("输入的不是小数数据!");}scanner.close();}
}
输入多个数字,求和与平均值,enter确认,非数字结束并输出
package com.wang.operator;import java.util.Scanner;public class Demo03 {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);double sum = 0;int m = 0;while(scanner.hasNextDouble()){double x = scanner.nextDouble();m++;sum = sum + x;System.out.println("输入第"+m+"个数据,当前结果sum="+sum);}System.out.println(m+"个数的和为"+sum);System.out.println(m+"个数的平均值为"+(sum/m));scanner.close();}
}
顺序结构
顺序执行,任何算法的基本结构。
选择结构
//如果只有一个表达式可以不用写括弧,看上去会简约一些
if(b){System.out.println("yes1");}if(b)System.out.println("yes1");
在第6行,if后面有一个分号; 而[分号也是一个完整的表达式]
如果b为true,会执行这个分号,然后打印yes
如果b为false,不会执行这个分号,然后打印yes
这样,看上去无论如何都会打印yespublic class HelloWorld {public static void main(String[] args) {boolean b = false;if (b);System.out.println("yes");} }
- if单选择结构
- if双选择结构
- if多选择结构
- 嵌套的if结构
- switch多选择结构:判断一个变量与一系列值中某个值是否相等,每个值称为一个分支。
package com.wang.operator;import java.util.Scanner;public class Demo04 {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);System.out.println("输入内容");String s = scanner.nextLine();//判断字符是否相等if (s.equals("Hello")){System.out.println(s);}else{System.out.println("不一致");}System.out.println("End");scanner.close();}
}
switch多选择结构:判断一个变量与一系列值中某个值是否相等,每个值称为一个分支。
变量类型可以是byte、short、int、char、String
注: 每个表达式结束,都应该有一个break;
注: String在Java1.7之前是不支持的, Java从1.7开始支持switch用String的,编译后是把String转化为hash值,其实还是整数
注: enum是枚举类型,在枚举章节有详细讲解
case标签必须为字符串常量或者字面量
case穿透:不加break就全输出
switch(expression){case value: break;default:}
package com.wang.operator;public class Demo05 {public static void main(String[] args) {String name = "wang";switch (name){case "王":System.out.println("王");break;case "wang":System.out.println("wang");break;default:System.out.println("error");}}
}
反编译 查看class文件 ctrl+alt+shift+s打开项目结构
while循环结构
while(布尔表达式){
// 循环内容
}
我们大多数情况会让循环停止下来,需要一个让表达式失效的方式来结束循环。
**死循环:**while(ture){} / /等待客户端连接、定时检查
do…while循环(先执行后判断)和while(先判断后执行)相似,不同在于do…while循环至少执行一次。
package com.wang.operator;public class Demo06 {public static void main(String[] args) {int a = 0;while (a < 0){System.out.println(a);a--;}System.out.println("===============");do{System.out.println(a);a--;}while(a<0 && a>-10);}
}
for循环结构
最有效、最灵活的循环结构(100.for+enter自动生成语句)
for(初始化;布尔表达式;更新){
//代码语句
}
死循环 for( ; ; ){}
// 计算0到100之间奇数与偶数和
package com.wang.operator;public class Demo07 {public static void main(String[] args) {int oddSum = 0;int evenSum = 0;for (int i = 0; i < 100; i++) {if (i%2!=0){oddSum+=i;}else{evenSum+=i;}}System.out.println("奇数和:"+oddSum+"\n"+"偶数和:"+evenSum);}}
print 输出完不会换行
println输出完会换行
//输出5的倍数,每行输出3个
package com.wang.operator;public class Demo08 {public static void main(String[] args) {for(int i=1; i<=1000 ; i++){if(i%5==0){System.out.print(i+"\t");}if(i%15==0){System.out.println("\n");}}}
}
//打印九九乘法表
package com.wang.operator;public class Demo09 { public static void main(String[] args) { for(int i=1;i<=9;i++){ for(int j=1;j<=9;j++){ if(j<=i){ System.out.print(j+"*"+i+"="+(j*i)+"\t"); }else{ System.out.println(); break; } } } }
}
//利用j<=i的动态变化来优化
package com.wang.operator;public class Demo09 { public static void main(String[] args) { for(int i=1;i<=9;i++){ for(int j=1;j<=i;j++){ System.out.print(j+"*"+i+"="+(j*i)+"\t"); } System.out.println(); } }
}
增强for循环:主要用于数组或集合的增强for循环。
for(声明语句:表达式){
//代码句子
}
package com.wang.operator;public class Demo10 { public static void main(String[] args) { int[] numbers = {10,20,30,40,50,60}; //定义了一个数组 for (int i=0;i<=5;i++){ System.out.println(numbers[i]); } System.out.println("========================"); //遍历数组的元素 for (int x:numbers){ System.out.println(x); } }
}
break、continue、goto
break用于强行退出循环,不执行循环中剩余的语句(可在switch中使用)
直接结束当前for循环
public class HelloWorld {public static void main(String[] args) {//打印单数 for (int j = 0; j < 10; j++) {if(0==j%2) break; //如果是双数,直接结束循环System.out.println(j);} } }
continue用于终止某次循环过程,接着进行下一次是否执行循环的判定
如果是双数,后面的代码不执行,直接进行下一次循环
public class HelloWorld {public static void main(String[] args) {//打印单数 for (int j = 0; j < 10; j++) {if(0==j%2) continue; //如果是双数,后面的代码不执行,直接进行下一次循环System.out.println(j);} } }
**标签:**指后面跟一个冒号的标识符,如:label:
package com.wang.operator;public class Demo11 { public static void main(String[] args) { //打印101-150之间所有的质数 //带标签的continue int count = 0; outer:for (int i=101;i<=150;i++){ for (int j=2;j<=i/2;j++){ if (i%j==0){ continue outer; } } System.out.print(i+" "); } }
}
使用标签结束外部循环
在外部循环的前一行,加上标签
在break的时候使用该标签
即能达到结束外部循环的效果public class HelloWorld {public static void main(String[] args) {//打印单数 outloop: //outloop这个标示是可以自定义的比如outloop1,ol2,out5for (int i = 0; i < 10; i++) {for (int j = 0; j < 10; j++) {System.out.println(i+":"+j);if(0==j%2) break outloop; //如果是双数,结束外部循环}}} }
借助boolean变量结束外部循环
需要在内部循环中修改这个变量值
每次内部循环结束后,都要在外部循环中判断,这个变量的值public class HelloWorld {public static void main(String[] args) {boolean breakout = false; //是否终止外部循环的标记for (int i = 0; i < 10; i++) {for (int j = 0; j < 10; j++) {System.out.println(i + ":" + j);if (0 == j % 2) {breakout = true; //终止外部循环的标记设置为truebreak;}}if (breakout) //判断是否终止外部循环break;}} }
Java方法
一个方法只完成1个功能,这样有利于后期扩展。
ctrl+/注释
//形式参数,用来定义作用的
//实际参数,实际调用传递给他的参数
修饰符 返回值类型 方法名(参数类型 参数名){
方法体
return 返回值;
}
值传递和引用传递: Java是值传递
//输入两个整数比较大小
package com.wang.method;import java.util.Scanner;public class Demo01 {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);int num1 =0;int num2 =0;System.out.println("输入第一个整数:");if (scanner.hasNextInt()) {num1 = scanner.nextInt();}else{System.out.println("输入的不是整数数据");}System.out.println("输入第二个整数:");if (scanner.hasNextInt()) {num2 = scanner.nextInt();}else{System.out.println("输入的不是整数数据");}int result = max(num1,num2);if (result!=0) {System.out.println("更大的数为:" + result);}else{System.out.println("两数相等!");}}public static int max(int num1,int num2){int result = 0; //return要提取到最外面if (num1==num2){return 0; //终止方法}if (num1<num2){result=num2;}else{result=num1;}return result;}
}
重载
在一个类中,有相同的函数名称,但形参不同的函数。
方法名称相同时,编译器根据调用方法的参数个数,参数类型等去逐个匹配,以选择对应的方法,如果匹配失败,则编译器报错。
规则:参数列表必须不同(个数不同、或类型不同、参数排列顺序不同等)。
命令行传参
有时候希望运行一个程序时再传递给它消息。这要靠传递命令行参数给main()函数实现。
可变参数
在方法声明中,在指定参数类型后加一个省略号(…)。
一个方法中只能指定一个可变参数,它必须是方法的最后一个参数。任何普通的参数必须在它之前声明。(不定项参数)
package com.wang.method;public class Demo02 {public static void main(String[] args) {//调用可变参数Demo02 demo02 = new Demo02();demo02.pringMax(12,13,2,3,4);}public void pringMax(double... numbers){ //在方法里,使用操作数组的方式处理参数 numbers 即可if (numbers.length == 0){System.out.println("No argument passed");return;}double result = 0;//排序for(int i=1;i< numbers.length;i++){if (numbers[i]>result){result=numbers[i];}}System.out.println("The max value is "+result);}
}
递归
A方法调用A方法。
递归结构:
- 递归头:什么时候不调用自身方法。如果没有头,将陷入死循环。
- 递归体:什么时候需要调用自身方法。
package com.wang.method;public class Demo03 {//阶乘public static void main(String[] args) {System.out.println(f(8));}public static int f(int n){if (n==1){return 1;}else{return n*f(n-1);}}
}
Java使用栈机制,递归会调用大量的函数,可能导致内存崩溃
计算器(结果尚未未实现整数输出功能)
写一个计算器,要求实现加减乘除功能,并且能够循环接受新的数据,通过用户交互实现
- 写四个方法:加减乘除
- 利用循环+switch进行用户交互
- 传递需要操作的两个数
- 输出结果
package com.wang.method;import java.util.Scanner;public class Demo04 {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);double num1 =0;double num2 =0;System.out.println("开始计算!");if(scanner.hasNextInt()){num1= scanner.nextInt();}else{if(scanner.hasNextDouble()) {num1 = scanner.nextDouble();}else {System.out.println("输入一个数!");}}String c="";if(scanner.hasNext()){c= scanner.next();}if(scanner.hasNextInt()){num2= scanner.nextInt();}else{if(scanner.hasNextDouble()) {num2 = scanner.nextDouble();}else {System.out.println("输入一个数!");}}switch (c){case "+":System.out.println(num1+"+"+num2+"="+add(num1,num2));break;case "-":System.out.println(num1+"-"+num2+"="+sub(num1,num2));break;case "*":System.out.println(num1+"*"+num2+"="+mul(num1,num2));break;case "/":System.out.println(num1+"/"+num2+"="+div(num1,num2));break;default:System.out.println("不正确的运算符");}}//加public static int add(int num1,int num2){int result = num1 +num2;return result;}public static double add(double num1,double num2){double result = num1 +num2;return result;}//减public static int sub(int num1,int num2){int result = num1 -num2;return result;}public static double sub(double num1,double num2){double result = num1 -num2;return result;}//乘public static int mul(int num1,int num2){int result = num1 *num2;return result;}public static double mul(double num1,double num2){double result = num1 *num2;return result;}//除public static int div(int num1,int num2){if (num2==0) {System.out.println("error");return 0;}else{int result = num1 / num2;return result;}}public static double div(double num1,double num2){if (num2==0) {System.out.println("error");return 0;}else{double result = num1 / num2;return result;}}}
数组
数组是相同类型数据的有序集合。每个数组元素可以通过一个下标来访问他们。
声明数组变量,才能在程序中使用数组。
dataType[] arrayRefVar; //声明一个数组。首选的方法、int[] nums;
dataType arrayRefVar[]; //效果相同,但不是首选方法(C和C++首选)nums = new int[10]; //创建一个数组
Java语言使用new操作符来创建数组:
dataType[] arrayRefVar = new dataType[arraySizw]; //int[] nums = new int[10];
获取数组长度:
array.length
创建数组的时候,要指明数组的长度。
new int[5]
引用概念:
如果变量代表一个数组,比如a,我们把a叫做引用
与基本类型不同
int c = 5; 这叫给c赋值为5
声明一个引用 int[] a;
a = new int[5];
让a这个引用,指向数组public class HelloWorld {public static void main(String[] args) {//声明一个引用int[] a;//创建一个长度是5的数组,并且使用引用a指向该数组a = new int[5];int[] b = new int[5]; //声明的同时,指向一个数组} }
把一个数组的值,复制到另一个数组中
System.arraycopy(src, srcPos, dest, destPos, length)
src: 源数组
srcPos: 从源数组复制数据的起始位置
dest: 目标数组
destPos: 复制到目标数组的起始位置
length: 复制的长度public class HelloWorld {public static void main(String[] args) {int a [] = new int[]{18,62,68,82,65,9};int b[] = new int[3];//分配了长度是3的空间,但是没有赋值//通过数组赋值把,a数组的前3位赋值到b数组//方法一: for循环for (int i = 0; i < b.length; i++) {b[i] = a[i];}//方法二: System.arraycopy(src, srcPos, dest, destPos, length)//src: 源数组//srcPos: 从源数组复制数据的起始位置//dest: 目标数组//destPos: 复制到目标数组的启始位置//length: 复制的长度 System.arraycopy(a, 0, b, 0, 3);//把内容打印出来for (int i = 0; i < b.length; i++) {System.out.print(b[i] + " ");}} }
内存分析
堆://创建数组、赋值
存放new的对象和数组
可以被所有的线程共享,不会存放别的对象引用
栈://声明数组
- 存放基本变量类型(会包含这个基本类型的具体数值)
- 引用对象的变量(会存放这个引用在堆里面的具体地址)
方法区:
- 可以被所有的线程共享
- 包含了所有的class和static变量
三种初始化
如果指定了数组的内容,就不能同时设置数组的长度。
- 静态初始化:创建+赋值
int[] a = {1,2,3,4,5,6,7,8,9};
Man[] mans = {new Man(1,1),new Man(2,2)};
- 动态初始化:
int[] a = new int[2];
a[0]=1;
a[1]=2;
- 数组的默认初始化:数组是引用类型,他的元素相当于类的实例变量,因此数组一经分配空间,其中的每个元素也被按照实例变量同样的方式被隐式初始化。
四个基本特点
- 长度确定
- 元素类型相同,不允许混合
- 元素可以是任何数据类型,包括基本类型和引用类型
- 数组变量属引用类型,数组可看成对象,数组中的每个元素相当于该对象的成员变量。数组本身就是对象,Java中对象是在堆中的,因此数组无论保存原始类型还是其他对象类型,数组对象本身是在堆中的。
数组边界
合法区间:[0,length-1]
ArrayIndexOutOfBoundsException:数组下标越界异常!
package com.wang.array;public class Demo01 {public static void main(String[] args) {int[] nums = new int[10];nums[0] = 1;for (int i = 0; i < nums.length ; i++) {System.out.print(nums[i]+" ");}System.out.println(nums[10]);for (int num :nums){ // nums.forSystem.out.println(nums); //适合遍历输出,但是取不到下标}}
}
使用方法打印与反转整数与小数类型数组
package com.wang.array;public class Demo02 {public static void main(String[] args) {int[] arrays = {1,2,3,4,5};double[] arrays2 = {1.1,2.2,3,4,5};int[] reverse = reverse(arrays);double[] reverse2 = reverse(arrays2);printArray(reverse);printArray(reverse2);}//打印数组public static void printArray(int[] arrays){for (int array : arrays) {System.out.print(array+" ");}System.out.println("");}public static void printArray(double[] arrays){for (double array : arrays) {System.out.print(array+" ");}System.out.println("");}//反转数组public static int[] reverse(int[] arrays){int[] result = new int[arrays.length];for(int i=0, j=result.length-1; i < arrays.length;i++,j--){result[j]=arrays[i];}return result;}public static double[] reverse(double[] arrays){double[] result = new double[arrays.length];for(int i=0, j=result.length-1; i < arrays.length;i++,j--){result[j]=arrays[i];}return result;}
}
多维数组
多维数组可以看出是数组的数组,比如二维数组就是一个特殊的一维数组,其每一个元素都是一个一维数组。
二维数组:
int a[][] = new int[2][5]; //两行五列
Java中使用多维数组不多,主要是面向对象!
package com.wang.array;public class Demo03 {public static void main(String[] args) {int[][] a ={{1,2},{1,2,3},{1},{1,2}};System.out.println(a.length); //几行System.out.println(a[1].length); //二行几列for (int i=0; i<a.length;i++){ //遍历输出二维数组for(int j=0; j<a[i].length;j++){ //foriSystem.out.print(a[i][j]+" ");}System.out.println("");}}
}
Arrays类
Arrays是针对数组的工具类,可以进行 排序,查找,复制填充等功能。 大大提高了开发人员的工作效率。
数组的工具类:java.util.Arrays
copyOfRange(int[] original, int from, int to) // 第二个参数表示开始位置(取得到) // 第三个参数表示结束位置(取不到)
|
数组复制 |
---|---|
toString() | 转换为字符串 |
sort | 排序 |
binarySearch | 搜索 |
equals | 判断是否相同 |
fill | 填充 |
import java.util.Arrays;public class HelloWorld {public static void main(String[] args) {int a[] = new int[] { 18, 62, 68, 82, 65, 9 };// copyOfRange(int[] original, int from, int to)// 第一个参数表示源数组// 第二个参数表示开始位置(取得到)// 第三个参数表示结束位置(取不到)int[] b = Arrays.copyOfRange(a, 0, 3);for (int i = 0; i < b.length; i++) {System.out.print(b[i] + " ");}} }
package com.wang.array;import java.util.Arrays;public class Demo04 {public static void main(String[] args) {int[] a = {1,2,4,6,4,234,5423};System.out.println(a); //[I@1b6d3586,hashcode//打印数组元素Array.toStringSystem.out.println(Arrays.toString(a));int a[] = new int[] { 18, 62, 68, 82, 65, 9 };//复制数组 // copyOfRange(int[] original, int from, int to)// 第一个参数表示源数组// 第二个参数表示开始位置(取得到)// 第三个参数表示结束位置(取不到)int[] b = Arrays.copyOfRange(a, 0, 3);Arrays.sort(a); //排序System.out.println(Arrays.toString(a));Arrays.fill(a,2,4,0); //数组填充,二到四个元素被零填充[2,4)左闭右开System.out.println(Arrays.toString(a));Arrays.fill(a,0); //数组填充System.out.println(Arrays.toString(a));}
}
给数组赋值:通过Arrays.fill方法
对数组排序:通过Arrays.sort方法,按升序
比较数组:通过Arrays.equals方法比较数组中元素值是否相等
查找数组元素:通过Arrays.binarySearsh方法能对排序好的数组进行二分查找法操作
查询元素出现的位置
需要注意的是,使用binarySearch进行查找之前,必须使用sort进行排序
如果数组中有多个相同的元素,查找结果是不确定的import java.util.Arrays;public class HelloWorld {public static void main(String[] args) {int a[] = new int[] { 18, 62, 68, 82, 65, 9 };Arrays.sort(a);System.out.println(Arrays.toString(a));//使用binarySearch之前,必须先使用sort进行排序System.out.println("数字 62出现的位置:"+Arrays.binarySearch(a, 62)); } }
冒泡排序
总共有八大排序。
冒泡排序和选择排序
1.冒泡排序是比较相邻位置的两个数,而选择排序是按顺序比较,找最大值或者最小值;
2.冒泡排序每一轮比较后,位置不对都需要换位置,选择排序每一轮比较都只需要换一次位置;
3.冒泡排序是通过数去找位置,选择排序是给定位置去找数;
冒泡排序可以借助boolean变量结束外部循环,
嵌套循环,时间复杂度O(n2)
package com.wang.array;import java.util.Arrays;public class Demo05 {public static void main(String[] args) {/*冒泡排序1.比较数组中,两个相邻的元素,如果第一个数比第二个数大,我们就交换他们的位置2.每一次比较,都会产生出一个最大,或者最小的数字3.下一轮则可以少一次排序4.依次循环,直到结束*/int[] a={1,2,3,4};int[] sort = sort(a);System.out.println(Arrays.toString(sort));}public static int[] sort(int[] array){ //优化-当有一轮没有交换位置的时候,说明已经排序好了,则结束int temp = 0;//外层循环,判断要走多少次for (int i = 0; i < array.length-1; i++) {boolean flag = false; //通过flag标识位减少没有意义的比较//内层循环,比价判断两个数,如果第一个数,比第二个数大,则交换位置for (int j = 0;j < array.length-1-i;j++){if (array[j+1]<array[j]){ //flag的作用点在这里!temp = array[j];array[j]= array[j+1];array[j+1]=temp;flag = true;}if (flag==false){break;}}return array;}
}
稀疏数组
稀疏数组的存放和读取
package com.wang.array;public class Demo06 {public static void main(String[] args) {int[][] a=new int[11][11];a[1][2]=1;a[2][3]=2;System.out.println("输出原始数组:");arraysPrint(a);System.out.println("===============");//转换为稀疏数组保存//获取有效值个数//1.计数不为零的个数int sum =0;for (int i = 0; i < a.length; i++) {for (int j = 0; j < a[i].length; j++) {if(a[i][j]!=0){sum++;}}}System.out.println("有效值的个数:"+sum);//2.创建一个稀疏数组int[][] a2=new int[sum+1][3];a2[0][0]=a.length;a2[0][1]=a[0].length;a2[0][2]=sum;//3.遍历二维数组,将非零值存放进去int count=0;//用来表示存放行数for (int i = 0; i < a.length; i++) {for (int j = 0; j < a[i].length; j++) {if(a[i][j]!=0){count++;a2[count][0]=i;a2[count][1]=j;a2[count][2]=a[i][j];}}}//4.输出稀疏数组System.out.println("稀疏数组:");arraysPrint(a2);System.out.println("===============");System.out.println("还原:");//1.读取稀疏数组值int[][] a3= new int[a2[0][0]][a2[0][1]];//2.给其中元素还原值for (int i = 1; i < a2.length; i++) {a3[a2[i][0]][a2[i][1]]=a2[i][2];}//输出还原数组arraysPrint(a3);}public static void arraysPrint(int[][] arrays){for (int[] ints : arrays) { // a.for遍历输出for (int anInt : ints) {System.out.print(anInt+"\t");}System.out.println();}}
}
arrays.for遍历输出
面向对象编程OOP
引用的概念,如果一个变量的类型是 类类型,而非基本类型,那么该变量又叫做引用。
//创建一个对象 new Hero; //使用一个引用来指向这个对象 Hero h = new Hero(); Hero h2 = h; //h2指向h1所指向的对象
一个引用,同一时间,只能指向一个对象。
属性(这里的属性应该包含了构造器的定义)加方法变成一个类。
**面向对象思想:**抽象。物以类聚,分类的思维方式。对于描述复杂的事物,为了从宏观上把握、从整体上合理分析,我们需要使用面向对象的思路来分析整个系统。但是,具体到微观操作,仍然需要面向过程的思路去处理。
面向对象编程的本质就是:以类的方式组织代码,以对象的组织(封装)数据。
三大特性:
- 封装
- 继承
- 多态
从认识论的角度:先有对象后有类。对象,是具体的事物。类,是抽象的,是对对象的抽象。
从代码运行的角度:先有类后有对象。类是对象的模板。
类与对象的创建
使用new关键字创建对象。
使用new关键字创建的时候,除了分配内存空间之外,还会给创建好的对象进行默认的初始化以及对类中构造器的调用。
package com.wang.oop;//学生类
public class Student {//属性:字段String name;int age;//这里有一个默认的构造器// public Student() {// }//方法public void study(){System.out.println(this.name+"在学习");//this指这一个类}
}
package com.wang.oop;//一个项目应该只存在一个main方法
public class Application {public static void main(String[] args) {//类:抽象的,实例化//类实例化后会返回一个自己的对象!//student对象就是一个Student类的具体实例!Student xiaoming = new Student();Student xiaohong= new Student();xiaoming.name="小明";xiaoming.age= 3;System.out.println(xiaoming.name);System.out.println(xiaoming.age);}
}
构造器
通过一个类创建一个对象,这个过程叫做实例化
实例化是通过调用构造方法(又叫做构造器)实现的
类中的构造器也成为构造方法,是在进行创建对象的时候必须要调用的。并且构造器有以下两个特点:
- 必须和类的名字相同
- 必须没有返回类型,也不能写void
类的创建
package com.wang.oop;public class Person {//一个类即使什么都不写,它也会存在一个方法//显示的定义构造器String name;int age;//实例化初始值//使用new关键字,本质是在调用构造器//无参构造 默认构造器public Person(){ //Person person = new Person();想使用这样new Person()时必须有这样一个构造器}//有参构造:一旦定义了有参构造,无参必须显示定义public Person(String name){ Person person = new Person("wang");自动判断,调用有参构造,相当于方法的重载this.name =name;//前面的name代表类,后面的代表传进来的参数}public Person(String name, int age){this.name = name;this.age = age;}
}
/*构造器:1.和类名相同2.没有返回值作用:1.new 本质在调用构造方法2.初始化对象的值注意点:1.定义有参构造之后,如果想使用无参构造,显示的定义一个无参的构造Alt + Insertthis. = //this后面的指这一个类*/
类的调用
package com.wang.oop;public class Application02 {public static void main(String[] args) {Person person = new Person(); //因为使用了这个,所以需要显示定义一个无参构造Person person1 = new Person("wang");Person person2=new Person("wang",23);System.out.println(person.name);System.out.println(person1.name);System.out.println(person2.name+person2.age);}
}
this
this这个关键字,相当于普通话里的“我”
this即代表当前对象,也就是调用方法的对象//参数名和属性名一样 //在方法体中,只能访问到参数name public void setName1(String name){name = name; }//为了避免setName1中的问题,参数名不得不使用其他变量名 public void setName2(String heroName){name = heroName; }//通过this访问属性 public void setName3(String name){//name代表的是参数name//this.name代表的是属性namethis.name = name; }
如果要在一个构造方法中,调用另一个构造方法,可以使用this()
package com.wang.oop.demo01;public class Hero {String name; //姓名 float hp; //血量 float armor; //护甲 int moveSpeed; //移动速度//带一个参数的构造方法 public Hero(String name){System.out.println("一个参数的构造方法");this.name = name; }//带两个参数的构造方法 public Hero(String name,float hp){this(name); //调用了一个参数的构造方法System.out.println("两个参数的构造方法");this.hp = hp; }public static void main(String[] args) {Hero teemo = new Hero("提莫",383);System.out.println(teemo.name);}}
创建对象内存分析
如果一个变量是基本类型
比如 int hp = 50;
我们就直接管hp叫变量
=表示赋值的意思。
如果一个变量是类类型
比如 Hero h = new Hero();
我们就管h叫做引用。
=不再是赋值的意思
=表示指向的意思
比如 Hero h = new Hero();
这句话的意思是
引用h,指向一个Hero对象
栈://声明数组
- 存放基本变量类型(会包含这个基本类型的具体数值)
- 引用对象的变量(会存放这个引用在堆里面的具体地址)
堆://创建数组、赋值
存放new的对象和数组
可以被所有的线程共享,不会存放别的对象引用
方法区:
- 可以被所有的线程共享
- 包含了所有的class和static变量
理解Java的值传递
关于teemo为什么最终仍然指向“旧”提莫: 第31行调用revive方法,实参为teemo,teemo本身是一个引用,占用一个内存单元,其中存放“旧”提莫的地址值,而java为值传递,所以该调用只是将teemo的值,即“旧”提莫的地址值,将该值拷贝下来,复制给形参h,此时teemo显然仍指向“旧”提莫,h现在存放“旧”提莫的地址值,但是通过调用构造方法,新建了一个Hero对象,并将"新"提莫的地址值赋值给h。 结果:teemo指向“旧”提莫,h指向“新”提莫。 理解关键:java基于值传递而非引用传递。
public class Hero {String name; //姓名 float hp; //血量 float armor; //护甲 int moveSpeed; //移动速度 public Hero(){ }public Hero(String name,float hp){this.name = name;this.hp = hp; }//复活 public void revive(Hero h){h = new Hero("提莫",383); }public static void main(String[] args) {Hero teemo = new Hero("提莫",383);//受到400伤害,挂了teemo.hp = teemo.hp - 400; teemo.revive(teemo); //teemo.hp = -17;因为形参h传递到了实参teemo的指向,即地址,然后又指向了新构造的对象 //问题: System.out.println(teemo.hp); 输出多少? 怎么理解? } }
小结
类与对象
类是一个模板:抽象;对象是一个具体的实例
方法
定义、调用!
对象的引用
引用类型:基本类型(8)
对象是通过引用来操作的:栈–>堆
属性:字段Field 成员变量
默认初始化:
数字:0 0.0;char:u0000;boolean:false;引用:null
修饰符 属性类型 属性名 = 属性值!
对象的创建和使用
必须使用new 关键字创造对象,构造器
对象的属性 wang.name
对象的方法 wang.sleep()
类
静态的属性 属性
动态的行为 方法
封装
“高内聚,低耦合:”
高内聚,类的内部数据操作细节自己完成,不允许外部干涉;低耦合,仅暴露少量的方法给外部使用。
**封装(数据的隐藏):**通常应禁止直接访问一个对象中数据的实际表示,而应该通过操作接口来访问,这称为信息隐藏。
属性私有,get/set
/*
- 提高程序的安全性,保护数据
- 隐藏代码的实现细节
- 统一接口
- 提高系统的可维护性
*/
package com.wang.oop.demo05;public class Student {//private:私有//属性私有private String name;private int id;private char sex;private int age;//提供一些可以操作这个属性的方法//提供一些public的get、set方法//get获得这个数据public String getName(){return this.name;}//set给这个数据设置值public void setName(String name){this.name=name;}//alt +insert 自动生成get、setpublic int getId() {return id;}public void setId(int id) {this.id = id;}public int getAge() {return age;}public void setAge(int age) { //封装可以规避这种不合法的数据if(age>130 || age<0){ //不合法this.age = 0;}else {this.age = age;}}
}
/*
1. 提高程序的安全性,保护数据
2. 隐藏代码的实现细节
3. 统一接口
4. 提高系统的可维护性*/
继承
继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模。
extends:扩展。子类是父类的扩展。
Java中类只有单继承,没有多继承!
package com.wang.oop.demo06;
//Java中,所有的类都默认直接或者间接继承Object类
//基类、父类
public class Person /*extend Object*/ {private int money = 10_0000_0000;public void say(){System.out.println("say hi");}public int getMoney() {return money;}public void setMoney(int money) {this.money = money;}//ctrl + H 打开继承项
}package com.wang.oop.demo06;
//派生类、子类
public class Student extends Person{}package com.wang.oop.demo06;
public class Application {public static void main(String[] args) {Student student = new Student();student.say(); //可以调用Person类中的方法}
}
Super
public class Student extends Person{public Student() {super(); //隐藏代码,调用了父类的无参构造,且调用父类的构造器,必须要在子类构造器的第一行//当父类没有无参构造,子类无参构造需要显示的定义父类有参}private String name = "da";public void test(String name){System.out.println(name); //zhuangSystem.out.println(this.name); //daSystem.out.println(super.name); //wang}
}
/*
父类Hero提供了一个有参的构造方法:
public Hero(String name){this.name = name;
}
但是没有提供无参的构造方法
子类应该怎么处理?
*/
package charactor;
public class Hero {public String name;protected float hp;public Hero(String name){this.name = name;}
// 故意不提供无参的构造方法
// public Hero(){// } public static void main(String[] args) { }
}//子类添加有参构造方法
public ADHero(String name) {super(name);System.out.println("adHero的构造方法");}
重写
重写都是方法的重写,和属性无关。
重写:需要又继承关系,子类重写父类的方法!
- 方法名必须相同
- 参数列表必须相同
- 修饰符:范围可以扩大: public > protected > default > private
- 抛出的异常:范围 可以被缩小,但不能扩大; ClassNotFoundException --> Exception(大)
为什么要重写:父类的功能,子类不一定需要,或者不一定满足。
Alt + Insert ; override;
package com.wang.oop.demo07;
//重写都是方法的重写,和属性无关。
public class A extends B{//Override 重写@Override //注解:有功能的注释!public void test() {super.test();}
}
隐藏
与重写类似,方法的重写是子类覆盖父类的对象方法
隐藏,就是子类覆盖父类的类方法
package charactor;public class Hero {public String name;protected float hp;//类方法,静态方法//通过类就可以直接调用public static void battleWin(){System.out.println("hero battle win");}
}package charactor;
public class ADHero extends Hero implements AD{ @Overridepublic void physicAttack() {System.out.println("进行物理攻击");}//隐藏父类的battleWin方法public static void battleWin(){System.out.println("ad hero battle win");} public static void main(String[] args) {Hero.battleWin();ADHero.battleWin();}
}
Hero h =new ADHero();
h.battleWin(); //battleWin是一个类方法
h是父类类型的引用
但是指向一个子类对象
h.battleWin(); 会调用父类的方法?还是子类的方法?**父类引用指向子类对象: **
**静态方法, 引用是啥类型就输出啥类的方法; **
非静态方法, 对象是啥类型就输出啥类型的方法;
//当继承的方法为静态方法时,父类指向子类,JVM使用的是静态绑定//当继承的方法不是静态方法时,父类指向子类,JVM使用的是动态绑定//想要继承的方法为静态的方法时,想要调用子类的重写方法,直接子类名称.静态方法//当出现private,final,static,以及构造器的时候,JVM会调用静态绑定
多态
操作符的多态
+ 可以作为算数运算,也可以作为字符串连接同一个操作符在不同情境下,具备不同的作用
如果+号两侧都是整型,那么**+代表 数字相加**
如果+号两侧,任意一个是字符串,那么**+代表字符串连接**
类的多态: 都是同一个类型,调用同一个方法,却能呈现不同的状态
父类引用指向子类对象要实现类的多态,需要如下条件
- 父类(接口)引用指向子类对象
- 调用的方法有 重写
/* 如果物品的种类特别多,那么就需要设计很多的方法 比如useArmor,useWeapon等等这个时候采用多态来解决这个问题 设计一个方法叫做useItem,其参数类型是Item 如果是使用血瓶,调用该方法 如果是使用魔瓶,还是调用该方法 无论英雄要使用什么样的物品,只需要一个方法即可 */ package charactor;import property.Item; import property.LifePotion; import property.MagicPotion;public class Hero {public String name;protected float hp;public void useItem(Item i){i.effect();}public static void main(String[] args) {Hero garen = new Hero();garen.name = "盖伦";LifePotion lp =new LifePotion();MagicPotion mp =new MagicPotion();garen.useItem(lp);garen.useItem(mp); }}
动态编译:类型:可扩展性更强;
即同一方法可以根据发送对象的不同而采用多种不同的行为方式。
多态注意事项:
- 多态是方法的多态,属性没有多态
- 父类和子类,有联系 (类型转换异常:ClassCastException!)
- 存在条件:继承关系,方法需要重写,父类引用指向子类对象! Father f1 = new Son();
有些方法不能重写:
- static 方法,属于类 不属于实例
- final 常量
- private 方法
package com.wang.oop;
import com.wang.oop.demo08.Person;
import com.wang.oop.demo08.Student;
public class Application { public static void main(String[] args) { //一个对象的实际类型是确定的 //可以指向的引用类型不确定:父类的引用指向子类 //Student 能调用的方法都是自己的或者继承父类的! Student s1 = new Student(); //Person 父类型,可以指向子类,但是不能调用子类独有的方法 Person s2 = new Student(); Object s3 = new Student(); //Object是所有子类的祖宗类 // 对象能执行哪些方法,主要看对象左边的类型,和右边关系不大! s1.eat(); ((Student)s2).eat(); //强制转换 子类重写了父类的方法,执行子类的方法 }
}
Object
Object类是所有类的父类
声明一个类的时候,默认是继承了Object
public class Hero extends Object
toString
Object类提供一个toString方法,所以所有的类都有toString方法
toString()的意思是返回当前对象的字符串表达
通过 System.out.println 打印对象就是打印该对象的toString()返回值
System.out.println(h.toString());//直接打印对象就是打印该对象的toString()返回值System.out.println(h);
finalize
当一个对象没有任何引用指向的时候,它就满足垃圾回收的条件
当它被垃圾回收的时候,它的finalize() 方法就会被调用。
finalize() 不是开发人员主动调用的方法,而是由虚拟机JVM调用的。
package charactor;public class Hero {public String name;protected float hp;public String toString(){return name;}public void finalize(){System.out.println("这个英雄正在被回收");}public static void main(String[] args) {//只有一引用Hero h;for (int i = 0; i < 100000; i++) {//不断生成新的对象//每创建一个对象,前一个对象,就没有引用指向了//那些对象,就满足垃圾回收的条件//当,垃圾堆积的比较多的时候,就会触发垃圾回收//一旦这个对象被回收,它的finalize()方法就会被调用h = new Hero();}}
}
equals
equals() 用于判断两个对象的内容是否相同
假设,当两个英雄的hp相同的时候,我们就认为这两个英雄相同
package charactor;public class Hero {public String name;protected float hp;public boolean equals(Object o){ //Hero类向上转型if(o instanceof Hero){Hero h = (Hero) o; //Object类向下转型return this.hp == h.hp; //this指的是当前对象,也就是调用equal方法的对象,所以this.hp指的是h1.hp}return false;}public static void main(String[] args) {Hero h1= new Hero();h1.hp = 300;Hero h2= new Hero();h2.hp = 400;Hero h3= new Hero();h3.hp = 300;System.out.println(h1.equals(h2));System.out.println(h1.equals(h3));}
}
hashCode
hashCode方法返回一个对象的哈希值,但是在了解哈希值的意义之前,讲解这个方法没有意义。
线程同步相关方法
Object还提供线程同步相关方法
wait()
notify()
notifyAll()
这部分内容的理解需要建立在对线程安全有足够的理解的基础之上,所以会放在线程交互 的章节讲解
getClass()
getClass()会返回一个对象的类对象,属于高级内容,不适合初学者过早接触,关于类对象的详细内容请参考反射机制
练习
重写Item的 toString(), finalize()和equals()方法
toString() 返回Item的name + price
finalize() 输出当前对象正在被回收
equals(Object o) 首先判断o是否是Item类型,然后比较两个Item的price是否相同
package com.wang.oop.demo11;public class Item {public String name;protected float price;public String toString(){return this.name +"价格"+this.price;}public void finalize(){System.out.println("当前对象正在被回收");}public boolean equals(Object o){if (o instanceof Item){Item h = (Item) o;return this.price==h.price;}return false;}public static void main(String[] args) {Item s1 = new Item();s1.name="宝剑";s1.price=800;Item s2 = new Item();s2.name="好盾牌";s2.price=800;System.out.println(s1.toString());System.out.println(s1.equals(s2));}
}
final
final修饰类,方法,基本类型变量,引用的时候分别有不同的意思。
修饰类
当Hero被修饰成final的时候,表示Hero不能够被继承
其子类会出现编译错误
修饰方法
Hero的useItem方法被修饰成final,那么该方法在ADHero中,不能够被重写
修饰基本类型变量
final修饰基本类型变量,表示该变量只有一次赋值机会
修饰引用
final修饰引用
h引用被修饰成final,表示该引用只有1次指向对象的机会
常量
对象转型
子类转父类(向上转型),说的通
父类转子类(向下转型),有的时候行,有的时候不行,所以必须进行强制转换。强制转换的意思就是 转换有风险,风险自担。
以下是对完整的代码的关键行分析
14行: 把ad当做Hero使用,一定可以
转换之后,h引用指向一个ad对象
15行: h引用有可能指向一个ad对象,也有可能指向一个support对象
所以把h引用转换成AD类型的时候,就有可能成功,有可能失败
因此要进行强制转换,换句话说转换后果自负
到底能不能转换成功,要看引用h到底指向的是哪种对象
在这个例子里,h指向的是一个ad对象,所以转换成ADHero类型,是可以的
16行:把一个support对象当做Hero使用,一定可以
转换之后,h引用指向一个support对象
17行:这个时候,h指向的是一个support对象,所以转换成ADHero类型,会失败。
失败的表现形式是抛出异常 ClassCastException 类型转换异常package charactor;import charactor1.Support;public class Hero {public String name; protected float hp;public static void main(String[] args) {Hero h =new Hero();ADHero ad = new ADHero();Support s =new Support();h = ad;ad = (ADHero) h;h = s;ad = (ADHero)h; }}
没有继承关系的两个类,互相转换,一定会失败
虽然ADHero和APHero都继承了Hero,但是彼此没有互相继承关系
“把魔法英雄当做物理英雄来用”,在语义上也是说不通的实现类转换成接口(向上转型)
接口转换成实现类(向下转型)
10行: ad引用指向ADHero, 而adi引用是接口类型:AD,实现类转换为接口,是向上转型,所以无需强制转换,并且一定能成功
12行: adi实际上是指向一个ADHero的,所以能够转换成功
14行: adi引用所指向的对象是一个ADHero,要转换为ADAPHero就会失败。假设能够转换成功,那么就可以使用magicAttack方法,而adi引用所指向的对象ADHero是没有magicAttack方法的。
package charactor;public class Hero {public String name; protected float hp;public static void main(String[] args) {ADHero ad = new ADHero();AD adi = ad;ADHero adHero = (ADHero) adi;ADAPHero adapHero = (ADAPHero) adi;adapHero.magicAttack(); }}
[Java 向上转型和向下转型](
1、把子类对象直接赋给父类引用叫upcasting向上转型,向上转型不用强制转型。
如Father father = new Son();
2、把指向子类对象的父类引用赋给子类引用叫向下转型(downcasting),要强制转型,要向下转型,必须先向上转型为了安全可以用instanceof判断。
如father就是一个指向子类对象的父类引用,把father赋给子类引用son 即Son son =(Son)father;
其中father前面的(Son)必须添加,进行强制转换。
3、upcasting 会丢失子类特有的方法,但是子类overriding 父类的方法,子类方法有效,向上转型只能引用父类对象的属性,要引用子类对象属性,则要写getter函数。
4、向上转型的作用,减少重复代码,父类为参数,调有时用子类作为参数,就是利用了向上转型。这样使代码变得简洁。体现了JAVA的抽象编程思想。
**父类引用指向子类对象: **
静态方法, 引用是啥类型就输出啥类的方法;
非静态方法, 对象是啥类型就输出啥类型的方法;
package com.wang.oop.Demo09;
/*
向上转型后父类引用不能调用子类自己的方法,就是父类没有但是子类的方法,如果调用不能编译通过,比如子类的speak方法。
非要调用子类的属性呢?如果不向下转型就需要给需要的属性写getter方法。class Male extends Human {String name = "Male";public String getName(){return this.name;}
}
非要调用子类扩展的方法,比如speak方法,就只能向下转型了。
*/public class Human {public void sleep() {System.out.println("Human sleep..");}public static void doSleep(Human h){h.sleep();}//此时传递的参数是父类对象,但是实际调用时传递子类对象,就是向上转型。public static void main(String[] args) {Human h = new Male();// 向上转型doSleep(new Male());//此处匿名子类对象,当然实际应用时应该是用上面的向上转型公式,然后将子类对象传递进来,这样以后好在向下转型,此处没有向下转型,所以直接用了匿名类对象。doSleep(new Female());}
}class Male extends Human {@Overridepublic void sleep() {System.out.println("Male sleep..");}
}class Female extends Human {@Overridepublic void sleep() {System.out.println("Female sleep..");}
}
instanceof 和类型转换
用来测试一个对象是否为一个类的实例,用法为:
boolean result = obj instanceof Class // obj必须是引用类型,不能是基本类型
当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果result 都返回 true,否则返回false。
注意:编译器会检查 obj 是否能转换成右边的class类型,如果不能转换则直接报错,如果不能确定类型,则通过编译,具体看运行时定。
package com.wang.oop;
import com.wang.oop.demo06.Teacher;
import com.wang.oop.demo08.Person;
import com.wang.oop.demo08.Student;
public class Application { public static void main(String[] args) { //Object > String //Object > Person > Teacher //Object > Person > Student Object object = new Student(); System.out.println(object instanceof Student); //true System.out.println(object instanceof Person); //true System.out.println(object instanceof Object); //true System.out.println(object instanceof Teacher); //false System.out.println(object instanceof String); //false System.out.println("========================="); Person person = new Student(); System.out.println(person instanceof Student); //true System.out.println(person instanceof Person); //true System.out.println(person instanceof Object); //true // System.out.println(person instanceof String); //编译报错 }
}
x instanceof y
编译能不能通过取决于x与y有没有父子关系;看x引用的左边
结果是true还是false看的是x的子类型是不是y的子类型;即看x引用的右边
访问修饰符
private修饰属性
使用private修饰属性
自身:是可以访问的
同包子类:不能继承
不同包子类:不能继承
同包类:不能访问
其他包类:不能访问
注: 红色字体,表示不可行
package/friendly/default
没有修饰符即代表package/friendly/default
float maxHP; 血量上限
protected 受保护的
受保护的修饰符
protected float hp; 血量
public 公共的
公共的修饰符
public String name; 姓名
任何地方,都可以访问
总结
那么什么情况该用什么修饰符呢?
从作用域来看,public能够使用所有的情况。 但是大家在工作的时候,又不会真正全部都使用public,那么到底什么情况该用什么修饰符呢?
- 属性通常使用private封装起来
- 方法一般使用public用于被调用
- 会被子类继承的方法,通常使用protected
- package用的不多,一般新手会用package,因为还不知道有修饰符这个东西
再就是作用范围最小原则
简单说,能用private就用private,不行就放大一级,用package,再不行就用protected,最后用public。 这样就能把数据尽量的封装起来,没有必要露出来的,就不用露出来了
static关键字
代码块:在构造方法前
static静态代码块:最先执行,且只执行一次
类属性
当一个属性被static修饰的时候,就叫做类属性,又叫做静态属性
当一个属性被声明成类属性,那么所有的对象,都共享一个值
与对象属性对比:
不同对象的 对象属性 的值都可能不一样。
比如盖伦的hp 和 提莫的hp 是不一样的。
但是所有对象的类属性的值,都是一样的。package com.wang.oop.demo01; /* 类属性: 又叫做静态属性 对象属性: 又叫实例属性,非静态属性 如果一个属性声明成类属性,那么所有的对象,都共享这么一个值 给英雄设置一个类属性叫做“版权" (copyright), 无论有多少个具体的英雄,所有的英雄的版权都属于 Riot Games公司。 */ public class Hero {public String name; //实例属性,对象属性,非静态属性protected float hp;static String copyright= "版权由Riot Games公司所有";;//类属性,静态属性public static void main(String[] args) {Hero garen = new Hero();garen.name = "盖伦";//Hero.copyright = "版权由Riot Games公司所有";System.out.println(garen.name);System.out.println(garen.copyright);Hero teemo = new Hero();teemo.name = "提莫";System.out.println(teemo.name);System.out.println(teemo.copyright);} }
访问类属性有两种方式
- 对象.类属性
teemo.copyright
- 类.类属性
Hero.copyright
这两种方式都可以访问类属性,访问即修改和获取,但是建议使用第二种 类.类属性 的方式进行,这样更符合语义上的理解
类方法
类方法: 又叫做静态方法
对象方法: 又叫实例方法,非静态方法
访问一个对象方法,必须建立在有一个对象的前提的基础上
访问类方法,不需要对象的存在,直接就访问
package charactor;
/*
类方法: 又叫做静态方法对象方法: 又叫实例方法,非静态方法访问一个对象方法,必须建立在有一个对象的前提的基础上
访问类方法,不需要对象的存在,直接就访问
*/
public class Hero {public String name;protected float hp;//实例方法,对象方法,非静态方法//必须有对象才能够调用public void die(){hp = 0;}//类方法,静态方法//通过类就可以直接调用public static void battleWin(){System.out.println("battle win");}public static void main(String[] args) {Hero garen = new Hero();garen.name = "盖伦";//必须有一个对象才能调用garen.die();Hero teemo = new Hero();teemo.name = "提莫";//无需对象,直接通过类调用Hero.battleWin();}
}
和访问类属性一样,调用类方法也有两种方式
- 对象.类方法
garen.battleWin();
- 类.类方法
Hero.battleWin();
这两种方式都可以调用类方法,但是建议使用第二种 类.类方法 的方式进行,这样更符合语义上的理解。
并且在很多时候,并没有实例,比如在前面练习的时候用到的 随机数的获取办法
Math.random()
random()就是一个类方法,直接通过类Math进行调用,并没有一个Math的实例存在。
如果在某一个方法里,调用了对象属性,比如
public String getName(){
return name;
}
name属性是对象属性,只有存在一个具体对象的时候,name才有意义。 如果方法里访问了对象属性,那么这个方法,就必须设计为对象方法
属性初始化
对象属性初始化
对象属性初始化有3种
- 声明该属性的时候初始化
- 构造方法中初始化
- 初始化块
package charactor;public class Hero {public String name = "some hero"; //声明该属性的时候初始化protected float hp;float maxHP;{maxHP = 200; //初始化块} public Hero(){hp = 100; //构造方法中初始化}}
类属性初始化
类属性初始化有2种
- 声明该属性的时候初始化
- 静态初始化块
package charactor;public class Hero {public String name;protected float hp;float maxHP;//物品栏的容量public static int itemCapacity=8; //声明的时候 初始化static{itemCapacity = 6;//静态初始化块 初始化}public Hero(){}public static void main(String[] args) {System.out.println(Hero.itemCapacity);}}
属性初始化顺序
类属性(静态变量声明、静态初始化块(前后顺序))>对象属性(变量属性声明、初始化块(前后顺序))>构造方法
单例模式
单例模式又叫做 Singleton模式,指的是一个类,在一个JVM里,只有一个实例存在。
单例模式三要素
- 构造方法私有化
- 静态属性指向实例
- public static的 getInstance方法,返回第二步的静态属性
饿汉式单例模式
/*
GiantDragon 应该只有一只,通过私有化其构造方法,使得外部无法通过new 得到新的实例。
GiantDragon 提供了一个public static的getInstance方法,外部调用者通过该方法获取12行定义的对象,而且每一次都是获取同一个对象。 从而达到单例的目的。
这种单例模式又叫做饿汉式单例模式,无论如何都会创建一个实例
*/
package com.wang.oop.demo02;public class GaintDragon {//私有化构造方法使得该类无法在外部通过new 进行实例化private GaintDragon(){}//准备一个类属性,指向一个实例化对象。 因为是类属性,所以只有一个private static GaintDragon instance = new GaintDragon();//public static 方法,提供给调用者获取12行定义的对象public static GaintDragon getInstance(){ //返回一个对象return instance;}}package com.wang.oop.demo02;public class TestGiantDragon{public static void main(String[] args){//new实例化会报错//GaintDragon g = new GaintDragon();//只能通过getInstance得到private对象的地址GaintDragon g1 = GaintDragon.getInstance();GaintDragon g2 = GaintDragon.getInstance();GaintDragon g3 = GaintDragon.getInstance();//都是一个对象System.out.println(g1==g2); //tureSystem.out.println(g1==g3); //ture}
}
懒汉式单例模式
懒汉式单例模式与饿汉式单例模式不同,只有在调用getInstance的时候,才会创建实例
package charactor;public class GiantDragon {//私有化构造方法使得该类无法在外部通过new 进行实例化private GiantDragon(){ }//准备一个类属性,用于指向一个实例化对象,但是暂时指向nullprivate static GiantDragon instance;//public static 方法,返回实例对象public static GiantDragon getInstance(){//第一次访问的时候,发现instance没有指向任何对象,这时实例化一个对象if(null==instance){instance = new GiantDragon();}//返回 instance指向的对象return instance;}
}
饿汉式与懒汉式
饿汉式是立即加载的方式,无论是否会用到这个对象,都会加载。
如果在构造方法里写了性能消耗较大,占时较久的代码,比如建立与数据库的连接,那么就会在启动的时候感觉稍微有些卡顿。
懒汉式,是延迟加载的方式,只有使用的时候才会加载。 并且有线程安全的考量(鉴于同学们学习的进度,暂时不对线程的章节做展开)。
使用懒汉式,在启动的时候,会感觉到比饿汉式略快,因为并没有做对象的实例化。 但是在第一次调用的时候,会进行实例化操作,感觉上就略慢。
看业务需求,如果业务上允许有比较充分的启动和初始化时间,就使用饿汉式,否则就使用懒汉式
枚举
枚举enum是一种特殊的类(还是类),使用枚举可以很方便的定义常量
比如设计一个枚举类型 季节,里面有4种常量
public enum Season {SPRING,SUMMER,AUTUMN,WINTER //注:因为是常量,所以一般都是全大写
}
一个常用的场合就是switch语句中,使用枚举来进行判断.
package com.wang.oop.demo03;public class Application {public static void main(String[] args) {Season season = Season.SPRING; //实例switch (season){case SPRING:System.out.println("春天");break;case SUMMER:System.out.println("夏天");break;}}
}
遍历枚举
借助增强型for循环,可以很方便的遍历一个枚举都有哪些常量
public class HelloWorld {public static void main(String[] args) {for (Season s : Season.values()) { //Season.value()System.out.println(s);}}
}
package com.wang.oop.demo03;public class Application {public enum Char{ //在类里面定义TANK,WIZARD,ASSASSIN,ASSIST,WARRIOR}public static void main(String[] args) {for (Char type:Char.values()){switch(type){case TANK :System.out.println("坦克");break;case ASSIST:System.out.println("");}}}
}
abstract抽象类
作用:子类继承的时候必须重写这个方法。避免开发的时候忘记重写了
在类中声明一个方法,这个方法没有实现体,是一个“空”方法
当一个类有抽象方法的时候,该类必须被声明为抽象类
package charactor;public abstract class Hero {String name;float hp;float armor;int moveSpeed;public static void main(String[] args) {}// 抽象方法attack // Hero的子类会被要求实现attack方法 public abstract void attack();}
抽象类可以没有抽象方法
一旦一个类被声明为抽象类,就不能够被直接实例化
abstract抽象方法,只有方法名字,没有方法的实现。
- 不能new这个抽象类,只能靠子类去实现它;约束!
- 抽象类中可以写普通的方法。
- 抽象方法必须在抽象类中。
接口
接口:只有规范!自己无法写方法~专业的约束!约束和实现分离:面向接口编程。
接口的本质是契约!
声明接口的关键字是interface
接口中定义的基本类型都是静态常量:public static final
接口中的所有定义其实都是抽象的public abstract
*接口都需要实现类:类的结尾Impl,implements
实现了接口的类,就需要重写接口的方法
多继承,利用接口实现
package com.wang.oop.Demo04;public interface AD {public void physicAttack();
}public class ADHero implements AD{@Overridepublic void physicAttack() {System.out.println("physic attack");}public static void main(String[] args) {ADHero ad = new ADHero();ad.physicAttack();}
}
抽象类和接口的区别
区别1:
子类只能继承一个抽象类,不能继承多个
子类可以实现多个接口
区别2:
抽象类可以定义
public,protected,package,private
静态和非静态属性
final和非final属性
但是接口中声明的属性,只能是
public
静态
final的
即便没有显式的声明
注: 抽象类和接口都可以有实体方法。 接口中的实体方法,叫做默认方法
//resistMagic即便没有显式的声明为 public static final//但依然默认为public static finalint resistMagic = 0;
内部类
非静态内部类:
可以直接在一个类里面定义
语法: new 外部类().new 内部类()
作为Hero的非静态内部类,是可以直接访问外部类的private实例属性name的
package charactor;public class Hero {private String name; // 姓名float hp; // 血量float armor; // 护甲int moveSpeed; // 移动速度// 非静态内部类,只有一个外部类对象存在的时候,才有意义// 战斗成绩只有在一个英雄对象存在的时候才有意义class BattleScore {int kill;int die;int assit;public void legendary() {if (kill >= 8)System.out.println(name + "超神!");elseSystem.out.println(name + "尚未超神!");}}public static void main(String[] args) {Hero garen = new Hero();garen.name = "盖伦";// 实例化内部类// BattleScore对象只有在一个英雄对象存在的时候才有意义// 所以其实例化必须建立在一个外部类对象的基础之上BattleScore score = garen.new BattleScore();score.kill = 9;score.legendary();}}
静态内部类
在一个类里面声明一个静态内部类
比如敌方水晶,当敌方水晶没有血的时候,己方所有英雄都取得胜利,而不只是某一个具体的英雄取得胜利。
与非静态内部类不同,静态内部类水晶类的实例化 不需要一个外部类的实例为基础,可以直接实例化
语法:new 外部类.静态内部类();
因为没有一个外部类的实例,所以在静态内部类里面不可以访问外部类的实例属性和方法
除了可以访问外部类的私有静态成员外,静态内部类和普通类没什么大的区别
package charactor;public class Hero {public String name;protected float hp;private static void battleWin(){System.out.println("battle win");}//敌方的水晶static class EnemyCrystal{int hp=5000;//如果水晶的血量为0,则宣布胜利public void checkIfVictory(){if(hp==0){Hero.battleWin();//静态内部类不能直接访问外部类的对象属性System.out.println(name + " win this game");}}}public static void main(String[] args) {//实例化静态内部类Hero.EnemyCrystal crystal = new Hero.EnemyCrystal();crystal.checkIfVictory();}}
匿名类
匿名类指的是在声明一个类的同时实例化它,使代码更加简洁精练
通常情况下,要使用一个接口或者抽象类,都必须创建一个子类
有的时候,为了快速使用,直接实例化一个抽象类,并“当场”实现其抽象方法。
既然实现了抽象方法,那么就是一个新的类,只是这个类,没有命名。
这样的类,叫做匿名类
package charactor;public abstract class Hero {String name; //姓名float hp; //血量float armor; //护甲int moveSpeed; //移动速度public abstract void attack();public static void main(String[] args) {ADHero adh=new ADHero();//通过打印adh,可以看到adh这个对象属于ADHero类adh.attack();System.out.println(adh);//因为在实现抽象方法时,实际已经生成了一个新(匿名)类,就如讲解中示例的Hero$1这个类。//换言之,也就不是将抽象类Hero实例化了,实际上实例化的是匿名类Hero$1,这才是匿名的真意。Hero h = new Hero(){//当场实现attack方法public void attack() {System.out.println("新的进攻手段");}};h.attack();//通过打印h,可以看到h这个对象属于Hero$1这么一个系统自动分配的类名System.out.println(h);}}
本地类
本地类可以理解为有名字的匿名类
内部类与匿名类不一样的是,内部类必须声明在成员的位置,即与属性和方法平等的位置。
本地类和匿名类一样,直接声明在代码块里面,可以是主方法,for循环里等等地方
package charactor;public abstract class Hero {String name; //姓名float hp; //血量float armor; //护甲int moveSpeed; //移动速度public abstract void attack();public static void main(String[] args) {//与匿名类的区别在于,本地类有了自定义的类名class SomeHero extends Hero{public void attack() {System.out.println( name+ " 新的进攻手段");}}SomeHero h =new SomeHero();h.name ="地卜师";h.attack();}}
匿名类中使用外部局部变量
在匿名类中使用外部的局部变量,外部的局部变量必须修饰为final
为什么要声明为final,其机制比较复杂,请参考第二个Hero代码中的解释
注:在jdk8中,已经不需要强制修饰成final了,如果没有写final,不会报错,因为编译器偷偷的帮你加上了看不见的final
默认方法
默认方法是JDK8新特性,指的是接口也可以提供具体方法了,而不像以前,只能提供抽象方法
Mortal 这个接口,增加了一个默认方法 revive,这个方法有实现体,并且被声明为了default
package charactor;public interface Mortal {public void die();default public void revive() {System.out.println("本英雄复活了");}
}
作用:
假设没有默认方法这种机制,那么如果要为Mortal增加一个新的方法revive,那么所有实现了Mortal接口的类,都需要做改动。
但是引入了默认方法后,原来的类,不需要做任何改动,并且还能得到这个默认方法
通过这种手段,就能够很好的扩展新的类,并且做到不影响原来的类
//问: ADAPHero同时实现了AD,AP接口,那么 ADAPHero 对象调用attack()的时候,是调用哪个接口的attack()?
//ADAPhero 实现AD AP 接口,必须重写该接口的方法,这样才不会报错
UML图
UML-Unified Module Language
统一建模语言,可以很方便的用于描述类的属性,方法,以及类和类之间的关系
类图
接口图
继承关系
带箭头的实线,表示 Spider,Cat, Fish都继承于Animal这个父类.
实现关系
表示 Fish实现了 Pet这个接口
继承与接口练习
/*
this后加 . 用来调用非构造方法和成员熟悉,this后不加 . 用来调用构造方法。this(“”)用来调用本类中的构造方法。
super(参数)用来调用父类构造方法。*/
题目:
\1. 创建Animal类,它是所有动物的抽象父类。
\2. 声明一个受保护的整数类型属性legs,它记录动物的腿的数目。
\3. 定义一个受保护的构造器,用来初始化legs属性。
\4. 声明抽象方法eat。
\5. 声明具体方法walk来打印动物是如何行走的(包括腿的数目)。
\1. Spider继承Animal类。
\2. 定义默认构造器,它调用父类构造器来指明所有蜘蛛都是8条腿。
\3. 实现eat方法
根据UML类创建pet(宠物)接口
\1. 提供getName() 返回该宠物的名字
\2. 提供setName(String name) 为该宠物命名
\3. 提供 play()方法
\1. 该类必须包含String属性来存宠物的名字。
\2. 定义一个构造器,它使用String参数指定猫的名字;该构造器必须调用超类构造器来指明所有的猫都是四条腿。
\3. 另定义一个无参的构造器。该构造器调用前一个构造器(用this关键字)并传递一个空字符串作为参数
\4. 实现Pet接口方法。
\5. 实现eat方法。
public abstract class Animal {String name;protected int legs;protected Animal(String name, int legs){this.name=name;this.legs=legs;}public abstract void eat();public void walk(Animal h){System.out.println(name+"用"+legs+"条腿走路");}public static void main(String[] args) {Animal h = new Animal("猪",4) {@Overridepublic void eat() {System.out.println(this.name+"拱白菜");}};h.eat();h.walk(h);}
}public class Spider extends Animal{public Spider(String name , int legs){super(name, 8);}@Overridepublic void eat() {System.out.println(this.name+"吃虫子");}public static void main(String[] args) {Spider spider = new Spider("蜘蛛",8);spider.eat();spider.walk(spider);}
}public interface Pet {String getName();void setName(String name);void play();
}public class Cat extends Animal implements Pet {public Cat(String name,int legs){super(name,4);}/*this后加 . 用来调用非构造方法和成员熟悉,this后不加 . 用来调用构造方法。this(“”)用来调用本类中的构造方法。super(参数)用来调用父类构造方法。*/public Cat(){ //无参构造调用该类中前一个构造器this(" ",0);}@Overridepublic void eat() {System.out.println(this.name+"吃鱼");}@Overridepublic String getName() {System.out.println(name);return name;}@Overridepublic void setName(String name) {this.name=name;}@Overridepublic void play() {System.out.println(name+"自己玩");}public static void main(String[] args) {Cat cat = new Cat("猫",4);cat.eat();cat.getName();cat.setName("大猫");cat.getName();cat.walk(cat);cat.play();}
}
数字与字符串
装箱拆箱
封装类
所有的基本类型,都有对应的类类型
比如int对应的类是Integer
这种类就叫做封装类
package digit;public class TestNumber {public static void main(String[] args) {int i = 5;//把一个基本类型的变量,转换为Integer对象Integer it = new Integer(i);//把一个Integer对象,转换为一个基本类型的intint i2 = it.intValue();}
}
Number类
数字封装类有
Byte,Short,Integer,Long,Float,Double
这些类都是抽象类Number的子类
基本类型转封装类与封装类转基本类型
package digit;public class TestNumber {public static void main(String[] args) {int i = 5;//基本类型转换成封装类型Integer it = new Integer(i);//封装类型转换成基本类型int i2 = it.intValue(); //intValue()}
}
自动装箱
不需要调用构造方法,通过=符号 自动把 基本类型 转换为 类类型 就叫装箱
package digit;public class TestNumber {public static void main(String[] args) {int i = 5;//基本类型转换成封装类型Integer it = new Integer(i);//自动转换就叫装箱Integer it2 = i;}
}
自动拆箱
不需要调用Integer的intValue方法,通过=就自动转换成int类型,就叫拆箱
package digit;public class TestNumber {public static void main(String[] args) {int i = 5;Integer it = new Integer(i);//封装类型转换成基本类型int i2 = it.intValue();//自动转换就叫拆箱int i3 = it;}
}
Integer.MAX_VALUE
int的最大值可以通过其对应的封装类Integer.MAX_VALUE获取
package digit;public class TestNumber {public static void main(String[] args) {//int的最大值System.out.println(Integer.MAX_VALUE);//int的最小值 System.out.println(Integer.MIN_VALUE);}
}
拆箱与装箱
package com.wang.oop.demo13;public class Demo01 {public static void main(String[] args) {int i = 5;Integer it = new Integer(i);Integer it2 = i; //装箱int i2 = it; //拆箱byte b = 127;Byte by = new Byte(b);Byte by2 = b; //装箱byte b2 =by; //拆箱Byte by3 = i; //Byte和int不能自动装箱Integer it3 = b; //Integer和byte不能自动装箱byte b3 = it; //byte和Integer不能自动拆箱int i3 = by; //int和Byte能自动拆箱}
}
字符串转换
数字转字符串
方法1: 使用String类的静态方法valueOf
方法2: 先把基本类型装箱为对象,然后调用对象的toString
package digit;public class TestNumber {public static void main(String[] args) {int i = 5;//方法1String str = String.valueOf(i);//方法2Integer it = i;String str2 = it.toString();}
}
字符串转数字parse
调用Integer的静态方法parseInt //parse解析的意思
package digit;public class TestNumber {public static void main(String[] args) {String str = "999";int i= Integer.parseInt(str);System.out.println(i);}
}
常用Math类
四舍五入, 随机数,开方,次方,π,自然常数
package digit;public class TestNumber {public static void main(String[] args) {float f1 = 5.4f;float f2 = 5.5f;//5.4四舍五入即5System.out.println(Math.round(f1));//5.5四舍五入即6System.out.println(Math.round(f2));//得到一个0-1之间的随机浮点数(取不到1)System.out.println(Math.random());//得到一个0-10之间的随机整数 (取不到10)System.out.println((int)( Math.random()*10));//开方System.out.println(Math.sqrt(9));//次方(2的4次方)System.out.println(Math.pow(2,4));//πSystem.out.println(Math.PI);//自然常数System.out.println(Math.E); //2.71828...}
}
利用Math方法算出自然常数,与判断质数
package numberAndString.mathMethod;public class Test {public static void main(String[] args) {System.out.println(Math.E);int n = Integer.MAX_VALUE;System.out.println(Math.pow((1 + 1d / n), n));//自然常数int num =1;for (int i = 0; i < 10000000; i++) {boolean prime = isPrime(i);if(prime) {num+=1;}}System.out.println(num);}public static boolean isPrime(int a) {boolean flag = true;if (a < 2) {// 素数不小于2return false;} else {for (int i = 2; i <= Math.sqrt(a); i++) {if (a % i == 0) {// 若能被整除,则说明不是素数,返回falseflag = false;break;// 跳出循环}}}return flag;}
}
格式化输出
如果不使用格式化输出,就需要进行字符串连接,如果变量比较多,拼接就会显得繁琐
使用格式化输出,就可以简洁明了
%s 表示字符串
%d 表示数字
%n 表示换行
%d表示整数,%f表示小数
package digit;public class TestNumber {public static void main(String[] args) {String name ="盖伦";int kill = 8;String title="超神";//直接使用+进行字符串连接,编码感觉会比较繁琐,并且维护性差,易读性差String sentence = name+ " 在进行了连续 " + kill + " 次击杀后,获得了 " + title +" 的称号";System.out.println(sentence);//使用格式化输出//%s表示字符串,%d表示数字,%n表示换行String sentenceFormat ="%s 在进行了连续 %d 次击杀后,获得了 %s 的称号%n";//使用printf格式化输出System.out.printf(sentenceFormat,name,kill,title);//使用format格式化输出System.out.format(sentenceFormat,name,kill,title);}
}
printf和format能够达到一模一样的效果,在printf中直接调用了format
format格式化方法
package com.wang.oop.demo13;import java.util.Locale;public class Demo03 {public static void main(String[] args) {int year = 2020;//总长度,左对齐,补0,千位分隔符,小数点位数,本地化表达//直接打印数字System.out.format("%d%n",year);//总长度是8,默认右对齐System.out.format("%8d%n",year);//总长度是8,左对齐System.out.format("%-8d%n",year);//总长度是8,不够补0System.out.format("%08d%n",year);//千位分隔符System.out.format("%,8d%n",year*10000);//小数点位数System.out.format("%.2f%n",Math.PI);//不同国家的千位分隔符System.out.format(Locale.FRANCE,"%,.2f%n",Math.PI*10000);System.out.format(Locale.US,"%,.2f%n",Math.PI*10000);System.out.format(Locale.UK,"%,.2f%n",Math.PI*10000);}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4mtd8kua-1633015831054)(C:\Users\15021\AppData\Roaming\Typora\typora-user-images\image-20210905224605712.png)]
字符
保存一个字符的时候用char,封装类Character
char c1 = 'a';char c2 = '1';//字符1,而非数字1char c3 = '中';//汉字字符char c4 = 'ab'; //只能放一个字符char c1 = 'a';Character c = c1; //自动装箱c1 = c;//自动拆箱//常用方法System.out.println(Character.isLetter('a'));//判断是否为字母System.out.println(Character.isDigit('a')); //判断是否为数字System.out.println(Character.isWhitespace(' ')); //是否是空白System.out.println(Character.isUpperCase('a')); //是否是大写System.out.println(Character.isLowerCase('a')); //是否是小写System.out.println(Character.toUpperCase('a')); //转换为大写System.out.println(Character.toLowerCase('A')); //转换为小写String a = 'a'; //不能够直接把一个字符转换成字符串String a2 = Character.toString('a'); //转换为字符串//常见转义System.out.println("使用\\t制表符可以达到对齐的效果");System.out.println("abc\tdef");System.out.println("ab\tdef");System.out.println("a\tdef");System.out.println("一个\\t制表符长度是8");System.out.println("12345678def");System.out.println("换行符 \\n");System.out.println("abc\ndef");System.out.println("单引号 \\'");System.out.println("abc\'def");System.out.println("双引号 \\\"");System.out.println("abc\"def");System.out.println("反斜杠本身 \\");System.out.println("abc\\def");
通过Scanner从控制台读取字符串,然后把字符串转换为字符数组
参考的转换方式:
String str = “abc123”;
char[] cs = str.toCharArray();
package com.wang.oop.demo13;import java.util.Scanner;public class Demo05 {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);System.out.println("输入字符串:");String str = scanner.nextLine();char[] cs = str.toCharArray();for (int i = 0; i < cs.length; i++) {if(Character.isUpperCase(cs[i]))System.out.print(cs[i]);if(Character.isDigit(cs[i]))System.out.print(cs[i]);}scanner.close();}
}
字符串
字符串是一个类,所以我们见到的字符串都是对象。
常见创建字符串手段:
\1. 每当有一个字面值出现的时候,虚拟机就会创建一个字符串
\2. 调用String的构造方法创建一个字符串对象
\3. 通过+加号进行字符串拼接也会创建新的字符串对象
package character;public class TestString {public static void main(String[] args) {String garen ="盖伦"; //字面值,虚拟机碰到字面值就会创建一个字符串对象String teemo = new String("提莫"); //创建了两个字符串对象char[] cs = new char[]{'崔','斯','特'};String hero = new String(cs);// 通过字符数组创建一个字符串对象String hero3 = garen + teemo;// 通过+加号进行字符串拼接}
}
String 被修饰为final,所以是不能被继承的
immutable 是指不可改变的
比如创建了一个字符串对象
String garen =“盖伦”;
不可改变的具体含义是指:
不能增加长度
不能减少长度
不能插入字符
不能删除字符
不能修改字符
一旦创建好这个字符串,里面的内容 永远 不能改变
String 的表现就像是一个常量
字符串格式化String.format
//直接使用+进行字符串连接,编码感觉会比较繁琐,并且维护性差,易读性差String sentence = name+ " 在进行了连续 " + kill + " 次击杀后,获得了 " + title +" 的称号";System.out.println(sentence);//格式化字符串//%s表示字符串,%d表示数字,%n表示换行String sentenceFormat ="%s 在进行了连续 %d 次击杀后,获得了 %s 的称号%n";String sentence2 = String.format(sentenceFormat, name,kill,title);System.out.println(sentence2);
length方法返回当前字符串的长度
可以有长度为0的字符串,即空字符串
随机字符串
创建一个长度是5的随机字符串,随机字符有可能是数字,大写字母或者小写字母
package com.wang.oop.demo13;import java.util.Random;public class Demo06 {public static void main(String[] args) {//0-9 48-57; A-Z 65-90; a-z 97-122String str = creatString(5);System.out.println(str);}public static String creatString(int length){String str = "";int num;char c;Random rd = new Random();for(int i =0; i<length; i++){num = rd.nextInt(75)+48; // 随机数范围:[48,122)if((num>=58 && num<=64) || (num>=91 && num<=96)){i--;continue;}c = (char)num;str = str +c;}return str;}
}
字符串数组排序
public static String[] sort(String[] str){String temp = "";for (int i = 0; i< str.length; i++){for (int j = 0; j < str.length-i-1; j++) {char[] ch0 = str[j].toCharArray();char[] ch1 = str[j+1].toCharArray();if(Character.toLowerCase(ch0[0]) > Character.toLowerCase(ch1[0])){temp = str[j];str[j] = str[j+1];str[j+1] = temp;}}}return str;}
穷举法破解密码
public static String crack(int length,String str){String result ="";char[] ch0 = str.toCharArray();for (int i = 0; i < length; i++) {for (int j = 33; j < 126; j++) {if(ch0[i]==(char)j)result+=(char)j;}}return result;}
操纵字符串
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LT4q3Zm2-1633015831055)(C:\Users\15021\AppData\Roaming\Typora\typora-user-images\image-20210906233610594.png)]
charAt(int index) 获取指定位置的字符String sentence = "盖伦,在进行了连续8次击杀后,获得了 超神 的称号"; char c = sentence.charAt(0); System.out.println(c);toCharArray() 获取对应的字符数组String sentence = "盖伦,在进行了连续8次击杀后,获得了超神 的称号"; char[] cs = sentence.toCharArray(); //获取对应的字符数组 System.out.println(sentence.length() == cs.length);subString 截取子字符串String sentence = "盖伦,在进行了连续8次击杀后,获得了 超神 的称号"; //截取从第3个开始的字符串 (基0)String subString1 = sentence.substring(3); System.out.println(subString1); //截取从第3个开始的字符串 (基0)//到5-1的位置的字符串//左闭右开String subString2 = sentence.substring(3,5); System.out.println(subString2);split 根据分隔符进行分隔String sentence = "盖伦,在进行了连续8次击杀后,获得了 超神 的称号"; //根据,进行分割,得到3个子字符串String subSentences[] = sentence.split(",");for (String sub : subSentences) {System.out.println(sub);}trim 去掉首尾空格String sentence = " 盖伦,在进行了连续8次击杀后,获得了 超神 的称号 "; System.out.println(sentence);//去掉首尾空格System.out.println(sentence.trim());toLowerCase 全部变成小写
toUpperCase 全部变成大写String sentence = "Garen"; //全部变成小写System.out.println(sentence.toLowerCase());//全部变成大写System.out.println(sentence.toUpperCase());indexOf lastIndexOf 判断字符或者子字符串出现的位置
contains 是否包含子字符串String sentence = "盖伦,在进行了连续8次击杀后,获得了超神 的称号"; System.out.println(sentence.indexOf('8')); //字符第一次出现的位置 System.out.println(sentence.indexOf("超神")); //字符串第一次出现的位置 System.out.println(sentence.lastIndexOf("了")); //字符串最后出现的位置 System.out.println(sentence.indexOf(',',5)); //从位置5开始,出现的第一次,的位置 System.out.println(sentence.contains("击杀")); //是否包含字符串"击杀"replaceAll 替换所有的
replaceFirst 只替换第一个String sentence = "盖伦,在进行了连续8次击杀后,获得了超神 的称号"; String temp = sentence.replaceAll("击杀", "被击杀"); //替换所有的 temp = temp.replaceAll("超神", "超鬼"); System.out.println(temp); temp = sentence.replaceFirst(",","");//只替换第一个 System.out.println(temp);
例子
public static String upperFirst(String sentence){ //每个单词的首字母都转换为大写String sentence1 = "";String[] str = sentence.split(" ");for (int i = 0; i < str.length; i++) {char ch0 =Character.toUpperCase(str[i].charAt(0));String str1 = str[i].substring(1);str[i] = ch0 + str1;sentence1 += str[i]+" ";}return sentence1;}public static void specilFirst(String sentence,char p){ //统计这段绕口令有多少个以p开头的单词,不分大小写String[] str = sentence.split(" ");p = Character.toUpperCase(p);int num = 0;for (int i = 0; i < str.length; i++) {char ch0 = Character.toUpperCase(str[i].charAt(0));if(p==ch0)num++;}System.out.printf("有%d个以%c开头的单词",num,p);System.out.println();}public static String upperLower(String sentence){ //间隔大写小写模式String sentence1 = "";String[] str = sentence.split(" ");for (int i = 0; i < str.length; i++) {char[] ch0 = str[i].toCharArray();for (int j = 0; j < ch0.length; j++) {if(j%2==0)ch0[j]=Character.toUpperCase(ch0[j]);elsech0[j]=Character.toLowerCase(ch0[j]);}String word = new String(ch0); //通过字符数组创建一个字符串对象sentence1 += word +" ";}return sentence1;}public static String upperLast(String sentence){ //每个单词的最后一个字母变大写String sentence1 = "";String[] str = sentence.split(" ");for (int i = 0; i < str.length; i++) {char[] ch0 = str[i].toCharArray();ch0[ch0.length-1] = Character.toUpperCase(ch0[ch0.length-1]);str[i]=new String(ch0);sentence1 += str[i]+" ";}return sentence1;}public static String lastWordUpperFirst(String sentence,String word){ //最后一个想找的单词首字母大写int index = sentence.lastIndexOf(word);char[] ch0 = sentence.toCharArray();ch0[index]=Character.toUpperCase(ch0[index]);String str = new String(ch0); //通过字符数组创建一个字符串对象return str;}
}
比较字符串
str1和str2的内容一定是一样的!
但是,并不是同一个字符串对象
package character;public class TestString {public static void main(String[] args) {String str1 = "the light"; String str2 = new String(str1);//==用于判断是否是同一个字符串对象System.out.println( str1 == str2); }
}
是否是同一个对象-特例
一般说来,编译器每碰到一个字符串的字面值,就会创建一个新的对象
所以在第6行会创建了一个新的字符串"the light"
但是在第7行,编译器发现已经存在现成的"the light",那么就直接拿来使用,而没有进行重复创建
package character;public class TestString {public static void main(String[] args) {String str1 = "the light";String str3 = "the light";System.out.println( str1 == str3);}
}
equals equalsIgnoreCase
使用equals进行字符串内容的比较,必须大小写一致
equalsIgnoreCase,忽略大小写判断内容是否一致
package character;public class TestString {public static void main(String[] args) {String str1 = "the light";String str2 = new String(str1);String str3 = str1.toUpperCase();//==用于判断是否是同一个字符串对象System.out.println( str1 == str2); //falseSystem.out.println(str1.equals(str2));//完全一样返回trueSystem.out.println(str1.equals(str3));//大小写不一样,返回falseSystem.out.println(str1.equalsIgnoreCase(str3));//忽略大小写的比较,返回true}}
是否以子字符串开始或者结束
String str1 = "the light";String start = "the";String end = "Ight";System.out.println(str1.startsWith(start));//以...开始System.out.println(str1.endsWith(end));//以...结束
StringBuffer
StringBuffer是可变长的字符串
append delete insert reverse | 追加 删除 插入 反转 |
---|---|
length capacity | 长度 容量 |
String str1 = "let there ";StringBuffer sb = new StringBuffer(str1); //根据str1创建一个StringBuffer对象sb.append("be light"); //在最后追加System.out.println(sb);sb.delete(4, 10);//删除4-10之间的字符System.out.println(sb);sb.insert(4, "there ");//在4这个位置插入 thereSystem.out.println(sb);sb.reverse(); //反转System.out.println(sb);
为什么StringBuffer可以变长?
和String内部是一个字符数组一样,StringBuffer也维护了一个字符数组。 但是,这个字符数组,留有冗余长度
比如说new StringBuffer(“the”),其内部的字符数组的长度,是19,而不是3,这样调用插入和追加,在现成的数组的基础上就可以完成了。
如果追加的长度超过了19,就会分配一个新的数组,长度比原来多一些,把原来的数据复制到新的数组中,看上去 数组长度就变长了 参考MyStringBuffer
length: “the”的长度 3
capacity: 分配的总空间 19
注: 19这个数量,不同的JDK数量是不一样的
String str1 = "the";StringBuffer sb = new StringBuffer(str1);System.out.println(sb.length()); //内容长度System.out.println(sb.capacity());//总空间
StringBuffer性能
String与StringBuffer的性能区别?
生成10位长度的随机字符串
然后,先使用String的+,连接10000个随机字符串,计算消耗的时间
然后,再使用StringBuffer连接10000个随机字符串,计算消耗的时间
时间System.currentTimeMillis
package character;public class TestString {public static void main(String[] args) {int total = 10000;String s = randomString(10);StringBuffer sb = new StringBuffer();String str1 = "";long start = System.currentTimeMillis();for (int i = 0; i <total; i++) {str1+=s;}long end = System.currentTimeMillis();System.out.printf("使用字符串连接+的方式,连接%d次,耗时%d毫秒%n",total,end-start);total *=100;start = System.currentTimeMillis();for (int i = 0; i <total; i++) {sb.append(s);}end = System.currentTimeMillis();System.out.printf("使用StringBuffer的方式,连接%d次,耗时%d毫秒%n",total,end-start);}private static String randomString(int length) {String pool = "";for (short i = '0'; i <= '9'; i++) {pool += (char) i;}for (short i = 'a'; i <= 'z'; i++) {pool += (char) i;}for (short i = 'A'; i <= 'Z'; i++) {pool += (char) i;}char cs[] = new char[length];for (int i = 0; i < cs.length; i++) {int index = (int) (Math.random() * pool.length());cs[i] = pool.charAt(index);}String result = new String(cs);return result;}}
边界条件判断
插入之前,首先要判断的是一些边界条件。 比如插入位置是否合法,插入的字符串是否为空
扩容
\1. 要判断是否需要扩容。 如果插入的字符串加上已经存在的内容的总长度超过了容量,那么就需要扩容。
\2. 数组的长度是固定的,不能改变的,数组本身不支持扩容。 我们使用变通的方式来解决这个问题。
\3. 根据需要插入的字符串的长度和已经存在的内容的长度,计算出一个新的容量。 然后根据这个容量,创建一个新的数组,接着把原来的数组的内容,复制到这个新的数组中来。并且让value这个引用,指向新的数组,从而达到扩容的效果。
插入字符串
\1. 找到要插入字符串的位置,从这个位置开始,把原数据看成两段,把后半段向后挪动一个距离,这个距离刚好是插入字符串的长度
\2. 然后把要插入的数据,插入这个挪出来的,刚刚好的位置里。
修改length的值
最后修改length的值,是原来的值加上插入字符串的长度
insert(int, char)
参数是字符的insert方法,通过调用insert(int, String) 也就实现了。
append
追加,就是在最后位置插入。 所以不需要单独开发方法,直接调用insert方法,就能达到最后位置插入的效果
日期
Date类
注意:是java.util.Date;
而非 java.sql.Date,此类是给数据库访问的时候使用的
时间原点概念
所有的数据类型,无论是整数,布尔,浮点数还是字符串,最后都需要以数字的形式表现出来。
日期类型也不例外,换句话说,一个日期,比如2020年10月1日,在计算机里,会用一个数字来代替。
那么最特殊的一个数字,就是零. 零这个数字,就代表Java中的时间原点,其对应的日期是1970年1月1日 8点0分0秒 。 (为什么是8点,因为中国的太平洋时区是UTC-8,刚好和格林威治时间差8个小时)
为什么对应1970年呢? 因为1969年发布了第一个 UNIX 版本:AT&T,综合考虑,当时就把1970年当做了时间原点。
所有的日期,都是以为这个0点为基准,每过一毫秒,就+1。
创建日期对象
// 当前时间Date d1 = new Date();System.out.println("当前时间:");System.out.println(d1);System.out.println();// 从1970年1月1日 早上8点0分0秒 开始经历的毫秒数Date d2 = new Date(5000);System.out.println("从1970年1月1日 早上8点0分0秒 开始经历了5秒的时间");System.out.println(d2);
getTime
getTime() 得到一个long型的整数
这个整数代表 从1970.1.1 08:00:00:000 开始 每经历一毫秒,增加1
直接打印对象,会看到 “Tue Jan 05 09:51:48 CST 2016” 这样的格式,可读性比较差,为了获得“2016/1/5 09:51:48”这样的格式需要日期格式化
//注意:是java.util.Date;//而非 java.sql.Date,此类是给数据库访问的时候使用的Date now= new Date();//打印当前时间System.out.println("当前时间:"+now.toString());//getTime() 得到一个long型的整数//这个整数代表 1970.1.1 08:00:00:000,每经历一毫秒,增加1System.out.println("当前时间getTime()返回的值是:"+now.getTime());Date zero = new Date(0);System.out.println("用0作为构造方法,得到的日期是:"+zero);
System.currentTimeMillis()
当前日期的毫秒数
new Date().getTime() 和 System.currentTimeMillis() 是一样的
不过由于机器性能的原因,可能会相差几十毫秒,毕竟每执行一行代码,都是需要时间的
练习
借助随机数,创建一个从1995.1.1 00:00:00 到 1995.12.31 23:59:59 之间的随机日期
package com.wang.oop.datad;import java.text.SimpleDateFormat;
import java.text.ParseException;
import java.util.Date;public class Date1995 {public static void main(String[] args) throws ParseException { //SimpleDateFormat 日期格式化类SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String str1 = "1995-01-01 00:00:00";String str2 = "1995-12-31 23:59:59";Date date1 = sf.parse(str1); //转换为日期形式System.out.println(sf.format(date1));Date date2 = sf.parse(str2);System.out.println(sf.format(date2));long d1 = date1.getTime();long d2 = date2.getTime();long date = (long)(Math.random()*(d2-d1)+d1);System.out.println(sf.format(date));}
}
SimpleDateFormat 日期格式化类
日期转字符串
y 代表年;M 代表月;d 代表日;H 代表24进制的小时;h 代表12进制的小时;m 代表分钟;s 代表秒;S 代表毫秒
//y 代表年//M 代表月//d 代表日//H 代表24进制的小时//h 代表12进制的小时//m 代表分钟//s 代表秒//S 代表毫秒SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS" );Date d= new Date();String str = sdf.format(d);System.out.println("当前时间通过 yyyy-MM-dd HH:mm:ss SSS 格式化后的输出: "+str);SimpleDateFormat sdf1 =new SimpleDateFormat("yyyy-MM-dd" );Date d1= new Date();String str1 = sdf1.format(d1);System.out.println("当前时间通过 yyyy-MM-dd 格式化后的输出: "+str1);
练习
package com.wang.oop.datad;import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;//准备一个长度是9的日期数组
//使用1970年-2000年之间的随机日期初始化该数组
//按照这些日期的时间进行升序排序
//比如 1988-1-21 12:33:22 就会排在 1978-4-21 19:07:23 前面,因为它的时间更小,虽然日期更大public class DateFormat {public static void main(String[] args) throws ParseException {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");Date start = sdf.parse("1970-1-1 0:0:0");Date end = sdf.parse("2000-12-31 23:59:59");Date[] dates = new Date[9];long rand;for (int i = 0; i <dates.length ; i++) {rand = (long)(Math.random()*(end.getTime()-start.getTime())+start.getTime());dates[i] = new Date(rand);}System.out.println("得到的随机日期数组:");for (int i = 0; i < dates.length; i++) {System.out.print(sdf.format(dates[i])+"\t\t");if (i%3==2)System.out.println();}long temp;for (int i = 0; i < dates.length; i++) {boolean flag = false;for (int j = 0; j <dates.length-i-1 ; j++) {if (dates[j].getTime()>dates[j+1].getTime()){temp = dates[j].getTime();dates[j] = new Date(dates[j+1].getTime());dates[j+1] = new Date(temp);flag = true;}}if (flag==false)break;}System.out.println("排序后的随机日期数组:");for (int i = 0; i < dates.length; i++) {System.out.print(sdf.format(dates[i])+"\t\t");if (i%3==2)System.out.println();}}
}
Calendar
Calendar类即日历类,常用于进行“翻日历”,比如下个月的今天是多久
采用单例模式获取日历对象Calendar.getInstance();
package date;//
import java.util.Calendar;
import java.util.Date;public class TestDate {public static void main(String[] args) {//采用单例模式获取日历对象Calendar.getInstance();Calendar c = Calendar.getInstance();//通过日历对象得到日期对象Date d = c.getTime();Date d2 = new Date(0);c.setTime(d2); //把这个日历,调成日期 : 1970.1.1 08:00:00}
}
add方法,在原日期上增加年/月/日
set方法,直接设置年/月/日
package com.wang.oop.datad;
import java.util.Calendar;
import java.util.Date;
import java.text.SimpleDateFormat;public class TextCalendar {private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");public static void main(String[] args) {Calendar c = Calendar.getInstance();Date now = c.getTime();// 当前日期System.out.println("当前日期:\t" + format(c.getTime()));// 下个月的今天c.setTime(now);c.add(Calendar.MONTH, 1);System.out.println("下个月的今天:\t" +format(c.getTime()));// 去年的今天c.setTime(now);c.add(Calendar.YEAR, -1);System.out.println("去年的今天:\t" +format(c.getTime()));// 上个月的第三天c.setTime(now);c.add(Calendar.MONTH, -1);c.set(Calendar.DATE, 3);System.out.println("上个月的第三天:\t" +format(c.getTime()));c.setTime(now);c.add(Calendar.MONTH,2);c.set(Calendar.DATE,-2);System.out.println("下个月的倒数第三天:\t" +format(c.getTime()));}private static String format(Date time) {return sdf.format(time);}
}
异常
异常:Exception
检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。在编译时不能被简单的忽略。
运行时异常:是可能被程序员避免的异常。可以在编译时被忽略。
错误:错误不是异常,是脱离程序员控制的问题。在代码中通常被忽略。
异常处理机制
抛出异常
捕获异常
异常处理五个关键字:try、catch、finally、throw、throws
package com.wang.exception;public class Demo01 {public static void main(String[] args) {int a = 0;int b = 0;//Ctrl + Alt + Ttry{ //try监控区域System.out.println(a/b);}catch (Error e){ //catch(想要捕获的异常类型!) 捕获异常System.out.println("Error");e.printStackTrace(); //打印错误的栈信息}catch (Exception e){System.out.println("Exception");}catch (Throwable t){System.out.println("Throwable");}finally { //处理善后工作System.out.println("finally");}}}
在开发中,如果去调用别人写的方法时,是否能知道别人写的方法是否会发生异常?这是很难判断的。针对这种情况,Java总允许在方法的后面使用throws关键字对外声明该方法有可能发生异常,这样调用者在调用方法时,就明确地知道该方法有异常,并且必须在程序中对异常进行处理,否则编译无法通过。
如下面代码
public static void main(String[] args) {// TODO Auto-generated method stubint result = divide(4,2);System.out.println(result);
}public static int divide(int x,int y) throws Exception
{int result = x/y;return result;
}
这时候 编译器上会有错误提示 Unhandled exception type Exception
所以需要对调用divide()方法进行try…catch处理
public static void main(String[] args) {try {int result = divide(4,2);System.out.println(result);} catch (Exception e) {e.printStackTrace();}}public static int divide(int x,int y) throws Exception
{int result = x/y;return result;
}
}
package com.wang.exception;public class Demo02 {public static void main(String[] args) {new Demo02().test(0,0);}//假设这方法中,处理不了这个异常,方法上抛出异常throwspublic void test(int a,int b) throws ArithmeticException{if(b==0){throw new ArithmeticException(); //主动抛出的异常,一般在方法中使用}}
}
编译时异常
在Java 中,Exception类中除了RuntimeException 类及其子类外都是编译时异常。编译时异常的特点是Java编译器会对其进行检查,如果出现异常就必须对异常进行处理,否则程序无法编译通过。
处理方法
使用try… catch 语句对异常进行捕获
使用throws 关键字声明抛出异常,调用者对其进行处理
2.运行时异常
RuntimeException 类及其子类运行异常。运行时异常的特点是Java编译器不会对其进行检查。也就是说,当程序中出现这类异常时,即使没有使用try… catch 语句捕获使用throws关键字声明抛出。程序也能编译通过。运行时异常一般是程序中的逻辑错误引起的,在程序运行时无法修复。例如 数据取值越界。
3.自定义异常
JDK中定义了大量的异常类,虽然这些异常类可以描述编程时出现的大部分异常情况,但是在程序开发中有时可能需要描述程序中特有的异常情况。例如divide()方法中不允许被除数为负数。为类解决这个问题,在Java中允许用户自定义异常,但自定义的异常类必须继承自Exception或其子类。例子如下
package www.kangxg.jdbc;public class DivideDivideByMinusException extends Exception {/*** */
private static final long serialVersionUID = 1L;public DivideDivideByMinusException(){super();
}public DivideDivideByMinusException(String message)
{super(message);
}
}
package www.kangxg.jdbc;public class Example {public static void main(String[] args) throws Exception {try {int result = divide(4,-2);System.out.println(result);} catch (DivideDivideByMinusException e) {System.out.println(e.getMessage());}}public static int divide(int x,int y) throws DivideDivideByMinusException
{if(y<0){throw new DivideDivideByMinusException("被除数是负数");}int result = x/y;return result;
}}
new FileInputStream(f)异常
Java中通过 new FileInputStream(f) 试图打开某文件,就有可能抛出文件不存在异常FileNotFoundException
如果不处理该异常,就会有编译错误
File f= new File("d:/LOL.exe");//试图打开文件LOL.exe,会抛出FileNotFoundException,如果不处理该异常,就会有编译错误new FileInputStream(f);
异常处理常见手段: try catch finally throws
1.将可能抛出FileNotFoundException 文件不存在异常的代码放在try里
2.如果文件存在,就会顺序往下执行,并且不执行catch块中的代码
- 如果文件不存在,try 里的代码会立即终止,程序流程会运行到对应的catch块中
- e.printStackTrace(); 会打印出方法的调用痕迹,如此例,会打印出异常开始于TestException的第16行,这样就便于定位和分析到底哪里出了异常
FileNotFoundException是Exception的子类,使用Exception也可以catch住FileNotFoundException
多异常捕捉办法
有的时候一段代码会抛出多种异常,比如
new FileInputStream(f);Date d = sdf.parse("2016-06-03");
这段代码,会抛出 文件不存在异常 FileNotFoundException 和 解析异常ParseException
解决办法之一是分别进行catch
catch (FileNotFoundException e) {System.out.println("d:/LOL.exe不存在");e.printStackTrace();
} catch (ParseException e) {System.out.println("日期格式解析错误");e.printStackTrace();
}
另一个种办法是把多个异常,放在一个catch里统一捕捉
catch (FileNotFoundException | ParseException e) {
这种方式从 JDK7开始支持,好处是捕捉的代码更紧凑,不足之处是,一旦发生异常,不能确定到底是哪种异常,需要通过instanceof 进行判断具体的异常类型
public static void main(String[] args) {File f = new File("d:/LOL.exe");try {System.out.println("试图打开 d:/LOL.exe");new FileInputStream(f);System.out.println("成功打开");SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");Date d = sdf.parse("2016-06-03");} catch (FileNotFoundException | ParseException e) {if (e instanceof FileNotFoundException)System.out.println("d:/LOL.exe不存在");if (e instanceof ParseException)System.out.println("日期格式解析错误");e.printStackTrace();}
finally
无论是否出现异常,finally中的代码都会被执行
try{System.out.println("试图打开 d:/LOL.exe");new FileInputStream(f);System.out.println("成功打开");}catch(FileNotFoundException e){System.out.println("d:/LOL.exe不存在");e.printStackTrace();}finally{System.out.println("无论文件是否存在, 都会执行的代码");}
throws
考虑如下情况:
主方法调用method1
method1调用method2
method2中打开文件
method2中需要进行异常处理
但是method2不打算处理,而是把这个异常通过throws****抛出去
那么method1就会接到该异常。 处理办法也是两种,要么是try catch处理掉,要么也是抛出去。
method1选择本地try catch住 一旦try catch住了,就相当于把这个异常消化掉了,主方法在调用method1的时候,就不需要进行异常处理了
public class TestException {public static void main(String[] args) {method1();}private static void method1() {try {method2();} catch (FileNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();}}private static void method2() throws FileNotFoundException {File f = new File("d:/LOL.exe");System.out.println("试图打开 d:/LOL.exe");new FileInputStream(f);System.out.println("成功打开");}
}
throw 和 throws
throws与throw这两个关键字接近,不过意义不一样,有如下区别:
\1. throws 出现在方法声明上,而throw通常都出现在方法体内。
\2. throws 表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某个异常对象。
异常分类
可查异常: CheckedException
可查异常即必须进行处理的异常,要么try catch住,要么往外抛,谁调用,谁处理,比如 FileNotFoundException
如果不处理,编译器,就不让你通过
运行时异常RuntimeException指: 不是必须进行try catch的异常
常见运行时异常:
除数不能为0异常:ArithmeticException
下标越界异常:ArrayIndexOutOfBoundsException
空指针异常:NullPointerException
在编写代码的时候,依然可以使用try catch throws进行处理,与可查异常不同之处在于,即便不进行try catch,也不会有编译错误
Java之所以会设计运行时异常的原因之一,是因为下标越界,空指针这些运行时异常太过于普遍,如果都需要进行捕捉,代码的可读性就会变得很糟糕。
错误Error,指的是系统级别的异常,通常是内存用光了
在默认设置下,一般java程序启动的时候,最大可以使用16m的内存
如例不停的给StringBuffer追加字符,很快就把内存使用光了。抛出OutOfMemoryError
与运行时异常一样,错误也是不要求强制捕捉的
运行时异常: 都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。 运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。
非运行时异常 (编译异常): 是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
Throwable
Throwable是类,Exception和Error都继承了该类
所以在捕捉的时候,也可以使用Throwable进行捕捉
如图: 异常分Error和Exception
Exception里又分运行时异常和可查异常。
自定义异常
一个英雄攻击另一个英雄的时候,如果发现另一个英雄已经挂了,就会抛出EnemyHeroIsDeadException
创建一个类EnemyHeroIsDeadException,并继承Exception
提供两个构造方法
- 无参的构造方法
- 带参的构造方法,并调用父类的对应的构造方法
class EnemyHeroIsDeadException extends Exception{public EnemyHeroIsDeadException(){}public EnemyHeroIsDeadException(String msg){super(msg);}
}
在Hero的attack方法中,当发现敌方英雄的血量为0的时候,抛出该异常
\1. 创建一个EnemyHeroIsDeadException实例
\2. 通过throw 抛出该异常
\3. 当前方法通过 throws 抛出该异常
在外部调用attack方法的时候,就需要进行捕捉,并且捕捉的时候,可以通过e.getMessage() 获取当时出错的具体原因
package charactor;public class Hero {public String name;protected float hp;public void attackHero(Hero h) throws EnemyHeroIsDeadException{if(h.hp == 0){throw new EnemyHeroIsDeadException(h.name + " 已经挂了,不需要施放技能" );}}public String toString(){return name;}class EnemyHeroIsDeadException extends Exception{public EnemyHeroIsDeadException(){}public EnemyHeroIsDeadException(String msg){super(msg);}}public static void main(String[] args) {Hero garen = new Hero();garen.name = "盖伦";garen.hp = 616;Hero teemo = new Hero();teemo.name = "提莫";teemo.hp = 0;try {garen.attackHero(teemo);} catch (EnemyHeroIsDeadException e) {// TODO Auto-generated catch blockSystem.out.println("异常的具体原因:"+e.getMessage());e.printStackTrace();}}
}
每一个throws的异常都需要catch住
接口IStringBuffer中声明的方法需要抛出异常
public static class IndexIsNegativeException extends Exception{public IndexIsNegativeException(){}public IndexIsNegativeException(String msg){super(msg);}}public static class IndexOutOfRangeException extends Exception{public IndexOutOfRangeException(){}public IndexOutOfRangeException(String msg){super(msg);}}public static class NullPointerException extends Exception{public NullPointerException(){}public NullPointerException(String msg){super(msg);}}
//**每一个throws的异常都需要catch住**
// 接口IStringBuffer中声明的方法需要抛出异常public void delete(int start, int end) throws IndexIsNegativeException,IndexOutOfRangeException,NullPointerException {//边界条件判断if (start<0)throw new IndexIsNegativeException("start下标为负异常");if (start>length)throw new IndexOutOfRangeException("start下标超出范围异常");if (end<0)throw new IndexIsNegativeException("end下标为负异常");if (end>length)throw new IndexOutOfRangeException("end下标超出范围异常");if (start>=end)throw new IndexIsNegativeException("下标异常");System.arraycopy(value,end,value,start,length-end);length -= end-start;}try{sb.insert(1000, "let ");System.out.println(sb);}catch (IndexOutOfRangeException i){System.out.println(i.getMessage());i.printStackTrace();} catch (NullPointerException n) {System.out.println(n.getMessage());n.printStackTrace();} catch (IndexIsNegativeException i) {System.out.println(i.getMessage());i.printStackTrace();}}
练习
这是一个类图
Account类: 银行账号
属性: balance 余额
方法: getBalance() 获取余额
方法: deposit() 存钱
方法: withdraw() 取钱
OverdraftException: 透支异常,继承Exception
属性: deficit 透支额
package exception;public class OverDraftException extends Exception{private double deficit;public double getDeficit() {return deficit;}public OverDraftException(String msg, double deficit) {super(msg);this.deficit = deficit;}}package exception;public class Account {protected double balance;public Account(double balance) {this.balance = balance;}public double getBalance() {return balance;}public void deposit(double amt){this.balance+=amt;}public void withdraw(double amt) throws OverDraftException{if(this.balance<amt)throw new OverDraftException("余额不足", amt-this.balance);this.balance-=amt;}public static void main(String[] args) {//开户存了1000Account a = new Account(1000);//存钱1000a.deposit(1000);//查看余额System.out.println(a.getBalance());try {//取2001a.withdraw(2001);} catch (OverDraftException e) {System.err.println("透支金额:"+e.getDeficit());e.printStackTrace();}}}
类: CheckingAccount 支票账户,具备透支额度,继承Account
属性:overdraftProtection 透支额度
package exception;public class CheckingAccount extends Account {private double overdraftProtection;public CheckingAccount(double balance) {super(balance);}public CheckingAccount(double balance, double overdraftProtection) {super(balance);this.overdraftProtection = overdraftProtection;}public void withdraw(double amt) throws OverDraftException {if (amt > this.balance + overdraftProtection) {double deficit = amt - (this.balance + overdraftProtection);throw new OverDraftException("透支额度超标", deficit);}this.balance -= amt;}public static void main(String[] args) {//开户存了1000块,拥有500的透支额度CheckingAccount a = new CheckingAccount(1000, 500);//存了1000a.deposit(1000);//查询余额System.out.println(a.getBalance());try {a.withdraw(600);System.out.println(a.getBalance());a.withdraw(600);System.out.println(a.getBalance());a.withdraw(600);System.out.println(a.getBalance());a.withdraw(600);System.out.println(a.getBalance());a.withdraw(600);System.out.println(a.getBalance());} catch (OverDraftException e) {System.err.println("透支超额:"+e.getDeficit());e.printStackTrace();}}}
I/O
f1.getAbsolutePath(); // 绝对路径
f.exist(); //文件是否存在
f.isDirectory(); //是否是文件夹
f.isFile(); //是否是文件(非文件夹)
f.length(); //文件长度
f.lastModified(); //文件最后修改时间
f.setLastModified(0); //设置文件修改时间为1970.1.1 08:00:00
f.renameTo(f2); //文件重命名
f.list(); // 以字符串数组的形式,返回当前文件夹下的所有文件(不包含子文件及子文件夹)
File[]fs= f.listFiles(); // 以文件数组的形式,返回当前文件夹下的所有文件(不包含子文件及子文件夹)
f.getParent(); // 以字符串形式返回获取所在文件夹
f.getParentFile(); // 以文件形式返回获取所在文件夹
f.mkdir(); // 创建文件夹,如果父文件夹skin不存在,创建就无效
f.mkdirs(); // 创建文件夹,如果父文件夹skin不存在,就会创建父文件夹
f.createNewFile(); // 创建一个空文件,如果父文件夹skin不存在,就会抛出异常
f.getParentFile().mkdirs(); // 所以创建一个空文件之前,通常都会创建父目录
f.listRoots(); // 列出所有的盘符c: d: e: 等等
f.delete(); // 刪除文件
f.deleteOnExit(); // JVM结束的时候,刪除文件,常用于临时文件的删除
import java.io.File;// 绝对路径File f1 = new File("d:/LOLFolder");System.out.println("f1的绝对路径:" + f1.getAbsolutePath());// 相对路径,相对于工作目录,如果在eclipse中,就是项目目录File f2 = new File("LOL.exe");System.out.println("f2的绝对路径:" + f2.getAbsolutePath());// 把f1作为父目录创建文件对象File f3 = new File(f1, "LOL.exe");System.out.println("f3的绝对路径:" + f3.getAbsolutePath());File f = new File("d:/LOLFolder/LOL.exe");System.out.println("当前文件是:" +f);//文件是否存在System.out.println("判断是否存在:"+f.exists());//是否是文件夹System.out.println("判断是否是文件夹:"+f.isDirectory());//是否是文件(非文件夹)System.out.println("判断是否是文件:"+f.isFile());//文件长度System.out.println("获取文件的长度:"+f.length());//文件最后修改时间long time = f.lastModified();Date d = new Date(time);System.out.println("获取文件的最后修改时间:"+d);//设置文件修改时间为1970.1.1 08:00:00f.setLastModified(0);//文件重命名File f2 =new File("d:/LOLFolder/DOTA.exe");f.renameTo(f2);System.out.println("把LOL.exe改名成了DOTA.exe");System.out.println("注意: 需要在D:\\LOLFolder确实存在一个LOL.exe,\r\n才可以看到对应的文件长度、修改时间等信息");
遍历文件夹及子文件夹
package com.wang.IO;import java.io.File;public class Demo01 {static long minSize=Long.MAX_VALUE;static long maxSize=0;static File minFile = null;static File maxFile = null;public static void main(String[] args) {File f1 =new File("D:/电影");traverse(f1);System.out.printf("最大的文件是%s,其大小是%,d字节%n",maxFile.getAbsoluteFile(),maxFile.length());System.out.printf("最小的文件是%s,其大小是%,d字节%n",minFile.getAbsoluteFile(),minFile.length());}public static void traverse(File f1){if (f1.isFile()){if (f1.length()>maxSize){maxSize = f1.length();maxFile = f1;}if(f1.length()!=0 && f1.length()<minSize){minSize = f1.length();minFile = f1;}}if(f1.isDirectory()){File[] fs = f1.listFiles();if(null!=fs)for (File f : fs) {traverse(f); //递归}}}
}
流
当不同的介质之间有数据交互的时候,JAVA就使用流来实现。
数据源可以是文件,还可以是数据库,网络甚至是其他的程序
比如读取文件的数据到程序中,站在程序的角度来看,就叫做输入流
InputStream字节输入流
OutputStream字节输出流
用于以字节的形式读取和写入数据
所有的流,无论是输入流还是输出流,使用完毕之后,都应该关闭。 如果不关闭,会产生对资源占用的浪费。 当量比较大的时候,会影响到业务的正常开展。
File f = new File("d:/lol.txt");// 创建基于文件的输入流FileInputStream fis = new FileInputStream(f);// 通过这个输入流,就可以把数据从硬盘,读取到Java的虚拟机中来,也就是读取到内存中
InputStream是字节输入流,同时也是抽象类,只提供方法声明,不提供方法的具体实现。
FileInputStream 是InputStream子类,以FileInputStream 为例进行文件读取
try {//准备文件lol.txt其中的内容是AB,对应的ASCII分别是65 66File f =new File("d:/lol.txt");//创建基于文件的输入流FileInputStream fis =new FileInputStream(f);//创建字节数组,其长度就是文件的长度byte[] all =new byte[(int) f.length()];//以字节流的形式读取文件所有内容fis.read(all);for (byte b : all) {//打印出来是65 66System.out.println(b);}//每次使用完流,都应该进行关闭fis.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}
OutputStream是字节输出流,同时也是抽象类,只提供方法声明,不提供方法的具体实现。
FileOutputStream 是OutputStream子类,以FileOutputStream 为例向文件写出数据
注: 如果文件d:/lol2.txt不存在,写出操作会自动创建该文件。
但是如果是文件 d:/xyz/lol2.txt,而目录xyz又不存在,会抛出异常
public static void main(String[] args) {try {// 准备文件lol2.txt其中的内容是空的File f = new File("d:/lol2.txt");// 准备长度是2的字节数组,用88,89初始化,其对应的字符分别是X,Ybyte data[] = { 88, 89 };// 创建基于文件的输出流FileOutputStream fos = new FileOutputStream(f);// 把数据写入到输出流fos.write(data);// 关闭输出流fos.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}
public static void main(String[] args) {try {File f = new File("d:/xyz/abc/def/lol2.txt");//因为默认情况下,文件系统中不存在 d:\xyz\abc\def,所以输出会失败//首先获取文件所在的目录File dir = f.getParentFile();//如果该目录不存在,则创建该目录if(!dir.exists()){// dir.mkdir(); //使用mkdir会抛出异常,因为该目录的父目录也不存在dir.mkdirs(); //使用mkdirs则会把不存在的目录都创建好}byte data[] = { 88, 89 };FileOutputStream fos = new FileOutputStream(f);fos.write(data);fos.close();} catch (IOException e) {e.printStackTrace();}}
拆分文件与合并文件
public class TestStream {public static void main(String[] args) {// int eachSize = 100*1024;
// File srcFile = new File("D://JavaSE.md");
// splitFile(srcFile,eachSize);murgeFile("d://新建文件夹","JavaSE.md");}private static void splitFile(File srcFile, int eachSize) {if (0 == srcFile.length())throw new RuntimeException("文件长度为0,不可拆分");byte[] fileContent = new byte[(int) srcFile.length()];// 先把文件读取到数组中try {FileInputStream fis = new FileInputStream(srcFile);fis.read(fileContent);fis.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}// 计算需要被划分成多少份子文件int fileNumber;// 文件是否能被整除得到的子文件个数是不一样的// (假设文件长度是25,每份的大小是5,那么就应该是5个)// (假设文件长度是26,每份的大小是5,那么就应该是6个)if (0 == fileContent.length % eachSize)fileNumber = (int) (fileContent.length / eachSize);elsefileNumber = (int) (fileContent.length / eachSize) + 1;for (int i = 0; i < fileNumber; i++) {String eachFileName = srcFile.getName() + "-" + i;File eachFile = new File(srcFile.getParent(), eachFileName);byte[] eachContent;// 从源文件的内容里,复制部分数据到子文件// 除开最后一个文件,其他文件大小都是100k// 最后一个文件的大小是剩余的if (i != fileNumber - 1) // 不是最后一个eachContent = Arrays.copyOfRange(fileContent, eachSize * i, eachSize * (i + 1));else // 最后一个eachContent = Arrays.copyOfRange(fileContent, eachSize * i, fileContent.length);try {// 写出去FileOutputStream fos = new FileOutputStream(eachFile);fos.write(eachContent);// 记得关闭fos.close();System.out.printf("输出子文件%s,其大小是 %d字节%n", eachFile.getAbsoluteFile(), eachFile.length());} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}/*** 合并的思路,就是从eclipse.exe-0开始,读取到一个文件,就开始写出到 eclipse.exe中,直到没有文件可以读* @param folder* 需要合并的文件所处于的目录* @param fileName* 需要合并的文件的名称* @throws FileNotFoundException*/private static void murgeFile(String folder, String fileName) {try {// 合并的目标文件File destFile = new File(folder, fileName);FileOutputStream fos = new FileOutputStream(destFile);int index = 0;while (true) {//子文件File eachFile = new File(folder, fileName + "-" + index++);//如果子文件不存在了就结束if (!eachFile.exists())break;//读取子文件的内容FileInputStream fis = new FileInputStream(eachFile);byte[] eachContent = new byte[(int) eachFile.length()];fis.read(eachContent);fis.close();//把子文件的内容写出去fos.write(eachContent);fos.flush();System.out.printf("把子文件 %s写出到目标文件中%n",eachFile);}fos.close();System.out.printf("最后目标文件的大小:%,d字节" , destFile.length());} catch (FileNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
关闭流
所有的流,无论是输入流还是输出流,使用完毕之后,都应该关闭。 如果不关闭,会产生对资源占用的浪费。 当量比较大的时候,会影响到业务的正常开展。
在try的作用域里关闭文件输入流,在前面的示例中都是使用这种方式,这样做有一个弊端;
如果文件不存在,或者读取的时候出现问题而抛出异常,那么就不会执行这一行关闭流的代码,存在巨大的资源占用隐患。 不推荐使用
在finally中关闭
这是标准的关闭流的方式
\1. 首先把流的引用声明在try的外面,如果声明在try里面,其作用域无法抵达finally.
\2. 在finally关闭之前,要先判断该引用是否为空
\3. 关闭的时候,需要再一次进行try catch处理
这是标准的严谨的关闭流的方式,但是看上去很繁琐,所以写不重要的或者测试代码的时候,都会采用上面的有隐患try的方式,因为不麻烦~
package stream;import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;public class TestStream {public static void main(String[] args) {File f = new File("d:/lol.txt");FileInputStream fis = null;try {fis = new FileInputStream(f);byte[] all = new byte[(int) f.length()];fis.read(all);for (byte b : all) {System.out.println(b);}} catch (IOException e) {e.printStackTrace();} finally {// 在finally 里关闭流if (null != fis)try {fis.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}
}
使用try()的方式
把流定义在try()里,try,catch或者finally结束的时候,会自动关闭
这种编写代码的方式叫做 try-with-resources, 这是从JDK7开始支持的技术
所有的流,都实现了一个接口叫做 AutoCloseable,任何类实现了这个接口,都可以在try()中进行实例化。 并且在try, catch, finally结束的时候自动关闭,回收相关资源。
public static void main(String[] args) {File f = new File("d:/lol.txt");//把流定义在try()里,try,catch或者finally结束的时候,会自动关闭try (FileInputStream fis = new FileInputStream(f)) {byte[] all = new byte[(int) f.length()];fis.read(all);for (byte b : all) {System.out.println(b);}} catch (IOException e) {e.printStackTrace();}}
字符流
Reader字符输入流
Writer字符输出流
专门用于字符的形式读取和写入数据
FileReader 是Reader子类,以FileReader 为例进行文件读取
public static void main(String[] args) {// 准备文件lol.txt其中的内容是ABFile f = new File("d:/lol.txt");// 创建基于文件的Readertry (FileReader fr = new FileReader(f)) {// 创建字符数组,其长度就是文件的长度char[] all = new char[(int) f.length()];// 以字符流的形式读取文件所有内容fr.read(all);for (char b : all) {// 打印出来是A BSystem.out.println(b);}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
FileWriter 是Writer的子类,以FileWriter 为例把字符串写入到文件
public static void main(String[] args) {// 准备文件lol2.txtFile f = new File("d:/lol2.txt");// 创建基于文件的Writertry (FileWriter fr = new FileWriter(f)) {// 以字符流的形式把数据写入到文件中String data="abcdefg1234567890";char[] cs = data.toCharArray();fr.write(cs);} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
文件加密与解密
public static void main(String[] args) {File f1 = new File("d://test.txt");File f2 = new File("d://test2.txt");File f3 = new File("d://test3.txt");encodeFile(f1,f2);decodeFile(f2,f3);
}public static void encodeFile(File encodingFile,File encodedFile){try(FileReader fr= new FileReader(encodingFile)){char[] all = new char[(int)encodingFile.length()];fr.read(all);for (int i = 0; i < all.length; i++) {if(all[i]=='9')all[i]='0';else if (all[i]<'9' && all[i]>='0')all[i]+=1;else if (all[i]=='z' | all[i]=='Z')all[i]-=25;else if ((all[i]<'z' && all[i]>='a')||(all[i]<'Z' && all[i]>='A'))all[i]+=1;}try(FileWriter fw = new FileWriter(encodedFile)){fw.write(all);}}catch(IOException e){e.printStackTrace();}
}public static void decodeFile(File decodingFile,File decodedFile){try(FileReader fr= new FileReader(decodingFile)){char[] all = new char[(int)decodingFile.length()];fr.read(all);for (int i = 0; i < all.length; i++) {if(all[i]=='0')all[i]='9';else if (all[i]<'9' && all[i]>='0')all[i]-=1;else if (all[i]=='a' | all[i]=='A')all[i]+=25;else if ((all[i]<'z' && all[i]>='a')||(all[i]<'Z' && all[i]>='A'))all[i]-=1;}try(FileWriter fw = new FileWriter(decodedFile)){fw.write(all);}}catch(IOException e){e.printStackTrace();}
}
编码方式
工作后经常接触的编码方式有如下几种:
ISO-8859-1 ASCII 数字和西欧字母
GBK GB2312 BIG5 中文
UNICODE (统一码,万国码)
其中
ISO-8859-1 包含 ASCII
GB2312 是简体中文,BIG5是繁体中文,GBK同时包含简体和繁体以及日文。
UNICODE 包括了所有的文字,无论中文,英文,藏文,法文,世界所有的文字都包含其中
两个16进制一个字节,8个对应4个字节
比如在ISO-8859-1中,a 字符对应的数字是0x61
而UNICODE中对应的数字是 0x00000061,倘若一篇文章大部分都是英文字母,那么按照UNICODE的方式进行数据保存就会消耗很多空间
在这种情况下,就出现了UNICODE的各种减肥子编码, 比如UTF-8对数字和字母就使用一个字节,而对汉字就使用3个字节,从而达到了减肥还能保证健康的效果
UTF-8,UTF-16和UTF-32 针对不同类型的数据有不同的减肥效果,一般说来UTF-8是比较常用的方式
package stream;import java.io.UnsupportedEncodingException;public class TestStream {public static void main(String[] args) {String str = "中";showCode(str);}private static void showCode(String str) {String[] encodes = { "BIG5", "GBK", "GB2312", "UTF-8", "UTF-16", "UTF-32" };for (String encode : encodes) {showCode(str, encode);}}private static void showCode(String str, String encode) {try {System.out.printf("字符: \"%s\" 的在编码方式%s下的十六进制值是%n", str, encode);byte[] bs = str.getBytes(encode);for (byte b : bs) {int i = b&0xff;System.out.print(Integer.toHexString(i) + "\t");}System.out.println();System.out.println();} catch (UnsupportedEncodingException e) {System.out.printf("UnsupportedEncodingException: %s编码方式无法解析字符%s\n", encode, str);}}
}
文件的编码方式-记事本
字符保存在文件中肯定也是以数字形式保存的,即对应在不同的棋盘上的不同的数字
用记事本打开任意文本文件,并且另存为,就能够在编码这里看到一个下拉。
ANSI 这个不是ASCII的意思,而是采用本地编码**的意思。如果你是中文的操作系统,就会使GBK,如果是英文的就会是ISO-8859-1
Unicode UNICODE原生的编码方式
Unicode big endian 另一个 UNICODE编码方式
UTF-8 最常见的UTF-8编码方式,数字和字母用一个字节, 汉字用3个字节。
byte[] bs = str.getBytes("UTF-8");String str = new String(bs,"GBK");int i = b&0xff;System.out.print(Integer.toHexString(i) + "\t");
FileReader得到的是字符,所以一定是已经把字节根据某种编码识别成了字符了
而FileReader使用的编码方式是Charset.defaultCharset()的返回值,如果是中文的操作系统,就是GBK
FileReader是不能手动设置编码方式的,为了使用其他的编码方式,只能使用InputStreamReader来代替,像这样:
new InputStreamReader(new FileInputStream(f),Charset.forName(“UTF-8”));
File f = new File("E:\\project\\j2se\\src\\test.txt");System.out.println("默认编码方式:"+Charset.defaultCharset());//FileReader得到的是字符,所以一定是已经把字节根据某种编码识别成了字符了//而FileReader使用的编码方式是Charset.defaultCharset()的返回值,如果是中文的操作系统,就是GBKtry (FileReader fr = new FileReader(f)) {char[] cs = new char[(int) f.length()];fr.read(cs);System.out.printf("FileReader会使用默认的编码方式%s,识别出来的字符是:%n",Charset.defaultCharset());System.out.println(new String(cs));} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}//FileReader是不能手动设置编码方式的,为了使用其他的编码方式,只能使用InputStreamReader来代替//并且使用new InputStreamReader(new FileInputStream(f),Charset.forName("UTF-8")); 这样的形式try (InputStreamReader isr = new InputStreamReader(new FileInputStream(f),Charset.forName("UTF-8"))) {char[] cs = new char[(int) f.length()];isr.read(cs);System.out.printf("InputStreamReader 指定编码方式UTF-8,识别出来的字符是:%n");System.out.println(new String(cs));} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}
缓存流
以介质是硬盘为例,字节流和字符流的弊端:
在每一次读写的时候,都会访问硬盘。 如果读写的频率比较高的时候,其性能表现不佳。
为了解决以上弊端,采用缓存流。
缓存流在读取的时候,会一次性读较多的数据到缓存中,以后每一次的读取,都是在缓存中访问,直到缓存中的数据读取完毕,再到硬盘中读取。
缓存流在写入数据的时候,会先把数据写入到缓存区,直到缓存区达到一定的量,才把这些数据,一起写入到硬盘中去。按照这种操作模式,就不会像字节流,字符流那样每写一个字节都访问硬盘,从而减少了IO操作
缓存字符输入流 BufferedReader 可以一次读取一行数据
创建文件字符流
缓存流必须建立在一个存在的流的基础上
public static void main(String[] args) {// 准备文件lol.txt其中的内容是// garen kill teemo// teemo revive after 1 minutes// teemo try to garen, but killed againFile f = new File("d:/lol.txt");// 创建文件字符流// 缓存流必须建立在一个存在的流的基础上try (FileReader fr = new FileReader(f);BufferedReader br = new BufferedReader(fr);){while (true) {// 一次读一行String line = br.readLine();if (null == line)break;System.out.println(line);}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
PrintWriter 缓存字符输出流, 可以一次写出一行数据
缓存流必须建立在一个存在的流的基础上
public static void main(String[] args) {// 向文件lol2.txt中写入三行语句File f = new File("d:/lol2.txt");try (// 创建文件字符流FileWriter fw = new FileWriter(f);// 缓存流必须建立在一个存在的流的基础上 PrintWriter pw = new PrintWriter(fw); ) {pw.println("garen kill teemo");pw.println("teemo revive after 1 minutes");pw.println("teemo try to garen, but killed again");} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
有的时候,需要立即把数据写入到硬盘,而不是等缓存满了才写出去。 这时候就需要用到flush
try(FileWriter fr = new FileWriter(f);PrintWriter pw = new PrintWriter(fr);) {pw.println("garen kill teemo");//强制把缓存中的数据写入硬盘,无论缓存是否已满pw.flush(); pw.println("teemo revive after 1 minutes");pw.flush();pw.println("teemo try to garen, but killed again");pw.flush();}...
移除文件中的注释
public static void removeComments(File javaFile){StringBuilder sb = new StringBuilder();//定义字符串容器//读取数据try(FileReader fr = new FileReader(javaFile);BufferedReader br = new BufferedReader(fr);){while(br.ready()){String str = br.readLine(); //读取数据if(str.trim().startsWith("//")){ //过滤单行注释continue;}sb.append(str + "\n"); //添加到字符串容器}} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}//写入数据try(FileWriter fw = new FileWriter(javaFile);PrintWriter pw = new PrintWriter(fw);){pw.write(sb.toString()); //写入读取数据} catch (IOException e) {e.printStackTrace();}}
数据流
DataInputStream 数据输入流
DataOutputStream 数据输出流
使用数据流的writeUTF()和readUTF() 可以进行数据的格式化顺序读写
如本例,通过DataOutputStream 向文件顺序写出 布尔值,整数和字符串。 然后再通过DataInputStream 顺序读入这些数据。
注: 要用DataInputStream 读取一个文件,这个文件必须是由DataOutputStream 写出的,否则会出现EOFException,因为DataOutputStream 在写出的时候会做一些特殊标记,只有DataInputStream 才能成功的读取。
public static void main(String[] args) {write();read();}private static void read() {File f =new File("d:/lol.txt");try (FileInputStream fis = new FileInputStream(f);DataInputStream dis =new DataInputStream(fis);){boolean b= dis.readBoolean();int i = dis.readInt();String str = dis.readUTF();System.out.println("读取到布尔值:"+b);System.out.println("读取到整数:"+i);System.out.println("读取到字符串:"+str);} catch (IOException e) {e.printStackTrace();}}private static void write() {File f =new File("d:/lol.txt");try (FileOutputStream fos = new FileOutputStream(f);DataOutputStream dos =new DataOutputStream(fos);){dos.writeBoolean(true);dos.writeInt(300);dos.writeUTF("123 this is gareen");} catch (IOException e) {e.printStackTrace();}}
对象流
对象流指的是可以直接把一个对象以流的形式传输给其他的介质,比如硬盘
对象的所有属性都打包发送
一个对象以流的形式进行传输,叫做序列化。 该对象所对应的类,必须是实现Serializable接口
**注:**把一个对象序列化有一个前提是:这个对象的类,必须实现了Serializable接口
package charactor;import java.io.Serializable;public class Hero implements Serializable {//表示这个类当前的版本,如果有了变化,比如新设计了属性,就应该修改这个版本号private static final long serialVersionUID = 1L;public String name;public float hp;}package stream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;import charactor.Hero;public class TestStream {public static void main(String[] args) {//创建一个Hero garen//要把Hero对象直接保存在文件上,务必让Hero类实现Serializable接口Hero h = new Hero();h.name = "garen";h.hp = 616;//准备一个文件用于保存该对象File f =new File("d:/garen.lol");try(//创建对象输出流FileOutputStream fos = new FileOutputStream(f);ObjectOutputStream oos =new ObjectOutputStream(fos);//创建对象输入流 FileInputStream fis = new FileInputStream(f);ObjectInputStream ois =new ObjectInputStream(fis);) {oos.writeObject(h);Hero h2 = (Hero) ois.readObject();System.out.println(h2.name);System.out.println(h2.hp);} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (ClassNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
package com.wang.IO;
import com.wang.IO.Hero;import java.io.*;
/*
准备一个长度是10,类型是Hero的数组,使用10个Hero对象初始化该数组然后把该数组序列化到一个文件heros.lol接着使用ObjectInputStream 读取该文件,并转换为Hero数组,验证该数组中的内容,是否和序列化之前一样*/
public class ObjectStream {public static void main(String[] args) {Hero[] hs = new Hero[10];for (int i = 0; i < hs.length; i++) {hs[i] = new Hero(new String("hero"+i),600);}File f = new File("D:/test.txt");try(FileOutputStream fos = new FileOutputStream(f);ObjectOutputStream oos = new ObjectOutputStream(fos);FileInputStream fis = new FileInputStream(f);ObjectInputStream ois = new ObjectInputStream(fis);){oos.writeObject(hs);Hero[] h2 = (Hero[])ois.readObject();for (Hero hero : h2) {System.out.println(hero.name);System.out.println(hero.hp);System.out.println(hero.money);}}catch (IOException e){e.printStackTrace();}catch(ClassNotFoundException e){e.printStackTrace();}}
}
System.in
System.out 是常用的在控制台输出数据的
System.in 可以从控制台输入数据
package stream;import java.io.IOException;
import java.io.InputStream;public class TestStream {public static void main(String[] args) {// 控制台输入try (InputStream is = System.in;) {while (true) {// 敲入a,然后敲回车可以看到// 97 13 10// 97是a的ASCII码// 13 10分别对应回车换行int i = is.read();System.out.println(i);}} catch (IOException e) {e.printStackTrace();}}
自动创建有一个属性的类文件。
通过控制台,获取类名,属性名称,属性类型,根据一个模板文件,自动创建这个类文件,并且为属性提供setter和getter
package stream;import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Scanner;public class TestStream {public static void main(String[] args) {// 接受客户输入Scanner s = new Scanner(System.in);System.out.println("请输入类的名称:");String className = s.nextLine();System.out.println("请输入属性的类型:");String type = s.nextLine();System.out.println("请输入属性的名称:");String property = s.nextLine();String Uproperty = toUpperFirstLetter(property);// 读取模版文件File modelFile = new File("E:\\project\\j2se\\src\\Model.txt");String modelContent = null;try (FileReader fr = new FileReader(modelFile)) {char cs[] = new char[(int) modelFile.length()];fr.read(cs);modelContent = new String(cs);} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();} //替换String fileContent = modelContent.replaceAll("@class@", className);fileContent = fileContent.replaceAll("@type@", type);fileContent = fileContent.replaceAll("@property@", property);fileContent = fileContent.replaceAll("@Uproperty@", Uproperty);String fileName = className+".java";//替换后的内容System.out.println("替换后的内容:");System.out.println(fileContent);File file = new File("E:\\project\\j2se\\src",fileName);try(FileWriter fw =new FileWriter(file);){fw.write(fileContent);} catch (IOException e) {e.printStackTrace();}System.out.println("文件保存在:" + file.getAbsolutePath());}public static String toUpperFirstLetter(String str){char upperCaseFirst =Character.toUpperCase(str.charAt(0));String rest = str.substring(1);return upperCaseFirst + rest;}
}
ArrayList
为了解决数组的局限性,引入容器类的概念。 最常见的容器类就是
ArrayList
容器的容量"capacity"会随着对象的增加,自动增长
只需要不断往容器里增加英雄即可,不用担心会出现数组的边界问题。
方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9O031LZe-1633015948494)(C:\Users\15021\AppData\Roaming\Typora\typora-user-images\image-20210922224739825.png)]
add 有两种用法
第一种是直接add对象,把对象加在最后面
heros.add(new Hero("hero " + i));
第二种是在指定位置加对象
heros.add(3, specialHero);
public static void main(String[] args) {ArrayList heros = new ArrayList();// 把5个对象加入到ArrayList中for (int i = 0; i < 5; i++) {heros.add(new Hero("hero " + i));}System.out.println(heros);// 在指定位置增加对象Hero specialHero = new Hero("special hero");heros.add(3, specialHero);System.out.println(heros.toString());}
通过方法contains 判断一个对象是否在容器中
判断标准: 是否是同一个对象,而不是name是否相同
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BRJUYfc2-1633015948495)(C:\Users\15021\AppData\Roaming\Typora\typora-user-images\image-20210922225134583.png)]
通过get获取指定位置的对象,如果输入的下标越界,一样会报错
indexOf用于判断一个对象在ArrayList中所处的位置
与contains一样,判断标准是对象是否相同,而非对象的name值是否相等
remove用于把对象从ArrayList中删除
remove可以根据下标删除ArrayList的元素
heros.remove(2);
也可以根据对象删除
heros.remove(specialHero);
set用于替换指定位置的元素
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-guyWB3jG-1633015948496)(C:\Users\15021\AppData\Roaming\Typora\typora-user-images\image-20210922225432468.png)]
size 用于获取ArrayList的大小
heros.size();
toArray可以把一个ArrayList对象转换为数组。
需要注意的是,如果要转换为一个Hero数组,那么需要传递一个Hero数组类型的对象给toArray(),这样toArray方法才知道,你希望转换为哪种类型的数组,否则只能转换为Object数组
package collection;import java.util.ArrayList;import charactor.Hero;public class TestCollection {public static void main(String[] args) {ArrayList heros = new ArrayList();// 初始化5个对象for (int i = 0; i < 5; i++) {heros.add(new Hero("hero " + i));}Hero specialHero = new Hero("special hero");heros.add(specialHero);System.out.println(heros);Hero hs[] = (Hero[])heros.toArray(new Hero[]{});System.out.println("数组:" +hs);}
}
addAll 把另一个容器所有对象都加进来
heros.addAll(anotherHeros);
clear 清空一个ArrayList
heros.clear();
List接口
ArrayList实现了接口List
常见的写法会把引用声明为接口List类型
注意:是java.util.List,而不是java.awt.List
public static void main(String[] args) {//ArrayList实现了接口List//常见的写法会把引用声明为接口List类型//注意:是java.util.List,而不是java.awt.List//接口引用指向子类对象(多态)List heros = new ArrayList();heros.add( new Hero("盖伦"));System.out.println(heros.size());}
泛型Generic
不指定泛型的容器,可以存放任何类型的元素
指定了泛型的容器,只能存放指定类型的元素以及其子类
public static void main(String[] args) {//对于不使用泛型的容器,可以往里面放英雄,也可以往里面放物品List heros = new ArrayList();heros.add(new Hero("盖伦"));//本来用于存放英雄的容器,现在也可以存放物品了heros.add(new Item("冰杖"));//对象转型会出现问题Hero h1= (Hero) heros.get(0);//尤其是在容器里放的对象太多的时候,就记不清楚哪个位置放的是哪种类型的对象了Hero h2= (Hero) heros.get(1);//引入泛型Generic//声明容器的时候,就指定了这种容器,只能放Hero,放其他的就会出错List<Hero> genericheros = new ArrayList<Hero>();genericheros.add(new Hero("盖伦"));//如果不是Hero类型,根本就放不进去//genericheros.add(new Item("冰杖"));//除此之外,还能存放Hero的子类genericheros.add(new APHero());//并且在取出数据的时候,不需要再进行转型了,因为里面肯定是放的Hero或者其子类Hero h = genericheros.get(0);}
为了不使编译器出现警告,需要前后都使用泛型,像这样:
List genericheros = new ArrayList();
不过JDK7提供了一个可以略微减少代码量的泛型简写方式
List genericheros2 = new ArrayList<>();
后面的泛型可以用<>来代替,聊胜于无吧
遍历
for循环遍历
// 第一种遍历 for循环System.out.println("--------for 循环-------");for (int i = 0; i < heros.size(); i++) {Hero h = heros.get(i);System.out.println(h);}
使用迭代器Iterator遍历集合中的元素
for (Iterator iterator = heros.iterator(); iterator.hasNext()
how2j学习总结-未完相关推荐
- pythonb超分辨成像_Papers | 超分辨 + 深度学习(未完待续)
1. SRCNN 1.1. Contribution end-to-end深度学习应用在超分辨领域的开山之作(非 end-to-end 见 Story.3 ). 指出了超分辨方向上传统方法( spar ...
- Linux学习总结 (未完待续...)
Linux学习总结: 1.用户管理部分 a,用户与组配置文件 a1.与用户和组相关的配置文件:passwd,shadow group,gshadow a2.超级权限控制Sudo的配置文件:/etc/s ...
- c++课程学习(未完待续)
关于c++课程学习 按照计划,我首先阅读谭浩强c++程序设计一书的ppt,发现第一章基本上都是很基础的东西. 同时,书中与班导师一样,推荐了使用visual c++. 而师爷的教程里面推荐使用的是ec ...
- Multimodal Deep Learning(多模态深度学习)未完待续
摘要: 本文提出一种在深度网络上的新应用,用深度网络学习多模态.特别的是,我们证明了跨模态特征学习--如果在特征学习过程中多模态出现了,对于一个模态而言,更好的特征可以被学习(多模态上学习,单模态上测 ...
- 个体软件过程(PSP)学习笔记 (未完)
个体软件过程 前言 软件工程漫谈 软件工程认识观 标准定义 将系统化的.规范的.可度量的方法应用于软件的开发.运行和维护的过程,即将工程化应用于软件中;以上所述方法的研究 软件开发管理 项目管理是基础 ...
- linux安装java学习环境(未完待续)
linux安装java学习环境 数据库连接命令 连接mysql数据库 格式为: mysql -hip地址 -p端口号 -u root -p 输入密码例如: mysql -hlocalhost -p33 ...
- 【iOS开发】—— SDWebImage源码学习(未完)
文章目录 什么是SDWebImage? sd_setImageWithURL调用关系 步骤一 步骤三 步骤四 步骤五 步骤六 下载步骤 UIImageView+ WebCache UIView+ We ...
- nmap学习记录(未完待续)
这里写自定义目录标题 声明! 学习内容 一.端口查询 二.服务指纹 三.局域网探测 reference 声明! 请勿从事违法行为! 学习内容 一.端口查询 原理 常规扫描 nmap scanme.nm ...
- XSS学习笔记(未完)
XSS基础学习 1. XSS 1.1 客户端Cookie: 1.2 XSS攻击类型 1.3 工具/平台 1.4 利用方式 1.4.1 非手工方式 1.4.1.1自动化攻击:beef 1.4.2 手工方 ...
最新文章
- 程序员奶爸用树莓派制作婴儿监护仪:哭声自动通知,还能分析何时喂奶
- UVA 11491 Erasing and Winning 奖品的价值 (贪心)
- python flask 配置处理
- 高德地图哪个语音包最好_高德地图妲己语音
- Python中super()和__init__()方法
- 【Java类加载机制】深入加载器
- 线程的局部变量ThreadLocal概念
- FreeBSD 10 将使用 Clang 编译器替换 GCC
- Twitter Storm安装配置(Ubuntu系统)单机版
- JDK源码(15)-Class
- Silverlight 中文教程第四部分:使用 Style 元素更好地封装观感 (木野狐译)
- python调用hive与java调用区别_使用Pyhive调用
- linux 命令是什么的缩写,Linux一部分命令解释(命令缩写代表什么意思)
- 百度搜索查找关键词技巧-信息收集能力
- (伪)原创,采集工具应用
- 2.1 A k-armed Bandit Problem
- ECM 手机MIC电路简单设计描述
- 汇编语言课程设计动态图形设计小车动态图形
- 论文投稿变量书写格式汇总
- 操作系统真象还原第5章:保护模式进阶,向内核进阶
热门文章
- app测试、web测试-怎么测?
- 用html做一个旅游网首页
- Python数据可视化第 2 讲:matplotlib 绘图中文字体设置
- 100+文档格式预览,私有化部署,10分钟搭建专属IM
- 网络基础知识 TCP UDP IP
- Android利用Java反射获取用户手机的rom定制系统及版本,EMUI,MIUI,ColorOS,FunthouchOS等
- FIX协议介绍与QuickFIX使用入门(上)
- [POI2014]DOO-Around the world
- 轻微课学画画好不好:我的体验反馈
- 基础的http协议构成