# Java基础
# Java概述
java之父: 詹姆斯高斯林
1995年诞生于 sun公司
2009年 被 oracle 公司收购
# Java版本介绍
最新的版本是java11
版本 名称 发行日期 JDK 1.0 Oak(橡树) 1996-01-23 JDK 1.1 1997-02-19 JDK 1.1.4 Sparkler(宝石) 1997-09-12 JDK 1.1.5 Pumpkin(南瓜) 1997-12-13 JDK 1.1.6 Abigail(阿比盖尔–女子名) 1998-04-24 JDK 1.1.7 Brutus(布鲁图–古罗马政治家和将军) 1998-09-28 JDK 1.1.8 Chelsea(切尔西–城市名) 1999-04-08 J2SE 1.2 Playground(运动场) 1998-12-04 J2SE 1.2.1 none(无) 1999-03-30 J2SE 1.2.2 Cricket(蟋蟀) 1999-07-08 J2SE 1.3 Kestrel(美洲红隼) 2000-05-08 J2SE 1.3.1 Ladybird(瓢虫) 2001-05-17 J2SE 1.4.0 Merlin(灰背隼) 2002-02-13 J2SE 1.4.1 grasshopper(蚱蜢) 2002-09-16 J2SE 1.4.2 Mantis(螳螂) 2003-06-26 Java SE 5.0 (1.5.0) Tiger(老虎) 2004-09-30 Java SE 6.0 (1.6.0) Mustang(野马) 2006-04 Java SE 7.0 (1.7.0) Dolphin(海豚) 2011-07-28 Java SE 8.0 (1.8.0) Spider(蜘蛛) 2014-03-18 Java SE 9.0 2017-09-21 Java SE 10.0 2018-03-21
javaSE 11 2018-09-25
# Java平台版本
JAVAME
JAVASE
JAVAEE
# Java语言特点
简单性
面向对象
分布式处理
开源
跨平台
# Java环境搭建
jdk java development kit java开发工具包
jre java runtime environment java运行环境
jvm: java virtual machine java虚拟机
jdk中包含jre,jre中包含jvm,所以我们只需要安装jdk就可以跑java程序了
# HelloWorld
- 保证文件的扩展名是打开的:
bin: 存放二进制的命令,
db: database, 数据库
include: 底层需要引入的c/c++ 的头文件
jre: java运行环境
lib: 引用的类库
src.zip: Java 源码包
进入jdk的安装目录下的bin目录,右键->新建->文本文件-> HelloWorld.java
使用notepad++ 编辑该文件,在文件中写入如下代码
class HelloWorld{ public static void main(String[] args){ System.out.println("Hello World, welcome to DuoYi!!"); } }
编译和运行:
打开dos控制台;进入到bin目录下 编译
javac HelloWorld.java
运行:
java HelloWorld
总结:
javac 文件名.java java 类名 快速打开cmd的方法: notepad++在文件上右键,打开文件所在的文件夹(命令行)
执行流程:
使用javac命令(编译) 把.java文件变成了.class文件(字节码文件),就是jvm能够识别的文件;
使用java命令,执行程序中的代码
# 环境变量配置
为什么要配置环境变量:
我们当前的程序都是下载bin文件夹的下面,原因是我们用的两个命令,javac, java 都位于bin文件夹下,如果把.java文件放到其他文件夹中,其他文件夹中不包含这两个命令就会导致程序运行不了.我们不能每次都把文件放到bin文件夹下, 想要把文件放到其他文件夹下面,又希望能够使用这两个命令,所以可以通过配置环境变量的方式来实现
配置环境变量的目的: 让java命令 可以在计算机的任意位置执行
方法:
打开配置的框:计算机->右键->属性->高级系统设置->环境变量
在系统变量中新建-> JAVA_HOME
编译Path变量: 把%JAVA_HOME%\bin 添加进来
点击三个确定
验证成功:
重新打开cmd控制台,在控制台上输入java/java -version 有内容输出,代表配置成功
如果输出: java不是内部命令: 代表配置失败,按照上面的步骤重来一次,直到成功为止
# 注释
文档注释
1. /** 注释内容 */
多行注释
/* 注释内容 */
单行注释
// 当行注释
实例
/** www.51doit.com all rights reserve desc: 这是多易大数据第六期的第一个java程序:helloworld @author: Mr Liu @createTime; 2018-12-27 17:13 */ class HelloWorld{ /* 这是一个main方法:他是java程序执行的入口, main方法的书写格式是固定的 */ public static void main(String[] args){ // 这是一条输出语句,用于打印一句话. System.out.println("Hello World, welcome to DuoYi!!"); //begVIP } }
# 关键字
概念
被java语言赋予特定含义的单词
特点
所有的字母都是小写的, 在一些高级的开发工具中会有特殊颜色的显示
保留字
goto, const
注意事项
在一些其他资料中,也可能把关键字称之为保留字
public ,static ,class, void, byte,short,int, null, true, false.......
# 标识符
概念
给类,接口,方法,变量起名字的字符序列
组成规则
1、由数字、字母、下划线和美元符号组成
2、不能以数字开头
3、不能使用关键字
4、区分大小写
5、见名知意
约定俗成的命名法
- 给类和接口命名: 大驼峰命名法:
大驼峰命名法: 首字母大写,多个单词,每个单词的首字母都大写StudentManagementSystem, HelloWorld
- 给方法和变量起名字: 小驼峰命名法:
大驼峰命名法: 首字母小写,多个单词,每个单词的首字母都大写proName, receiveAddr
- 包: 域名反转,用.隔开
包: 包的本质就是文件夹
www.alibaba.com fastjson
com.alibaba.项目名称.模块名称 com.alibaba.fastjson.
www.51doit.com com._51doit.javase.day01.HelloWorld
www.alibaba.com tmall
com.alibaba.tmall.product
com.alibaba.tmall.order
com.alibaba.tmall.pay
com.alibaba.tmall.member
- 常量: 值不变的量所有的字母都大写, 多个单词中间使用_ 隔开
(public static final double )PI = 3.14;
SERECT_KEY = "ufsdafjaslufoufdsaersef90fsdofsda";
# 常量
# 概念及分类
概念: 在java程序运行的过程中,其值保持不变的量
分类:
字面值常量:
字符串常量: 用 "" 括起来的内容
整数常量: 所有整数 10
小数常量: 所有小数 12.8
布尔常量: true, false
字符常量: 用单引号括起来的内容 , 单引号中只能有一个字符 '8'
空常量: null
自定义常量: final修饰的变量
# 常量的表现形式
# 进制的分类和组成
2进制: 0,1,10,11,100,101,110,111,1000..... 0,1 0b 如:0b100
8进制: 0,1,2,3,4..7,10,...17,20....... 0-7 0 如:0100
10进制:0,1,2...9,10,11,19,20...... 0-9 默认 如:100
16进制: 0,..9,a,b,c,d,e,f,10,...19.1a,1b....1f,20.... 0-9a-f 0x 如:0x100
# 进制相互转换
其他进制转10进制:每一位上的数乘以对应进制数的位数减一次方,做累加
1234 = 1x1000 + 2x100 + 3x10 + 4x1
= 1 x 10^3 + 2x10^2 + 3x10^1 + 4x 10^0
0b1100 = 1x2^3 + 1x2^2 + 0x2^1 + 0x2^0 = 8 + 4 = 12
练习 : 将如下的数字转成10进制
0b1101101 = 64 + 32 + 8+4 + 1 = 109 0x1df = 256 + 13*16 + 15 = 256 + 169+39 + 15 = 256 + 208+ 15 = 479 0712 = 7* 64 + 8 +2 = 10 + 448 = 458 0b111010 = 32+ 16+ 8+ 2 = 48 + 10 = 58
进制转成其他进制:转成几进制就除以几,直到商为0,把余数反转
练习:
把 80 转成8进制:120 80 / 8 = 10 .... 0 10 / 8 = 1 .... 2 1 / 8 = 0 .... 1
快速转换法:略
# 有符号表示法
- 原码: 用最高位表示符号,其他为表示数值, 正数的符号位是0,负数的符号位是1
7的原码: 0000 0111
-7的原码: 1000 0111
- 反码: 正数的反码和原码相同,负数的反码,符号位不变,其他位取反
7的反码: 0000 0111
-7的反码: 1111 1000
- 补码: 正数的补码和原码相同, 负数的补码, 在反码的基础上,末位加1
7的补码: 0000 0111
-7的补码: 1111 1001
结论:计算机中存储的都是二进制的补码,负数的补码-1得到反码,反码取反得到源码
有符号数据练习(8位)
已知某数X的原码为0b10110100,试求X的补码和反码。 补码: 1100 1100 反码: 1100 1011 已知某数X的补码0b11101110,试求其原码。(因为是负数,先求反码,再求原码) 原码 1001 0010
# 变量
# 变量的概念
在java程序运行的过程中,其值可以在一定范围内发生改变的量
主要作用: 存储
变量的定义格式:
数据类型 变量名 = 初始值;
整数: int
字符串: String
class VarDemo{
//数据类型 变量名 = 初始值;
public static void main(String[] args){
//定义一个分数的变量
int score = 0;
//
int golds = 300;
String name = "一缕82年的清风";
score = 100000; // 对score变量重新赋值
System.out.println(score);
System.out.println(golds);
System.out.println(name);
}
}
变量的声明格式:
数据类型 变量名;
# 变量的注意事项
变量所在的大括号叫做变量的作用域,同一个作用域中不能有多个名字相同的变量
int score = 0; int score = 100;//错误: 已在方法 main(String[])中定义了变量 score
定义在方法中的变量叫做局部变量,局部变量不赋初值不能使用
int age;// 声明了一个int类型的age 的变量 System.out.println(age);// 错误: 可能尚未初始化变量age
同一行可以定义多个变量.(但是不建议这么使用,可读性查)
int a,b,c=10;// int a;int b; int c = 10; System.out.println(a);//错误: 可能尚未初始化变量a public class HelloDay1 { public static void main(String [] args) { // 定义byte类型变量 byte b = 10; System.out.println(b); // 定义short类型变量 short s = 100; System.out.println(s); // 定义double类型变量 double d = 13.14; System.out.println(d); // 定义char类型变量 char c ='c'; System.out.println(c); // 定义long类型变量 long l = 10000000000L; System.out.println(l); // 定义float类型变量 //float f = 13.14; // 需要加f,浮点数默认是double类型,显然是大转小 float f = 13.14f; // 需要加f,浮点数默认是double类型,显然是大转小 System.out.println(f); } }
# 数据类型
# 计算机的存储单元
我们知道计算机是可以用来存储数据的,但是无论是内存还是硬盘,计算机存储设备的最小信息单元叫“位(bit)”,我们又称之为“比特位”,通常用小写的字母”b”表示。而计算机中最小的存储单元叫“字节(byte)”,
通常用大写字母”B”表示,字节是由连续的8个位组成
1byte(B) = 8bit
1KB = 1024B
1MB = 1024KB
1GB = 1024MB
1TB = 1024GB
# 数据类型的分类和范围
Java语言是强类型语言,对于每一种数据都给出了明确的数据类型,不同的数据类型也分配了不同的内存空间,所以它们表示的数据大小也是不一样的。
- 基本数据类型
整数类: byte short int long
小数类: float double
布尔类: boolean
字符类: char
- 引用数据类型
除了基本数据类型以外的所有类型
类 String
接口
数组
- 数据类型的内存占用
数据类型 | 关键字 | 内存占用 | 取值范围 |
---|---|---|---|
整数 | byte | 1 | -128~127 |
short | 2 | -32768~32767 | |
int | 4 | -2的31次方到2的31次方-1 | |
long | 8 | -2的63次方到2的63次方-1 | |
浮点数 | float | 4 | 1.401298e-45到3.402823e+38 |
double | 8 | 4.9000000e-324 到1.797693e+308 | |
字符 | char | 2 | 0-65535 |
布尔 | boolean | 1 | true,false |
字节(byte) : 8位二进制 称之为一个字节 2^8 = 256
bit: 二进制位
注意事项:
整数的默认类型是int, 小数的默认类型是double
float g = 12.3;//错误: 不兼容的类型: 从double转换到float可能会有损失 //原因:12.3默认是double类型的,把double类型赋值给4个字节的float有可能float装不下 //解决方法: 指定12.3为float类型,避免出现损失. 在小数的后面加上一个
定义float类型的小数一定要在小数的后面加上一个f.
定义byte,short类型的变量,如果=右面的值在等号左边的类型范围之内,则可以直接赋值
byte a = 120;//没毛病 short b = 10;//没毛病
byte a = 129;// 错误: 不兼容的类型: 从int转换到byte可能会有损失
定义long类型的变量的时候,如果=右边的值在int的范围之内,则可以直接赋值,如果超出了int的范围,需要在整数的后面加上一个L (推荐使用L)
long f = 2147483648;// 错误: 过大的整数: 2147483648(超出了int的范围,在末位加上L即可解决)
# 基本数据类型的默认值
每一种基本数据类型都对应一个默认值
只有定义在类中方法外的变量才有默认值,定义在方法中的局部变量没有默认值(不赋初值不能使用)
静态的方法中只能调用外部用static修饰的变量(方法)
int b;//加上static
public static void main(String[] args){
System.out.println(b);//错误: 无法从静态上下文中引用非静态变量 b
}
class DataTypeDemo2{
//定义在类中方法外的变量才是有默认值的;
static int b;//0
static boolean bo ;//false
public static void main(String[] args){
int a;// 定义在方法中的变量叫做局部变量,不赋初值不能使用
//System.out.println(a);// 错误: 可能尚未初始化变量a
System.out.println(b);//0
System.out.println(bo);//false
}
}
# 数据类型的运算规则
byte,short的赋值
byte,short,char 不能直接运算的,需要先转换成int类型;给byte,short 类型的变量赋值的时候,要看=的右面是否有变量:
如果都是常量 : 看最终运算出来的结果是否在=左边的范围之内,如果在,则可以直接赋值
如果有变量: 则按照运算的规则(byte,short,char 不能直接运算的,需要先转换成int类型),如果的到的结果的范围大于=左边类型的范围,则不能赋值
class DataTypeDemo3{
public static void main(String[] abc){
byte b1=3,b2=4,b;
//b=b1+b2;// 错误: 不兼容的类型: 从int转换到byte可能会有损失(int 大于 byte)
b=3+4;//正确: b = 7;
}
}
float的赋值
byte,short,char—int—long—float—double
float f = 123L;// 正确的
我们可以把一个long类型的值赋值给一个float类型的变量
3.4 x10^38 2^63 -1
3.4x10^38 > 3.4x8^38 = 3.4 * (2^3) ^ 38 = 3.4 x2^ 114 >>> 2^63 -1
float的最大值是远大于 long的最大值的.所以我们可以把一个long类型的值赋值给一个float类型的变量
>float f = 12.3; // 错误的, 加f
>
>float f = 12;// 正确的.
# char类型解读
字符: 用单引号括起来的内容,只能有一个字符 2个字节: 0-65535
ASCII (1个字节)
GB2312 (2个字节一个中文, 英文和数字1个字节)
GBK:
GBK18320: (BIG5)
ISO: 国际标准化组织
unicode: 万国码 (2个字节)
utf-8: 用可变字节传输 英文/数字(1个字节), 中文(3个字节)
utf-16
char底层存的是编码
char类型一旦参与运算,用的是底层的编码;直接打印,打印的是对应的字符
char ch = 'a';
System.out.println(ch);//a
System.out.println(ch + 1);//98
char类型的默认值就是0, 编码是0所对应的字符(表现形式是一个空格) \u0000
class DataTypeDemo4{
public static void main(String[] args){
char ch = 'a';
System.out.println(ch);//a
System.out.println(ch + 1);//98
System.out.println('A' + 0);//65
System.out.println('a'+'b');//97+98 195
char ch2 = 97;
System.out.println(ch2);//a
char ch3 = '\u0061';// 代表用16进制数赋值(97)
System.out.println(ch3);//97
System.out.println('3'+'4');//103
System.out.println('3'+4);//55
}
}
# 加法运算
+: 正号 加法运算 字符串的连接
- 从左往右依次运算,有括号的先算括号里的
- String 和任意类型做加法得到的都是字符串,做拼接操作
- 布尔类型不能和其他基本数据类型做运算
class DataTypeDemo5{
public static void main(String[] args){
System.out.println('a');//a
System.out.println('a'+1);//98 // 字符
System.out.println("hello"+'a'+1);//hello98 hello971 right: helloa1
System.out.println('a'+1+"hello");//971hello right: 98hello
System.out.println("5+5="+5+5);//5+5=10 right: 5+5=55
System.out.println(5+5+"=5+5");//right: 10=5+5 error: 55=5+5
System.out.println(true+"3");//true3
//System.out.println(true +'2');// 错误: 二元运算符 '+' 的操作数类型错误
}
}
# 类型转换
把一个表示数据范围大的数值或者变量赋值给另一个表示数据范围小的变量
# 强制类型转换
目标类型 变量名 = (目标类型) 要转换的值;
基本数据类型,除了布尔,其他七种类型之间都可以进行强转, 一般把大类型转成小类型
class ForceTypeZH{
public static void main(String[] args){
long a = 10;
//int d = a;//错误: 不兼容的类型: 从long转换到int可能会有损失
int b =(int) a;
System.out.println(b);
byte by = (byte)130;
System.out.println(by);//-126
}
}
# 自动类型转换
把一个表示数据范围小的数值或者变量赋值给另一个表示数据范围大的变量
byte-- short
--
int-- long-- float-- double
--
char
public class HelloDay1 {
public static void main(String [] args) {
// 定义double类型变量
double d = 13;
System.out.println(d); // 13.0 默认是int转为double了
byte b = 12;
short s = b;
int i = s;
System.out.println(i); // 12
// char c = b; // 不兼容
// 强制类型转换
// int k = 88.9;
int k = (int)88.8;
System.out.println(k); // 88 数据丢失
}
}
# 运算符
运算符:对常量或者变量进行操作的符号
表达式:用运算符把常量或者变量连接起来符合java语法的式子就可以称为表达式。 不同运算符连接的表达式体现的是不同类型的表达式。
算术表达式中包含多个基本数据类型的值的时候,整个算术表达式的类型会自动进行提升。 提升规则:
1.byte类型,short类型和char类型将被提升到int类型
2.整个表达式的类型自动提升到表达式中最高等级操作数同样的类型,等级顺序:byte,short,char int long float double
# 算数运算符
注意事项: 整数和整数相运算,得到的还是整数,要想得到小数必须让小数参与运算
/**
算数运算符:
+
-
*
/
%
*/
class MathOperatorDemo{
public static void main(String[] args){
int a = 15;
int b = 2;
System.out.println(a+b);// 17
System.out.println(a-b);//13
System.out.println(a*b);//30
System.out.println(a/b);//7, 两个int想运算得到的结果还算int类型
System.out.println(a%b);//余数小于除数 1
System.out.println(a/b*1.0);//7.0
System.out.println(1.0*a/b);//7.5
System.out.println((double)a/b);//7.5
System.out.println((double)(a/b));//7.0
}
}
- 字符串的“+”操作
/**
1.当“+”操作中出现字符串时,这个”+”是字符串连接符,而不是算术运算。
2.在”+”操作中,如果出现了字符串,就是连接运算符,否则就是算术运算。当连续进行“+”操作时,从左到右逐个执行。
*/
public class HelloDay1 {
public static void main(String [] args) {
System.out.println("it"+444); // it444
System.out.println(4+"it"); //4it
System.out.println(4+5+"it"); //9it
System.out.println("it"+443); //it443
}
}
# 自增自减运算符
++ 和 --
/**
单独使用:没有区别,都是做+1 的操作
参与运算:
前加加: 先加1 ,后运算
后加加: 先运算,后加1
常见运算: 赋值,打印,小括号
*/
public class HelloDay1 {
public static void main(String [] args) {
int i = 10;
System.out.println(i); // 10
int j = i++; // 这一步由于++在变量后,所以先给j赋值,再i加一
System.out.println(j); //10
System.out.println(i); // 11
int p=++i; // 这一步先i加一,再赋值给p
System.out.println(i); // 12
System.out.println(p); // 12
}
}
# 赋值运算符
= += -= *= /= %=
/**
= : int a = 100;
+= a+= b 相当于a = a+b;
-=
*=
/=
%=
+= -= *= /= %= 中包含一个默认的强制类型转换
*/
class GiveValueOperatorDemo{
public static void main(String[] ar){
int a = 15;
int b = 2;
System.out.println(a=b);//2, 把b给a,打印a
System.out.println(a);//2
a+=b;//a=a+b
System.out.println(a);//4
a-=b;//a=a-b
System.out.println(a);//2
a*=b;//a=a*b
System.out.println(a);//4
a/=b;//a=a/b
System.out.println(a);//2
a%=b;// a=a%b
System.out.println(a);//0
//short s=1;
//s = s+1; //错误: 不兼容的类型: 从int转换到short可能会有损失,因为1默认是int
// 可以这样强制类型转换: s = (short)(s+20)
short s=1;
s+=1;//正确的, 包含了一个强制类型转换 s = (short)(s+1)
}
}
# 关系运算符
特点: 得到的结果都是布尔类型的值
符号 | 说明 |
---|---|
== | a==b,判断a和b的值是否相等,成立为true,不成立为false |
!= | a!=b,判断a和b的值是否不相等,成立为true,不成立为false |
> | a>b,判断a是否大于b,成立为true,不成立为false |
>= | a>=b,判断a是否大于等于b,成立为true,不成立为false |
< | a<b,判断a是否小于b,成立为true,不成立为false |
<= | a<=b,判断a是否小于等于b,成立为true,不成立为false |
# 逻辑运算符
逻辑运算符,是用来连接关系表达式的运算符。当然,逻辑运算符也可以直接连接布尔类型的常量或者变量。
符号 | 作用 | 说明 |
---|---|---|
& | 逻辑与 | a&b,a和b都是true,结果为true,否则为false |
| | 逻辑或 | a|b,a和b都是false,结果为false,否则为true |
^ | 逻辑异或 | a^b,a和b结果不同为true,相同为false |
! | 逻辑非 | !a,结果和a的结果正好相反 |
public class HelloDay1 {
public static void main(String [] args) {
int i = 10;
int j = 12;
int k = 13;
// 有false则false
System.out.println((i>j)&(i>k)); // false
System.out.println((i<j)&(i<k)); // true
System.out.println((i>j)&(i<k)); // false
// 有true则true
System.out.println((i>j)|(i>k)); // false
System.out.println((i<j)|(i<k)); // true
System.out.println((i>j)|(i<k)); // true
// 相同为false,不同为true
System.out.println((i>j)^(i>k)); // false
System.out.println((i<j)^(i<k)); // false
System.out.println((i>j)^(i<k)); // true
boolean a = true;
boolean b = false;
System.out.println(a&b);//false
System.out.println(a|b);//true
System.out.println(a^b);//true
System.out.println(!(a&b));//true
System.out.println((2>3)|(4!=3));// true
System.out.println((2!=3)^(4<=3));//true
System.out.println((2>=1)&(4!=3));//true
System.out.println((2>=1)&(4!=3)|(2>=1)^(4!=3));//true&true|true^true true
}
}
短路逻辑运算符
符号 | 作用 | 说明 |
---|---|---|
&& | 短路与 | 作用和&相同,但是有短路效果 |
|| | 短路或 | 作用和|相同,但是有短路效果 |
/**
1.逻辑与&,无论左边真假,右边都要执行。短路与&&,如果左边为真,右边执行;如果左边为假,右边不执行
2.逻辑或|,无论左边真假,右边都要执行。短路或||,如果左边为假,右边执行;如果左边为真,右边不执行。
*/
public class HelloDay1 {
public static void main(String [] args) {
int i = 10;
int j = 12;
int k = 13;
// 有false则false
System.out.println((i>j)&&(i>k)); // false
System.out.println((i<j)&&(i<k)); // true
System.out.println((i>j)&&(i<k)); // false
// 有true则true
System.out.println((i>j)||(i>k)); // false
System.out.println((i<j)||(i<k)); // true
System.out.println((i>j)||(i<k)); // true
}
}
# 位运算符
& 、^ 、|: 连接整数, 需要把整数转成二进制的补码,逐位进行运算,把0当成false, 把1 当成true
~ : 逐位取反
15 & 2 =2
15 | 2 =15
15 ^ 2 =13
~2 =-3
分析:
15补码: 00000000 00000000 00000000 00001111
2的补码: 00000000 00000000 00000000 00000010
-----------------------------------------------------------
& 00000000 00000000 00000000 00000010 2 (同时为1才为1)
| 00000000 00000000 00000000 00001111 15(有1则为1)
^ 00000000 00000000 00000000 00001101 13 (相同则为1)
~ 11111111 11111111 11111111 11111101 (补码-1得到反码)
11111111 11111111 11111111 11111100 (反码取反得源码)
10000000 00000000 00000000 00000011 (原码) -3
针对二进制位进行操作的运算符:(连接整数)
<< 左移: 空位补0,高位丢弃 --> 左移几位相当于乘以2的几次方
>> 右移: 空位补最高位 --> 右移几位相当于除以2的几次方
>>> 无符号右: 空位都补0
7<<2 = 28
7: 00000000 00000000 00000000 00000111
<<2 00000000 00000000 00000000 00011100 28
15 >>2 = 3
15: 00000000 00000000 00000000 00001111
>>2 00000000 00000000 00000000 00000011 3
# 三元运算符
格式:关系表达式 ? 表达式1 : 表达式2;
执行流程:
如果条件为true,运算后的结果是表达式1;
如果条件为false,运算后的结果是表达式2;
实例代码
class ThreeEyesOperatorDemo{
public static void main(String[] args){
int a = 15;
int b = 23;
int c = a>b?a:b;// 求a 和 b的最大值
System.out.println(c);//23
String d = a<b?"a":"b";
System.out.println(d);//a
/**
获取两个long类型整数中的最大值,输出其结果
获取三个float小数中的最大值,输出其结果定义三个float变量
*/
long a = 10000;
long b = 345;
long max = a>b?a:b;
System.out.println("最大值为:" + max);
float f1 = 123.9f;
float f2 = 12;
float f3 = 90.3f;
float f4 = f1>f2?f1:f2;// 代表f1 f2 中的最大值
float f = f3>f4?f3:f4;// 三个数中的最大值
System.out.println("三个数中的最大值为:"+f);
float max1 = f1>f2?(f1>f3?f1:f3):(f2>f3?f2:f3);//如果条件成立,f1是前两个数中的最值,只需比较f1和f3即可,反之比较 f2和f3
float max2 = f1>f2&f1>f3?f1:(f2>f3?f2:f3);//条件成立,代表f1是最大值,条件不成立代表f1不是最大值,则最大值在f2和f3中产生
System.out.println("三个数中的最大值为:"+max1);
System.out.println("三个数中的最大值为:"+max2);
}
}
# 数据输入
# Scanne基本步骤
根据输入输出最大数
import java.util.Scanner;
public class HelloDay1 {
public static void main(String [] args) {
// 创建对象
Scanner sc = new Scanner(System.in);
// 接收数据
int h1 = sc.nextInt(); // 接收数字字符
int h2 = sc.nextInt();
int h3 = sc.nextInt();
int tempHeight = h1>h2?h1:h2;
int maxHeight = tempHeight>h3?tempHeight:h3;
// 输出数据
System.out.println(maxHeight);
}
}
相关函数使用
获取键盘录入的信息
对象名.nextInt(); // 获取键盘录入的整数
.nextShort();
.nextLong();
.nextFloat(); // 小数
.nextDouble();
..... nextChar(); //这个方法是没有的
对象名.nextLine(); //获取字符串
注意:
如果我们在程序中既使用了nextInt(...基本),还使用nextLine(); 我们要把nextLine放到nextInt(...) 的上面
否则会导致nextLine, 没办法输入进去! 如果我们非要把nextInt(...)放下面: 把nextLine 改成next();
import java.util.Scanner;
public class HelloDay1 {
public static void main(String [] args) {
// 创建对象
Scanner sc = new Scanner(System.in);
// 接收数据
int h1 = sc.nextInt(); // 接收数字字符
String sm = sc.nextLine(); // 读取字符串
// 输出数据
System.out.println(h1); // 14
System.out.println(sm); // 空的
// 接收数据
int h2 = sc.nextInt(); // 接收数字字符
String sm2 = sc.next(); // 读取字符串
// 输出数据
System.out.println(h2); // 14
System.out.println(sm2); // 14
}
}
# 流程控制
# 顺序结构
顺序结构是程序中最简单最基本的流程控制,没有特定的语法结构,按照代码的先后顺序,依次执行, 程序中大多数的代码都是这样执行的。
# 分支结构
if语句
if(关系表达式) { 语句体;} //注意:if语句中的大括号是可以省略的,如果省略,那么值控制到第一个分号结束 public class HelloDay1 { public static void main(String [] args) { if(3!=4) System.out.println("哈哈"); // 会打印 System.out.println("呵呵"); } }
if...else...语句
public class HelloDay1 { public static void main(String [] args) { int a=5,b = 3; if (a==b){ System.out.println("a等于b"); }else{ System.out.println("a不等于b"); } } }
if...else if...else..语句
import java.util.Scanner; public class HelloDay1 { public static void main(String [] args) { System.out.println("开始"); Scanner sc = new Scanner(System.in); System.out.println("请输出数字1-7中的一个:"); int week = sc.nextInt(); if (week==1){ System.out.println("周一"); }else if(week>5&&week<=7){ System.out.println("放假了"); }else { System.out.println("周二-周五"); } } }
switch语句
语法:
switch(表达式) { case 常量值1: 语句体1; break; case 常量值2: 语句体2; break; … default: 语句体n+1; break; } /** **执行流程**: 1. 首先计算出表达式的值 2. 其次,和case依次比较,一旦有对应的值,就会执行相应的语句,在执行的过程中,遇到break就会结束。 3. 最后,如果所有的case都和表达式的值不匹配,就会执行default语句体部分,然后程序结束掉。 **注意事项:** **1.** case后面只能跟常量,不能跟变量 **2.** 多个case后面的常量值不能相同 **3.** case的顺序有要求么? 没有,可以放到任意位置 **4.** default一定放在最后么? 也不是可以放到任意位置 **5.** default 可以省略么? 可以 **6.** break是否可以省略,可以省略,如果省略的话,代码会继续向下执行,不管下面的case是否匹配成功,一直执行到再次遇 到break,或者是执行到了switch语句结束(case的穿透现象) **7.** switch语句何时结束: 遇到break, 或者代码执行到了switch语句的最后 */ import java.util.Scanner; public class HelloDay1 { public static void main(String [] args) { System.out.println("开始"); Scanner sc = new Scanner(System.in); int week = sc.nextInt(); switch(week){ case 1: System.out.println("周一"); break; case 2,3,4,5: // 可多个值 System.out.println("周二-周五"); break; default: System.out.println("放假"); } } }
练习
//1、季节区分 public class SwitchTest { public static void main(String[] args) { System.out.println(6|7|8); System.out.println(12|1|2); Scanner sc = new Scanner(System.in); System.out.println("请输入月份"); int month = sc.nextInt(); switch(month) { case 12: // case的穿透现象 case 1: case 2: System.out.println("冬季"); break; case 3: case 4: case 5: System.out.println("春季"); break; case 6: case 7: case 8: System.out.println("夏季"); break; case 9: case 10: case 11: System.out.println("秋季"); break; default: System.out.println("您输入的月份不合法"); } } } //2、实现一个计算器,从键盘录入第一个数,运算的符号(+-*除;取余 ),第二个数执行结果 public class SwitchTest2 { public static void main(String[] args) { Scanner sc = new Scanner(System.in); System.out.println("请输入一个整数"); int a = sc.nextInt(); System.out.println("请输入一个符号(+-*/)"); String b = sc.next(); System.out.println("请输入第三个数"); int c = sc.nextInt(); switch(b) { case "+": System.out.println(a+b+c+"="+(a+c)); break; case "-": System.out.println(a+b+c+"="+(a-c)); break; case "*": System.out.println(a+b+c+"="+(a*c)); break; case "/": System.out.println(a+b+c+"="+(a/c)); break; default: System.out.println("不会算!"); } } }
# 循环结构
for循环
语法格式
for(初始化语句;判断条件语句;控制条件语句) {循环体语句体;} //大括号省略的情况和if相同 /** **执行流程:** A:执行初始化语句 B:执行判断条件语句,看其结果是true还是false 如果是false,循环结束。 如果是true,继续执行。 C:执行循环体语句 D:执行控制条件语句 E:回到B继*/ public static void main(String[] args) { //打印100 句 "学大数据来" for(int i=0;i<100;i++) { // 0 -- 99 System.out.println("学大数据来"+i); } }
练习
//1、打印1-10/打印10-1/打印1-100中所有的偶数/求1-10之和 public static void main(String[] args) { //1-10 for(int i=1;i<=10;i++) { System.out.println(i); } //10-1 for(int i=10;i>0;i--) { System.out.println(i); } for(int i=1;i<=10;i++) { System.out.println(11-i); } System.out.println("------"); //3,打印1-100中所有的偶数 for(int i=1;i<=50;i++) {//方法1 System.out.println(i*2); } for(int i=1;i<=100;i++) {//方法2 if(i%2 == 0) { System.out.println(i); } } for(int i=2;i<=100;i+=2) {//方法3 System.out.println(i); } //4.1-10 之和 int sum = 0;//sum 带表1-10 之和 for(int i=1;i<=10;i++) { sum += i;// sum = sum +i; 把1-10 都加到sum身上 } System.out.println(sum);//55,在循环外打印,循环结束后才计算完毕 //求 1-100 奇数和 偶数和 } //2、求1-100 奇数和、偶数和 public class ForTest2 { public static void main(String[] args) { int singleSum = 0;//奇数和 int doubleSum = 0;//偶数和 for(int i=1;i<=100;i++) { if(i%2 == 0) {//偶数 doubleSum += i; }else {// 奇数 singleSum += i; } } System.out.println("奇数和为:"+singleSum); System.out.println("偶数和为:"+doubleSum); } } //3、请统计1-1000之间分别满足如下条件的数据有多少个:对3整除余2、对5整除余3、对7整除余2 public class ForTest3 { public static void main(String[] args) { int count1 = 0; int count2 = 0; int count3 = 0; for(int i=1;i<=1000;i++) { if(i%3 == 2) { count1++; } if(i%5 == 3) {// 注意不要使用else if, 有可能会漏掉一些数 count2++; } if(i%7==2) { count3++; } } System.out.println(count1); System.out.println(count2); System.out.println(count3); } } //4、水仙花数 public class HelloDay1 { public static void main(String [] args) { for (int i =100;i<=1000;i++) { int ge = i%10; int shi = i/10%10; int bai = i/100%10; if (ge*ge*ge+shi*shi*shi+bai*bai*bai == i){ System.out.println(i); } } } }
while循环语句
语法
while(判断条件语句) { 循环体语句体; 控制条件语句; } // 演示 int i = 0; while(i<100) { System.out.println("垂死病中惊坐起,带上书本去自习"+i); i++; }
练习
//1、打印1-10/打印10-1/打印1-100中所有的偶数/求1-10之和 public class WhileTest { public static void main(String[] args) { int i = 1; while(i<=10) { System.out.println(i); i++; } i=10; while(i>0) { System.out.println(i); i--; } int sum = 0;//1-10之和 int j=1; while(j<=10) { sum += j; j++; } System.out.println(sum);//55 int singleSum = 0; int doubleSum = 0; j=1; while(j<=100) { if(j%2 == 0) { doubleSum += j; }else { singleSum += j; } j++; } System.out.println(singleSum); System.out.println(doubleSum); } } //2、世界最高山峰是珠穆朗玛峰(8844.43米=8844430毫米),一张纸为0.1毫米厚,请问折叠多少次后可以达到珠穆朗玛峰的高度? public class HelloDay1 { public static void main(String [] args) { int count = 0; double paper = 0.1; int zf = 8844430; while (paper <= zf) { paper *= 2; count ++; } System.out.println(count); //27 } }
for与while区别
for循环和while循环对比: for循环适合针对一个范围判断进行操作 while循环适合不知道循环的次数,或者要求循环的次数
do...while语句
语法
初始化语句; do { 循环体语句; 控制条件语句; } while(判断条件语句); public class HelloDay1 { public static void main(String [] args) { System.out.println("开始"); int j = 1; do { System.out.println(j); // 始终会执行一次 j++; }while(j<=5); } }
死循环
for (;;){ System.out.println("for"); } while(true){ System.out.println("while"); } do { System.out.println("do ... while"); }while(true); //案例:登陆循环 public class CircleLoginDemo2 { public static void main(String[] args) { Scanner sc = new Scanner(System.in); String password;//局部变量不赋初值不能使用 do { System.out.println("请输入密码"); password = sc.nextLine();// 赋值的操作 }while(!"abc1234".equals(password)); System.out.println("登录成功"); } }
跳转控制语句
continue 用再循环中,基于条件控制,跳过某次循环体内容的执行,继续下一次的执行
break 用在循环中,基于条件控制,终止循环体内容的执行,也就是说结束当前的整个循环
/** * 跳转控制语句: * break: * 在switch语句中; 在循环语句中; 离开使用场景没有意义 * 结束单层循环: * continue: * 只能用在循环语句中,离开使用场景没有意义 * 结束本次循环 * return: 结束整个方法;并返回给方法的调用者 * main方法的调用者: jvm */ public class JumpCtrlDemo { public static void main(String[] args) { for(int i=0;i<10;i++) { if(i == 3) { //break;// 结束单层循环 //continue;//结束本次循环 return; } System.out.println(i); } System.out.println("over"); } }
循环嵌套:再循环中在嵌套循环
//1、打印四行五列的星星 public class CircleQTDemo { public static void main(String[] args) { /*for(int i=1;i<=4;i++) {// 外层循环: 控制行 for(int j=1;j<=5;j++) {//内层循环: 控制列 System.out.print("*");// 内层循环,不换行 } System.out.println();// 内层循环结束后换行 }*/ for(int i=1;i<=5;i++) { for(int j=1;j<=i;j++) { System.out.print("*"); } System.out.println(); } } } //2、打印99乘法表 public class NineNineXTableDemo { public static void main(String[] args) { System.out.println("\"helloworld\""); for(int i=1;i<=9;i++) { for(int j=1;j<=i;j++) { System.out.print(j+"x"+i+"="+j*i+"\t"); } System.out.println(); } } }
# Random
作用: 用于产生一个随机数
使用案例:
import java.util.Random;
public class HelloDay1 {
public static void main(String [] args) {
// 创建对象
Random r = new Random();
// 获取随机数
for (int i=0; i<10; i++){
int number = r.nextInt(10); // 0-9之间的随机数
System.out.println("number:"+number);
}
}
}
案例:猜数字
import java.util.Random;
import java.util.Scanner;
public class HelloDay1 {
public static void main(String [] args) {
// 创建对象
Random r = new Random();
int number = r.nextInt(100)+1;
while (true) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入你猜的数字:");
int guessNumber = sc.nextInt();
if (guessNumber > number) {
System.out.println("猜大了");
}else if (guessNumber < number){
System.out.println("猜小了");
}else {
System.out.println("猜对了");
break;
}
}
}
}
# 数组
数组(array)是一种用于存储多个相同类型数据的存储模型
语法
格式一: 数据类型[] 变量名 (推荐)
范例: int[] arr
解读:定义了一个int类型的数组,数组名arr
格式二: 数据类型 变量名[]
范例: int arr[]
定义了一个int类型的变量,变量名是arr数组
# 内存分配
Java 程序在运行时,需要在内存中分配空间。为了提高运算效率,就对空间进行了不同区域的划分,因为每一片区域都有特定的处理数据方式和内存管理方式。
下面分析数组的内存分配方式:
int[] arr = new int[3];
/**
堆内存: new int[3] 语句会使java在内存中开辟一个连续的能存储3个int默认值的空间,并为其添加索引编号,并返回数组的首元素内存地址给变量arr,之后通过索引偏移,指向其他数组元素,通过new出来的都有一个内存地址,在使用完后会通过垃圾回收机制回收
栈内存:程序运行时存储的变量名,如这里的arr,存储的是局部变量,运行完毕后立即消失
*/
int[] arr2 = arr // 多个数组变量指向同一个数组变量时,影响的都是同一份内存地址
# 数组初始化和访问
Java中的数组必须先初始化,然后才能使用,所谓初始化:就是为数组中的数组元素分配内存空间,并为每个数组元素赋值
动态初始化:初始化时只指定数组长度,由系统为数组分配初始值
格式: 数据类型[ ] 变量名 = new 数据类型[数组长度]; 范例 int[] arr = new int[3];
数组的访问方式:
数组内部保存的数据的访问方式 格式:数组名[索引] 索引是数组中数据的编号方式 作用:索引用于访问数组中的数据使用,数组名[索引]等同于变量名,是一种特殊的变量名 特征①:索引从0开始 特征②:索引是连续的 特征③:索引逐一增加,每次加1
代码演示
public class ArrayDemo { public static void main(String[] args) { // 动态数组初始化,自动分配初始值 int[] arr = new int[3]; /* * 左边的int说明数组的每个元素类型 * []说明是数组 * arr是数组变量名 * 右边的new为数组申请的内存空间 * int说明数组中的元素类型是int类型 * [3]说明数组长度为3*/ // 数组元素访问 System.out.println(arr); // 输出数组名,类似于首元素地址 [I@77459877 System.out.println(arr[0]); // 0 System.out.println(arr[1]); // 0 System.out.println(arr[2]); // 0 arr[2] = 300; System.out.println(arr[2]); // 300 } }
静态初始化:初始化时指定每个数组元素的初始值,由系统决定数组长度
格式:数据类型 [ ] 变量名 = new 数据类型[ ] { 数据1 , 数据2 , 数据3 , ……} ; 范例: int [ ] arr = new int[ ] { 1 , 2 , 3 } ; 简化格式: 数据类型 [ ] 变量名 = { 数据1 , 数据2 , 数据3 , ……} ; // 以后看到[]就想到是个数组,可以省略new 范例(推荐): int [ ] arr = { 1 , 2 , 3 } ; public class ArrayDemo { public static void main(String[] args) { // 静态数组初始化 int[] arr = {1,2,4,6}; // 数组元素访问 System.out.println(arr); // 输出数组名,类似于首元素地址 [I@77459877 System.out.println(arr[0]); // 1 System.out.println(arr[1]); // 2 System.out.println(arr[2]); // 4 } }
常见数组问题:
索引越界: 访问了数组中不存在的索引对应的元素,造成索引越界问题
int[] arr = {1,2,4,6}; System.out.println(arr[5]); // Index 5 out of bounds for length 4
空指针异常: 访问的数组已经不再指向堆内存的数据,造成空指针异常
int[] arr = {1,2,4,6}; arr = null; // null:空值,引用数据类型的默认值,表示不指向任何有效对象 System.out.println(arr[0]);
# 数组遍历
代码演示
int[] arr = {1, 2, 4, 6};
for (int x = 0; x < arr.length; x++) { // 数组.length 获取长度
System.out.println(arr[x]);
}
// while
int[] arr = {1, 2, 4, 6};
int j = 0;
while (j < arr.length) {
System.out.println(arr[j]);
j++;
}
练习:
// 获取数组中的最值
int[] arr = {1, 2, 4, 6};
int max = 0; // 需要给初始值,因为是局部变量
for (int i = 0; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
System.out.println(max);
# 方法
方法是具有独立功能的代码块组织成一个整体,使其具有特殊功能的代码集
注意:
1、方法必须先创建才可以使用,该过程称为方法的定义
2、方法创建后并不是直接执行,需要手动使用后才执行,该过程称为方法的调用
# 方法的定义
格式: public static void 方法名 ( ) {
//方法体
}
范例: public static void isEvenNumber() {
//方法体
}
# 方法的调用
格式: 方法名 ( );
范例: isEvenNumber( );
代码演示
public class ArrayDemo {
public static void main(String[] args) {
isEvenNumber(); // 调用
}
//定义一个变量,判断该数据是否是偶数
public static void isEvenNumber() { // 定义
int number = 10;
if (number % 2 == 0) {
System.out.println(true);
} else {
System.out.println(false);
}
}
}
带参方法的定义和调用
格式: public static void 方法名 ( 参数 ) { … … }
格式(单个参数): public static void 方法名 ( 数据类型 变量名 ) { … … }
范例(单个参数): public static void isEvenNumber( int number) { … … }
格式(多个参数): public static void 方法名 ( 数据类型 变量名1 ,数据类型 变量名2 ,…… ) { … … }
范例(多个参数): public static void getMax( int number1 , int number2 ) { … … }
/*
注意:
方法定义时,参数中的数据类型与变量名都不能缺少,缺少任意一个程序将报错
方法定义时,多个参数之间使用逗号( ,)分隔*/
带参方法调用(实参)
格式: 方法名 ( 参数 ) ;
格式(单个参数): 方法名 ( 变量名/常量值 ) ;
范例(单个参数): isEvenNumber( 5 ) ;
格式(多个参数): 方法名 ( 变量名1/常量值1 ,变量名2/常量值2 ) ;
范例(多个参数): getMax ( 5,6 ) ;
// 方法调用时,参数的数量与类型必须与方法定义中的设置相匹配,否则程序将报错
代码演示
/**
形参:方法定义中的参数
等同于变量定义格式,例如:int number
实参:方法调用中的参数
等同于使用变量或常量,例如: 10 number
*/
public class MethodDemo {
public static void main(String[] args) {
isEvenNumber(10); // 常量调用
int a = 3;
isEvenNumber(a); // 变量调用
System.out.println(maxInt(a,6)); // 多参数调用
}
public static void isEvenNumber(int number) { // 带参数的方法
if(number%2==0) {
System.out.println(true);
}else{
System.out.println(false);
}
}
public static int maxInt(int s1, int s2) { // 带返回值的方法
if (s1>s2) {
return s1;
}else{
return s2;
}
}
}
# 形参和实参
形参:由数据类型和变量名组成,通常作用到具体的局部变量中的参数
实参:是方法调用时的参数,要么是变量名,要么是常量,与形参相互呼应
# 带返回值的方法
格式: public static 数据类型 方法名( 参数 ) {
return 数据 ;
}
范例1:public static boolean isEvenNumber( int number ) {
return true ;
}
范例2:public static int getMax( int a, int b ) {
return 100 ;
}
//注意:方法定义时return后面的返回值与方法定义上的数据类型要匹配,否则程序将报错
# 方法的注意事项
- 方法不能嵌套定义,区别于Python的方法嵌套
- void表示无返回值,可以省略return,也可以单独的书写return,后面不加数据
# 方法的通用格式
格式: public static 返回值类型 方法名(参数) {
方法体;
return 数据 ;
}
/**
public static 修饰符,目前先记住这个格式
返回值类型 方法操作完毕之后返回的数据的数据类型,如果方法操作完毕,没有数据返回,这里写void,而且方法体中一般不写return
方法名 调用方法时候使用的标识
参数 由数据类型和变量名组成,多个参数之间用逗号隔开
方法体 完成功能的代码块
return 如果方法操作完毕,有数据返回,用于把数据返回给调用者
定义方法时,要做到两个明确
明确返回值类型:主要是明确方法操作完毕之后是否有数据返回,如果没有,写void;如果有,写对应的数据类型
明确参数:主要是明确参数的类型和数量
调用方法时
void类型的方法,直接调用即可
非void类型的方法,推荐用变量接收调用
*/
# 方法重载
方法重载指同一个类中定义的多个方法之间的关系,满足下列条件的多个方法相互构成重载
要求:
多个方法在同一个类中
多个方法具有相同的方法名
多个方法的参数不相同,类型不同或者数量不同
# 方法重载特点
- 重载仅对应方法的定义,与方法的调用无关,调用方式参照标准格式
- 重载仅针对同一个类中方法的名称与参数进行识别,与返回值无关,换句话说不能通过返回值来判定两个方法是否相互构成重载(和返回值无关)
//错误实例: 与返回值无关
public static void fn(int a) {
}
public static int fn(int a) {
}
// 正确实例
public static void fn(int a,int b) { // 或者类型不同
}
public static int fn(int a) {
return 0;
}
练习:使用方法重载的思路,设计比较两个整数是否相同的方法,兼容全整数类型(byte/short/int/long)
public class MethodDemo {
public static void main(String[] args) {
System.out.println(compare(10,20));
System.out.println(compare((byte)10,(byte)20));
System.out.println(compare((short)10,(short)20));
}
public static boolean compare(int a, int b) {
System.out.println("int");
return a == b;
}
public static boolean compare(long a, long b) {
System.out.println("long");
return a == b;
}
public static boolean compare(short a, short b) {
System.out.println("short");
return a == b;
}
public static boolean compare(byte a, byte b) {
System.out.println("byte");
return a == b;
}
}
# 方法参数传递
- 基础数据类型:对于基本数据类型的参数,形式参数的改变,不影响实际参数的值(因为是值拷贝)
public class MethodDemo {
public static void main(String[] args) {
int number = 100;
System.out.println("调用方法前number: "+number); // 100
change(number);
System.out.println("调用方法后number: "+number); // 100
}
public static void change(int a) { // 值拷贝
a = 200;
}
}
- 引用类型:对于引用类型的参数,形式参数的改变,影响实际参数的值
public class MethodDemo {
public static void main(String[] args) {
int[] arr = {1,2,4};
System.out.println("调用方法前number: "+arr[0]); // 1
change(arr);
System.out.println("调用方法后number: "+arr[0]); // 200
}
public static void change(int[] a) { // 传入的是数组的首地址的内存地址,所以为引用类型
a[0] = 200;
}
}
练习:遍历输出数组
public class MethodDemo {
public static void main(String[] args) {
int[] arr = {11,22,43,56};
printArray(arr);
}
public static void printArray(int[] a) { // 传入的是数组的首地址的内存地址,所以为引用类型
System.out.print("[");
for (int x= 0; x<a.length; x++){
if(x == a.length-1) {
System.out.print(a[x]);
}else{
System.out.print(a[x]+", ");
}
}
System.out.print("]");
}
}
练习:数组反转
public class MethodDemo {
public static void main(String[] args) {
int[] arr = {11, 22, 43, 56};
revserArray(arr);
for(int i=0;i<arr.length;i++) {
System.out.println(arr[i]);
}
}
public static void revserArray(int[] a) {
for (int start = 0, end = a.length - 1; start <= end; start++, end--) { // for循环中可以操作多个变量,表达式以;结束
int temp = a[start];
a[start] = a[end];
a[end] = temp;
}
}
}
# 面向对象
万物皆对象,客观存在的事物皆为对象
什么是对象属性:对象具有的各种特征,每个对象的每个属性都拥有特定的值
什么是对象行为:对象能够执行的操作
# 类和对象
类:类是对现实生活中一类具有共同属性和行为的事物的抽象 对象:是能够看得到摸的着的真实存在的实体
# 类定义
类的重要性:是Java程序的基本组成单位 类是什么:是对现实生活中一类具有共同属性和行为的事物的抽象,确定对象将会拥有的属性和行为
public class 类名 {
// 成员变量
变量1的数据类型 变量1;
变量2的数据类型 变量2;
…
// 成员方法
方法1;
方法2
}
/*
类的组成:属性和行为
属性:在类中通过成员变量来体现(类中方法外的变量)
行为:在类中通过成员方法来体现(和前面的方法相比去掉static关键字即可)
*/
对象定义和使用
创建对象
格式:类名 对象名 = new 类名();
范例:Phone p = new Phone();
使用对象
1:使用成员变量
格式:对象名.变量名
范例:p.brand
2:使用成员方法
格式:对象名.方法名()
范例:p.call()
练习:
// 类:Phone.java
package com.it;
public class Phone { // 在com.it的模块下创建Phone类
// 成员变量
String brand;
int price;
// 成员方法
public void call() { // 不需要static,(概念)用public修饰的static成员变量和成员方法本质是全局变量和全局方法,当声明它类的对象市,不生成static变量的副本,而是类的所有实例共享同一个static变量
System.out.println("打电话");
}
public void sendMessage() {
System.out.println("发短信");
}
}
//-----------------------------------------------------------
// 对象的使用:PhoneDemo.java
package com.it;
public class PhoneDemo { // 在com.it的模块下创建PhoneDemo类
public static void main(String[] args) {
// 创建对象
Phone p = new Phone(); // new出来的,在堆内存,有默认值
// 使用成员变量
System.out.println(p.brand); // null
System.out.println(p.price); // 0
p.brand = "mi";
p.price = 2999;
System.out.println(p.brand); // mi
System.out.println(p.price); // 2999
// 使用成员方法
p.call(); // 打电话
p.sendMessage(); // 发短信
}
}
# 对象内存图
package com.itheima;
public class PhoneDemo { // 在com.itheima的模块下创建PhoneDemo类
public static void main(String[] args) {
// 创建对象
Phone p = new Phone(); // new出来的,在堆内存,有默认值
// 使用成员变量
System.out.println(p.brand); // null
System.out.println(p.price); // 0
p.brand = "mi";
p.price = 2999;
System.out.println(p.brand); // mi
System.out.println(p.price); // 2999
// 使用成员方法
p.call(); // 打电话
p.sendMessage(); // 发短信
}
}
/**
1、首先main函数作为入口函数入栈,
2、创建对象,在堆区创建对象,默认初始化对象后,赋值给栈内变量p
3、多个对象的执行流程类似
4、多个对象指向相同对象,会相互影响,如:Phone p1 = p;
*/
# 成员变量和局部变量
成员变量:类中、方法外的变量
局部变量:方法中的变量
区别 | 成员变量 | 局部变量 |
---|---|---|
类中位置不同 | 类中方法外 | 方法内或者方法声明上 |
内存中位置不同 | 堆内存 | 栈内存 |
生命周期不同 | 随着对象的存在而存在,随着对象的消失而消失 | 随着方法的调用而存在,随着方法的调用完毕而消失 |
初始化值不同 | 有默认的初始化值 | 没有默认的初始化值,必须先定义,赋值,才能使用 |
演示
public class Phone { // 在com.itheima的模块下创建Phone类
// 成员变量
String brand;
int price;
// 成员方法
public void call() {
int i = 0; // 局部变量,栈中(运行时加载)
System.out.println("打电话"+i);
}
}
# 封装
private关键字:是一个权限修饰符,作用是保护成员不被别的类使用,被private修饰的成员只在本类中才能访问.
针对private修饰的成员变量,如果需要被其他类使用,提供相应的操作:
提供“get变量名()”方法,用于获取成员变量的值,方法用public修饰
提供“set变量名(参数)”方法,用于设置成员变量的值,方法用public修饰
// 类定义:Student.java package fengzhuang; public class Student { String name; private int age; // 私有成员变量,只允许类中方法访问 public void setAge(int a) { // 方法设置属性值 // 封装我们的age规则 if(a <0||a>100) { System.out.println("输入有误"); }else{ age = a; } } public int getAge() { // 通过get属性方法来获取 return age; } public void show() { System.out.println(name + "," + age); } } //------------------------------------------------ // StudentDemo.java package fengzhuang; public class StudentDemo { public static void main(String[] args) { Student s = new Student(); // 按理说为什么需要封装成方法的方式来修改成员变量? /** * 首先age变量比较敏感,不想让外界随意修改,我们需要进行封装 * 其次,通过封装成方法,我们可以判断设置的age是否合理 * 换句话说就是统一了设置成员变量的入口和封装了我们的规范 * */ s.setAge(18); s.name = "xxx"; s.show(); } }
this关键字
this修饰的变量用于指代成员变量: 方法的形参如果与成员变量同名,不带this修饰的变量指的是形参,而不是成员变量(同名,则局部变量优先) 方法的形参没有与成员变量同名,不带this修饰的变量指的是成员变量(不同名,name就是成员变量)
什么时候使用this呢?解决局部变量隐藏成员变量
this:代表所在类的对象引用
记住:方法被哪个对象调用,this就代表哪个对象
问题复现:
// 类中代码: public class Student { private String name = "wo"; private int age; // 私有成员变量,只允许类中方法访问 public void setAge(int a) { // 封装我们的age规则 if(a <0 || a>100) { System.out.println("输入有误"); }else{ this.age = a; } } public int getAge() { return age; } public void setName(String name) { System.out.println("修改之前:"+this.name); // 修改之前: wo name = name; // 问题出在这,局部变量name名和成员变量name冲突,局部变量优先,所以这里的name都是局部的name // 应该改为:this.name = name // 这里的this表示的成员变量 } public String getName(){ return name; } } //-------------------------------------------- //测试类 public class StudentDemo { public static void main(String[] args) { Student s = new Student(); s.setAge(18); s.setName("sss"); System.out.println(s.getAge()+", "+s.getName()); // 18,null 不是sss } }
this内存原理
1、在入口函数中创建对象,通过new在堆内存中开辟一个内存空间,返回一个内存地址给s,
2、当setName方法中的this,表示的就是s的内存地址,根据s的内存地址找到堆空间的对象数据,在未初始化时name属性都是null
3、换句话说,this就是这个对象
封装总结:
1、封装概述 是面向对象三大特征之一(封装,继承,多态) 是面向对象编程语言对客观世界的模拟,客观世界里成员变量都是隐藏在对象内部的,外界时无法直接操作的 2、封装原理 将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问 成员变量private,提供对应的getXxx()/setXxx()方法 3. 封装好处 通过方法来控制成员变量的操作,提高了代码的安全性,把代码用方法进行封装,提高了代码的复用性
# 构造方法
构造方法是一种特殊的方法,作用是用来初始化对象数据
public class 类名{
修饰符 类名( 参数 ) { // 类似于C++的构造函数
}
}
/*
注意事项:
构造方法的创建
如果没有定义构造方法,系统将给出一个默认的无参数构造方法
如果定义了构造方法,系统将不再提供默认的构造方法
构造方法的重载
如果自定义了带参构造方法,还要使用无参数构造方法,就必须再写一个无参数构造方法
推荐的使用方式
无论是否使用,都手工书写无参数构造方法
*/
// Student.java
package fengzhuang;
public class Student {
private String name;
private int age;
// 无参构造方法
public Student() {
System.out.println("无参构造");
}
// 重载有参构造方法
public Student(String name) {
this.name = name;
System.out.println("有参构造");
}
// 重载有参构造方法
public Student(String name, int age) {
this.name = name;
this.age = age;
System.out.println("有参构造");
}
public void show() {
System.out.println(age+" "+name); // 修改之前: wo
}
}
//----------------------------------------
// StudentDemo.java
package fengzhuang;
public class StudentDemo {
public static void main(String[] args) {
Student s = new Student(); // 无参构造
s.show(); // 0 null
Student s1 = new Student("wang"); // 有参构造
s1.show(); // 0,wang
Student s2 = new Student("wang",12); // 有参构造
s2.show(); // 12,wang
}
}
# 标准类制作
成员变量
使用private修饰
构造方法
提供一个无参构造方法
提供一个带多个参数的构造方法
成员方法
提供每一个成员变量对应的setXxx()/getXxx()
提供一个显示对象信息的show()
创建对象并为其成员变量赋值的两种方式
1、无参构造方法创建对象后使用setXxx()赋值
2、使用带参构造方法直接创建带有属性值的对象
# 字符串
API: 应用程序编程接口
Java API :指的就是 JDK 中提供的各种功能的 Java类,这些类将底层的实现封装了起来,我们不需要关心这些类是如何实现的, 只需要学习这些类如何使用即可,我们可以通过帮助文档来学习这些API如何使用
String 类在 java.lang 包下,所以使用的时候不需要导包,String 类代表字符串,Java 程序中的所有字符串文字(例如“abc”)都被实现为此类的实例,也就是说Java 程序中所有的双引号字符串,都是 String 类的对象
字符串的特点:
字符串不可变,它们的值在创建后不能被更改
虽然 String 的值是不可变的,但是它们可以被共享,字符串效果上相当于字符数组( char[] ),但是底层原理是字节数组( byte[] )
# String构造方法
通过查询java的开发文档,我们了解到String的构造函数有很多,下面我们挑选几个重要的进行讲解
方法名 | 说明 |
---|---|
public String() | 创建一个空白字符串对象,不含有任何内容 |
public String(char[] chs) | 根据字符数组的内容,来创建字符串对象 |
public String(byte[] bys) | 根据字节数组的内容,来创建字符串对象 |
String s = “abc”; | 直接赋值的方式创建字符串对象,内容就是abc |
代码演示
public class StringDemo {
public static void main(String[] args) {
// public String(): 创建一个空白字符串对象,不含有任何内容
String s1 = new String();
System.out.println(s1); //
// public String(char[] chs),根据字符数组创建字符串对象
char[] chs = {'a','b','3'};
String s2 = new String(chs); // ab3
System.out.println(s2);
// public String(byte[] bys) ,根据byte字节数组创建字符串对象
byte[] bys = {97,99,92};
String s3 = new String(bys);
System.out.println(s3); // ac\
// 直接赋值输出字符串(推荐)
String s4 = "abc";
System.out.printlb(s4); //abc
}
}
# String对象的特点
通过 new 创建的字符串对象,每一次 new 都会申请一个内存空间,虽然内容相同,但是地址值不同
char[] chs = {'a', 'b', 'c'}; String s1 = new String(chs); String s2 = new String(chs); //上面的代码中,JVM会首先创建一个字符数组,然后每一次new的时候都会有一个新的地址,只不过s1和s2参考的字符串内容是相同的
以“ ”方式给出的字符串,只要字符序列相同(顺序和大小写),无论在程序代码中出现几次,JVM 都只会建立一个 String 对象,并在字符串池中维护
String s3 = "abc"; String s4 = "abc"; //在上面的代码中,针对第一行代码,JVM 会建立一个 String 对象放在字符串池中,并给 s3 参考;第二行则让 s4 直接参考字符串池中的 String 对象,也就是说它们本质上是同一个对象
# String字符串的比较
使用 == 做比较
- 基本类型:比较的是数据值是否相同
- 引用类型:比较的是地址值是否相同,显然如果对象于对象之间的比较是基于引用的比较
字符串是对象,它比较内容是否相同,是通过一个方法来实现的,这个方法叫:equals(); public boolean equals(Object anObject):将此字符串与指定对象进行比较。由于我们比较的是字符串对象,所以参数直接传递一个字符串。
public class StringDemo {
public static void main(String[] args) {
// 构造方法的方式得到字符串对象
char[] chs = {'a', 'b', 'c'};
String s1 = new String(chs);
String s2 = new String(chs);
System.out.println(s1 == s2); // false
// 直接赋值的方式得到对象
String s3 = "abc";
String s4 = "abc";
System.out.println(s3 == s4); // true == 比较的是对象的内存地址是否相同
//比较字符串对象地址是否相同
System.out.println(s1 == s3); // false
System.out.println(s1.equals(s2)); // true
System.out.println(s1.equals(s3)); // true
System.out.println(s3.equals(s4)); // true
}
}
练习:
// 1、用户登陆
import java.util.Scanner;
public class StringDemo {
public static void main(String[] args) {
// 已知用户名和密码,定义两个字符串表示即可
String username = "itwang";
String password = "555";
for (int i=0; i<3;i++ ){
// 键盘录入要登陆的用户名和密码,用Scanner实现
Scanner sc = new Scanner(System.in);
System.out.println("请用户输入用户名:");
String name = sc.nextLine();
System.out.println("请用户输入密码:");
String pwd = sc.nextLine();
if (name.equals(username) && pwd.equals(password)){
System.out.println("登陆成功");
break;
}else{
System.out.println("登陆失败,您还有:"+(2-i)+"机会");
}
}
}
}
// 遍历字符串
import java.util.Scanner;
public class StringDemo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入遍历字符串:");
String str = sc.nextLine();
for(int i=0;i<str.length();i++){
System.out.println(str.charAt(i)); // charAt通过索引获取字符串值
}
}
}
// 求出键盘录入的字符串中的大写字母、小写字母、数字出现的次数
import java.util.Scanner;
public class StringDemo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入遍历字符串:");
String str = sc.nextLine();
int bigCount=0; // 函数中的变量必须赋值
int smCount=0;
int intCount=0;
for(int i=0;i<str.length();i++){
char ch = str.charAt(i); // charAt通过索引获取字符串值
if (ch > 'A' && ch < 'Z') {
bigCount++;
}else if(ch>'a' &&ch<'z') {
smCount++;
}else if(ch>'0'&& ch<'9'){
intCount++;
}
}
System.out.println("b:"+bigCount+"s:"+smCount+"i:"+intCount);
}
}
# 常见方法
String str = "abcdefg";
//length():统计当前字符串的字符个数
int i = str.length();
System.out.println(i);
//indexOf():查找指定字符再字符串中的位置
int index = str.indexOf("a");
System.out.println(index);
//小写转大写
//toUpperCase():将字符串中的字符变为了大写形式
String str = str.toUpperCase();
System.out.println(str);
//toLowerCase():将字符串中的字符变为小写
String str = "WWMMDDHH";
String str1 = str3.toLowerCase();
System.out.println(str);
//substring:截取字符串
String str = "abcdefg";
String str = str5.substring(0, 3);
System.out.println(str);
String str = str5.substring(3);
System.out.println(str);
//replaceAll:替换当前字符串中指定内容
String str = str5.replaceAll("abc", "xyz");
System.out.println(str);
//trim:能够去掉当前字符串中两端的空格
String str = " abc def ";
System.out.println(str.length());
String str1 = str9.trim();
System.out.println(str);
System.out.println(str1);
//字符串合字符串 + 等于拼接
String str1 = "123";
String str2 = "100";
System.out.println(str1+str2);
//将字符串变为整数
int num1 = Integer.parseInt(str1);
int num2 = Integer.parseInt(str2);
System.out.println(num1+num2);
String str1000 = "abcde";
//charAt:找到指定字符串中位置的字符
char char = str1000.charAt(2);
System.out.println(char);
# StringBuilder
首先我们知道String 创建的字符串对象是不可变的,如果创建后字符串,会在堆内存中的常量池中创建
public class StringDemo {
public static void main(String[] args) {
String s = "hello"; // 堆内存中创建字符串,放入常量池中,返回地址给栈上的s
s += " world"; // 堆内存中创建"world"字符串,在于原有的"hello"拼接,得到hello world的字符串存入堆中返回地址给s
System.out.println(s);
}
}
/**
如果对字符串进行拼接操作,每次拼接,都会构建一个新的 String 对象,既耗时,又浪费内存空间,而这种操作还不可避免。那么有没有一种比较好的方式可以解决这个问题呢?答案是肯定的,我们可以通过 Java 提供的 StringBuilder 类就来解决这个问题。*/
不可变字符串:String
可变字符串:StringBuilder 是一个可变的字符串类,我们可以把它看成是一个容器;这里的可变指的是 StringBuilder 对象中的内容是可变的
- StringBuilder构造方法:
方法名 | 说明 |
---|---|
public StringBuilder() | 创建一个空白可变字符串对象,不含有任何内容 |
public StringBuilder(String str) | 根据字符串的内容,来创建可变字符串对象 |
- StringBuilder的反转和追加
方法名 | 说明 |
---|---|
public StringBuilder append(任意类型) | 添加数据,并返回对象本身 |
public StringBuilder reverse() | 返回相反的字符序列 |
代码
public class StringDemo {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
System.out.println("sb:" + sb+"length:"+sb.length()); //
// 带参数构造
StringBuilder sb2 = new StringBuilder("java");
System.out.println("sb2:" + sb2+"length:"+sb2.length()); // java 4
StringBuilder sb3=sb2.append("wang"); // 返回一个builder
System.out.println("sb3:"+sb3); // javawang
System.out.println(sb3==sb2); // true 说明sb3是在sb2基础上追加数据的
// 简写
sb2.append("xxx"); // 因为该方法返回的是对象本身,所以可以不用赋值
sb2.append(10);
System.out.println(sb2); // javawangxxx10
// 链式编程
sb2.append("2o").append("333");
System.out.println(sb2); // javawangxxx102o333
// 反转
sb2.reverse();
System.out.println(sb2); // 333o201xxxgnawavaj
}
}
StringBuilder和String相互转化
/** StringBuilder 转换为 String: public String toString():通过 toString() 就可以实现把 StringBuilder 转换为 String String 转换为 StringBuilder: public StringBuilder(String s):通过构造方法就可以实现把 String 转换为 StringBuilder */ public class StringDemo { public static void main(String[] args) { StringBuilder sb = new StringBuilder(); sb.append("java"); // StringBuilder 转 String String s = sb.toString(); System.out.println(s); // String 转 StringBuilder String s1 = "xxx"; StringBuilder s2 = new StringBuilder(s1); System.out.println(s2); } }
练习:
// 字符串反转 public class StringDemo { public static void main(String[] args) { Scanner sc = new Scanner(System.in); System.out.println("请输出字符串:"); String str = sc.nextLine(); System.out.println(MyReverse(str)); } public static String MyReverse(String s){ return new StringBuilder(s).reverse().toString(); } }
# 继承
定义老师类和学生类,从中抽取出共同特征为一个父类,用继承的方式书写代码
// Person类
package jicheng;
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
}
// Tearch类
package jicheng;
public class Teacher extends Person {
public Teacher(String name,int age) {
super.setName(name); // 或者将父类的属性改为protect
super.setAge(age);
}
public Teacher(){} // 如果写了有参构造,就得写无参
public void teach() {
System.out.println("用爱教育");
}
}
// Dome测试类
package jicheng;
public class Demo {
public static void main(String[] args) {
Teacher t1 = new Teacher();
t1.setAge(12);
t1.setName("wang");
System.out.println(t1.getName()+"创建成功");
t1.teach();
Teacher t2 = new Teacher("xxx",22); // 需要在子类中写构造函数,不能使用父类得构造,且要调用父类得方法
System.out.println(t2.getName()+"创建成功");
t2.teach();
}
}
# 概念
继承是面向对象三大特征之一。可以使得子类具有父类的属性和方法,还可以在子类中重新定义,追加属性和方法
- 继承的格式: 格式:public class 子类名 extends 父类名 { } 范例:public class Zi extends Fu { } Fu:是父类,也被称为基类、超类 Zi:是子类,也被称为派生类
- 继承中子类的特点: 子类可以有父类的内容 子类还可以有自己特有的内容
# 继承的好处和弊端
好处:
- 提高了代码的复用性(多个类相同的成员可以放到同一个类中)
- 提高了代码的维护性(如果方法的代码需要修改,修改一处即可)
弊端:
- 继承让类与类之间产生了关系,类的耦合性增强了,当父类发生变化时子类实现也不得不跟着变化,削弱了子类的独立性
继承使用场景:
- 继承体现的关系:is a,如:猫是动物的一种
- 假设法:我有两个类A和B,如果他们满足A是B的一种,或者B是A的一种,就说明他们存在继承关系,这个时候就可以考虑使用继承来体现,否则就不能滥用继承
- 举例:苹果和水果,猫和动物,猫和狗
# 继承变量/方法访问
在子类方法中访问一个变量:
- 子类局部范围找(函数中局部优先级最高)
- 子类成员范围找(如果本类有就在本类中取)
- 父类成员范围找,再没就报错
继承中成员方法的访问特点:
- 子类成员范围找
- 父类成员范围找
- 如果都没有就报错(不考虑父亲的父亲…)
继承中构造方法的访问特点:
子类中所有的构造方法默认都会访问父类中无参的构造方法(无论子类是有参还是无参都调父类的无参),为什么?
因为子类会继承父类中的数据,可能还会使用父类的数据。所以,子类初始化之前,一定要先完成父类数据的初始化
每一个子类构造方法的第一条语句默认都是:super(),这里的super() 调用的是父类的无参构造
如果父类中没有无参构造方法,只有带参构造方法,该怎么办呢? 1、通过使用super关键字去显示的调用父类的带参构造方法,比如我们可以通过super(10)来间接初始化 2、在父类中自己提供一个无参构造方法 推荐:自己给出无参构造方法
代码演示
// Fu.java
package jicheng;
public class Fu {
public int age;
public Fu() {
System.out.println("父类的无参构造");
}
public Fu(int age) {
System.out.println("父类的有参构造");
}
}
// Zi.java
package jicheng;
public class Zi extends Fu {
public Zi(){
// super() // 所有的子类构造都有这句话
System.out.println("子类的无参构造");
}
public Zi(int age){
// super() // 所有的子类构造都有这句话
this.age = age;
System.out.println("子类的有参构造");
}
}
//Demo.java
package jicheng;
public class Demo {
public static void main(String[] args) {
Zi z = new Zi();
System.out.println("Demo:"+z.age); // 23,访问的是本对象的属性
Zi z2 = new Zi(3);
/*输出:
*父类的无参构造
子类的无参构造
Demo:0
父类的无参构造
子类的有参构造 */
}
}
# super
super 关键字的用法和 this 关键字的用法相似
- this:代表本类对象的引用
- super:代表父类存储空间的标识(可以理解为父类对象引用)
关键字 | 访问成员变量 | 访问构造方法 | 访问成员方法 |
---|---|---|---|
this | this.成员变量访问本类成员变量 | this(…) 访问本类构造方法 | this.成员方法(…) 访问本类成员方法 |
super | super.成员变量访问父类成员变量 | super(…) 访问父类构造方法 | super.成员方法(…) 访问父类成员方法 |
代码
// Fu.java
package jicheng;
public class Fu {
public int age = 12;
}
// Zi.java
package jicheng;
public class Zi extends Fu {
public int age =23;
public void show() {
int age = 2;
System.out.println(age); // 2
System.out.println(this.age); // 23
System.out.println(super.age); // 12
}
}
// Demo.java
package jicheng;
/*
* 测试类
* */
public class Demo {
public static void main(String[] args) {
Zi z = new Zi();
System.out.println("Demo:"+z.age); // 23,访问的是本对象的属性
z.show();
}
}
# 方法重写
方法重写概述:子类中出现了和父类中一模一样的方法声明
方法重写的应用:当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法,这样,即沿袭了父类的功能,又定义了子类特有的内容
@Override:是一个注解(注解后面会学习到),可以帮助我们检查重写方法的方法声明的正确性
练习:手机类和新手机类
// Phone.java
package com.it;
public class Phone {
public void call(String name) {
System.out.println("给"+name+"打电话");
}
}
// NewPhone.java
package com.it;
public class NewPhone extends Phone {
// 添加自己特有的功能,同时延续父类的基础功能
@Override // 如果写了Override,可以帮我们检测下面的名字是否正确
public void call(String name) {
System.out.println("开启视频功能");
//System.out.println("给"+name+"打电话");
super.call(name);
}
}
//PhoneDemo.java
package com.it;
public class PhoneDemo {
public static void main(String[] args) {
Phone p = new Phone();
p.call("王");
System.out.println("--------");
NewPhone p1 = new NewPhone();
p1.call("王");
}
}
方法重写注意事项:
- 私有方法不能被重写(父类私有成员子类是不能继承的)
- 子类方法访问权限不能更低(public > 默认 > 私有)
代码演示
// Phone.java
package com.it;
public class Phone {
private void show() {
System.out.println("父类中的show方法");
}
public void method() {
System.out.println("父类中的method方法");
}
protected void tet() {
System.out.println("父类的tet方法");
}
}
// NewPhone.java
package com.it;
public class NewPhone extends Phone {
/*
@Override // 报错,父类中的私有方法不能被子类重写
private void show() {
System.out.println("子类中的show方法");
}*/
/*
@Override
void method() { // 报错,因为父类的public权限最大,但是这里的方法没有权限修饰符,
// 默认权限是小于public的,所以报错,要么添加pubulic,要么父类去掉public
System.out.println("子类中的method方法");
}*/
@Override // 成功
public void tet(){ // 父类的是protect权限,子类改为public、protected都可以,只要不低于protect就行
System.out.println("子类的tet方法");
}
/*
@Override // 成功
public void method() {
System.out.println("子类中的method方法");
}*/
}
# 继承注意事项
- Java中类只支持单继承,不支持多继承
- Java中类支持多层继承
代码演示
// Ma.java
package jicheng;
public class Ma {
public void dance() {
System.out.println("妈妈爱跳舞");
}
}
// Fu.java
package jicheng;
public class Fu extends YeYe {
}
// Zi.java
/*public class Zi extends Fu,Ma { //错误, 不能多继承,但可以层次嵌套继承
}*/
练习:定义猫和狗类
// Cat.java
package jicheng;
public class Animal {
private String name;
private int age;
public Animal(){};
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
// Cat.java
package jicheng;
public class Cat extends Animal{
public Cat() {
}
public Cat(String name, int age) {
super(name, age); // 默认调用父类的带参构造函数,使用command+n快速生成
}
public void catchMouse(){
System.out.println("猫抓老鼠");
}
}
//Demo.java
package jicheng;
/*
* 测试类
* */
public class Demo {
public static void main(String[] args) {
Cat c1 = new Cat();
c1.setName("加菲猫");
c1.setAge(12);
System.out.println(c1.getName()+","+c1.getAge());
c1.catchMouse();
Cat c2 = new Cat("波斯猫",1);
System.out.println(c2.getName()+","+c2.getAge());
c2.catchMouse();
}
}
# 包
# 建包
概述:其实就是文件夹;
作用:对类进行分类管理
包的定义格式:
- 格式:package 包名; (多级包用.分开)
- 范例:package com.itwang;
package com.it; // 如果不导包,直接就可以javac HelloWorld.java ,再java HelloWorld就可以执行,如果导包后就会将生成的class文件放到包下,执行也要去包下执行
public class Demo {
public static void main(String[] args) {
System.out.println("Hello World")
}
}
带包的java类编译和执行:
手动建包:
1、按照以前的格式编译java文件 javac HelloWorld.java 2、手动创建包 在E盘建立文件夹com,然后在com下建立文件夹it 3、把class文件放到包的最里面 把HelloWorld.class文件放到com下的it这个文件夹下 4、带包执行 java com.it.HelloWorld
自动建包: (建包后所有的class文件都在这里)
1、javac –d . HelloWorld.java 在当前文件下生成class文件 2、java com.itheima.HelloWorld
# 导包
使用不同包下的类时,使用的时候要写类的全路径,写起来太麻烦了,为了简化带包的操作,Java就提供了导包的功能
导包的格式:
- import 包名;
- 范例:import cn.it.Teacher
代码演示:
/* 文件目录
|-src
|- cn
|-Teacher.java
|-com
|-Demo.java
*/
// Teacher.java
package cn;
public class Teacher {
public void teach() {
System.out.println("有爱走遍天下");
}
}
// Demo.java
package com;
import cn.Teacher; // 导包
public class Demo {
public static void main(String[] args) {
// 方式一
cn.Teacher t2 = new cn.Teacher();
t2.teach();
// 方式二:导包,更简单
Teacher t3 = new Teacher();
t3.teach();
}
// 对比
java.util.Scanner sc = new java.util.Scanner(System.in);
}
# 修饰符
# 权限修饰符
修饰符 | 同一个类中 | 同一个包中非子类的无关类和同一个包中的子类 | 不同包的子类 | 不同包的无关类 |
---|---|---|---|---|
private | √ | |||
默认 | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
- private修饰的只能在本类中访问到
- 同一个包内的非子类的无关类可以访问非private的其他属性和方法
- 不同包下的子类,只能访问protected和public,无关类只能访问public
# 状态修饰符
final 关键字是最终的意思,可以修饰成员方法,成员变量,类
final 修饰的特点
- 修饰方法:表明该方法是最终方法,不能被重写
- 修饰变量:表明该变量是常量,不能再次被赋值
- 修饰类:表明该类是最终类,不能被继承
代码:
public class FinalDemo {
public static void main(String[] args) {
/*
int age = 20;
age = 100;
System.out.println(age); // 正常输出100
*/
// final修饰基本类型变量
final int age = 12;
// age =34; // 报错,无法被修改
System.out.println(age);
// final修饰引用类型变量
Student s = new Student();
s.age = 100;
System.out.println(s.age); // 100
final Student s1 = new Student();
s1.age = 2;
System.out.println(s1.age); // 2, 这里是可以变的,原因是final修饰的是引用的地址,只要地址不变,值可以变(类似C++常量指针)
// s1 = new Student(); // 报错
}
}
final修饰局部变量:
- 变量是基本类型:final 修饰指的是基本类型的数据值不能发生改变
- 变量是引用类型:final 修饰指的是引用类型的地址值不能发生改变,但是地址里面的内容是可以发生改变的
static 关键字是静态的意思,可以修饰成员方法,成员变量
static 修饰的特点(举个例子:公司会给办公司配个饮水机【员工们可以共享,一个饮水机,如果你特殊你可以单独申请一个饮水机】,但是不会给每个人陪个水杯,这里的水杯就是不共享的属性)
- 被类的所有对象共享:这也是我们判断是否使用静态关键字的条件
- 可以通过类名调用:当然,也可以通过对象名调用 (推荐使用类名调用)
代码
// StaticDemo.java
package Finall;
public class StaticDemo {
public static void main(String[] args) {
// 如果一个成员变量被static修饰,建议通过类.静态名修改
Student.university ="sanLiTun"; // 推荐
Student s = new Student();
s.age = 12;
//s.university="ChongQing";
s.name = "Wang";
s.show();
Student s2 = new Student();
s2.age = 15;
//s2.university="ChongQing"; // 如果这里重新赋值,那么后面的在没修改情况下就都是这个了,也就是说static只有一个坑位
s2.name = "Li";
s2.show();
/*
* 这里有个问题:如果有多个学生,那么我就要写多个重复的属性,比如这里的university,他们都是来之一个大学,不想每次都输入,如果不写,又是null
* 这是在 Student类中的 public String university;的情况下,如果我们将其改为public static university,那么多个对象的这个属性将会
* 指向同一个静态变量区
* */
Student s3 = new Student();
s3.age = 17;
s3.name = "xing";
s3.show(); // xing,17,sanLiTun
}
}
// Student.java
package Finall;
public class Student {
public int age;
public String name;
// public String university;
public static String university;
public void show() {
System.out.println(name+","+age+","+university);
}
}
static访问特点
非静态的成员方法:
- 能访问静态的成员变量
- 能访问非静态的成员变量
- 能访问静态的成员方法
- 能访问非静态的成员方法
静态的成员方法
- 能访问静态的成员变量
- 能访问静态的成员方法
总结成一句话就是:静态成员方法只能访问静态成员
代码演示
package Finall;
public class Student {
// 非静态成员变量
private String name = "王";
// 静态成员变量
private static String university = "清华";
// 非静态成员方法
public void show1() {
}
// 非静态成员方法
public void show2() {
System.out.println(name);
System.out.println(university);
show1();
show3();
// 非静态成员方法可以访问所有的静态成员变量和方法
}
// 静态成员方法
public static void show3() {
//System.out.println(name); // 报错
System.out.println(university);
//show1(); // 报错
show4();
// 静态成员方法只能访问静态的成员和方法: 思考,因为main函数是静态的,所以在其中调用的函数也应该是静态的
}
// 静态成员方法
public static void show4() {
}
}
# 多态
同一个对象,在不同时刻表现出来的不同形态
举例:猫 我们可以说猫是猫:猫 cat = new 猫(); 我们也可以说猫是动物:动物 animal = new 猫(); 这里猫在不同的时刻表现出来了不同的形态,这就是多态。
多态的前提和体现:
- 有继承/实现关系
- 有方法重写
- 有父类引用指向子类对象,如:动物 animal = new 猫()
// Animal.java
package it;
public class Animal {
public void eat(){
System.out.println("动物吃东西");
}
}
// Cat.java
package it;
public class Cat extends Animal{ // 1、继承关系
// 2、重写eat方法
@Override
public void eat(){
System.out.println("猫吃鱼");
}
}
// AnimalDemo.java
package it;
public class AnimalDemo {
public static void main(String[] args) {
// 3、有父类引用指向子类对象
Animal a = new Cat(); // 这里的 Animal a 就是父类引用,也就是说多个继承Animal的对象都可以
a.eat();
}
}
# 多态中成员访问特点
- 成员变量:编译看左边,执行看左边
- 成员方法:编译看左边,执行看右边(如果没有左边实现,则调用父类的)
为什么成员变量和成员方法的访问不一样呢?因为成员方法有重写,而成员变量没有,换句话说只有实现重写的才能被多态,才能通过多态,在父类的对象中被调用
// Animal.java
package it;
public class Animal {
// 成员变量
public int age = 40;
public void eat(){
System.out.println("动物吃东西");
}
}
// Cat.java
package it;
public class Cat extends Animal{ // 1、继承关系
// 成员变量
public int age = 20;
public int weight =12;
// 2、重写eat方法
@Override
public void eat(){
System.out.println("猫吃鱼");
}
public void playGame(){
System.out.println("猫猫笑");
}
}
// AnimalDemo.java
package it;
public class AnimalDemo {
public static void main(String[] args) {
// 3、有父类引用指向子类对象
Animal a = new Cat(); // 编译看左边
System.out.println(a.age); // 40 成员变量编译和执行都看左边
// System.out.println(a.weight); // 报错,因为此时创建的是Animal的对象a,所以只能访问Animal中的age,没有weight
a.eat(); // 猫吃鱼, 方法执行右边的继承类的方法
// a.playGame(); // 报错,编译时看左边,也就是说只有左边有这个方法,才能实现编译,
}
}
# 好处和弊端
多态的好处:提高了程序的扩展性 具体体现:定义方法的时候,使用父类型作为参数,将来在使用的时候,使用具体的子类型参与操作
多态的弊端:不能使用子类的特有功能,必须在父类中有对应的实现才行
// Animal.java
package it;
public class Animal {
public void eat(){
System.out.println("动物吃东西");
}
}
// Cat.java
package it;
public class Cat extends Animal{ // 1、继承关系
// 2、重写eat方法
@Override
public void eat(){
System.out.println("猫吃鱼");
}
}
// Dog.java
package it;
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
// AnimalOperator.java
package it;
public class AnimalOperator {
public void useAnimal(Animal a){ // 统一所有实现了Animal的多态对象的调用方法,就不用特别复杂,针对每个类去调用
a.eat();
}
}
// AnimalDemo.java
package it;
public class AnimalDemo {
public static void main(String[] args) {
AnimalOperator ao = new AnimalOperator();
Cat c = new Cat();
Dog d= new Dog();
ao.useAnimal(c);
ao.useAnimal(d);
}
}
# 多态的转型
- 向上转型,从子到父,父类引用指向子类对象
- 向下转型,从父到子,父类引用转为子类对象,如果没有继承关系的强转会报错
// AnimalDemo.java
package it;
public class AnimalDemo {
public static void main(String[] args) {
Animal a = new Cat(); // 父类引用指向子类对象:向上转型
a.eat(); // 猫吃鱼
// a.playGame // 编译时报错,无法调用工,只能手动实现一个Cat对象
/* 为了调用playGame方法而重新实现一个c对象,没必要
Cat c = new Cat();
c.eat(); // 猫吃鱼
c.playGame(); // 猫猫笑*/
Cat c = (Cat)a; // 父类引用转为子类对象:向下转型
c.eat();
c.playGame();
}
}
# 抽象类
在Java中,一个没有方法体的方法应该定义为抽象方法,而类中如果有抽象方法,该类必须定义为抽象类
# 抽象类的特点
抽象类和抽象方法必须使用 abstract 关键字修饰
public abstract class 类名 {} public abstract void eat();
抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
抽象类不能实例化:抽象类如何实例化呢?参照多态的方式,通过子类对象实例化,这叫抽象类多态
抽象类的子类:要么重写抽象类中的所有抽象方法;要么是抽象类(变成抽象类后,他的子类还是要实现抽象方法)
// Animal.java
package it;
public abstract class Animal { // 抽象类,有抽象方法的必须是抽象类
/*
public void eat(){
System.out.println("动物吃东西");
}*
凡事父类中,子类要重写的方法,都应该设置为抽象方法,否则子类继承后,如果没有重写将会容到父类的这个方法中
*/
public abstract void eat(); // 使用abstract来使方法没有方法体,且有抽象方法的类必须是抽象类
public void sleep() { // 抽象类中不一定有抽象方法
System.out.println("睡觉");
}
}
// Cat.java
package it;
public class Cat extends Animal{ // 1、继承抽象类,重写抽象方法
// 2、重写eat方法
@Override
public void eat(){
System.out.println("猫吃鱼");
}
public void sleep(){
System.out.println("猫睡觉");
}
public void look(){
System.out.println("看看");
}
}
// Dog.java
package it;
public abstract class Dog extends Animal { // 我就不想重写怎么板,加abstract
}
// AnimalDemo.java
package it;
public class AnimalDemo {
public static void main(String[] args) {
//Animal a = new Animal(); // 报错, 抽象类无法实例出对象
Animal a = new Cat(); // 采用继承的方法进行抽象实例
a.eat(); // 猫吃鱼
a.sleep(); // 睡觉 ,抽象类中的sleep会被a实例继承,如果是a中本身有该方法优先
//a.look(); // 报错,a为Animal中的引用,无法调用Cat中的look方法
// 总之,a是Animal的引用,向上转型后只能调用Animal中神明的方法,且如果子类中重写了,会优先重写的
Cat c = (Cat)a;
c.look(); // 向下转型
// 抽象子类Dog
// Animal d = new Dog(); // 报错,因为Dog也是抽象类,不能被实例,只能被继承
}
}
# 抽象类的成员特点
- 成员变量:可以是变量,也可以是常量
- 构造方法:有构造方法,但是不能实例化,那么,构造方法的作用是什么呢?用于子类访问父类数据的初始化
- 成员方法:可以有抽象方法:限定子类必须完成某些动作;也可以有非抽象方法:提高代码复用
package it;
public abstract class Animal { // 抽象类,有抽象方法的必须是抽象类
private int age = 20;
private final String city ="cq";
public Animal() {}
public void show() {
System.out.println(age+", "+city);
}
public abstract void eat();
}
# 接口
接口就是一种公共的规范标准,只要符合规范标准,大家都可以通用,Java中的接口更多的体现在对行为的抽象
# 接口特点
接口用关键字interface修饰:public interface 接口名 {}
类实现接口用implements表示:public class 类名 implements 接口名 {}
接口不能实例化
接口如何实例化呢?参照多态的方式,通过实现类对象实例化,这叫接口多态。 多态的形式:具体类多态,抽象类多态,接口多态。 多态的前提:有继承或者实现关系;有方法重写;有父(类/接口)引用指向(子/实现)类对象
接口的子类:要么重写接口中的所有抽象方法;要么是抽象类
// Jumpping.java
package chouxiang;
// 接口定义
public interface Jumping {
public abstract void jump();
}
// Cat.java
package chouxiang;
public class Cat implements Jumping {
@Override
public void jump() {
System.out.println("猫可以跳高了");
}
}
// JumpingDemo.java
package chouxiang;
public class JumppingDemo {
public static void main(String[] args) {
// Jumpping j = new Jumpping // 报错,接口不能被直接实例
Jumping j = new Cat();
j.jump(); // 猫可以跳高了
}
}
# 接口的成员特点
- 成员变量 只能是常量,默认修饰符:public static final
- 构造方法 没有,因为接口主要是扩展功能的,而没有具体存在
- 成员方法 只能是抽象方法 默认修饰符:public abstract 关于接口中的方法,JDK8和JDK9中有一些新特性,后面再讲解
// Jumpping.java
package chouxiang;
// 接口定义
public interface Jumping {
public int num = 10; // 等价与 public static final int num = 10;
public final int num2 = 13;
public abstract void jump();
void show(); // 默认给我们添加了 public abstract
// public Jumping () {}; // 报错,接口是没有构造函数了,有人问了,那子类的super()访问的是哪一个父类那,是所有类的父类Object
// public class JumImpl extends Object implements Jumping {}
// public void show() {}; // 报错,不能有非抽象方法
}
//JumImpl.java
package chouxiang;
public class JumImpl implements Jumping {
@Override
public void show() {
System.out.println("show子类方法");
}
@Override
public void jump() {
System.out.println("jump方法实现");
}
}
//JumppingDemo.java
package chouxiang;
public class JumppingDemo {
public static void main(String[] args) {
JumImpl j = new JumImpl();
//j.num = 22; // 报错,默认的话接口的变量都是声明为 public static final
System.out.println(j.num);
//j.num2 = 21; // final无法被访问
System.out.println(j.num2);
System.out.println(Jumping.num); // 接口可以直接访问变量
j.jump();
}
}
# 类和接口的关系
- 类和类的关系:继承关系,只能单继承,但是可以多层继承
- 类和接口的关系:实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口
- 接口和接口的关系:继承关系,可以单继承,也可以多继承
// 类和接口关系:
public class JumImpl extends Object implements Jumping,Jumping2 {}
// 接口和接口关系:
public interface Jumping extends Jumping3 {}
public interface Jumping extends Jumping3,Inter4 {}
# 抽象类和接口的区别
成员区别
- 抽象类 变量,常量;有构造方法;有抽象方法,也有非抽象方法
- 接口 常量;抽象方法
关系区别
- 类与类 继承关系,单继承
- 类与接口 实现关系,可以单实现,也可以多实现
- 接口与接口 继承关系,单继承,多继承
设计理念区别
- 抽象类 对类抽象,包括属性、行为
- 接口 对行为抽象,主要是行为
举个栗子:
门和报警器的例子:
//门:都有open()和close()两个动作,这个时候,我们可以分别使用抽象类和接口来定义这个抽象概念
//抽象类
public abstract class Door {
public abstract void open();
public abstract void close();
}
// 接口
public interface Door {
void open();
void close();
}
// 随着时间发展,目前很多门都有报警器了,那如何把报警器融合到类中那,是在接口和抽象类中都添加报警方法吗?显然不好,
public interface Alram { // 接口是对行为的抽象
void alarm();
}
public abstract class Door { // 抽象类对事物的抽象
public abstract void open();
public abstract void close();
}
public class AlarmDoor extends Door implements Alarm {
public void oepn() {
//....
}
public void close() {
//....
}
public void alarm() {
//....
}
}
# 形参和返回值
# 类名作为形参和返回值
- 方法的形参是类名,其实需要的是该类的对象
- 方法的返回值是类名,其实返回的是该类的对象
// Cat.java
package xingcan;
public class Cat {
public void eat() {
System.out.println("猫吃鱼");
}
}
// CatOperator.java
package xingcan;
public class CatOperator {
public void useCat(Cat c) { // 类名作为形参时,需要传入对象
c.eat();
}
public Cat getCat() { // 类名作为返回值都是需要返回对象
Cat c = new Cat();
return c;
}
}
// CatDemo.java
package xingcan;
public class CatDemo {
public static void main(String[] args) {
CatOperator co = new CatOperator(); // 创建操作类的对象
Cat c = new Cat();
co.useCat(c); // 需要创建一个Cat对象传入
Cat c2 = co.getCat();
c2.eat();
}
}
# 抽象类名作为形参和返回值
- 方法的形参是抽象类名,其实需要的是该抽象类的子类对象
- 方法的返回值是抽象类名,其实返回的是该抽象类的子类对象
//Animal.java
package xingcan;
public abstract class Animal {
public abstract void eat();
}
//AnimalOperator.java
package xingcan;
public class AnimalOperator {
public void useAnimal(Animal a){
a.eat();
}
public Animal getAnimal() {
return new Cat();
}
}
//Cat.java
package xingcan;
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
//AnimalDemo.java
package xingcan;
public class AnimalDemo {
public static void main(String[] args) {
AnimalOperator ao= new AnimalOperator();
Animal a = new Cat();
ao.useAnimal(a); // 创建实现抽象类的子类传入
Animal a2 = ao.getAnimal();
a2.eat();
}
}
# 接口名作为形参和返回值
- 方法的形参是接口名,其实需要的是该接口的实现类对象
- 方法的返回值是接口名,其实返回的是该接口的实现类对象
//Jumpping.java
package xingcan;
public interface Jumpping {
void jump();
}
//JumppingOperator
package xingcan;
public class JumppingOperator {
public void useJumpping(Jumpping j){
j.jump();
}
public Jumpping getJumping(){
Jumpping j = new Cat(); // 返回一个接口实现的对象
return j;
}
}
//Cat.java
package xingcan;
public class Cat implements Jumpping {
@Override
public void jump() {
System.out.println("猫猫跳");
}
}
// JumppingDemo.java
package xingcan;
public class JumppingDemo {
public static void main(String[] args) {
JumppingOperator jo = new JumppingOperator();
Jumpping j = new Cat(); // 使用多态的方式实现接口,也就是说Jumping接口的对象引用可以用实现他的类对象
jo.useJumpping(j); // 需要实现接口的对象
Jumpping j2 = jo.getJumping(); // 接口返回的是实现的接口的对象
j2.jump();
}
}
# 内部类
内部类:就是在一个类中定义一个类。举例:在一个类A的内部定义一个类B,类B就被称为内部类
格式:
public class 类名{
修饰符 class 类名{
}
}
范例:
public class Outer {
public class Inner {
}
}
# 内部类的访问特点
- 内部类可以直接访问外部类的成员,包括私有
- 外部类要访问内部类的成员,必须创建对象
public class Outer {
private int num =10;
public class Inner {
public void show() {
System.out.println("外部内私有:"+num); // 可以访问外部内属性/方法
}
}
public void method() {
//show(); // 不能直接访问内部类的方法/属性
//通过创建内部类的对象去访问
Inner i = new Inner();
i.show();
}
}
# 内部类分类
按照内部类在类中定义的位置不同,可以分为如下两种形式:
- 在类的成员位置:成员内部类
- 在类的局部位置:局部内部类
成员内部类,外界如何创建对象使用呢?
- 格式:外部类名.内部类名 对象名 = 外部类对象.内部类对象; 范例:Outer.Inner oi = new Outer().new Inner();
// Outer.java
package it;
public class Outer {
private int num =10;
// 注意:一般来说,内部类创建只给内部成员访问,不会使用public权限
public class Inner { // 成员内部类
public void show() {
System.out.println("外部内私有:"+num); // 可以访问外部内属性/方法
}
}
private class Inner2 { // 成员内部类
public void show() {
System.out.println("私有外部内私有:"+num); // 可以访问外部内属性/方法
}
}
public void method() {
//show(); // 不能直接访问内部类的方法/属性
//通过创建内部类的对象去访问
Inner i = new Inner();
i.show();
Inner2 i2 = new Inner2();
i2.show(); // 通过提供的method方法返回内部类的属性/方法结果
}
}
// InnerDemo.java
package it;
public class InnerDemo {
public static void main(String[] args) {
// 一般不这样写,内部类不会提供给外部使用
Outer.Inner i = new Outer().new Inner(); // 创建内部类对象,并调用
i.show();
// 通过外部类的公用函数来提供服务
Outer o = new Outer();
o.method(); // 会执行内部类的方法
}
}
局部内部类:局部内部类是在方法中定义的类,所以外界是无法直接使用,需要在方法内部创建对象并使用;该类可以直接访问外部类的成员,也可以访问方法内的局部变量
//Outer.java
package it;
public class Outer {
private int num = 10;
public void method() {
int num2 = 20; // 方法中的也可以使用
class Inner { // 局部内部类
public void show() {
System.out.println("外部内私有:" + num+" method的变量:"+num2); // 可以访问外部类属性/方法
}
}
// 调用内部类,通过内部类对象
Inner i = new Inner();
i.show();
}
}
//InnerDemo.java
package it;
public class InnerDemo {
public static void main(String[] args) {
Outer o = new Outer();
o.method();
}
}
# 匿名内部类
前提:存在一个类或者接口,这里的类可以是具体类也可以是抽象类
格式:
new 类名或者接口名 () {
重写方法;
};
范例:
new Inter() {
public void show() {
}
}
本质:是一个继承了该类或者实现了该接口的子类匿名对象
// Inter.java
package it;
public interface Inter {
void show();
}
// Outer.java
package it;
public class Outer {
public int cout = 0; // 内部类中的变量必须在外部类中提前声明
public void method() {
// int cout = 1; // 报错,内部类中使用但未声明的任何局部变量必须在内部类的正文之前明确分配。
// 匿名内部类是局部内部类的一种
new Inter() { // 存在一个类或者接口,这里的类可以是具体类也可以是抽象类
@Override
public void show() {
cout +=1;
System.out.println("匿名内部类: "+ cout); // 匿名内部类: 1
}
}.show(); // 相当于匿名对象掉了show方法
// 更加方便的以便调用,所以可以赋值, 匿名内部类可以赋值个接口/类
Inter i = new Inter() { // 存在一个类或者接口,这里的类可以是具体类也可以是抽象类,这样可以多次使用
@Override
public void show() {
cout +=1;
System.out.println("匿名内部类: "+cout);
}
};
i.show(); // 匿名内部类: 2
i.show(); // 匿名内部类: 3
}
}
// InnerDemo.java
package it;
public class InnerDemo {
public static void main(String[] args) {
Outer o = new Outer();
o.method();
}
}
匿名内部类的使用
// Jumpping.java
package it;
public interface Jumpping {
void jump();
}
//JumppingOperator.java
package it;
public class JumppingOperator {
public void method(Jumpping j) {
j.jump();
}
}
//Cat.java
package it;
public class Cat implements Jumpping {
@Override
public void jump() {
System.out.println("猫跳高");
}
}
//JumppingDemo.java
package it;
public class JumppingDemo {
public static void main(String[] args) {
JumppingOperator jo = new JumppingOperator();
Jumpping j = new Cat();
jo.method(j);
// 如果我要添加个狗可以跳高,那么还要添加Dog类,很复杂
System.out.println("---------------------");
//使用匿名内部类
jo.method(new Jumpping() { // 通过匿名内部类,返回实现接口/类的对象
@Override
public void jump() {
System.out.println("狗可以跳高了");
}
});
jo.method(new Jumpping() {
@Override
public void jump() {
System.out.println("猪可以跳高了");
}
});
}
}
# 常用API
# Math类
Math 包含执行基本数字运算的方法;没有构造方法,
如何使用类中的成员呢?看类的成员是否都是静态的,如果是,通过类名就可以直接调用
Math类的常用方法:
public static int abs(int a) | 返回参数的绝对值 |
---|---|
public static double ceil(double a) | 返回大于或等于参数的最小double值,等于一个整数 |
public static double floor(double a) | 返回小于或等于参数的最大double值,等于一个整数 |
public static int round(float a) | 按照四舍五入返回最接近参数的int |
public static int max(int a,int b) | 返回两个int值中的较大值 |
public static int min(int a,int b) | 返回两个int值中的较小值 |
public static double pow(double a,double b) | 返回a的b次幂的值 |
public static double random() | 返回值为double的正值,[0.0,1.0) |
# System类
System 包含几个有用的类字段和方法,它不能被实例化
System常用方法
方法名 | 说明 |
---|---|
public static void exit(int status) | 终止当前运行的 Java 虚拟机,非零表示异常终止 |
public static long currentTimeMillis() | 返回当前时间(以毫秒为单位) |
# Object类
Object 是类层次结构的根,每个类都可以将 Object 作为超类。所有类都直接或者间接的继承自该类;
构造方法:public Object();
回想面向对象中,为什么说子类的构造方法默认访问的是父类的无参构造方法?因为它们的顶级父类只有无参构造方法
Object常用方法
方法名 | 说明 |
---|---|
public String toString() | 返回对象的字符串表示形式。建议所有子类重写该方法,自动生成 |
public boolean equals(Object obj) | 比较对象是否相等。默认比较地址,重写可以比较内容,自动生成 |
# Arrays类
冒泡排序
package Demo;
public class ArrayDemo {
public static void main(String[] args) { // 静态函数需要调用静态函数
int[] arr = {1,24,55,23,4};
System.out.println("排序前: "+arrayToString(arr));
for (int i=0;i<arr.length-1;i++){
for(int j=0;j<arr.length-i-1;j++) {
if (arr[j]>arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
System.out.println("排序后: "+arrayToString(arr));
}
// 显示数组
public static String arrayToString(int[] arr) {
StringBuilder sb = new StringBuilder();
sb.append("[");
for (int i= 0; i<arr.length;i++ ){
if (i == arr.length-1){
sb.append(arr[i]);
}else{
sb.append(arr[i]).append(", ");
}
}
sb.append("]");
String s = sb.toString();
return s;
}
}
Arrays 类包含用于操作数组的各种方法
方法名 | 说明 |
---|---|
public static String toString(int[] a) | 返回指定数组的内容的字符串表示形式 |
public static void sort(int[] a) | 按照数字顺序排列指定的数组 |
工具类的设计思想:
- 构造方法用 private 修饰
- 成员用 public static 修饰
package Demo;
import java.util.Arrays;
public class ArrayDemo {
public static void main(String[] args) { // 静态函数需要调用静态函数
int[] arr = {1, 24, 55, 23, 4};
System.out.println("排序前: " + Arrays.toString(arr));
// 排序
Arrays.sort(arr);
System.out.println("排序后: " + Arrays.toString(arr));
}
}
# 基本类型包装类
将基本数据类型封装成对象的好处在于可以在对象中定义更多的功能方法操作该数据,常用的操作之一:用于基本数据类型与字符串之间的转换。
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
- Integer 类的概述和使用:包装一个对象中的原始类型 int 的值
方法名 | 说明 |
---|---|
public Integer(int value) | 根据 int 值创建 Integer 对象(过时) |
public Integer(String s) | 根据 String 值创建 Integer 对象(过时) |
public static Integer valueOf(int i) | 返回表示指定的 int 值的 Integer 实例 |
public static Integer valueOf(String s) | 返回一个保存指定值的 Integer 对象 String |
public class ArrayDemo {
public static void main(String[] args) {
/*
Integer i1 = new Integer(100);
System.out.println(i1);
Integer i2 = new Integer("100");
System.out.println(i2);
Integer i3 = new Integer("abc"); // 报错,无法解析字符串
System.out.println(i3);*/
// 推荐
Integer i2 = Integer.valueOf("100");
System.out.println(i2);
Integer i3 = Integer.valueOf("abc"); // 报错,无法解析字符串
System.out.println(i3);
}
}
# int和string转换
int 转换为 String:public static String valueOf(int i):返回 int 参数的字符串表示形式。该方法是 String 类中的方法
String 转换为 int: public static int parseInt(String s):将字符串解析为 int 类型。该方法是 Integer 类中的方法
public class StringDemo {
public static void main(String[] args) {
// int --> string
int number = 100;
// 方式一:
String s1 = "" + number;
System.out.println(s1);
// 方式二:
String s2 = String.valueOf(number);
System.out.println(s2);
// string --> int
String s = "10";
// 方式一
Integer i = Integer.valueOf(s);
int x = i.intValue();
System.out.println(x);
// 方式二:
int y = Integer.parseInt(s);
System.out.println(y);
}
}
练习:字符串数据排序
public class StringDemo {
public static void main(String[] args) {
String s = "99 21 24 1 45 22 53";
String[] strArr = s.split(" ");
int[] arr = new int[strArr.length];
for (int i=0; i<arr.length;i++){
arr[i]=Integer.parseInt(strArr[i]);
}
Arrays.sort(arr);
StringBuilder sb = new StringBuilder();
for (int i=0;i<arr.length;i++){
if (i==arr.length-1){
sb.append(arr[i]);
}else{
sb.append(arr[i]).append(" ");
}
}
String result = sb.toString();
System.out.println(result);
}
}
# 自动拆装箱
装箱:把基本数据类型转换为对应的包装类类型
拆箱:把包装类类型转换为对应的基本数据类型
注意:在使用包装类类型的时候,如果做操作,最好先判断是否为 null,我们推荐的是,只要是对象,在使用前就必须进行不为 null 的判断
public class StringDemo {
public static void main(String[] args) {
// 装箱
Integer i = Integer.valueOf(100);
// 自动装箱
Integer ii = 100; // 底层自动执行了装箱动作
// 拆箱 + 装箱
ii = ii.intValue() +200;
//自动拆箱
ii += 20;
Integer iii = null;
iii += 30;
System.out.println(iii); // 报错: NullPointException
}
}
# Date类
Date 代表了一个特定的时间,精确到毫秒
方法名 | 说明 |
---|---|
public Date() | 分配一个 Date对象,并初始化,以便它代表它被分配的时间,精确到毫秒 |
public Date(long date) | 分配一个 Date对象,并将其初始化为表示从标准基准时间起指定的毫秒数 |
方法名 | 说明 |
---|---|
public long getTime() | 获取的是日期对象从1970年1月1日 00:00:00到现在的毫秒值 |
public void setTime(long time) | 设置时间,给的是毫秒值 |
import java.util.Date;
public class StringDemo {
public static void main(String[] args) {
Date d1 = new Date(); // 无参构造
System.out.println(d1); // Mon Nov 02 14:43:07 CST 2020
long date =1000*60*60; // 时间戳
Date d2 = new Date(date);
System.out.println(d2); // Thu Jan 01 09:00:00 CST 1970
System.out.println(d1.getTime()); // 1604299590645, 距离1970.1.1的时间戳
long time = 1000*60*60;
d1.setTime(time);
System.out.println(d1); // Thu Jan 01 09:00:00 CST 1970
long time2 = System.currentTimeMillis();
d1.setTime(time2);
System.out.println(d1); // Mon Nov 02 14:49:12 CST 2020
}
}
# SimpleDateFormat
是一个具体的类,用于以区域设置敏感的方式格式化和解析日期。日期和时间格式由日期和时间模式字符串指定,在日期和时间模式字符串中,从‘A’到‘Z’以及从‘a’到‘z’引号的字母被解释为表示日期或时间字符串的组件的模式字母。
方法名 | 说明 |
---|---|
public SimpleDateFormat() | 构造一个SimpleDateFormat,使用默认模式和日期格式 |
public SimpleDateFormat(String pattern) | 构造一个SimpleDateFormat使用给定的模式和默认的日期格式 |
/* y 年
M 月
d 日
H 时
m 分
s 秒*/
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class StringDemo {
public static void main(String[] args) throws ParseException {
Date d1 = new Date(); // 无参构造
System.out.println(d1); // Mon Nov 02 14:43:07 CST 2020
// Date --> String
// 无参构造
SimpleDateFormat sdf = new SimpleDateFormat();
String s = sdf.format(d1);
System.out.println(s); // 2020/11/2 下午2:56
// 带参构造:指定格式
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String s2 = sdf2.format(d1);
System.out.println(s2); // 2020年11月02日 14:59:06
// String-->Date
String ss = "2020-11-02 14:59:06";
SimpleDateFormat sdf3 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date dd = sdf3.parse(ss);
System.out.println(dd); // Mon Nov 02 14:59:06 CST 2020
}
}
日期工具类
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateUtil {
private DateUtil(){}; // 外界无法创建对象
public static String dateToString(Date date, String format) {
SimpleDateFormat sdf = new SimpleDateFormat(format);
String s = sdf.format(date);
return s;
}
public static Date stringToDate(String s,String format) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat(format);
Date d = sdf.parse(s);
return d;
}
}
# Calendar 类
Calendar 为某一时刻和一组日历字段之间的转换提供了一些方法,并为操作日历字段提供了一些方法;Calendar 提供了一个类方法 getInstance 用于获取 Calendar 对象,其日历字段已使用当前日期和时间初始化:Calendar rightNow = Calendar.getInstance();
方法名 | 说明 |
---|---|
public int get(int field) | 返回给定日历字段的值 |
public abstract void add(int field, int amount) | 根据日历的规则,将指定的时间量添加或减去给定的日历字段 |
public final void set(int year,int month,int date) | 设置当前日历的年月日 |
import java.util.Calendar;
public class StringDemo {
public static void main(String[] args) {
Calendar c = Calendar.getInstance(); // 多态的形式
System.out.println(c);
int ear = c.get(Calendar.YEAR);
int month = c.get(Calendar.MONTH)+1; // 默认含有0
int day = c.get(Calendar.DATE);
System.out.println(ear+"年"+month+"月"+day); // 2020年11月2
c.add(Calendar.YEAR,-3); // 年份减3
int ear2 = c.get(Calendar.YEAR);
int month2 = c.get(Calendar.MONTH)+1; // 默认含有0
int day2 = c.get(Calendar.DATE);
System.out.println(ear2+"年"+month2+"月"+day2); // 2017年11月2
c.set(2024,11,11); // 设置时间端
int ear3 = c.get(Calendar.YEAR);
int month3 = c.get(Calendar.MONTH)+1;
int day3 = c.get(Calendar.DATE);
System.out.println(ear3+"年"+month3+"月"+day3); // 2024年12月11
}
}
练习:求任意一个年份的二月有多少天
import java.util.Calendar;
import java.util.Scanner;
public class StringDemo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入年份:");
int year = sc.nextInt();
Calendar c = Calendar.getInstance();
c.set(year,2,1);
// 2月这个月的最后一天
c.add(Calendar.DATE,-1);
int date = c.get(Calendar.DATE);
System.out.println(year+"年的2月份有"+date+"天");
}
}
# 异常
异常:就是程序出现了不正常的情况
异常体系
非RuntimeException -->
--> Exception
RuntimeException --> --> Throwable --> Object
Error
/*
Throwable:是所有错误错误的超类
Error:严重问题,不需要处理
Exception:称为异常类,它表示程序本身可以处理的问题
RuntimeException:在编译期是不检查的,出现问题后,需要我们回来修改代码
非 RuntimeException:编译期就必须处理的,否则程序不能通过编译,就更不能正常运行了
*/
# JVM默认处理方案
如果程序出现了问题,我们没有做任何处理,最终 JVM 会做默认的处理:
- 把异常的名称,异常原因及异常出现的位置等信息输出在了控制台
- 程序停止执行
# Throwable的成员方法
public String getMessage() | 返回此 throwable 的详细消息字符串 |
---|---|
public String toString() | 返回此可抛出的简短描述 |
public void printStackTrace() | 把异常的错误信息输出在控制台 |
# 异常处理
如果程序出现了问题,我们需要自己来处理,有两种方案:
try … catch …
try { 可能出现异常的代码; } catch(异常类名 变量名) { 异常的处理代码; } /* 执行流程: 程序从 try 里面的代码开始执行 出现异常,会自动生成一个异常类对象,该异常对象将被提交给Java运行时系统 当Java运行时系统接收到异常对象时,会到catch中去找匹配的异常类,找到后进行异常的处理 执行完毕之后,程序还可以继续往下执行 */ public class ExceptionDemo { public static void main(String[] args) { System.out.println("开始"); method(); System.out.println("结束"); } public static void method(){ try{ int[] arr = {1,2,4}; System.out.println(arr[3]); // new ArrayIndexOutOfBoundsException(); 异常发生会创建一个这个错误对象,传给catch去匹配 }catch(ArrayIndexOutOfBoundsException e){ // 匹配到了将错误对象传递给e //System.out.println("你访问的数组不存在"); //e.printStackTrace(); // 输出错误 System.out.println(e.getMessage()); // 异常原因 Index 3 out of bounds for length 3 System.out.println(e.toString()); // java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3 } } }
throws
虽然我们通过 try…catch…可以对异常进行处理,但是并不是所有的情况我们都有权限进行异常的处理,也就是说,有些时候可能出现的异常是我们处理不了的,这个时候该怎么办呢?针对这种情况,Java 提供了 throws 的处理方案。(甩锅动作)总之都得try...catch...
throws 异常类名; /* 注意:这个格式是跟在方法的括号后面的 编译时异常必须要进行处理,两种处理方案:try...catch …或者 throws,如果采用 throws 这种方案,将来谁调用谁处理 运行时异常可以不处理,出现问题后,需要我们回来修改代码 */ import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class ExceptionDemo { public static void main(String[] args) { System.out.println("开始"); //如果method发生报错,进行判断 try { method(); }catch (ArrayIndexOutOfBoundsException e){ System.out.println(e.getMessage()); } try { method2(); }catch (ParseException e){ System.out.println(e.getMessage()); } System.out.println("结束"); } // 运行时异常 public static void method() throws ArrayIndexOutOfBoundsException { // throws只是一个抛的动作,就是摔锅的上一个调用的 int[] arr = {1, 2, 4}; System.out.println(arr[3]); } // 编译时异常 public static void method2() throws ParseException{ // 编译时异常的甩锅到调用方 String s = "2033-08-09"; SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); Date d = sdf.parse(s); // parseException 的父亲时Exception,不是运行时异常,编译时异常 System.out.println(d); } }
# 编译时异常和运行时异常的区别
Java 中的异常被分为两大类:编译时异常和运行时异常,也被称为受检异常和非受检异常; 所有的 RuntimeException 类及其子类被称为运行时异常,其他的异常都是编译时异常
- 编译时异常:必须显示处理,否则程序就会发生错误,无法通过编译
- 运行时异常:无需显示处理,也可以和编译时异常一样处理
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ExceptionDemo {
public static void main(String[] args) {
System.out.println("开始");
method();
method2();
System.out.println("结束");
}
// 运行时异常
public static void method() {
try {
int[] arr = {1, 2, 4};
System.out.println(arr[3]); // new ArrayIndexOutOfBoundsException(); 异常发生会创建一个这个错误对象,传给catch去匹配
} catch (ArrayIndexOutOfBoundsException e) { // 匹配到了将错误对象传递给e
e.printStackTrace(); // 输出错误
}
}
// 编译时异常
public static void method2() {
try {
String s = "2033-08-09";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date d = sdf.parse(s); // parseException 的父亲时Exception,不是运行时异常,编译时异常
System.out.println(d);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
# 泛型
泛型:是JDK5中引入的特性,它提供了编译时类型安全检测机制,该机制允许在编译时检测到非法的类型。它的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢? 顾名思义,就是将类型由原来的具体的类型参数化,然后在使用/调用时传入具体的类型这种参数类型可以用在类、方法和接口中,分别被称为泛型类、泛型方法、泛型接口。
泛型定义格式:
- <类型>:指定一种类型的格式。这里的类型可以看成是形参 <类型1,类型2…>:指定多种类型的格式,多种类型之间用逗号隔开。这里的类型可以看成是形参 将来具体调用时候给定的类型可以看成是实参,并且实参的类型只能是引用数据类型
泛型的好处:
- 把运行时期的问题提前到了编译期间
- 避免了强制类型转换
# 泛型类
定义格式:
格式:修饰符 class 类名<类型> { }
范例:public class Generic<T> { }
此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
// Generic.java
public class Generic<E,V> {
private E s;
private V a;
public Generic() {
}
public Generic(E s, V a) {
this.s = s;
this.a = a;
}
public void setS(E s) {
this.s = s;
}
public void setA(V a) {
this.a = a;
}
@Override
public String toString() {
return "Generic{" +
"s=" + s +
'}';
}
}
// Generic_01.java
public class Generic_01 {
public static void main(String[] args) {
Generic<String,Integer> g1 = new Generic<String, Integer>("王立",24);
Generic<String,Integer> g2 = new Generic<String, Integer>();
g2.setS("wu");
g2.setA(22);
System.out.println(g2);
System.out.println(g1);
}
}
# 泛型方法
泛型方法的定义格式:
格式:修饰符 <类型> 返回值类型 方法名(类型 变量名) { }
范例:public <T> void show(T t) { }
// Generic.java
public class Generic<E> { // 使用范型类改进
/* 古板的写法,不同的类型都得进行重载
public void show(String s){
System.out.println(s);
}
public void show(Integer i){
System.out.println(i);
}
public void show(Boolean b){
System.out.println(b);
}*/
public void show(E e){
System.out.println(e);
}
}
// Generic_01.java
public class Generic_01 {
public static void main(String[] args) {
Generic<String> g1 = new Generic<String>();
g1.show("wang");
Generic<Integer> g2 = new Generic<Integer>();
g2.show(23);
}
}
上面我们使用了范型类改进,但是还是感觉麻烦,每次都要new时写类型,下面我们使用范型方法来升华。
//Generic.java
public class Generic {
// 一本万利
public <T> void show(T t) { // 通过范型方法可以接受任意的引用数据类型, 传入是判定数据类型给参数
System.out.println(t);
}
}
// Generic_01.java
public class Generic_01 {
public static void main(String[] args) {
Generic g = new Generic();
g.show("王");
g.show(12);
}
}
# 范型接口
泛型接口的定义格式:
格式:修饰符 interface 接口名<类型> { }
范例:public interface Generic<T> { }
//GenerivInt.java
public interface GenerivInt<T> {
void show(T t);
}
//GenImpl.java
public class GenImpl<T> implements Generiv_02<T> { // 这里的T必须是从实例中传来
@Override
public void show(T t) {
System.out.println(t);
}
}
//GenericDemo.java
public class GenericDemo {
public static void main(String[] args) {
Generiv_02<String> g = new GenImpl<String>();
g.show("王");
Generiv_02<Integer> g1 = new GenImpl<Integer>();
g1.show(12);
}
}
# 类型通配符
为了表示各种泛型List的父类,可以使用类型通配符
- 类型通配符:<?> List<?>:表示元素类型未知的List,它的元素可以匹配任何的类型 这种带通配符的List仅表示它是各种泛型List的父类,并不能把元素添加到其中
如果说我们不希望List<?>是任何泛型List的父类,只希望它代表某一类泛型List的父类,可以使用类型通配符的上限
- 类型通配符上限:<? extends 类型> List<? extends Number>:它表示的类型是Number或者其子类型
除了可以指定类型通配符的上限,我们也可以指定类型通配符的下限
- 类型通配符下限:<? super 类型> List<? super Number>:它表示的类型是Number或者其父类型
import java.util.ArrayList;
import java.util.List;
public class GenericDemo {
// 类型通配符: <?>, 表示创建任意类型的List集合
List<?> list1 = new ArrayList<Object>();
List<?> list2 = new ArrayList<Number>();
List<?> list3 = new ArrayList<Integer>();
// 类型通配符上限:<? extends 类型>
// List<? extends Number> list4 = new ArrayList<Object>(); // 报错,上限是Number,元素为Object,肯定不行
List<? extends Number> list5 = new ArrayList<Number>();
List<? extends Number> list6 = new ArrayList<Integer>();
// 类型通配符下限:<? super 类型>
List<? super Number> list7 = new ArrayList<Object>();
List<? super Number> list8 = new ArrayList<Number>();
// List<? super Number> list9 = new ArrayList<Integer>(); // 报错,比Number小的Integer不行
}
# 可变参数
可变参数又称参数个数可变,用作方法的形参出现,那么方法参数个数就是可变的了
- 格式:修饰符 返回值类型 方法名(数据类型… 变量名) { } 范例:public static int sum(int… a) { }
可变参数注意事项
- 这里的变量其实是一个数组 如果一个方法有多个参数,包含可变参数,可变参数要放在最后
public static void main(String[] args) {
sum(10,2,4,6);
}
public static void sum(int...a){
for(int s:a){
System.out.println(s);
}
}
# 可变参数使用
Arrays工具类中有一个静态方法:
public static <T> List<T> asList(T... a):返回由指定数组支持的固定大小的列表
返回的集合不能做增删操作,可以做修改操作
List接口中有一个静态方法:
- public static <E> List<E> of(E... elements):返回包含任意数量元素的不可变列表
返回的集合不能做增删改操作
Set接口中有一个静态方法:
- public static <E> Set<E> of(E... elements) :返回一个包含任意数量元素的不可变集合
在给元素的时候,不能给重复的元素
返回的集合不能做增删操作,没有修改的方法
import java.util.Arrays;
import java.util.List;
import java.util.Set;
public class GenericDemo {
public static void main(String[] args) {
List<String> l = Arrays.asList("hello","world","java"); // [hello, world, java]
// l.add("sxxx"); // 报错:UnsupportedOperationException ,长度固定,增删都不可以
// l.remove("hello") // 报错:UnsupportedOperationException
l.set(0,"xxx");
System.out.println(l);
List<String> l2 = List.of("hello","xxx","go","go"); // 返回包含任意数量的元素的不可变元素
// l2.add("x2232"); // UnsupportedOperationException
// l2.remove("hello") // 报错:UnsupportedOperationException
// l.set(0,"xxx"); // 报错:UnsupportedOperationException
System.out.println(l2); // [hello, xxx, go, go]
Set<String> l3 = Set.of("hello","xxx","go","python"); // 不可重复
// l3.add("javaee"); //UnsupportedOperationException
// l2.remove("hello") // 报错:UnsupportedOperationException
System.out.println(l3); //[python, hello, go, xxx]
}
}
# 集合
编程的时候如果要存储多个数据,使用长度固定的数组存储格式,不一定满足我们的需求,更适应不了变化的需求,那么,此时该如何选择呢?
# 概念
集合类的特点:提供一种存储空间可变的存储模型,存储的数据容量可以发生改变 集合类有很多,目前我们先学习一个:ArrayList
ArrayList<E>:可调整大小的数组实现,<E>:是一种特殊的数据类型,泛型。
怎么用呢?
在出现E的地方我们使用引用数据类型替换即可。举例:ArrayList<String>,ArrayList<Student>
# 方法
# 构造方法和添加方法
方法名 | 说明 |
---|---|
public ArrayList() | 创建一个空的集合对象 |
public boolean add(E e) | 将指定的元素追加到此集合的末尾 |
public void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
代码
public class StringDemo {
public static void main(String[] args) {
// 创建一个空集合对象
// ArrayList<String> arr = new ArrayList<>(); // 后面的<>中可省略
ArrayList<String> arr = new ArrayList<String>();
// 输出集合
System.out.println(arr); // []
// 添加元素
// public boolean add: 会返回一个添加是否成功
arr.add("wang");
arr.add("java");
System.out.println(arr); // [wang,java]
// 指定位置添加元素
arr.add(1,"one");
System.out.println(arr); // [wang,one,java]
// arr.add(5,"xxx"); // 如果超出应有元素的数据插入,会报错:集合索引越界
}
}
- 常用方法
方法名 | 说明 |
---|---|
public boolean remove(Object o) | 删除指定的元素,返回删除是否成功 |
public E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
public E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
public E get(int index) | 返回指定索引处的元素 |
public int size() | 返回集合中的元素的个数 |
代码演示
public class StringDemo {
public static void main(String[] args) {
// 创建一个空集合对象
ArrayList<String> arr = new ArrayList<String>();
arr.add("one");
arr.add("two");
arr.add("three");
// 输出集合
System.out.println(arr); // [one, two, three]
// public boolean remove: 删除指定元素
arr.remove("one");
System.out.println(arr); // [two, three]
// public E remove(int index): 删除指定位置元素,并返回元素
System.out.println(arr.remove(0)); // two
System.out.println(arr); // [three]
// public E set(int index,E element): 设置指,返回旧数据
System.out.println(arr.set(0,"fix-three")); // three
System.out.println(arr); // [fix-three]
// public E get(int index): 返回索引对应的指
System.out.println(arr.get(0)); // fix-three
}
}
练习
// 遍历集合
public class StringDemo {
public static void main(String[] args) {
// 创建一个空集合对象
ArrayList<String> arr = new ArrayList<String>();
arr.add("one");
arr.add("two");
arr.add("three");
for (int i = 0; i < arr.size(); i++) {
System.out.println(arr.get(i));
}
}
}
// 存储学生对象并遍历
public class Student {
private String name;
private int age;
public Student() {}
public Student(String name,int age) {
this.name = name;
this.age = age;
}
public String getName(){
return this.name;
}
}
//-------------------------------------------
public class StudentDemo {
public static void main(String[] args) {
ArrayList<Student> arr = new ArrayList<>();
Student s1 = new Student("wang",33);
Student s2 = new Student("li",30);
Student s3 = new Student("zhu",13);
arr.add(s1);
arr.add(s2);
arr.add(s3);
for(int i=0;i<arr.size();i++) {
System.out.println(arr.get(i).getName());
}
}
}
# 学生管理练习
import java.util.ArrayList;
import java.util.Scanner;
public class StudentDemo {
public static void main(String[] args) {
// 创建集合对象
ArrayList<Student> arr = new ArrayList<>();
while (true) {
System.out.println("------学生管理系统------");
System.out.println("1、添加学生");
System.out.println("2、删除学生");
System.out.println("3、修改学生");
System.out.println("4、查看学生");
System.out.println("5、退出");
System.out.println("请输入你的选择:");
Scanner sc = new Scanner(System.in);
String line = sc.nextLine();
switch (line) {
case "1":
addStudent(arr);
break;
case "2":
deleteStudent(arr);
break;
case "3":
updateStudent(arr);
break;
case "4":
findAllStudent(arr);
break;
case "5":
System.out.println("谢谢使用");
System.exit(0); // jvm退出;
default:
System.out.println("输入有误");
break;
}
}
}
public static void addStudent(ArrayList<Student> array) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入学生学号:");
String sid = sc.nextLine();
System.out.println("请输入学生名称:");
String name = sc.nextLine();
System.out.println("请输入学生年龄:");
int age = sc.nextInt();
System.out.println("请输入学生地址:");
String addr = sc.next();
// 创建学生对象
Student s = new Student(name,age,sid,addr);
array.add(s);
System.out.println("添加成功");
}
public static void findAllStudent(ArrayList<Student> array) {
// 数据是否判断
if (array.size() == 0) {
System.out.println("无信息");
return;
}
// \t进行tab键缩进
System.out.println("学号\t\t姓名\t\t年龄\t\t居住地");
for (int i=0;i<array.size();i++){
Student s = array.get(i);
System.out.println(s.getSid()+"\t\t"+s.getName()+"\t\t"+s.getAge()+"\t\t"+s.getAddr());
}
}
public static void deleteStudent(ArrayList<Student> array) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入你要删除的学号:");
String sid = sc.nextLine();
for(int i=0;i<array.size();i++){
Student s = array.get(i);
if(s.getSid().equals(sid)) {
array.remove(i);
System.out.println("删除成功");
return;
}
}
System.out.println("未查找到该学号");
}
public static void updateStudent(ArrayList<Student> array) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入你要修改的学生的学号:");
String sid = sc.nextLine();
System.out.println("请输入学生的新姓名:");
String name = sc.nextLine();
System.out.println("请输入学生的新年纪:");
int age = sc.nextInt();
System.out.println("请输入学生的新地址:");
String addr = sc.next();
Student s = new Student(name,age,sid,addr);
for(int i=0; i<array.size();i++) {
Student s2 = array.get(i);
if (s2.getSid().equals(sid)) {
array.set(i,s);
System.out.println("修改成功");
break;
}
}
}
}
# 集合进阶
集合类的特点:提供一种存储空间可变的存储模型,存储的数据容量可以随时发生改变。
集合类体系结构:
集合
Collection(单列 val集合) Map(双列 key:val集合)
List(可重复) Set(不可重复) HashMap ...
ArrayList LinkedList... HashSet、TreeSet...
Collection集合概述:
- 是单例集合的顶层接口,它表示一组对象,这些对象也称为Collection的元素
- JDK 不提供此接口的任何直接实现,它提供更具体的子接口(如Set和List)实现
创建Collection集合的对象:
- 多态的方式
- 具体的实现类ArrayList
import java.util.ArrayList;
import java.util.Collection;
public class CollectionDemo_1 {
public static void main(String[] args) {
// 创建Colleciton集合对象
Collection<String> c = new ArrayList<String>();
// 添加元素: boolean add(E e)
c.add("hello");
c.add("CQ");
System.out.println(c); // 重写toString方法
}
}
# Collection常用方法
方法名 | 说明 |
---|---|
boolean add(E e) | 添加元素 |
boolean remove(Object o) | 从集合中移除指定的元素 |
void clear() | 清空集合中的元素 |
boolean contains(Object o) | 判断集合中是否存在指定的元素 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 集合的长度,也就是集合中元素的个数 |
import java.util.ArrayList;
import java.util.Collection;
public class CollectionDemo_1 {
public static void main(String[] args) {
// 创建Colleciton集合对象
Collection<String> c = new ArrayList<String>();
// 添加元素: boolean add(E e)
c.add("hello");
c.add("hello"); // 可重复
System.out.println(c.add("CQ")); // true,源码上所有都是返回true
System.out.println(c); // 重写toString方法,[hello, hello, CQ]
// boolean remove(object o): 从集合中移除执行元素
System.out.println(c.remove("hello")); //true
System.out.println(c.remove("hlo")); //false
// void clear():清空集合元素
//c.clear();
// boolean contains(Object 0): 判断是否存在指定元素
System.out.println(c.contains("world")); //false
// int size()
System.out.println(c.size()); // 2
}
}
# Collection 集合遍历
terator:迭代器,集合的专用遍历方式:
- terator<E> iterator():返回此集合中元素的迭代器,通过集合的iterator()方法得到
- 迭代器是通过集合的iterator()方法得到的,所以我们说它是依赖于集合而存在的
Iterator中的常用方法:
- E next():返回迭代中的下一个元素
- boolean hasNext():如果迭代具有更多元素,则返回 true
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class CollectionDemo_1 {
public static void main(String[] args) {
// 创建Colleciton集合对象
Collection<String> c = new ArrayList<String>();
// 添加元素: boolean add(E e)
c.add("hello");
c.add("hello"); // 可重复
c.add("java"); // 可重复
Iterator<String> it = c.iterator(); // String必须和上面的集合中元素类型一致,多态的形式得到
/*
* ArrayList中实现的iterator:
* public Iterator<E> iterator() {
return new Itr();
}
*
*private class Itr implements Iterator<E> {...}
*/
// next方法
System.out.println(it.next()); //hello
System.out.println(it.next()); //hello
System.out.println(it.next()); //java
//System.out.println(it.next()); //报错,NoSuchElementException
if(it.hasNext()){ // 判断
System.out.println(it.next());
}
// 常用遍历迭代器方法
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
}
}
案例遍历学生:
//Student.java
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
}
// CollectionDemo.java
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class CollectionDemo_1 {
public static void main(String[] args) {
// 创建Colleciton集合对象
Collection<Student> c = new ArrayList<Student>();
Student s1 = new Student("张某某",12);
Student s2 = new Student("王某某",13);
Student s3 = new Student("孙某某",15);
c.add(s1);
c.add(s2);
c.add(s3);
// 常用遍历迭代器方法
for (Student s : c) { // 新的简写,for循环自带迭代器初始化
System.out.println(s.getName());
}
// 创建迭代器
Iterator<Student> it = c.iterator();
while (it.hasNext()){
Student s = it.next();
System.out.println(s.getName());
}
}
}
# List集合
概述:
- 有序集合(也称为序列),用户可以精确控制列表中每个元素的插入位置。用户可以通过整数索引访问元素,并搜索列表中的元素
- 与Set集合不同,列表通常允许重复的元素
List集合特点:
- 有序:存储和取出的元素顺序一致
- 可重复:存储的元素可以重复
特有方法:
方法名 | 说明 |
---|---|
void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
E get(int index) | 返回指定索引处的元素 |
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ListDemo01 {
public static void main(String[] args) {
// 创建集合对象
List<String> list = new ArrayList<String>();
// 添加元素
list.add("hello");
list.add("hello");
list.add("java");
list.add(0,"xxx"); // 不能越界
System.out.println(list.remove(0)); // 删除索引/元素,返回该元素
System.out.println(list.set(0,"2222")); // 修改某索引上的元素,返回该元素
System.out.println(list.get(0)); // 获取某索引上的元素
// 输出集合
System.out.println(list); // [hello, hello, java]
// 迭代器的方法遍历
Iterator<String> it = list.iterator();
// 遍历1
while(it.hasNext()) {
String s = it.next();
System.out.println(s);
}
// 遍历2
for(int i=0;i<list.size();i++){
String s = list.get(i);
System.out.println(s);
}
}
}
注意:并发修改异常,迭代器遍历的过程中,通过集合对象修改了集合中的元素,造成了迭代器获取元素中判断预期修改值和实际修改值不一致,用for循环遍历,然后用集合对象做对应的操作即可
while(it.hasNext()) {
String s = it.next();
if(s.equals("java")){
list.add("javaee"); // 报错 ConcurrentModificationException,在add方法触发时会在iter的next方法中的checkformodification方法检测在循环中的总数是否有变化,有则跑错
}
System.out.println(s);
}
// 通过for循环解决
for(int i =0; i<list.size();i++){
String s = list.get(i);
if(s.equals("java")){
list.add("javaee");
}
System.out.println(s);
}
# ListIterator
ListIterator:列表迭代器
- 通过List集合的listIterator()方法得到,所以说它是List集合特有的迭代器
- 用于允许程序员沿任一方向遍历列表的列表迭代器,在迭代期间修改列表,并获取列表中迭代器的当前位置
ListIterator中的常用方法
- E next():返回迭代中的下一个元素
- boolean hasNext():如果迭代具有更多元素,则返回 true
- E previous():返回列表中的上一个元素
- boolean hasPrevious():如果此列表迭代器在相反方向遍历列表时具有更多元素,则返回 true
- void add(E e):将指定的元素插入列表
import java.util.List;
import java.util.ListIterator;
public class ListDemo01 {
public static void main(String[] args) {
// 创建集合对象
List<String> list = new ArrayList<String>();
// 添加元素
list.add("hello");
list.add("hello");
list.add("java");
// 迭代器的方法遍历
ListIterator<String> it = list.listIterator();
// 遍历1
while(it.hasNext()) {
String s = it.next();
System.out.println(s);
}
// 反向遍历
while(it.hasPrevious()) {
String s = it.previous();
System.out.println(s);
}
// 重点:
while(it.hasNext()) {
String s = it.next();
if(s.equals("java")){
it.add("java33"); // 之前使用Iterator对象是报错的,这里没有报错,在此add赋值后会修改计数
}
}
System.out.println(list);
}
}
# 增强for循环
增强for:简化数组和Collection集合的遍历
- 实现Iterable接口的类允许其对象成为增强型 for语句的目标,因为里面有一个Iterator
- 它是JDK5之后出现的,其内部原理是一个Iterator迭代器
格式:
for(元素数据类型 变量名 : 数组或者Collection集合) {
//在此处使用变量即可,该变量就是元素
}
范例:
int[] arr = {1, 2, 3, 4, 5};
for(int i : arr) {
System.out.println(i);
}
//练习:
import java.util.ArrayList;
import java.util.List;
public class ListDemo01 {
public static void main(String[] args) {
// 创建集合对象
List<String> list = new ArrayList<String>();
// 添加元素
list.add("hello");
list.add("hello");
list.add("java");
// 因为List是Collection的子接口
for(String s:list){
System.out.println(s);
}
// 注意
for(String s:list){
if(s.equals("java")){
list.add("java33"); // 报错,因为增强for中使用的是iterator,默认,所以add会出错 ConcurrentModificationException
}
}
}
}
# 数据结构
数据结构是计算机存储、组织数据的方式。是指相互之间存在一种或多种特定关系的数据元素的集合,通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。
# 栈
栈是一种数据先进后出的模型
- 数据进入栈模型的过程称为:压/进栈
- 数据离开栈模型的过程称为:弹/出栈
# 队列
队列是一种数据先进先出的模型
- 数据从后端进入队列模型的过程称为:入队列
- 数据从前端离开队列模型的过程称为:出队列
# 数组
数组是一种查询快,增删慢的模型
查询数据通过索引定位,查询任意数据耗时相同,查询速度快
删除数据时,要将原始数据删除,同时后面每个数据前移,删除效率低
添加数据时,添加位置后的每个数据后移,再添加元素,添加效率极低
# 链表
链表是一种增删快(对比数组),查询慢的模型
组成:
- 节点= 数据+下一个节点地址
- 头节点=head+空地址/数据
- 尾节点=数据+空地址
# List集合子类
List集合常用子类:ArrayList,LinkedList
- ArrayList:底层数据结构是数组,查询快,增删慢
- LinkedList:底层数据结构是链表,查询慢,增删快
练习:分别使用ArrayList和LinkedList完成存储字符串并遍历
import java.util.*;
public class ListDemo01 {
public static void main(String[] args) {
// 创建集合对象
ArrayList<String> array = new ArrayList<String>();
array.add("hello");
array.add("world");
array.add("java");
// 遍历
for(String s:array){
System.out.println(s);
}
LinkedList<String> lkt = new LinkedList<String>();
lkt.add("hello");
lkt.add("world");
lkt.add("go");
// 遍历
for(String s:lkt){
System.out.println(s);
}
}
}
LinkedList集合特有功能:
方法名 | 说明 |
---|---|
public void addFirst(E e) | 在该列表开头插入指定的元素 |
public void addLast(E e) | 将指定的元素追加到此列表的末尾 |
public E getFirst() | 返回此列表中的第一个元素 |
public E getLast() | 返回此列表中的最后一个元素 |
public E removeFirst() | 从此列表中删除并返回第一个元素 |
public E removeLast() | 从此列表中删除并返回最后一个元素 |
import java.util.*;
public class ListDemo01 {
public static void main(String[] args) {
LinkedList<String> lkt = new LinkedList<String>();
lkt.add("hello");
lkt.add("world");
lkt.add("go");
lkt.addFirst("first");
lkt.addLast("last");
System.out.println(lkt.getFirst()); // first
System.out.println(lkt.removeFirst()); // first
System.out.println(lkt); // [hello, world, go, last]
}
}
# Set集合
Set集合特点:
- 不包含重复元素的集合
- 没有带索引的方法,所以不能使用普通for循环遍历
import java.util.*;
public class ListDemo01 {
public static void main(String[] args) {
// 创建集合对象
Set<String> s = new HashSet<String>(); // HashSet是Set集合的实现,对集合的迭代顺序不作任何保证
// 添加元素
s.add("hello");
s.add("world");
s.add("java");
//s.add("java"); // 报错,必须不重复
// 遍历
for (String s1 : s) {
System.out.println(s1); // 无序输出
}
}
}
# 哈希值
哈希值:是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
Object类中有一个方法可以获取对象的哈希值
- public int hashCode():返回对象的哈希码值
对象的哈希值特点:
- 同一个对象多次调用hashCode()方法返回的哈希值是相同的
- 默认情况下,不同对象的哈希值是不同的。而重写hashCode()方法,可以实现让不同对象的哈希值相同
// 创建学生对象
Student s1 = new Student("王",13);
// 同一个对象多次调用hashCode()返回是相同的
System.out.println(s1.hashCode()); // 1528902577
System.out.println(s1.hashCode()); // 1528902577
// 默认情况下(使用Object下的hashCode,可以重写),不同对象的哈希值是不相同的
Student s2 = new Student("王",13);
System.out.println(s2.hashCode()); // 1927950199
System.out.println("hello".hashCode()); // 99162322
System.out.println("hello".hashCode()); // 99162322
System.out.println("world".hashCode()); // 113318802
# HashSet集合
HashSet集合特点
- 底层数据结构是哈希表
- 对集合的迭代顺序不作任何保证,也就是说不保证存储和取出的元素顺序一致
- 没有带索引的方法,所以不能使用普通for循环遍历
- 由于是Set集合,所以是不包含重复元素的集合
- 要保证元素唯一性,需要重写hashCode()和equals()
// 创建集合对象
HashSet<String> hs = new HashSet<String>();
// 添加元素
hs.add("hello");
hs.add("world");
hs.add("java");
// 遍历
for(String s:hs){
System.out.println(s); // 无序
}
源码分析:
1、调用对象的hashCode方法获取对象的哈希表,根据对象的哈希值计算对象的存储位置
2、判断该位置是否有元素存在如果没有,将元素存储到该位置;如果有,则遍历该位置的所有元素,和新元素比较哈希值是否相同,如果不相同则存储入;如果有相同的,调用equals方法比较对象内容是否相同,相同则说明元素重复,不相同则存储
# 哈希表
JDK8之前,底层采用数组+链表实现,可以说是一个元素为链表的数组;JDK8以后,在长度比较长的时候,底层实现了优化
未重写Student.java的equals、hashCode方法之前
// 创建集合对象
HashSet<Student> hs = new HashSet<Student>();
Student s1 = new Student("王",23);
Student s2 = new Student("li",12);
Student s3 = new Student("王",23);
// 添加元素
hs.add(s1);
hs.add(s2);
hs.add(s3);
// 遍历
for(Student s:hs){
System.out.println(s.getName()); // 无序 王 li 王
}
重写Student.java的equals、hashCode方法
//Student.java
publice class Student{
...
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
// Demo.java
// 创建集合对象
HashSet<Student> hs = new HashSet<Student>();
Student s1 = new Student("王",23);
Student s2 = new Student("li",12);
Student s3 = new Student("王",23);
// 添加元素
hs.add(s1);
hs.add(s2);
hs.add(s3);
// 遍历
for(Student s:hs){
System.out.println(s.getName()); // 无序 王 li
}
# LinkedHashSet集合
LinkedHashSet集合特点
- 哈希表和链表实现的Set接口,具有可预测的迭代次序
- 由链表保证元素有序,也就是说元素的存储和取出顺序是一致的
- 由哈希表保证元素唯一,也就是说没有重复的元素
// 创建集合对象
LinkedHashSet<String> hs = new LinkedHashSet<String>();
// 添加元素
hs.add("hello");
hs.add("world");
hs.add("java");
// hs.add("java");
// 遍历
for(String s:hs){
System.out.println(s); // 有序、无重复
}
# TreeSet集合
TreeSet集合特点:
- 元素有序,这里的顺序不是指存储和取出的顺序,而是按照一定的规则进行排序,具体排序方式取决于构造方法 TreeSet():根据其元素的自然排序进行排序 TreeSet(Comparator comparator) :根据指定的比较器进行排序
- 没有带索引的方法,所以不能使用普通for循环遍历
- 由于是Set集合,所以不包含重复元素的集合
import java.util.TreeSet;
public class HashDemo {
public static void main(String[] args) {
// 创建集合对象
TreeSet<Integer> ts = new TreeSet<Integer>(); //所有基本类型存储是都是包装类类型,无参构造是自然排序
// 添加元素
ts.add(10); // 自动装箱
ts.add(2);
ts.add(99);
ts.add(30);
ts.add(30); // 不包含重复元素
// 遍历
for(Integer s:ts){
System.out.println(s); // 2、10、30、99
}
}
}
自然排序Comparable的使用:
- 存储学生对象并遍历,创建TreeSet集合使用无参构造方法, 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
// Student.java
import java.util.Objects;
public class Student implements Comparable<Student> {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public int compareTo(Student o) {
//return 0; // 自会返回元素,认为只有一个输出
//return 1; // 按照存储的顺序输出,每次存储的比之前的大
//return -1; // 按照存储的顺序倒叙输出
// 自定义
int num = this.age - o.age; // this表示的是前一个比较的元素,o表示当前的, 升序this放前面
return num == 0?this.name.compareTo(o.name):num; // compareTo用来比较字符串是否相同,返回0\-1\1
}
}
// Demo.java
import java.util.TreeSet;
public class HashDemo {
public static void main(String[] args) {
// 创建集合对象
TreeSet<Student> ts = new TreeSet<Student>(); //所有基本类型存储是都是包装类类型,无参构造是自然排序
// 报错:ClassCastException,需要让学生类实现排序接口,需要实现compare方法
Student s1 = new Student("xishi",20);
Student s3 = new Student("yiaochan",23);
Student s2 = new Student("wangzhaojun",23);
Student s4 = new Student("yangyuhuan",25);
// 添加元素
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
// 遍历
for(Student s:ts){
System.out.println(s.getName());
}
}
}
/*
结论
1、用TreeSet集合存储自定义对象,无参构造方法使用的是自然排序对元素进行排序的
2、自然排序,就是让元素所属的类实现Comparable接口,重写compareTo(T o)方法
3、重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
*/
比较器排序Comparator的使用:
- 存储学生对象并遍历,创建TreeSet集合使用带参构造方法 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
import java.util.*;
public class HashDemo {
public static void main(String[] args) {
// 创建集合对象
TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
//return 0;
int num = s1.getAge() - s2.getAge();
return num == 0 ? s1.getName().compareTo(s2.getName()) : num;
}
}); // 带参比较器, 传入一个实现了compare的实例
Student s1 = new Student("xishi", 20);
Student s3 = new Student("yiaochan", 23);
Student s2 = new Student("wangzhaojun", 23);
Student s4 = new Student("yangyuhuan", 25);
// 添加元素
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
// 遍历
for (Student s : ts) {
System.out.println(s.getName());
}
}
}
/*
1、用TreeSet集合存储自定义对象,带参构造方法使用的是比较器排序对元素进行排序的
2、比较器排序,就是让集合构造方法接收Comparator的实现类对象,重写compare(T o1,T o2)方法
3、重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
*/
# Map集合
Map集合概述
- Interface Map<K,V> K:键的类型;V:值的类型 将键映射到值的对象;不能包含重复的键;每个键可以映射到最多一个值
创建Map集合的对象
- 多态的方式
- 具体的实现类HashMap
Map集合的成员方法1:
方法名 | 说明 |
---|---|
V put(K key,V value) | 添加元素 |
V remove(Object key) | 根据键删除键值对元素 |
void clear() | 移除所有的键值对元素 |
boolean containsKey(Object key) | 判断集合是否包含指定的键 |
boolean containsValue(Object value) | 判断集合是否包含指定的值 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 集合的长度,也就是集合中键值对的个数 |
import java.util.HashMap;
import java.util.Map;
public class MapDemo {
public static void main(String[] args) {
// 创建集合对象
Map<String, String> m= new HashMap<String,String>();
// 添加元素
m.put("001","wang");
m.put("002","li");
m.put("003","zhu");
m.put("003","zu"); // 建唯一必须,会出现替换值
System.out.println(m); // {001=wang, 002=li, 003=zhu}
// 删除元素
System.out.println(m.remove("003")); // zu
System.out.println(m.remove("004")); // null
// 包含
System.out.println(m.containsKey("001")); // true
// 集合长度
System.out.println(m.size()); // 2
System.out.println(m); // {001=wang, 002=li}
}
}
Map集合获取功能
方法名 | 说明 |
---|---|
V get(Object key) | 根据键获取值 |
Set<K> keySet() | 获取所有键的集合 |
Collection<V> values() | 获取所有值的集合 |
Set<Map.Entry<K,V>> entrySet() | 获取所有键值对对象的集合 |
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class MapDemo {
public static void main(String[] args) {
// 创建集合对象
Map<String, String> m= new HashMap<String,String>();
// 添加元素
m.put("张无忌","赵敏");
m.put("郭靖","黄蓉");
m.put("杨过","小龙女");
// 获取功能
System.out.println(m.get("张无忌")); // 赵敏
System.out.println(m.get("张三丰")); // null
// 获取所有键集合
Set<String> keySet = m.keySet();
for (String k:keySet){
System.out.println(k);
}
// 获取所有值
Collection<String> v = m.keySet();
for(String vl:v){
System.out.println(vl);
}
}
}
Map集合遍历
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class MapDemo {
public static void main(String[] args) {
// 创建集合对象
Map<String, String> m= new HashMap<String,String>();
// 添加元素
m.put("张无忌","赵敏");
m.put("郭靖","黄蓉");
m.put("杨过","小龙女");
// Map遍历1
Set<String> keySet = m.keySet();
for (String s:keySet){
String val = m.get(s);
System.out.println(s+", "+val);
}
// Map遍历2
Set<Map.Entry<String,String>> entrySet = m.entrySet(); // 先得到每一对的集合
for(Map.Entry<String,String> me:entrySet){
String key = me.getKey();
String val = me.getValue();
System.out.println(key+", "+val);
}
}
}
集合嵌套
// ArrayList中嵌套HashMap
import java.util.*;
public class MapDemo {
public static void main(String[] args) {
ArrayList<HashMap<String,String>> array = new ArrayList<HashMap<String, String>>();
HashMap<String,String> hm1 = new HashMap<String, String>();
hm1.put("孙策","大乔");
hm1.put("周瑜","大乔");
array.add(hm1);
HashMap<String,String> hm2 = new HashMap<String, String>();
hm2.put("诸葛亮","大乔");
hm2.put("赵云","大乔");
array.add(hm2);
for(HashMap<String,String> h:array){
Set<String> keySet = h.keySet();
for (String s:keySet){
String s1 = h.get(s);
System.out.println(s+","+s1);
}
}
}
}
// HashMap中嵌套ArrayList
import java.util.*;
public class MapDemo {
public static void main(String[] args) {
HashMap<String, ArrayList<String>> hm = new HashMap<String, ArrayList<String>>();
ArrayList<String> sgyy = new ArrayList<String>();
sgyy.add("诸葛亮");
sgyy.add("赵云");
hm.put("三国", sgyy);
ArrayList<String> hlm = new ArrayList<String>();
hlm.add("贾宝玉");
hlm.add("林黛玉");
hm.put("红楼", hlm);
System.out.println(hm); //{红楼=[贾宝玉, 林黛玉], 三国=[诸葛亮, 赵云]}
//遍历
Set<String> keySet = hm.keySet();
for (String key:keySet){
ArrayList<String> val = hm.get(key);
for (String s:val){
System.out.println(s);
}
}
}
}
练习:键盘录入一个字符串,要求统计字符串中每个字符串出现的次数。举例:键盘录入“aababcabcdabcde” 在控制台输出:“a(5)b(4)c(3)d(2)e(1)”
import java.util.*;
public class MapDemo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串:");
String line = sc.nextLine();
HashMap<Character, Integer> hm = new HashMap<Character, Integer>();
for(int i=0;i<line.length();i++){
char key = line.charAt(i);
Integer value = hm.get(key);
if(value==null){
hm.put(key,1);
}else{
value++;
hm.put(key,value);
}
}
StringBuilder sb = new StringBuilder();
Set<Character> keySet = hm.keySet();
for(Character key:keySet){
Integer val = hm.get(key);
sb.append(key).append("(").append(val).append(")");
}
String res = sb.toString();
System.out.println(res);
}
}
# Collections
Collections类的概述
- 由静态方法组成
- 是针对集合操作的工具类
Collections类的常用方法
public static <T extends Comparable<? super T>> void sort(List<T> list):将指定的列表按升序排序
public static void reverse(List<?> list):反转指定列表中元素的顺序
public static void shuffle(List<?> list):使用默认的随机源随机排列指定的列表
public class MapDemo {
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
list.add(12);
list.add(20);
list.add(30);
list.add(1);
Collections.sort(list); // 排序
System.out.println(list); // [1, 12, 20, 30]
Collections.reverse(list); // 倒叙
System.out.println(list); // [30, 20, 12, 1]
Collections.shuffle(list); // 洗牌
System.out.println(list);
}
}
练习:通过Collections进行学生类的自定义排序
import java.util.*;
public class MapDemo {
public static void main(String[] args) {
ArrayList<Student> array = new ArrayList<Student>();
Student s1 = new Student("林青霞", 30);
Student s2 = new Student("张曼玉", 3);
Student s3 = new Student("黎明", 12);
Student s4 = new Student("阿明", 12);
array.add(s1);
array.add(s2);
array.add(s3);
array.add(s4);
// 普通排序
// Collections.sort(array);
// 自定义排序,通过比较器
Collections.sort(array, new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
// 按照年龄从小到大排序,年龄相同时,通过字母排序
int num = s1.getAge() - s2.getAge();
int num2 = num == 0 ? s1.getName().compareTo(s2.getName()):num;
return num2;
}
});
for (Student s:array){
System.out.println(s.getName());
}
}
}
# IO流
# File
File:它是文件和目录路径名的抽象表示
- 文件和目录是可以通过File封装成对象的
- 对于File而言,其封装的并不是一个真正存在的文件,仅仅是一个路径名而已。它可以是存在的,也可以是不存在的。将来是要通过具体的操作把这个路径的内容转换为具体存在的
构造方法:
方法名 | 说明 |
---|---|
File(String pathname) | 通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例 |
File(String parent, String child) | 从父路径名字符串和子路径名字符串创建新的 File实例 |
File(File parent, String child) | 从父抽象路径名和子路径名字符串创建新的 File实例 |
import java.io.File;
public class FileDemo {
public static void main(String[] args) {
// 三种初始化file对象的方法
File f1 = new File("/Users/wangli/Desktop/java_Pro/myList/myList/src/myFile_01/java.txt"); // 绝对路径, 不存在也不报错
System.out.println(f1); // /Users/wangli/Desktop/java_Pro/myList/myList/src/myFile_01/java.txt
File f2 = new File("/Users/wangli/Desktop/java_Pro/myList/myList/src/myFile_01","java.txt"); // 带参构造
System.out.println(f2); // /Users/wangli/Desktop/java_Pro/myList/myList/src/myFile_01/java.txt
File f3 = new File("/Users/wangli/Desktop/java_Pro/myList/myList/src/myFile_01");
File f4 = new File(f3,"java.txt");
System.out.println(f4);
}
}
File类创建功能
方法名 | 说明 |
---|---|
public boolean createNewFile() | 当具有该名称的文件不存在时,创建一个由该抽象路径名命名的新空文件 |
public boolean mkdir() | 创建由此抽象路径名命名的目录 |
public boolean mkdirs() | 创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录 |
import java.io.File;
import java.io.IOException;
public class FileDemo {
public static void main(String[] args) throws IOException {
// 创建文件
File f1 = new File("/Users/wangli/Desktop/java_Pro/myList/myList/src/myFile_01/java.txt"); // 绝对路径, 不存在也不报错
System.out.println(f1.createNewFile()); // true,创建成功
// 创建目录
File f2 = new File("/Users/wangli/Desktop/java_Pro/myList/myList/src/myFile_01/sss");
System.out.println(f2.mkdir()); // 创建成功返回true,存在返回false
// 多级目录创建
File f3 = new File("/Users/wangli/Desktop/java_Pro/myList/myList/src/myFile_01/java/html");
System.out.println(f3.mkdirs());
}
}
File类判断和获取功能
方法名 | 说明 |
---|---|
public boolean isDirectory() | 测试此抽象路径名表示的File是否为目录 |
public boolean isFile() | 测试此抽象路径名表示的File是否为文件 |
public boolean exists() | 测试此抽象路径名表示的File是否存在 |
public String getAbsolutePath() | 返回此抽象路径名的绝对路径名字符串 |
public String getPath() | 将此抽象路径名转换为路径名字符串 |
public String getName() | 返回由此抽象路径名表示的文件或目录的名称 |
public String[] list() | 返回此抽象路径名表示的目录中的文件和目录的名称字符串数组 |
public File[] listFiles() | 返回此抽象路径名表示的目录中的文件和目录的File对象数组 |
import java.io.File;
import java.io.IOException;
public class FileDemo {
public static void main(String[] args) throws IOException {
File f = new File("myList/java.txt"); // 当前项目目录下的java.txt
System.out.println(f.isDirectory()); // false
System.out.println(f.isFile()); // true
System.out.println(f.exists()); // true
System.out.println(f.getName()); // java.txt
System.out.println(f.getAbsoluteFile()); // 绝对路径
System.out.println(f.getPath()); // myList/java.txt
File f2 = new File("myList");
String[] strArray = f2.list(); // 返回的是文件/目录的字符串
for (String s:strArray){
System.out.println(s);
}
File[] fileArray = f2.listFiles(); // 返回的是文件/目录的对象集合
for(File file:fileArray){
System.out.println(file.getName()); // 可操作对象的方法/属性
}
// 文件的删除
File f4 = new File("myList/java2.txt");
//System.out.println(f4.createNewFile());
System.out.println(f4.delete()); // 删除文件
File f5 = new File("myList/javase");
System.out.println(f5.mkdir());
System.out.println(f5.delete()); // 删除目录,注意如果目录非空,是不能删除的
}
}
# 递归
以编程的角度来看,递归指的是方法定义中调用方法本身的现象。
递归解决问题要找到两个内容:
- 递归出口:否则会出现内存溢出 递归规则:与原问题相似的规模较小的问题
练习:用递归求5的阶乘,并把结果在控制台输出
public class FileDemo {
public static void main(String[] args) {
int res = jc(5);
System.out.println(res);
}
public static int jc(int n){
if (n==1){
return 1;
}else{
return n*jc(n-1);
}
}
}
练习:遍历目录
import java.io.File;
import java.io.IOException;
public class FileDemo {
public static void main(String[] args) throws IOException {
File f = new File("myList/src");
getAllFilePath(f);
}
public static void getAllFilePath(File srcFile){
File[] fileArray = srcFile.listFiles();
if(fileArray != null){
for (File file:fileArray){
if(file.isDirectory()){
getAllFilePath(file);
}else{
System.out.println(file.getAbsolutePath());
}
}
}
}
}
# IO流
IO流概述
- IO:输入/输出(Input/Output)
- 流:是一种抽象概念,是对数据传输的总称。也就是说数据在设备间的传输称为流,流的本质是数据传输
- IO流就是用来处理设备间数据传输问题的。常见的应用:文件复制;文件上传;文件下载
IO流分类
- 按照数据的流向 输入流:读数据 输出流:写数据
- 按照数据类型来分 字节流 字节输入流;字节输出流 字符流 字符输入流;字符输出流
一般来说,我们说IO流的分类是按照数据类型来分的,那么这两种流都在什么情况下使用呢?
如果数据通过Window自带的记事本软件打开,我们还可以读懂里面的内容,就使用字符流,否则使用字节流。如果你不知道该使用哪种类型的流,就使用字节流
# 字节流写数据
字节流抽象基类
- InputStream:这个抽象类是表示字节输入流的所有类的超类
- OutputStream:这个抽象类是表示字节输出流的所有类的超类 子类名特点:子类名称都是以其父类名作为子类名的后缀
- FileOutputStream:文件输出流用于将数据写入File FileOutputStream(String name):创建文件输出流以指定的名称写入文件
字节流写数据的3种方式:
方法名 | 说明 |
---|---|
void write(int b) | 将指定的字节写入此文件输出流一次写一个字节数据 |
void write(byte[] b) | 将 b.length字节从指定的字节数组写入此文件输出流一次写一个字节数组数据 |
void write(byte[] b, int off, int len) | 将 len字节从指定的字节数组开始,从偏移量off开始写入此文件输出流一次写一个字节数组的部分数据 |
import java.io.FileOutputStream;
import java.io.IOException;
public class FileDemo {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("myList/java.txt"); // 如果文件不存在会自动创建
/*
* 1、创建字节输出流对象(调用系统功能创建了文件,创建字节输出流对象,让字节输出流对象指向文件)
2、调用字节输出流对象的写数据方法
3、释放资源(关闭此文件输出流并释放与此流相关联的任何系统资源)
*/
fos.write(97); // 写入了数据
//byte[] bys = {76,98,97,99};
byte[] bys = "wangl我".getBytes();
fos.write(bys);
fos.write(bys,1, 2); // 从索引1开始写2个 --》 an
fos.close(); // 关闭此文件的输出流,释放与此文件相关的系统资源
}
}
注意:异常处理字节流+追加数据
import java.io.FileOutputStream;
import java.io.IOException;
public class FileDemo {
public static void main(String[] args) {
FileOutputStream fos = null; // 全局初始化
try {
//FileOutputStream fos = new FileOutputStream("myList/java.txt"); // 重新覆盖文件的方式写文件
fos = new FileOutputStream("myList/java.txt", true); // 追加写文件
for (int i = 0; i < 10; i++) {
fos.write("hello\n".getBytes()); // 使用\n来换行, window是\r\n
}
} catch (IOException e) {
e.printStackTrace();
} finally { // finally:在异常处理时提供finally块来执行所有清除操作
if(fos != null) { // 防止空指针
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
# 字节流读数据
需求:把文件fos.txt中的内容读取出来在控制台输出
- FileInputStream:从文件系统中的文件获取输入字节 FileInputStream(String name):通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名
import java.io.*;
public class FileDemo {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("myList/java.txt");
/*
byte[] bys = new byte[5];
// 第一次读数据
int len = fis.read(bys);
System.out.println(len); // 5
System.out.println(new String(bys)); // hello
len = fis.read(bys);
System.out.println(len); // 5
System.out.println(new String(bys)); // worl
*/
byte[] bys = new byte[1024]; // 字节流读数据(一次读一个字节数组数据)
int len;
while((len=fis.read(bys))!=-1) {
System.out.println(new String(bys,0,len)); // 转化指定长度的字符串
}
}
}
练习:复制一个文件到另一个文件中
import java.io.*;
public class FileDemo {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("myList/java.txt");
FileOutputStream fos = new FileOutputStream("myList/java2.txt"); // 输出文件
int by;
while((by=fis.read())!=-1) { // 一个一个字节的读
fos.write(by);
}
fos.close();
fis.close();
}
}
练习:复制图片
import java.io.*;
public class FileDemo {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("myList/gomap.png"); // 读对象
FileOutputStream fos = new FileOutputStream("myList/copy.png"); // 写对象
byte[] bys = new byte[1024];
int len;
while((len=fis.read(bys))!=-1){ // 一次读一个字节数组数据
fos.write(bys,0,len);
}
}
}
# 字节缓冲流
- BufferOutputStream:该类实现缓冲输出流。 通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节导致底层系统的调用(而是一次性输出到指定文件中),提高写效率
- BufferedInputStream:创建BufferedInputStream将创建一个内部缓冲区数组。 当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次很多字节,提高读效率
构造方法:
- 字节缓冲输出流:BufferedOutputStream(OutputStream out)
- 字节缓冲输入流:BufferedInputStream(InputStream in)
为什么构造方法需要的是字节流,而不是具体的文件或者路径呢?
- 字节缓冲流仅仅提供缓冲区,而真正的读写数据还得依靠基本的字节流对象进行操作
public class FileDemo {
public static void main(String[] args) throws IOException {
// 写数据
FileOutputStream fos = new FileOutputStream("myList/box.txt"); // 写对象
BufferedOutputStream bos = new BufferedOutputStream(fos); // 创建一个写数据缓存区
bos.write("hello\n".getBytes());
bos.write("world\n".getBytes()); // 默认读写缓冲区都是8192字节大小
bos.close();
// 读数据
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("myList/box.txt"));
// 方式1: 一次读取一个字节数据
/*int by;
while ((by=bis.read())!=-1){
System.out.println((char)by);
}
bis.close();*/
// 一次读取一个字节数组
byte[] bys = new byte[1024];
int len;
while((len=bis.read(bys))!=-1) {
System.out.println(new String(bys,0,len));
}
}
}
练习:复制视频到模块目录下的“字节流复制图片.avi”
public class FileDemo {
public static void main(String[] args) throws IOException {
// 记录开始时间
long startTime = System.currentTimeMillis();
//method1(); // 共耗时:82161毫秒
//method2(); // 共耗时:147毫秒
//method3(); // 共耗时:371毫秒
method4(); // 共耗时:36毫秒
// 结束时间
long endTime = System.currentTimeMillis();
System.out.println("共耗时:"+(endTime-startTime)+"毫秒");
}
// 基于一次一个读写一个字节的方式
public static void method1() throws IOException{
FileInputStream fis = new FileInputStream("/Users/wangli/Desktop/java_Pro/note/10_字符流复制Java文件改进版.avi");
FileOutputStream fos = new FileOutputStream("myList/copy.avi");
int by;
while((by=fis.read())!=-1){
fos.write(by);
}
fos.close();
fis.close();
}
// 基于字节流一次读写一个字节数组
public static void method2() throws IOException{
FileInputStream fis = new FileInputStream("/Users/wangli/Desktop/java_Pro/note/10_字符流复制Java文件改进版.avi");
FileOutputStream fos = new FileOutputStream("myList/copy.avi");
byte[] bys = new byte[1024];
int len;
while((len=fis.read(bys))!=-1){
fos.write(bys,0,len);
}
fos.close();
fis.close();
}
// 字节流冲流一次读写一个字节
public static void method3() throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("/Users/wangli/Desktop/java_Pro/note/10_字符流复制Java文件改进版.avi"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("myList/copy.avi"));
int by;
while((by=bis.read())!=-1){
bos.write(by);
}
bos.close();
bis.close();
}
// 字节流冲流一次读写一个字节数组
public static void method4() throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("/Users/wangli/Desktop/java_Pro/note/10_字符流复制Java文件改进版.avi"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("myList/copy.avi"));
byte[] bys = new byte[1024];
int len;
while((len=bis.read(bys))!=-1){
bos.write(bys,0,len);
}
bos.close();
bis.close();
}
}
# 字符流
为什么会出现字符流?由于字节流操作中文不是特别的方便,所以Java就提供字符流:
- 字符流 = 字节流 + 编码表
用字节流复制文本文件时,文本文件也会有中文,但是没有问题,原因是最终底层操作会自动进行字节拼接成中文,如何识别是中文的呢? 汉字在存储的时候,无论选择哪种编码存储,第一个字节都是负数
/*
FileInputStream fis = new FileInputStream("myList/java.txt");
int by;
while ((by = fis.read()) != -1) {
System.out.println((char)by); // 每次读取一个字节,然而UTF-8编码的中文占3个字节,这必使其乱码,GBK占2个
}
*/
// 汉字都是负数表示
String s = "中国";
byte[] bys = s.getBytes(); // [-28, -72, -83, -27, -101, -67]
byte[] bys1 = s.getBytes("GBK"); // [-42, -48, -71, -6]
System.out.println(Arrays.toString(bys));
System.out.println(Arrays.toString(bys1));
# 编码表
基础知识:
- 计算机中储存的信息都是用二进制数表示的;我们在屏幕上看到的英文、汉字等字符是二进制数转换之后的结果
- 按照某种规则,将字符存储到计算机中,称为编码。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码 。这里强调一下:按照A编码存储,必须按照A编码解析,这样才能显示正确的文本符号。否则就会导致乱码现象
- 字符编码:就是一套自然语言的字符与二进制数之间的对应规则(A,65)
字符集:
- 是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等
- 计算机要准确的存储和识别各种字符集符号,就需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBXXX字符集、Unicode字符集等
# 字符串编解码
- 编码: byte[] getBytes():使用平台的默认字符集将该 String编码为一系列字节,将结果存储到新的字节数组中 byte[] getBytes(String charsetName):使用指定的字符集将该 String编码为一系列字节,将结果存储到新的字节数组中
- 解码: String(byte[] bytes):通过使用平台的默认字符集解码指定的字节数组来构造新的 String String(byte[] bytes, String charsetName):通过指定的字符集解码指定的字节数组来构造新的 String
// 编码
String s = "中国";
// 默认采用UTF-8编码
byte[] bys = s.getBytes(); // [-28, -72, -83, -27, -101, -67]
// 指定编码格式
byte[] bys1 = s.getBytes("GBK"); // [-42, -48, -71, -6]
System.out.println(Arrays.toString(bys));
System.out.println(Arrays.toString(bys1));
// 解码
String ss = new String(bys);
System.out.println(ss);
String s2 = new String(bys,"GBK");
System.out.println(s2); // 涓浗
String s3 = new String(bys1,"GBK");
System.out.println(s3); // 中国
字符流中的编码解码问题:
字符流抽象基类
- Reader:字符输入流的抽象类
- Writer:字符输出流的抽象类
字符流中和编码解码问题相关的两个类:
- InputStreamReader
- OutputStreamWriter
public class CharDemo {
public static void main(String[] args) throws IOException {
/*
* InputStreamReader:是从字节流到字符流的桥梁
* 它读取字节,并使用指定的编码将其解码为字节
* 它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集
* OutputStreamWriter:是从字符流到字节流的桥梁
* 是从字符流到字节流的桥梁,使用指定的编码将写入的字符编码为字节
* 它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集*/
// 写数据;OutputStreamWriter
FileOutputStream fos = new FileOutputStream("myList/java.txt");
//OutputStreamWriter osw = new OutputStreamWriter(fos); // 默认字符编码
OutputStreamWriter osw = new OutputStreamWriter(fos,"GBK"); // 指定字符编码
osw.write("中国");
osw.close();
FileInputStream fis = new FileInputStream("myList/java.txt");
//InputStreamReader isr = new InputStreamReader(fis);
InputStreamReader isr = new InputStreamReader(fis,"GBK");// 默认UTF8读取
// 一次读取一个字符数据
int ch;
while((ch=isr.read())!=-1){
System.out.println((char)ch);
}
isr.close();
}
}
字符流写数据的5种方式
方法名 | 说明 |
---|---|
void write(int c) | 写一个字符 |
void write(char[] cbuf) | 写入一个字符数组 |
void write(char[] cbuf, int off, int len) | 写入字符数组的一部分 |
void write(String str) | 写一个字符串 |
void write(String str, int off, int len) | 写一个字符串的一部分 |
// 写数据;OutputStreamWriter
FileOutputStream fos = new FileOutputStream("myList/java.txt");
OutputStreamWriter osw = new OutputStreamWriter(fos); // 默认字符编码
// 写一个字符
osw.write(97); // a
// 写字符数组
char[] chs = {'c','a','x'};
osw.write(chs);
osw.write(chs,0,chs.length-1); // 写入部分字符数组
// 写字符串
osw.write("我爱中国");
osw.write("我爱中国",0,2);
// 刷新缓冲区,写入文件中: 字符-->字节-->缓存区
osw.flush();
osw.close(); // 关闭流,先刷新缓存区
字符流读数据
方法名 | 说明 |
---|---|
int read() | 一次读一个字符数据 |
int read(char[] cbuf) | 一次读一个字符数组数据 |
public class CharDemo {
public static void main(String[] args) throws IOException {
// 写数据;OutputStreamWriter
InputStreamReader isr = new InputStreamReader(new FileInputStream("myList/java.txt"));
// 一次读一个字符数据
int ch; // ch表示读取的字符对应的数值
while ((ch = isr.read()) != -1) {
System.out.println((char) ch);
}
// 一次读取一个字符数组数据
char[] chs = new char[1024]; // 数组
int len; // 记录读取了多少
while ((len = isr.read(chs)) != -1) { // 当传入一个字符数组的时候,返回读取的长度
System.out.println(new String(chs, 0, len));
}
isr.close();
}
}
FileReader:用于读取字符文件的便捷类
- FileReader(String fileName)
FileWriter:用于写入字符文件的便捷类
- FileWriter(String fileName)
public class CharDemo {
public static void main(String[] args) throws IOException {
// 根据数据源创建字符输入流对象
FileReader fr = new FileReader("myList/java.txt");
// 根据目的地创建字符输出流对象
FileWriter fw = new FileWriter("myList/copy.txt");
// 读写数据, FileReader 继承InputStreamReader, FileWriter继承OutputStream
/*
int ch;
while((ch=fr.read())!=-1){
fw.write(ch);
}
*/
char[] chs = new char[1024];
int len;
while((len=fr.read(chs))!=-1){
fw.write(chs,0,len);
}
fw.close();
fr.close();
}
}
# 字符缓冲流
- BufferedWriter:将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入,可以指定缓冲区大小,或者可以接受默认大小。默认值足够大,可用于大多数用途
- BufferedReader:从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取,可以指定缓冲区大小,或者可以使用默认大小。 默认值足够大,可用于大多数用途
构造方法:
- BufferedWriter(Writer out) BufferedReader(Reader in)
public class CharDemo {
public static void main(String[] args) throws IOException {
// 根据目的地创建字符输出流对象
FileWriter fw = new FileWriter("myList/copy.txt");
// 缓存流,默认缓存8192字符
BufferedWriter bw = new BufferedWriter(fw);
bw.write("hello");
bw.write("world");
bw.close();
fw.close();
BufferedReader br = new BufferedReader(new FileReader("myList/copy.txt"));
// 一次读取一个字符数据
int ch;
while((ch=br.read())!=-1){
System.out.println((char)ch);
}
// 一次读取一个字符数组
char[] chs = new char[1024];
int len;
while((len=br.read(chs))!=-1){
System.out.println(new String(chs,0,len));
}
br.close();
}
}
BufferedWriter:
- void newLine():写一行行分隔符,行分隔符字符串由系统属性定义
BufferedReader:
- public String readLine() :读一行文字。 结果包含行的内容的字符串,不包括任何行终止字符,如果流的结尾已经到达,则为null
// 缓存流,默认缓存8192字符
BufferedWriter bw = new BufferedWriter(new FileWriter("myList/copy.txt"));
// 写数据
for(int i=0;i<10;i++){
bw.write("hello"+i);
bw.newLine();
}
bw.flush();
BufferedReader br = new BufferedReader(new FileReader("myList/copy.txt"));
String s = br.readLine();
System.out.println(s); // hello0
String s1 = br.readLine();
System.out.println(s1); // hello1
bw.close();
br.close();
练习
// 集合到文件
public class CharDemo {
public static void main(String[] args) throws IOException {
ArrayList<String> array = new ArrayList<String>();
array.add("hello");
array.add("world");
array.add("java");
// 创建字符缓存输出流对象
BufferedWriter bw = new BufferedWriter(new FileWriter("myList/java.txt"));
// 遍历集合,得到一个字符数据
for (String s : array) {
bw.write(s);
bw.newLine();
bw.flush();
}
bw.close();
}
}
//文件到集合
public class CharDemo {
public static void main(String[] args) throws IOException {
// 创建字符缓存输出流对象
BufferedReader br = new BufferedReader(new FileReader("myList/java.txt"));
ArrayList<String> array = new ArrayList<String>();
String line;
while((line= br.readLine())!=null){
array.add(line);
}
// 遍历集合
for(String s:array){
System.out.println(s);
}
br.close();
}
}
# 目录复制
单级目录复制
public class CharDemo {
public static void main(String[] args) throws IOException {
// 创建数据源目录file对象
File srcFile = new File("/Users/wangli/Desktop/原型");
// 获取数据源目录File对象的名称
String srcFolderName = srcFile.getName();
// 创建目的地file对象
File destFolder = new File("myList",srcFolderName);
if(!destFolder.exists()){
destFolder.mkdir();
}
// 获取数据源目录文件
File[] listFiles = srcFile.listFiles();
// 遍历File数组
for(File srFile:listFiles){
String srFileName = srFile.getName();
File destFile = new File(destFolder,srFileName);
//复制文件
copyFile(srFile,destFile);
}
}
public static void copyFile(File srcFile,File destFile) throws IOException{
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile));
byte[] bys = new byte[1024];
int len;
while ((len=bis.read(bys))!=-1){
bos.write(bys,0,len);
}
bos.close();
bis.close();
}
}
多级目录复制
public class CharDemo {
public static void main(String[] args) throws IOException {
// 创建数据源目录file对象
File srcFile = new File("/Users/wangli/Desktop/原型");
// 创建目的地file对象
File destFolder = new File("myList");
// 写方法实现文件夹复制
copyFolder(srcFile,destFolder);
}
private static void copyFolder(File srcFile, File destFolder) throws IOException {
if(srcFile.isDirectory()){
// 在目的地创建目录
String srcFileName = srcFile.getName();
File newFolder = new File(destFolder,srcFileName);
if (!newFolder.exists()){
newFolder.mkdir();
}
// 获取文件
File[] fileArray = srcFile.listFiles();
for(File file:fileArray){
// 把该File递归
copyFolder(file,newFolder);
}
}else{
File newFile = new File(destFolder,srcFile.getName());
copyFile(srcFile,newFile);
}
}
public static void copyFile(File srcFile,File destFile) throws IOException{
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile));
byte[] bys = new byte[1024];
int len;
while ((len=bis.read(bys))!=-1){
bos.write(bys,0,len);
}
bos.close();
bis.close();
}
}
# 标准输入输出流
System类中有两个静态的成员变量:
- public static final InputStream in:标准输入流。通常该流对应于键盘输入或由主机环境或用户指定的另一个输入源 public static final PrintStream out:标准输出流。通常该流对应于显示输出或由主机环境或用户指定的另一个输出目标
自己实现键盘录入数据:
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
写起来太麻烦,Java就提供了一个类实现键盘录入 Scanner sc = new Scanner(System.in);
public class CharDemo {
public static void main(String[] args) throws IOException {
// public static final InputStream in: 标准输入流
InputStream is = System.in;
/*
int by;
while((by=is.read())!=-1){
System.out.println((char)by);
}
// 不能读取汉字
*/
// 如何将字节流转换为字符流
InputStreamReader isr = new InputStreamReader(is);
// 使用字符流能不能读取一行数据,但是一次读取一行数据是字符缓存输入流的方法
BufferedReader br = new BufferedReader(isr);
System.out.println("请输入一个字符串:");
String line = br.readLine();
System.out.println("你输入的字符串:"+line);
}
}
System类中有两个静态的成员变量:
- public static final InputStream in:标准输入流。通常该流对应于键盘输入或由主机环境或用户指定的另一个输入源 public static final PrintStream out:标准输出流。通常该流对应于显示输出或由主机环境或用户指定的另一个输出目标
输出语句的本质:是一个标准的输出流
- PrintStream ps = System.out; PrintStream类有的方法,System.out都可以使用
# 打印流
打印流分类:
- 字节打印流:PrintStream 字符打印流:PrintWriter
打印流的特点:
- 只负责输出数据,不负责读取数据;永远不会抛出IOException;有自己的特有方法
方法名 | 说明 |
---|---|
PrintWriter(String fileName) | 使用指定的文件名创建一个新的PrintWriter,而不需要自动执行刷新 |
PrintWriter(Writer out, boolean autoFlush) | 创建一个新的PrintWriter out:字符输出流 autoFlush: 一个布尔值,如果为真,则println , printf ,或format方法将刷新输出缓冲区 |
# 对象序列化
对象序列化:就是将对象保存到磁盘中,或者在网络中传输对象 这种机制就是使用一个字节序列表示一个对象,该字节序列包含:对象的类型、对象的数据和对象中存储的属性等信息;字节序列写到文件之后,相当于文件中持久保存了一个对象的信息;反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。
要实现序列化和反序列化就要使用对象序列化流和对象反序列化流:
- 对象序列化流:ObjectOutputStream 对象反序列化流:ObjectInputStream
对象序列化流: ObjectOutputStream
将Java对象的原始数据类型和图形写入OutputStream。 可以使用ObjectInputStream读取(重构)对象。 可以通过使用流的文件来实现对象的持久存储。 如果流是网络套接字流,则可以在另一个主机上或另一个进程中重构对象
构造方法: ObjectOutputStream(OutputStream out):创建一个写入指定的OutputStream的ObjectOutputStream
序列化对象的方法: void writeObject(Object obj):将指定的对象写入ObjectOutputStream
注意:
- 一个对象要想被序列化,该对象所属的类必须必须实现Serializable 接口 Serializable是一个标记接口,实现该接口,不需要重写任何方法
# 对象反序列化
对象反序列化流: ObjectInputStream
- ObjectInputStream反序列化先前使用ObjectOutputStream编写的原始数据和对象
构造方法:
- ObjectInputStream(InputStream in):创建从指定的InputStream读取的ObjectInputStream
反序列化对象的方法:
- Object readObject():从ObjectInputStream读取一个对象
public class CharDemo {
public static void main(String[] args) throws IOException,ClassNotFoundException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("myList/java.txt"));
// 创建对象
Student s = new Student("wang",12);
oos.writeObject(s); // 序列化到文件中
// 如果报错:NotSerializableException,需要实现一个Serializable接口
oos.close();
// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("myList/java.txt"));
Object obj = ois.readObject();
Student ss =(Student)obj;
System.out.println(ss.getName());
ois.close();
}
}
序列化常见问题
1、用对象序列化流序列化了一个对象后,假如我们修改了对象所属的类文件,读取数据会不会出问题呢?
会出问题,会抛出InvalidClassException异常
2、如果出问题了,如何解决呢?
重新序列化
给对象所属的类加一个serialVersionUID
private static final long serialVersionUID = 42L;
3、如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?
给该成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程
练习
//Student.java
import java.io.Serializable;
public class Student implements Serializable {
// 如果没有添加这个UID值,不一定是42l,那么如果你的类发生变化,系统自动生成的UID可能不一致导致报错
private static final long serialVersionUID = 42L;
private String name;
// transient 可以在反序列是隐藏
private transient int age;
public Student(){}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
//CharDemo.java
public class CharDemo {
public static void main(String[] args) throws IOException,ClassNotFoundException {
write();
read();
}
private static void read() throws IOException,ClassNotFoundException {
// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("myList/java.txt"));
Object obj = ois.readObject();
Student ss =(Student)obj;
System.out.println(ss);
ois.close();
}
private static void write() throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("myList/java.txt"));
// 创建对象
Student s = new Student("wang",12);
oos.writeObject(s); // 序列化到文件中
// 如果报错:NotSerializableException,需要实现一个Serializable接口
oos.close();
}
}
# Properties
Properties概述
- 是一个Map体系的集合类
- Properties可以保存到流中或从流中加载
- 属性列表中的每个键及其对应的值都是一个字符串
import java.util.Properties;
import java.util.Set;
public class CharDemo {
public static void main(String[] args) throws IOException,ClassNotFoundException {
// 创建集合对象
Properties prop = new Properties();
// 存储元素
prop.put("itwang01","lin");
prop.put("itwang02","zhang");
prop.put("itwang03","lee");
// 遍历集合
Set<Object> keySet = prop.keySet();
for(Object key:keySet){
Object val = prop.get(key);
System.out.println(key+", "+val);
}
}
}
Properties作为集合的特有方法
方法名 | 说明 |
---|---|
Object setProperty(String key, String value) | 设置集合的键和值,都是String类型,底层调用 Hashtable方法 put |
String getProperty(String key) | 使用此属性列表中指定的键搜索属性 |
Set<String> stringPropertyNames() | 从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串 |
import java.util.Properties;
import java.util.Set;
public class CharDemo {
public static void main(String[] args) throws IOException,ClassNotFoundException {
// 创建集合对象
Properties prop = new Properties();
// 存储元素
prop.setProperty("001","wang");
prop.setProperty("002","li");
prop.setProperty("003","x");
System.out.println(prop.getProperty("001")); //wang
Set<String> names = prop.stringPropertyNames();
for (String key:names){
//System.out.println(key); // 获取键
System.out.println(prop.getProperty(key));
}
// 遍历集合
Set<Object> keySet = prop.keySet();
for(Object key:keySet){
Object val = prop.get(key);
System.out.println(key+", "+val);
}
}
}
Properties和IO流结合的方法
方法名 | 说明 |
---|---|
void load(InputStream inStream) | 从输入字节流读取属性列表(键和元素对) |
void load(Reader reader) | 从输入字符流读取属性列表(键和元素对) |
void store(OutputStream out, String comments) | 将此属性列表(键和元素对)写入此 Properties表中,以适合于使用 load(InputStream)方法的格式写入输出字节流 |
void store(Writer writer, String comments) | 将此属性列表(键和元素对)写入此 Properties表中,以适合使用 load(Reader)方法的格式写入输出字符流 |
import java.util.Properties;
public class CharDemo {
public static void main(String[] args) throws IOException,ClassNotFoundException {
// 把集合中数据保存到文件
myStore();
// 把文件的数据加载到集合中
myLoad();
}
// 存储数据
public static void myStore() throws IOException {
Properties prop = new Properties();
// 存储元素
prop.setProperty("001","wang");
prop.setProperty("002","li");
prop.setProperty("003","x");
FileWriter fw = new FileWriter("myList/java.txt");
prop.store(fw,null);
fw.close();
}
public static void myLoad() throws IOException {
Properties prop = new Properties();
FileReader fr = new FileReader("myList/java.txt");
prop.load(fr);
fr.close();
System.out.println(prop);
}
}
# 多线程
# 进程
是正在运行的程序,是系统进行资源分配和调用的独立单位,每一个进程都有它自己的内存空间和系统资源。
# 线程
是进程中的单个顺序控制流,是一条执行路径
- 单线程:一个进程如果只有一条执行路径,则称为单线程程序 多线程:一个进程如果有多条执行路径,则称为多线程程序
# 多线程实现
多线程的实现方案有两种:继承Thread类;实现Runnable接口
方案1:继承Thread类
- 定义一个类MyThread继承Thread类;在MyThread类中重写run()方法;创建MyThread类的对象;启动线程
// MyThread.java
public class MyThread extends Thread{
// 重写run方法
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for(int i=0;i<100;i++){
// 通过getName获取线程名称
System.out.println(getName()+":"+i); // Thread-0,其实是在Thread的无参构造中调用了getThreadNum
}
}
}
// MyThreadDemo.java
public class MyThreaDemo {
public static void main(String[] args) {
MyThread my1 = new MyThread();
// 也可以在构造方法的时候设置线程名,但是需要写构造方法
MyThread my2 = new MyThread("xxx");
// run仅仅代表触发函数,并没有启动多线程的异步效果
//my1.run();
//my2.run();
// 设置线程名
my1.setName("高铁");
my1.setName("飞机");
// 通过start导致此线程开始执行,由java虚拟机调用此线程的run方法
my1.start();
my2.start();
/*
* 两个小问题:
为什么要重写run()方法?因为run()是用来封装被线程执行的代码
run()方法和start()方法的区别?
run():封装线程执行的代码,直接调用,相当于普通方法的调用
start():启动线程;然后由JVM调用此线程的run()方法
*/
}
}
Thread类中设置和获取线程名称的方法
- void setName(String name):将此线程的名称更改为等于参数 name String getName():返回此线程的名称 通过构造方法也可以设置线程名称
如何获取main()方法所在的线程名称?
- public static Thread currentThread():返回对当前正在执行的线程对象的引用
# 线程调度
线程有两种调度模型:
- 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
- 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程 获取的 CPU 时间片相对多一些(Java使用的是抢占式调度模型)
Thread类中设置和获取线程优先级的方法
- public final int getPriority():返回此线程的优先级
- public final void setPriority(int newPriority):更改此线程的优先级 线程默认优先级是5;线程优先级的范围是:1-10;线程优先级高仅仅表示线程获取的CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到你想要的效果
MyThread my1 = new MyThread("高铁");
// 也可以在构造方法的时候设置线程名,但是需要写构造方法
MyThread my2 = new MyThread("飞机");
MyThread my3 = new MyThread("客车");
// 获取线程优先级
System.out.println(my1.getPriority()); // 5
System.out.println(my2.getPriority()); // 5
System.out.println(my3.getPriority()); // 5
my1.setPriority(5);
my2.setPriority(10);
my3.setPriority(1);
// 通过start导致此线程开始执行,由java虚拟机调用此线程的run方法
my1.start();
my2.start();
my3.start();
# 线程控制
方法名 | 说明 |
---|---|
static void sleep(long millis) | 使当前正在执行的线程停留(暂停执行)指定的毫秒数 |
void join() | 等待这个线程死亡 |
void setDaemon(boolean on) | 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出 |
// sleep测试
@Override
public void run() {
for(int i=0;i<100;i++){
// 通过getName获取线程名称
System.out.println(getName()+":"+i); // Thread-0,其实是在Thread的无参构造中调用了getThreadNum
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// join测试
MyThread my1 = new MyThread("高铁");
MyThread my2 = new MyThread("飞机");
my1.start();
my1.join(); // 需要等到my1完后才继续
my2.start();
// 设置守护线程
public class MyThreaDemo {
public static void main(String[] args) throws InterruptedException {
// 也可以在构造方法的时候设置线程名,但是需要写构造方法
MyThread my2 = new MyThread("张飞");
MyThread my3 = new MyThread("关羽");
Thread.currentThread().setName("刘备"); // 主线程
// 设置守护线程,主线程挂了跟着挂
my2.setDaemon(true);
my3.setDaemon(true);
my2.start();
my3.start();
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
方案2:实现Runnable接口
- 定义一个类MyRunnable实现Runnable接口 在MyRunnable类中重写run()方法;创建MyRunnable类的对象;创建Thread类的对象,把MyRunnable对象作为构造方法的参数;启动线程
//MyRunable.java
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
// MyThreadDemo.java
public class MyThreaDemo {
public static void main(String[] args) throws InterruptedException {
MyRunnable my = new MyRunnable();
Thread t1 = new Thread(my);
Thread t2 = new Thread(my,"xxx"); // 可以传递一个线程名
t1.start();
t2.start();
}
}
# 线程同步
在实际生活中,售票时出票也是需要时间的,所以,在出售一张票的时候,需要一点时间的延迟,接下来我们去修改卖票程序中卖票的动作:每次出票时间100毫秒,用sleep()方法实现。
数据安全问题:
为什么出现问题?(这也是我们判断多线程程序是否会有数据安全问题的标准)
是否是多线程环境
是否有共享数据
是否有多条语句操作共享数据
如何解决多线程安全问题呢?
基本思想:让程序没有安全问题的环境
怎么实现呢?
把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
Java提供了同步代码块的方式来解决
# 同步代码块
锁多条语句操作共享数据,可以使用同步代码块实现
格式:
synchronized(任意对象) {
多条语句操作共享数据的代码
}
synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁。
/*
同步的好处和弊端
好处:解决了多线程的数据安全问题
弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
*/
代码演示:
//SellTick.java
public class SellTicket implements Runnable {
private int tickets = 100;
private Object obj = new Object(); // 同一把锁
@Override
public void run() {
while(true){
synchronized (obj){
// 假设t1抢到CPU的执行权
if(tickets>0){
// t1进来后,就会把这段代码锁起来
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"张票");
tickets--;
}
}
}
}
}
// SellTicketDemo.java
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st,"窗口1");
Thread t2 = new Thread(st,"窗口2");
Thread t3 = new Thread(st,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
# 同步方法
就是把synchronized关键字加到方法上
格式:修饰符 synchronized 返回值类型 方法名(方法参数) { }
同步方法的锁对象是什么呢? this
同步静态方法:就是把synchronized关键字加到静态方法上
格式:修饰符 static synchronized 返回值类型 方法名(方法参数) { }
同步静态方法的锁对象是什么呢?类名.class,也就是将obj换成类.class
代码
public class SellTicket implements Runnable {
private static int tickets = 100;
private Object obj = new Object(); // 同一把锁
// 同步方法
private static synchronized void sellTick(){
if(tickets>0){
// t1进来后,就会把这段代码锁起来
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"张票");
tickets--;
}
}
@Override
public void run() {
while(true){
sellTick();
}
}
}
# 线程安全类
StringBuffer
- 线程安全,可变的字符序列 从版本JDK 5开始,被StringBuilder 替代。 通常应该使用StringBuilder类,因为它支持所有相同的操作,但它更快,因为它不执行同步
Vector
- 从Java 2平台v1.2开始,该类改进了List接口,使其成为Java Collections Framework的成员。 与新的集合实现不同, Vector被同步。 如果不需要线程安全的实现,建议使用ArrayList代替Vector
Hashtable
- 该类实现了一个哈希表,它将键映射到值。 任何非null对象都可以用作键或者值 从Java 2平台v1.2开始,该类进行了改进,实现了Map接口,使其成为Java Collections Framework的成员。 与新的集合实现不同, Hashtable被同步。 如果不需要线程安全的实现,建议使用HashMap代替Hashtable
# 锁
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁, 为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作
Lock中提供了获得锁和释放锁的方法
- void lock():获得锁 void unlock():释放锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化 ReentrantLock的构造方法
- ReentrantLock():创建一个ReentrantLock的实例
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SellTicket implements Runnable {
private int tickets = 100;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while(true){
try{
lock.lock();
if(tickets>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"张票");
tickets--;
}
}finally {
lock.unlock();
}
}
}
}
# 生产者消费者
生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻,所谓生产者消费者问题,实际上主要是包含了两类线程:
- 一类是生产者线程用于生产数据 一类是消费者线程用于消费数据
为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库:
- 生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为 消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为
为了体现生产和消费过程中的等待和唤醒,Java就提供了几个方法供我们使用,这几个方法在Object类中 Object类的等待和唤醒方法:
方法名 | 说明 |
---|---|
void wait() | 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法 |
void notify() | 唤醒正在等待对象监视器的单个线程 |
void notifyAll() | 唤醒正在等待对象监视器的所有线程 |
// Box.java
public class Box {
private int milk;
private boolean state = false;
public synchronized void put(int milk) {
// 如果有牛奶,则需要等待消费
if (state) {
try {
wait(); // 必须有锁对象,需要synchronized
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.milk = milk;
System.out.println("送奶工将第" + this.milk + "瓶奶放入奶箱中");
state = true;
//唤醒其他线程
notifyAll();
}
public synchronized void get() {
if (!state) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("用户拿到第" + this.milk + "瓶奶");
state = false;
//唤醒其他线程
notifyAll();
}
}
// Producer.java
public class Producer implements Runnable {
private Box b;
public Producer(){}
public Producer(Box b) {
this.b = b;
}
@Override
public void run() {
for(int i=1;i<6;i++){
b.put(i);
}
}
}
//Customer.java
public class Customer implements Runnable {
private Box b;
public Customer(Box b) {
this.b = b;
}
@Override
public void run() {
while(true){
b.get();
}
}
}
//BoxDemo.java
public class BoxDemo {
public static void main(String[] args) {
Box b = new Box();
Producer p = new Producer(b);
Customer c = new Customer(b);
Thread t1 = new Thread(p);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
# 网络编程
# 计算机网络
是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统
网络编程: 在网络通信协议下,实现网络互连的不同计算机上运行的程序间可以进行数据交换
# UDP协议
- 用户数据报协议(User Datagram Protocol)
- UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。 由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输
- 例如视频会议通常采用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议
# TCP协议
传输控制协议 (Transmission Control Protocol)
TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”
三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠 第一次握手,客户端向服务器端发出连接请求,等待服务器确认 第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求 第三次握手,客户端再次向服务器端发送确认信息,确认连接
# UDP通信
UDP协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket对象,但是这两个Socket只是发送,接收数据的对象。因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念(Java提供了DatagramSocket类作为基于UDP协议的Socket)
//SendDemo.java
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
public class SendDemo {
public static void main(String[] args) throws IOException {
// 创建发送端的socket对象
DatagramSocket ds = new DatagramSocket();
// 创建数据,并数据打包
byte[] bys = "hello udp,我".getBytes();
InetAddress addrs = InetAddress.getByName("127.0.0.1");
int length = bys.length;
int port = 10086;
DatagramPacket dp = new DatagramPacket(bys,length,addrs,port);
// 发送数据
ds.send(dp);
// 关闭
ds.close();
}
}
// ReceiveDemo.java
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket(10086);
// 创建个数据包,接受数据
byte[] bys = new byte[1024];
DatagramPacket dp = new DatagramPacket(bys, bys.length);
ds.receive(dp);
// 解析数据包
byte[] datas = dp.getData();
// 获取实际数据长度
int len = dp.getLength();
String dataString = new String(datas,0,len);
System.out.println("数据是:"+dataString);
ds.close();
}
}
# TCP通信
TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket对象,从而在通信的两端形成网络虚拟链路,一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信。
Java对基于TCP协议的的网络提供了良好的封装,使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信 Java为客户端提供了Socket类,为服务器端提供了ServerSocket类
//Client.java
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
public class Client {
public static void main(String[]args) throws IOException{
//Socket s = new Socket(InetAddress.getByName("127.0.0.1"),10000);
Socket s = new Socket("127.0.0.1",10001);
// 获取输出流
OutputStream os = s.getOutputStream();
os.write("hello,我来了".getBytes()); // 给服务器发送数据
// 读取服务器数据
InputStream is= s.getInputStream();
byte[] bys = new byte[1024];
int len = is.read(bys);
String data = new String(bys,0,len);
System.out.println("客户端:"+ data);
s.close();
}
}
//Server.java
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
// 创建服务器socket对象
ServerSocket ss = new ServerSocket(10001);
Socket s = ss.accept();
// 获取输入流,读数据
InputStream is = s.getInputStream();
byte[] bys = new byte[1024];
int len = is.read(bys);
String data = new String(bys,0,len);
System.out.println("数据是:"+data);
// 反馈
OutputStream os = s.getOutputStream();
os.write("数据已收到".getBytes());
s.close();
ss.close();
}
}
练习:键盘输入封装
//Client.java
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
public class Client {
public static void main(String[]args) throws IOException{
//Socket s = new Socket(InetAddress.getByName("127.0.0.1"),10000);
Socket s = new Socket("127.0.0.1",10001);
// 数据来自于键盘录入,直到输入的数是886
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
// 封装输出流对象
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
String line;
while((line=br.readLine())!=null){
if("886".equals(line)){
break;
}
bw.write(line);
bw.newLine();
bw.flush();
}
s.close();
}
}
//Server.java
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
// 创建服务器socket对象
ServerSocket ss = new ServerSocket(10001);
Socket s = ss.accept();
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line;
while((line=br.readLine())!=null){ // 需要转换为BufferReader才能使用readLine
System.out.println(line);
}
ss.close();
}
}
# Lambda
练习:启动一个线程,在控制台输出一句话:多线程程序启动了
public class LambdaDemo {
public static void main(String[] args) {
// 使用匿名内部类的方式改进
/*
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("多线程启动");
}
}).start(); // 这种方式就不用再创建一个类,实现接口,然后调用
*/
// lambda改进,更加简单
new Thread(()->{
System.out.println("多线程启动");
}).start();
}
}
# Lambda格式
Lambda表达式的标准格式
匿名内部类中重写run()方法的代码分析:
- 方法形式参数为空,说明调用方法时不需要传递参数 方法返回值类型为void,说明方法执行没有结果返回 方法体中的内容,是我们具体要做的事情
Lambda表达式的代码分析:
- ():里面没有内容,可以看成是方法形式参数为空 ->:用箭头指向后面要做的事情 { }:包含一段代码,我们称之为代码块,可以看成是方法体中的内容
综上:
Lambda表达式的格式
格式:(形式参数) -> {代码块}
形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
->:由英文中画线和大于符号组成,固定写法。代表指向动作
代码块:是我们具体要做的事情,也就是以前我们写的方法体内容
Lambda表达式的前提:有一个接口;接口中有且仅有一个抽象方法
练习1:无参无返回Lambda表达
// Eatable.java
public interface Eatable {
void eat();
}
//EatableImpl.java
public class EatableImpl implements Eatable {
@Override
public void eat() {
System.out.println("一天一个苹果");
}
}
//LambdaDemo.java
public class LambdaDemo {
public static void main(String[] args) {
// 在主方法中调用useEatable方法
Eatable e= new EatableImpl();
useEatable(e);
// 使用匿名内部类
useEatable(new Eatable() {
@Override
public void eat() {
System.out.println("一天一苹果,内部类");
}
});
// 匿名表达式
useEatable(()->{
System.out.println("一天一苹果,匿名函数");
});
}
public static void useEatable(Eatable e) {
e.eat();
}
}
练习2:带参无返回Lambda表达
// Flyable.java
public interface Flyable {
void fly(String s);
}
// FlyableDemo.java
public class FlyableDemo {
public static void main(String[] args) {
// 匿名内部类
useFlyable(new Flyable() {
@Override
public void fly(String s) {
System.out.println(s);
System.out.println("内部类");
}
});
// Lambda表达式
useFlyable((String s)->{
System.out.println(s);
System.out.println("Lambda");
});
}
public static void useFlyable(Flyable f){
f.fly("川普加油");
}
}
练习3:参数省略模式
public class FlyableDemo {
public static void main(String[] args) {
// 1、所有的参数类型,可以省略,要省略就都省略
useFlyable((s)->{
System.out.println(s);
System.out.println("Lambda");
});
// 2、如果有且只有一个参数,可以省略括号
useFlyable(s->{
System.out.println(s);
});
// 3、如果代码块的语句只有一句,那么可以省略大括号和分号,如果有return时,把return也省掉
useFlyable(s-> System.out.println(s));
}
public static void useFlyable(Flyable f){
f.fly("川普加油");
}
}
注意事项:
- 使用Lambda必须要有接口,并且要求接口中有且仅有一个抽象方法
- 必须有上下文环境,才能推导出Lambda对应的接口: 根据局部变量的赋值得知Lambda对应的接口:Runnable r = () -> System.out.println("Lambda表达式"); 根据调用方法的参数得知Lambda对应的接口:new Thread(() -> System.out.println("Lambda表达式")).start();
# 与内部类的区别
所需类型不同
-匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
-Lambda表达式:只能是接口
使用限制不同
-如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
-如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式
实现原理不同
-匿名内部类:编译之后,产生一个单独的.class字节码文件
-Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成
# 方法引用
在使用Lambda表达式的时候,我们实际上传递进去的代码就是一种解决方案:拿参数做操作,那么考虑一种情况:如果我们在Lambda中所指定的操作方案,已经有地方存在相同方案,那是否还有必要再写重复逻辑呢? 答案肯定是没有必要,那我们又是如何使用已经存在的方案的呢?这就是我们要讲解的方法引用,我们是通过方法引用来使用已经存在的方案
实例:
// PrintableDemo.java
public class PrintableDemo {
public static void main(String[] args) {
usePrintable(s-> System.out.println(s));
// 方法引用: ::
usePrintable(System.out::println); // 可推导的就是可以省略的,这个是把参数传导了println方法中
}
public static void usePrintable(PrinteInter p){
p.printString("爱生活爱Java");
}
}
// PrinterInter.java
public interface PrinteInter {
void printString(String s);
}
/*
回顾一下我们在体验方法引用中的代码
Lambda表达式:usePrintable(s -> System.out.println(s));
分析:拿到参数 s 之后通过Lambda表达式,传递给 System.out.println 方法去处理
方法引用:usePrintable(System.out::println);
分析:直接使用 System.out 中的 println 方法来取代Lambda,代码更加的简洁
推导与省略:
如果使用Lambda,那么根据“可推导就是可省略”的原则,无需指定参数类型,也无需指定的重载形式,它们都将被自动推导
如果使用方法引用,也是同样可以根据上下文进行推导
方法引用是Lambda的孪生兄弟
*/
方法引用符 :: 该符号为引用运算符,而它所在的表达式被称为方法引用。
# Lambda表达式支持的方法引用
常见的引用方式:
引用类方法
引用类方法,其实就是引用类的静态方法
/*格式:类名::静态方法 范例:Integer::parseInt Integer类的方法:public static int parseInt(String s) 将此String转换为int类型数据*/ public class CovertDemo { public static void main(String[] args) { /* useCovert((String s)->{ return Integer.parseInt(s); }); */ // 优化 useCovert(s->Integer.parseInt(s)); // 引用类方法来改进 useCovert(Integer::parseInt); // lambda表达式被类方法替代是,它的形式参数全部传递给静态方法作为参数 } public static void useCovert(Converter c){ int number = c.covert("555"); System.out.println(number); } }
引用对象的实例方法
引用对象的实例方法,其实就引用类中的成员方法
/*格式:对象::成员方法 范例: "HelloWorld"::toUpperCase String类中的方法:public String toUpperCase() 将此String所有字符转换为大写*/ //Printer.java public interface Printer { void printUpperCase(String s); } // PrintDemo.java public class PrinterDemo { public static void main(String[] args) { // Lambda表达式 usePrinter((String s)->{ /* String result = s.toUpperCase(); System.out.println(result); */ System.out.println(s.toUpperCase()); }); // 简化 usePrinter(s -> System.out.println(s.toUpperCase())); // 对象引用实例方法 PrintString ps = new PrintString(); usePrinter(ps::printUpper); // lambda表达式被对象实例方法替换时,它的形式参数全部传递给方法作为参数 } private static void usePrinter(Printer p){ p.printUpperCase("Hello world"); } } // PrintString.java public class PrintString { public void printUpper(String s){ String result = s.toUpperCase(); System.out.println(result); } }
引用类的实例方法
引用类的实例方法,其实就是引用类中的成员方法
/* 格式:类名::成员方法 范例:String::substring String类中的方法: public String substring(int beginIndex,int endIndex) 从beginIndex开始到endIndex结束,截取字符串。返回一个子串,子串的长度为endIndex-beginIndex */ // MyString.java public class MyStringDemo { public static void main(String[] args) { // lambda表达式 useMyString((String s, int x, int y) -> { return s.substring(x, y); }); // 简化 useMyString((s, x, y) -> s.substring(x, y) ); // 类的实例方法 useMyString(String::substring); // lambda表达式被类的实例方法替代的时候,第一个参数作为调用者,后面的参数全部传递给该方法作为参数 } private static void useMyString(MyString my) { String s = my.mySubString("Hello world", 2, 5); System.out.println(s); } } // MyString.java public interface MyString { String mySubString(String s,int x,int y); }
引用构造器
引用构造器,其实就是引用构造方法
/* 格式:类名::new 范例:Student::new */ //StudentBuilder.java public interface StudentBuilder { Student build(String name,int age); } //StudentDemo.java public class StudentDemo { public static void main(String[] args) { // 匿名内部类 useStudentBuilder(new StudentBuilder() { @Override public Student build(String name, int age) { return new Student(name,age); } }); useStudentBuilder((String name,int age)->{ /* Student s = new Student(name,age); return s;*/ return new Student(name,age); }); // 优化 useStudentBuilder((name,age)->new Student(name,age)); // 方法引用 useStudentBuilder(Student::new); // lambda 表达式被构造器替代的时候,他的所有参数都会传递给new的初始化 } private static void useStudentBuilder(StudentBuilder sb){ Student s = sb.build("哇那个",12); System.out.println(s.getName()); } }
# 函数式接口
函数式接口:有且仅有一个抽象方法的接口 Java中的函数式编程体现就是Lambda表达式,所以函数式接口就是可以适用于Lambda使用的接口,只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。
如何检测一个接口是不是函数式接口呢?
- @FunctionalInterface 放在接口定义的上方:如果接口是函数式接口,编译通过;如果不是,编译失败
//MyInter.java
@FunctionalInterface
public interface MyInter {
void show();
}
//Interemo.java
public class InterDemo {
public static void main(String[] args) {
MyInter my = () -> System.out.println("函数式接口");
my.show();
}
}
# 函数式接口作为方法的参数
如果方法的参数是一个函数式接口,我们可以使用Lambda表达式作为参数传递:startThread(() -> System.out.println(Thread.currentThread().getName() + "线程启动了"));
public class RunnableDemo {
public static void main(String[] args) {
// 匿名内部类
startThread(new Runnable() {
@Override
public void run() {
System.out.println("内部类启动");
}
});
// lambda启动
startThread(()-> System.out.println("lambda表达式启动"));
}
private static void startThread(Runnable r){
new Thread(r).start();
}
}
# 函数式接口作为方法的返回值
如果方法的返回值是一个函数式接口,我们可以使用Lambda表达式作为结果返回
public class ComparatorDemo {
public static void main(String[] args) {
ArrayList<String> array = new ArrayList<String>();
array.add("ccc");
array.add("23cc");
array.add("c");
System.out.println("排序前:" + array);
Collections.sort(array); // 自然排序
System.out.println("排序后:" + array);
System.out.println("排序前:" + array);
Collections.sort(array,getComparator()); // 自定义排序
System.out.println("排序后:" + array);
}
private static Comparator<String> getComparator() {
//匿名内部类
/*
return new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
};*/
return (String s1,String s2)->{
return s1.length()-s2.length();
};
}
}
# 常用接口
Supplier接口: Supplier<T>接口也被称为生产型接口,如果我们指定了接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据供我们使用
/* Supplier<T>:包含一个无参的方法 T get():获得结果 该方法不需要参数,它会按照某种实现逻辑(由Lambda表达式实现)返回一个数据 */ import java.util.function.Supplier; public class SupplierDemo { public static void main(String[] args) { String s = getString(()->"林青霞"); System.out.println(s); Integer i = getInteger(()->2); System.out.println(i); } private static Integer getInteger(Supplier<Integer> sup){ return sup.get(); } private static String getString(Supplier<String> sup){ return sup.get(); } }
练习:获取数组中的最大值
import java.util.function.Supplier; public class SupplierDemo { public static void main(String[] args) { int[] arr = {19,112,14,4,24}; int maxVal = getMax(()->{ int max = arr[0]; for (int i=1;i<arr.length;i++){ if(arr[i]>max){ max=arr[i]; } } return max; }); System.out.println(maxVal); } private static Integer getMax(Supplier<Integer> sup){ return sup.get(); } }
Consumer接口:Consumer<T>接口也被称为消费型接口,它消费的数据的数据类型由泛型指定
Consumer<T>:包含两个方法 void accept(T t):对给定的参数执行此操作 default Consumer<T> andThen(Consumer after):返回一个组合的 Consumer,依次执行此操作,然后执行 after操作
import java.util.function.Consumer;
public class ConsumerDemo {
public static void main(String[] args) {
operatorString("大王",(String s)->{
System.out.println("s");
}); // 可简化
operatorString("小王",System.out::println); // 方法引用
operatorString("红桃",s->{
System.out.println(new StringBuilder(s).reverse().toString());
});
operatorString("林青霞",s-> System.out.println(s),s-> System.out.println(new StringBuilder(s).reverse().toString()));
}
// 消费一个字符串两种方式
private static void operatorString(String name,Consumer<String> con1,Consumer<String> con2){
//con1.accept(name);
//con2.accept(name);
// 等价于上面两句
con1.andThen(con2).accept(name);
}
// 消费一个字符串
private static void operatorString(String name, Consumer<String> con){
con.accept(name);
}
}
练习:String[] strArray = {"林青霞,30", "张曼玉,35", "王祖贤,33"};,字符串数组中有多条信息,请按照格式:“姓名:XX,年龄:XX"的格式将信息打印出来。
import java.util.function.Consumer;
public class ConsumerDemo {
public static void main(String[] args) {
String[] strArr = {"林青霞,30", "张曼玉,20"};
printInfo(strArr, (String str) -> {
String name = str.split(",")[0];
System.out.print("姓名:" + name);
}, (String str) -> {
int age = Integer.parseInt(str.split(",")[1]);
System.out.println(",年纪:" + age);
});
}
private static void printInfo(String[] strArray, Consumer<String> con1, Consumer<String> con2) {
for (String str : strArray) {
con1.andThen(con2).accept(str);
}
}
}
Predicate<T>接口: 通常用于判断参数是否满足指定的条件
boolean test(T t):对给定的参数进行判断(判断逻辑由Lambda表达式实现),返回一个布尔值 default Predicate<T> negate():返回一个逻辑的否定,对应逻辑非 default Predicate<T> and(Predicate other):返回一个组合判断,对应短路与 default Predicate<T> or(Predicate other):返回一个组合判断,对应短路或
import java.util.function.Predicate;
public class ConsumerDemo {
public static void main(String[] args) {
boolean b1 = checkString("hello",(String s)->{
return s.length()>8;
});
System.out.println(b1);
}
private static boolean checkString(String s,Predicate<String> pre){
return pre.test(s);
}
}
# Stream流
使用Stream流的方式完成过滤操作
/*
创建一个集合,存储多个字符串元素;把集合中所有以"张"开头的元素存储到一个新的集合;把"张"开头的集合中的长度为3的元素存储到一个新的集合
遍历上一步得到的集合。
Stream流的使用
生成流
通过数据源(集合,数组等)生成 ->list.stream()
中间操作
一个流后面可以跟随零个或多个中间操作,其目的主要是打开流,做出某种程度的数据过滤/映射,然后返回一个新的流,交给下一个操作使用,filter()
终结操作
一个流只能有一个终结操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作,forEach
*/
import java.util.ArrayList;
public class ConsumerDemo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("张鳗鱼");
list.add("张大仙");
list.add("张无忌");
list.add("张敏");
list.add("王析");
list.add("川普");
list.stream().filter(s->s.startsWith("张")).filter(s->s.length()==3).forEach(s -> System.out.println(s));
}
private static boolean checkString(String s,Predicate<String> pre){
return pre.test(s);
}
}
各类数据结构初始化
public class ConsumerDemo {
public static void main(String[] args) {
//1、Collection体系的集合可以使用默认的方法stream()生成流
List<String> list = new ArrayList<String>();
Stream<String> listStream = list.stream();
// 2、集合
Set<String> set = new HashSet<String>();
Stream<String> setStream = set.stream();
// 3、Map
Map<String,Integer> map = new HashMap<String,Integer>();
Stream<String> keyStream = map.keySet().stream();
Stream<Integer> valStream = map.values().stream();
// 数组
String[] stringArray = {"hello","Java"};
Stream<String> setArrStream = Stream.of(stringArray);
Stream<String> setArrStream2 = Stream.of("hello","Python");
}
}
# 反射和模块化
# 类加载
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过类的加载,类的连接,类的初始化这三个步骤来对类进行初始化。如果不出现意外情况,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载或者类初始化。
类的加载
- 就是指将class文件读入内存,并为之创建一个 java.lang.Class 对象 任何类被使用时,系统都会为之建立一个 java.lang.Class 对象
类的连接
- 验证阶段:用于检验被加载的类是否有正确的内部结构,并和其他类协调一致 准备阶段:负责为类的类变量分配内存,并设置默认初始化值 解析阶段:将类的二进制数据中的符号引用替换为直接引用
类的初始化
- 在该阶段,主要就是对类变量进行初始化
类的初始化步骤:
- 假如类还未被加载和连接,则程序先加载并连接该类
- 假如该类的直接父类还未被初始化,则先初始化其直接父类
- 假如类中有初始化语句,则系统依次执行这些初始化语句 注意:在执行第2个步骤的时候,系统对直接父类的初始化步骤也遵循初始化步骤1-3
类的初始化时机:
- 创建类的实例
- 调用类的类方法
- 访问类或者接口的类变量,或者为该类变量赋值
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
- 初始化某个类的子类
- 直接使用java.exe命令来运行某个主类
# 类加载器
类加载器的作用:
- 负责将.class文件加载到内存中,并为之生成对应的 java.lang.Class 对象,虽然我们不用过分关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行。
JVM的类加载机制
- 全盘负责:就是当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
- 父类委托:就是当一个类加载器负责加载某个Class时,先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
- 缓存机制:保证所有加载过的Class都会被缓存,当程序需要使用某个Class对象时,类加载器先从缓存区中搜索该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存储到缓存区
ClassLoader:是负责加载类的对象
ClassLoader 中的两个方法
- static ClassLoader getSystemClassLoader():返回用于委派的系统类加载器
- ClassLoader getParent():返回父类加载器进行委派
Java运行时具有以下内置类加载器
- Bootstrap class loader:它是虚拟机的内置类加载器,通常表示为null ,并且没有父null
- Platform class loader:平台类加载器可以看到所有平台类 ,平台类包括由平台类加载器或其祖先定义的Java SE平台API,其实现类和JDK特定的运行时类
- System class loader:它也被称为应用程序类加载器 ,与平台类加载器不同。 系统类加载器通常用于定义应用程序类路径,模块路径和JDK特定工具上的类
# 反射
Java反射机制:是指在运行时去获取一个类的变量和方法信息。然后通过获取到的信息来创建对象,调用方法的一种机制。由于这种动态性,可以极大的增强程序的灵活性,程序不用在编译期就完成确定,在运行期仍然可以扩展。
我们要想通过反射去使用一个类,首先我们要获取到该类的字节码文件对象,也就是类型为Class类型的对象 这里我们提供三种方式获取Class类型的对象:
- 使用类的class属性来获取该类对应的Class对象。举例:Student.class将会返回Student类对应的Class对象
- 调用对象的getClass()方法,返回该对象所属类对应的Class对象,该方法是Object类中的方法,所有的Java对象都可以调用该方法
- 使用Class类中的静态方法forName(String className),该方法需要传入字符串参数,该字符串参数的值是某个类的全路径,也就是完整包名的路径
public class ReflectDemo {
public static void main(String[] args) throws ClassNotFoundException {
// 使用类的class属性来获取该类对应的class对象
Class<Student> c1 = Student.class;
System.out.println(c1); // class Reflect.Student
Class<Student> c2 = Student.class;
System.out.println(c1==c2); // true
// 调用对象的getClass方法,返回该类对应的class对象
Student s = new Student();
Class<? extends Student> c3 = s.getClass();
System.out.println(c1==c3); // true
// 调用工Class的类中的静态方法forName
Class<?> c4 = Class.forName("Reflect.Student"); // 通过配置文件加载类
System.out.println(c1==c4); // true
}
}
# 反射的使用
/**
Class类中用于获取构造方法的方法
Constructor<?>[] getConstructors():返回所有公共构造方法对象的数组
Constructor<?>[] getDeclaredConstructors():返回所有构造方法对象的数组
Constructor<T> getConstructor(Class<?>... parameterTypes):返回单个公共构造方法对象
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):返回单个构造方法对象
Constructor类中用于创建对象的方法
T newInstance(Object... initargs):根据指定的构造方法创建对象
*/
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class ReflectDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 调用工Class的类中的静态方法forName
Class<?> c = Class.forName("Reflect.Student");
// Constructor<?>[] cons =c.getConstructors(); // 获取到所有的公共的构造函数
Constructor<?>[] cons =c.getDeclaredConstructors(); // 获取所有的构造函数
for (Constructor con:cons){
System.out.println(con);
}
// 获取公共的单个构造函数,使用getConstructor
Constructor<?> con = c.getConstructor(); // 无参表示,获取无参构造
System.out.println(con);
// 获取公私有的单个构造函数
Constructor<?> con1=c.getDeclaredConstructor(String.class,int.class);
System.out.println(con1);
// 使用反射
//Object s =con1.newInstance("王小",12); // 错误,因为从con1是私有的方法
Object s =con.newInstance();
System.out.println(s); // Student{name='null', age=0, addr='null'}
// 暴力反射,如果反射的函数是私有的,可以通过setAccessible来修改,取消访问检查
con1.setAccessible(true);
Object s1 =con1.newInstance("王立",12);
System.out.println(s1); // Student{name='王立', age=12, addr='null'}
}
# 反射获取成员变量
Class类中用于获取成员变量的方法
- Field[] getFields():返回所有公共成员变量对象的数组
- Field[] getDeclaredFields():返回所有成员变量对象的数组
- Field getField(String name):返回单个公共成员变量对象
- Field getDeclaredField(String name):返回单个成员变量对象
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
public class ReflectDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 调用工Class的类中的静态方法forName
Class<?> c = Class.forName("Reflect.Student");
// 获取公共的成员变量
// Field[] fields = c.getFields(); // 获取公共的成员变量
Field[] fields = c.getDeclaredFields(); // 获取所有的成员变量
for(Field field:fields){
System.out.println(field); // public java.lang.String Reflect.Student.addr
}
// 获取单个公共的成员变量
Field a = c.getField("addr");
System.out.println(a);
// 使用反射
Constructor<?> con = c.getConstructor();
Object obj = con.newInstance();
// obj.a = "重庆";
a.set(obj,"重庆"); // 给obj的addr设置值
// 暴力反射
Field n = c.getDeclaredField("name");
n.setAccessible(true);
n.set(obj,"111");
System.out.println(obj); // Student{name='111', age=0, addr='重庆'}
}
}
# 反射获取成员方法
首先实现Student.java
public class Student {
private String name;
int age;
public String addr;
public Student(){};
private Student(String name){
this.name=name;
}
private Student(String name, int age) {
this.name = name;
this.age = age;
}
public Student(String name, int age, String addr) {
this.name = name;
this.age = age;
this.addr = addr;
}
// 成员方法
private void function(){
System.out.println("function");
}
public void method1(){
System.out.println("method1");
}
public void method2(String s){
System.out.println("method:"+s);
}
public String method3(String s,int i){
return s+", "+i;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", addr='" + addr + '\'' +
'}';
}
}
其次实现反射方法
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 调用工Class的类中的静态方法forName
Class<?> c = Class.forName("Reflect.Student");
// 获取公有成员方法, 包含了有类或接口,继承的所有共有方法
//Method[] methods = c.getMethods();
Method[] methods = c.getDeclaredMethods(); // 获取该类的所有方法,但是不包含父类的
for(Method m:methods){
System.out.println(m);
}
// 获取单个共有的方法
Method m = c.getMethod("method1");
System.out.println(m);
Constructor<?> con =c.getConstructor();
Object obj = con.newInstance();
// 使用invoke调用指定对象的指定函数
m.invoke(obj); // 由于m的函数没有返回值,所以不用接受,同时没有参数
Method m2 = c.getMethod("method2", String.class); // 需要注意method2需要string的参数
m2.invoke(obj,"xxx"); // method:xxx
// 暴力反射函数
Method m4 = c.getDeclaredMethod("function"); // 私有成员方法
m4.setAccessible(true);
m4.invoke(obj);
}
}
# 模块化
Java语言随着这些年的发展已经成为了一门影响深远的编程语言,无数平台,系统都采用Java语言编写。但是,伴随着发展,Java也越来越庞大,逐渐发展成为一门“臃肿” 的语言。而且,无论是运行一个大型的软件系统,还是运行一个小的程序,即使程序只需要使用Java的部分核心功能, JVM也要加载整个JRE环境。 为了给Java“瘦身”,让Java实现轻量化,Java 9正式的推出了模块化系统。Java被拆分为N多个模块,并允许Java程序可以根据需要选择加载程序必须的Java模块,这样就可以让Java以轻量化的方式来运行。
# 模块的基本使用
模块的基本使用步骤
- 创建模块(按照以前的讲解方式创建模块,创建包,创建类,定义方法) 为了体现模块的使用,我们创建2个模块。一个是myOne,一个是myTwo
- 在模块的src目录下新建一个名为module-info.java的描述性文件,该文件专门定义模块名,访问权限,模块依赖等信息 描述性文件中使用模块导出和模块依赖来进行配置并使用
- 模块中所有未导出的包都是模块私有的,他们是不能在模块之外被访问的 在myOne这个模块下的描述性文件中配置模块导出 模块导出格式:exports 包名;
- 一个模块要访问其他的模块,必须明确指定依赖哪些模块,未明确指定依赖的模块不能访问 在myTwo这个模块下的描述性文件中配置模块依赖 模块依赖格式:requires 模块名; 注意:写模块名报错,需要按下Alt+Enter提示,然后选择模块依赖
- 在myTwo这个模块的类中使用依赖模块下的内容
/*
|-myOne
|-src
|-com
|-module-info.java
|-test
|-Student.java
|-myTwo
|-src
|-module-info.java
|-cn
|-Demo
*/
// Student.java
package com.test;
public class Student {
public void study(){
System.out.println("好好学校");
}
}
// Demo.java
import com.test.Student;
public class Demo {
public static void main(String[] args) {
Student s = new Student();
s.study();
}
}
// myOne中的module-info
module myOne{
exports com.test;
}
// myTwo中的module-info
module myTwo{
requires myOne;
}
# 模块服务的使用
服务:从Java 6开始,Java提供了一种服务机制,允许服务提供者和服务使用者之间完成解耦,简单的说,就是服务使用者只面向接口编程,但不清楚服务提供者的实现类。
Java 9的模块化系统则进一步的简化了Java的服务机制。Java 9允许将服务接口定义在一个模块中,并使用uses语句来声明该服务接口,然后针对该服务接口提供不同的服务实现类,这些服务实现类。可以分布在不同的模块中,服务实现模块则使用provides语句为服务接口指定实现类,服务使用者只需要面向接口编程即可。
// com文件下创建service文件,新建MySevice.java, 在新建impl文件夹写Orange.java和Apple.java,去实现接口
//MyService.java
public interface MyService {
void service();
}
// impl/Apple.java
package com.servers.impl;
import com.servers.MyService;
public class Apple implements MyService {
@Override
public void service() {
System.out.println("苹果吃");
}
}
// impl/Orange.java
package com.servers.impl;
import com.servers.MyService;
public class Orange implements MyService {
@Override
public void service() {
System.out.println("橘子吃");
}
}
// myOne/module-info.java
import com.servers.MyService;
import com.servers.impl.Orange;
module myOne{
exports com.test;
exports com.servers; // 导出服务接口所在包
provides MyService with Orange; // 在这里控制,怎么实现这个服务,这里选则橘子
}
// myTwo/module-info.java
import com.servers.MyService;
module myTwo{
requires myOne;
uses MyService;
}
// 在myTwo中的cn/新建Test.java
package cn;
import com.servers.MyService;
import java.util.ServiceLoader;
public class Test {
public static void main(String[] args) {
// 加载服务
ServiceLoader<MyService> myServices = ServiceLoader.load(MyService.class);
// 遍历MyService中服务
for(MyService my:myServices){
my.service();
}
}