Markdown
标题
#+空格+ 一级标题
字体
粗体 %两边两个*
斜体 %两边一个*
斜体+加粗 %两边三个*
删除线 %两边两个~
引用
文本引用效果 %用>+内容
分割线
%三个- 或者三个~
图片
% ! + [] + ()
超链接
点击跳转
%[] + ()
列表
-
A -
B %数字+点+空格
表格
名字|性别|生日
–|--|–|
张三|男|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配置
#打开应用
calc
mspaint
notepad
#ping 命令
ping www.google.com
#创建文件夹
md
#文件
cd 空格 >a.txt
del a.txt
rd
摩尔定律
第三代语言
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;
int decVal = 26;
int hexVal = 0x1a;
int oxVal = 032;
int binVal = 0b11010;
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;
System.out.println(i);
{
System.out.println(i);
int j = 6;
System.out.println(j);
}
System.out.println(j);
}
}
变量作用域
- 类变量:从属类
- 实例变量:在类里,从属于对象,如果不初始化,会变成默认值(基本类型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);
Variable variable = new Variable();
System.out.println(variable.str);
System.out.println(variable.age);
System.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;
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){
int i = 2;
System.out.println( i== 1 | i++ ==2 );
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));
System.out.println(Integer.toBinaryString(j));
System.out.println(i^j);
System.out.println(i^0);
System.out.println(i^i);
}
}
条件运算符 ?、:
扩展赋值运算符:+=、-=、*=、/=
Ctrl+D 在IDEA中复制当前行到下一行
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);
System.out.println(b+c);
System.out.println(c+d);
System.out.println(d+e);
System.out.println(d+f);
}
}
一元运算符
int b = a++;
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);
}
}
位运算是对二进制位的运算
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;
System.out.println(a+b);
System.out.println(""+a+b);
System.out.println(a+b+"");
}
}
三元运算符 条件运算符
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()){
String str = scanner.next();
System.out.println("输入的内容为:"+str);
}
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);
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 这样,看上去无论如何都会打印yes
public 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( ; ; ){}
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输出完会换行
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;
}
}
}
}
}
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) {
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:
for (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;
break;
}
}
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;
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){
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;
dataType arrayRefVar[];
nums = new int[10];
Java语言使用new操作符来创建数组:
dataType[] arrayRefVar = new dataType[arraySizw];
获取数组长度:
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;
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];
for (int i = 0; i < b.length; i++) {
b[i] = a[i];
}
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){
System.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++){
System.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 };
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);
System.out.println(Arrays.toString(a));
int a[] = new int[] { 18, 62, 68, 82, 65, 9 };
int[] b = Arrays.copyOfRange(a, 0, 3);
Arrays.sort(a);
System.out.println(Arrays.toString(a));
Arrays.fill(a,2,4,0);
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));
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) {
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;
for (int j = 0;j < array.length-1-i;j++){
if (array[j+1]<array[j]){
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("===============");
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);
int[][] a2=new int[sum+1][3];
a2[0][0]=a.length;
a2[0][1]=a[0].length;
a2[0][2]=sum;
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];
}
}
}
System.out.println("稀疏数组:");
arraysPrint(a2);
System.out.println("===============");
System.out.println("还原:");
int[][] a3= new int[a2[0][0]][a2[0][1]];
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) {
for (int anInt : ints) {
System.out.print(anInt+"\t");
}
System.out.println();
}
}
}
arrays.for遍历输出
面向对象编程OOP
引用的概念,如果一个变量的类型是 类类型,而非基本类型,那么该变量又叫做引用。
new Hero;
Hero h = new Hero();
Hero h2 = h;
一个引用,同一时间,只能指向一个对象。
属性(这里的属性应该包含了构造器的定义)加方法变成一个类。
**面向对象思想:**抽象。物以类聚,分类的思维方式。对于描述复杂的事物,为了从宏观上把握、从整体上合理分析,我们需要使用面向对象的思路来分析整个系统。但是,具体到微观操作,仍然需要面向过程的思路去处理。
面向对象编程的本质就是:以类的方式组织代码,以对象的组织(封装)数据。
三大特性:
- 封装
- 继承
- 多态
从认识论的角度:先有对象后有类。对象,是具体的事物。类,是抽象的,是对对象的抽象。
从代码运行的角度:先有类后有对象。类是对象的模板。
类与对象的创建
使用new关键字创建对象。
使用new关键字创建的时候,除了分配内存空间之外,还会给创建好的对象进行默认的初始化以及对类中构造器的调用。
package com.wang.oop;
public class Student {
String name;
int age;
public void study(){
System.out.println(this.name+"在学习");
}
}
package com.wang.oop;
public class Application {
public static void main(String[] args) {
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;
public Person(){
}
public Person(String name){
this.name =name;
}
public Person(String name, int age){
this.name = name;
this.age = age;
}
}
类的调用
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即代表当前对象,也就是调用方法的对象
public void setName1(String name){
name = name;
}
public void setName2(String heroName){
name = heroName;
}
public void setName3(String name){
this.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);
teemo.hp = teemo.hp - 400;
teemo.revive(teemo);
}
}
小结
-
类与对象 类是一个模板:抽象;对象是一个具体的实例 -
方法 定义、调用! -
对象的引用 引用类型:基本类型(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 String name;
private int id;
private char sex;
private int age;
public String getName(){
return this.name;
}
public void setName(String name){
this.name=name;
}
public 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;
}
}
}
继承
继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模。
extends:扩展。子类是父类的扩展。
Java中类只有单继承,没有多继承!
package com.wang.oop.demo06;
public class Person {
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;
}
}
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();
}
}
Super
public class Student extends Person{
public Student() {
super();
}
private String name = "da";
public void test(String name){
System.out.println(name);
System.out.println(this.name);
System.out.println(super.name);
}
}
package charactor;
public class Hero {
public String name;
protected float hp;
public Hero(String name){
this.name = name;
}
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
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{
@Override
public void physicAttack() {
System.out.println("进行物理攻击");
}
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(); 会调用父类的方法?还是子类的方法?
**父类引用指向子类对象: **
**静态方法, 引用是啥类型就输出啥类的方法; **
非静态方法, 对象是啥类型就输出啥类型的方法;
多态
操作符的多态 + 可以作为算数运算,也可以作为字符串连接
同一个操作符在不同情境下,具备不同的作用 如果+号两侧都是整型,那么**+代表 数字相加** 如果+号两侧,任意一个是字符串,那么**+代表字符串连接**
类的多态: 都是同一个类型,调用同一个方法,却能呈现不同的状态 父类引用指向子类对象
要实现类的多态,需要如下条件
- 父类(接口)引用指向子类对象
- 调用的方法有 重写
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 s1 = new Student();
Person s2 = new Student();
Object s3 = new Student();
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());
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++) {
h = new Hero();
}
}
}
equals
equals() 用于判断两个对象的内容是否相同
假设,当两个英雄的hp相同的时候,我们就认为这两个英雄相同
package charactor;
public class Hero {
public String name;
protected float hp;
public boolean equals(Object o){
if(o instanceof Hero){
Hero h = (Hero) o;
return this.hp == h.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;
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 {
@Override
public void sleep() {
System.out.println("Male sleep..");
}
}
class Female extends Human {
@Override
public void sleep() {
System.out.println("Female sleep..");
}
}
instanceof 和类型转换
用来测试一个对象是否为一个类的实例,用法为:
boolean result = obj instanceof Class
当 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 object = new Student();
System.out.println(object instanceof Student);
System.out.println(object instanceof Person);
System.out.println(object instanceof Object);
System.out.println(object instanceof Teacher);
System.out.println(object instanceof String);
System.out.println("=========================");
Person person = new Student();
System.out.println(person instanceof Student);
System.out.println(person instanceof Person);
System.out.println(person instanceof Object);
}
}
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;
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 = "盖伦";
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方法,返回第二步的静态属性
饿汉式单例模式
package com.wang.oop.demo02;
public class GaintDragon {
private GaintDragon(){
}
private static GaintDragon instance = new GaintDragon();
public static GaintDragon getInstance(){
return instance;
}
}
package com.wang.oop.demo02;
public class TestGiantDragon{
public static void main(String[] args){
GaintDragon g1 = GaintDragon.getInstance();
GaintDragon g2 = GaintDragon.getInstance();
GaintDragon g3 = GaintDragon.getInstance();
System.out.println(g1==g2);
System.out.println(g1==g3);
}
}
懒汉式单例模式
懒汉式单例模式与饿汉式单例模式不同,只有在调用getInstance的时候,才会创建实例
package charactor;
public class GiantDragon {
private GiantDragon(){
}
private static GiantDragon instance;
public static GiantDragon getInstance(){
if(null==instance){
instance = new GiantDragon();
}
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()) {
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) {
}
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{
@Override
public 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的 即便没有显式的声明 注: 抽象类和接口都可以有实体方法。 接口中的实体方法,叫做默认方法
int 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 + "超神!");
else
System.out.println(name + "尚未超神!");
}
}
public static void main(String[] args) {
Hero garen = new Hero();
garen.name = "盖伦";
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;
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.attack();
System.out.println(adh);
Hero h = new Hero(){
public void attack() {
System.out.println("新的进攻手段");
}
};
h.attack();
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接口的类,都需要做改动。
但是引入了默认方法后,原来的类,不需要做任何改动,并且还能得到这个默认方法
通过这种手段,就能够很好的扩展新的类,并且做到不影响原来的类
UML图
UML-Unified Module Language 统一建模语言,可以很方便的用于描述类的属性,方法,以及类和类之间的关系
类图
接口图
继承关系
带箭头的实线,表示 Spider,Cat, Fish都继承于Animal这个父类.
实现关系
表示 Fish实现了 Pet这个接口
继承与接口练习
题目:
\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) {
@Override
public 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);
}
@Override
public 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);
}
public Cat(){
this(" ",0);
}
@Override
public void eat() {
System.out.println(this.name+"吃鱼");
}
@Override
public String getName() {
System.out.println(name);
return name;
}
@Override
public void setName(String name) {
this.name=name;
}
@Override
public 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 it = new Integer(i);
int 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();
}
}
自动装箱
不需要调用构造方法,通过=符号 自动把 基本类型 转换为 类类型 就叫装箱
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) {
System.out.println(Integer.MAX_VALUE);
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;
Integer it3 = b;
byte b3 = it;
int i3 = by;
}
}
字符串转换
数字转字符串
方法1: 使用String类的静态方法valueOf 方法2: 先把基本类型装箱为对象,然后调用对象的toString
package digit;
public class TestNumber {
public static void main(String[] args) {
int i = 5;
String str = String.valueOf(i);
Integer 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;
System.out.println(Math.round(f1));
System.out.println(Math.round(f2));
System.out.println(Math.random());
System.out.println((int)( Math.random()*10));
System.out.println(Math.sqrt(9));
System.out.println(Math.pow(2,4));
System.out.println(Math.PI);
System.out.println(Math.E);
}
}
利用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) {
return false;
} else {
for (int i = 2; i <= Math.sqrt(a); i++) {
if (a % i == 0) {
flag = 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);
String sentenceFormat ="%s 在进行了连续 %d 次击杀后,获得了 %s 的称号%n";
System.out.printf(sentenceFormat,name,kill,title);
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;
System.out.format("%d%n",year);
System.out.format("%8d%n",year);
System.out.format("%-8d%n",year);
System.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';
char 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);
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) {
String 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;
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次击杀后,获得了 超神 的称号";
String subString1 = sentence.substring(3);
System.out.println(subString1);
String subString2 = sentence.substring(3,5);
System.out.println(subString2);
split 根据分隔符进行分隔
String sentence = "盖伦,在进行了连续8次击杀后,获得了 超神 的称号";
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));
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){
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]);
else
ch0[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);
System.out.println(str1.equals(str2));
System.out.println(str1.equals(str3));
System.out.println(str1.equalsIgnoreCase(str3));
}
}
是否以子字符串开始或者结束
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);
sb.append("be light");
System.out.println(sb);
sb.delete(4, 10);
System.out.println(sb);
sb.insert(4, "there ");
System.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();
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”这样的格式需要日期格式化
Date now= new Date();
System.out.println("当前时间:"+now.toString());
System.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 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 代表毫秒
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;
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 c = Calendar.getInstance();
Date d = c.getTime();
Date d2 = new Date(0);
c.setTime(d2);
}
}
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;
try{
System.out.println(a/b);
}catch (Error e){
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) {
int 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);
}
public 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");
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) {
e.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) {
System.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);
}
}
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) {
Account a = new Account(1000);
a.deposit(1000);
System.out.println(a.getBalance());
try {
a.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) {
CheckingAccount a = new CheckingAccount(1000, 500);
a.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());
File f2 = new File("LOL.exe");
System.out.println("f2的绝对路径:" + f2.getAbsolutePath());
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);
f.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);
InputStream是字节输入流,同时也是抽象类,只提供方法声明,不提供方法的具体实现。 FileInputStream 是InputStream子类,以FileInputStream 为例进行文件读取
try {
File 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) {
System.out.println(b);
}
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
OutputStream是字节输出流,同时也是抽象类,只提供方法声明,不提供方法的具体实现。 FileOutputStream 是OutputStream子类,以FileOutputStream 为例向文件写出数据
注: 如果文件d:/lol2.txt不存在,写出操作会自动创建该文件。 但是如果是文件 d:/xyz/lol2.txt,而目录xyz又不存在,会抛出异常
public static void main(String[] args) {
try {
File f = new File("d:/lol2.txt");
byte data[] = { 88, 89 };
FileOutputStream fos = new FileOutputStream(f);
fos.write(data);
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
public static void main(String[] args) {
try {
File f = new File("d:/xyz/abc/def/lol2.txt");
File dir = f.getParentFile();
if(!dir.exists()){
dir.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) {
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) {
e.printStackTrace();
}
int fileNumber;
if (0 == fileContent.length % eachSize)
fileNumber = (int) (fileContent.length / eachSize);
else
fileNumber = (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;
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) {
e.printStackTrace();
}
}
}
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) {
e.printStackTrace();
} catch (IOException e) {
e.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 {
if (null != fis)
try {
fis.close();
} catch (IOException e) {
e.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 (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) {
File f = new File("d:/lol.txt");
try (FileReader fr = new FileReader(f)) {
char[] all = new char[(int) f.length()];
fr.read(all);
for (char b : all) {
System.out.println(b);
}
} catch (IOException e) {
e.printStackTrace();
}
}
FileWriter 是Writer的子类,以FileWriter 为例把字符串写入到文件
public static void main(String[] args) {
File f = new File("d:/lol2.txt");
try (FileWriter fr = new FileWriter(f)) {
String data="abcdefg1234567890";
char[] cs = data.toCharArray();
fr.write(cs);
} catch (IOException e) {
e.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());
try (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) {
e.printStackTrace();
}
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) {
e.printStackTrace();
}
缓存流
以介质是硬盘为例,字节流和字符流的弊端: 在每一次读写的时候,都会访问硬盘。 如果读写的频率比较高的时候,其性能表现不佳。
为了解决以上弊端,采用缓存流。 缓存流在读取的时候,会一次性读较多的数据到缓存中,以后每一次的读取,都是在缓存中访问,直到缓存中的数据读取完毕,再到硬盘中读取。
缓存流在写入数据的时候,会先把数据写入到缓存区,直到缓存区达到一定的量,才把这些数据,一起写入到硬盘中去。按照这种操作模式,就不会像字节流,字符流那样每写一个字节都访问硬盘,从而减少了IO操作
缓存字符输入流 BufferedReader 可以一次读取一行数据
创建文件字符流 缓存流必须建立在一个存在的流的基础上
public static void main(String[] args) {
File 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) {
e.printStackTrace();
}
}
PrintWriter 缓存字符输出流, 可以一次写出一行数据
缓存流必须建立在一个存在的流的基础上
public static void main(String[] args) {
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) {
e.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 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) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
package com.wang.IO;
import com.wang.IO.Hero;
import java.io.*;
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) {
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) {
e.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();
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();
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) {
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);
List<Hero> genericheros = new ArrayList<Hero>();
genericheros.add(new Hero("盖伦"));
genericheros.add(new APHero());
Hero h = genericheros.get(0);
}
为了不使编译器出现警告,需要前后都使用泛型,像这样:
List genericheros = new ArrayList(); 不过JDK7提供了一个可以略微减少代码量的泛型简写方式
List genericheros2 = new ArrayList<>();
后面的泛型可以用<>来代替,聊胜于无吧
遍历
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()😉 {}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S87dovk9-1633015948498)(C:\Users\15021\AppData\Roaming\Typora\typora-user-images\image-20210922230633500.png)]
List<Hero> heros = new ArrayList<Hero>();
for (int i = 0; i < 5; i++) {
heros.add(new Hero("hero name " +i));
}
System.out.println("--------使用while的iterator-------");
Iterator<Hero> it= heros.iterator();
while(it.hasNext()){
Hero h = it.next();
System.out.println(h);
}
System.out.println("--------使用for的iterator-------");
for (Iterator<Hero> iterator = heros.iterator(); iterator.hasNext();) {
Hero hero = (Hero) iterator.next();
System.out.println(hero);
}
使用增强型for循环可以非常方便的遍历ArrayList中的元素,这是很多开发人员的首选。
不过增强型for循环也有不足: 无法用来进行ArrayList的初始化 无法得知当前是第几个元素了,当需要只打印单数元素的时候,就做不到了。 必须再自定下标变量。
System.out.println("--------增强型for循环-------");
for (Hero h : heros) {
System.out.println(h);
}
练习
首先初始化一个Hero集合,里面放100个Hero对象,名称分别是从 hero 0 hero 1 hero 2 … hero 99.
通过遍历的手段,删除掉名字编号是8的倍数的对象
public static void main(String[] args) {
List<Hero> heros = new ArrayList<>();
for (int i = 0; i < 100; i++) {
heros.add(new Hero("hero name "+i));
}
List<Hero> deletingHeros = new ArrayList<>();
for (Hero h : heros) {
int id = Integer.parseInt(h.name.substring(10));
if (0 == id % 8)
deletingHeros.add(h);
}
for (Hero h : deletingHeros) {
heros.remove(h);
}
System.out.println(heros);
-------------------------------------------------------------------------------------------------------------
Iterator<Hero> iterator = heros.iterator();
for (int i = 8; i <= 100 ; i += 8) {
String name = "hero " + i;
while(iterator.hasNext()){
Hero h = iterator.next();
if(h.name.equals(name)){
iterator.remove();
System.out.println(h.name+"已被删除");
break;
}
}
}
System.out.println(heros);
集合框架
序列分先进先出FIFO,先进后出FILO FIFO在Java中又叫Queue 队列 FILO在Java中又叫Stack 栈
LinkedList双向链表
Deque双向链表
除了实现了List接口外,LinkedList还实现了双向链表结构Deque,可以很方便的在头尾插入删除数据
什么是链表结构: 与数组结构相比较,数组结构,就好像是电影院,每个位置都有标示,每个位置之间的间隔都是一样的。 而链表就相当于佛珠,每个珠子,只连接前一个和后一个,不用关心除此之外的其他佛珠在哪里。
LinkedList<Hero> ll =new LinkedList<Hero>();
ll.addLast(new Hero("hero1"));
ll.addLast(new Hero("hero2"));
ll.addLast(new Hero("hero3"));
System.out.println(ll);
ll.addFirst(new Hero("heroX"));
System.out.println(ll);
System.out.println(ll.getFirst());
System.out.println(ll.getLast());
System.out.println(ll);
System.out.println(ll.removeFirst());
System.out.println(ll.removeLast());
System.out.println(ll);
Queue队列 先进先出FIFO
LinkedList 除了实现了List和Deque外,还实现了Queue接口(队列)。 Queue是先进先出队列 FIFO,常用方法: offer 在最后添加元素 poll 取出第一个元素 peek 查看第一个元素
List ll =new LinkedList<Hero>();
Queue<Hero> q= new LinkedList<Hero>();
System.out.print("初始化队列:\t");
q.offer(new Hero("Hero1"));
Hero h = q.poll();
System.out.println(h);
h=q.peek();
System.out.print("查看peek()第一个元素:\t");
FILO先进后出栈Stack
与FIFO(先入先出的)队列类似的一种数据结构是FILO先入后出栈Stack
push 在最后添加元素
poll 取出最后一个元素
peek 查看最后一个元素
二叉树
二叉树由各种节点组成 二叉树特点: 每个节点都可以有左子节点,右子节点 每一个节点都有一个值
二叉树排序
假设通过二叉树对如下10个随机数进行排序 67,7,30,73,10,0,78,81,10,74 排序的第一个步骤是把数据插入到该二叉树中 插入基本逻辑是,小、相同的放左边,大的放右边 \1. 67 放在根节点 \2. 7 比 67小,放在67的左节点 \3. 30 比67 小,找到67的左节点7,30比7大,就放在7的右节点 \4. 73 比67大, 放在67的右节点 \5. 10 比 67小,找到67的左节点7,10比7大,找到7的右节点30,10比30小,放在30的左节点。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ikSADnb1-1633015948500)(C:\Users\15021\AppData\Roaming\Typora\typora-user-images\image-20210925225110935.png)]
package collection;
public class Node {
public Node leftNode;
public Node rightNode;
public Object value;
public void add(Object v) {
if (null == value)
value = v;
else {
if ((Integer) v -((Integer)value) <= 0) {
if (null == leftNode)
leftNode = new Node();
leftNode.add(v);
}
else {
if (null == rightNode)
rightNode = new Node();
rightNode.add(v);
}
}
}
public static void main(String[] args) {
int randoms[] = new int[] { 67, 7, 30, 73, 10, 0, 78, 81, 10, 74 };
Node roots = new Node();
for (int number : randoms) {
roots.add(number);
}
}
}
二叉树排序:遍历
通过上一个步骤的插入行为,实际上,数据就已经排好序了。 接下来要做的是看,把这些已经排好序的数据,遍历成我们常用的List或者数组的形式
二叉树的遍历分左序,中序,右序 左序即: 中间的数遍历后放在左边 中序即: 中间的数遍历后放在中间 右序即: 中间的数遍历后放在右边 如图所见,我们希望遍历后的结果是从小到大的,所以应该采用中序遍历
public List<Object> values() {
List<Object> values = new ArrayList<>();
if (null != leftNode)
values.addAll(leftNode.values());
values.add(value);
if (null != rightNode)
values.addAll(rightNode.values());
return values;
}
HashMap
HashMap储存数据的方式是—— 键值对
package collection;
import java.util.HashMap;
public class TestCollection {
public static void main(String[] args) {
HashMap<String,String> dictionary = new HashMap<>();
dictionary.put("adc", "物理英雄");
dictionary.put("apc", "魔法英雄");
dictionary.put("t", "坦克");
System.out.println(dictionary.get("t"));
}
}
对于HashMap而言,key是唯一的,不可以重复的。 所以,以相同的key 把不同的value插入到 Map中会导致旧元素被覆盖,只留下最后插入的元素。 不过,同一个对象可以作为值插入到map中,只要对应的key不一样
heroMap.clear(); //清空map
keySet()可以获取所有的key, values()可以获取所有的value
package com.wang.arraylist;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import com.wang.oop.demo01.Hero;
public class HashMapTest {
public static void main(String[] args) {
List<Hero> heros = new ArrayList<>();
for (int i = 0; i < 300_0000; i++) {
heros.add(new Hero("hero-"+(int)(Math.random()*9000+1000)));
}
forFine(heros);
hashMap(heros);
}
public static void forFine(List h){
int num=0;
long start = System.currentTimeMillis();
for (int i = 0; i < h.size(); i++) {
Hero hero = (Hero) h.get(i);
if (hero.name.equals("hero-5555"))
num++;
}
long end = System.currentTimeMillis();
System.out.println("耗时:"+(end-start)+",共找到"+num+"个");
}
public static void hashMap(List<Hero> hs){
HashMap<String,List<Hero>> heroMap = new HashMap<>();
long start = System.currentTimeMillis();
for (Hero h : hs) {
List<Hero> list = heroMap.get(h.name);
if(list==null){
list = new ArrayList<>();
heroMap.put(h.name,list);
}
list.add(h);
}
List<Hero> result = heroMap.get("hero-5555");
long end = System.currentTimeMillis();
System.out.println("耗时:"+(end-start)+",共找到"+result.size()+"个");
}
}
HashSet
Set中的元素,不能重复
Set中的元素,没有顺序。 严格的说,是没有按照元素的插入顺序排列
HashSet的具体顺序,既不是按照插入顺序,也不是按照hashcode的顺序。
package collection;
import java.util.HashSet;
public class TestCollection {
public static void main(String[] args) {
HashSet<String> names = new HashSet<String>();
names.add("gareen");
System.out.println(names);
names.add("gareen");
System.out.println(names);
}
}
Set不提供get()来获取指定位置的元素 所以遍历需要用到迭代器,或者增强型for循环
package collection;
import java.util.HashSet;
import java.util.Iterator;
public class TestCollection {
public static void main(String[] args) {
HashSet<Integer> numbers = new HashSet<Integer>();
for (int i = 0; i < 20; i++) {
numbers.add(i);
}
for (Iterator<Integer> iterator = numbers.iterator(); iterator.hasNext();) {
Integer i = (Integer) iterator.next();
System.out.println(i);
}
for (Integer i : numbers) {
System.out.println(i);
}
}
}
HashSet和HashMap的关系
HashSet自身并没有独立的实现,而是在里面封装了一个Map. HashSet是作为Map的key而存在的 而value是一个命名为PRESENT的static的Object对象,因为是一个类属性,所以只会有一个。
private static final Object PRESENT = new Object();
package com.wang.arraylist;
import java.util.HashSet;
import java.util.Random;
public class Demo02 {
public static void main(String[] args) {
String[] str = randomString(100);
for (int i = 0;i<str.length;i++) {
if (i>0 && i%20==0)
System.out.println();
System.out.print(str[i]+" ");
}
HashSet<String> strs = new HashSet<>();
HashSet<String> doubleStrs = new HashSet<>();
for (String s : str) {
if(!strs.add(s))
doubleStrs.add(s);
}
System.out.println();
System.out.println("重复的有:"+(str.length-strs.size()));
for (String s : doubleStrs) {
System.out.print(s+" ");
}
}
public static String[] randomString(int length){
String[] str = new String[length];
char[] c = new char[2];
for (int i = 0; i < length; i++) {
c[0] = (char)randomInt();
c[1] = (char)randomInt();
str[i] = new String(c);
}
return str;
}
public static int randomInt(){
Random rd = new Random();
int num = 0;
num = rd.nextInt(75)+48;
if((num>=58 && num<=64) || (num>=91 && num<=96)){
num = randomInt();
}else {
return num;
}
return num;
}
}
Collection
Collection是 Set List Queue和 Deque的接口 Queue: 先进先出队列 Deque: 双向链表
**注:**Collection和Map之间没有关系,Collection是放一个一个对象的,Map 是放键值对的 **注:**Deque 继承 Queue,间接的继承了 Collection
LinkedList是双向链表,它实现了Deque接口,也就是双向对列接口
接口只能继承接口,不能实现,继承接口后可以增加新的方法。
Collections
Collections是一个类,容器的工具类,就如同Arrays是数组的工具类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Maw5MIQD-1633015948503)(C:\Users\15021\AppData\Roaming\Typora\typora-user-images\image-20210927222338867.png)]
public class TestCollection {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>();
for (int i = 0; i < 10; i++) {
numbers.add(i);
}
System.out.println("集合中的数据:");
System.out.println(numbers);
Collections.reverse(numbers);
System.out.println("翻转后集合中的数据:");
System.out.println(numbers);
}
}
Collections.shuffle(numbers);
System.out.println("混淆后集合中的数据:");
Collections.sort(numbers);
System.out.println("排序后集合中的数据:");
Collections.swap(numbers,0,5);
System.out.println("交换0和5下标的数据后,集合中的数据:");
Collections.rotate(numbers,2);
System.out.println("把集合向右滚动2个单位,标的数据后,集合中的数据:");
System.out.println("把非线程安全的List转换为线程安全的List");
List<Integer> synchronizedNumbers = (List<Integer>) Collections.synchronizedList(numbers);
集合框架的联系与区别
ArrayList vs HashSet
ArrayList: 有顺序 HashSet: 无顺序
HashSet的具体顺序,既不是按照插入顺序,也不是按照hashcode的顺序。
不保证Set的迭代顺序; 确切的说,在不同条件下,元素的顺序都有可能不一样
换句话说,同样是插入0-9到HashSet中, 在JVM的不同版本中,看到的顺序都是不一样的。 所以在开发的时候,不能依赖于某种臆测的顺序,这个顺序本身是不稳定的.
List中的数据可以重复 Set中的数据不能够重复 重复判断标准是: 首先看hashcode是否相同 如果hashcode不同,则认为是不同数据 如果hashcode相同,再比较equals,如果equals相同,则是相同数据,否则是不同数据
ArrayList vs LinkedList
ArrayList 插入,删除数据慢 LinkedList, 插入,删除数据快 ArrayList是顺序结构,所以定位很快,指哪找哪。 就像电影院位置一样,有了电影票,一下就找到位置了。 LinkedList 是链表结构,就像手里的一串佛珠,要找出第99个佛珠,必须得一个一个的数过去,所以定位慢
100000组数据在最后插入和中间插入整数都是ArrayList比较快,
用add插入时如果位置比较靠前linkedlist会比较快
HashMap vs Hashtable
HashMap和Hashtable都实现了Map接口,都是键值对保存数据的方式 区别1: HashMap可以存放 null Hashtable不能存放null 区别2: HashMap不是线程安全的类 Hashtable是线程安全的类
HashMap<String,String> hashMap = new HashMap<String,String>();
hashMap.put(null, "123");
hashMap.put("123", null);
Hashtable<String,String> hashtable = new Hashtable<String,String>();
hashtable.put(null, "123");
hashtable.put("123", null);
几种set
HashSet: 无序 LinkedHashSet: 按照插入顺序 TreeSet: 从小到大排序
HashSet<Integer> numberSet1 =new HashSet<Integer>();
numberSet1.add(88);
numberSet1.add(8);
numberSet1.add(888);
System.out.println(numberSet1);
LinkedHashSet<Integer> numberSet2 =new LinkedHashSet<Integer>();
numberSet2.add(88);
numberSet2.add(8);
numberSet2.add(888);
System.out.println(numberSet2);
TreeSet<Integer> numberSet3 =new TreeSet<Integer>();
numberSet3.add(88);
numberSet3.add(8);
numberSet3.add(888);
System.out.println(numberSet3);
HashMap原理与字典
在展开HashMap原理的讲解之前,首先回忆一下大家初中和高中使用的汉英字典。
比如要找一个单词对应的中文意思,假设单词是Lengendary,首先在目录找到Lengendary在第 555页。
然后,翻到第555页,这页不只一个单词,但是量已经很少了,逐一比较,很快就定位目标单词Lengendary。
555相当于就是Lengendary对应的hashcode
-----hashcode概念----- 所有的对象,都有一个对应的hashcode(散列值) 比如字符串“gareen”对应的是1001 (实际上不是,这里是方便理解,假设的值) 比如字符串“temoo”对应的是1004 比如字符串“db”对应的是1008 比如字符串“annie”对应的也****是1008
-----保存数据----- 准备一个数组,其长度是2000,并且设定特殊的hashcode算法,使得所有字符串对应的hashcode,都会落在0-1999之间 要存放名字是"gareen"的英雄,就把该英雄和名称组成一个键值对,存放在数组的1001这个位置上 要存放名字是"temoo"的英雄,就把该英雄存放在数组的1004这个位置上 要存放名字是"db"的英雄,就把该英雄存放在数组的1008这个位置上 要存放名字是"annie"的英雄,然而 "annie"的hashcode 1008对应的位置已经有db英雄了,那么就在这里创建一个链表,接在db英雄后面存放annie
-----查找数据----- 比如要查找gareen,首先计算"gareen"的hashcode是1001,根据1001这个下标,到数组中进行定位,(根据数组下标进行定位,是非常快速的) 发现1001这个位置就只有一个英雄,那么该英雄就是gareen. 比如要查找annie,首先计算"annie"的hashcode是1008,根据1008这个下标,到数组中进行定位,发现1008这个位置有两个英雄,那么就对两个英雄的名字进行逐一比较(equals),因为此时需要比较的量就已经少很多了,很快也就可以找出目标英雄 这就是使用hashmap进行查询,非常快原理。
这是一种用空间换时间的思维方式
HashSet的数据是不能重复的,相同数据不能保存在一起,到底如何判断是否是重复的呢? 根据HashSet和HashMap的关系,我们了解到因为HashSet没有自身的实现,而是里面封装了一个HashMap,所以本质上就是判断HashMap的key是否重复。
再通过上一步的学习,key是否重复,是由两个步骤判断的: hashcode是否一样 如果hashcode不一样,就是在不同的坑里,一定是不重复的 如果hashcode一样,就是在同一个坑里,还需要进行equals比较 如果equals一样,则是重复数据 如果equals不一样,则是不同数据。
public static int hashcode(String str){
if (0 == str.length())
return 0;
int num = 0;
char[] c = str.toCharArray();
for (int i = 0; i < c.length; i++) {
num+=c[i];
}
num = num * 32;
num = num<0?0-num:num;
num%=2000;
return num;
}
比较器 Comparator
假设Hero有三个属性 name,hp,damage 一个集合中放存放10个Hero,通过Collections.sort对这10个进行排序 那么到底是hp小的放前面?还是damage小的放前面?Collections.sort也无法确定 所以要指定到底按照哪种属性进行排序 这里就需要提供一个Comparator给定如何进行两个对象之间的大小比较
Collections.sort(heros,c); //重写的比较器大于时正序,小于时倒序
public static void main(String[] args) {
Random r =new Random();
List<Hero> heros = new ArrayList<Hero>();
for (int i = 0; i < 10; i++) {
heros.add(new Hero("hero "+ i, r.nextInt(100), r.nextInt(100)));
}
System.out.println("初始化后的集合:");
System.out.println(heros);
Comparator<Hero> c = new Comparator<Hero>() {
@Override
public int compare(Hero h1, Hero h2) {
if(h1.hp>=h2.hp)
return 1;
else
return -1;
}
};
Collections.sort(heros,c);
System.out.println("按照血量排序后的集合:");
System.out.println(heros);
}
Comparable
使Hero类实现Comparable接口 在类里面提供比较算法 Collections.sort就有足够的信息进行排序了,也无需额外提供比较器Comparator 注: 如果返回-1, 就表示当前的更小,否则就是更大;重写的大于时返回1正序,小于时倒序
public class Hero implements Comparable<Hero>{
@Override
public int compareTo(Hero anotherHero) {
if(damage<anotherHero.damage)
return 1;
else
return -1;
}
练习
public static void main(String[] args) {
TreeSet<Integer> nums = new TreeSet<>();
for (int i = 0 ; i < 10 ; i++){
nums.add((int)(Math.random()*100));
}
System.out.println(nums);
Comparator<Integer> c = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
if (o1 < o2)
return 1;
else
return -1;
}
};
TreeSet<Integer> num = new TreeSet<>(c);
for (int i = 0 ; i < 10 ; i++){
num.add((int)(Math.random()*100));
}
System.out.println(num);
}
聚合操作
JDK8之后,引入了对集合的聚合操作,可以非常容易的遍历,筛选,比较集合中的元素。
像这样:
? String name =heros
? .stream()
? .sorted((h1,h2)->h1.hp>h2.hp?-1:1)
? .skip(2)
? .map(h->h.getName())
? .findFirst()
? .get();
但是要用好聚合,必须先掌握Lambda表达式,聚合的章节讲放在Lambda与聚合操作部分详细讲解
public static void main(String[] args) {
Random r = new Random();
List<Hero> heros = new ArrayList<Hero>();
for (int i = 0; i < 10; i++) {
heros.add(new Hero("hero " + i, r.nextInt(1000), r.nextInt(100)));
}
System.out.println("初始化集合后的数据 (最后一个数据重复):");
System.out.println(heros);
Collections.sort(heros,new Comparator<Hero>() {
@Override
public int compare(Hero o1, Hero o2) {
return (int) (o2.hp-o1.hp);
}
});
Hero hero = heros.get(2);
System.out.println("通过传统方式找出来的hp第三高的英雄名称是:" + hero.name);
String name =heros
.stream()
.sorted((h1,h2)->h1.hp>h2.hp?-1:1)
.skip(2)
.map(h->h.getName())
.findFirst()
.get();
System.out.println("通过聚合操作找出来的hp第三高的英雄名称是:" + name);
}
泛型
ArrayList 默认接受Object类型的对象,所以所有对象都可以放进ArrayList中 所以get(0) 返回的类型是Object 接着,需要进行强制转换才可以得到APHero类型或者ADHero类型。 如果软件开发人员记忆比较好,能记得哪个是哪个,还是可以的。 但是开发人员会犯错误,比如第20行,会记错,把第0个对象转换为ADHero,这样就会出现类型转换异常
ArrayList heros = new ArrayList();
heros.add(new APHero());
heros.add(new ADHero());
APHero apHero = (APHero) heros.get(0);
ADHero adHero = (ADHero) heros.get(1);
ADHero adHero2 = (ADHero) heros.get(0);
使用泛型的好处: 泛型的用法是在容器后面添加 Type可以是类,抽象类,接口 泛型表示这种容器,只能存放APHero,ADHero就放不进去了。
子类对象可以放进去
假设容器的泛型是Hero,那么Hero的子类APHero,ADHero都可以放进去 和Hero无关的类型Item还是放不进去
支持泛型的类
设计一个支持泛型的栈MyStack 设计这个类的时候,在类的声明上,加上一个,表示该类支持泛型。 T是type的缩写,也可以使用任何其他的合法的变量,比如A,B,X都可以,但是一般约定成俗使用T,代表类型。
public class MyStack<T> {
LinkedList<T> values = new LinkedList<T>();
public void push(T t) {
values.addLast(t);
}
public T pull() {
return values.removeLast();
}
public T peek() {
return values.getLast();
}
public static void main(String[] args) {
MyStack<Hero> heroStack = new MyStack<>();
heroStack.push(new Hero());
heroStack.push(new Item());
MyStack<Item> itemStack = new MyStack<>();
itemStack.push(new Item());
itemStack.push(new Hero());
}
通配符
? extends
ArrayList heroList<? extends Hero> 表示这是一个Hero泛型或者其子类泛型 heroList 的泛型可能是Hero heroList 的泛型可能是APHero heroList 的泛型可能是ADHero 所以 可以确凿的是,从heroList取出来的对象,一定是可以转型成Hero的
但是,不能往里面放东西,因为 放APHero就不满足 放ADHero又不满足
public static void main(String[] args) {
ArrayList<APHero> apHeroList = new ArrayList<APHero>();
apHeroList.add(new APHero());
ArrayList<? extends Hero> heroList = apHeroList;
Hero h= heroList.get(0);
heroList.add(new ADHero());
}
? super
ArrayList heroList<? super Hero> 表示这是一个Hero泛型或者其父类泛型 heroList的泛型可能是Hero heroList的泛型可能是Object
可以往里面插入Hero以及Hero的子类 但是取出来有风险,因为不确定取出来是Hero还是Object
public static void main(String[] args) {
ArrayList<? super Hero> heroList = new ArrayList<Object>();
heroList.add(new Hero());
heroList.add(new APHero());
heroList.add(new ADHero());
Hero h= heroList.get(0);
}
泛型通配符?
泛型通配符? 代表任意泛型 既然?代表任意泛型,那么换句话说,这个容器什么泛型都有可能
所以只能以Object的形式取出来 并且不能往里面放对象,因为不知道到底是一个什么泛型的容器
public static void main(String[] args) {
ArrayList<APHero> apHeroList = new ArrayList<APHero>();
ArrayList<?> generalList = apHeroList;
Object o = generalList.get(0);
generalList.add(new Item());
generalList.add(new Hero());
generalList.add(new APHero());
}
小结
如果希望只取出,不插入,就使用? extends Hero 如果希望只插入,不取出,就使用? super Hero 如果希望,又能插入,又能取出,就不要用通配符?
package collection;
import java.util.ArrayList;
import java.util.List;
import charactor.GiantDragon;
import charactor.Hero;
public class Node<T extends Comparable<T>> {
public Node<T> leftNode;
public Node<T> rightNode;
public T value;
public void add(T t) {
if (null == value)
value = t;
else {
if (t.compareTo(value) <= 0) {
if (null == leftNode)
leftNode = new Node<T>();
leftNode.add(t);
}
else {
if (null == rightNode)
rightNode = new Node<T>();
rightNode.add(t);
}
}
}
public List<T> values() {
List<T> values = new ArrayList<>();
if (null != leftNode)
values.addAll(leftNode.values());
values.add(value);
if (null != rightNode)
values.addAll(rightNode.values());
return values;
}
public static void main(String[] args) {
int randoms[] = new int[] { 67, 7, 30, 73, 10, 0, 78, 81, 10, 74 };
Node<Integer> roots = new Node<>();
for (int number : randoms) {
roots.add(number);
}
System.out.println(roots.values());
Node<Hero> heros = new Node<>();
Node<GiantDragon> dragons = new Node<>();
}
}
对象转型
根据面向对象学习的知识,子类转父类是一定可以成功的
但是子类泛型不能转换为父类泛型。
父类泛型也不能转子类泛型。
Lambda
匿名类方式遍历满足条件的数据
首先准备一个接口HeroChecker,提供一个test(Hero)方法 然后通过匿名类的方式,实现这个接口
接着调用filter,传递这个checker进去进行判断,这种方式就很像通过Collections.sort在对一个Hero集合排序,需要传一个Comparator的匿名类对象进去一样。
package lambda;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import charactor.Hero;
public class TestLambda {
public static void main(String[] args) {
Random r = new Random();
List<Hero> heros = new ArrayList<Hero>();
for (int i = 0; i < 5; i++) {
heros.add(new Hero("hero " + i, r.nextInt(1000), r.nextInt(100)));
}
System.out.println("初始化后的集合:");
System.out.println(heros);
System.out.println("使用匿名类的方式,筛选出 hp>100 && damange<50的英雄");
HeroChecker checker = new HeroChecker() {
@Override
public boolean test(Hero h) {
return (h.hp>100 && h.damage<50);
}
};
filter(heros,checker);
}
private static void filter(List<Hero> heros,HeroChecker checker) {
for (Hero hero : heros) {
if(checker.test(hero))
System.out.print(hero);
}
}
}
package lambda;
import charactor.Hero;
public interface HeroChecker {
public boolean test(Hero h);
}
从匿名类演变成Lambda表达式
Lambda表达式可以看成是匿名类一点点演变过来
-
匿名类的正常写法
HeroChecker c1 = new HeroChecker() {
? public boolean test(Hero h) {
? return (h.hp>100 && h.damage<50);
? }
};
-
把外面的壳子去掉 只保留方法参数和方法体 参数和方法体之间加上符号 ->
HeroChecker c2 = (Hero h) ->{
? return h.hp>100 && h.damage<50;
};
-
把return和{}去掉
HeroChecker c3 = (Hero h) ->h.hp>100 && h.damage<50;
-
把 参数类型和圆括号去掉(只有一个参数的时候,才可以去掉圆括号)
HeroChecker c4 = h ->h.hp>100 && h.damage<50;
-
把c4作为参数传递进去
filter(heros,c4);
-
直接把表达式传递进去
filter(heros, h -> h.hp > 100 && h.damage < 50);
匿名方法
与匿名类 概念相比较, Lambda 其实就是匿名方法,这是一种把方法作为参数进行传递的编程思想。
虽然代码是这么写
filter(heros, h -> h.hp > 100 && h.damage < 50);
但是,Java会在背后,悄悄的,把这些都还原成匿名类方式。 引入Lambda表达式,会使得代码更加紧凑,而不是各种接口和匿名类到处飞。
弊端
Lambda表达式虽然带来了代码的简洁,但是也有其局限性。 \1. 可读性差,与啰嗦的但是清晰的匿名类代码结构比较起来,Lambda表达式一旦变得比较长,就难以理解 \2. 不便于调试,很难在Lambda表达式中增加调试信息,比如日志 \3. 版本支持,Lambda表达式在JDK8版本中才开始支持,如果系统使用的是以前的版本,考虑系统的稳定性等原因,而不愿意升级,那么就无法使用。
Lambda比较适合用在简短的业务代码中,并不适合用在复杂的系统中,会加大维护成本。
改写Comparator
Comparator<Hero> c = new Comparator<Hero>() {
@Override
public int compare(Hero o1, Hero o2) {
if (o1.hp<=o2.hp)
return 1;
else
return -1;
}
};
Collections.sort(heros,c);
Collections.sort(heros,(h1,h2)-> h1.hp>h2.hp?1:-1);
常用
随机数
import java.util.Random;
import static java.lang.Math.*;
a[i][j]=(int)(Math.random()*100);
a[i][j]=new Random().nextInt(100);
Random rd=new Random();
num=rd.nextInt(75)+48;
System.arraycopy()
把一个数组的值,复制到另一个数组中
System.arraycopy(src, srcPos, dest, destPos, length) src: 源数组 srcPos: 从源数组复制数据的起始位置 dest: 目标数组 destPos: 复制到目标数组的起始位置 length: 复制的长度
Array.deepToString
Arrays.deepToString()主要用于数组中还有数组的情况(即多维数组,包括二维数组)
实例
离黄金分割点 0.618最近
寻找某两个数相除,其结果 离黄金分割点 0.618最近
分母和分子不能同时为偶数 分母和分子 取值范围在[1-20]
package com.wang.base;
import java.util.Scanner;
public class Demo01 {
public static void main(String[] args) {
double diff = 1;
int a = 0;
int b = 0;
for (int i = 1; i <= 20; i++) {
for (int j = 1; j < i; j++) {
double val = Math.abs((double)j/i - 0.618);
if (val<diff){
a =i;
b =j;
diff=val;
}
}
}
System.out.println("最近的两个数是:"+b+"和"+a);
}
}
寻找所有的水仙花数
水仙花数定义: \1. 一定是3位数 \2. 每一位的立方,加起来恰好是这个数本身,比如153=111+555+333
寻找所有的水仙花数
package com.wang.base;
public class Demo02 {
public static void main(String[] args) {
int num1 = 0;
System.out.println("水仙花数:");
for (int i = 1; i <=9 ; i++) {
for (int j = 0; j <=9; j++) {
for (int k = 0; k <=9; k++) {
num1 = i*100+j*10+k;
if(num1==Math.pow(i,3)+Math.pow(j,3)+Math.pow(k,3))
System.out.print(num1+" ");
}
}
}
}
}
四元方程
package com.wang.base;
public class Demo03 {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
for (int j = 0; j < 100; j++) {
for (int k = 0; k < 100; k++) {
for (int l = 0; l < 100; l++) {
if (i+j==8 && k-l==6 && i+k==14 && j+l==10)
System.out.println(i+" "+j+" "+k+" "+l);
}
}
}
}
}
}
import java.util.Scanner;
public class HelloWorld {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
boolean flag = true;
do {
System.out.print("第一行两数相加的和:");
int a = scanner.nextInt();
System.out.print("第二行两数相加的和:");
int b = scanner.nextInt();
System.out.print("第一列两数相加的和:");
int c = scanner.nextInt();
System.out.print("第二列两数相加的和:");
int d = scanner.nextInt();
int m, n, x, y;
for (m = a; m >= 0; m--) {
n = a - m;
x = c - m;
y = x - b;
if (y + n == d) {
System.out.println("四个数字从左到右从上到下为:" + m + " " + n + " " + x + " " + y + " ");
flag = false;
break;
}
}
if (flag) {
System.out.println("输入错误,请重新输入。");
}
} while (flag);
}
}
选择排序法和冒泡排序法
package com.wang.base;
public class Demo04 {
public static void main(String[] args) {
int[] a= new int[10];
for (int i = 0; i < a.length; i++)
a[i] = (int)(Math.random()*100);
print(a);
for(int i = 0;i<a.length-1;i++){
for(int j= i+1;j<a.length;j++){
if(a[j]<a[i]){
int temp = a[i];
a[i]=a[j];
a[j]=temp;
}
}
}
print(a);
for(int i = 0;i<a.length-1;i++){
boolean flag = false;
for(int j=0;j<a.length-i-1;j++){
if(a[j+1]>a[j]){
int temp = a[j];
a[j]= a[j+1];
a[j+1]=temp;
flag = true;
}
}
if(!flag)
break;
}
print(a);
}
public static int[] reverse(int[] a){
for (int i = 0; i < a.length/2; i++) {
int b = a[i];
a[i] = a[a.length-i-1];
a[a.length-i-1] = b;
}
return a;
}
public static void print(int[] a){
for(int i:a)
System.out.print(i+" ");
System.out.println();
}
}
二维数组排序
import java.util.Arrays;
public class HelloWorld {
public static void main(String[] args) {
int a[][] = new int[5][8];
for (int i = 0; i < a.length; i++) {
for (int j = 0; j < a[i].length; j++) {
a[i][j] = (int) (Math.random() * 100);
}
}
System.out.println("打印二维数组");
for (int[] i : a) {
System.out.println(Arrays.toString(i));
}
int b[] = new int[a.length * a[0].length];
for (int i = 0; i < a.length; i++) {
System.arraycopy(a[i], 0, b, i * a[i].length, a[i].length);
}
Arrays.sort(b);
for (int i = 0; i < a.length; i++) {
System.arraycopy(b, a[i].length * i, a[i], 0, a[i].length);
}
System.out.println("打印排序后的二维数组");
for (int[] i : a) {
System.out.println(Arrays.toString(i));
}
}
}
|