基础知识
计算机的组成 :
硬件 : 能看得见, 摸得着的电子元器件.
软件 : 存储在硬件中的数据(可以执行) : 指令+数据的集合.
系统软件 : 操作系统 OS, 让用户能够方便的使用电脑硬件.(没有国产OS)
应用软件 : 为了完成某种功能的软件. 学习java目标就是应用软件.
人机交互 : 人通过OS来使用电脑.
GUI :图形化界面
CLI :命令行, 只有一个控制台(console).
在windows中启动控制台 :
Win+r => cmd 回车(动令)
目录 : 用于管理批量的子文件和子目录.
C:\Users\pauliuyou>_
‘>’称为前导符
C:\Users\pauliuyou这是一个目录, 称为当前目录,也称为工作目录.
dir 目录的意思, 作用是列出当前目录下的内容 : 包括子文件和子目录.
切换盘符 : 举例d; 切换到D盘
Md (make directory) 目录名1 目录名2 在当前目录中创建新子目录
把右面的称为命令行参数, 作用是给命令传递进一步的信息数据.
Md 新目录名1 新目录名2
Cd (change directory) 改变当前工作目录.
Cd 目标目录
2个特殊目录 :
- . 点目录表示当前目录
- … 点点目录表示当前目录的上级目录(父目录)
- \ 称为反斜杠
- / 称为斜杠
- \的作用是用于表示父子关系.
- /也可以, 并且在linux系统中必须使用/来表示目录分隔.
路径(path) : 一条路, 这条路的尽头是一个文件或目录.(也称为地址)
Cd MyWork\JavaSE\day01\code
相对路径 : 总是从当前目录为出发点. .\MyWork\JavaSE\day01\code 就是相对路径, 相对路径的前面”./”可以省略
绝对路径 : 以根目录为开始,为出发点的路径 D:\MyWork\JavaSE\day01\code 就是绝对路径. 特点 : 不会出错
- cd/ 直接回到当前盘的根目录
- cd… 返回上一级目录
- cd…/…返回到上两级目录
- Cd /d +路径 可以任意切换目标目录
- exit 退出命令行
计算机语言发展历程
第一代 : 打孔机 面向硬件开发.
第二代 : 汇编语言 面向CPU开发, 缺点: 不能跨CPU
第三代 : C/C++, 面向OS开发, 优点 : 效率高, 缺点 : 不能跨OS
第四代 : java , 面向VM开发, 优点 : 不受OS影响. 缺点 : 相对汇编稍慢
Java的8个特性
- 简单 : 相对于c/c++
- 面向对象 : 相对于面向过程, 面向过程关注问题解决的步骤, 面向对象关注是具有功能的对象
- 分布式 : 基于网络的多主机协助.
- 健壮 : 强类型(所有数据必须有类型), 异常处理, 垃圾收集(内存中应该被释放的空间没有释放. 把空间标记为可用状态就是清理垃圾) GC.
- 安全 : 所有java程序必须通过类加载器
- 跨平台 : 所有java程序中的指令都是面向VM的. 只要有VM, java程序就能执行
- 性能好 : 是编译型比解释型要快.
- 多线程 : 提升服务器的吞吐量, 最大化利用CPU.
Java两种核心机制
- Java虚拟机(Java Virtal Machine)
- 垃圾收集机制(Garbage Collection)
运行java程序的最低要求是 JRE
JRE = JVM + 核心类库
开发java程序必须要求有JDK
JDK = JRE + 开发工具
常用java开发命令
Javac 编译
Java 运行
开发java程序的步骤
- 创建.java源文件
- 编译.java源文件, 生成.class字节码文件
- 运行.class字节码文件
开发程序具体步骤
-
找到d:\MyWork\JavaSE\day01\code目录, 在code目录中右击新建一个文本文件, 改名为Hello.java
-
右击此文本文件, 使用notePad++打开编辑
注意点 : 编辑文件时, 大小写敏感, 所有标点符号必须使用英文半角的标点符号
- 打开命令行窗口, 切换当前工作目录为刚才的code目录中, 并确保刚才编辑的.java文件就在当前目录下.
D:
Cd mywork\javase\day01\code
-
使用命令javac Hello.java 编译源文件, 编译的结果是生成Hello.class字节码文件
-
仍然还是在当前目录中, 使用命令java Hello 执行程序
注意点 : java Hello后面的内容不要加后缀
解决乱码
-
全选并复制所有内容
-
点击菜单”格式” 选中 “以ANSI格式编码”
-
此时会全乱, 把所有内容删除,重新粘贴刚才复制的内容
-
确保右下角的编码方式为ANSI, 如果是utf8重复刚才的步骤
类 : java程序的基本单位
方法 : java程序的基本功能单位
语句 : java程序的最小执行单位
类 {
方法1 {
语句1;
语句2;
……
}
方法2 {
}
….
}
主类 : 包含主方法的类称为主类
非主类 : 不包含主方法的类称为非主类
一个源文件中可以写多个类, 编译以后的结果是每个类都有自己独立的.class文件
但是一个源文件中只能写一个公共类, 非公共类随便, 主类随便
Java程序的执行过程 :
java 主类
java命令一执行, 就会动态地创建一个JVM出来
主类的作用就是告诉JVM加载哪个类并执行, 加载主类以后, 再找到主类的入口方法, 执行入口方法.
主方法一旦执行完毕, JVM销毁, 程序退出
小结第一个程序 :
- Java源文件以“java”为扩展名。源文件的基本组成部分是类(class),如本类中的HelloWorld类。
- 一个源文件中最多只能有一个public类。其它类的个数不限,如果源文件包含一个public类,则文件名必须按该类名命名。
- Java应用程序的执行入口是main()方法。它有固定的书写格式:public static void main(String[] args) {…}
- Java语言严格区分大小写。
- Java方法由一条条语句构成,每个语句以“;”结束。
- 大括号都是成对出现的,缺一不可。
注释
// 单行注释 : 只对当前行有效
/*
多行注释 : 可以给多行文字进行注释, 有头有尾
再来一行, 多行注释不可以嵌套!!
*/
/**
public 是公共的, 是一个形容词, 用于修饰后面的东西.
class 是类, 是一个关键字, 用于声明一个类. 类是java程序的基本单位.
Hello 是类名, 通常类名首字母大写.
类名后面的{}及其中的内容称为 类体
类 = 类头(类签名) + 类体;
主类 : 包含主方法的类就是主类
被public修饰的类 称为 公共类
一个源文件中必须只能有一个公共类, 公共类的类名必须和文件名一致.
*/
public class Hello{
/**
下面的这个东西称为方法, 也称为函数, 是java程序的一个独立的功能单位.
类中包含方法, 方法必须写在类中.
public 是公共的, static 静态的 都是修饰符
void main(String[] args) 方法签名, main称为方法名.
参数后面的一对{}及其中的内容称为方法体.
方法 = 方法签名 + 方法体
方法必须写在类中, 并且不可以嵌套.
此方法称为主方法, 也称为入口方法, 程序总是从main方法开始执行.
写法固定, 必须背会它!!!
*/
public static void main(String[] args){
// 语句 : java程序的最小执行单位, 并且要求必须以;结尾
// 方法中可以包含语句 , 并且可以包含多条语句.
System.out.println(“这里自由发挥,语句1”);
System.out.println(“语句2”);
System.out.println(“语句3”);
System.out.println(“语句4”);
}
/* 方法在同一个类中不可以重复定义!!
public static void main(String[] args) {
System.out.println(“我也是main”);
}*/
public static void method1() {
System.out.println(“method1()…”);
}
}
// 非公共类 : 没有被public修饰的类
class Hello2 { // 非主类, 不包含主方法
public static void test2() {
System.out.println(“test2()…”);
}
}
//public class Hello3 // Hello3不能被public修饰, 因为公共类的类名必须和文件名一致
class Hello3 {
public static void main(String[] args) {
System.out.println(“Hello3 main()…”);
}
}
注意点 : 对于程序的任何修改都必须要重新保存文件, 并重新编译新的.class文件.
NotePad++快捷操作
- Ctrl + a 全选
- Ctrl + c 复制
- Ctrl + v 粘贴
- Ctrl + x 剪切
- Ctrl + z 撤销
- Ctrl + y 重做
- Ctrl + s 保存
- Shift + tab 把选中的内容整体左移
- Tab 把选中的内容整体右移
- Ctrl + l 删除当前行
- Ctrl + d 快速复制当前
JAVA基本语法
关键字 : 在java程序中有特殊含义和作用的单词. 不要直接使用关键字作为标识符
保留字 : 在c/c++中是关键字, 但是在java中目前还不是, 但是将来有可能会成为关键字.
不要直接使用保留字作为标识符
标识符 : 用于标识某个东西的符号(是一个名字)
通常比如标识类, 变量, 方法….
定义合法标识符规则:(必须遵守)
-
组成由26个字母大小写, 0~9, _和$
-
数字不可以开头, 比如3A这个类名不合法, 但是A3就是合法的
-
不可以直接使用关键字和保留字, 但是可以包含关键字和保留字
-
大小写敏感, 最长65535
-
不可以包含空格
java中的名称命名规范(可以不遵守,但是最好遵守)
包名 : 全部小写
类名 : 首字母必须大写, 后面的单词首字母也大写
MyClassName 驼峰命名法
变量,方法名 : 首字母必须小写, 后面的单词首字母大写
myVarName
**常量名 😗*全部大写,单词之间用_隔开
MY_CONST_NAME唯一允许使用_的地方.
编程风格
缩进 : 使用tab
运算符 : 两加都加上空格 4*3/2+5, 4 * 3 / 2 + 5
{}块是行尾风格
变量
内存中的一块区域, 用于保存数据.
此区域必须要命名, 如果没有名字, 我们没有办法定位它.
此区域必须要被特定的数据类型约束, 数据类型可以决定空间大小,并且决定此空间中的数据可以做什么
变量的两个最重要的要素, 就是 数据类型 和 变量名
变量必须先声明, 后使用.
数据类型
Java是一种静态的强类型语言。对于每一个常量、变量、形参、返回值等类型都是非常严格
数据类型作用 :
-
决定了空间的大小.
-
决定了里面的数据可以做什么.
数据类型分类
基本数据类型
(内存区域中保存的是数据本身) 8种
1.数值型:
整数型:
byte:1字节 -128~127
short:2字节 -32768~32768
int:4字节 -20多亿~20多亿
long:8字节 900万亿亿
浮点型:
float:4字节 -1038~1038
double:8字节 10^308
2.字符型:
char:2字节
3.布尔型:
boolean:1字节(true false)
false 为0 true 为真,但是只能用字面量,不能赋值
引用数据类型
内存区域中保存的是别的数据的地址
按照声明的位置划分:
局部变量:
- 方法体{}中
- 方法签名的()中的形参列表
- 代码块{}中
成员变量:
- 在类中,但是在方法和代码块外面
- 有static修饰的成员变量称为静态变量,所有对象共享
- 没有有static修饰的成员变量称为实例变量,每一个对象独立
public class VariableType {
static int a;//成员变量之一:静态变量
int b;//成员变量之一:实例变量
String info;//成员变量之一:实例变量
Student stu;//成员变量之一:实例变量
static{
//静态代码块,后面讲
int c = 1;//局部变量
}
{
//构造代码块,后面讲
int d = 1;//局部变量
}
//(String[] args) 局部变量
public static void main(String[] args) {
int num = 10;//num局部变量,是基本数据类型的局部变量
String str = "hello";//str是局部变量,是引用型局部变量
Student student = new Student("张三");//student是局部变量,是引用型局部变量
}
//int a, int b局部变量
public static int sum(int a, int b){
return a + b;
}
//(int x, int y)局部变量
public void swap(int x, int y){
int temp = x;//局部变量
x = y;
y = temp;
}
}
各种数据类型的使用:
不同的数据使用不同的数据类型来保存, 取决于数据的上限是多少.
变量 : 内存区域中的数据可以修改的量
常量 : 内存区域中的数据不允许修改的量, 包括 字面量(写出来多少就是多少), 和 被final修饰的量:
200, 5.2, false, true, null, ‘a’, “ccc”
赋值符号左侧必须是变量, 如果是常量不可以!!!
范围大的变量是可以接收范围小的量值, 会发生自动数据类型转换
范围小的变量接收范围大的量值时, 必须强制类型转换, 强制类型转换有风险!!!
整数字面量默认使用的类型是int型.
浮点数字面量默认使用的类型是double型(8字节)
数值型范围大小排序, 从大到小:
double > float > long > int > short > byte
> char
任意非long整数变量作运算, 结果总是int型.
多种不同类型的变量混合运算时, 类型会升级为最大的类型
Char 是基本数据类型, 在内存中占用2个字节, 保存的是某个字符的Unicode码值,
它的范围是0~65535. 所以本质上来讲它也是整数, 可以参与整数和浮点数运算.
Char和short,byte不兼容, 互相赋值时, 必须强制类型转换.
Boolean 只允许两个值, 一个true, 一个是false
进制
计算权值以10为底, 这样的数称为10进制数.
10进制没有10, 逢10进1, 最大是9
X进制, 计算权值是以X为底的n次幂. 逢X进一
19 + 1 = 20
二进制 : 计算权值是以2为底的n次幂, 逢2进1
十六进制 : 计算权值是以16为底的n次幂, 逢16进1
进制转换:
十进制 二进制 十六进制
0 0000 0
1 0001 1
2 0010 2
3 0011 3
4 0100 4
5 0101 5
6 0110 6
7 0111 7
8 1000 8
9 1001 9
10 1010 A
11 1011 B
12 1100 C
13 1101 D
14 1110 E
15 1111 F
每个十六进制数都可以机械地转化为4个比特的二进制数.
每4个位的二进制数可以转换成一个十六进制数.
0110 => 0x6
0101 1101 => 0x5D
2.变量按照数据类型
分为基本数据类型变量和引用数据类型变量
列出8种基本数据类型, 并写出基本数据类型变量和引用数据类型变量的区别.
1) 基本数据类型 : 内存区域中保存数据本身
数值型
整数:byte short int long char
浮点数:float double
布尔型
boolean
2) 引用(Reference)数据类型 : 内存区域中保存其他数据的地址
地址 : 内存中某个字节的编号. 本质上是一个正整数…
引用变量占用的空间取决于JDK位数 : 如果是32位JDK, 它用4字节整数来保存
如果是64位JDK, 它用8字节整数来保存地址.
地址为0的地址是一个无效地址, 称为null
3 .列出变量使用注意事项
-
先声明, 后使用.
-
变量必须要有数据类型和变量名
-
变量中数据只能在其数据范围内变化.
-
变量有其作用范围, 作用范围由声明语句所隶属的一对{}.
-
同一个作用范围内, 变量不能重复声明(declare)
-
变量必须初始化(出生时进行的赋值)才能使用
int a;
System.out.println(a);
4.判断下列的带( )行的对错
int i1 = 20;
short s1 = i1; (F)
char c1 = 97; (T) char c1 = ‘a’;
char c2 = ‘我’ - ‘你’;(T)
char c3 = (char)(c1 - 32); //可以达到小写变大写的效果(T)
float f1 = i1;(T)
long l1 = 234234239933L;(F) 整数字面量默认使用4字节的int来保存的.
f1 = l1 * 20;(T)
double d1 = .342;
d1 = i1 * f1 * l1;(T) // 混合运算时, 结果类型是范围最大的那个类型
l1 = f1 / 10000;(F)
boolean b1 = (boolean)1;(F)
变量的分类 :
- 按照数据类型来分类
a) 基本数据类型 (保存数据本身)
b) 引用数据类型 (保存对象地址)
2) 按照声明语句的位置来分类
局部变量 : 声明在方法中的变量
范围小, 寿命短.
成员变量 : 声明在类中方法外的变量
范围大, 寿命长
进制
十进制 : 只有0~9, 逢10进1, 一个数计算权值时以10为底n次幂
二进制 : 只有0和1, 逢2进1, 权值计算以2为底
十六进制 : 只有0~F, 逢16进1, 权值计算以16为底
计算机底层的所有数据全是二进制补码.
底层区分正负数使用的是一个数的最高位, 如果最高位为0, 说明这个数是正数
如果最高位符号位值为1, 说明这个数是负数.
补码 :
正数的补码就是自身.
0011 1101 -> 这是一个正数
0x3D -> 3*16+13 = 61, 所以0011 1101就是61
负数的补码 :
由它的相反数全部取反+1得到
1110 1001 -> 这是一个负数, 并且它一定是补码, 负多少?
还原它的相反数就可以.
-1 : 1110 1000
取反 : 0001 0111 => 0x17 => 23
所以1110 1001就是-23
运算符
左移 : 左移一位是一个数*2
右移 : 右移一位是一个数/2
与运算(&) : 对应二进制上有0就是0, 只有全是1的结果才是1
或运算(|) : 对应二进制上有1就是1, 只有全是0结果才是0
整数除法: 注意点 : 小数部分直接丢弃, 所以有可能会有精度丢失!!
取模 % 应用场景
-
M % N结果总是在[0~n-1], 可以让一个未知数落在一个已经的范围内.
-
M % N 结果为0, 说明M可以被N整除
M % N 结果不为0, 说明M不能被N整除.
- M % 2 结果为0, 说明这个数是偶数.
M % 2 结果不为0, 说明这个数是奇数
自增 : 自已就可以变化. 是单目运算.
Int n = 5;
n++; 后加加, 先用后加
等效于 n = n + 1;
++n; 前加加, 先加后用
等效于 n = n + 1;
Int a = 8;
Int b = a++; // b:8, a:9
Int x = 11;
Int y = ++x; // x:12, y:12
赋值运算符 :
- 优先级最低, 所以总是最后执行
- 它是从右向左的逻辑, 右面的值没有确定, 不可以向左走.
- 看到赋值操作心里要紧张, 因为它一定会改变变量的值.
- 累操作, 累加, 累减
- +=, -=, *=, /= 不会引发数据类型的变化, 所以是安全的.
比较运算的结果总是布尔值
- == 比较是否相等
- != 比较是否不等
- 以上和!=可用于所有类型的数据. 但是对象之间, 比较的是对象的地址值.
比较大小的操作 :
>, >=, <, <= 只适用于基本数据类型中的数值型
布尔类型不可以比大小, 还有对象不能比大小.
3 < x < 6
3 < x结果是boolean, 再让boolean和6比大小.
应该写成
3 < x && x < 6
&& 短路与 :
只要有假结果一定是假
|| 短路或 :
只要有真结果一定是真
优先级 :
最高的是. 和(), 最低的是 =
逻辑与算符
& 逻辑与
| 逻辑或
! 逻辑非
&& 短路与
|| 短路或
^ 逻辑异或 (追求的是 “异”)
三元运算符
格式:
(条件表达式)? 表达式1:表达式2;
条件表达式 为true,运算后的结果是表达式1;
条件表达式 为false,运算后的结果是表达式2;
表达式1和表达式2为同种类型
三元运算符与if-else的联系与区别:
1)三元运算符可简化if-else语句
2)三元运算符要求必须返回一个结果。
3)if后的代码块可有多个语句
if-else语句
if语句三种格式:
-
if (条件表达式) {
执行代码块;
} -
if (条件表达式) {
执行代码块;
} else {
执行代码块;
} -
if (条件表达式1) {
执行代码块1;
} else if (条件表达式2) {
执行代码块2;
} else if (条件表达式3) {
执行代码块3;
}
…… else {
执行代码块n;
}
switch语句
switch(变量){
case 常量1:
语句1;
break;
case 常量2:
语句2;
break;
… …
case 常量N:
语句N;
break;
default:
语句;
break;
}
switch语句相关规则
1.switch(表达式)中表达式的返回值必须是下述几种类型之一:byte,short,char,int,String, 枚举;
2.case子句中的值必须是常量,且所有case子句中的值应是不同的;
3.default子句是可任选的,当没有匹配的case时,执行default
4.break语句用来在执行完一个case分支后使程序跳出switch语句块;如果没有break,程序会顺序执行到后面第 一个break语句或直接执行到switch结尾(这种现象称为穿透)
2.基本数据类型
8种基本数据类型:
-
数值型
- 整数
byte 1000 0000, 0111 1111 => 0x80, 0x7F
short 1000 0000 0000 0000, 0111 1111 1111 1111 => 0x8000, 0x7FFF
char 0000 0000 0000 0000, 1111 1111 1111 1111 => 0x0000, 0xFFFF
int 1(31个0), 0(31个1) => 0x80000000, 0x7FFFFFFF
long 1(63个0), 0(63个1) => 0x8000000000000000, 0x7FFFFFFFFFFFFFFF
- 浮点数
float
double
-
布尔型
boolean
3.计算下列结果, 分析过程
只需要计算到十六进制形式即可
byte a = 0x6B;
byte b = 0x5D;
// 0110 1011&
// 0101 1101=
// 0100 1001 => 0x49
System.out.println(a & b);
// 0110 1011 |
// 0101 1101 =
// 0111 1111 => 0x7F
System.out.println(a | b);
// 0110 1011 ^
// 0101 1101 =
// 0011 0110 => 0x36
System.out.println(a ^ b);
4.运算符%的作用和实际的应用
%的作用是取余或取模.
1) M % N 结果总是0~N-1, 可以让一个未知数落在一个已知范围内
2) M % N 结果如果为0, 说明M可以被N整除.
3) M % 2 结果如果为0, 说明M是偶数, 如果结果不为0, 说明M是奇数
5.判断:
1)相同字面量十六进制表示的数比十进制要大, 对或错? 为什么?
10以内是相等的. 0x3, 3
其他的情况会大
0x80, 和 80
如果0x80是byte型, 它实际是-128, 80
十六进制本身不承载任何数据类型信息, 只是忠实的反映二进制.
2)if else if else if else 语句中, 如果同时有多个条件都为true, 那么将会有多个语句块被执行,
错, 这是分支, 一定只有一个分支被执行, 不可能执行多个语句块
3)switch case case default 语句中, 如果同时有多个条件都为true, 那么将会有多个语句块被执行
错, case 不允许重复相同.
6.变量的分类法 :
1) 局部变量 : 在方法中声明
范围小, 寿命短
2) 成员变量 : 在类中方法外声明
范围大, 寿命长
循环结构
循环语句功能
在某些条件满足的情况下,反复执行特定代码的功能
循环语句的四个组成部分
初始化部分(init_statement)
循环条件部分(test_exp)
循环体部分(body_statement)
迭代部分(alter_statement)
循环语句分类
while 循环
do/while 循环
for 循环
while循环语句
语法格式
[初始化语句]
while( 布尔值测试表达式){
语句或语句块;
[更改语句;]
}
应用举例
public class WhileLoop {
public static void main(String args[]){
int result = 0;
int i=1;
while(i<=5) {
result += i;
i++;
}
System.out.println("result=" + result);
}
}
do-while循环语句
语法格式
[初始化语句]
do{
语句或语句块;
[更改语句;]
}while(布尔值测试表达式);
应用举例
public class WhileLoop {
public static void main(String args[]){
int result = 0, i=1;
do {
result += i;
i++;
} while(i<=5);
System.out.println("result=" + result);
}
}
注意要点
先执行语句一次,再判断条件
for循环语句
语法格式
for (初始化表达式①; 布尔值测试表达式②⑤⑦; 更改表达式){
语句或语句块③⑥ ;
}
应用举例
public class ForLoop {
public static void main(String args[]){
int result = 0;
for(int i=1; i<=5; i++) {
result += i;
}
System.out.println("result=" + result);
}
}
注意要点
先判断条件,再执行语句
嵌套循环
- 将一个循环放在另一个循环体内,就形成了嵌套循环。其中,for ,while ,do…while均可以作为外层循环和内层循环。
- 实质上,嵌套循环就是把内层循环当成外层循环的循环体。当只有内层循环的循环条件为false时,才会完全跳出内层循环,才可结束外层的当次循环,开始下一次的循环。
- 设外层循环次数为m次,内层为n次,则内层循环体实际上需要执行m*n次。
嵌套循环练习
九九乘法表
public class NineNine{
public static void main(String[] args){
for(int i = 1;i < 10; i++){
for(int j = 1;j < 10; j++){
int k = i * j;
System.out.print(""+ i + "*" + j + "=" + k + " ");
}
System.out.println("");
}
}
}
空心等腰三角形
//扩展 : 打印高度为n的空心的等腰三角形.
class HomeWork41{
public static void main(String[] args){
int n = Integer.parseInt(args[0]);
for(int i = 0;i < n;i++){
for(int j = 0; j < n - i - 1;j++){
System.out.print(" ");
}
for(int j = 0;j < 2*i+1;j++){
//第n行中间不加空格,所以i<n-1;
//j==0 && j==2i时,两个腰边有*,所以在他们之间打出空格的条件如下
if ((i < n-1) && j > 0 && j < 2*i ){
System.out.print(" ");
}else{
System.out.print("*");
}
}
System.out.println();
}
}
}
输入年、月、日,判断这一天是当年的第几天
先不考虑非法值输入
注:判断一年是否是闰年的标准:
1)可以被4整除,但不可被100整除
2)可以被400整除
例如:1900,2200等能被4整除,但同时能被100整除,但不能被400整除,不是闰年
public class TestExer11{
public static void main(String[] args){
int year = Integer.parseInt(args[0]);
int month = Integer.parseInt(args[1]);
int day = Integer.parseInt(args[2]);
//判断这一天是当年的第几天==>从1月1日开始,累加到xx月xx日这一天
//(1)[1,month-1]个月满月天数
//(2)第month个月的day天
//(3)单独考虑2月份是否是29天(依据是看year是否是闰年)
//2、声明一个变量days,用来存储总天数,直接初始化为day,这样就不用再加day天了
int days = day;
//3、累加[1,month-1]个月满月天数
switch(month){
case 12:
//累加的1-11月
days += 30;//这个30是代表11月份的满月天数
//这里没有break,继续往下走
case 11:
//累加的1-10月
days += 31;//这个31是代表10月的满月天数
//这里没有break,继续往下走
case 10:
days += 30;//9月
case 9:
days += 31;//8月
case 8:
days += 31;//7月
case 7:
days += 30;//6月
case 6:
days += 31;//5月
case 5:
days += 30;//4月
case 4:
days += 31;//3月
case 3:
days += 28;//2月
//4、在这里考虑是否可能是29天
if(year%4==0 && year%100!=0 || year%400==0){
days++;//多加1天
}
case 2:
days += 31;//1月
}
//5、输出结果
System.out.println(year + "年" + month + "月" + day + "日是这一年的第" + days + "天");
}
}
特殊流程控制语句
break 语句
break语句用于终止某个语句块的执行
{ ……
break;
……
}
break语句出现在多层嵌套的语句块中时,可以通过标签指明要终止的是哪一层语句块
class BreakTest2 {
public static void main(String[] args) {
// 循环可以加标签, 标签的命名: 只要是合法标识符就可以
l1 : for (int i = 0; i < 10; i++) {
l2 : for (int j = 0; j < 5; j++) {
System.out.println(" j : " + j);
if (j == 2) {
//break; // 默认中断离我最近的循环.
break l1; // 中断指定标签对应的循环.输出0 1 2以后,循环终止
}
}
System.out.println("i : " + i);
}
}
continue 语句
- continue语句用于跳过某个循环语句块的一次执行
- continue语句出现在多层嵌套的循环语句体中时,可以通过标签指明要跳过的是哪一层循环
continue 语句用法
public class ContinueTest {
public static void main(String[] args) {
// continue : 继续, 跳过循环的某次执行, 直接进入下一次执行. 对于循环的破坏力度不大
// 只能用于循环
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
continue; // continue暗含的逻辑, 应该向下走, 但是出现小意外, 导致得跳过.
}
System.out.println("i : " + i);
}
}
}
return 语句
- return:并非专门用于结束循环的,它的功能是结束一个方法。当一个方法执行到一个return语句时,这个方法将被结束。
- 与break和continue不同的是,return直接结束整个方法,不管这个return处于多少层循环之内
特殊流程控制语句说明
- break只能用于switch语句和循环语句中
- continue 只能用于循环语句中
- 二者功能类似,但continue是终止本次循环,break是终止本层循环
- break、continue之后不能有其他的语句,因为程序永远不会执行其后的语句(有就会报错)
- 标号语句必须紧接在循环的头部
方法(method)
方法(函数)的制造环节
方法(method) :
就是函数, 是java程序中的某个独立的功能的体现.
方法必须声明在类中, 并且方法不可以嵌套, 只能并列存在
方法的声明 :
修饰符 返回值类型 方法名(数据类型1 形参1, 数据类型2 形参2, 数据类型3 形参3....) {
语句构成的方法体;
return 返回值;
}
方法 = 方法签名(不执行, 虽然不执行,但是重要, 因为它相当于方法的使用说明书API) + 方法体(执行);
形参 : 形式参数, 在方法声明时的数据, 但是此数据在声明时是没有值的, 没有参数不行, 同时参数的值是多少也没有影响.
形参就是变量, 是某个方法的局部变量.
返回值 : 方法的结果. 如果返回值类型是void, 表示方法没有返回值.
方法的使用环节 :
调用方法 : 方法名(实参列表); // 实参列表必须和形参列表匹配!!!
返回值 : 方法调用本身就是返回值. 返回值的接收只有一次机会.
最简单的方法 : 无返回值, 无参数, 即使是无参, 也不能省略().
无参方法在被调用时, 也不能省略()
方法调用注意点 :
- 没有具体返回值的情况,返回值类型用关键字void表示,那么该函数中的return语句如果在最后一行可以省略 不写。
- 定义方法时,方法的结果应该返回给调用者,交由调用者处理。
- 方法中只能调用方法,不可以在方法内部定义方法。
- 方法的返回值只有一次机会接收, 就是在调用时
- 如果在方法中又调用了方法本身, 称为递归调用
方法代码练习实例:
public class MethodTest {
public static void test() {
System.out.println("test()....");
//return; // 如果方法的返回值为void, 并且在最后有return, 可以省略这个return
}
// 完成一个功能, 两个整数求和. 一个方法可以被多次调用, 并且每次的参数可以不同.
// 方法的每次调用都是一个独立的执行, 互不相干!!!
public static int add(int a, int b) {
System.out.println("add (int a, int b)...");
int c = a + b;
return c; // 返回的是c中的值.
}
public static void main(String[] args) {
System.out.println("main begin");
int x = add(10, 20); // 10称为实参, 20也是实参, 在调用时, 会把10给方法的a, 20给方法的b
//System.out.println(c);
System.out.println(x);
System.out.println(add(50, 80)); // add先执行, println后执行
// 如果方法没有返回值, 禁止接收!!!!
//int y = test(); // 无参方法的调用
test(); // ()如果跟在一个东西后面, 99%可能性是方法调用!! ()是方法的标志.{}是块的标志, []是数组的标志
// 没有返回值的方法, 禁止打印!!!!!
//System.out.println(test());
System.out.println("main end");
}
}
方法的重载
重载(Overload) :
同一个类中, 方法可以有多个重名的, 只要参数不同即可, 这样的一系列方法形成重载.
参数不同 体现为 :
参数的个数不同, 参数的类型不同, 参数的顺序不同.
重载和方法的返回值没有关系 .
如何区分是调用哪个方法, 由实参类型来决定
实参和形参之间是有匹配度的. 优先完全匹配, 再兼容匹配.
方法重载的目的 :
功能类似的参数不同的方法, 用同一个方法名来描述, 对于调用者来说简单, 只需要记一个名字(方法的提供者麻烦)
重载代码练习实例:
public class OverLoadTest {
public static int add(int a, int b) {
System.out.println("add(int a, int b)...");
int c = a + b;
return c;
}
public static int add(int a, int b, int c, int d) {
System.out.println("add(int a, int b, int c, int d)");
int e = a + b + c + d;
return e;
}
public static double add(double a, int b) {
System.out.println("add(double a, int b)");
double c = a + b;
return c;
}
/* 此方法和上面的方法是冲突, 不是重载!!!!
public static double add(double b, int a) {
System.out.println("add(double a, int b)");
double c = b + a;
return c;
}*/
public static double add(int a, double b) {
System.out.println("add(int a, double b)");
double c = a + b;
return c;
}
public static byte add(byte a, byte b) {
System.out.println("add(byte a, byte b)...");
byte c = (byte)(a + b);
return c;
}
public static void main(String[] args) {
System.out.println(add((byte)10, (byte)30));
System.out.println(add(10.2, 30));
System.out.println(add(5, 8));
System.out.println(100); // int
System.out.println("abc"); // String
System.out.println(3.22); // double
System.out.println('A'); // char
System.out.println(true); // boolean
}
}
跨类调用静态方法, 必须使用类.方法
int c = MethodTest.add(a, b); // .表示隶属关系.
System.out.println©;
递归调用
方法调用时,调用方法本身,称为递归调用
递归调用代码实例练习:
public class SimpleTest {
/**
求n的阶乘
求 n * n-1的阶乘, 递归 : 把问题分解成一个小问题和一个子问题.
递归是告诉计算机做什么, 而普通方法则是告诉计算机怎么做.
*/
public static int test(int n) {
//test(n); // 方法调用自身, 这样的调用称为递归调用, 这样的递归会形成无限递归, 死归
if (n <= 1) {
return 1;
}
return n * test(n - 1);
}
public static void main(String[] args) {
System.out.println(test(5));
}
}
家庭记账软件项目
public class FamilyAccount {
public static void main(String[] args) {
// 声明变量保存基本金, 初值是10000
int balance = 10000;
// 声明变量保存记账本, 初值是明细表的表头
String details = "收支\t账户金额\t收支金额\t说 明\n";
// 声明一个用于控制循环的布尔变量, 初值是true
boolean loopFlag = true;
// 写一个循环
do {
// 1) 打印主菜单
System.out.println("-----------------家庭收支记账软件-----------------");
System.out.println("");
System.out.println(" 1 收支明细");
System.out.println(" 2 登记收入");
System.out.println(" 3 登记支出");
System.out.println(" 4 退 出");
System.out.println("");
System.out.print(" 请选择(1-4) : ");
// 2) 通过调用工具类的方法Utility.readMenuSelection(), 获取用户输入的'1'~'4',
// 此方法会返回一个char型的值, 声明变量接收这个char型值.
char ch = Utility.readMenuSelection(); // 它会一直等待用户从键盘输入数字
// 3) 对用户的输入作分支
switch (ch) {
// 4) 如果是'1', 打印记账本字符串
case '1' :
System.out.println(details);
break;
// 5) 如果是'2', 打印"处理收入"
case '2' :
//打印提醒 : 本次收入金额 :
System.out.print("本次收入金额 : ");
//真的调用工具方法获取用户从键盘的实际的输入的整数, 并用变量接收.
int money1 = Utility.readNumber();
//打印提醒 : 本次收入说明 :
System.out.print("本次收入说明 : ");
//真的调用工具方法获取用户从键盘的实际的输入的说明字符串, 并用变量接收.
String string1 = Utility.readString();
// 调整余额, 累加本次收入的金额
balance += money1;
// 拼接本次收入明细
//String info1 = "收入" + 调整后的余额 + 本次收入的金额 + 本次收入说明字符串 + 换行
String info1 = "收入" + "\t" + balance + "\t\t" + money1 + "\t\t" + string1 + "\n";
// 最后把此次收入明细串, 拼接到details的后面
details += info1;
break;
// 6) 如果是'3', 打印"处理支出"
case '3' :
//打印提醒 : 本次支出金额 :
System.out.print("本次支出金额 : ");
//真的调用工具方法获取用户从键盘的实际的输入的整数, 并用变量接收.
int money2 = Utility.readNumber();
//打印提醒 : 本次支出说明 :
System.out.print("本次支出说明 : ");
//真的调用工具方法获取用户从键盘的实际的输入的说明字符串, 并用变量接收.
String string2 = Utility.readString();
// 调整余额, 把支出的金额从余额中累减
balance -= money2;
// 拼接本次支出明细
//String info2 = "支出" + 调整后的余额 + 本次支出的金额 + 本次支出说明字符串 + 换行
String info2 = "支出" + "\t" + balance + "\t\t" + money2 + "\t\t" + string2 + "\n";
// 最后把此次收入明细串, 拼接到details的后面
details += info2;
break;
// 7) 如果是'4', 把控制循环的布尔置为false
case '4' :
System.out.print("确认是否退出(Y/N) : ");
char confirm = Utility.readConfirmSelection();
if (confirm == 'Y') {
loopFlag = false;
}
break;
}
} while (loopFlag);
}
}
面向对象编程
类与对象
类:一类具有相同特性的事物的抽象描述,例如:Student,Circle等
对象:这类事物的一个具体的个体,例如:其中一个学生对象
类是模板,设计图,理解为汽车设计图
对象是实体,理解为实际存在的汽车
面向对象的三大特征:
- 封装
- 继承
- 多态
面向对象 : 关注的是具有功能的对象
面向对象3条主线:
- 类及类的成员的学习
- 三大特性(封装, 继承, 多态)
- 其他关键字
面向对象编程中 : 目标是使用对象来完成功能, 如果对象没有现成的, 制造一个对象来使用.
面向对象程序重点 : 类的设计
类 : 描述复杂的现实世界某种事物, 是一个抽象的,是概念上的.
类就是和普通数据类型一样的数据类型, 只不过是一种复合类型. 类类型或称为引用类型
对象 : 类的某个具体的实体, 也称为实例.
重点 : 类的研究和设计. 类的成员 : 属性, 方法, 构造器
设计类 :
考虑事物的数据部分, 成员(member)变量(属性)来描述.
考虑事物的行为动作, 成员(member)方法来描述.
面向对象代码练习实例
创建类及类的成员
public class Teacher {
// 特征 : 姓名, 年龄, 性别, 身高, 体重, 工资 .....
String name; // 对象属性, 隶属于某个对象, 也称为实例变量.
int age;
String gender;
// 行为 : 吃饭, 上课, 睡觉, 玩, 看电影, 做饭 ......
// 方法 : 修饰符, 返回值类型, 方法名(参数列表) {方法体}
// 对象方法, 隶属于某个对象. 注意点 : 方法千万不要加static修饰符!!!!!
public void eat(String some) { // 吃什么取决于调用者传的参
System.out.println("老师在吃" + some);
}
public void lesson() {
System.out.println("老师在上课...");
}
// 自我介绍, 用一个字符串描述对象的详细信息, 详细信息就是对象的所有属性值拼接
public String say() {
String str = "姓名 : " + name + ", 年龄 : " + age + ", 性别 : " + gender;
return str;
}
}
创建类的对象
public class TeacherTest {
public static void main(String[] args) {
// 类已经就绪 , 模板就位, 使用模板制造对象
Teacher t = new Teacher(); // 左侧的Teacher是对象的类型.
/*
int n = 200;
n = 30;
System.out.println(n);
*/
t.name = "佟刚";
t.age = 40;
t.gender = "男";
System.out.println(t.name);
System.out.println(t.age);
System.out.println(t.gender);
t.lesson();
t.eat("面包");
String s = t.say(); // 接收返回值
System.out.println(s);
}
}
成员变量
(1)静态变量与实例变量的选择
- 如果这个特征值是整个类的所有对象共享的,那么就声明为静态的;
- 一个对象设置和修改了静态变量,这个类的所有对象都会受到影响
- 所以,建议通过类来修改和访问,而不是通过对象
- 如果这个特征值是每一个对象独立的,那么就声明为非静态的;
- 一个对象设置和修改了和其他对象无关
(2)成员变量与局部变量的异同
相同点:
1、变量的三要素
数据类型、变量名、变量值
2、变量的使用要求
无论是成员变量还是局部变量都要先声明后使用,即我们只能使用声明过的变量,否则编译会报错
不同点:
1、作用域与可见性范围:
-
局部变量有作用域:超出作用域就不能使用
-
成员变量有可见性范围:只要可见就可以通过相应的方式直接操作
-
在本类中,直接访问,但是静态的方法和代码块不能直接访问本类非静态的成员变量
-
在其他类中:静态的建议通过**“类名.”**进行访问,虽然也可以通过“对象名."进行访问
非静态的通过**“对象名."**进行访问
-
2、重名问题
- 同一个作用域中局部变量不能重复声明
- 同一个类中不能成员变量不能重复声明
- 当局部变量与成员变量重名时,要注意区别
- 静态变量:类名.静态变量
- 实例变量:this.实例变量
3、初始化要求
- 局部变量必须手动初始化
- 方法体和代码块中必须有“变量 = 值"的语句,才能使用这个变量
- 方法的形参必须在调用时传入实参为其赋值
- 成员变量
- 如果没有显式初始化,它有默认值
- 也可以进行显式赋值
4、内存位置
- 局部变量:方法调用时在方法对应的栈中
- 成员变量:
- 静态变量:在类对应的方法区中
- 实例变量:在每一个对象的堆中
5、生命周期
- 局部变量:只有该方法或代码块正在执行时,才会存在,执行完立刻消失
- 成员变量:
- 静态变量:只要这个类还在,就一直可以访问,即静态变量与类共存亡
- 实例变量:只要这个对象还在,就可以访问到,即实例变量与对象共存亡,每一个对象的实例变量是独立的
6、修饰符
- 局部变量:没有修饰符,除了final,如果加了final就表示值不能修改,就是常量了
- 成员变量:可以有很多修饰符:public, protected, private ,static, final, transient, volatile等
成员方法
(1)什么是方法
方法(method),又称为函数(function),是指代表一段独立的可复用的功能。
换句话说,我们定义方法/抽取方法的目的是使得某个功能可以被重复使用。
例如:
System.out.println(xx); 这里调用了out对象的println方法,用来输出xx并换行。
Math.sqrt(x):这里调用了Math类的sqrt方法,用来获取x的平方根。
Scanner对象的nextInt():调用Scanner对象的 nextInt(),用来从键盘接收一个整数值
(2)方法的特点和要求
- 方法是必须先声明后使用
- 方法不调用不执行,调用一次执行一次
(3)声明格式
必须在类中,方法外声明
【修饰符】 返回值类型 方法名(【形参列表】){
方法体
}
关于【修饰符】有很多:public, protected, private,static,abstract,final,native,synchronized等等,一个个慢慢学。
(4)分类
- 静态方法:有static修饰的
- 本类中直接调用
- 其他类中,静态的建议通过**“类名.”**进行访问,虽然也可以通过“对象名."进行访问
- 非静态方法:没有static修饰的
- 本类中,只能在本类的非静态方法和代码块中直接调用,同样静态的方法和代码块不能直接访问本类非静态的成员方法
- 其他类中,非静态方法通过**“对象名."**进行访问
(5)参数传递机制:形参与实参
- 方法的参数:方法名后面()里面的东西,就是方法的参数
- 为什么有方法的参数?方法的参数是做什么用的?
方法是代表一个独立的可复用的功能。
那么我们的方法体,就是实现功能的代码。
当我们在实现这个功能的时候,需要一些额外的数据。
例如:设计一个求两个整数和的功能,那么我们就需要两个整数
如何得到这两个整数?
A:在方法体里面从键盘输入
B:让调用者传入这两个整数,至于这两个整数从哪里来,由调用者自己决定
结论:方法的参数是用来接收数据的,调用者给它的数据,用于辅助完成方法的功能 - 参数分为形参和实参
形参:方法声明的()中的是形参,因为在被调用之前,他没有值,只是个形式
int sum3(int a, int b) 没有调用之前,(int a, int b) 是没有值的,是个形式,但是要说明数据类型,
准备接受什么值。
- 实参:方法调用的()中的是实参,因为在调用时,它就有具体的值了。
sum3(1,2) 其中1,2是实参
sum3(i,j) 其中i,j是实参
d.sum3(d.sum3(i,j) ,k); 对于d.sum3(i,j)部分,i和j是实参,
d.sum3(d.sum3(i,j) ,k)部分,第一次调用d.sum3(i,j)的结果是实参之一,k是实参之二 - 实参:给形参赋值的
(6)返回值与return
看方法完成的功能,是否需要调用接收结果。
如果不需要返回结果,那么返回值类型处就写void,在方法体中,如果要提前结束方法,可以使用return;
如果需要返回结果,那么返回值类型处就不能写void,写具体的类型,在方法体中,必须有return 返回值;
(7)方法的参数传递机制
方法的参数传递机制
即实参给形参传了什么值?互相之间的关系是什么样?
(1)实参给形参赋值
(2)实参给形参什么值?
基本数据类型,数据值
引用数据类型,地址值
(3)实参是否会被形参影响
基本数据类型,形参无论怎么修改和实参无关,因为是实参是复制了一份数据给形参。
引用数据类型,形参对象修改了属性/形参数组对象修改了元素,实参会跟着变,因为实参把对象的地址值给形参了,
那么形参和实参就是指向同一个对象。
陷阱:
当形参指向了新对象之后,接下来的操作就和实参无关了
(8)重载
定义:在一个类中出现了,两个或更多个,方法名称相同,形参列表不同的方法就是重载。和返回值类型、权限修饰符无关。
(9)main方法的命令行参数
public class TestCommandParam {
//main方法的(String[] args)形参怎么传实参
//java 类名 参数值1 参数值2 参数值3
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}
}
}
(10)方法调用的入栈与出栈
每一次方法调用都会在栈中开辟一块独立的空间,用来存储这个方法的局部变量等信息,所以每一次局部变量的值都是全新的。称为“入栈”
每一次方法调用结束之后,都会释放栈空间。称为“出栈”
构造器
构造器也称为构造方法,
本质上是一个方法. 作用 : 在对象创建时进行初始化工作的
构造器的特殊性 :
1) 构造方法名和类名一致, 这是唯一允许使用首字母大写的方法名的方法
2) 它不声明返回值类型, 甚至连void也没有
3) 它不可以被一些关键字修饰, static ,final, abstract…
4) 它不能像普通方法一样随意调用. 只能在创建对象时执行仅有的一次
如果我们在类中并未提供任何构造器, 编译器会自动添加一个缺省构造器 : 无参, 修饰符和类一致, 方法中没有语句.
如果我们在类中提供了构造器, 编译器就不会再自动添加一个缺省构造器了
结论 : 所有类都必须有构造器
构造器重载 : 多个构造器只要参数不同就可以重载
构造器的要求
- 每一个类都有构造器
- 如果一个类没有手动编写构造器,编译器就会自动生成一个无参构造器
- 如果一个类手动编写了构造器,那么编译器就不会自动给你加无参构造器了,如果需要可以自己写
- 构造器的名称必须和类名相同
- 构造器没有返回值类型(包括void)
- 构造器可以重载
构造器的作用和调用
- 构造器的作用是在创建对象时为成员变量初始化用的。
- 我们创建对象时,new后面就是构造器。创建哪个类的对象,就调用哪个类的构造器。
new 类名() 表示调用无参构造创建对象
new 类名(实参) 表示调用有参构造创建对象
示例代码
1、没有编写构造器
示例代码:
class ClassA{
}
public class TestConstructor {
public static void main(String[] args) {
ClassA a = new ClassA();
//调用无参构造,这个无参构造是编译器自动增加的
}
}
2、只编写了有参构造
示例代码:
class ClassB{
private int b;
public ClassB(int b) {
this.b = b;
}
}
public class TestConstructor {
public static void main(String[] args) {
// ClassB b1 = new ClassB();
//报错,编译器不会自动增加无参构造,因为你自己写了有参构造
ClassB b2 = new ClassB(5);//调用有参构造创建对象
}
}
3、既有有参构造又有无参构造
示例代码:
class ClassC{
private int num;
public ClassC(int num) {
this.num = num;
}
public ClassC() {
}
}
public class TestConstructor {
public static void main(String[] args) {
ClassC c1 = new ClassC();//调用无参构造
ClassC c2 = new ClassC(6);//调用有参构造
}
}
4、构造器如果写返回值类型,就不是构造器了
class ClassD{
//不是构造器
public void ClassD(){
System.out.println("普通方法");
}
}
public class TestConstructor {
public static void main(String[] args) {
ClassD d1 = new ClassD();
//调用无参构造
//调用编译器自动生成的无参构造
d1.ClassD();//调用普通方法
}
}
构造器的权限修饰符
(1)当一个类没有编写构造器,编译器会自动产生一个无参构造。这个无参构造的权限修饰符和类一致。
下面的两个类:
- TestConstructor2的无参构造,权限修饰符是public的。
- Demo的无参构造,权限修饰符是缺省的。
public class TestConstructor2 {
}
class Demo{
}
(2)怎么定它的权限修饰符是什么?
看你希望它在哪个范围内能够创建对象
- 如果你只希望这个类在本类中创建对象,即类的外面不能创建对象,那么就是private,例如:单例,枚举等类型
- 如果你只希望这个类在本包中创建对象,出了这个包就不能随意创建对象,那么就是缺省的
- …
如何选择有参构造和无参构造
- 无参构造,创建对象简单,直接new 类名()就可以。特别是后面通过反射或框架帮我们创建对象,那么无参构造都是最最有用的。而且在子类继承时,无参构造也很有用。如果没有极特殊的情况,一个类都要保留它的无参构造。
- 有参构造,如果你在new对象时,就能够为成员变量提供初始值,那么选择有参构造呢,会更方便。
- 你声明的构造器的形式越多,那么代表自己或其他人来创建这个类的对象的方式就越多,就更灵活方便。
对象关联
对象关联 : 一个类的对象关联另外一个类的对象, 一个对象拥有另外一个对象
为什么要关联 : 在当前类中要频繁地使用另外一个类的对象
如何关联 : 在当前类中把另外一个类的对象作为我的属性即可.
属性该如何处理 :
-
- 修改全参构造
-
- 提供get/set方法
-
- 修改say方法
一旦作为属性, 就是成员, 成员意味着大家随便用!!!
代码实例练习(一):
public class Teacher {
private String name;
private int age;
private String gender;
private Computer myComputer; // 对象关联, 被关联的对象
public Teacher() {}
public Teacher(String name, int age, String gender, Computer myComputer) {
this.name = name;
this.age = age;
this.gender = gender;
this.myComputer = myComputer;
}
public void setMyComputer(Computer myComputer) {
this.myComputer = myComputer;
}
public Computer getMyComputer() {
return myComputer;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getGender() {
return gender;
}
public String say() {
return "姓名 : " + name + ", 年龄 : " + age + ", 性别 : " + gender + ", 我的电脑 : " + myComputer.say();
}
//public void lesson(Computer com) {
public void lesson() {
System.out.println(name + "老师在使用电脑[" + myComputer.say() + "]上课");
}
public void film() {
System.out.println("老师在使用电脑[" + myComputer.say() + "]在看电影");
}
}
对象双向关联实例一
我们让一个Boy对象关联一个Girl对象,同时,让这个Girl对象也关联这个Boy对象,有点像“你中有我,我中有你”的关系。
public class Boy {
private String name;
private Girl girlFriend;//Boy对象关联了一个Girl的对象
public Boy(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Girl getGirlFriend() {
return girlFriend;
}
public void setGirlFriend(Girl girlFriend) {
this.girlFriend = girlFriend;
}
}
public class Girl {
private String name;
private Boy boyFriend;//Girl的对象关联了一个Boy对象
public Girl(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Boy getBoyFriend() {
return boyFriend;
}
public void setBoyFriend(Boy boyFriend) {
this.boyFriend = boyFriend;
}
}
public class TestBoyAndGirl {
public static void main(String[] args) {
//new了两个对象,各自现在是独立的
Boy boy = new Boy("张三");
Girl girl = new Girl("翠花");
//boy对象和girl对象没有任何关联
//为boy和girl建立关联关系
boy.setGirlFriend(girl);
girl.setBoyFriend(boy);
//通过boy对象访问boy的信息,以及和他关联的girl信息
System.out.println("男:" + boy.getName() + ",他女友:" + boy.getGirlFriend().getName());
//同样,如果你需要,也可以通过gril对象访问girl的信息,以及与她关联的boy信息
System.out.println("女:" + girl.getName() + ",她男友:" + girl.getBoyFriend().getName());
}
}
public class TestBoyAndGirls {
public static void main(String[] args) {
Boy[] boys = new Boy[2];
boys[0] = new Boy("张三");
boys[1] = new Boy("李四");
Girl[] girls = new Girl[2];
girls[0] = new Girl("翠花");
girls[1] = new Girl("如花");
for (int i = 0; i < boys.length; i++) {
boys[i].setGirlFriend(girls[i]);
girls[i].setBoyFriend(boys[i]);
}
//遍历boy数组,可以获取boy们的信息,也可以获取他们女友的信息
for (int i = 0; i < boys.length; i++) {
System.out.println("男:" + boys[i].getName() + ",他女友:" + boys[i].getGirlFriend().getName());
}
//同理,遍历girls数组,可以获取girl们的信息,也可以获取他们男友的信息
for (int i = 0; i < girls.length; i++) {
System.out.println("女:" + girls[i].getName() + ",她男友:" + girls[i].getBoyFriend().getName());
}
}
}
匿名对象
我们也可以不定义对象的句柄,而直接调用这个对象的方法。这样的对象叫做匿名对象。
如:new Person().shout();
特征一:封装和隐藏
Java中通过将数据声明为私有的(private),再提供公共的(public)方法:getXxx()和setXxx()实现对该属性的操作,以实现下述目的:
- 隐藏一个类中不需要对外提供的实现细节;
- 使用者只能通过事先定制好的方法来访问数据,可以方便地加入控制逻辑,限制对属性的不合理操作;
- 便于修改,增强代码的可维护性;
封装代码实例练习
class Teacher2 {
// 成员变量, 也可以加上修饰, 可以用private
// 封装 : 成员私有化, 保护类中的数据
private String name = "某老师"; // private修饰的成员, 只能被同类的其他成员访问, 不能被其他类直接访问
private int age = 20;
private String gender;
// 间接设置属性的途径 : 通过set方法, 无返回有参数
// 为了提升形参名称的可读性, 使用全称
public void setName(String name) { // 给属性赋值的间接方法, setName("芳芳")
// this表示对象. 特指当前对象
this.name = name; // 属性被形参赋值, 变量的访问有就近原则!!! 如果不加this关键字限定, 就不会给对象属性赋值
}
// 间接获取属性的途径 : 通过get方法, 有返回无参数
public String getName() { // getter
return this.name;
}
public void setAge(int age) {
if (age < 0 || age > 120) { // 防止非法数据入侵
return; // 方法提前弹栈
}
this.age = age;
}
public int getAge() {
return age;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getGender() {
return gender;
}
// 成员方法
public void lesson() {
System.out.println(name + "老师在上课");
}
public void eat(String some) {
System.out.println(name + "老师在吃" + some);
}
public String say() {
return "姓名 : " + this.name + ", 年龄 : " + this.age + ", 性别 : " + gender;
}
}
set方法:
无返回值,有参数 (setName) Name是成员变量
public void setName(String name){
return; (无返回值 有没有return 都行)
}
get方法:
有返回值,无参数 (getName) Name是成员变量
public String getName(){
return name;
}
get set方法测试类中使用
public class MyDateTest {
public static void main(String[] args) {
MyDate d1 = new MyDate();
System.out.println("生日 : " + d1.say());
MyDate d2 = new MyDate();
d2.setYear(2020);
d2.setMonth(22);
d2.setDay(88);
System.out.println(d2.getYear());
System.out.println(d2.getMonth());
System.out.println(d2.getDay());
System.out.println("今天 : " + d2.say());
}
}
this关键字
表示当前对象,set方法的形参名故意和属性名冲突, 然后用this限定再解决这个冲突
this用在哪里
this只能出现在非静态代码块、非静态方法、构造器中
如果出现在非静态代码块和构造器中,表示正在new的对象;
如果出现在非静态方法中,表示调用该方法的对象;
使用形式
this.成员变量
在非静态代码块、非静态方法、构造器中出现成员变量与局部变量同名时,可以用this.成员变量来进行区分。
this.成员方法
在非静态方法中,如果要调用本类的方法,也可以使用this.成员方法的形式,当然省略“this."也是一样的。
this()或this(实参列表)
在构造器的首行,如果要表示调用本类的其他构造器时,可以使用this()或this(实参列表)。
this()表示调用本类无参构造;
this(实参列表)表示调用本类有参构造;
使用this()注意点 :
1) 一定要有一个构造器中是没有this()
2) this() 必须位于首行, 保证将来父类构造方法一定是要先执行
JavaBean
JavaBean是一种Java语言写成的可重用组件
JavaBean是指符合如下标准的Java类:
- 类是公共的
- 有公共无参构造器
- 有属性,有get/set方法
JavaBean 它的对象可以一次性携带批量数据.
package
package 包名.子包名.子子包名.子子子包名;
作用 : 把当前源文件中的所有类生成的.class文件放入指定的包目录结构中
package 必须位于源文件中的第一行
惯例包名有四层 :
机构类型.机构名称.项目名称.模块名称;
举例:
package com.atguigu.javase.javabean;
一旦使用了package, 有以下麻烦 :
1) 编译时变麻烦 编译时必须使用 javac -d 目标目录 源文件名列表.
实际编译时通常 javac 空格 -d 空格 . 空格 源文件名列表
2) 如果在别的不同包中使用当前类时, 必须使用当前类的全限定名称(fully qulified name) : 包名.子包名. 子子包名.子子子包名.类名;
com.atguigu.javase.javabean.Teacher
import
import 导入, 导入类的全限定名称, 作用就是可以导入类
一旦把某个其他包的类导入 , 这个类在本类中的使用就可以简化为使用简单类名. 如果坚持想要用全限定, 仍然没有问题.
import 语句必须位于package下面, 类外面.
import com.atguigu.javase.javabean.*; *表示任意, 这种写法不推荐!!!
数组
数组定义 :
- 一组相同类型的数据的集合, 便于统一处理(用循环处理
- 任意数据类型都可以创建数组, 包括基本数据类型和引用数据类型
- 数组属引用类型,数组型数据是对象(object),数组中的每个元素相当于该对象的成员变量
动态初始化:
数组声明且为数组元素分配空间与赋值的操作分开进行
public class ArrayTest {
public static void main(String[] args) {
int[] arr; // arr是数组名, 本质是一个引用变量, 保存地址.
arr = new int[5]; // 动态初始化,创建了拥有5个整数元素的数组对象
arr[0] = 2; // 相当于是首地址 + 0偏移, 定位的是第一个格子
arr[1] = 4;
arr[arr[0]] = 6;
arr[arr[1]] = arr[0] + arr[2];
System.out.println(arr[0]); // 访问下标为0的元素
System.out.println(arr[1]);
System.out.println(arr[2]);
System.out.println(arr[3]);
System.out.println(arr[4]);
// 通常为的写法是声明和创建可以合体
int[] arr2 = new int[4];
}
}
数组静态初始化:
在定义数组的同时就为数组元素分配空间并赋值
class ArrayTest5 {
public static void main(String[] args) {
// 静态初始化1 : 声明和初始化可以分开
int[] arr1 = new int[]{3, 2, 1, 5, 9, 7}; // 创建和初始化同步完成, []中不能加长度, 编译器会自己数多少个
for (int i = 0; i < arr1.length; i++) {
System.out.print(arr1[i] + " ");
}
System.out.println();
// 静态初始化2 : 声明和初始化不能分开
int[] arr2 = {7, 2, 10, 30}; // 写法 : 仅适用于声明及创建在同一个语句上.
for (int i = 0; i < arr2.length; i++) {
System.out.print(arr2[i] + " ");
}
System.out.println();
int[] arr3;
//arr3 = {3, 2, 9, 7};
arr3 = new int[]{3, 2, 9, 7};
}
}
数组声明 :
元素的数据类型[] 数组名; // 最好用这种写法
元素的数据类型 数组名[];
int[] arr;
char[] arr2;
数组创建 :
new 元素数据类型[元素个数]; // 元素个数不能超过int范围
arr = new int[3];
arr2 = new char[9];
数组元素的访问 :
数组名[偏移量] = 值;
Sytstem.out.println(数组名[偏移量]);
数组对象一旦创建 : 长度不可以改变, 元素数据类型也不能改变.
数组循环代码实例练习:
// 创建26个元素的char数组, 保存所有大写字母
public class ArrayExer2 {
public static void main(String[] args) {
char[] arr = new char[26];
for (int i = 0; i < arr.length; i++) {
arr[i] = (char)('A' + i);
}
// 遍历
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
}
引用数组
对象数组本质是引用数组, 引用数组也称为对象数组的原因是, 引用指向对象
空指针异常, 就是指用一个值为null的引用, 调用它的成员
出现空指针一定有 . 操作, .操作就是万恶之首
package com.atguigu.javase.array;
import com.atguigu.javase.javabean.MyDate;
public class MyDateTest {
public static void main(String[] args) {
// 改进 : 创建一个拥有10个元素的Student数组, 创建10个学生对象,要求id是1~10, 姓名:小明, 年级1~6, 分数是50~100分
// 改进2 : 只打印3年级同学的信息?
MyDate[] arr = new MyDate[6];
for (int i = 0; i < arr.length; i++) {
int year = 2010 + i;
int month = 5 + i;
int day = 8 + i ;
arr[i] = new MyDate(year, month, day);
}
// 遍历
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i].say());
}
}
public static void main1(String[] args) {
// 引用数组, 缺省值都是空指针!!!
MyDate[] arr = new MyDate[5];
arr[0] = new MyDate(1992, 5, 8);
arr[1] = new MyDate(2000, 8, 9);
arr[3] = new MyDate(2008, 8, 8);
arr[4] = new MyDate(2018, 10, 20);
// 此时下标为2的元素里面还是空指针 , 所以有风险!!, 就把这种元素称为空洞.
arr[2] = new MyDate(2020, 6, 3); // 补上空洞
// 遍历
for (int i = 0; i < arr.length; i++) {
if (arr[i] != null) { // 非空指针
System.out.println(arr[i].say());
} else { // 空指针
System.out.println(arr[i]);
}
}
}
}
数组常用算法
普通for循环(遍历)
for (int i = 0; i < arr.length; i++) {
int tmp = arr[i];
System.out.print(tmp + "");
}
System.out.println();
增强型for循环
for ( int tmp : arr){
System.out.print(tmp + " ");
}
System.out.println();
冒泡排序
for (int i = 0; i < arr.length-1; i++) {
for (int j = 0; j < arr.length -1-i; j++) {
if (arr[j] > arr[j+1]){
int tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
}
}
}
选择排序
for (int i = 0; i < arr.length-1; i++) {
int minIndex = i;
for (int j = i +1; j < arr.length ; j++) {
if (arr[j] < arr[minIndex]){
minIndex = j;
}
}
int tmp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = tmp;
}
数组反转
for (int i = 0; i < arr.length/2; i++) {
//交换位置i 和 arr.length -1 -i
int tmp =arr[i];
arr[i] = arr[arr.length-1-i];
arr[arr.length -1 -i] = tmp;
}
数组缩减
int[] newArr = new int[arr.length/2];
for (int i = 0; i < newArr.length; i++) {
newArr[i] = arr[i];
}
arr = newArr;
数组扩容
int[] newArr = new int[(int)(arr.length * 1.5)];
for (int i = 0; i <arr.length ; i++) {
newArr[i] = arr[i];
}
取子数组
//保存一个 所以奇数的子数组 计数器count
int[] newArr = new int[arr.length];
int count = 0;
for (int i = 0; i < arr.length; i++) {
if (arr[i]%2 != 0){
newArr[count] = arr[i];
count++;
}
}
int[] finalArr = new int[count];
for (int i = 0; i < count; i++) {
finalArr[i] = newArr[i];
}
可变参数
体现在个数可以变化。
可变参数必须位于参数列表的最后,可以完全兼容数组类型, 在运行时 本质仍然还是数组.
代码实例
public static int avg(int... values) {
int sum = 0;
for (int i = 0; i < values.length; i++) {
sum += values[i];
}
return sum / values.length;
}
二维数组
声明 : 元素数据类型[][] [][] [] [] 数组名;
创建 :
new 元素数据类型[子数组个数] [每个子数组的长度];
new 元素数据类型[子数组个数] []; // 此时并没有真的子数组
数组名[子数组索引] = new 元素数据类型[长度];
代码实例
public static void main(String[] args) {
// 静态初始化1
int[][] arrarr1 = new int[][]{{2, 3}, {9, 7, 3, 4}, {5, 8, 10, 20, 30, 40}, {7}};
// 静态初始伦2, 使用受限 :声明和初始化必须在同一行语句上
int[][] arrarr2 = {{2, 3}, {9, 7, 3, 4}, {5, 8, 10, 20, 30, 40}, {7}};
int[][] arrarr3;
arrarr3 = new int[][] {{3, 9, 1}, {10, 20, 30 , 40, 50}};
}
杨辉三角形
public class YangHui {
public static void main(String[] args) {
int[][] yanghui =new int[10][];
for (int i = 0; i < yanghui.length; i++) {
yanghui[i] = new int[i + 1];
for (int j = 0; j < i + 1; j++) {
if (j == 0 || j == i){
yanghui[i][j] = 1;
}else {
yanghui[i][j] = yanghui[i - 1][j - 1] + yanghui[i - 1][j];
}
System.out.print(yanghui[i][j] + " ");
}
System.out.println();
}
}
}
Math.random()
0 < Math.random() < 1
代码练习实例
public class ArrayTest {
public static void main(String[] args) {
int[] arr = new int[8];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) (Math.random() * -20);
}
// 遍历
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
// 0 1 2 3 4 5 6 7
//11 12 6 0 17 12 13 16
int max = arr[0]; // 假设第一个值最大
for (int i = 0; i < arr.length; i++) {
if (arr[i] > max) {// 如果数组中的i下标的元素的值大于最大值
max = arr[i]; // 刷新最大值(就像把世界纪录刷新)
}
}
System.out.println("max = " + max);
// 抄写代码
// 找出最小值
int min = arr[0];
for (int i = 0; i < arr.length; i++) {
if (arr[i] < min) {
min = arr[i];
}
}
System.out.println("min = " + min);
}
Idea使用
设置字体 :
File -> settings 打开设置窗口
展开左侧的Editor -> 找到font
右侧的设置中改变文字大小和字体.
快捷键:
Alt + 回车, 快速修复, 快捷操作
Ctrl + p 方法调用时的参数列表提醒
Shift + 回车 在任意位置直接进入新行
Main 回车, 主方法
Sout 回车, 打印输出语句
soutv 回车,打印输出语句,包括变量
Ctrl + d 快速复制行
Ctrl + y 快速删除行
Alt + shift + 上下方向可以调整代码位置
在idea中 测试类必须是公共类
继承
父类
从现有类创建子类, 现有类称为父类, 基类(子类会在父类的基础之上进行扩张), 超类(在子类中使用super标识父类).
子类继承父类的所有成员(构造器除外), 包括属性和方法. 继承体现的是所有权.
子类
子类可以继承父类中的私有成员, 但是在子类中没有直接使用权.
可以通过从父类继承的公共的get/set方法间接访问
类继承语法规则:
class Subclass extends Superclass{ }
单继承 :
一个子类只允许有一个直接父类, 间接父类不限, 支持多层继承, 不支持多重继承
在java中只支持单继承
c++中可以支持多重继承, 太复杂.
重写(覆盖) Override :
子类不满从父类继承的方法. 然后对其进行改造, 重写, 重置.
方法重写条件 :
-
方法签名完全一致, 体现为返回值类型一致, 方法名完全一致, 参数完全一致 ( 顺序一致, 类型一致, 个数一致 )
-
子类方法的访问控制修饰符的范围要大于等于父类的. 体现子类是父类的扩张.
-
被覆盖方法不可以被private, static, final修饰
-
子类方法抛出的受检异常类型要小于等于父类方法抛出的异常
关键字super
在Java类中使用super来调用父类中的指定操作:
- super可用于访问父类中定义的属性
- super可用于调用父类中定义的成员方法
- super可用于在子类构造方法中调用父类的构造器
注意:
- 尤其当子父类出现同名成员时,可以用super进行区分
- super的追溯不仅限于直接父类
- super和this的用法相像,this代表本类对象的引用,super代表父类的内存空间的标识
特殊注释@Override
注解 : 是特殊的注释, 特殊在于可以被编译器和JVM识别. @Override特别作用就是提醒编译器
此方法要完成方法覆盖了, 请帮助我们做方法覆盖的条件的检查, 如果方法覆盖有问题,编译出错!!!
构造器不可以继承, 但是对于子类来说, 父类构造器是可见的.
并且在创建对象时, 父类构造器先于子类构造器执行.
- 所有子类的构造器中默认的第一行就是super();
如果父类中没有无参构造器, 就会造成子类中的构造器会出现问题.
- 也可以在子类构造器中显式的使用super(…) 显式调用父类的有参构造.
super()和this() 不可以在同一个构造器中共存!!!
java语言规定 : 子类构造器中的第一行
必须是super(…) 或者是 this(…)
如果是super(…) 直接调用了父类构造
如果是this(…) 间接调用了父类构造.
结论 :
子类构造器中必须要有先对父类构造的调用.
如何保证先调用? 通过强制的要求this(…)或super(…)必须第一行.
体现先父后子.
多态
多态 : 多种形态, 子类对象的多种父类形态.把子类对象当成父类对象来使用
本态 : 子类对象的本类形态.
- 本态引用 : 把子类对象赋值于子类类型的引用变量.
- 多态引用 : 把子类对象赋值于父类类型的引用变量
从右向左看, 子类对象的多种父类形态. 一个子类可以有多个父类
从左向右看, 父类类型的引用指向子类对象的不确定性, 多样性
在java中有两种体现:
- 对象的多态性
- 引用变量的多态性
Java引用变量有两个类型:
编译时类型和运行时类型
编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。
若编译时类型和运行时类型不一致,就出现多态(Polymorphism)
对象的多态性
子类对象的多种父类形态, 换言之就是把子类对象作为父类对象来使用.
引用变量的多态性
- 一个基本型变量只能有一种确定的数据类型
- 一个引用类型变量可能指向(引用)多种不同类型的对象
Person p = new Student();
Object o = new Person();
//Object类型的变量o,指向Person类型的对象
o = new Student();
//Object类型的变量o,指向Student类型的对象
子类可看做是特殊的父类,所以父类类型的引用可以指向子类的对象:向上转型(upcasting)。
多态副作用
一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法(多态副作用)
Student m = new Student();
m.school = “pku”; //合法,Student类有school成员变量
Person e = new Student();
e.school = “pku”; //非法,Person类没有school成员变量
属性和方法是在编译时确定的,编译时引用变量e为Person类型,Person类型中并没有school成员变量,因而编译错误, 方法的使用也是一样
虚拟方法调用(Virtual Method Invocation)
正常的方法调用
Person e = new Person();
e.getInfo();
Student e = new Student();
e.getInfo();
虚拟方法调用(多态情况下)
Person e = new Student();
e.getInfo(); //调用Student类的getInfo()方法
编译时类型和运行时类型
编译时e为Person类型,而方法的调用是在运行时确定的,所以调用的是Student类的getInfo()方法。
——动态绑定
instanceof 操作符
x instanceof A:检验x是否为类A的对象,返回值为boolean型。
- 要求x所属的类与类A必须是子类和父类的关系,否则编译错误。
- 如果x属于类A的子类B,x instanceof A值也为true。
public class Person extends Object {…}
public class Student extends Person {…}
public class Graduate extends Student {…}
public void method1(Person e) {
if (e instanceof Person)
// 处理Person类及其子类对象
if (e instanceof Student)
//处理Student类及其子类对象
if (e instanceof Graduate)
//处理Graduate类及其子类对象
对象类型转换
基本数据类型的Casting:
自动类型转换:
小的数据类型可以自动转换成大的数据类型
如long g=20; double d=12.0f
强制类型转换:
可以把大的数据类型强制转换(casting)成小的数据类型
如 float f=(float)12.0; int a=(int)1200L
对Java对象的强制类型转换称为造型
- 从子类到父类的类型可以自动进行
- 从父类到子类的类型转换必须通过造型(强制类型转换)实现
- 无继承关系的引用类型间的转换是非法的
- 在造型前可以使用instanceof操作符测试一个对象的类型
关键字static
1、static的含义
static:静态的,属于类共有的。严格来讲,它不属于面向对象的范畴。
那么,什么情况下使用静态?
- 当某个成员变量的值不是每个对象独立的,而且所有对象共享的,这样的变量就可以声明为静态变量
- 当某个方法的调用不会因为对象的不同而不同,即和对象无关,这样的方法就可以声明为静态方法
示例一:
/*
* static修饰的成员变量,是属于类的,该类所有对象共享的
*/
public class TestStaticField {
public static void main(String[] args) {
Chinese c1 = new Chinese();
Chinese c2 = new Chinese();
//设置c1对象的三个成员变量的值
c1.country = "中国";
c1.name = "张三";
c1.age = 23;
System.out.println("c1:" + c1.country + "," + c1.name + "," + c1.age);//c1:中国,张三,23
System.out.println("c2:" + c2.country + "," + c2.name + "," + c2.age);//c2:中国,null,0
System.out.println("-------------------------");
//设置c2对象的三个成员变量的值
c2.country = "中华人民共和国";
c2.name = "李四";
c2.age = 24;
System.out.println("c1:" + c1.country + "," + c1.name + "," + c1.age);//c1:中华人民共和国,张三,23
System.out.println("c2:" + c2.country + "," + c2.name + "," + c2.age);//c2:中华人民共和国,李四,24
System.out.println("-------------------------");
//修改Chinese类的country
Chinese.country = "大中国";
System.out.println("c1:" + c1.country + "," + c1.name + "," + c1.age);//c1:中华人民共和国,张三,23
System.out.println("c2:" + c2.country + "," + c2.name + "," + c2.age);//c2:中华人民共和国,李四,24
}
}
/*
* 声明一个中国人这样的一个类:Chinese
* 需要这些属性:国家名称、个人的姓名,年龄
* 每一个中国人的姓名和年龄是不同的,每一个人都有单独的一份
* 而国家的名称是大家共享的,不需要每个人单独存储一份 *
*/
class Chinese{
//这里成员变量没有加private,是为了我们上面测试使用方便
static String country;
String name;
int age;
}
示例二:
public class TestStaticMethod {
public static void main(String[] args) {
int[] array = {2,4,1,5};
int max2 = Tools2.max(array);
System.out.println("最大值:" + max2);
Tools2 t2 = new Tools2();
int max3 = t2.max(array);//可以这么用,但是麻烦,还创建对象了
System.out.println("最大值:" + max3);
}
}
class Tools2{
//max方法的调用和Tools2的对象无关
public static int max(int[] arr){
//假设第一个元素最大
int max = arr[0];
//和arr数组剩下的元素[1,arr.length-1]比较,看是否还有比max大的
for (int i = 1; i < arr.length; i++) {
//当arr中其他的元素比max大,修改max的值
if(max < arr[i]){
max = arr[i];
}
}
//返回max
return max;
}
}
2、static不能修饰
- top-level的类和接口
- 但是可以有静态内部类和静态内部接口(后面讲)
- 构造器
- 局部变量
- 抽象方法
3、static可以修饰
(1)修饰成员变量,称为静态变量或类变量
- 该类所有对象共享
- 静态变量在方法区分配内存,和类信息存储在一起
- 如果在类外部可见,可以通过"类名." 或 "对象名."进行访问
- 强烈建议使用"类名."
- 如果提供get/set,那么它的get/set也是静态的
- 当在静态方法或静态代码块中出现局部变量与静态变量重名时,使用“类名.“进行区别,而不是使用"this.”
- 在类初始化时初始化
- 显式初始化
- 静态代码块
- 虽然可以在构造器中访问,但是一般不会在构造器中初始化它,除非每次创建对象都要修改它的值
public class TestStaticMemory {
public static void main(String[] args) {
Teacher.school = "尚硅谷";
Teacher t1 = new Teacher();
t1.name = "韩顺平";
Teacher t2 = new Teacher();
t2.name = "柴林燕";
}
}
//做一个尚硅谷的信息管理系统
//声明尚硅谷的老师
class Teacher{
static String school;
String name;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cC6UKRbK-1597399140927)(E:/JAVA%E8%AF%BE%E4%BB%B6%EF%BC%88%E4%B8%80%EF%BC%89/JavaSE/0522Java%E7%8F%AD%E4%B8%B2%E8%AE%B2/%E7%AC%AC4%E6%AC%A1%E4%B8%B2%E8%AE%B2%E8%B5%84%E6%96%99/%E7%AC%AC4%E6%AC%A1%E4%B8%B2%E8%AE%B2%E7%AC%94%E8%AE%B0/imgs/image-20200403100146807.png)]
public class TestStaticField2 {
public static void main(String[] args) {
American.setCountry("美国");
}
}
//美国人
class American{
//成员变量私有化,可以提供get/set
private static String country;
private String name;
public static String getCountry() {
return country;
}
public static void setCountry(String country) {
//局部变量(String country)与静态变量private static String country;同名
//不使用this.,使用“类名."进行区别
//为什么?
//因为country不属于对象,属于类
//调用setCountry方法时,可能根本没有American的对象
American.country = country;
}
public String getName() {
return name;
}
public void setName(String name) {
//因为局部变量(String name)与成员变量private String name;同名,
//所以需要在成员变量的前面加this.
this.name = name;
}
}
(2)修饰成员方法,称为静态方法或类方法
- 使用"类名." 或 "对象名."进行访问
- 强烈建议使用"类名."
- 静态方法中不允许“直接”使用本类/父类可见的非静态成员
- 反过来可以,即非静态方法中可以直接使用本类/父类可见的静态成员
- 静态方法中也不能出现this和super的引用
- 原因是静态方法等使用时,可能没有实例对象,而this和super都是指实例对象
- 父类的静态方法可以被继承,但是父类的静态方法不能被重写
- 可以通过“子类名.父类的静态方法”的形式进行调用
- 父接口的静态方法**不能**被继承,只能通过“接口名."进行调用
public class TestStaticMethodOverride {
public static void main(String[] args) {
Son.method();//可以被继承到子类中
Father.test();
Son.test();//好像重写了一样
//多态引用:父类的变量指向子类的对象
Father f = new Son();
f.test();//父类的test方法
//如果子类重写了test(),那么执行的应该是"子类的test方法",但是这里不是
}
}
class Father{
public static void method(){
System.out.println("父类的静态方法");
}
//这个test是父类的
public static void test(){
System.out.println("父类的test方法");
}
}
class Son extends Father{
//这个test是子类自己的
//@Override //这个注解是说,这个方法是重写父类的方法
public static void test(){
System.out.println("子类的test方法");
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U9yCCczt-1597399140932)(E:/JAVA%E8%AF%BE%E4%BB%B6%EF%BC%88%E4%B8%80%EF%BC%89/JavaSE/0522Java%E7%8F%AD%E4%B8%B2%E8%AE%B2/%E7%AC%AC4%E6%AC%A1%E4%B8%B2%E8%AE%B2%E8%B5%84%E6%96%99/%E7%AC%AC4%E6%AC%A1%E4%B8%B2%E8%AE%B2%E7%AC%94%E8%AE%B0/imgs/image-20200403101147865.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aFPfYPc6-1597399140934)(E:/JAVA%E8%AF%BE%E4%BB%B6%EF%BC%88%E4%B8%80%EF%BC%89/JavaSE/0522Java%E7%8F%AD%E4%B8%B2%E8%AE%B2/%E7%AC%AC4%E6%AC%A1%E4%B8%B2%E8%AE%B2%E8%B5%84%E6%96%99/%E7%AC%AC4%E6%AC%A1%E4%B8%B2%E8%AE%B2%E7%AC%94%E8%AE%B0/imgs/image-20200403101308778.png)]
(3)修饰代码块,称为静态代码块
如果成员变量想要初始化的值不是一个硬编码的常量值,而是需要通过复杂的计算或读取文件、或读取运行环境信息等方式才能获取的一些值,该怎么办呢?可以使用静态代码块
【修饰符】 class 类名{
static{
静态初始化
}
}
- 静态初始化块:为静态变量初始化
- 一个类的静态代码块只会执行一次,在类初始化时执行
- 本质上静态变量的显示赋值与静态代码块的代码会被编译器合并成一个()的类初始化方法,每一个类都有一个类初始化方法,在类被加载器加载到内存之后,对类初始化时执行
- 子类初始化时,如果发现父类还未初始化,那么会先初始化父类,那么父类的静态代码块就会执行;如果父类已经初始化过了,那么就不需要重复初始化父类,即父类的静态代码块不会执行两次。
- 静态代码块中不允许直接访问本类的非静态成员,也不能出现this和super
(4)修饰成员内部类,称为静态内部类(后面章节讲)
【修饰符】 class 类名{
//静态内部类
【修饰符】 static class 内部类名{
}
}
(5)静态导入
import static 包.类名.静态成员;
import static 包.类名.*;
现在另一个类中有如下静态成员:
package com.atguigu.test18.other;
public class Constant {
public static int a = 1;
public static int b = 1;
private static int c = 1;
public int d = 1;
public static void method(){
//...
}
}
没有使用静态导入,在当前类中要使用另一个类的静态成员时,需要“类名.”:
/*
* static和public
*
* (1)是否可见
* public:可不可以访问到,是否可见
* (2)如何拿到可见的它
* static:
* 不需要对象.,直接“类名."就可以
* 没有static:
* 需要“对象."才可以
*/
import com.atguigu.test18.other.Constant;
public class TestImport {
public static void main(String[] args) {
System.out.println(Constant.a);
System.out.println(Constant.b);
// System.out.println(Constant.c);//错误,私有的
Constant.method();
}
}
如果使用静态导入,在当前类中使用这个类的静态成员时,前面就不用加“类名.”了
package com.atguigu.test18;
import static com.atguigu.test18.other.Constant.*; //静态导入
public class TestImport {
public static void main(String[] args) {
System.out.println(a);
System.out.println(b);
//System.out.println(c);//私有不可见
method();
// System.out.println(d);//错误,d没有被导入进来,而且需要对象使用
}
}
4、注意
- 本类的静态成员不能直接访问本类的非静态成员
- 静态方法和静态代码块中不允许出现this和super
- 父类的静态方法可以被继承,但是父类的静态方法不能被重写
- 父接口的静态方法不能被继承,只能通过“接口名."进行调用
单例(Singleton)设计模式
饿汉式单例 :
在类加载时就创建唯一对象, 适用对象创建简单. 没有安全问题
-
封装构造器
-
声明私有静态本类类型的引用属性, 指向唯一对象
-
声明公共静态方法, 用于获取唯一对象
class Singleton1 {
// 在本类中new唯一对象, 用一个私有静态属性作为引用指向唯一对象, 防止外部直接修改它.
private static Singleton1 only = new Singleton1();
// 再写一个公共静态方法, 用以获取唯一对象
public static Singleton1 getInstance() {
return only;
}
// 私有化构造器, 封死new操作, 防止外部随意new对象.
private Singleton1() {
}
}
懒汉式单例 :
在类加载时不创建对象, 在获取对象时才创建. 有线程安全问题. 适用于创建对象耗时.
-
封装构造器
-
声明私有静态本类类型的引用属性, 不指向对象
-
声明公共静态方法, 用于获取唯一对象, 在方法中保证只有第一次调用时才创建唯一对象.
(延迟初始化)
class Singleton2 {
private static Singleton2 only = null;
public static Singleton2 getInstance() {
if (only == null) { // 只有在第一次获取对象时才调用
only = new Singleton2();
}
return only;
}
private Singleton2() {}
}
链表
package com.atguigu.javase.review;
class Node {
Object value; // 数据域
Node next; // 指针域
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
}
/**
* 在指定的Index索引处插入新元素
* @param index, 范围必须是[0~size-1]
* @param obj
*/
public void add(int index, Object obj) {
if (index < 0 || index >= size) {
return;
}
Node newNode = new Node(obj);
Node prev = head;
// 定位到index-1的元素
for (int i = 0; i < index - 1; i++) {
prev = prev.next;
}
newNode.next = prev.next; // 新结点指向前一个结点的下一个.
prev.next = newNode;
size++;
if (index == 0) { // 如果是头部插入, 更新头为新结点.
head = newNode;
}
}
/**
* 获取指定对象在链表中的索引
* @param obj
* @return
*/
public int indexOf(Object obj) {
int index = 0;
Node tmp = head;
while (tmp != null) {
if (tmp.value.equals(obj)) {
return index;
}
tmp = tmp.next;
index++;
}
return -1;
}
/**
* 根据索引直接删除某元素
* @param index
* @return
*/
public boolean remove(int index) {
if (index < 0 || index >= size) {
return false;
}
Node prev = head;
for (int i = 0; i < index - 1; i++) {
prev = prev.next;
}
prev.next = prev.next.next;
if (prev.next == null) {
tail = prev;
}
size--;
if (index == 0) {
head = head.next;
}
return true;
}
class Link {
private Node head;
private Node tail;
private int size;
public void add(Object obj) {
Node newNode = new Node(); // 创建新结点, 所有数据都封装在结点对象中才能链
newNode.value = obj; // 携带数据
if (head == null) { // 如果链表为空
head = newNode;
tail = newNode;
} else { // 链表非空
tail.next = newNode;
tail = newNode;
}
size++;
}
public void travel() {
Node tmp = head;
while (tmp != null) { // 当tmp为null时, 说明最后一个结点也遍历了.
System.out.println(tmp.value);
tmp = tmp.next;
}
System.out.println("****************************");
}
public int size() {
return size;
}
public void delete(Object obj) {
if (head == null) { // 如果链表为空, 直接返回
return;
}
if (head.value.equals(obj)) { // 判断是否要删除头
head = head.next; // 让头变成新头, 新头就是老头的next
size--;
return;
}
// 不是删除头结点
Node prev = head;
while (prev.next != null) { // 只要目标非空就循环
if (prev.next.value.equals(obj)) { // 如果条件满足, 定位prev就成功
prev.next = prev.next.next; // 删除的目标结点的下一个节点地址, 回刷给prev.next就可以
size--;
// 如果删除的是尾结点??
if (prev.next == null) {
tail = prev; // 更新尾引用
}
break;
}
prev = prev.next; // 让prev继续向后搜索.
}
}
/*
public int size() {
int size = 0;
Node tmp = head;
while (tmp != null) { // 当tmp为null时, 说明最后一个结点也遍历了.
size++;
tmp = tmp.next;
}
return size;
}
*/
}
public class LinkTest {
public static void main(String[] args) {
Link link = new Link();
link.add("bb");
link.add("cc");
link.add("dd");
link.add("xx");
link.add("zz");
link.add("aa");
link.travel();
System.out.println(link.size());
link.delete("bb");
link.travel();
System.out.println(link.size());
}
}
二叉树代码
package com.atguigu.javase.datastruct;
public class Tree {
private class TreeNode {
private Comparable value; // 数据域
private TreeNode left; // 左子
private TreeNode right; // 右子
public TreeNode(Comparable value) {
this.value = value;
}
}
private TreeNode root;
private int size;
public void add(Comparable obj) {
TreeNode newNode = new TreeNode(obj);
if (root == null) { // 树为空
root = newNode;
} else {
insert(root, newNode);
}
size++;
}
private void insert(TreeNode target, TreeNode newNode) {
if (newNode.value.compareTo(target.value) < 0) { // 向左走
if (target.left == null) {
target.left = newNode;
} else { // 左子非空
insert(target.left, newNode);
}
} else { // 向右走
if (target.right == null) {
target.right = newNode;
} else {
insert(target.right, newNode);
}
}
}
public int getSize() {
return size;
}
public void travel() {
view(root);
}
private void view(TreeNode node) {
if (node == null) {
return;
}
if (node.left != null) {
// 把左子又当成一个新的小树
view(node.left);
}
System.out.println(node.value);
if (node.right != null) {
// 把右子又当成一个新的小树
view(node.right);
}
}
}
public class TreeTest {
public static void main(String[] args) {
Tree tree = new Tree();
tree.add(10);
tree.add(5);
tree.add(20);
tree.add(15);
tree.add(7);
tree.add(2);
tree.add(9);
tree.travel();
}
}
初始化块
初始化块(代码块)作用:对Java对象进行初始化
- 程序的执行顺序:声明成员变量的默认值
- 显式初始化、多个初始化块依次被执行(同级别下按先后顺序执行)
- 构造器再对成员进行赋值操作
静态代码块:
一个类中初始化块若有修饰符,则只能被static修饰,称为静态代码块(static block ),当类被载入时,类属性的声明和静态代码块先后顺序被执行,且只被执行一次。
static块通常用于初始化static (类)属性
class Person {
public static int total;
static {
total = 100;//为total赋初值
}
…… //其它属性或方法声明
}
静态语句块, 静态初始化块, 是一个特殊方法. 它只在类加载时执行仅有一次. 在第一次使用类时, 类就会加载了
编译器会把这段代码的所有static块及显式赋值都整合, 写成一个方法叫 :
只有方法能包含语句.
可以给类的属性进行初始化, 所以把它也称为类的初始化器.(类构造器)
静态块是顺序执行的
静态代码块:
用static 修饰的代码块
1.可以有输出语句。
2.可以对类的属性、类的声明进行初始化操作。
3.不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法。
4.若有多个静态的代码块,那么按照从上到下的顺序依次执行。
5.静态代码块的执行要先于非静态代码块。
6.静态代码块只执行一次
非静态代码块:
没有static修饰的代码块
1.可以有输出语句。
2.可以对类的属性、类的声明进行初始化操作。
3.可以调用静态的变量或方法。
4.若有多个非静态的代码块,那么按照从上到下的顺序依次执行。
5.每次创建对象的时候,都会执行一次。且先于构造器执行
关键字:final
在Java中声明类、属性和方法时,可使用关键字final来修饰,表示“最终”。
final标记的类不能被继承 提高安全性,提高程序的可读性。
String类、System类、StringBuffer类
final标记的方法不能被子类重写
Object类中的getClass()。
final标记的变量(成员变量或局部变量)即称为常量。名称大写,且只能被赋值一次
- final标记的成员变量必须在声明的同时或在每个构造方法中或代码块中显式赋值,然后才能使用。
- final double PI=3.14;
final修饰类
final class A{
}
class B extends A{ //错误,不能被继承。
}
final修饰方法
class A{
public final void print(){
System.out.println(“A”);
}
}
class B extends A{
public void print(){ //错误,不能被重写。
System.out.println(“尚硅谷”);
}
}
final修饰变量——常量
class A{
private final String INFO = “atguigu”; //声明常量
public void print(){
//INFO = “尚硅谷”;
}
}
常量名要大写,内容不可修改
抽象类(abstract class)
抽象类与具体类
- 具体类 — 对现实世界一种实体的抽象定义
- 抽象类 — 对现实世界某一类的多种不同实体的统一抽象定义
- 用abstract关键字来修饰一个类时,这个类叫做抽象类
- 用abstract来修饰一个方法时,该方法叫做抽象方法
- 抽象方法:只有方法的声明,没有方法的实现。以分号结束:abstract int abstractMethod( int a );
- 含有抽象方法的类必须被声明为抽象类
- 抽象类不能被实例化。抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类
- 不能用abstract修饰属性、私有方法、构造器、静态方法、final的方法。
抽象类中包含的成员包括:
- 属性
- 构造器
- 具体方法
- 抽象方法
注意:
- 抽象类不能被实例化
- 子类(具体类)可继承抽象父类 ,实现抽象方法
- 具体子类适用抽象父类的多态
模板方法设计模式(TemplateMethod)
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
解决的问题:
- 当功能内部一部分实现是确定,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
- 编写一个抽象父类,父类提供了多个子类的通用方法,并把一个或多个方法留给其子类实现,就是一种模板模式。
实例:
abstract class Template{
public final void getTime(){
long start = System.currentTimeMillis();
code();
long end = System.currentTimeMillis();
System.out.println("执行时间是:"+(end - start));
}
public abstract void code();
}
class SubTemplate extends Template{
public void code(){
for(int i = 0;i<10000;i++){
System.out.println(i);
} } }
接口
接口的用途是用来定义现实世界不同类型事物的共同行为特征。
接口中所有方法均为抽象方法
示例:
public interface Flyer {
public void takeoff();
……
}
接口可以包含以下成员:
属性
接口中的所有属性均被视静态常量。例如,下面几种方式的声明是等效的:
int num = 10;
public int num = 10;
public static final int num = 10;
抽象方法
接口中所有方法均为抽象方法。例如,下面两种方式的声明是等效的:
public abstract void takeoff();
public void takeoff();
有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果
- 一个类可以实现多个接口
- 接口不能被实例化
- 具体类(子类)可以实现接口(父类) ,并实现接口中的全部抽象方法
- class SubClass implements InterfaceA{ }
- 具体类适用父接口的多态
- 接口也可以继承其它接口
实现(implements)
- 实现接口的类中必须提供接口中所有方法的具体实现内容,方可实例化。否则,仍为抽象类。
- 接口的主要用途就是被实现类实现。(面向接口编程)
代码示例:
public interface Runner {
public void start();
public void run();
public void stop();
}
public class Person implements Runner {
public void start() {
// 准备工作:弯腰、蹬腿、咬牙、瞪眼 // 开跑
}
public void run() {
// 摆动手臂
// 维持直线方向
}
public void stop() {
// 减速直至停止、喝水。
} }
代码练习
public interface Flyer {
public static final int num = 10; // 全局常量
void takeOff();
void fly();
void land();
}
public class FlyerTest {
public static void main(String[] args) {
//Flyer f = new Flyer();
Flyer f = new Plane(); // 子类对象适用于接口的多态.
f.takeOff(); // 都是虚拟方法调用!!!
f.fly();
f.land();
}
}
代理模式(Proxy)
代理模式 :
把代理对象当成被代理对象使用
使用场景 :
-
代理对象更容易, 被代理对象的创建极其复杂.
-
有对被代理对象的方法的升级的需要, 但又不能修改被代理类. 用代理类来完成增强的部分
面向接口 : 只把子类对象当成接口类型, 具体子类类型是什么不重要.
接口作用 : 把用户和子类这两个完全不同的东西, 连接起来.
代码练习:
interface HouseRent {
void rent();
}
class FangDong implements HouseRent {
@Override
public void rent() {
System.out.println("我有房子要出租, 是新婚房, 请爱护, 房租卡号 : 234293482348234");
}
}
class FangDong2 implements HouseRent {
@Override
public void rent() {
System.out.println("我有房子要出租, 刚死了2个人, 晚上要小心");
}
}
class LianJia implements HouseRent {
private HouseRent houseRent = new FangDong2();
@Override
public void rent() { // 代理对象的方法
System.out.println("先交中介费20000");
houseRent.rent(); // 被代理对象的实际方法, 原始方法调用
System.out.println("按时交房租, 不然赶走..., 支持微信, 支付宝");
}
}
public class ProxyTest {
public static void main(String[] args) {
// 面向接口编程, 把子类对象的个性抹去, 只看对象是否满足了接口标准.
HouseRent hr = new LianJia();// 看不到, 使用者对于对象的要求不高.
hr.rent();
}
}
工厂方法:
工厂模式, 就是通过方法调用获取到对象
interface Worker {
void work();
}
class Teacher implements Worker {
public void work() {
System.out.println("老师在上课...");
}
}
class Student implements Worker {
public void work() {
System.out.println("学生在听课...");
}
}
class Factory {
public static Worker getWorker1() {
return new Teacher();
}
public static Worker getWorker2() {
return new Student();
}
}
public class FactoryTest {
public static void main(String[] args) {
// 使用者拿到的对象是什么不重要, 只要符合接口就可以, 也是面向接口编程.
Worker worker1 = Factory.getWorker1(); // 对象的获取不再需要new.
worker1.work();
Worker worker2 = Factory.getWorker2();
worker2.work();
}
}
内部类
一个类的定义位于另一个类的内部, 里面的类称为内部类, 外面的类称为外部类
分类 :
成员内部类 :
声明在类中方法外的内部类
-
普通内部类 : 没有static修饰的成员内部类
-
嵌套类 : 加static修饰的成员内部类
局部内部类 :
声明在方法中的内部类
-
普通局部内部类 : 直接在方法中声明类, 并再使用此类, 类的生命周期短
-
匿名内部类 : 没有类名的局部内部类( 超重点!!! )
对象关联时, 关联对象只能访问被关联对象的公共成员.
普通内部类的使用, 比对象关联更进了一步, 内部类对象中要更加频繁的直接访问外部类的所有成员时
class Outer {
private int num = 10; // 外部类的当前对象的属性.
private void outerTest2() {
System.out.println("Outer private method()...");
}
// 普通内部类, 没有static修饰, 意味着它隶属于外部类对象
// 也算为外部类的成员, 成员可以互访, 所以成员都直接访问
public class Inner1 {
//public static String n = "abc";
private int num = 100;
public void inner1Test1() {
System.out.println(Inner1.this.num); // 就近原则会导致访问的是本类的this对象的num属性
System.out.println(Outer.this.num); // 外部类的当前对象的num属性
outerTest2(); // 直接使用了外部类对象的所有成员. 像对象关联, 内部类关联外部类.
}
}
public void outerTest1() {
// 要想使用内部类, 必须得创建对象才行
Inner1 inner1 = this.new Inner1(); // 当前外部类对象.new
inner1.inner1Test1();
}
public void outerTest3() {
System.out.println(Inner2.age);
Inner2 inner2 = new Inner2();
System.out.println(inner2.getName());
}
// 嵌套类
public static class Inner2 { // 它和外部类几乎没有什么联系.
// 本质上和外部类是平级的, 只不过是声明在里面.
public static int age = 20;
public static void inner2Test() {
System.out.println("Inner2 static method");
}
private int id;
private String name;
public static int getAge() {
return age;
}
public static void setAge(int age) {
Inner2.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
public class InnerTest {
public static void main3(String[] args) {
// 普通局部内部类, 只能在本方法中使用
{
class Inner3 {
private int n = 200;
private String s;
}
class Inner4 {
private int n = 200;
private String s;
}
Inner3 inner3 = new Inner3();
System.out.println(inner3.n);
for (int i = 0; i < 2; i++) {
};
}
//new Inner3();
}
public static void test() {
//new Inner3();
}
public static void main2(String[] args) {
Outer outer = new Outer();
//outer.outerTest1();
// 直接创建内部类对象, 外部类对象.new 内部类();
Outer.Inner1 oi1 = outer.new Inner1(); //new outer.Inner1(); //new Outer.Inner1();
oi1.inner1Test1();
new Outer().new Inner1().inner1Test1();
Outer.Inner2.age = 50;
System.out.println(Outer.Inner2.age);
// 嵌套类创建对象, 更简单直观
Outer.Inner2 oi2 = new Outer.Inner2();
System.out.println(oi2);
}
}
匿名内部类
匿名内部类不能定义任何静态成员、方法和类,只能创建匿名内部类的一个实例。一个匿名内部类一定是在new的后面,用其隐含实现一个接口或继承一个类。
new 父类构造器(实参列表)|实现接口(){
//匿名内部类的类体部分
};
匿名内部类对象的应用场景 : 一次性使用某个接口的实现子类. 接口也通常很简单, 只有一个方法
代码实例
public class AnnonymousTest {
public static void main(String[] args) {
IA ia = new IA() { // 匿名内部类通常用于接口.
// 类体的部分相当于接口的实现子类
@Override
public void test(int n) {
System.out.println("test : " + n);
}
};
ia.test(100);
// 类是匿名的, 并且创建了匿名对象.
new IA() {
@Override
public void test(int n) {
System.out.println("test2 : " + n);
}
}.test(300);
}
}
枚举
枚举 : 对象的个数是可数且是固定的类型就可以声明枚举类型
关键字enum
enum Week {
// 常量对象都是全局常量
MON, TUE, WED, THU, FRI, SAT, SUN;// 默认调用了无参构造创建枚举对象
}
枚举类的主要方法
-
values()方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。
Week[] values = Week.values();
-
valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常。
ts = TrafficSignal.valueOf(“STOP”);
枚举类的属性
- 枚举类对象可以有属性
- 若枚举类显式的定义了带参数的构造器, 则在列出枚举值时也必须对应的传入参数
enum TrafficSignal {
// 常量对象都是全局常量.
STOP(10), GO(20), CAUTION; // 默认调用了无参构造创建枚举对象
private int seconds;
private TrafficSignal(int seconds) {
this.seconds = seconds;
}
private TrafficSignal() {
}
枚举总结
-
必须在枚举类的第一行声明枚举类对象
-
枚举类和普通类的区别:
使用 enum 定义的枚举类默认继承了 java.lang.Enum 类
枚举类的构造器只能使用 private 访问控制符
枚举类的所有实例必须在枚举类中显式列出(, 分隔 ; 结尾). 列出的实例系统会自动添加 public static final 修饰
-
JDK 1.5 中可以在 switch 表达式中使用Enum定义的枚举类的对象作为表达式, case 子句可以直接使用枚举值的名字, 无需添加枚举类作为限定
代码实例
enum TrafficSignal {
// 常量对象都是全局常量.
STOP(10), GO(20), CAUTION; // 默认调用了无参构造创建枚举对象
private int seconds;
private TrafficSignal(int seconds) {
this.seconds = seconds;
}
private TrafficSignal() {
}
public int getSeconds() {
return seconds;
}
public void setSeconds(int seconds) {
this.seconds = seconds;
}
@Override
public String toString() {
return super.toString() + ", 时间 : " + seconds;
}
}
public class EnumTest {
public static void main(String[] args) {
int n = Integer.parseInt(args[0]);
//new TrafficSignal();
TrafficSignal ts = TrafficSignal.CAUTION; // ts引用变量就指向了CAUTION常量对象
System.out.println(ts);
ts = TrafficSignal.valueOf("STOP"); // 根据常量对象名获取常量对象
System.out.println(ts);
TrafficSignal[] values = TrafficSignal.values();// 返回对象数组
ts = values[n];
System.out.println(ts);
/*在TestWeek类中声明方法void printWeek(Week week),根据参数值打印相应的中文星期字符串。
然后以第2步中的枚举值调用printWeek方法,输出中文星期。
*/
switch (ts) { // switch()中的必须是变量, 且数据类型只能是非long整数, 字符串, 枚举
case STOP : // case 后面必须跟常量, 常量就是字面量和被final修饰的量.
System.out.println("红灯停");
break;
case GO :
System.out.println("绿灯行");
break;
case CAUTION :
System.out.println("黄灯慢");
break;
}
}
}
注解
注解 : 特殊的注释, 特殊在可以被编译器, 和JVM识别. 是一个修饰符
一般的注解不能修饰包和语句块
@Override
提醒编译器帮助我们作方法覆盖的条件的检查. 只能修饰method, 不能修饰field(属性), constructor(构造器), type(类)
@Deprecated
作用是提醒 使用者, 它修饰的目标是过期的, 是不推荐使用. 将来在新版本有可能真的删除它!!!
(可以修饰类, 属性, 构造器, 方法, 形参, 局部变量)
@SuppresWarnings
作用是提醒编译器, 不要警告!!!, 必须要传参
不需要传参的注解称为标记型注解
给注解进行注解的注解称为元注解. 限制注解的使用的
@Target元注解
作用是约束注解, 可以修饰的目标
ElementType.TYPE(类), FIELD(属性), METHOD(方法), CONSTRUCTOR(构造器), PARAMETER(形参)
@Retention元注解
作用是约束注解, 它可以停留在什么时期
支持3个级别的停留期:
- RetentionPolicy.SOURCE(源码) 编译生成的.class文件中是没有注解的
- RetentionPolicy.CLASS(类文件) 编译生成的.class文件中是有注解的, 但是会被类加载器忽略掉. 这是默认的
- RetentionPolicy.RUNTIME(运行时) 编译生成的.class文件中是有注解的, 并且会被类加载器加载. 通过反射处理
注解的应用是代替复杂的配置文件
自定义注解, 没有@Target约束时, 它可以修饰类, 属性, 方法, 构造器, 形参, 局部变量
/**
自定义注解
*/
@Target({ElementType.TYPE, ElementType.FIELD}) // MyAnnotation这个注解可以用在类上和属性上.
@Retention(RetentionPolicy.RUNTIME) // MyAnnotation这个注解可以反射处理.
@interface MyAnnotaion {
int id() default 10; // 属性的写法像方法.
String name() default "name缺省值";
}
异常
异常 : 程序在运行时出现的非正常状况, 会导致程序的崩溃, 应该处理, 使得程序更加 鲁棒
异常分类
1.按照程度来分 :
1) Error : 严重错误!!
2) Exception : 一般问题, 通过可以通过代码处理
2.按照处理方式来分 :
1) 受检异常 : 在程序中必须显式处理的异常, 如果不处理, 编译出错, 所以也称为编译时异常Exception及其子类(RuntimeException及其子类除外) : 一般严重问题.
2) 非受检异常 : 在程序中不是必须显式处理的异常, 如果不处理, 编译通过, 但是运行时还会出现, 也称为运行时异常
Error及其子类 太严重了
RuntimeException及其子类 太轻微了
java.lang.Throwable类为所有异常类的父类。
- 在编写异常处理程序时,不应使用Throwable类
- 应使用其不同的子类异常以表示特定的异常错误
Throwable类定义的以下方法可应用在对其各子类异常的处理上:
- public String getMessage() 获取异常信息,返回字符串
- public void printStackTrace() 获取异常类名和异常信息,以及异常出现在程序中的位置。返回值void
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xQJWt7j6-1597399140937)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1592310322116.png)]
异常的处理
1.捕获
try {
可能抛出异常的语句;
} catch (可能的异常类型1 引用) {
如果在try语句中真的出现了异常对象时, catch就起作用, 把异常对象抓住
在这个语句块中对其进行处理.
} catch (可能的异常类型2 引用) {
处理异常2
} catch (可能的异常类型n 引用) {
处理异常n
} finally { // final 最终的, 完美的, 终结的. finally是副词, 最终地, 末了地
无论前面发生什么, 都要执行.
通常在这里做 释放 资源的操作, 通常都是硬件资源, 通过OS申请到. 如果不释放会造成资源泄漏
}
三种组合
- try catch
- try catch finally
- try finally
代码实例:
public static void main2(String[] args) {
System.out.println("main begin");
boolean b = true;
if (b) {
//return;
}
try {
int n = Integer.parseInt(args[0]);
System.out.println(n);
int[] arr = null;
//System.out.println(arr.length);
if (b) {
return;
}
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println(e);
} catch (NumberFormatException e) {
System.out.println(e.getMessage());
} catch (Exception e) {
System.out.println("其他异常 : " + e);
} finally {
System.out.println("我是finally, 我一定执行...");
}
// try catch 不是保护自己, 是保护后面的代码
System.out.println("main end"); // 核心代码
}
}
2.直接抛出(方法声明)
在方法声明中使用throws 可能的异常类型列表, 提醒和警告调用者, 调用者此方法有风险.
在方法体中使用语句 throw 异常对象,方法一旦throw异常, 相当于return, 会提前结束返回.
有throw受检异常 必须要有throws警告
如果有throws异常, 但没有throw可以.
throws 是吓唬人
throw 玩真的.
代码实例:
public static int divide(int m, int n) throws DivideByZeroException {
try {
int a = m / n;
return a;
} catch (ArithmeticException e) {
throw new DivideByZeroException(e); // 包装就是对象关联, 对象关联通常是通过构造器完成.
}
}
public static void main(String[] args) {
try {
System.out.println(divide(10, 2));
System.out.println(divide(10, 0));
} catch (DivideByZeroException e) {
e.printStackTrace();
}
}
3.捕获再抛出
先捕获一个已知异常, 再把已知异常包装为自定义异常对象, 再抛出自定义异常对象.
1) 统一所有异常, 可以作一些统一操作
2) 改变原来的异常的类型, 由受检变成非受检, 或者反之.
代码实例:
public static int divide4(int m, int n) throws DivideByZeroException {
if (n == 0) {
throw new DivideByZeroException("除数为0错误!!!"); // 异常的抛出, 是一种消息传递机制
}
return m / n;
}
public static void main4(String[] args) {
try {
System.out.println(divide4(10, 2));
System.out.println(divide4(10, 0));
} catch (DivideByZeroException e) {
System.out.println(e.getMessage());
}
System.out.println("核心代码");
}
处理方式的选择 :
1) 捕获 : 方法如果抛出异常会导致栈空了, 这样的方法尽量捕获异常
2) 抛出 : 被调用方法或功能方法一定要抛出异常, 出问题时一定要让调用者知道
重写方法声明抛出异常的原则
重写方法不能抛出比被重写方法范围更大的异常类型。在多态的情况下,对methodA()方法的调用-异常的捕获按父类声明的异常处理。
public class A {
public void methodA() throws IOException {
……
} }
public class B1 extends A {
public void methodA() throws FileNotFoundException {
……
} }
public class B2 extends A {
public void methodA() throws Exception { //报错
……
} }
人工抛出异常
Java异常类对象除在程序执行过程中出现异常时由系统自动生成并抛出,也可根据需要人工创建并抛出
- 首先要生成异常类对象,然后通过throw语句实现抛出操作(提交给Java运行环境)。
IOException e = new IOException();
throw e; - 可以抛出的异常必须是Throwable或其子类的实例。下面的语句在编译时将会产生语法错误:
throw new String(“want to throw”);
创建用户自定义异常类
很多情况下,需要创建应用程序特定的异常类,以利于可维护性的需要
例如:
public class DividedByZeroException extends Exception {
public DividedByZeroException(String message) {
super(message);
}
public DividedByZeroException(Exception cause) {
super(cause);
}
}
如果方法可能抛出受检异常,那么在方法声明中必须显式指明:
public double divide(int x, int y) throws DividedByZeroException {
…
}
如果可能抛出多个异常,应使用逗号将这些异常分隔表示:
public double divide(int x, int y) throws DividedByZeroException, OtherException {…}
也可以声明抛出涵盖这些异常的父类异常:
public double divide(int x, int y) throws Exception { … }
捕获再抛出异常
一些情况下,会先捕获异常,然后将该异常包装为自定义异常,再抛出给调用者。
例如:
try {
result = x/y;
} catch (ArithmeticException e) {
throw new DividedByZeroException(e);
}
包装类Wrapper
包装类 :
把基本数据包装成对象类型
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean
装箱 :
通过包装类的构造器实现:
Integer t1 = new Integer(i); //手工装箱
Integer t2 = 500; //自动装箱
new Xxx(xxx);
Xxx.valueOf(xxx);
Xxx x = xxx;
Float f = new Float(“4.56”);
Long l = new Long(“asdf”); //NumberFormatException
拆箱 :
调用包装类的.xxxValue()方法:
boolean b = bObj.booleanValue();//手动拆箱
boolean n = bObj;//自动拆箱
ref.xxxValue();
xxx = ref;
把字符串转换成基本值
通过包装类的构造器实现:
int i = new Integer(“12”);
通过包装类的parseXxx(String s)静态方法:
Float f = Float.parseFloat(“12.1”);
Xxx.parseXxx(“xxx”);
基本数据类型转换成字符串
调用字符串重载的valueOf()方法:
String fstr = String.valueOf(2.34f);
更直接的方式:
String intStr = 5 + “”;
字符串相关类
String类
构造字符串对象
常量对象:字符串常量对象是用双引号括起的字符序列
例如:“你好”、“12.97”、"boy"等
字符串的字符使用Unicode字符编码,一个字符占两个字节
String : 是一个final类,内容不可改变的Unicode字符序列. 字符有顺序, 所以可以有下标的概念
任何的对于字符串的修改都会产生新对象
常量区 : 专门用于保存字符串字面量常量对象
字符串拼接时
如果是有变量参与拼接过程, 它的结果字符串一定在GC堆
如果参与拼接的全部都是常量, 它的结果字符串仍在常量区
String类较常用构造方法:
- String s1 = new String();
- String s2 = new String(String original);
- String s3 = new String(char[] a);
- String s4 = new String(char[] a,int startIndex,int count);
字符串转换为基本数据类型
- Integer包装类的public static int parseInt(String s):可以将由“数字”字符组成的字符串转换为整型
- 类似地,使用java.lang包中的Byte、Short、Long、Float、Double类调相应的类方法可以将由“数字”字符组成的字符串,转化为相应的基本数据类型
基本数据类型转换为字符串
- 调用String类的public String valueOf(int n)可将int型转换为字符串
- 相应的valueOf(byte b)、valueOf(long l)、valueOf(float f)、valueOf(double d)、valueOf(boolean b)可由参数的相应类到字符串的转换
字符串与字符数组常用方法
0 2 6 10 13 18 24 30 36 38
String string = " abc QQC 123我喜欢你,你喜欢我吗?我不喜欢你.xxx YYY ";
public int length()
返回字符串的长度(字符个数) string.length()
public char charAt(int index)
返回字符串中的指定下标处的字符 string.charAt(14)
public char[] toCharArray()
把字符串转换为相应的字符数组, 是内部数组的一个副本
System.arraycopy(value, 0, result, 0, value.length);
参数value 就是源数组对象, 第2个参数0 就是源数组要复制的开始下标
第3个参数result 是目标数组对象, 第4个参数0 就是目标数组要复制的开始下标
第5个参数value.length 是总共要复制的元素个数
public boolean equals(Object anObject)
比较字符串的内容
public int compareTo(String anotherString)
比较当前字符串和参数中的字符串的大小
public int indexOf(String s)
获取参数中的子串s 在当前字符串中首次出现的下标
string.indexOf(“喜欢”) => 14 , string.indexOf(“不喜欢”) => 25, string.indexOf(“很喜欢”) => -1
indexOf(String s ,int startpoint)
获取参数中的子串s 在当前字符串中首次出现的下标, 从startPoint位置开始搜索
string.indexOf(“喜欢”, 15) => 19, string.indexOf(“喜欢”, 20) => 26, string.indexOf(“喜欢”, 27) => -1
public int lastIndexOf(String s)
获取参数中的子串s 在当前字符串中首次出现的下标, 是从右向左搜索
string.lastIndexOf(“喜欢”) => 26
public int lastIndexOf(String s ,int startpoint)
获取参数中的子串s 在当前字符串中首次出现的下标, 并且从指定的startpoint从右向左
string.lastIndexOf(“喜欢”, 25) => 19, string.lastIndexOf(“喜欢”, 18) => 14, string.lastIndexOf(“喜欢”, 13) => -1
public boolean startsWith(String prefix)
判断当前字符串是否以参数中的子串为开始
string.startsWith(" abc") => true, string.startsWith(“abc”) => false
public boolean endsWith(String suffix)
判断当前字符串是否以参数中的子串为结束
string.endsWith(“xxx YYY”); string.endsWith(".mp3");
public String substring(int start,int end)
截取子串, 需要指定开始的索引和结束的索引
string.substring(13, 17) => “我喜欢你” , 包含开始索引, 不包含结束索引
子串长度 = 结束索引 - 开始索引
string.substring(13, string.length()) => 取13到后面所有
public String substring(int startpoint)
string.substring(13) => 取13到后面所有
public String replace(char oldChar,char newChar)
把当前串中的所有oldChar字符替换为newChar字符, 返回新串
子串是以正则表达式的形式工作的
public String trim()
切除首尾空白字符(码值小于等于32的字符), 返回新串
public String concat(String str) 字符串连接
public String toUpperCase() 把字符串所有小写变成大写字母
public String toLowerCase() 把所写大写变成小写
public String[] split(String regex) 把字符串以参数中的子串为切割器, 切割成多个部分
StringBuilder类StringBuffer类
String:不可变字符序列
StringBuffer:可变字符序列、效率低、线程安全
StringBuilder(JDK1.5):可变字符序列、效率高、线程不安全
- java.lang.StringBuffer代表可变的字符序列,可以对字符串内容进行增删
- 很多方法与String相同,但StingBuffer是可变长度的
- StringBuffer是一个容器
StringBuffer类有三个构造方法:
- StringBuffer()初始容量为16的字符串缓冲区
- StringBuffer(int size)构造指定容量的字符串缓冲区
- StringBuffer(String str)将内容初始化为指定字符串内容
String s = new String(“我喜欢学习”);
StringBuffer buffer = new StringBuffer(“我喜欢学习”);
buffer.append(“数学”);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rkp829Bx-1597399140948)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1592396917056.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LaLAoYv7-1597399140950)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1592396926575.png)]
StringBuilder
内容可以改变的Unicode字符序列. 像Student对象. setge(20), 对象的内部属性值就发生了变化
StringBuilder append(…)
追加任意数据到当前字符串末尾, 当前字符串内容就会变化成新的. 和字符串的+操作是等效的
StringBuilder insert(int index, …)
在参数index位置处插入任意数据, 当前字符串也会变化
StringBuilder reverse() 反转
StringBuilder delete(int begin, int end)
从当前串中删除指定区间, begin位置包括, 不包括end位置
void setCharAt(int index, char newChar)
替换指定下标处的字符为新字符
基于数组的处理, 会扩容, 也会自动插入, 内部有计数器控制
扩容 : 老容量 * 2 + 2
日期类
java.lang.System类
System类提供的public static long currentTimeMillis()用来返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差
此方法适于计算时间差
计算世界时间的主要标准有:
- UTC(Universal Time Coordinated)
- GMT(Greenwich Mean Time)
- CST(Central Standard Time)
java.util.Date类
表示特定的瞬间,精确到毫秒
构造方法:
Date( )使用Date类的无参数构造方法创建的对象可以获取本地当前时间
Date(long date)
常用方法
getTime():返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数
toString():把此 Date 对象转换为以下形式的 String: dow mon dd hh:mm:ss zzz yyyy 其中: dow 是一周中的某一天 (Sun, Mon, Tue, Wed, Thu, Fri, Sat),zzz是时间标准
java.util.Calendar(日历)类
Calendar是一个抽象基类,主用用于完成日期字段之间相互操作的功能
获取Calendar实例的方法
- 使用Calendar.getInstance()方法
- 调用它的子类GregorianCalendar的构造器
一个Calendar的实例是系统时间的抽象表示,通过get(int field)方法来取得想要的时间信息。比如YEAR、MONTH、DAY_OF_WEEK、HOUR_OF_DAY 、MINUTE、SECOND
public void set(int field,int value)
public void add(int field,int amount)
public final Date getTime()
public final void setTime(Date date)
总结
- Calendar 内部用数组保存数据, 操作不便, 因为要处理下标
- 月份比实际小1
- calendar 最大的问题是不能保持原有的值, 内容变化后无法还原
代码实例
package com.atguigu.javase.common;
import org.junit.Test;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateTimeTest {
@Test
public void test1() {
long time = System.currentTimeMillis(); // 距离1970-01-01 00:00:00.000 到此方法执行瞬间经过的毫秒数
System.out.println(time);
Date date = new Date();
System.out.println(date);
//SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S E a z");
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String format = simpleDateFormat.format(date);
System.out.println(format);
String str = "2008-08-08 10:20:30"; // 格式一定要匹配
try {
Date parse = simpleDateFormat.parse(str);
System.out.println(parse);
} catch (ParseException e) {
e.printStackTrace();
}
String format1 = simpleDateFormat.format(time);// 格式化毫秒
System.out.println(format1);
}
}
@Test
public void test2() {
//Calendar calendar = new Calendar();
Calendar calendar = Calendar.getInstance();
System.out.println(calendar); // 内容很多
//calendar.getYear();
int year = calendar.get(Calendar.YEAR);
System.out.println(year);
int month = calendar.get(Calendar.MONTH);
System.out.println(month); // 存储的比实际要小1.
int day = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(day);
//calendar.setYear(2008);
// 设置成北京奥运会 2008,8, 8
calendar.set(Calendar.YEAR, 2008); // 设置年
calendar.set(Calendar.MONTH, 7);
calendar.set(Calendar.DAY_OF_MONTH, 8);
System.out.println(calendar.getTime());
calendar.add(Calendar.MONTH, 3); // 奥运会后3个月
calendar.add(Calendar.DAY_OF_MONTH, -1000); // 奥动会3个月后,又1000天以前
System.out.println(calendar.getTime());
}
@Test
public void test1() {
Date date = new Date(2008 - 1900, 8 - 1 , 8); // 1900. 1
System.out.println(date);
}
新时间日期API
Java 的日期与时间 API 问题由来已久,Java 8 之前的版本中关于时间、日期及其他时间日期格式化类由于线程安全、重量级、序列化成本高等问题而饱受批评。Java 8 吸收了 Joda-Time 的精华,以一个新的开始为 Java 创建优秀的 API。新的 java.time 中包含了所有关于时钟(Clock),本地日期(LocalDate)、本地时间(LocalTime)、本地日期时间(LocalDateTime)、时区(ZonedDateTime)和持续时间(Duration)的类。历史悠久的 Date 类新增了 toInstant() 方法,用于把 Date 转换成新的表示形式。这些新增的本地化时间日期 API 大大简化了了日期时间和本地化的管理。
LocalDate
LocalTime
LocalDateTime 内部关联了一个LocalDate对象和一个LocalTime
DateTimeFormatter
代码示例
@Test
public void test4() {
LocalDate date = LocalDate.now(); //new LocalDate();
System.out.println(date);
int year = date.getYear();
int month = date.getMonthValue();
int day = date.getDayOfMonth();
System.out.println(year);
System.out.println(month);
System.out.println(day);
LocalDate date2 = date.withYear(2008).withMonth(8).withDayOfMonth(8);
System.out.println(date2);
// 奥运会500天以后
LocalDate date3 = date2.plusDays(500); // date2.minusDays(500)
System.out.println(date3);
}
Math类
java.lang.Math提供了一系列静态方法用于科学计算;其方法的参数和返回值类型一般为double型。
abs 绝对值
acos,asin,atan,cos,sin,tan 三角函数
sqrt 平方根
pow(double a,doble b) a的b次幂
log 自然对数
exp e为底指数
max(double a,double b)
min(double a,double b)
random() 返回0.0到1.0的随机数
long round(double a) double型数据a转换为long型(四舍五入)
toDegrees(double angrad) 弧度—>角度
toRadians(double angdeg) 角度—>弧度
代码示例
public class MathTest {
@Test
public void test1() {
double random = Math.random();
System.out.println(random);
int n = (int)(Math.random() * 100);
System.out.println(n);
System.out.println(Math.round(3.5));
System.out.println(Math.round(-3.5));
System.out.println(Math.ceil(3.2));
System.out.println(Math.floor(3.8));
}
}
BigInteger类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YpStWgux-1597399140952)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1592564602829.png)]
@Test
public void test2() {
BigInteger bi1 = new BigInteger("239842394238742837429837429837429837423423397298374");
BigInteger bi2 = new BigInteger("32908238293882339289383289382983948273489237498235093480958340958039");
BigInteger multiply = bi1.multiply(bi2);
System.out.println(multiply);
}
BigDecimal类
一般的Float类和Double类可以用来做科学计算或工程计算,但在商业计算中,要求数字精度比较高,故用到java.math.BigDecimal类。BigDecimal类支持任何精度的定点数。
构造器
public BigDecimal(double val)
public BigDecimal(String val)
常用方法
public BigDecimal add(BigDecimal augend)
public BigDecimal subtract(BigDecimal subtrahend)
public BigDecimal multiply(BigDecimal multiplicand)
public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)
代码示例
@Test
public void test3() {
// 有理数
BigDecimal bd1 = new BigDecimal("2349823894723894723894728973489234234234.29384238947289374289374982374982374982634872364782634782364234");
BigDecimal bd2 = new BigDecimal("2349234234234234234234234234823894723894723894728973489234234234.29384345354344334344238947289374289374982374982374982634872364782634782364234");
BigDecimal add = bd1.add(bd2);
System.out.println(add);
}
JAVA集合
数组 : 解决批量数据的存储问题, 长度和类型都不可以变化
集合 : 解决批量对象的存储问题. 长度和类型是可以变化的
把集合可以简单的看作是一个可变长度的Object[]
Java 集合可分为 Collection 和 Map 两种体系
Collection接口:表示不按添加顺序存放对象的集合,集合内元素可以重复,即“无序可重复”集合
Set:元素无序、不可重复的集合 —类似高中的“集合”
List:元素有序,可重复的集合 —”动态”数组
Map接口:具有映射关系“key-value对”的集合 Map : 保存一对一对的对象
Collection 接口
Collection 接口是 List、Set 和 Queue 接口的父接口,该接口里定义的方法既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合
JDK不提供此接口的任何直接实现,而是提供更具体的子接口(如:Set和List)实现
在 Java5 之前,Java 集合会丢失容器中所有对象的数据类型,把所有对象都当成 Object 类型处理;从 Java5 增加了泛型以后,Java 集合可以记住容器中对象的数据类型
Collection : 保存一个一个的对象. 无序可重复
无序 : 不按添加顺序放元素,但是它内部也一定是有规则的. 可重复 : 相同内容的对象可以多次添加
boolean add(Object obj) ; // 添加参数中的对象到当前集合容器中.
boolean contains(Object obj) ; // 判断当前集合中是否包含了参数中指定的对象
boolean remove(Object obj); // 从当前集合中删除参数中指定的对象
int size(); // 获取当前集合中的元素个数
foreach循环也称为增强型for循环
小总结:
Collection : 解决批量对象的存储问题
Set : 无序不可重复
HashSet : 基于数组使用哈希实现的Set集合, 称为散列表.
插入,删除,检索都是最快. 唯一缺点就是对内存要求高.
适用场景 : 只要内存够, 选我没错
TreeSet : 基于二叉搜索树(红黑树)实现的Set集合
优点 : 对内存要求低, 检索性能极高, 使用二分法原理.
缺点 : 插入和删除操作速度慢, 定位位置, 还有`可能要旋转
适用场景 : 排序和搜索比较多, 内存小
List : 有序可重复
ArrayList : 基于数组实现的List集合
优点 : 检索效率高, 遍历速度快, 末端插入删除数据是最快的.
缺点 : 要求内存连续,对内存要求高, 非末端插入或删除数据最慢的, 因为有大量元素的移动.
适用场景 : 适用于保存存档数据, 检索较多.
LinkedList : 基于链表实现的List集合
优点 : 对内存要求低, 插入或删除数据极快.
缺点 : 检索效率低, 遍历速度慢.
适用场景 : 频繁地修改元素, 检索少. 通常用于内存管理
Set 接口
Set接口是Collection的子接口,set接口没有提供额外的方法
Set 集合不允许包含相同的元素,如果试把两个相同的元素加入同一个 Set 集合中,则添加操作失败。
Set 判断两个对象是否相同不是使用 == 运算符,而是根据 equals 方法
Set 子接口 : 无序不可重复
HashSet : 使用哈希算法实现的Set集合
去重规则 :两个对象的equals为true, 并且两个对象的hashCode也相等
TreeSet : 基于二叉树实现的Set集合
HashSet
HashSet 是 Set 接口的典型实现,大多数时候使用 Set 集合时都使用这个实现类
HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取和查找性能
HashSet 具有以下特点:
- 不能保证元素的排列顺序
- HashSet 不是线程安全的
- 集合元素可以是 null
当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法来得到该对象的 hashCode 值,然后根据 hashCode 值决定该对象在 HashSet 中的存储位置
HashSet 集合判断两个元素相等的标准:两个对象通过 hashCode() 方法比较相等,并且两个对象的 equals() 方法返回值也相等
代码示例
// 2) 保存10个20以内的随机整数, 必须10个. 遍历集合
@Test
public void exer12() {
Set set = new HashSet(); // set.size() == 0
while (set.size() < 10) {
set.add((int)(Math.random() * 20));
}
System.out.println(set);
}
TreeSet
List接口
Java中数组用来存储数据的局限性
List集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引
List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
JDK API中List接口的实现类常用的有:ArrayList、LinkedList和Vector
List 集合里添加了一些根据索引来操作集合元素的方法
void add(int index, Object ele)
boolean addAll(int index, Collection eles)
Object get(int index)
int indexOf(Object obj)
int lastIndexOf(Object obj)
Object remove(int index)
Object set(int index, Object ele)
List subList(int fromIndex, int toIndex)
ArrayList
ArrayList 是 List 接口的典型实现类
ArrayList : 基于数组实现的List集合
本质上,ArrayList是对象引用的一个变长数组
ArrayList 是线程不安全的,而 Vector 是线程安全的,即使为保证 List 集合线程安全,也不推荐使用Vector
Arrays.asList(…) 方法返回的 List 集合既不是 ArrayList 实例,也不是 Vector 实例。
Arrays.asList(…) 返回值是一个固定长度的 List 集合
代码示例
// 创建List集合, 保存10个20以内的随机整数, 不要重复的.
@Test
public void exer22() {
List list = new ArrayList();
for (int i = 0; i < 10; i++) {
int n = (int)(Math.random() * 20);
if (list.contains(n)) {
i--;
continue;
}
list.add(n);
}
System.out.println(list);
}
@Test
public void test5() {
Set set = new HashSet();
Student s1 = new Student(1, "小明", 2, 90);
Student s2 = new Student(2, "小丽", 3, 80);
Student s3 = new Student(1, "小明", 2, 90);
set.add(s1);
set.add(s2);
set.add(s3); // 不能去重, 要想去重, 必须重写equals和hashCode
set.add(s1); // 这个无条件去重, 已经添加过s1了.
set.add(300);
set.add(new Integer(300));
System.out.println(s1.equals(s3));
for (Object tmp : set) {
System.out.println(tmp);
}
}
LinkedList
对于频繁的插入或删除元素的操作,建议使用LinkedList类,效率较高
LinkedList : 基于链表实现的List集合
新增方法:
void addFirst(Object obj)
void addLast(Object obj)
Object getFirst()
Object getLast()
Object removeFirst()
Object removeLast()
@Test
public void work1() {
/*创建一个Set集合, 保存10个20以内的随机整数
把Set中的所有数保存在另一个List集合中
尝试对List集合中的数据排序*/
Set set = new HashSet();
for (int i = 0; i < 10; i++) {
set.add((int)(Math.random() * 20));
}
System.out.println(set);
List list = new LinkedList(set); // 创建集合时可以直接以另一个集合为参数, 初始元素就是参数中集合的元素
/*
for (Object obj : set) {
list.add(obj);
}*/
//list.addAll(set); // addAll就是直接把另一个集合中的所有数据全部添加到list中.
//System.out.println(list);
// 遍历List集合 : 1) 增强for, 2) 经典for, 3) 迭代器
// 尝试对List排序
for (int i = 0; i < list.size() - 1; i++) {
for (int j = 0; j < list.size() - 1 - i; j++) {
if (((Comparable)list.get(j)).compareTo(list.get(j + 1)) > 0) {
Object tmp = list.set(j, list.get(j + 1));
list.set(j + 1, tmp);
}
}
}
System.out.println(list);
}
Map接口
Map : 保存一对一对的对象. 具有映射关系的键对象和值对象
键到值 是单向一对一的映射. 通过键只能找到唯一的一个值对象. 单向 : 只能从键找值, 不可以从值找键
本质上来讲Map集合暗含了2个子集合, 一个是保存所有键对象的Set子集合(无序不可重复)
另一个是保存所有值对象的Collection子集合(无序可重复)
Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value
Map 中的 key 和 value 都可以是任何引用类型的数据
Map 中的 key 用Set来存放,不允许重复,即同一个 Map 对象所对应的类,须重写hashCode()和equals()方法
key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到唯一的、确定的 value
-
Object put(Object key, Object value); // 写入词条
-
Object remove(Object key) ; // 删除给定键, 并同时会把值对象也删除.
-
Object get(Object key); // 根据参数指定的键对象, 找到对应的值对象, 查词典
-
int size(); 条目个数
-
Set keySet() 获取保存所有键对象的Set子集合.
-
Set entrySet() 获取保存所有键和值组合的条目对象 的Set集合
-
HashMap : 使用哈希算法实现的Map集合, 主要针对的是键对象而言(Hashtable 和HashMap一样, 但是速度慢, 尽量避免使用)
-
Properties 主要专门处理属性文件(配置文件)
-
TreeMap : 基于二叉搜索树实现的Map集合, 主要针对的是键对象而言
代码示例
/*编写程序,在main方法中创建Map集合(使用泛型),用来存放圆的半径(key)和面积(value);
以半径为key,面积为value,将半径1-50的圆面积数据(直接取整)保存其中;
将Map中的半径数据取至Set集合中;
遍历Set集合的半径,逐一从Map中取出对应的面积值,并将半径和面积打印出来。*/
@Test
public void exer() {
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for (int i = 1; i < 51; i++) {
int area = (int)(Math.PI * i * i);
map.put(i, area);
}
// 遍历
Set<Integer> keys = map.keySet();
Iterator<Integer> iterator = keys.iterator();
while (iterator.hasNext()) {
Integer key = iterator.next();
Integer value = map.get(key);
System.out.println(key + " >>>>>>>>>>>>>>>>>>>>>>>>>> " + value);
}
}
@Test
public void test1() {
// <Integer是键的类型, String是值的类型>
Map<Integer, String> map = new HashMap<Integer, String>(); // 空词典
map.put(3, "three"); // 写入词条
map.put(9, "nine");
map.put(2, "two");
map.put(1, "one");
map.put(4, "four");
map.put(10, "ten");
map.put(9, "NINE"); // 键无序不可重复. 但是值会刷新为新值
System.out.println(map.size()); // 条目个数
String val = map.get(40); // 查词典
System.out.println(val);
System.out.println(map);
map.remove(2); // 删除键对象2的同时, 也会删除值对象
System.out.println(map);
// 拿到保存所有键对象的Set子集合
Set<Integer> keys = map.keySet();
Iterator<Integer> iterator = keys.iterator();
while (iterator.hasNext()) {
Integer key = iterator.next();
String value = map.get(key);
System.out.println(key + " >>>>>>>>>>>>>>> " + value);
}
System.out.println("********************************************");
// 拿到保存所有条目对象的Set集合
/*
Set entries = map.entrySet(); // 如果不用泛型, 处理不方便
for (Object tmp : entries) {
System.out.println(tmp);
}*/
Set<Map.Entry<Integer, String>> entries = map.entrySet();
for (Map.Entry<Integer, String> tmp : entries) {
//System.out.println(tmp);
System.out.println(tmp.getKey() + " <<<<<<<<<<<<<<<<<<<<<<<< " + tmp.getValue());
}
}
Properties代码示例
@Test
public void test2() throws IOException {
Properties properties = new Properties();
// 一旦load, 它会自动把文件中的所有键 - 值对 put到当前map中
properties.load(new FileInputStream("test.properties"));
String user = properties.getProperty("user");
System.out.println(user);
String url = properties.getProperty("url");
System.out.println(url);
String hello = properties.getProperty("hello");
System.out.println(hello);
properties.setProperty("hello", "world");
}
Iterator对象(迭代器)
Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素
所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象
Iterator 仅用于遍历集合,Iterator 本身并不提供承装对象的能力。如果需要创建 Iterator 对象,则必须有一个被迭代的集合
在调用it.next()方法之前必须要调用it.hasNext()进行检测。若不调用,且下一条记录无效,直接调用it.next()会抛出NoSuchElementException异常
1) 向集体要迭代器
Iterator iterator = set.iterator(); // 注意事项1 : 迭代器的使用必须是新鲜的, 有热气的.
//set.add("abc"); // 内部被污染. 迭代器就不新鲜
// 2) 不断询问迭代器内部指针的后面是否有下一个元素, 直到内部指针的后面没有元素为止
while (iterator.hasNext()) {
// 3) 如果有下一个元素, 获取到下一个元素, 获取完后, 内部指针要下移
Object next = iterator.next(); // 注意事项2 : 必须和hashNext一一对应. next()方法在循环中必须只能调用一次
System.out.println(next);
}
// 迭代器的使用是一次性的
Comparator自定义比较器
class StudentComparator implements Comparator {
@Override
public int compare(Object o1, Object o2) throws RuntimeException {
if (!(o1 instanceof Student) || !(o2 instanceof Student)) {
throw new RuntimeException("对象不可比");
}
return ((Student)o1).getGrade() - ((Student)o2).getGrade();
}
}
class MyComparator implements Comparator {
@Override
public int compare(Object o1, Object o2) { // 理论上可以比较任意对象.
int n = (int)(((Person)o1).getWeight() * 100 - ((Person)o2).getWeight() * 100);
if (n == 0) {
n = 1;
}
return n;
}
}
@Test
public void exer2() {
// 实现定制排序
Comparator comparator = new MyComparator();
Set set = new TreeSet(comparator);
set.add(new Person("张三", 30, 80));
set.add(new Person("李四", 20, 90));
set.add(new Person("王五", 30, 30));
set.add(new Person("王六", 40, 30));
for (Object obj : set) {
System.out.println(obj);
}
}
@Test
public void exer3() {
// 实现定制排序, 匿名内部类对象.
Set set = new TreeSet(new Comparator() {
@Override// 类体就是接口的实现子类的类体
public int compare(Object o1, Object o2) {
return ((Person) o1).getName().compareTo(((Person) o2).getName());}
});
set.add(new Person("张三", 30, 80));
set.add(new Person("李四", 20, 90));
set.add(new Person("王五", 30, 30));
set.add(new Person("jack", 25, 50));
set.add(new Person("jack", 25, 50));
for (Object obj : set) {
System.out.println(obj);
}
}
操作集合的工具类:Collections
Collection是接口, 加上s后,就变成了工具类
Arrays也是工具类, Files, Paths
Collections 是一个操作 Set、List 和 Map 等集合的工具类
Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法
排序操作:(均为static方法)
- reverse(List):反转 List 中元素的顺序
- shuffle(List):对 List 集合元素进行随机排序
- sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
- sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
- swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
- Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
- Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
- Object min(Collection)
- Object min(Collection,Comparator)
- int frequency(Collection,Object):返回指定集合中指定元素的出现次数
- void copy(List dest,List src):将src中的内容复制到dest中
- boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
代码示例
@Test
public void test1() {
List<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < 10; i++) {
list.add((int)(Math.random() * 20));
}
System.out.println(list);
Collections.reverse(list); // 反转List集合.
System.out.println(list);
Collections.sort(list); // 排序
System.out.println(list);
Collections.reverse(list);
System.out.println(list);
Collections.shuffle(list); // 洗牌
System.out.println(list);
Integer max = Collections.max(list);
System.out.println("max = " + max);
}
泛型(Generic)
泛型 : 解决类型安全问题, 使得类型不再模糊, 不再不确定
集合<泛型类型> 引用 = new 集合<泛型类型>()
比如在集合中, 如果不用泛型 , 它可以保存任意类型的元素, 类型不安全
获取元素时, 元素也是Object类型. 要想访问特有成员, 必须造型, 造型有风险, 必须要判断
集合中只能保存 泛型类型 指定的类型的对象, 其他都不行
从集合中获取元素时, 一定只能获取到 泛型类型 的对象
注意:静态方法中不可使用泛型类型
泛型的声明
interface List 和 class TestGen<K,V>
其中,T,K,V不代表值,而是表示类型。这里使用任意字母都可以。常用T表示,是Type的缩写
泛型的实例化
一定要在类名后面指定类型参数的值(类型)。如:
List strList = new ArrayList();
Iterator iterator = customers.iterator();
T只能是类,不能用基本数据类型填充
泛型类
1.对象实例化时不指定泛型,默认为:Object。
2.泛型不同的引用不能相互赋值。
3.加入集合中的对象类型必须与指定的泛型类型一致
4.静态方法中不能使用类的泛型。
5.如果泛型类是一个接口或抽象类,则不可创建泛型类的对象。
6.不能在catch中使用泛型
7.从泛型类派生子类,泛型类型可以具体化
自定义泛型
//自定义泛型类
class Person<X> { // X表示这是在当前类中的一个临时的类型参数(形参), X代表某类型.
// 在创建对象时, 由调用者来决定它的真正类型.
private String name;
private X info;
public Person() {
}
public Person(String name, X info) {
this.name = name;
this.info = info;
}
}
class GenericTest{
@Test
public void test(){
Person<String> p1 = new Person<String>("小丽","赢了");
String info1 = p1.getInfo();
Person<Integer> p2 = new Person<Integer>("小王",95);
Integer info2 = p2.getInfo();
Person p3 = new Person("小明",true);
Object info3 = p3.getInfo();
}
}
泛型方法
方法,也可以被泛型化,不管此时定义在其中的类是不是泛型化的。在泛型方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型
泛型方法的格式:
[访问权限] <泛型> 返回类型 方法名([泛型标识 参数名称]) 抛出的异常
class GenericMethod {
// 泛型方法, 在返回值类型左侧使用泛型声明
// 泛型方法必须要有泛型类型参数, 否则永远无法知道具体类型. 因为在调用时实参会携带类型信息.
// 泛型类型X是和方法的某次调用相关.
public <X> X getGeneric(X x) {
return x;
}
public static <Y> Y getGeneric2(Y y) {
return y;
}
public void test2() {
//X x = null;
}
}
泛型和继承的关系
如果B是A的一个子类型(子类或者子接口),而G是具有泛型声明的类或接口,G并不是G的子类型
比如:String是Object的子类,但是List并不是List的子类
public void testGenericAndSubClass() {
// 在泛型的集合上
List<Person> personList = null;
List<Man> manList = null;
// personList = manList;(报错)
}
通配符
1.使用类型通配符:?
比如:List<?> ,Map<?,?>
List<?>是List、List等各种泛型List的父类。
2.读取List<?>的对象list中的元素时,永远是安全的,因为不管list的真实类型是什么,它包含的都是Object。
3.写入list中的元素时,不行。因为我们不知道c的元素类型,我们不能向其中添加对象。
唯一的例外是null,它是所有类型的成员。
将任意元素加入到其中不是类型安全的:
Collection<?> c = new ArrayList();
c.add(new Object()); // 编译时错误
因为我们不知道c的元素类型,我们不能向其中添加对象。
add方法有类型参数E作为集合的元素类型。我们传给add的任何参数都必须是一个未知类型的子类。因为我们不知道那是什么类型,所以我们无法传任何东西进去。
唯一的例外的是null,它是所有类型的成员。
另一方面,我们可以调用get()方法并使用其返回值。返回值是一个未知的类型,但是我们知道,它总是一个Object
public static void main(String[] args) {
List<?> list = null;
list = new ArrayList<String>();
list = new ArrayList<Double>();
//list.add(3);
list.add(null);
List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
l1.add(“尚硅谷");
l2.add(15);
read(l1);
read(l2); }
static void read(List<?> list){
for(Object o : list){
System.out.println(o);
} }
有限制的通配符
<?>允许所有泛型的引用调用
举例:
System.out.println(clazz3 == clazz4);
}
## 类加载器ClassLoader
类加载器是用来把类(class)装载进内存的。JVM 规范定义了两种类型的类加载器:启动类加载器(bootstrap)和用户自定义加载器(user-defined class loader)。
**类加载使用的是双亲委派机制 :** 不可以写自定类, 也叫核心类, 不能替换核心类.
**通过系统类加载器加载Teacher类.**
把加载任务委派给扩展类加载器类, 扩展类加载器再把加载任务委派给引导类加载器.
引导类加载器发现这个类不是核心类, 就把加载任务驳回给扩展类加载器
扩展类加载器发现这个类不是我该加载的, 也会把任务驳回给系统类加载器
系统类加载器发现加载任务都被驳回, 自己加载这个类Teacher
**通过系统类加载器加载HashMap类**.
把加载任务委派给扩展类加载器类, 扩展类加载器再把加载任务委派给引导类加载器.
引导类加载器发现这个类是核心类, 当仁不让, 它会加载此类
扩展类加载器发现这个类已经加载了, 就不加载, 返回
系统类加载器发现这个类已经加载了, 就不加载.
```java
@Test
public void test7() {
// 类加载器的3个层次
// 最底层的是Bootstrap ClassLoader引导类加载器, 负责加载jdk/jre/lib目录下所有.jar
// 上面的是Extention ClassLoader 扩展类加载器, 负责加载jdk/jar/lib/ext目录下的所有.jar
// 最上层是System ClassLoader 系统类加载器(应用程序类加载器) 负责加载项目的classpath中所有类的加载
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
ClassLoader extentionClassLoader = systemClassLoader.getParent();
System.out.println(extentionClassLoader);
ClassLoader bootstrapClassLoader = extentionClassLoader.getParent();
System.out.println(bootstrapClassLoader); // 引导类加载器无法获取.
}
通过类加载器创建对象
@Test
public void test8() {
try {
Class clazz = Class.forName("com.atguigu.javase.reflect.Teacher");
//Object obj = clazz.newInstance();
// public Teacher(String name, int age, String gender)
// 1) 先获取有参构造器对象
// 需要的是形式参数类型(类模板对象)列表
Constructor constructor = clazz.getConstructor(String.class, int.class, String.class);
// 2) 再反射创建
// 需要的是实参值列表
Object obj = constructor.newInstance("佟刚", 40, "男"); // new Teacher("佟刚", 40, "男");
System.out.println(obj);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
创建类对象并获取类的完整结构
创建类的对象:
调用Class对象的newInstance()方法
要 求:
1)类必须有一个无参数的构造器。
2)类的构造器的访问权限需要足够。
// 无参构造器创建对象
Constructor constructor1 = clazz.getConstructor();
Object obj2 = constructor1.newInstance();
System.out.println(obj2);
没有无参构造器时:
1)通过Class类的getDeclaredConstructor(Class … parameterTypes)取得本类的指定形参类型的构造器
2)向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。
// 全参构造器创建对象
Constructor constructor2 = clazz.getConstructor(String.class, int.class, String.class);// 需要类模板对象的列表, 形参数据类型列表
Object obj3 = constructor2.newInstance("芳芳", 20, "女");
System.out.println(obj3);
获取方法:
// 获取方法对象
Method lessonMethod = clazz.getMethod("lesson", String.class, int.class); // 方法名, 形参类型列表
Object retValue = lessonMethod.invoke(obj3, "web", 101); // 实参列表
System.out.println(retValue); // 如果方法没有返回值时, 它会返回null.
// 调用私有方法
Method testMethod = clazz.getDeclaredMethod("test");
testMethod.setAccessible(true);
testMethod.invoke(obj3);
通过反射调用类的完整结构
1.实现的全部接口
public Class<?>[] getInterfaces()
确定此对象所表示的类或接口实现的接口。
2.所继承的父类
public Class<? Super T> getSuperclass()
返回表示此 Class 所表示的实体(类、接口、基本类型)的父类的 Class。
3.全部的构造器
public Constructor[] getConstructors()
返回此 Class 对象所表示的类的所有public构造方法。
public Constructor[] getDeclaredConstructors()
返回此 Class 对象表示的类声明的所有构造方法。
Constructor类中:
取得修饰符: public int getModifiers();
取得方法名称: public String getName();
取得参数的类型:public Class<?>[] getParameterTypes();
4.全部的方法
public Method[] getDeclaredMethods()
返回此Class对象所表示的类或接口的全部方法
public Method[] getMethods()
返回此Class对象所表示的类或接口的public的方法
Method类中:
public Class<?> getReturnType()取得返回值类型
public Class<?>[] getParameterTypes()取得全部的参数类型
public int getModifiers()取得修饰符
public Class<?>[] getExceptionTypes()取得所有抛出的异常类型
5.全部的Field
public Field[] getFields()
返回此Class对象所表示的类或接口的public的Field。
public Field[] getDeclaredFields()
返回此Class对象所表示的类或接口的全部Field。
// 访问属性
//Field nameField = clazz.getField("name"); // 只能获取公共的属性, 包括本类和父类.
Field nameField = clazz.getDeclaredField("name"); // 获取本类中声明的属性
System.out.println(nameField);
nameField.setAccessible(true);
nameField.set(obj, "佟刚");
Field方法中:
public int getModifiers() 以整数形式返回此Field的修饰符
public Class<?> getType() 得到Field的属性类型
public String getName() 返回Field的名称。
6.Annotation相关
get Annotation(Class annotationClass)
getDeclaredAnnotations()
// 获取注解
Annotation annotation = clazz.getAnnotation(MyAnnotation.class);
System.out.println(annotation);
7.泛型相关
获取父类泛型类型:Type getGenericSuperclass()
泛型类型:ParameterizedType
获取实际的泛型类型参数数组:getActualTypeArguments()
8.类所在的包 Package getPackage()
Lambda 表达式
Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
Lambda 表达式语法
Lambda 表达式:在Java 8 语言中引入的一种新的语法元素和操作符。这个操作符为 “->” , 该操作符被称为 Lambda 操作符或箭头操作符。它将 Lambda 分为两个部分:
左侧:指定了 Lambda 表达式需要的参数列表
右侧:指定了 Lambda 体,是抽象方法的实现逻辑,也即 Lambda 表达式要执行的功能。
前提 : 类型推断. 就可以省略一些东西
lambda省略匿名内部类写法中的new 接口() {}, 只剩下里面的方法.
方法签名中的修饰符, 返回值, 方法名也可以省略, 只保留参数列表
并且参数列表中的数据类型省略.
如果参数只有一个, ()也省略它
如果方法体中只有一行语句, 方法体的{} 省略
如果语句是return 可以省略return
lambda表达式中的适用的接口中必须只能有一个抽象方法, 而且在Lambda体中也尽量只有一条语句.
方法 : 也称函数, y=f(x), y=10x; x是参数, y是结果. x是输入, y是输出
lambda的目标 是让我们聚集函数的本质, 有输入和输出. 无形中把函数提升到对象级别的高度
代码示例:
@Test
public void test1() {
List<Integer> list = new ArrayList<>(); // 因为左面的泛型和右面的泛型必须一致, 编译器进行了推断.
I2 i21 = new I2() {
@Override
public int test(String str) {
return 0;
}
};
// 基于推断, 接口, 方法名等省略掉.
I2 i22 = (String str) -> {return 0;};
System.out.println(i21.test("abc"));
System.out.println(i22.test("abc"));
}
简化后代码示例:
@Test
public void test5() {
I4 i41 = new I4() {
@Override
public String test4(String n, int m) {
return n + m;
}
};
I4 i42 = (n, m) -> n + m;
System.out.println(i41.test4("abc", 300));
System.out.println(i42.test4("abc", 300));
}
函数式(Functional)接口
只包含一个抽象方法的接口,称为函数式接口。
你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)。
我们可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。
Comparator 比较器, 它的作用是比较两个T类型的对象, 返回整数, 如果左大右小返回正数, 左小右大返回负数,相等返回0
int compare(T o1, T o2); // 有参有返回
Java 内置四大核心函数式接口
Consumer 消费器, 它的作用就是消费一个T类型的对象, 并没有返回
void accept(T t); // 有参无返回, 有输入 没输出
// 写一个消费器, 消费一个Student对象.
@Test
public void exer1() {
Student s = new Student(1, "小明", 2, 80);
Consumer<Student> c1 = new Consumer<Student>() {
@Override
public void accept(Student student) {
System.out.println(student);
}
};
Consumer<Student> c2 = t -> System.out.println(t);
c1.accept(s);
c2.accept(s);
}
Supplier 供给器, 它的作用就是用供给一个T类型的对象. 不需要参数
T get(); // 无参有返回, 无输入, 有输出
@Test
public void test2() {
Supplier<Double> supplier1 = new Supplier<Double>() {
@Override
public Double get() {
return Math.random();
}
};
//Supplier<Double> supplier2 = () -> Math.random(); // Lambda体中调用的方法random()和接口中的get()方法模式一致. 无参有返回
Supplier<Double> supplier2 = Math::random; // Lambda体中调用的方法random()和接口中的get()方法模式一致. 无参有返回
System.out.println(supplier1.get());
System.out.println(supplier2.get());
}
Function<T, R> 转换器, 它的作用是传入一个T类型的对象, 转换成R类型的对象并返回.
R apply(T t); // 有参有返回, 有输入有输出
// 写一个转换器,把学生对象转换成Double对象.
@Test
public void exer3() {
Student s = new Student(1, "小明", 3, 90);
Function<Student, Double> f1 = new Function<Student, Double>() {
@Override
public Double apply(Student student) {
return student.getScore();
}
};
Function<Student, Double> f2 = t -> t.getScore();
System.out.println(f1.apply(s));
System.out.println(f2.apply(s));
}
Predicate 判定器, 它的作用是传入一个T类型的对象, 经过某种判断结果是真或假
boolean test(T t); // 有参有返回, 返回固定是boolean, 有输入有输出.
// 写个判定器, 判断一个学生对象是否及格.
@Test
public void exer4() {
Student s = new Student(1, "小明", 3, 90);
Predicate<Student> predicate1 = new Predicate<Student>() {
@Override
public boolean test(Student student) {
return student.getScore() >= 60;
}
};
Predicate<Student> predicate2 = t -> t.getScore() >= 60;
System.out.println(predicate1.test(s));
System.out.println(predicate2.test(s));
}
方法引用
lambda体中的调用的方法和接口中的方法的模式一致时, 就可以使用方法引用.
方法引用:使用操作符 “::” 将类(或对象) 与 方法名分隔开来。
如下三种主要使用情况:
对象::实例方法名
类::静态方法名
类::实例方法名
forEach(System.out::println)
构造器引用
格式: ClassName::new
与函数式接口相结合,自动与函数式接口中方法兼容。
可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象方法的参数列表一致!且方法的返回值即为构造器对应类的对象
Stream
集合 : 是一个抽象的概念, 作用和关注点是批量的对象如何存储.
Stream 流 : 是一个抽象的概念, 作用就是对流中的批量的对象进行处理的东西
Stream流的特点 :
-
- Stream不是集合, 不存数据, 只处理数据
-
- Stream不会改变源数据, 只会产生新流.
-
- Stream的中间操作是延迟执行, 只有终止操作发起的时候, 中间操作才生效.
-
- 只能消费一次, 用完作废, 但是会产生新的流
-
- 像迭代器, 只能使用一次, 但是可以支持并发.
流的处理3步骤 :
- 创建流 : 从数据源把流创建出来.
-
- 从集合中获取流
-
- 从数组中获取流
-
- 基于散列数据创建流
-
- 基于供给器产生流(会产生无限流)
@Test
public void test4() {
// 使用供给器产生一个流, 通常会产生一个无限流
//Stream<Double> generate = Stream.generate(() -> Math.random()); // 会形成无限流!!!
Stream<Double> generate = Stream.generate(Math::random); // 会形成无限流!!!
generate.forEach(System.out::println);
}
@Test
public void test3() {
// 基于散列数据创建流
Stream<String> stream = Stream.of("a", "qq", "bb", "22", "我们", "hh");
stream.forEach(System.out::println);
}
@Test
public void test2() {
// 基于数组获取流
Integer[] arr = {8, 9, 2, 1, 0, 5};
Stream<Integer> stream = Arrays.stream(arr);
stream.forEach(System.out::println);
}
@Test
public void test1() {
// 基于集合获取流
List<Student> list = StudentData.getList();
Stream<Student> stream = list.stream(); // stream()是Collection接口中的缺省方法, 作用就是获取集合对应的流
stream.forEach(t -> System.out.println(t));
}
- 中间操作 : 一系列的操作, 是可选的.
- filter(判定器) : 把流中的每个对象都经过判定器, 如果判定结果为true, 留下, 如果为false,丢弃. 产生新流中都是判定为真的对象
- distinct() : 去重 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
- limit(long maxSize) : 限制流中的最大对象个数, 参数用的long, 国为处理的是大数据.
- skip(long n) : 略过前n个元素
- map(转换器) : 把流中的所有对象全部转换为另外一种类型的对象, 产生新流中全是新类型的对象.
- sorted() : 自然排序
- sorted(比较器) : 定制排序
// 3年级没有及格的同学倒序显示前2个.
@Test
public void exer3() {
StudentData.getList().stream().distinct().filter(t -> t.getGrade() == 3)
.filter(t -> t.getScore() < 60).sorted((t1, t2) -> -(int)(t1.getScore() - t2.getScore()))
.limit(2).forEach(System.out::println);
}
@Test
public void test11() {
// 排序, 报错的原因是流中的对象的类并未实现 Comparable接口
//StudentData.getList().stream().distinct().sorted().forEach(System.out::println);
StudentData.getList().stream().distinct().sorted((t1, t2) -> t1.getGrade() - t2.getGrade()).forEach(System.out::println);
}
// 把所有学生映射为它的年级数据
@Test
public void exer2() {
StudentData.getList().stream().distinct().map(a -> a.getGrade()).forEach(System.out::println);
}
@Test
public void test10() {
StudentData.getList().stream().distinct().map(t -> t.getScore()).forEach(System.out::println);
}
// 找出4年级姓张的同学
@Test
public void exer1() {
StudentData.getList().stream().filter(t -> t.getGrade() == 4).filter(t -> t.getName().startsWith("张")).forEach(System.out::println);
}
// 过滤3年级及格的同学
@Test
public void test7() {
List<Student> list = StudentData.getList();
list.stream().filter(a -> a.getGrade() == 3).filter(t -> t.getScore() >= 60).forEach(System.out::println); // forEach(消费器)
}
- 终止操作 : 流处理的结束
- forEach(消费器) 把流中的每个对象都经过消费器消费一下.
- findFirst() 获取第一个元素
- long count() 返回流中的元素个数
- Optional reduce(二元操作符) 流中的对象两两反复结合的结果再和后面元素结合, 最后得出一个结果.
- Collect(采集器) 把流中的所有元素采集为一个新的集合或其他的东西.
@Test
public void test16() {
List<Student> collect = StudentData.getList().stream().distinct().filter(t -> t.getScore() >= 60).collect(Collectors.toList());
for (int i = 0; i < collect.size(); i++) {
System.out.println(collect.get(i));
}
}
// 求全校总分
@Test
public void test15() {
Optional<Double> reduce = StudentData.getList().stream().distinct().map(t -> t.getScore()).reduce((t1, t2) -> t1 + t2);
Double aDouble = reduce.orElse(9999999.999999);
System.out.println(aDouble);
}
@Test
public void test14() {
// 找出3年级谁最牛
Optional<Student> reduce = StudentData.getList().stream().distinct().filter(t -> t.getGrade() == 3).reduce((t1, t2) -> t1.getScore() > t2.getScore() ? t1 : t2);
Student student = reduce.orElse(new Student());
System.out.println(student);
}
@Test
public void test13() {
long count = StudentData.getList().stream().distinct().filter(t -> t.getGrade() == 2).count();
System.out.println(count);
}
@Test
public void test12() {
Optional<Student> first = StudentData.getList().stream().distinct().filter(t -> t.getGrade() == 6).filter(t -> t.getScore() > 80).findFirst();
//Student student = first.get(); // 如果容器中为空, get()时就直接抛出了异常.
Student student = first.orElse(new Student());
System.out.println(student.toString());
}
网络编程
网络层 : 在网络中定位主机(像电话号码) 只有ip不够 还需要再配合端口号(定位某进程)
IP + Port == 套接字(socket)
传输层 : 控制数据如何传输(像打电话,发短信)
TCP : 传输控制协议(打电话) 适用于稳定传输, 传输文件和重要数据.
UDP : 用户数据报协议(发短信, 发快递) 适用于高效传输, 但是数据有丢包无所谓(像视频传输)
TCP 方式有两个角色
服务器 : 被动等待 Server
客户端 : 主动请求 Client
C/S架构模式
B/S架构模式
Socket
- 利用套接字(Socket)开发网络应用程序早已被广泛的采用,以至于成为事实上的标准。
- 通信的两端都要有Socket,是两台机器间通信的端点
- 网络通信其实就是Socket间的通信。
- Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输。
- 一般主动发起通信的应用程序属客户端,等待通信请求的为服务端
基于Socket的TCP编程
客户端Socket的工作过程包含以下四个基本的步骤:
创建 Socket:根据指定服务端的 IP 地址或端口号构造 Socket 类对象。若服务器端响应,则建立客户端到服务器的通信线路。若连接失败,会出现异常。
打开连接到 Socket 的输入/出流: 使用 getInputStream()方法获得输入流,使用 getOutputStream()方法获得输出流,进行数据传输
按照一定的协议对 Socket 进行读/写操作:通过输入流读取服务器放入线路的信息(但不能读取自己放入线路的信息),通过输出流将信息写入线程。
关闭 Socket:断开客户端到服务器的连接,释放线路
服务器程序的工作过程包含以下四个基本的步骤:
调用 ServerSocket(int port) :创建一个服务器端套接字,并绑定到指定端口上。用于监听客户端的请求。
**调用 accept():**监听连接请求,如果客户端请求连接,则接受连接,返回通信套接字对象。
**调用 该Socket类对象的 getOutputStream() 和 getInputStream ():**获取输出流和输入流,开始网络数据的发送和接收。
**关闭ServerSocket和Socket对象:**客户端访问结束,关闭通信套接字。
客户端创建Socket对象
客户端程序可以使用Socket类创建对象,创建的同时会自动向服务器方发起连接。Socket的构造方法是:
- Socket(String host,int port)throws UnknownHostException,IOException:向服务器(域名是host。端 口号为port)发起TCP连接,若成功,则创建Socket对象,否则抛出异常。
- Socket(InetAddress address,int port)throws IOException:根据InetAddress对象所表示的IP地址以及端口号port发起连接。
客户端建立socketAtClient对象的过程就是向服务器发出套接字连接请求
服务器建立 ServerSocket 对象
- ServerSocket 对象负责等待客户端请求建立套接字连接,类似邮局某个窗口中的业务员。也就是说,服务器必须事先建立一个等待客户请求建立套接字连接的ServerSocket对象。
- 所谓“接收”客户的套接字请求,就是accept()方法会返回一个 Socket 对象
代码示例:
@Test
public void server() throws IOException {
ServerSocket server = new ServerSocket(9999); // 服务器绑定了本机的9999端口. 其他程序不能抢
Socket socket1 = server.accept();// 它会在绑定好的端口上进行监听...
System.out.println(socket1);
InputStream inputStream = socket1.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s = bufferedReader.readLine();
System.out.println(s);
// 关闭资源时的顺序是从后向前
bufferedReader.close();
socket1.close();
server.close();
}
@Test
public void client() throws IOException {
// 客户端必须要知道服务器的 ip 和 端口 才能连接
Socket socket2 = new Socket("127.0.0.1", 9999);// 直接连接服务器, 并创建Socket对象
System.out.println(socket2);
OutputStream outputStream = socket2.getOutputStream();
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
bufferedWriter.write("你好, 服务器, 我是客户端, 今天天气不错..");
bufferedWriter.newLine();
bufferedWriter.flush(); // 把缓冲区中的数据刷入网线.
// 关闭资源的顺序是从后向前
bufferedWriter.close();
socket2.close();
}