Java笔记(学习中。。)

Java笔记

eclipse的使用

alt + / 自动补全
Ctrl + shift + o 自动导包
Ctrl + shift + f 代码规范化
Ctrl + alt + 下 向下复制一行
Ctrl + shift + t 搜索结构
Ctrl + o 查找类中的方法、结构
Ctrl + 鼠标左键 查看源代码
alt + 左箭头 返回上一个编辑的页面
Ctrl + 1 快速修复
Ctrl + t 查看继承关系
Ctrl + shift + x/y 常量的大小写切换
alt + shift + s 生成一些结构

常用的dos指令

md 生成目录
cd 进入目录 cd…退出目录 cd\回到根目录
del 删除 del *.txt 删除txt的所有文件
rd 删除目录(目录中为空) 不为空时,用del wenjianjia1 然后再rd

预备知识

JDK = JRE + 开发工具集
JRE = JVM + JavaSE 标准类库

javac 编译 java 运行
.class 是字节码文件
java 的代码有分号;

//这是一个单行注释
/*
这是一个
多行注释
多行注释不可以嵌套使用
*/

文档注释(java特有)
文档注释可以被javadoc解析
在命令行中使用

javadoc –d file_name –author –version .java_name

来解析

/**
	@author
	@
	这是文档注释
*/

public 类名要和文件名同名,一个源文件中只能声明一个public类
程序的入口是 main()方法,其格式是固定的
简单的输出语句:
System.out.println();结尾会自动换行,参数为空时就表示换行
System.out.print();结尾不会换行

第一个java程序:

public class Hello{
	public static void main(String[ ] args){
		System.out.println(“Hello World!);
	}
}

编译以后会生成一个或多个字节码文件,取决于原文件中定义的类,文件名与类名相同

基本语法

java的标识符中可以含有$和_,不能包含空格,不能以数字开头
java中严格区分大小写
命名规范:
包名:全部小写
类名、接口名:所有单词的首字母大写
变量名、方法名:第一个首字母小写,后面开始首字母大写
常量名:全部都大写,且多单词用下划线链接

变量

先声明,后使用。
变量都有自己的作用域,Java中方法中的变量作用域就是方法的大括号内,在外面访问或输出都会报错

数据类型:分为基本数据类型和引用数据类型
基本数据类型:
整数类型(byte,short,int,long) int范围:-128~127
浮点型(float,double)
字符型(char)
布尔型(boolean)
引用数据类型:
类(class) 字符串在类中
接口(interface)
数组([ ])

根据变量在类中声明的位置又可以分为:
成员变量 vs 局部变量

声明long变量 必须以l或L结尾
声明float变量,必须以f或F结尾

关于字符型

1个字符 = 2字节
使用一对单引号,只能写一个字符
支持转移字符
支持使用Unicode值,如c1 = ‘\u0123’;

关于字符集:ascII只有英文和常用的字符
Unicode囊括世界上所有的字符
utf-8是Unicode的一种实现方式
windows命令行默认使用GBK进行解析

java中boolean型只能取true或false 注意区分大小写
char型单引号里面不能没有东西,但可以有空格

关于String

String不是基本数据类型!!,String属于引用数据类型
使用双引号“” 双引号里面可以为空
String可以和八种基本数据类型做运算,只能做连接运算
运算后结果自动转换成String

自动类型提升和强制类型转换在java中都可用
byte、short、char之间做运算时,结果都是int型
整形常量,默认为int型
浮点型常量,默认为double型

进制转换

Java中二进制以0b开头,八进制以0开头,十六进制以0x开头
二进制:最高位是符号位,0表示正数,1表示负数
正数的原码反码补码都相同,
对于负数:
0 0 0 0 1 1 1 0 :为+14
1 0 0 0 1 1 1 0 :为-14的原码
1 1 1 1 0 0 0 1 :除了符号位,各个位去反,就得到了-14的反码
1 1 1 1 0 0 1 0 :将反码加1,得到-14的补码

计算机底层都以补码的方式来存储数据!无论正负。
因此真正的-14在底层应该是1 1 1 1 0 0 1 0

十进制转二进制:除二取余的逆
对于二进制转十六进制(hex),八进制(octal)只需要将数字分组即可,八进制为3个一组,十六进制为4个一组

运算符

整数相除 /号 为整除
支持自增自减运算,支持%运算
^异或运算,相同则false,不同则true
&&短路与 &逻辑与
||短路或 |逻辑或
三元运算符冒号两边的类型要一致!并且会自动类型提升!

流程控制

使用Scanner类来从键盘获得用户输入
先导包,
再实例化Scanner类
再调用方法

import java.util.Scanner
class ScannerTest{
	public static void main(String[ ]  args){
		Scanner scan = new Scanner(System.in);
		int num = scan.nextInt();
		System.out.println(num) ;
	}
}

分支、循环语句和c类似
给程序计时:start = System.currentTimeMillis();
end = System.currentTimeMillis();
time = end – start;

在循环语句中加入标签

label:for(.....){
	break label;
}

实现带标签的break和continue,直接跳出想要跳出的循环

数组

创建一个数组:
int[] t1;

多个相同类型按一定顺序排列
数组的长度一旦确定就不能修改

数组的内存解析:

在这里插入图片描述
在这里插入图片描述

数组的Arrays工具类

在这里插入图片描述

面向对象

Java类及类的成员:属性、方法、构造器;代码块、内部类
面向对象的三大特征:封装性、继承性、多态性
其他关键字:this、super、static、final、abstract、interface、package、import

属性 = 成员变量 = field = 域、字段
方法 = 成员方法 = 函数 = method

对象之间是独立的,但是对象之间的赋值时,传递的是地址,和数组类似
对于在 类中使用static关键字声明的属性,每个对象是通用的

对象的内存解析

对象实体存放在堆空间中
方法中的变量都是局部变量,局部变量存放在栈空间中

属性(成员变量) vs 局部变量

相同点

定义变量的格式都一样
先声明后使用
变量都有对应的作用域

不同点

位置不同:

属性:直接定义在类的一对{ }内
局部变量:声明在方法内、方法形参,代码块内,构造器形参、构造器内部的变量

关于权限修饰符的不同:

属性:可以在声明属性是时,指明其权限,使用权限修饰符。
常用的权限修饰符:private、public、缺省、protected这属于封装性的知识
局部变量:不可以使用权限修饰符

默认初始化值的不同:

属性:根据其类型,都有默认初始化值。
局部变量:没有默认初始化值。
我们在调用局部变量之前,一定要显式的赋值

在内存中加载的位置不同:

属性:加载到堆空间中 (非static)
局部变量:加载到栈空间中

方法的声明和使用

方法的声明:权限修饰符 返回值类型 方法名(形参列表){
方法体
}

关于权限修饰符

常用的权限修饰符:private、public、缺省、protected
在此处不细说,在封装性中再细说

返回值类型

有返回值必须要声明返回值类型,并且使用return关键字
不需要返回值时,使用void

方法名

属于标识符,开头小写,首字母大写,需要“见名知意”

形参列表

可以声明多个或0个形参

方法的使用

方法内部可以调用当前类的属性,
也可以调用当前类中的方法

理解“万事万物皆对象”

1、 在Java语言范畴中,我们都将功能、结构等封装到类中,通过类的实例化,来调用具体的功能结构

Scanner,String等
文件:File
网络资源:URL

2、 涉及到Java语言与前端Html、后端的数据库交互时,前后端的结构在Java层面交互时,都体现为类、对象

对象数组的内存解析

对象也可以成为数组的元素
如:

class Student{
	int name;
}
Student[] stus = new Student[5];
stus[0] = new Student();

这就声明了一个“学生”对象的数组
引用类型的变量,只可能存储两类值:null 或 地址值(含变量的类型)
在声明了一个对象数组后,如果没有给数组赋值,则数组内为null
如果给数组赋值了对象,则数组内为地址值

匿名对象的使用

我们创建的的对象,没有显示的赋给一个变量名,即为匿名对象
匿名对象只能调用一次
使用如下:

		PhoneMall mall = new PhoneMall();
		//匿名对象的使用
		mall.show(new Phone());

class PhoneMall{
	public void show(Phone phone) {
		phone.sendEmail();
		phone.playGame();
	}
}

方法重载(overload)

在同一个类中,允许存在一个以上的同名方法,只要他们的参数个数或者参数类型不同即可
举例:Arrays类中有重载的sort() / binarySearch()
“两同一不同”:同一个类、同一个方法名
参数列表不同(参数顺序也算不同):参数个数不同、参数类型不同
注意:返回值不同,权限修饰符不同,形参变量名不同,方法体不同,都不构成重载

在通过对象调用方法时,如何确定一个指定的方法?
方法名 ----> 参数列表

可变参数的形参(JDK5.0新特性)

允许直接定义能和多个实参相匹配的形参。从而,以一种更简单的方式,来传递个数可变的实参。(即使传入空值也可以)

可变参数形参的格式:数据类型 … 变量名
当调用可变参数的形参方法时,传入的参数个数可以使0个,1个,2个。。。
可变参数形参的方法与本类中方法名相同,形参不同的方法之间构成重载
可变参数形参的方法与本类中方法名相同,形参类型也相同的数组不构成重载。换句话说,二者不能共存
可变个数形参在方法的形参中,必须声明在末尾
可变个数形参在方法的形参中,最多只能声明一个可变形参

方法参数的值传递机制

关于变量的赋值

如果变量是基本数据类型,此时赋值的是变量所保存的数据值。
如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值。

方法形参的传递机制:值传递

1、 形参:方法定义时,声明的小括号内的参数
实参:调用方法时,实际传递给形参的数据
2、 值传递机制
如果参数是基本数据类型,此时实参赋给形参的是,实参真实存储的数据值
如果参数是引用数据类型,此时实参赋给形参的是,实参存储数据的地址值

封装性

程序的设计追求:高内聚,低耦合
封装性的体现:

  1. 我们将类的属性私有化(private),同时,提供公共的(public)方法,来获取(getXxx)和设置(setXxx)此属性的值,由此来约束想要修改此属性的行为,避免乱赋值和随意使用。
  2. 不对外暴露的私有方法,此方法只在内部的其他方法中调用,不对外开放。
  3. 单例模式
  4. 。。。。。。

封装性的体现,需要权限修饰符来配合。

权限修饰符

Java规定的四种权限:private、缺省、protected、public(从小到大)

这4种权限可以用来修饰类及类的内部结构:属性、方法、构造器、内部类
具体的,4种权限都可以修饰类的内部结构:属性、方法、构造器、内部类
修饰类,只能使用:缺省、public
public的类可以import导包在其他包内使用,
但缺省的类即便导包也不能在其他包内使用。

在不同包的子类中,不能调用private和缺省权限的结构,可以调用protectedpublic
在不同包的普通类(非子类),只能调用public

构造器

构造器的作用:

创造对象:new + 构造器
初始化对象的信息

说明:

  1. 如果没有显式的定义类的构造器,则系统默认提供一个空参的构造器
  2. 定义构造器的格式:权限修饰符 类名(形参列表)
  3. 一旦我们现实的定义了构造器,系统就不再提供默认的空参的构造器了
  4. 一个类中,至少会有一个构造器

属性赋值的先后顺序

this关键字的使用

this可以用来修饰和调用:属性、方法、构造器
this理解为当前对象或当前正在创建的对象,通常省略
可以在构造器中调用构造器来减少代码的冗余,增加代码复用
如:
public Person(){
//初始化时要用的40行代码。。。
}
public Person(String name){
this(); //此时每当使用该构造器时,都会调用空参的构造器的40行代码
}
也可以调用指定的其他构造器,但不能调自己,也不能形成环路
调用其他构造器的代码必须写在构造器的首行。

import关键字

使用“xxx.*”的方式,可以调用xxx包下的所有结构,但是xxx的子包下的结构不能调用,仍需要显式的import
import static:表明导入指定类或接口中的静态结构:属性或方法

MVC的设计模式

在这里插入图片描述

继承性

权限修饰符 类名 extends 父类名{ }
一旦子类A继承了父类B以后,子类A中就获取了父类B中的所有的属性、方法。
只不过,由于封装性的影响,私有的结构不能够直接在子类中被调用。
Java中类的单继承性:一个子类只能有一个父类。
一个父类可以被多个子类继承

如果没有显式的继承,则此类继承于java.lang.Object类

方法的重写(override/overwrite)

子类继承父类之后,可以对父类中同名同参数的方法,进行覆盖操作
① 子类的方法名和形参列表与父类的相同
② 子类重写的方法的权限修饰符不小于父类方法的权限修饰符
>特殊情况:子类中不能重写父类中声明为private的方法
③ 返回值类型:

父类中是void,则子类重写的返回值也只能是void
父类被重写的方法的返回值类型是A类型,则子类中重写的方法的返回值类型可以是A类,或A类的子类
父类被重写的方法的返回值类型是基本数据类型,则类中重写的方法的返回值类型必须是相同的基本数据类型

④ 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常

⑤ 子类和父类中同名同参数的方法,要么都声明为非static的(考虑重写),要么都声明为static的(不是重写)

super关键字的使用

super理解为:父类的
super可以用来调用属性、方法、构造器
注意:属性是不会被覆盖的,在子类中定义了一个与父类重名的属性时,内存中存在了两个属性,一个是父类的,一个是子类的。
此时使用this调用时,会先去找子类中的属性,若找不到,则调用父类中的属性
super则直接调用父类中的属性

而方法可以被重写,因此想要调用父类中的方法就只能使用super关键字

super调用构造器

我们可以在子类的构造器中显式的使用“super(形参列表)”的方式,调用父类中指定的构造器
使用super(形参列表)必须用在子类中构造器的首行
在类的构造器中,针对于“this(形参列表)”或“super(形参列表)”只能二选一
当构造器的首行没有显式的声明“this(形参列表)”或“super(形参列表)”,则默认调用的是父类中空参的构造器,即默认加上了super()

Eclipse Debug的使用

如何调试程序?

  1. sysout语句
  2. Debug调试
    要设置断点

常用操作

在这里插入图片描述

多态性

一种事物的多种形态
对象的多态性:
父类的引用指向子类的对象
例如

//Man和Woman都是Person的子类
Person p1 = new Man();

p1.eat(); 此时实际执行的是子类重写的父类的方法-------虚拟方法调用

多态的使用:虚拟方法调用

编译的时候编译器认为p1是Person类的实例对象,所以只能调用父类中的方法
但是实际执行的时候,执行的是子类重写过的方法
因此,我们在编译期,只能调用父类中的方法,但在运行期,我们实际执行的是在子类中重写过的方法。

总结:编译,看左边;运行,看右边

多态的使用前提

① 类的继承关系
② 方法的重写

注意!

对象的多态性,只适用于方法,不适用于属性

Person p1 = new Man();
p1.eat()
p1.id 

此时调用的是父类中的id,且此处不可调用子类特有的属性

在多态情况下,父类的方法成为虚拟方法,父类根据赋给它的不同子类对象,动态的调用属于子类的方法。这样的方法调用是无法在编译期确定的
------动态绑定和虚拟方法调用

多态是一个运行时行为

向下转型的使用

有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法,
但是由于变量声明为父类类型,导致编译时只能调用父类中声明的属性和方法

如何调用子类特有的属性和方法呢?
Man m1 = p1; 此时报错:等号左右两边的类型不一致,不可进行赋值操作
向下转型:即使用强制类型转换

Man m1 = (Man)p1;

使用强转时,可能出现ClassCastException的异常

instanceof的使用

a instanceof A :判断对象a是否是类A的实例,如果是,返回true,如果不是,返回false。
为了不出现异常,通常先判断:

if(p1 instanceof Man){
	Man m1 = p1;
}

这样当p1不是Man类型时,就不会向下转型

方法的重载与重写

在这里插入图片描述

Object类的使用

① Object类是所有Java类的根父类
② 如果在类的声明中未使用extends关键字指明其父类,则默认父类 为java.lang.Object类。
③ Object类的功能(属性、方法)具有通用性
④ Object类只声明了一个空参的构造器

属性

方法

.clone() 克隆
.finalize() 在对象回收之前,垃圾回收器(gc)会通知该对象调用自己的finalize方法。不要主动的去调用该方法。
.getClass() 返回对象自己的类

equals(Object obj)、== 和equals()的区别

.equals(Object obj) 判断两个对象是否相等
一、 回顾 == 的使用
1.== 是一个运算符,可以使用在基本数据类型变量和引用数据类型变量中
2.如果比较的是基本数据类型变量:比较两个变量存储的数据是否相等。(类型不一定要一致)
如果比较的是引用数据类型变量(String是一个类),则比较两个对象的地址值,即两个引用是否指向同一个对象实体。
二、 equals()方法的使用
1. 是一个方法,不是运算符
2. 只能适用于引用数据类型
3. Object类中定义的equals()和 == 是相同的,即比较地址值
4. 像String、Date、File、包装类等,都重写了Object类中的equals()方法。
重写以后是比较了两个对象的试实体内容是否相同
5.通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的实体内容。那么就要对Object类中的equals()方法进行重写
重写格式:

public boolean equals(Object obj){
	if(this == obj){
		return true;
}
if(obj instanceof Customer){
Customer cust = (Customer)obj;
if(this.age == obj.age && this.name.equals(obj.name)){
	return true;
}else{
	return false;
	}
}
return false;
}

x.equals(null) 永远返回false

toString()的使用

1. 当我们输出一个对象的引用时,实际上就是调用了当前对象的toString()方法
2. Object类中toString()的定义:
输出全类名@十六进制地址值
3. 像String、Date、File、包装类等都重写了Object类中的toString方法。
4. 自定义类也可以重写toString方法

单元测试

1.选中当前工程 – 右键选择:build path – add libraries – JUnit 4 – 下一步
2.创建Java类,进行单元测试
此时的Java类要求:
① 此类是public的
② 此类要提供一个public的无参的构造器
3.在此类中声明单元测试方法
此时的单元测试方法:权限是public,没有返回值,没有形参
4.此方法上要声明注解:@Test 并在此类中导入:org.junit.Test
5.在方法体内写测试代码
6.左键双击方法名,右键run as – JUnit Test
会显示绿条或红条

包装类的使用(Wrapper)

在这里插入图片描述

JDK5.0新特性:自动装箱和自动拆箱

public class WrapperTest {
	// String类型 --->基本数据类型、包装类:调用包装类的parseXxx(String s)
	@Test
	public void test5() {
		String str1 = "123";
		// 错误的情况:
//		int num1 = (int)str1;
//		Integer in1 = (Integer)str1;
		// 可能会报NumberFormatException
		int num2 = Integer.parseInt(str1);
		System.out.println(num2 + 1);
		String str2 = "true1";
		boolean b1 = Boolean.parseBoolean(str2);
		System.out.println(b1);
	}
	// 基本数据类型、包装类--->String类型:调用String重载的valueOf(Xxx xxx)
	@Test
	public void test4() {
		int num1 = 10;
		// 方式1:连接运算
		String str1 = num1 + "";
		// 方式2:调用String的valueOf(Xxx xxx)
		float f1 = 12.3f;
		String str2 = String.valueOf(f1);// "12.3"

		Double d1 = new Double(12.4);
		String str3 = String.valueOf(d1);
		System.out.println(str2);
		System.out.println(str3);// "12.4"
	}
	/*
	 * JDK 5.0 新特性:自动装箱与自动拆箱
	 */
	@Test
	public void Test3() {
		int num1 = 10;
		method(num1);
		// 自动装箱
		int num2 = 10;
		Integer in1 = num2;
		// 自动拆箱
		int num3 = in1;
	}
	public void method(Object obj) {
		System.out.println(obj);
	}
	// 包装类 ---> 基本数据类型:调用包装类的XxxValue()
	@Test
	public void Test2() {
		Integer in1 = new Integer(12);
		int i1 = in1.intValue();
		System.out.println(i1 + 1);
	}
	// 基本数据类型 ---> 包装类:调用包装类的构造器
	@Test
	public void test1() {
		int num1 = 10;
		Integer in1 = new Integer(num1);
		System.out.println(in1.toString());

static关键字

static:静态的
static可以修饰:属性、方法、代码块、内部类

static修饰属性:静态变量

属性:按是否使用static修饰:又分为静态属性(类变量)、费静态属性(实例变量)
实例变量:每个对象都拥有一份独立的非静态属性
静态变量:多个对象共享一份静态变量,当通过某一个对象修改静态变量时,会导致其他变量也修改了。

其他说明:
1. 静态变量随着类的加载而加载。可以通过类.静态变量的方式来调用
2. 静态变量的加载要早于对象的创建
3. 由于类只会加载一次,所以静态变量在内存中只存在一份。存在方法区的静态域中

静态属性举例:System.out、Math.PI

类中的常量也常常修饰为static

static修饰方法:静态方法

  1. 随着类的加载而加载,课以通过类.方法来调用
  2. 静态方法中,只能调用静态的方法或属性
    非静态方法中,静态的和非静态的都能调用
  3. 静态的方法内,不能使用this和super关键字

操作静态属性的方法,通常设置为static的
静态方法不会被继承,而是公共的。
工具类中的方法,习惯上声明为static的,方便直接调用

单例(Singleton)设计模式

某个类只能存在一个对象实例
该类只提供一个取得其对象实例的方法
将类的构造器权限设置为private
只能调用该类的某个静态方法来返回类内部创建的对象
静态方法只能方位静态成员变量,所以类内部产生的该类对象的变量也必须定义成静态的

//饿汉式
class Bank{
	//私有化构造器
	private Bank() {
	}
	//内部创建类的对象,声明为静态
	private static Bank instance = new Bank();
	//提供公共静态方法返回对象
	public static Bank getInstance() {
		return instance;
	}
}

//懒汉式
class Order {
	// 私有化构造器
	private Order() {
	}
	// 声明当前类对象,没有初始化
	private static Order instance = null;
	// 声明public、static的返回当前类对象的方法
	public static Order getInstance() {
		if (instance == null) {
			instance = new Order();
		}
		return instance;
	}
}

区分饿汉式和懒汉式:
饿汉式:先造好了对象,对象加载时间过长。
天然线程安全的
懒汉式:延迟对象的创建
目前写法线程不安全。 —>到多线程时,再修改

java.lang.RunTime 就是单例模式

main方法的使用说明

  1. 是程序的入口
  2. main()也是一个普通的静态方法
  3. main()可以作为与控制台交互的方式

代码块

1. 代码块的作用:用来初始化类或对象
2. 只能使用static来修饰
3. 静态代码块 vs 非静态代码块
4. 静态代码块随着类的加载而执行,且只执行一次
5. 非静态代码块随着对象的创建而执行,每创建一个对象就执行一次
6. 静态代码块:初始化类的信息,多个静态代码块按照声明顺序执行
7. 非静态代码块,初始化对象的信息,多个非静态代码块按照声明顺序执行

final关键字

final:最终的
final可以用来修饰类、方法、变量

final修饰类

此类不能被其他类所继承
比如:String类、System类、StringBuffer类等

final修饰方法

表明此方法不能再被重写了
比如:Object类中的getClass()

final修饰变量

final修饰的属性(一般全大写):可以显式初始化,可以在构造器、代码块中初始化,不能在方法中初始化。不可修改!
final修饰的局部变量:表示常量。不可修改

static final

static final 修饰属性:全局常量
static final 修饰方法:

抽象类和抽象方法

通过继承关系,类变得越来越具体,父类则更一般,更通用。有时将一个父类设计的非常抽象,以至于,该类不会进行实例化。

abstract关键字的使用

可以用来修饰类、方法
被abstract修饰的类:
>此类不能实例化
>构造器用来继承,供子类使用
>开发中,都会提供子类,让子类实例化

被abstract修饰的方法:
>抽象方法只有方法的声明,没有方法体

包含抽象方法的类一定是一个抽象类(为了保护抽象方法不被调用),但抽象类中可以没有抽象方法。
若子类重写了所有的抽象方法后,此类方可实例化
若没有重写所有的抽象方法,则此类必须是抽象类

注意

1. abstract不能用来修饰:属性、构造器等结构
2. abstract不能用来修饰私有方法,静态方法,final的方法,final的类

抽象类的匿名子类

在这里插入图片描述

多态的应用:模板方法设计模式(TemplateMethod)

抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
解决的问题:当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。 
换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式。

接口(interface)

1.接口使用interface来定义
2.Java中,接口和类是并列的两个结构
3.如何定义接口?定义接口的成员
JDK7及以前,只能定义全局常量和抽象方法

全局常量:public static final,但是书写时可以不写(仍认为是常量)注意有final
抽象方法:public abstract

JDK8:除了定义全局常量和抽象方法以外,还可以定义静态方法、默认方法(略)
4.接口中不能定义构造器!,意味着接口不能实例化
5.java开发中,接口通过让类去实现(implements)的方式使用,
如果实现类覆盖了接口中的所有的抽象方法,则实现类可以实例化
如果没有,则此实现类仍为一个抽象类
6. Java类可以实现多个接口 —>弥补了类的单继承性的局限性
格式:class AA extends BB implements CC,DD{ }
7. 接口与接口之间可以继承,而且可以多继承
8. 接口的使用:体现了多态性
9. 接口实际上看作一种规范

接口的应用:代理模式(Proxy)

在这里插入图片描述

接口也可以发生多态,可以将接口看“父类”,对于不同的接口实现,可以产生多态性

接口的应用:工厂模式

简单工厂模式
工厂方法模式
抽象工厂模式

具体细节在学习框架时有体现

Java8中接口的新特性

JDK8:接口中除了定义全局常量和抽象方法,还可以定义静态方法和默认方法

接口中定义的静态方法,只能通过接口来调用

通过实现类的对象,可以调用接口中的默认方法
实现类可以重写接口中的默认方法

注意:

对于父类中与接口中重名的属性,会报错
但是对于父类中的与接口中的同名同参数的方法,优先调用父类中的方法;如果在实现类(子类)中重写了此方法,那么调用重写的方法
—————类优先原则

那么如何在子类(实现类)中调用接口中没有重写的方法?

使用CompareA.super.method1();
如果实现类实现了多个接口,而多个接口中定义了同名同参数的默认方法,那么在实现类中没有重写此方法的情况下,报错
—————接口冲突

但是对于同样场景下的抽象方法,不会报错。如果重写了改抽象方法,则同时视作两个接口的实现。

内部类

Java中将类A声明在类B中,类A成为内部类,类B称为外部类

内部类的分类:成员内部类 vs 局部内部类(方法内,代码块内,构造器内)

成员内部类

一方面,作为外部类的成员:

调用外部类的属性
可以用static修饰(外部类不能)
可以被四种不同的权限修饰

另一方面,作为一个类:

类内可以定义属性、方法、构造器等
可以被final修饰,表示此类不能被继承
可以被abstract修饰,表示不能实例化

实例化成员内部类:

//创建Dog实例(静态的成员内部类)
Person.Dog dog = new Person.Dog();
dog.show();
//创建Bird实例(非静态成员内部类)
// Person.Bird bird = new Person.Bird();      错误的
Person p = new Person();
Person.Bird bird = p.new Bird();

成员内部类中调用外部类的结构:Person.this.name;

开发中应用:要求一个方法返回实现好的接口,此时要在方法中创建接口的实现类(成员内部类)并返回

异常处理

异常:在Java语言中,将程序执行中发生的不正常情况称为“异常”。
(开发过程中的语法错误和逻辑错误不是异常)

Java程序在执行过程中所发生的异常事件可分为两类:

  • Error:
    Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如:栈溢出StackOverflowErrorOOM(OutOfMemoryError空间不足)。一般不编写针对性的代码进行处理。

  • Exception:
    其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。

    • 例如:
      • 空指针访问
      • 试图读取不存在的文件
      • 网络连接中断
      • 数组角标越界

异常分类:编译时异常和运行时异常

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mTzZq4vf-1643032559664)(media/8f75c4e64e40db3413b79210a6d3b73e.png)]

异常的体系结构

  • java.lang.Throwable
    • |-----java.lang.Error:一般不编写针对性的代码进行处理。
    • |-----java.lang.Exception:可以进行异常的处理
    • |------编译时异常(checked)
      • |-----IOException
      • |-----FileNotFoundException
      • |-----ClassNotFoundException
      • |------运行时异常(unchecked,RuntimeException)
      • |-----NullPointerException
      • |-----ArrayIndexOutOfBoundsException
      • |-----ClassCastException
      • |-----NumberFormatException
      • |-----InputMismatchException
      • |-----ArithmeticException

异常的处理:抓抛模型

  • 过程一:“抛”:程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象。并将此对象抛出。
    • 一旦抛出对象以后,其后的代码就不再执行。
  • 关于异常对象的产生:
    • ① 系统自动生成的异常对象
    • ② 手动的生成一个异常对象,并抛出(throw)
  • 过程二:“抓”:可以理解为异常的处理方式:
    • ① try-catch-finally
    • ② throws

异常处理方式一:try-catch-finally

try {

//可能出现异常的代码
}catch(异常类型1 变量名1){
//处理异常1的方式
}catch(异常类型2 变量名2) {
//处理异常2的方式
}
finally {
//一定会执行的代码
}
finally是可选的

finally中声明的是一定会被执行的代码。即使catch中又出现异常了,try中有return语句,catch中有return语句等情况。

像数据库连接,输入输出流、网路编程Socket等资源,JVM不能帮我们自动回收,就算之前的代码出现了异常,,也仍然需要回收资源,那么就要放在finally语句中处理

catch中的异常类型如果没有子父类关系,则谁声明在上,谁声明在下无所谓。

catch中的异常类型如果满足子父类关系,则要求子类一定声明在父类的上面。否则,报错

常用的异常对象处理的方式: ① String getMessage() ② printStackTrace()

在try结构中声明的变量,再出了try结构以后,就不能再被调用!

可以在外面声明(并且初始化),在try里面再赋值,这样在外面也能用。

体会1:使用try-catch-finally处理编译时异常,是得程序在编译时就不再报错,但是运行时仍可能报错。

相当于我们使用try-catch-finally将一个编译时可能出现的异常,延迟到运行时出现。

体会2:开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally了。

针对于编译时异常,我们说一定要考虑异常的处理。

异常处理方式二:throws+异常类型

1. "throws + 异常类型"写在方法的声明处。指明此方法执行时,可能会抛出的异常类型。

一旦当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws后异常类型时,就会被抛出。异常代码后续的代码,就不再执行!

2. 体会:try-catch-finally:真正的将异常给处理掉了。

throws的方式只是将异常抛给了方法的调用者。

并没有真正将异常处理掉。

注意

子类重写的方法,抛出的异常只能小于等于父类方法抛出的异常

开发中如何选择使用try-catch-finally 还是使用throws?

如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中有异常,必须使用try-catch-finally方式处理。

执行的方法a中,先后又调用了另外的几个方法,这几个方法是递进关系执行的。我们建议这几个方法使用throws的方式进行处理。而执行的方法a可以考虑使用try-catch-finally方式进行处理。

上文说的throws是处理异常的方式(即提交(抛)给上级代码处理)

而异常的体系结构中说的throw指的是异常对象的产生方式(手动抛出)

二者不同。

finally后的代码在throw之前就执行完了

用户自定义异常类

/*

* 如何自定义异常类?

* 1. 继承于现有的异常结构:RuntimeException 、Exception

* 2. 提供全局常量:serialVersionUID

* 3. 提供重载的构造器

*

*/

public class MyException extends Exception{

static final long serialVersionUID = -7034897193246939L;

public MyException(){

}

public MyException(String msg){

super(msg);

}

}

高級部分

Idea

Ctrl+Shift+Y 切换大小写

Alt+Enter 快速纠正

Ctrl+O 查看当前类的结构

Ctrl+P 查看参数

Ctrl+Shift+T 查找结构

多线程

进程是程序的一次执行过程,或是正在运行的一个程序,是动态的

如果一个进程在同一时间并行的执行多个线程,就是支持多线程的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IXJQvMjS-1643032559666)(media/9c91b3cebeca7be08bbe0d208bad45cb.png)]

虚拟机栈和程序计数器每个线程都有一个,方法区和堆每个进程只有一个,由线程共享

线程的创建和使用,方式一

Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread

类来体现。

方式一:继承Thread类

创建一个Thread的子类

重写run()方法

创建子类的对象

调用start()方法

每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常

把run()方法的主体称为线程体

通过该Thread对象的start()方法来启动这个线程,而非直接调用run()!

如果直接调用run(),就不会再启动一个新的线程了。

不可以让已经start的线程再去执行start,只能再new一个

Thread类常用方法

start();

run();需要重写

currentThread()静态方法,返回当前线程;

getName()返回当前线程名

setName()设置当前线程名

yield() 释放当前CPU的执行权(**不是阻塞!!**是回到了就绪态)

join()
在线程A中调用线程B的join方法时,线程A进入阻塞状态,直到线程B完全执行完以后,线程A才结束阻塞状态

stop() 已过时 强制结束这个线程

sleep(long millitime) 将线程阻塞一定的时间(毫秒)

isAlive() 判断线程是否存活

优先级

线程的优先级等级(整型常量)

MAX_PRIORITY:10

MIN _PRIORITY:1

NORM_PRIORITY:5

涉及的方法

getPriority() :返回线程优先值

setPriority(int newPriority) :改变线程的优先级

高优先级的线程也不一定就一定会先执行!

线程的创建和使用,方式二

创建一个实现了Runnable接口的实现类

实现接口中的抽象方法:run()

创建实现类的对象

将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象

通过Thread类的对象调用start()

开发中应该优先选择实现Runnable接口的方式:

由于Java的单继承性,一个类继承了Thread之后就无法继承其他类,而接口没有局限

实现的方式更适合处理多个线程有共享数据的情况

Thread类本身其实也是实现了Runnable接口

两种方式都需要重写run()方法

线程的生命周期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WOnwf4FA-1643032559668)(media/7d5fb7e323e2fa28df1e4ba876a8bba3.png)]

线程的同步

在Java中通过同步机制来解决线程安全问题

同步的方式,解决了线程安全问题,但是同步的地方实际上是单线程的,效率降低了。

方式一:同步代码块

synchronized(同步监视器)(){

需要被同步的代码

}

操作共享数据的代码,即为需要被同步的代码

同步监视器,俗称:锁 任何一个类的对象都可以充当锁

要求:多个线程必须要公用同一把锁

对于使用实现Runnable接口的多线程,锁可以直接用this,因为this只new了一个

但是继承方式不行,但是可以用反射的方法,将线程子类(类也是一个对象)作为锁:Window2.class,因为类只会加载一次。

方式二:同步方法

如果操作共享数据的代码正好声明在一个方法中,我们不妨将此方法声明为同步的

注意:同步方法中的锁由Java隐式的声明为this,因此在继承方式的多线程中,不能简单地将方法声明为synchronized,还需要将方法声明为static的。(实际上声明为static之后,锁就隐式的变成了线程子类)

死锁问题

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃

自己需要的同步资源,就形成了线程的死锁

出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于

阻塞状态,无法继续

锁A中嵌套锁B,在一个线程中先调用了A后调用了B,另一个线程中先调用了B后调用了A,就极有可能发生死锁。

解决方法:

专门的算法、原则

尽量减少同步资源的定义

尽量避免嵌套同步

方式三:Lock

从JDK5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。

实例化ReentrantLock对象(fair参数表示公平与否,也可不加参数)

lock.lock()调用

互斥访问的代码

lock.unlock();

由于lock之后必须要unlock,所以可以使用try-finally结构

synchronized和Lock的异同

相同点:都可以解决线程安全问题

不同点:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器

Lock需要手动的启动同步,结束同步也需要手动执行(更加灵活)

线程的通信

三个方法:

wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器(锁)

notify():一旦执行此方法,就会唤醒被wait()的一个线程,如果有多个被wait(),就唤醒优先级高的

botifyAll():一旦执行此方法,就会唤醒所有被wait的线程

wait(),notify(),botifyAll()必须使用在同步代码块或者同步方法中

这三个方法的调用者必须是同步代码块或同步方法中的同步监视器(锁),否则会出现IllegalMonitorStateException异常

这三个方法定义在Object类中,而不是Thread类中(因为任何一个对象都可以充当同步监视器)

面试题:sleep()和wait()的异同

相同点:都可以使得当前的线程进入阻塞状态

不同点:1)两个方法声明位置不同:Thread类中声明静态的sleep(),Object类中声明wait()

2)调用的要求不同:sleep()可以在任何需要的场景下调用,wait()必须在同步代码块或同步方法中调用

3)如果都是用在同步代码块或同步方法中,sleep()不会释放锁,wait()UI释放锁

生产者和消费者问题

见代码

JDK5.0新增线程创建方式

实现Callable接口

需要重写call()方法

  1. 创建实现Callable接口的实现类,并实现call()方法

  2. 创建Callable接口实现类的对象

  3. 使用Callable接口实现类的对象创建FutureTask的对象

  4. 使用FutureTask对象创建Thread对象

  5. 调用Thread对象的start()方法

  6. 如果希望输出call()的返回值,使用FutureTask对象调用自身的get()方法

与使用Runnable相比, Callable功能更强大些

 相比run()方法,可以有返回值

 方法可以抛出异常

 支持泛型的返回值

 需要借助FutureTask类,比如获取返回结果

使用线程池

开发中较常使用线程池,避免频繁创建和销毁线程,还可以实现重复利用。同时便于线程的管理

JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors

ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

 void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行

Runnable

 <T> Future<T> submit(Callable<T> task):执行任务,有返回值,一般又来执行

Callable

 void shutdown() :关闭连接池

Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

 Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池

 Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池

 Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池

 Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运

行命令或者定期地执行。

Executors创建的实际上是ThreadPoolExecutor类的对象

可以强转为ThreadPoolExecutor来获取更多管理方法(因为自动补全的其实声明为了接口)

*//提供指定线程数量的线程池

  • ExecutorService service = Executors.newFixedThreadPool(10);
    *//强转为原来的对象
  • ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
    *//设置线程池的属性
  • service1.setCorePoolSize(15);
    *// service1.setKeepAliveTime();

//提供实现Runnable接口或Callable接口的实现类的对象

  • service.execute(new NumberThread()); *//适用于Runnable
  • service.execute(new Number1Thread()); *//适用于Runnable
    // service.submit(Callable callable); //适用于Callable
    //关闭线程池
  • service.shutdown();

Java常用类

字符串

public final class String
  1. String声明为final,不可被继承

  2. String实现了Serializable接口,表示字符串支持序列化

  3. 实现了Comparable接口,表示 String可以比较大小

  4. String内部定义了final char[] value 用于存储字符串数据

  5. String:代表不可变的字符序列。简称:不可变性。

    1. 当对字符串重新赋值时,需要重新指定一块内存区域,而不能使用原有的value进行赋值

    2. 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能在原有的value进行赋值

    3. 当调用String的replace方法修改指定的字符或字符串时,也必须重新指定内存区域赋值。

  6. **通过字面量(区别于new方式)的方式给一个字符串赋值,此时的字符串值声明在字符串常量池中,**使用new方式的字符串,保存在堆空间中

  7. 常量池中是不会存储相同内容的字符串的。

面试题:String s1 = new String(“abc”);在内存中创建了几个对象?

两个。一个是堆空间中new出来的,一个是char[]对应的常量池中的数据“abc”

String类不同拼接操作的对比

如果拼接操作中出现了变量名,那么就是在堆空间中new了一个新对象:如String s = s1 +
“abc”或是String s = s1 + s2;

如果拼接的都是字面量形式,那么就在常量池中:如String s = “abc” + “defg”

String的intern()方法返回值保存在常量池中,其内容就是当前字符串。

final修饰的变量看做常量,保存在常量池中。

String类常用方法

String没有append和delete,因为不可变!

由于String不可变,很多方法都是以返回值的形式呈现结果,而不是原地更改!

int length():返回字符串的长度: return value.length

char charAt(int index): 返回某索引处的字符return value[index]

**boolean isEmpty():**判断是否是空字符串:return value.length == 0

**String toLowerCase():**使用默认语言环境,将 String 中的所有字符转换为小写

**String toUpperCase():**使用默认语言环境,将 String 中的所有字符转换为大写

String trim():去除前后的空格

**boolean equals(Object obj):**比较字符串的内容是否相同

**boolean equalsIgnoreCase(String anotherString):**与equals方法类似,忽略大小写

**String concat(String str):**将指定字符串连接到此字符串的结尾。
等价于用“+”

int compareTo(String anotherString):比较两个字符串的大小

String substring(int beginIndex):返回一个新的字符串,它是此字符串的从
beginIndex开始截取到最后的一个子字符串。

String substring(int beginIndex, int endIndex)
:返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)
左闭右开

查找:

**boolean endsWith(String suffix):**测试此字符串是否以指定的后缀结束

**boolean startsWith(String prefix):**测试此字符串是否以指定的前缀开始

**boolean startsWith(String prefix, int
toffset):**判断从toffset开始,是否以prefix开头

boolean contains(CharSequence s):当且仅当此字符串包含指定的 char
值序列时,返回true

int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引

int indexOf(String str, int
fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
(用于连续寻找)

**int lastIndexOf(String str):**返回指定子字符串在此字符串中最右边出现处的索引

(从后往前找)

**int lastIndexOf(String str, int
fromIndex):**返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索

注:indexOf和lastIndexOf方法如果未找到都是返回-1

替换:

**String replace(char oldChar, char newChar):**返回一个新的字符串,它是通过用
newChar 替换此字符串中出现的所有 oldChar 得到的。

**String replace(CharSequence target, CharSequence
replacement):**使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。

String replaceAll(String regex, String replacement) : 使用给定的replacement
替换此字符串所有匹配给定的正则表达式的子字符串。

String replaceFirst(String regex, String replacement) :
使用给定的replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。

匹配:

**boolean matches(String regex):**告知此字符串是否匹配给定的正则表达式。

切割:

**String[] split(String regex):**根据给定正则表达式的匹配拆分此字符串。

**String[] split(String regex, int
limit):**根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。

字符串字符数组:

public char[] toCharArray()

字符串字节数组:

public byte[] getBytes() 使用设置过的默认编码集

public byte[] getBytes(String charsetName) 也可以指定编码集

StringBuffer和StringBuilder

StringBuffer:可变的字符序列。线程安全的,效率低 底层使用char[]存储

StringBuilder(JDK5.0新增):可变的字符序列。线程不安全,效率高
底层使用char[]存储

StringBuffer(或StringBuilder)的空参构造器默认创建长度为16的数组,带参构造器则始终将数组长度额外增加16

当需要扩容时,默认扩容为原来的2倍(左移1位)+2,同时复制元素到新的数组中
指导意义:开发中建议使用StringBuffer(int capacity)或StringBuilder(int capacity)

常用方法(Buffer和Builder方法名都一样)

由于可变性,很多方法都是原地更改的,没有返回值

append(xxx):提供了很多的append()方法,用于进行字符串拼接

当append加上的是null时,会将null转换成“null”加入字符串中,长度加4

但是如果直接给构造器赋值null会报空指针异常!!!!

delete(int start,int end):删除指定位置的内容

replace(int start, int end, String str):把[start,end)位置替换为str

insert(int offset, xxx):在指定位置插入xxx

reverse() :把当前字符序列逆转

public int indexOf(String str)

public String substring(int start,int end)

public int length()

public char charAt(int n )

public void setCharAt(int n ,char ch)

时间

java.lang.System类

System类提供的public static long currentTimeMillis()用来返回当前时

间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。

称为时间戳。

java.util.Date类

java.util.Date类

|—java.sql.Date类

1.两个构造器的使用

>构造器一:Date():创建一个对应当前时间的Date对象

>构造器二:Date(long time)创建指定毫秒数的Date对象

2.两个方法的使用

>toString():显示当前的年、月、日、时、分、秒

>getTime():获取当前Date对象对应的毫秒数。(时间戳)

3. java.sql.Date对应着数据库中的日期类型的变量

>如何实例化

>如何将java.util.Date对象转换为java.sql.Date对象?

//情况一(多态的正常转换):

Date date4 = new java.sql.Date(2343243242323L);

java.sql.Date date5 = (java.sql.Date) date4;

//情况二(使用getTime获取时间戳):

Date date6 = new Date();

java.sql.Date date7 = new java.sql.Date(date6.getTime());

SimpleDateFormat

主要用于Date的解析和格式化

格式化:日期–>字符串,使用format方法

“yyyy-MM-dd hh:mm:ss” 可以指定格式(调用带参构造器)

解析:字符串–>日期,使用parse方法

Calendar(抽象类)
  1. 实例化:实例化子类GreGorianCalendar,或者调用其静态方法getInstance()

  2. get() 获取各种天数 set设置各种天数 一月是0 周日是1

  3. getTime() 转换成Date setTime用Date设置Calendar

JDK 8新的日期时间API

Date Calendar

Calendar将日期时间设置为可变的,月份

Date中的日期是自1900年起开始的偏移量,并不是真实的日期

格式化只对Date有用,对Calendar没有用

Date和Calendar也都不是线程安全的,也没有考虑地球自转带来的闰秒

因此在Java 8中引入了java.time API 纠正了过去的缺陷

LocalDate、LocalTime、LocalDateTime 的使用

说明:

1.LocalDateTime相较于LocalDate、LocalTime,使用频率要高

2.类似于Calendar

3.不可变

黄色的是实例化用的方法,没有偏移量

getXxx获取相关属性 withXxx设置相关属性(不可变,所以是返回值的形式)

plusXxx增加某段时间 minusXxx减少某段时间

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aGwJYkDw-1643032559669)(media/63ff28e109b3670f2bc6d8067801f06f.png)]

Instant瞬时

类似于java.utils.Date

Instant记录从1970年开始的毫秒数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HIFTeEIP-1643032559670)(media/1ea13cdfc9bebce42efad91fab6d1af6.png)]

now()之后的是本初子午线的日期时间,用对象去调用atOffset(ZoneOffset.ofHours(8))来获取东八区时间。

DateTimeFormatter

格式化和解析日期和时间

类似于SimpleDateFormat

方式一:预定义的标准格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME

方式二:本地化相关的格式。如:ofLocalizedDateTime()

FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT :适用于LocalDateTime

本地化相关的格式。如:ofLocalizedDate()

FormatStyle.FULL / FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT :
适用于LocalDate

方式三:自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss E”) 其中E表示星期几

Java比较器

使用两个接口中的任何一个:Comparable(自然排序) 或 Comparator(定制排序)

Comparable接口与Comparator的使用的对比:

*
Comparable接口的方式一旦一定,保证Comparable接口实现类的对象在任何位置都可以比较大小。

* Comparator接口属于临时性的比较

Comparable自然排序

实现Comparable接口需要重写CompareTo方法

重写compareTo(obj)的规则:

如果当前对象this大于形参对象obj,则返回正整数,

如果当前对象this小于形参对象obj,则返回负整数,

如果当前对象this等于形参对象obj,则返回零

Comparator定制排序

1.背景:

当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码,

或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,

那么可以考虑使用 Comparator 的对象来排序

2.重写**compare(Object o1,Object o2)**方法,比较o1和o2的大小:

如果方法返回正整数,则表示o1大于o2;

如果返回0,表示相等;

返回负整数,表示o1小于o2。

System类

全都是静态的方法:

currentTimeMillis();

exit(int status); 退出程序 status为0表示正常退出,非0表示异常退出

gc(); 请求系统的垃圾回收,致郁是否立即回收,取决于系统的拉垃圾回收算法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZNJSTdu5-1643032559671)(media/f8bf69ed0aecce30e3db41ba3d73b882.png)]

Math类

都是静态的方法

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) 角度—>弧度

枚举类(JDK 5.0 新增)

JDK 5.0 之前需要自定义枚举类

JDK 5.0 以后可以视同enum关键字定义

当需要定义一组常量时,强烈建议使用枚举类!!

如果枚举类只有一个对象,则可以作为单例模式的实现方式

JDK5.0之后,有了enum关键字

enum Season{
SPRING(“春天”,“春暖花开”),
SUMMER(“夏天”,“夏日炎炎”),
AUTUMN(“秋天”,“秋高气爽”),
WINTER(“冬天”,“冰天雪地”);

private final String SeasonName;
private final String SeasonDesc;

private Season(String SeasonName,String SeasonDesc){
this.SeasonName = SeasonName;
this.SeasonDesc = SeasonDesc;
}

}

//默认继承于Enum类,不需要重写toString(),自动toString为枚举类对象的名称,如:“SPRING”

JDK 5.0之前,需要自己写一个类

class Season1{
//声明Season1对象的属性
private final
String Season1Name;
private final String Season1Desc;
//私有化构造器
private
Season1(String Season1Name,String Season1Desc){
this.Season1Desc = Season1Desc;
this.Season1Name = Season1Name;
}

//提供枚举类的多个对象
public static final
Season1 SPRING = new Season1(“春天”,“春暖花开”);
public static final Season1 SUMMER = new Season1(“夏天”,“夏日炎炎”);
public static final Season1 AUTUMN = new Season1(“秋天”,“秋高气爽”);
public static final Season1 WINTER = new Season1(“冬天”,“冰天雪地”);

Enum类的常用方法和实现接口

.values() 输出数组,里面存放枚举类所有的对象

.valueOf(String name),根据名字,返回枚举类的对象;找不到就抛出异常:

IllegalArgumentException

实现接口时,

可以在类中实现接口抽象方法

有需求时,在每一个枚举类的对象后面都实现抽象方法

注解Annotation

JDK5.0开始,增加了对原数据(MetaData)的支持,也就是Annotation(注解)

注解就是代码里的特殊标记

注解是一种趋势,未来的开发模式都是基于注解的,更加简洁

一定程度上,可以说:框架 = 注解 + 反射 + 设计模式

2. Annocation的使用示例

* 示例一:生成文档相关的注解

* 示例二:在编译时进行格式检查(JDK内置的三个基本注解)

@Override: 限定重写父类方法, 该注解只能用于方法

@Deprecated: 用于表示所修饰的元素(类,
方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择

@SuppressWarnings: 抑制编译器警告

* 示例三:跟踪代码依赖性,实现替代配置文件功能

自定义注解

参照@SuppressWarnings的定义

  1. 注解声明为@interface

  2. 自定义注解自动继承了Annotation接口

  3. Annotation中的属性以无参方法来声明,无参方法的返回值就是该属性的类型。只能是基本数据类型和String、Class、enum、Annotation、数组。

  4. 可以在定义属性时设置初始值,使用default关键字

  5. 如果只有一个属性,建议参数名为value

  6. 没有属性的Annotation叫做标记

如果注解有成员,在使用注解时,要制定成员的值

public @interface MyAnnotation {
String value() default “hello”;
}

自定义注解必须配上注解的信息处理流程(反射)才有意义

JDK的四种元注解

元注解是用来修饰其他Annotation定义的

  1. @Retention 表明注解的生命周期(以枚举类的形式)

    SOURCE:编译时不会保留此注解

    CLASS:默认行为。会被编译到.class文件中,但是运行时不会被加载到内存中

    RUNTIME:既被编译到.class文件中,也会被加载到从内存中,此时可以被反射读取

  2. @Target:指明自定义注解能够修饰那些结构

自定义注解通常都会指明以上两个元注解

************************************************

  1. @Documented:表示所修饰的注解在被javadoc解析时,保留下来(默认不保留)

  2. @Inherited:被它修饰的注解即将具有继承性(即子类会继承父类的Annotation)

JDK 8中注解的新特性

  1. 可重复注解

    1. 在MyAnnotation上声明@Repeatable,成员值为MyAnnotations.class


      MyAnnotation的Target、Retention、Inherited等元注解与MyAnnotations相同。

  2. 类型注解

    ElementType.TYPE_PARAMETER
    表示该注解能写在类型变量的声明语句中(如:泛型声明)。

    ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。

Java集合

集合和数组都是Java容器

Java集合可以分为Collection和Map两种体系

* 集合框架

* |----Collection接口:单列集合,用来存储一个一个的对象

* |----List接口:存储有序的、可重复的数据。 -->“动态”数组

* |----实现类ArrayList、LinkedList、Vector

*

* |----Set接口:存储无序的、不可重复的数据 -->相当于数学集合

* |----实现类HashSet、LinkedHashSet、TreeSet

*

* |----Map接口:双列集合,用来存储一对(key - value)一对的数据

* |----实现类HashMap、LinkedHashMap、TreeMap、Hashtable、Properties

Collection接口

在添加元素到Collection当中时,要求重写元素的equals()方法

add() 添加一个元素

addAll(Collection collection) 将容器中的元素都添加进去

size() 返回长度

clear() 清空容器

contains(Object obj) 内部使用equals来比对数据,需要重写equals方法!

containsAll(Collection collection) 判断collection中的数据是否都在里面

Arrays工具类下有asList(…)可以快速创建一个ArrayList

remove和removeAll() 删除元素(差集)

retainAll() 取交集,原地修改

equals(Object obj):要想返回true,需要当前集合和形参集合的元素都相同。

对于List下的容器,顺序也要相同

hashCode():返回当前对象的哈希值

集合 —>数组:toArray()

数组 —>集合:调用Arrays类的静态方法asList()

注意传入int[ ]时会被认为是一个元素,需要传入Integer[
]才会认为是可变形参的多个元素

iterator():返回Iterator接口的实例,用于遍历集合元素。放在IteratorTest.java中测试

迭代器接口Iterator

Iterator只用来遍历Collection,不用来遍历Map

迭代器也是一种设计模式

//方式三:推荐
while
(iterator.hasNext()){
System.out.println(iterator.next());
}

Iterator默认指向第1个元素的前一个位置

hasNext()判断下一个元素是否为空

next()指针指向下一个元素,并返回指针指向的元素

remove()删除指针指向的元素

异常IllegalStateException,因为要删除的位置为空

JDK5.0:forEach循环

可以用来遍历Collection和数组

for(集合元素的类型 局部变量 : 集合对象)

内部仍然调用了迭代器。

for(Object obj : coll){
System.out.println(obj);
}

注意obj是形参,修改它并不会改变原本数组(集合)里的值

子接口List(有序)

|----Collection接口:单列集合,用来存储一个一个的对象

|----List接口:存储有序的、可重复的数据。 -->“动态”数组,替换原有的数组

|----ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[]
elementData存储

|----LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储

|----Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[]
elementData存储

面试题:ArrayList、LinkedList、Vector三者的异同?

* 同:三个类都是实现了List接口,存储数据的特点相同:存储有序的、可重复的数据

* 不同:见上

常用方法

首先Collection接口中的方法List都继承了过来

由于List接口表示有序的,所以还添加了一些和索引有关的方法

void add(int index, Object elem):在index位置插入elem元素

boolean addAll(int index, Collection
eles):从index位置开始将eles中所有元素添加进来

Object get(int index):获取指定index位置的元素

int indexOf(Object obj):返回obj在集合中首次出现的位置。如果不存在,返回-1.

int lastIndexOf(Object
obj):返回obj在当前集合中末次出现的位置。如果不存在,返回-1.

Object remove(int index):移除指定index位置的元素,并返回此元素

Object set(int index, Object ele):设置指定index位置的元素为ele

List subList(int fromIndex, int
toIndex):返回从fromIndex到toIndex位置的左闭右开区间的子集合

ArrayList的源码分析:

* jdk 7情况下

* ArrayList list = new
ArrayList();//底层创建了长度是10的Object[]数组elementData

* list.add(123);//elementData[0] = new Integer(123);

* …

* list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。

*
默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。

*

* 结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int
capacity)

*

* jdk 8中ArrayList的变化:

* ArrayList list = new ArrayList();//底层Object[]
elementData初始化为{}.并没有创建长度为10的数组

*

*
list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]

* …

* 后续的添加和扩容操作与jdk 7 无异。

*
小结:jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存

LinkList的源码解析

LinkedList list = new LinkedList();
内部声明了Node类型的first和last属性,默认值为null

* list.add(123);//将123封装到Node中,创建了Node对象。

*

* 其中,Node定义为:体现了LinkedList的双向链表的说法

* private static class Node<E> {

E item;

Node<E> next;

Node<E> prev;

Node(Node<E> prev, E element, Node<E> next) {

this.item = element;

this.next = next;

this.prev = prev;

}

}

Vector的源码分析:

即使Vector线程安全,开发中一般也不会使用

可以使用Collections工具类下面的静态方法synchronizedXxx()来将某个Collection同步化以适用于多线程

jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。

在扩容方面,默认扩容为原来的数组长度的2倍。

子接口Set(无序)

Set接口中没有额额外定义的方法,都是Collection中声明过的方法

|----Collection接口:单列集合,用来存储一个一个的对象

|----Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”

|----HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值

|----LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历对于频繁的遍历操作LinkedHashSet效率高于HashSet.

|----TreeSet:可以按照添加对象的指定属性,进行排序。

存储无序的、不可重复的数据

无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的。

不可重复性:保证添加的元素按照equals()判断时,不能返回true.即:相同的元素只能添加一个。

以下内容以HashSet为例:

我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断数组此位置上是否已经有元素:

如果此位置上没有其他元素,则元素a添加成功。 —>情况1

如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:

如果hash值不相同,则元素a添加成功。—>情况2

如果hash值相同,进而需要调用元素a所在类的equals()方法:

equals()返回true,元素a添加失败

equals()返回false,则元素a添加成功。—>情况3

对于添加成功的情况2和情况3而言:元素a
与已经存在指定索引位置上数据以链表的方式存储。

jdk 7 :元素a放到数组中,指向原来的元素。

jdk 8 :原来的元素在数组中,指向元素a

总结:七上八下,(7为头插法,8为尾插法)

要求:

向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()

要求:

重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码(hash值)

重写两个方法的小技巧:对象中用作 equals() 方法比较的
Field(属性),都应该用来计算 hashCode 值。

LinkedHashSet

LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。

优点:对于频繁的遍历操作,LinkedHashSet效率高于HashSet

TreeSet

TreeSet存储的数据要求是同一个类的对象,这样才能比较大小

两种排序方式:自然排序(实现Comparable接口) 和 定制排序(Comparator)

自然排序中,比较两个对象是否相同的标准为:compareTo()是否返回0,而不再是equals()

定制排序中,比较两个对象是否相同的标准为:compare()是否返回0,而不再是equals().

定制排序可以在TreeSet的构造器中指明

Map接口

Map的实现类的结构:

|----Map:双列数据,存储key-value对的数据 —类似于高中的函数:y = f(x)

|----HashMap:作为Map的主要实现类;线程不安全的,效率高;可以存储null的key和value

|----LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。

原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。对于频繁的遍历操作,此类执行效率高于HashMap。

|----TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序,

底层使用红黑树

|----Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value

|----Properties:常用来处理配置文件。key和value都是String类型

HashMap的底层:数组+链表 (jdk7及之前)

数组+链表+红黑树 (jdk 8)

面试题:

* 1. HashMap的底层实现原理?

* 2. HashMap 和 Hashtable的异同?

* 3. CurrentHashMap 与 Hashtable的异同?(暂时不讲)

二、Map结构的理解:

Map中的key:无序的、不可重复的,使用Set存储所有的key —>
key所在的类要重写equals()和hashCode() (以HashMap为例)

Map中的value:无序的、可重复的,使用Collection存储所有的value
—>value所在的类要重写equals()

一个键值对:key-value构成了一个Entry对象。

Map中的entry:无序的、不可重复的,使用Set存储所有的entry

HashMap的底层实现原理

HashMap map = new HashMap();

JDK 7

HashMap map = new HashMap():

在实例化以后,底层创建了长度是16的一维数组Entry[] table。

…可能已经执行过多次put…

map.put(key1,value1):

首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。

如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况1

如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:

如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。
----情况2

如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:

如果equals()返回false:此时key1-value1添加成功。 ----情况3

如果equals()返回true:使用value1替换value2。

补充:关于情况2和情况3:此时key1-value1和原来的数据以**链表**的方式存储。

在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。

JDK 8

jdk8 相较于jdk7在底层实现方面的不同:

* 1. new HashMap():底层没有创建一个长度为16的数组

* 2. jdk 8底层的数组是:Node[],而非Entry[]

* 3. 首次调用put()方法时,底层创建长度为16的数组

* 4. jdk7底层结构只有:数组+链表。jdk8中底层结构:数组+链表+红黑树。

* 4.1
形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)

4.2 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8
且当前数组的长度 >
64时
,此时此索引位置上的所有数据改为使用红黑树存储。

涉及到的概念(常量):

DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16

DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75

threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12

JDK8新增:

TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8

MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64

加载因子DEFAULT_LOAD_FACTOR设置的越小,链表的结构就会少一些,

LinkedHashMap(了解)

内部定义了Entry而不是Node,但是继承与Node

并且提供了记录插入顺序的before和after“指针”

源码中:

static class Entry<K,V> extends HashMap.Node<K,V> {

Entry<K,V> before, after;//能够记录添加的元素的先后顺序

Entry(int hash, K key, V value, Node<K,V> next) {

super(hash, key, value, next);

}

}

Map接口的常用方法

添加、删除、修改操作:

Object put(Object key,Object
value):将指定key-value添加到(或修改)当前map对象中

void putAll(Map m):将m中的所有key-value对存放到当前map中

Object remove(Object key):移除指定key的key-value对,并返回value

void clear():清空当前map中的所有数据

元素查询的操作:

Object get(Object key):获取指定key对应的value

boolean containsKey(Object key):是否包含指定的key

boolean containsValue(Object value):是否包含指定的value

int size():返回map中key-value对的个数

boolean isEmpty():判断当前map是否为空

boolean equals(Object obj):判断每一个键值对是否都相等

元视图操作的方法(遍历):

Map没有迭代器!!可以通过key构成的Set或values构成的Collection或键值对构成的Set中的迭代器进行遍历

Set keySet():返回所有key构成的Set集合

Collection values():返回所有value构成的Collection集合

Set entrySet():返回所有key-value对构成的Set集合

Hashtable子类Properties

Properties常用来处理配置文件。key和value都是String类型

新建文件xxx.properties

键值之间使用=连接 ,注意不要多加空格,以免发生歧义

使用IO流读取.properties文件,再使用properties对象的load(FileStream fs)方法来读取

getXxx获取键值

Collections工具类

Collections可以操作List、Set、Map(同步控制)

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中

ArrayList list = new ArrayList();
list.add(123);
list.add(96);
list.add(187);
list.add(324);
list.add(15);
*//要先保证dest的size够大,注意size不是底层数组的长度,而是当前List里有多少元素!!!

//错误的写法:
// ArrayList dest = new ArrayList(list.size());
//可以传一个Object数组进去吧size撑大

  • List dest = Arrays.asList(new Object[list.size()]);
    Collections.copy(dest,list);

System.out.println(dest);

boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List
对象的所有旧值

Collections 类中提供了多个 synchronizedXxx() 方法,

该方法可使将指定集合包装成线程同步的集合,从而可以解决

多线程并发访问集合时的线程安全问题

泛型Generic

泛型不能是基本数据类型,需要用他的包装类

如果实例化时没有指定泛型,默认泛型为java.lang.Object

泛型可以用多个参数<T1,T2,T3>

JDK7 新特性:类型推断

TreeSet<Employee> set = new TreeSet**<>**();

后面就不需要再写一遍泛型了,只需要保留<>即可

自定义泛型结构

泛型类

public class Order<T> {

String orderName;
int orderId;

*//类的内部结构可以使用泛型

  • T orderT;

}

继承的情况

public class SubOrder extends Order<Integer>{
}

此时SubOrder不再是泛型类

public class SubOrder<T> extends Order<T>{
}

此时SubOrder仍然是泛型类

注意!

声明泛型类的构造器时不要再加上泛型<>了 ,实例化的时候要加

泛型不同的引用不能相互赋值

由于泛型在实例化时才被指定,而类的静态结构早于对象的创建,因此静态结构不能使用类的泛型

异常类不能声明为泛型类

不能直接new一个不确定的泛型:

*//编译不通过!
*T[] arr = new T[20];
*//但是可以这样写:
*T[] arr1 = (T[])new Object[20];

泛型方法

class Order<E>{

E show(E e){

//此方法不是泛型方法!

}

public <T> List<T> copyFromArrayToList(T[] arr){

//此方法是一个泛型方法

}

}

泛型方法中出现了泛型的结构,但泛型参数与类的泛型参数没有任何关系。

换句话说,泛型方法所属的类是不是泛型类都没有关系。

泛型方法可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。

泛型的继承

虽然类A是类B的父类,但是G<A>
和G<B>二者不具备子父类关系,二者是并列关系。

故不能互相赋值

补充:类A是类B的父类,A<G> 是 B<G> 的父类

故可以相互赋值

通配符的使用

Java中通配符指的是:?

由于G<A>和G<B>不存在继承关系,故使用通配符来实现继承关系,从而实现多态

如:

类A是类B的父类,G<A>和G<B>是没有关系的,则二者共同的父类是:G<?>

对于List<?>就不能向其内部添加数据。可以添加null

但是允许读取数据,读取的数据类型为Object。

有限制条件的通配符的使用。

? extends A:

G<? extends A> 可以作为G<A>和G<B>的父类,其中B是A的子类,小于等于

? super A:

G<? super A> 可以作为G<A>和G<B>的父类,其中B是A的父类,大于等于

以上情况也可以读取数据,super的情况可以添加数据

IO流

File类

File类的一个对象,代表一个文件或一个文件目录(俗称:文件夹)

声明在java.io包下

File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,

并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO流来完成。

后续File类的对象常会作为参数传递到流的构造器中,指明读取或写入的"终点".

关于路径分隔符:

路径分隔符和系统有关:

windows和DOS系统默认使用“\”来表示,而Java中\为转义符,因此需要写两个,如\\

UNIX和URL使用“/”来表示

如何创建File类的实例

File(String filePath) 指的是文件

File(String parentPath,String childPath)
指的是parentPath下面的childPath文件目录

File(File parentFile,String childPath) 指的是parentFile目录下面的childPath文件

文件相关方法:

public String getAbsolutePath():获取绝对路径

public String getPath() :获取路径

public String getName() :获取名称

public String getParent():获取上层文件目录路径。若无,返回null

public long length() :获取文件长度(即:字节数)。不能获取目录的长度。

public long lastModified() :获取最后一次的修改时间,毫秒值

如下的两个方法适用于文件目录:

public String[] list() :获取指定目录下的所有文件或者文件目录的名称数组

public File[] listFiles() :获取指定目录下的所有文件或者文件目录的File数组

重命名:

public boolean renameTo(File dest):把文件重命名为指定的文件路径

比如:file1.renameTo(file2)为例:

要想保证返回true,需要file1在硬盘中是存在的,且file2不能在硬盘中存在。

判断:

public boolean isDirectory():判断是否是文件目录

public boolean isFile() :判断是否是文件

public boolean exists() :判断是否存在

public boolean canRead() :判断是否可读

public boolean canWrite() :判断是否可写

public boolean isHidden() :判断是否隐藏

文件的真实创建和删除

创建硬盘中对应的文件或文件目录

public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false

public boolean mkdir()
:创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。

public boolean mkdirs()
:创建文件目录。如果此文件目录存在,就不创建了。如果上层文件目录不存在,一并创建

删除磁盘中的文件或文件目录

public boolean delete():删除文件或者文件夹

删除注意事项:Java中的删除不走回收站。

//要想删除成功,io4文件目录下不能有子目录或文件

File file3 = new File(“D:\\io\\io1\\io4”);

file3 = new File(“D:\\io\\io1”);

System.out.println(file3.delete());

IO流的体系结构

可以按操作对象分为:节点流(直接作用于文件)和处理流(作用域其他流)

按传输的基本单位分为:字节流和字符流

按传输的方向分为:输入流和输出流

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0keLEgAn-1643032559673)(media/30c37f389c85f2974ce4bb2fceae2f68.png)]

四个基本的抽象基类:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-scoSoQrQ-1643032559674)(media/8d898911f1563dcf6ee2990f7fd34665.png)]

所有的流如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s1DYHz5T-1643032559676)(media/1222231781802cf698a0d4b3000db542.png)]

抽象基类 节点流(或文件流) 缓冲流(处理流的一种)

InputStream FileInputStream(read(byte[] buffer)) BufferedInputStream
(read(byte[] buffer))

OutputStream FileOutputStream(write(byte[] buffer,0,len)
BufferedOutputStream(write(byte[] buffer,0,len) / flush()

Reader FileReader (read(char[] cbuf)) BufferedReader (read(char[] cbuf) /
readLine())

Writer FileWriter (write(char[] cbuf,0,len) BufferedWriter (write(char[]
cbuf,0,len) / flush()

代码标准写法

读入:

read(): 一个一个的读字符

read(char[]):一次读入多个字符,取决于数组长度

@Test
public void
test2() {
FileReader fr = null;
try {
File file = new File(“hello.txt”);
fr = new FileReader(file);
char[] cbuf = new char[5];
int len;
while ((len = fr.read(cbuf)) != -1) {

//要用len而不是数组的length,因为最后读到的数据数组可能

**装不满!!!!
** *//文件读取的遍历方式一
// for (int i = 0; i < len; i++) {
// System.out.print(cbuf[i]);
// }
//方式二

  • String s = new String(cbuf, 0, len);
    System.out.print(s);
    }
    } catch (IOException e) {
    e.printStackTrace();
    } finally {
    if (fr != null) {注意此处的空指针一定要考虑,if写try里面外面都行
    try {
    fr.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }
    }

写出:

File可以不存在,会自动创建

若File已存在,则覆盖写,因为FileWriter()的第二个参数append默认是false

可以手动指定FileWriter (String str,boolean append)来进行追加写

关闭多个IO流时要注意:

即使一个流关闭时除了异常,其他的流也一定要关闭

因此不能将多个流卸载同一个try—catch中,但可以用finally

最好还是并列好几个try—catch(个人认为)

节点流中的字节流和字符流

FileReader和FileWriter

FileInputStream和FileOutputStream

字符流:用于文本文件(.txt,.java,.c,.cpp)

字节流,非文本文件(jpg,mp3,mp4,doc,avi,ppt等)

值得注意的是:

由于ASC码的存在,一个英文和数字(英文字符)仍是以一个字节的形式存储的,所以使用字节流也可以操作。

但是中文不是,在UTF-8中,一个汉字(中文字符)使用三个字节存储,因此中文不能用字节流操作(尤其是读取),只能使用字符流

但是单纯的搬运数据,字节流也可以办到(如复制,然后再window资源管理器中查看(解码)),只是无法解析

缓冲流(中的字节流和字符流)

BufferedInputStream和BufferedOutputStream

BufferedReader和BufferedWriter

目的是为了提高流的读取和写入速度

缓冲流内部定义了一个缓冲区来提高速度,容量为8192字节

缓冲流需要包在节点流的外面

关流的时候先关外层的流(即缓冲路流),再关内层的流(即节点流)

实际上:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略.

BufferedReader中多了一个方法:

String readline() 当读不到时返回null,不会读入换行符

BufferedWriter中多了一个方法:

void newline() 换行

转换流

InputStreamReader和InputStreamWriter

处理流的一种,属于字符流(看后缀),提供了字节流和字符流之间的转换

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OaprUgie-1643032559677)(media/9b55ba7f4b8b4ac4aca3fd3312cdc6ef.png)]

irs = new InputStreamReader(fis, “UTF-8”);
osw = new OutputStreamWriter(fos, “gbk”);

标准输入输出流

标准输入:键盘输入

标准输出:控制台输出

System类中的两个属性:

System.in:标准的输入流,默认从键盘输入

System.out:标准的输出流,默认从控制台输出

System类的setIn(InputStream is) / setOut(PrintStream
ps)方式重新指定输入和输出的流。

方法一:使用Scanner实现,调用next()返回一个字符串

方法二:使用System.in实现。System.in —> 转换流 —>
BufferedReader的readLine()

打印流

字节输出流和字符输出流

PrintStream和PrintWriter

提供了一系列重载的print() 和 println()

打印流还用于设置系统打印的地方:

FileOutputStream fos = new FileOutputStream(new File(“D:\\IO\\text.txt”));

// 创建打印输出流,设置为自动刷新模式(写入换行符或字节 ‘\n’
时都会刷新输出缓冲区)

ps = new PrintStream(fos, true);

if (ps != null) {

System.setOut(ps); // 此处把标准输出流(控制台输出)改成了文件

}

数据流

DataInputStream 和 DataOutputStream

作用:用于读取或写出基本数据类型的变量或字符串

write以后要手动flush,才能写入到文件中

读取的顺序要与当初写入文件时,保存的数据的顺序一致!

对象流

ObjectInputStream和ObjectOutPutStream

可以存取基本数据类型,也可以存取对象

序列化

将对象写入到数据源中的过程,叫做序列化

将对象从数据源中还原回来,叫做反序列化

对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。

当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。

Person需要满足如下的要求,方可序列化

1.需要实现接口:Serializable

2.当前类提供一个全局常量:serialVersionUID

3.除了当前Person类需要实现Serializable接口之外,还必须保证其内部所有属性也必须是可序列化的。(默认情况下,基本数据类型可序列化)

注意:

ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量

static变量不归对象所有,所以不能序列化;而transient关键字则是标识当前变量不用序列化的工具

随机存取文件流

RandomAccessFile

既可以输入也可以输出,其直接继承于Object类,而不是四个抽象基类

构造器:RandomAccessFile(String filename,String mode)

RandomAccessFile(File file,String mode)

关于 mode参数:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CbfRJNW0-1643032559678)(media/19a4c04e8c4ded358524d41f196b17f0.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ezxs29xT-1643032559679)(media/19a4c04e8c4ded358524d41f196b17f0.png)]

如果RandomAccessFile作为输出流时,写出到的文件如果不存在,则在执行过程中自动创建。

如果写出到的文件存在,则会对原有文件内容进行覆盖。(默认情况下,从头覆盖)

区别于节点流的文件覆盖

seek()方法可以定位文件指针,使用File类的length()方法可以定位到末尾(最后一个元素的后面一位)

网络编程

InetAddress类:就相当于网络ip

InetAddress.getByName(String ip)
来创建对象,支持域名(会自动连接DNS服务器来解析)

本地回路地址:127.0.0.1 对应:localhost

InetAddress.getLocalHost()来获取本机地址

getHostName()获取域名 getHostAddress()获取ip地址

端口号

被规定为一个 16 位的整数 0~65535。

端口分类:

**公认端口:**0~1023。被预先定义的服务通信占用(如:HTTP占用端口

80,FTP占用端口21,Telnet占用端口23)

**注册端口:**1024~49151。分配给用户进程或应用程序。(如:Tomcat占

用端口8080,MySQL占用端口3306,Oracle占用端口1521等)。

**动态/私有端口:**49152~65535。

端口号与IP地址的组合得出一个网络套接字:Socket。

TCP/IP协议簇

传输层协议中有两个非常重要的协议:

传输控制协议TCP(Transmission Control Protocol)

用户数据报协议UDP(User Datagram Protocol)。

**TCP/IP
以其两个主要协议:传输控制协议(TCP)和网络互联协议(IP)**而得名,实际上是一组协议,包括多个具有不同功能且互为关联的协议。

IP(Internet Protocol)协议是网络层的主要协议,支持网间互连的数据通信。

TCP/IP协议模型从更实用的角度出发,形成了高效的四层体系结构,即

物理链路层、IP层、传输层和应用层

UDP可用于视频的传输等

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TnC1E4TJ-1643032559680)(media/46e2668ac43c5a81a42f7c3f532a7543.png)]

TCP网络编程

UDP网络编程

使用DataGramSocket,并且封装一个数据报:

DataGramPacket(byte[] data,0,length,InetAddress inet,int port)

socket.send(packet)

socket.close()

接收端:DataGramSocket(int port) 只需指明端口号

也需要创建一个数据报来存放接受的数据

socket.receive(packet)

URL编程

如果查看网络中的资源不是通过接收资源,而是通过资源地址直接访问,就是URL编程的思路。

URL:Uniform Resource Locator 统一资源定位符,表示Internet中某一资源的地址

种子就是URL的一种

有了地址不一定就能访问到资源,只有拥有资源的服务器开启了,才能访问到

URL的基本结构由5部分组成:

<传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表

例如:

http://192.168.1.100:8080/helloworld/index.jsp#a?username=shkstart&password=123

#片段名:即锚点,例如看小说,直接定位到章节

参数列表格式:参数名=参数值&参数名=参数值…

new URL(String url)

URLConnection uc = url.openConnection()

uc.connect();

uc.getInputStream()表示从url中读数据

uc.getOutPutStream()表示将读出的数据写出

反射

Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期

借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内

部属性及方法。

加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个

类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可

以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看

到类的结构,所以,我们形象的称之为:反射

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JrnHArfp-1643032559681)(media/e1c7c03f2a69c32ee78529050c4f228d.png)]

Java反射机制提供的功能

在运行时判断任意一个对象所属的类

在运行时构造任意一个类的对象

在运行时判断任意一个类所具有的成员变量和方法

在运行时获取泛型信息

在运行时调用任意一个对象的成员变量和方法

在运行时处理注解

//疑问1:通过直接new的方式或反射的方式都可以调用公共的结构,开发中到底用那个?

//建议:直接new的方式。

//什么时候会使用:反射的方式。 反射的特征:动态性

有时候在实现不知道要早哪个类的对象,只有运行时才能确定,此时就必须使用反射

也体现了动态性。程序在跑起来之后,动态的根据需求造对象

//疑问2:反射机制与面向对象中的封装性是不是矛盾的?如何看待两个技术?

//不矛盾。

关于java.lang.Class的理解

1.类的加载过程:

程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。

接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件

**加载到内存中。此过程就称为类的加载。**加载到内存中的类,我们就称为运行时类,此

运行时类,就作为Class的一个实例。

2.换句话说,Class的实例就对应着一个运行时类。

3.加载到内存中的运行时类,**会缓存一定的时间。**在此时间之内,我们可以通过不同的方式来获取此运行时类。

获取Class实例的方式:

*//方式一:调用运行时类的属性
*Class<Person> personClass = Person.class;
System.out.println(personClass);

*//方式二:通过运行时类的对象来获取
*Person person = new Person();
Class personClass1 = person.getClass();
System.out.println(personClass1);

//方式三:调用Class静态方法:forName(String classPath) 常用*
*Class personClass2 = Class.forName(“com.citycheng.java.Person”);
System.out.println(personClass2);

*//方式四:实用类的加载器:ClassLoader(了解)
*ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class personClass3 = classLoader.loadClass(“com.citycheng.java.Person”);
System.out.println(personClass3);

哪些类型可以有Class对象?

(1)class: 外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类

(2)interface:接口

(3)[]:数组,只要数组的元素类型和维度一样,就是同一个Class

(4)enum:枚举

(5)annotation:注解@interface

(6)primitive type:基本数据类型

(7)void

类的加载过程(了解)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qSfeMXC6-1643032559681)(media/c26030789175717dc52570726601d205.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aLCJnUEn-1643032559682)(media/7fc3d4942cc9f7e1b22f80d3f50ea015.png)]

在链接环节,static变量被初始化为类型的默认值

在初始化环节,这些变量才会被赋值为我们初始化的值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kTXu00pv-1643032559683)(media/c7552ea434cf4c702f80f0682ea88a70.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LxWEw4Iw-1643032559684)(media/f16d9f8cc780fb9b50e020ac369f4c98.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e92A37D7-1643032559685)(media/561ffc289821730142d68e4598469a42.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WKDfbQZz-1643032559686)(media/dd6ca73994cea50b2c1ddaf79df3361b.png)]

通过反射创建运行时类的对象

使用运行时类的newInstance()方法。其内部调用了运行时类的空参构造器

要想此方法正常的创建运行时类的对象,要求:

1.运行时类必须提供空参的构造器

2.空参的构造器的访问权限得够。通常,设置为public。

在javabean中要求提供一个public的空参构造器。原因:

1.便于通过反射,创建运行时类的对象

2.便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器

反射获取类的结构

getFields():获取当前运行时类及其父类中声明为public访问权限的属性

getDeclaredFields():获取当前运行时类中声明的所有属性。(不包含父类中声明的属性)

getMethods():获取当前运行时类及其所有(包括Object)父类中声明为public权限的方法

getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)

getConstructors():获取当前运行时类中声明为public的构造器

getDeclaredConstructors():获取当前运行时类中声明的所有的构造器

getSuperclass()获取运行时类的父类,此方法拿不到泛型

getGenericSuperclass()获取运行时类的带泛型的父类,此方法可以拿到泛型

Class clazz = Person.class;

*//拿到带泛型的运行时类,拿到的是Type类型的通用类型

  • Type genericSuperclass = clazz.getGenericSuperclass();
    *//转换成泛型类型的类
  • ParameterizedType paramType = (ParameterizedType) genericSuperclass;
    *//获取泛型类型,因为可能有多个泛型,所以返回数组
  • Type[] actualTypeArguments = paramType.getActualTypeArguments();
    *// System.out.println(actualTypeArguments[0].getTypeName());
  • System.out.println(((Class)actualTypeArguments[0]).getName());

getInterfaces()获取运行时类实现的接口

getPackage获取运行时类所在的包

getAnnotations()获取运行时类声明的注解

反射调用类的结构

不常用:

Field id = personClass.getField(“id”);

由于属性非静态,所以调用属性一定要有对象

id.set(p,1001) 参数1:指明设置哪个对象的属性 参数2:将此属性值设置为多少

id.get§: 参数1:获取哪个对象的当前属性值

获取指定的属性(常用):

getDeclaredField(“id”) 获取所有声明的属性 需要掌握!

由于封装性,对于权限非public的要先设置成可访问的

id.setAccessible(true);

获取指定的某个方法

getDeclaredMethod():

参数1 :指明获取的方法的名称 参数2:指明获取的方法的形参列表

也要保证当前方法是可访问的

show.setAccessible(true);

调用方法的invoke():参数1:方法的调用者 参数2:给方法形参赋值的实参

invoke()的返回值即为对应类中调用的方法的返回值。

对于静态的方法:

使用invoke()调用时,可以invoke(Person.class),也可以invoke(null)

对于静态属性同理

反射的应用:动态代理(代码见Idea)

静态代理(static
proxy)
:见上文笔记中有述。被代理类和代理类实现同一个接口,一个代理类就对应一个被代理类。这样当代理的需求增多时,就必然会产生很多的代理类。

特点:代理类和被代理类在编译期间,就确定下来了。

动态代理:通过运行时,动态的根据被代理的对象,确定代理类,代理类只有一个

要想实现动态代理,需要解决的问题?

问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。

问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值