Java核心基础
文章目录
一、Java概述
1.1 什么是Java
Java是一门面向对象的编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程。
Java具有简单性、面向对象、分布式、健壮性、安全性、平台独立与可移植性、多线程、动态性等特点 。Java可以编写桌面应用程序、Web应用程序、分布式系统和嵌入式系统应用程序等。
1.2 Java语言的发展历史
1.3 Java语言平台
Java语言分为三大类,不同的类型针对不同的开发场景。
J2SE(标准版)
该版本是其它两者的基础,主要应用于普通桌面应用的开发。
J2ME(小型版)
该版本是Java的微型版,主要应用于嵌入式系统的开发,如手机系统、App的开发。
J2EE(企业版)
主要应用于开发企业级的应用,例如web应用程序的开发。
1.4 JDK、JRE和JVM
JDK(Java开发工具包)
JDK包含了JRE和JVM,并且包含了java程序的编译工具(javac.exe)。它提供了大量的API可以供Java程序员使用。
JRE(Java运行环境)
JRE提供了Java程序的运行环境,它包含了JVM。
JVM(Java虚拟机)
用于解释运行Java的字节码文件。也是Java语言实现跨平台的依赖。
JVM为什么能实现跨平台
不同平台的JVM会通过解释器将字节码文件编译成能够在对应的操作系统上运行的机器码文件。所以也可以说我们是在面向JVM编程。
二、程序的编译过程
实例:创建一个输出hello world的java程序
public class HelloWorld{
public static void main(String[] args){
System.out.println("hello world");
}
}
因为java 是面向对象的编程语言,所以我们实际上是在创建一个个的类
class HelloWorld 表示这个类的名字是HelloWorld
public static void main(String[] args) 这是主方法,所有代码的入口
执行过程
// 1. 将Java源文件编译成字节码文件
javac HelloWorld.java
// 2. JVM运行编译后的字节码文件
java HelloWorld
源文件由开发人员编写,然后JDK中的编译器(javac.exe)会将源文件编译成字节码文件,然后JVM会通过解释器去解释运行字节码文件。
.java 文件是java的源文件,但是不能直接运行,必须先被编译成为.class文件 才能够执行
java 使用 javac命令进行编译
四、Java的基本数据类型
4.1 罗列基本数据类型
四类 | 八种 | 字节数 | 数据范围 |
---|---|---|---|
字节 | byte | 1 | -128 ~ 127 |
短整型 | short | 2 | -32768 ~ 32767 |
整型 | int | 4 | -2147483648 ~ 2147483648 |
长整型 | long | 8 | -2^63 ~ 2^63-1 |
浮点型(单精度) | float | 4 | |
浮点型(双精度) | double | 8 | |
布尔型 | boolean | 1 | 只有两个值,true或者false |
字符型 | char | 2 | 一个字符,例如’1’, ‘a’, ‘$’, ‘中’ |
注意点:
- 整型的默认数据类型是int,浮点数的默认数据类型是double
- 使用long类型的时候需要在数据后面加上L,整数的数据类型是int,如果值的范围超过int数据类型的范围,就是发生编译错误
- 使用float类型的时候需要在数据后面加上F,小数的默认数据类型是double,而double不能自动转换成float,所以会发生编译错误
- E表示10的38次方
- float的数据范围比long要大
4.2 基本数据类型转换
byte,short,char—int—long—float—double
byte,short,char相互之间不转换,他们参与运算首先转换为int类型
boolean类型不能转换为其他的数据类型
4.3 Java标识符
4.3.1 组成规则
变量命名只能使用字母 数字 $ _
变量第一个字符 只能使用 字母 $ _
变量第一个字符 不能使用数字
注:_ 是下划线,不是-减号或者—— 破折号
4.3.2 注意事项
不能以数字开头
不能是Java中的关键字
4.3.3 命名规范
见名知意
包名小写
类名每个单词的首字母大写
变量名和方法名从第二个单词开始首字母大写
五、Java运算符
5.1 算术运算符
运算符 | 含义 | 运算符 | 含义 |
---|---|---|---|
+ | 加 | - | 减 |
* | 乘 | / | 除(取整数部分) |
% | 取余 | ++ | 加加 |
– | 减减 |
++和–的使用注意事项
-
单独使用
没有区别
-
混合使用
-
放在变量前面
表示先加一(或减一)再使用
-
放在变量后面
表示先使用在加一(或减一)
-
-
不能与常量使用
5.2 关系运算符
5.3 逻辑运算符
六、流程控制
6.1 选择结构
6.1.1 switch case
//语句结构
switch(表达式){
case:
语句体1;
break;
case:
语句体2;
break;
......
default:
语句体n;
break;
}
switch的注意事项:
-
break的作用问题
结束case的执行 直到遇见下一个break程序才会结束
专业术语称为case穿透 有没有使用场景? 有2 .case的值顺序无关紧要
-
default后面的break可以不写 但是一般不推荐
-
switch表达式的类型可以是哪些 笔试题
jdk1.5之前 byte short int char
jdk1.5之后 还可以是枚举
jdk1.7之后 还可以是字符串
//穿透场景
public class Demo02Switch {
public static void main(String[] args) {
//根据月份判断季节
int month = 2;
switch (month){
case 2:
case 3:
case 4:
System.out.println("春季");
break;
case 5:
case 6:
case 7:
System.out.println("夏季");
break;
case 8:
case 9:
case 10:
System.out.println("秋季");
break;
case 1:
case 11:
case 12:
System.out.println("冬季");
break;
default:
System.out.println("地球没有这个季节");
//break;
}
}
}
七、数组
7.1 数组的概述
数组是存储多个变量(元素)的东西(容器)
这多个变量的数据类型要一致
数组是引用类型
数组可以解决重复定义同类型变量的冗余问题。例如可以存放班级学生的成绩
7.2 数组的定义
7.2.1 动态定义
//数据类型[] 变量名 = new 数据类型[数组长度];
//1.
int[] arr = new int[10];
//2.
int[] arr2 = null;
arr2 = new int[10];
7.2.2 静态定义
//1. 数据类型[] 变量名 = new 数据类型[]{元素,元素,...,元素};
int[] arr = new int[]{1,2,3};
//2. 数据类型[] 变量名 = {元素,元素,...,元素};
int[] arr2 = {1,2,3};
7.3数组的内存图解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DLhrJVoe-1662125292069)(E:\develop\学习记录\day04\1.一个数组的内存图.png)]
创建一个数组,先在堆里面划分一块存放数组长度的区域
然后在栈里面创建数组变量,并将堆里面的数组地址给到这个变量
调用这个变量名时就是通过这个地址找到堆里面的数组
arr[0]变量名后面加上数字就是先通过地址找到堆里面的数组,然后通过后面的数字索引找到数组里面的元素
八、Java的内存区域划分
https://www.cnblogs.com/dolphin0520/p/3613043.html#!comm
- 堆:存储new出来的东西
- 栈:存储局部变量
- 方法区:存放.class文件的信息
- 本地方法区:
- 寄存器(程序计数器):跟cpu有关
两个引用共用一个对象的内存图解
九、面向对象
9.1 面向过程
面向过程开发,其实就是面向着具体的每一个步骤和过程,把每一个步骤和过程都亲力亲为完成,然后由这些功能方法相互调用,完成我们的需求。
面向过程的代表语言:C语言
9.2 面向对象
把重复的步骤和功能在进行封装在一个类中,封装时根据不同的功能,进行不同的封装,同时功能类似的封装在一起。
我们在使用的时候,找到对应的类就可以了。这就是所谓的面向对象思想。
面向对象的代表语言: Java语言
区别
面向过程强调的是每一个环节每一个步骤都需要亲力亲为自己去完成,事情变得复杂。
面向对象是根据Java中的一些类创建对象,让对象去帮我们调用方法(功能)完成,事情变得简单,
而且也更加符合我们懒人思想。
总之面向对象就是让我们从执行者变成了指挥者。(这也体现了面向对象的特点)
同时面向对象也有三个特征,即封装继承多态。
个人理解
面向过程在编写代码的时候看重的是事件的过程(例如内存、效率)
面向对象则是更注重结果,把过程封装在对象里面,通过调用对象的形式实现具体功能
9.3 类与对象
类
类是一组属性(特征)和行为的集合。我们可以当做是某一类事物的模板,从而使用该事物模板的属性和行为来描述这一类事物。
属性:就是该事物的状态信息
行为:就是该事物能够做什么
对象
对象是一类事物的具体体现。对象是类的一个实例(这里的对象并不是找个女朋友),那么对象就必须具备该类事物的属性和行为。
9.4 对象内存图
9.5 变量
变量:用来命名一个数据的标识符
基本数据类型:存储的是一个字面值
引用类型:存储的是一个地址值
9.5.1 变量在不同的位置,有不同的名称
分别是
字段,属性
参数
局部变量
不同名称的变量,其作用域是不一样的
属性
当一个变量被声明在类下面
变量就叫做字段 或者属性、成员变量、Field
比如变量i,就是一个属性。
那么从第2行这个变量声明的位置开始,整个类都可以访问得到
所以其作用域就是从其声明的位置开始的整个类
参数
如果一个变量,是声明在一个方法上的,就叫做参数
参数的作用域即为该方法内的所有代码
其他方法不能访问该参数
类里面也不能访问该参数
局部变量
声明在方法内的变量,叫做局部变量
其作用域在声明开始的位置,到其所处于的块结束位置
注意:
属性与局部变量和参数互不影响,可以使用相同的变量名,在使用的过程中Java程序会采用就近原则(局部变量优先于属性);
局部变量和参数的变量名必须不同
9.6 this关键字
9.7 构造方法
9.8 JavaBean
Bean:可重用组件
JavaBean:Java项目中的可重用组件
要求:
- 使用public创建Java类
- 变量使用private修饰
- 每一个变量都有get()、set()方法
- 拥有无参和全参的构造方法
9.9 static关键字
方法块的执行顺序
本类的静态代码块
被调用类的静态代码块
构造代码块
构造方法
public class Test {
static {
System.out.println("Test静态代码块");
}
{
System.out.println("Test构造代码块");
}
public Test(){
System.out.println("Test无参构造方法");
}
public static void main(String[] args) {
System.out.println("main方法!");
Anaiml.name="小白";
Test t = new Test();
Anaiml a = new Anaiml();
a.toString();
class Anaiml{
static String name;
static {
System.out.println("Anaiml静态代码块!");
}
{
System.out.println("Anaiml构造代码块!");
}
public Anaiml(){
System.out.println("Anaiml无参构造方法!");
}
public Anaiml(String name){
this.name = name;
System.out.println("Anaiml有参构造方法!");
}
@Override
public String toString() {
return "Anaiml [name=" + name + "]";
}
}
/*
执行顺序:
Test静态代码块
Anaiml静态代码块!
Test构造代码块
Test无参构造方法
Anaiml构造代码块!
Anaiml无参构造方法!
Anaiml构造代码块!
Anaiml有参构造方法!
*/
注意
static修饰的变量会在加载类的时候第一时间定义,并且会给默认值
然后按照代码顺序执行
public class SingleTon {
private static SingleTon singleTon = new SingleTon();
public static int count1=2;
public static int count2 = 0;
private SingleTon() {
System.out.println(count1);//0
count1++;
count2++;
}
public static SingleTon getInstance() {
return singleTon;
// return new SingleTon();
}
}
class Test{
public static void main(String[] args) {
SingleTon singleTon = SingleTon.getInstance();
}
}
9.10 抽象类
用于描述生活中的抽象概念,例如动物、人、建筑等
有抽象方法的类必须是抽象类
子类继承了抽象类,要么重写父类的所有抽象方法,要么它自己也是抽象类
抽象类不能被创建对象
抽象方法可以有参数,但不能有方法体
抽象类可以有构造方法,于自身没多大意义,供子类使用
9.11 接口
jdk1.7
抽象方法
修饰符 abstract 返回值类型 方法名称(参数列表);
jdk1.8
public default 返回值类型 方法名称(参数列表){
//方法体
}
怎么用?
我们创建实现类对象 用实现类对象进行调用 当然作为实现类也可以覆盖重写父接口的默认方法
问:jdk升级之后,可以定义默认方法,但是好处是什么? 解决了接口升级的问题
静态方法
public static 返回值类型 方法名称(参数列表){
//方法体
}
怎么用?
接口的静态方法 实现类不可以覆盖重写
使用的方式就是使用接口名称直接访问就行
问:jdk升级之后,可以定义静态方法,但是好处是什么? 如果不需要实现类的情况下,可以不定义也能实现需求
jdk1.9
私有方法
静态私有方法
priavte static 返回值类型 方法名称(){
//…
}
主要目的是为了防止接口在外部使用这个静态方法
默认私有方法
格式:
private 返回值类型 方法名称(){
//…
}
主要的目的只给接口使用
9.12 权限修饰符
权限修饰符 | 同一个类 | 同一个包 | 不同包下的子类 | 不同包,也非子类 |
---|---|---|---|---|
public | yes | yes | yes | yes |
protected | yes | yes | yes | no |
default | yes | yes | no | no |
private | yes | no | no | no |
9.13 内部类
9.13.1 内部类
9.13.2 匿名内部类
匿名内部类必须继承一个父类或者实现一个父接口。
//格式
new 父类名或者接口名(){
// 方法重写
@Override
public void method() {
// 执行语句
}
};
//使用方式
public class InnerDemo {
public static void main(String[] args) {
new A() {
@Override
public void showA() {
System.out.println("AAAA");
}
}.showA();
}
}
interface A{
void showA();
}
9.14 创建对象的过程
编译class文件------加载进内存(方法区)class对象----------new 对象 ------计算对象所占用的内存大小
十 API(应用程序编程接口)
10.1、String(字符串)
10.1 创建方式以及构造方法
10.2 String变量的内存区域
常量池:存放共享的常量
1.7及以前存放在方法区里,1.8及以后存放在堆里面
jdk1.7及以前:老年代、新生代、永久代(方法区)
jdk1.8及以后:老年代、新生代、(元空间)
10.3 区别
String str1 = "hello";
String str2 = new String("hello");
str1是直接引用的"hello"这个常量的变量
str2是先创建了一个String类的对象,然后由对象去引用的"hello"这个常量。
常量是共享的,所以引用了相同常量的对象地址也想等
10.4 常用方法
10.4.1String类的判断功能
equals
功能:判断字符串的内容与参数obj是否相同
用例
//boolean equals(Object obj)
public class Test02String {
public static void main(String[] args) {
String str1 = "hello";
System.out.println(str1.equals("hello"));//true
System.out.println("hello".equals(str1));//true
str1 = null;
System.out.println(str1.equals("hello"));//出现空指针异常
System.out.println("hello".equals(str1));//false
}
}
源码
public boolean equals(Object anObject) {
//判断内存地址,如果相等,直接返回true
if (this == anObject) {
return true;
}
//instanceof:用来测试一个对象是否为一个类的实例
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
equalsIgnoreCase
功能:判断字符串的内容是否相同(不区分大小写)
用例
//boolean equalsIgnoreCase(String str)
public class Test02String {
public static void main(String[] args) {
String str1 = "hello";
System.out.println(str1.equalsIgnoreCase("HeLLo"));//true
System.out.println("HeLLo".equalsIgnoreCase(str1));//true
}
}
startsWith
功能:判断字符串对象是否是参数str开头
用例
//boolean startsWith(String str)
public class Test02String {
public static void main(String[] args) {
String str1 = "hello";
System.out.println(str1.startsWith("h"));//true
System.out.println(str1.startsWith("hel"));//true
}
}
endsWith
功能:判断字符串对象是否是参数str结尾
//boolean endsWith(String str)
public class Test02String {
public static void main(String[] args) {
String str1 = "hello";
System.out.println(str1.endsWith("lo"));//true
System.out.println(str1.endsWith("o"));//true
}
}
**练习:**用户登录功能
/**
* 模拟用户登录的操作
* 期望用户键盘录入用户名和密码和验证码
* 然后跟我们所定义的信息(登录信息)进行比较
* 如果都相同,则提示登录成功
* 如果不相同,给用户三次机会,但是三次机会都登录不成功,让它联系管理员(自行扩展)
* 注意:验证码不区分大小写
*/
public class Demo01 {
public static void main(String[] args) {
String username = "root";
String password = "123456";
String yzm = "123tT";
int i = 3;
while (login(username,password,yzm)){
if (--i == 0){
System.out.println("你还剩余"+0+"次机会");
break;
}
System.out.println("你还剩余"+i+"次机会");
}
if (i>0){
System.out.println("登录成功");
}
}
public static boolean login(String username,String password,String yzm){
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名:");
String name = scanner.next();
System.out.println("请输入密码:");
String pad = scanner.next();
System.out.println("请输入验证码");
String yzm2 = scanner.next();
if (name.equals(username) && pad.equals(password) && yzm2.equalsIgnoreCase(yzm)){
return false;
}
return true;
}
}
10.4.2 String类的获取功能
length
功能:获取字符串对象中字符的个数也就是字符串的长度
用例
//int length()
public class Test02String {
public static void main(String[] args) {
String str1 = "hello";
System.out.println("该字符串的长度为:"+str1.length());//5
}
}
charAt
功能:获取字符串中指定索引位置的字符
用例
//char charAt(int index)
public class Test02String {
public static void main(String[] args) {
String str1 = "hello";
System.out.println(str1.charAt(2));//l
}
}
indexOf
功能:获取字符串中第一次出现的索引,没有返回-1
用例
//int indexOf(String str)
public class Test02String {
public static void main(String[] args) {
String str1 = "hello";
System.out.println(str1.indexOf('l'));//2
}
}
lastIndexOf
功能:获取str在字符串对象当中最后一次出现的索引位置
用例
//int lastIndexOf(String str)
public class Test02String {
public static void main(String[] args) {
String str1 = "hello";
System.out.println(str1.lastIndexOf('l'));//3
}
}
substring(int start)
功能:从start索引位置开始截取,到末尾结束
用例
//String substring(int start)
public class Test02String {
public static void main(String[] args) {
String str1 = "hello";
System.out.println(str1.substring(1));//ello
}
}
substring(int start,int end)
功能:从start索引位置开始截取到end结束(含头不含尾)
用例
//String substring(int start,int end)
public class Test02String {
public static void main(String[] args) {
String str1 = "hello";
System.out.println(str1.substring(1,3));//el
}
}
练习1:字符串的遍历
public class Demo03 {
public static void main(String[] args) {
String str = "sdf1g351f3ag1";
for (int i = 0; i < str.length(); i++) {
System.out.print(str.charAt(i)+"\t");//s d f 1 g 3 5 1 f 3 a g 1
}
}
}
练习2:统计键盘录入的字符串对象中不同种类字符的个数
public class Demo02 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入字符串:");
String str = scanner.next();
int[] counts = new int[4];//0表示小写字母;1表示大写字母;2表示数字;3表示其它
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (c <= 'z' && c >= 'a'){
counts[0]++;
}else if (c <= 'Z' && c >= 'A'){
counts[1]++;
}else if (c >= 48 && c <= 57){
counts[2]++;
}else {
counts[3]++;
}
}
System.out.println("0表示小写字母;1表示大写字母;2表示数字;3表示其它");
System.out.println(Arrays.toString(counts));
}
}
10.4.3 String类的转换功能
toCharArray
功能:把字符串对象转换为字符数组
//char[] toCharArray()
public class Test02String {
public static void main(String[] args) {
String str1 = "hello";
char[] chars = str1.toCharArray();
System.out.println(Arrays.toString(chars));//[h, e, l, l, o]
}
}
toLowerCase
功能:把字符串对象中所有的字符转换为小写
//String toLowerCase()
public class Test02String {
public static void main(String[] args) {
String str1 = "HELLo";
System.out.println(str1.toLowerCase());//hello
}
}
toUpperCase
功能:把字符串对象中所有的字符转换为大写
//String toUpperCase()
public class Test02String {
public static void main(String[] args) {
String str1 = "hello";
System.out.println(str1.toUpperCase());//HELLO
}
}
练习:键盘录入字符串,把录入字符串的首字母转换为大写,其他的小写。
public class Demo04 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入字符串:");//hello
String str = scanner.next();
String str1 = str.substring(0,1).toUpperCase();
String str2 = str.substring(1).toLowerCase();
System.out.println(str1+str2);//Hello
}
}
10.4.4 其它功能
trim
功能:去除字符串两端空格
//String trim()
split
功能:按照指定符号分割字符串
//String[] split(String str)
replace
功能:将字符串对象中所有的字符oldChar替换为字符newChar,并返回新的字符串
//String replace(char oldChar, char newChar)
replaceAll
功能:将字符串对象中所有的字符串regex替换为字符串replacement,并返回新的字符串
//String replaceAll(String regex, String replacement)
replaceFirst
功能:将字符串对象中第一个字符串regex替换为字符串replacement,并返回新的字符串
//String replaceFirst(String regex, String replacement)
用例:
public class Test02String {
public static void main(String[] args) {
String str = "ab43a2c43d";
System.out.println(str);
System.out.println(str.replaceFirst("43aa","opp"));//ab43a2c43d
System.out.println(str.replace("3ac","fty"));//ab43a2c43d
System.out.println(str.replaceAll("\\d","f"));//abffafcffd
}
}
contains
功能:判断字符串对象当中是否包含参数s
//boolean contains(CharSequence s)
isEmpty
功能:判断字符串对象当中内容是否为空,就算字符串中有空格也是不为空
//boolean isEmpty()
checkBounds
检查数组是否越界
offset:开始位置
length:元素长度
源码
private static void checkBounds(byte[] bytes, int offset, int length) {
if (length < 0)
throw new StringIndexOutOfBoundsException(length);
if (offset < 0)
throw new StringIndexOutOfBoundsException(offset);
if (offset > bytes.length - length)
throw new StringIndexOutOfBoundsException(offset + length);
}
codePointAt
获取字符串指定位置的字符,返回对应的ASCII码
10.5 字符串的拓展和思考
10.5.1 为什么直接输出字符串对象可以得到字符串值,而其它引用类型的对象是地址值
因为在输出对象时,都会自动调用对象的toString()方法
//Object类中的toString()方法
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
而Object类是所有类的父类,所以会调用Object类中的toString()方法
但是String类重写了toString()方法,所以会返回它本身的字符串值
//String类中的toString()方法
public String toString() {
return this;
}
10.5.2 为什么java1.7及以前常量池存放在方法区里,1.8及以后存放在堆里面
因为永久代里面的生命周期太长,常量存放在永久代会浪费内存空间。而堆里面有垃圾回收机制,常量会被自动回收,所以常量池由方法区转到了堆里面
10.6 StringBulder
伸缩性不足:由于多次扩容造成的内存浪费问题
10.1 Arrays
10.2 Date
importjava.text.DateFormat;
importjava.text.SimpleDateFormat;
import java.util.Date;
/*
使用format方法的代码为:
把Date对象转换成String
*/
public class Demo03DateFormatMethod {
public static void main(String[] args){
Date date= new Date();
// 创建日期格式化对象,在获取格式化对象时可以指定风格
DateFormatdf= new SimpleDateFormat("yyyy年MM月dd日");
String str= df.format(date);
System.out.println(str); // 2008年1月23日
}
}
/*
使用parse方法
把String转换成Date对象
*/
public class Demo04DateFormatMethod {
public static void main(String[] args) throws ParseException{
DateFormat df= new SimpleDateFormat("yyyy年MM月dd日");
String str= "2018年12月11日";
Date date= df.parse(str);
System.out.println(date); // Tue Dec 11 00:00:00 CST 2018
}
}
System
arraycopy
功能:复制数组
参数
src:要复制的数组(源数组)
srcPos:复制源数组的起始位置
dest:目标数组
destPos:目标数组的下标位置
length:要复制的长度
简单使用
10.3 Collections
java.utils.Collections是集合工具类,用来对集合进行操作
常用方法
public static boolean addAll(Collectionc,T…elements)往集合中添加一些元素
public static void shuffle(List<?> list)打乱集合顺序。
public static void sort(Listlist)将集合中元素按照默认规排序 升序
public static void sort(Listlist,Comparator<? super T>)将集合中的元素按照指定规则进行排序
十一、IO流
11.1 什么是IO流
按流向分
- 输出流:内存到磁盘
- 输入流:磁盘到内存
按种类分
-
BIO:同步并阻塞 效率低下
-
NIO:异步非阻塞 效率高
-
AIO
按分
-
字节流
-
字符流(FileWriter)
11.2 字符流(FileWriter)
11.2.1 字符输出流
11.2.1.1 构造方法
public FileWriter(String fileName){}
传递一个文件的名称,根据文件名称创建输出流对象
public FileWriter(String filename,boolean append){}
传递一个文件的名称以及表示是否追加的参数(默认为false,表示覆盖),根据文件名称创建输出流对象
11.2.1.2 成员方法
public void write(String str){}
写一个字符串到指定的文件中
public void flush(){}
将缓冲区的数据刷新到文件中
public void close(){}
将程序与硬盘之间打开的资源进行释放
用例
public class Demo01IO {
public static void main(String[] args) throws IOException {
FileWriter writer = new FileWriter("E:\\develop\\学习记录\\day09\\txt\\a.txt");
writer.write("hello ");//字符串、字符串的一部分、字符数组、字符数组的一部分
writer.flush();
writer.close();
}
}
11.2.2 字符输入流
11.2.2.1 构造方法
public FileReader(String fileName){}
传递一个文件的名称,根据文件名称创建输入流对象
11.2.2.2 成员方法
public void read(String str){}
一次读取一个字符
public void close(){}
将程序与硬盘之间打开的资源进行释放
用例
public class Demo01 {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("E:\\develop\\学习记录\\day09\\txt\\a.txt");
//一次读取一个字符
System.out.println(fr.read());
char[] chs = new char[1024];
//读取的字符长度
int len = fr.read(chs);
System.out.println(len);
}
}
11.2.3 注意事项:
*flush()方法和close()*的区别
- flush():刷新缓冲区,并且刷新了之后也可以写数据
- close():释放资源通道,在释放之前会刷新缓冲区,close之后就不可以写数据了
- 字符流 只能复制 用记事本可以打开的内容
相对路径与绝对路径
相对路径:指相当于当前目录下的路径地址
绝对路径:明确指定了地址
11.3 File类
File类主要用于文件和目录的创建、查找和删除等操作
11.3.1 构造方法
public File(String pathname)
通过将给的路径名字符串转换为抽象路径名来创建新的File实例
public File(String parent, Stringchild)
从父路径名字符串和子路径名字符串创建新的File实例
public File(File parent, Stringchild)
从父抽象路径名和子路径名字符串创建新的File实例
用例
// 文件路径名
String pathname ="D:\\aaa.txt"; File file1 = newFile(pathname);
// 文件路径名
String pathname2 = "D:\\aaa\\bbb.txt";
File file2 = new File(pathname2);
// 通过父路径和子路径字符串Stringparent = "d:\\aaa"; String child = "bbb.txt";
File file3 = new File(parent, child);
// 通过父级File对象和子路径字符串
File parentDir= newFile("d:\\aaa"); String child = "bbb.txt";
File file4 = new File(parentDir, child);
11.3.2 成员方法
函数 | 含义 | 函数 | 含义 |
---|---|---|---|
getName() | 获取文件名 | getPath() | 获取文件相对路径 |
getAbsoluteFile() | 获取文件绝对路径 | getParent() | 获取上层路径 |
renameTo(new file()) | 文件重命名 | exists() | 判断文件是否存在 |
canWrite() | 判断是否可写 | canRead() | 判断是否可读 |
isFile() | 判断是不是文件 | isDirectory | 判断是不是文件夹 |
lastModified() | 获取最后修改时间,返回一个毫秒数 | length() | 返回文件的大小 |
createNewFile() | 创建一个文件 | delete() | 删除一个文件或目录 |
mkdir() | 创建单层目录 | mkdirs() | 创建多层目录 |
list() | 返回文件夹的子集列表 | listFiles(··) | 返回文件夹的子集对象 |
public long length()
返回由此File表示的文件的长度
//如果是文件,就返回文件的大小
//如果是目录,返回的不知道什么东西
public class Demo02 {
public static void main(String[] args) {
File file = new File("E:\\develop\\IdeaProjects\\JavaSECode\\day18_code\\txtFile\\a.txt");
System.out.println(file.length());// 1
file = new File("E:\\develop\\IdeaProjects\\JavaSECode\\day18_code\\txtFile");
System.out.println(file.length());// 0
}
}
public booleancreateNewFile()
当且仅当具有该名称的文件尚不存在时,创建一个新的空文件
//如果是文件,则根据目录创建规定类型的文件
//如果是目录,则会创建一个未知类型的文件
public class Demo01 {
public static void main(String[] args) throws IOException {
File file = new File("E:\\develop\\IdeaProjects\\JavaSECode\\day18_code\\txtFile\\b.txt");
//不能创建包含不存在目录下的文件
file.createNewFile();
System.out.println(file.exists());
}
}
**用例:**打印多级目录
public class Demo01 {
public static void main(String[] args) {
File file = new File("E:\\develop\\IdeaProjects\\JavaSECode\\day18_code");
searchFile(file);
}
private static void searchFile(File file){
//如果是文件就直接打印
if (file.isFile()){
if (file.getName().endsWith(".java")){
System.out.println(file.getPath());
}
}else {
//如果是文件夹就执行递归调用
File[] files = file.listFiles();
System.out.println("文件夹名为"+file.getName());
for (int i = 0; i < files.length; i++) {
searchFile(files[i]);
}
}
}
}
注意事项
-
一个File对象代表硬盘中实际存在的一个文件或者目录,但与平台无关
-
无论该路径下是否存在文件或者目录,都不影响File对象的创建。
-
File类能新建、删除、重命名文件和目录,但File不能访问文件内容本身
-
如果需要访问文件内容,则需要使用输入/输出流
11.3.2 FileFilter(文件过滤器)
这是一个接口,用于文件的过滤。
用法就是将该接口创建的 对象作为listFiles()方法的参数。调用listFiles()方法的对象必须是一个目录,这个方法会返回一个File对象类型的数组,然后根据调解筛选出需要的File对象。
接口中只有一个方法
boolean accept(File pathname)
测试pathname是否应该包含在当前File目录中,符合则返回true
public class Demo02Filter {
public static void main(String[] args) {
File file = new File("E:\\develop\\IdeaProjects\\JavaSECode\\day18_code");
searchFile2(file);
}
//方法一
public static void searchFile(File file){
File[] files = file.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
// if (pathname.getName().endsWith(".java")){
// return true;
// }
// if (pathname.isDirectory()){
// return true;
// }
//规定需要的文件类型,例如文件夹和后缀为.java的文件
if (pathname.getName().endsWith(".java") || pathname.isDirectory()){
return true;
}
return false;
}
});
for (File fl : files) {
if (fl.isFile()){
System.out.println(fl.getName());
}else {
System.out.println("当前目录"+fl);
searchFile(fl);
}
}
}
//方法二
public static void searchFile2(File file){
File[] files = file.listFiles(pathname -> {
return pathname.getName().endsWith(".java") || pathname.isDirectory() ? true : false;
});
for (File fl : files) {
if (fl.isFile()){
System.out.println(fl.getName());
}else {
System.out.println("当前目录"+fl);
searchFile(fl);
}
}
}
}
11.4 字节流
一切数据皆可为字节
一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都是一个一个的字节,那么传输时一样如此。所以,**字节流可以传输任意文件数据。**在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。字符流其实是字节流的便捷类。
11.4.1 字节输出流
OutputStream
java.io.OutputStream抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。
它定义了字节输出流的基本共性功能方法:
public void close()
关闭此输出流并释放与此流相关联的任何系统资源
public void flush()
刷新此输出流并强制任何缓冲的输出字节被写出
public void write(byte[] b)
将指定的字节数组写入此输出流
public void write(byte[] b, int off, intlen)
从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流
public abstract void write(int b)
将指定的字节输出流
11.4.1.1 FileOutputStream类
构造方法
11.4.2 字节输入流
11.5 缓冲流
缓冲流是对基本流的一种增强,能够更高效的读写数据
缓冲流采用了装饰者设计模式
按照数据类型分类
字节缓冲流:BufferedInputStream,BufferedOutputStream
字符缓冲流:BufferedReader,BufferedWriter
基本原理
创建缓冲流对象时,会在内存中创建一个缓冲区,是一个默认大小(8192)的字符数组,通过缓冲区读写减少系统IO的次数,从而提高读写的效率。空间换时间
11.5.1 字符缓冲流
11.5.2 字节缓冲流
11.6 转换流
十二、面向对象三大特点
12.1 继承
12.1.1 概述
继承可以理解为现实生活中子女继承父母的财产。在程序中,继承的关系同样可以理解为子类继承父类,子类可以继承到父类除私有成员的一切。例如成员变量、成员方法等。
12.1.2 继承中构造方法的执行顺序
创建子类对象时的代码块执行顺序
public class People {
//final修饰的变量不能被修改值,且必须初始化
private final int height = 180;
static {System.out.println("父类静态代码块");}1
{System.out.println("父类的构造代码块");}
public People() {System.out.println("父类的构造方法");}
}
public class Student extends People {
static {System.out.println("子类的静态代码块");}2
{System.out.println("子类的构造代码块");}3
public Student() {System.out.println("子类的构造方法");}4
public static void main(String[] args) {
Student student = new Student();
}
}
/*
父类静态代码块
子类的静态代码块
父类的构造代码块
父类的构造方法
子类的构造代码块
子类的构造方法*/
12.1.3 super和this
this代表当前对象的引用,super表示子类对父类的引用
this()表示调用当前对象的构造方法,super()表示调用父类的构造方法
注意
在对子类对象进行初始化时,父类的构造函数也会运行
因为子类的构造函数默认第一行有一条隐式的语句super()
而且子类中所有的构造函数默认第一行都是super()
为什么不能在子类的构造方法中同时调用this和super方法
12.1.4 final关键字
修饰的变量即为常量,必须在构造方法或者静态代码块中赋值
12.2 多态
变量看左边
方法看右边
public class Person {
String name = "per";
public void showName(){
System.out.println(name);
}
}
public class Student extends Person {
String name = "stu";
public void showName(){
System.out.println(name);
}
}
public class Demo01 {
public static void main(String[] args) {
Person person = new Student();
System.out.println(person.name);//per
person.showName();//stu
}
}
父类向下转型成子类,可以使用子类的成员变量和方法
void useUSB(USB usb){
usb.open();
if (usb instanceof Keyboard){
Keyboard keyboard = (Keyboard)usb;
keyboard.type();
}
if (usb instanceof Mouse){
Mouse mouse = (Mouse) usb;
mouse.click();
}
usb.close();
}
十三、集合
13.1 List
ArrayList
LinkedList
public void addFirst(E e):将指定元素插入此列表的开头。
public void addLast(E e):将指定元素添加到此列表的结尾。
public E getFirst():返回此列表的第一个元素。
public E getLast() :返回此列表的最后一个元素。
public E removeFirst():移除并返回此列表的第一个元素。
public E removeLast():移除并返回此列表的最后一个元素。
public E pop():从此列表所表示的堆栈处弹出一个元素。
public void push(E e):将元素推入此列表所表示的堆栈。
public boolean isEmpty() :如果列表不包含元素,则返回true
Vector
13.2 Set
13.2.1 HashSet
底层原理
HashSet集合使用的是HashMap,而HashMap的底层数据结构是hash表,也就是数组加链表,jdk1.8之后添加了红黑树结构,也就是数组加链表加红黑树
链表的阈值是8,也就是说如果数组的长度达到了64并且链表的个数达到了8个,链表就会转成红黑树
-
开始添加元素,先判断数组是否为空或者长度是否等于0,如果是则进行扩容,如果不是,就执行元素的hashCode方法计算出元素的hash值,这个hash值就是元素存放的位置,也就是元素的索引值
-
然后判断该位置上是否存在其他元素,如果存在,则使用equals方法判断他们之间本身的含义是否相等,如果相等则不添加,不等则做添加准备
-
然后判断该位置上的节点是否是数节点,如果是就说明这已经转变成红黑树结构,直接插入红黑树中,如果不是就插入链表中,然后判断该链表的长度是否大于等于8,如果满足条件就转换成红黑树
结论:HashSet是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存储和查找性能。保证元素唯一性的方式依赖于:hashCode和equals这两个方法。
13.2.2 LinkedHashSet
LinkedHashSet是HashSet的一个子类,用法一致。
底层采用的数据结构是数组+双向链表+红黑树
HashMap与LinkedHashMap的区别?
HashMap的遍历顺序与存放顺序无关
LinkedHashMap的遍历顺序与存放顺序一致
结论:HashMap的特点是无序且不重复;LinkedHashMap的特点是有序且不重复
13.3 Map
13.3.1 介绍
Map是一种双列集合,以键值对的形式存储数据,一个键对应一个值,这种对应关系称之为映射。
<key,value>
在一个map集合中,key是唯一的数据,也就表示key是不能重复的;value不唯一,也就表value是可以重复的
Map常用类结构图
12.3.2 HashMap
描述
特点:存放数据以键值对形式,键不能重复,值可以重复,且键和值都可以为null。存取元素无序
底层数据结构:哈希表结构(数组+单链表+红黑树)
常用方法
-
public V put(K key, V value)
- 把指定的键与指定的值添加到Map集合中。 put即是添加也是修改
-
public V remove(Object key)
- 把指定的键所对应的键值对元素在Map集合中删除,返回被删除元素的值。
-
public V get(Object key)
- 根据指定的键,在Map集合中获取对应的值。
-
public Set keySet()
- 获取Map集合中所有的键,存储到Set集合中。 获取键集 遍历方式一
-
public Set<Map.Entry<K,V>> entrySet()
- 获取到Map集合中所有的键值对对象的集合(Set集合)。 遍历方式二
方法使用
public class Demo01 {
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
map.put("一更天","19:00");
map.put("二更天","21:00");
map.put("三更天","23:00");
map.put("四更天","01:00");
map.put("五更天","03:00");
map.put(null,null);
System.out.println(map);
map.remove("五更天");
System.out.println(map.get("一更天"));
Set<String> strings = map.keySet();
System.out.println(strings);
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
System.out.println(entry);
}
}
}
//控制台结果
{null=null, 二更天=21:00, 四更天=01:00, 一更天=19:00, 五更天=03:00, 三更天=23:00}
19:00
[null, 二更天, 四更天, 一更天, 三更天]
null=null
二更天=21:00
四更天=01:00
一更天=19:00
三更天=23:00
Process finished with exit code 0
13.2.3 LinkedHashMap
描述
特点:存放数据以键值对形式,键不能重复,值可以重复,且键和值都可以为null。存取元素有序
底层数据结构:哈希表结构(数组+双链表+红黑树)
13.2.4 properties
十四、泛型
泛型是java1.5出现的新特性
主要用来控制容器中存储的数据类型,避免发生转型的错误;
泛型主要有三种定义方式
-
定义带有泛型的类
-
定义带有泛型的方法
-
定义带有泛型的接口
1.定义带有泛型的类
public class Demo01GenericClass<T> {
T t;
public Demo01GenericClass() {}
public Demo01GenericClass(T t) {
this.t = t;
}
}
2.定义带有泛型的方法
public class Demo02GenericMethod {
public <T> void method(T t) {
System.out.println(t.toString());
}
}
3.定义带有泛型的接口
public interface Demo03GenericInterface<T> {
void show(T t);
}
public class Demo03GenericImpl implements Demo03GenericInterface<String>{
@Override
public void show(String str) {
System.out.println(str);
}
}
public class Demo03GenericImpl2<T> implements Demo03GenericInterface<T>{
@Override
public void show(T t) {
System.out.println(t);
}
}
//测试类
public class Test03GenericInterface {
public static void main(String[] args) {
//定义类的时候确定了泛型
Demo03GenericImpl demo03Generic1 = new Demo03GenericImpl();
demo03Generic1.show("666");
//定义类的时候没有确定泛型
//注意:如果在创建对象的时候没有规定泛型就默认为Object类型
Demo03GenericImpl2 demo03Generic2 = new Demo03GenericImpl2();
demo03Generic2.show(666);
}
}
4.泛型通配符(方法参数使用)
public class Demo04Generic <T>{
//表示不确定类型,可以传入Object类型的List,但是这个集合只能获取和删除里面的值,不能添加值
public T show(List<?> t) {
// t.add(null);
System.out.println(t.get(0));
return (T) t;
}
//泛型的上限 只能接受Person类和它的子类的list
public T showT(List<? extends Person> t) {
return (T) t;
}
//泛型的下限 只能接受Student类和它的父类的list
public T showT(List<? super Student> t) {
return (T) t;
}
}
十五、异常
异常是指程序中出现的bug
异常本身是一个类,产生异常就是创建异常对象并抛出了一个异常对象。Java处理异常的方式是中断处理。
抛出异常的处理过程
如果jvm在程序执行中捕获到了异常,会先根据产生异常的原因,分析异常的类型,然后找到对应的异常类。
编译器再创建这个类对象,throw给jvm,jvm将异常打印在控制台,然后停止运行。
15.1 Throwable
Throwable类是所有异常类的根类,都继承于它
15.1.1 Throwable体系下的两种类
Error
严重错误Error,无法通过处理的错误,只能事先避免,好比绝症
错误是不能处理的,因为这是系统内部的错误,运行时报错,系统问题
Exception
表示异常,异常产生后程序员可以通过代码的方式纠正,使程序继续运行,是必须要处理的。好比感冒、阑尾炎
这种是问题程序员可以手动处理的,例如抛出、声明或捕获。】
java中有两种异常
- 编译期错误。在编译时期,就会检查,如果没有处理异常,则编译失败。
- 运行时错误。程序在执行的时候,运行环境发现了程序中不能执行的操作。
15.1.2 Throwable中的常用方法
public void printStackTrace()
打印异常的详细信息
包含了异常的类型,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace
public String getMessage()
获取发生异常的原因。
提示给用户的时候,就提示错误原因
public String toString()
获取异常的类型和异常描述信息(不用)
15.2 异常的处理
Java异常处理的五个关键字:try**、catch、finally、throw、throws
15.2.1 throw抛出异常
在编写程序时,我们必须要考虑程序出现问题的情况。比如,在定义方法时,方法需要接受参数。那么,当调用方 法使用接受到的参数时,首先需要先对参数数据进行合法的判断,数据若不合法,就应该告诉调用者,传递合法的 数据进来。这时需要使用抛出异常的方式来告诉调用者。
15.2.2 throws声明异常
关键字throws运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(抛出异常)
throws用于进行异常类的声明,若该方法可能有多种异常情况产生,那么在throws后面可以写多个异常类,用逗 号隔开。
15.2.3 try…catch 捕获异常
Java中对异常有针对性的语句进行捕获,可以对出现的异常进行指定方式的处理
捕获异常语法如下:
try{
编写可能会出现异常的代码
}catch(异常类型 e){
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}
15.2.4 finally代码块
有一些特定的代码无论异常是否发生,都需要执行。另外,因为异常会引发程序跳转,导致有些语句执行不到。而finally就是解决这个问题的,在finally代码块中存放的代码都是一定会被执行的。
使用场景:
当我们在try语句块中打开了一些物理资源(磁盘文件/网络连接/数据库连接等),我们都得在使用完之后,最终关闭打开 的资源。
用例
public class Demo01 {
public static void main(String[] args){
int[] arr = {1,2,3,4,5};
System.out.println(get());
}
public static int get(){
int[] arr = {1,2,3,4,5};
try {//捕获异常
return getNum(2,arr);
} catch (Exception e) {
//处理异常
return -1;
}
}
//声明异常
public static int getNum(int index,int[] arr) {
// if (index < 0 || index >= arr.length){
// //抛出异常
// throw new ArrayIndexOutOfBoundsException("你给了一个非法索引");
// }
return arr[index];
}
}
throw和throws的区别
throw是在方法类抛出异常,表示如果遇到了某种异常,就抛出这个异常并停止运行程序
throws是声明异常,表示这个方法可能会有异常,但没有进行处理
异常注意事项
-
运行时异常被抛出可以不处理。即不捕获也不声明抛出。因为jvm会帮助你处理
-
如果父类抛出了多个异常,子类覆盖父类方法时,只能抛出相同的异常或者是他的子集。
-
父类方法没有抛出异常,子类覆盖父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不 能声明抛出
-
当多异常处理时,捕获处理,前边的类不能是后边类的父类
-
在try/catch后可以追加finally代码块,其中的代码一定会被执行,通常用于资源回收。 如果finally有return语句,永远返回finally中的结果,避免该情况.
-
finally不能单独使用
十六 、反射
16.1 概念
反射是一个框架的灵魂,也是实现java动态运行的一个重要支撑
可以通过反射获取到一个不固定类的信息,降低代码的耦合性。例如Java框架的使用时不需要改动源代码的,但它可以给到我们想要的类对象,使用的就是反射的原理。
编写代码的原则:高内聚、低耦合
运行期依赖,编译器不依赖
反射在运行期间可以获取到类的各个组成部分,然后封装成对象
16.1.1 Java代码的三个阶段
-
Resoure 源代码阶段
代码的编写和编译 ==> class文件
-
class文件加载成对象
在方法区内根据class文件创建出一块存放类的信息
说白了就是用一个Class类对象来表示一个类的字节码文件 -
Runtime 代码运行阶段
16.1.2反射创建对象的过程
从Java代码的编写到Java对象的运行,会经历三个阶段,分布是源代码阶段,Class类对象阶段,运行阶段,
每个类被编译成字节码文件后都会被类加载器加载到Java内存中,然后使用一个Class类对象来表现,并且这个Class对象在内存中是独一无二的,所以无论通过那种方式创建的class对象都是执行的同一个引用地址
通过反射创建对象就是利用Class类对象来创建我们需要的对象
16.2 反射的运用
16.2.1 获取class对象的方式
Class.forName(“全类名”)
将字节码文件加载进内存 返回class对象
多用于配置文件 将类名定义在配置文件中 读取文件 加载类
类名.class
通过类名的属性class获取
多用于参数的传递
对象.getClass()
getClass() 方法在Object类中定义的
多用于对象获取字节码的方式
结论
同一个字节码文件(.class)在一次程序运行的过程中 只会被加载一次 不论通过哪种方式获取的class对象都是同一个
16.2.2 获取class的成员变量
Field getField(String name)
获取指定名称的public修饰的成员变量
Field[] getFields()
获取所有public修饰的成员变量
Field[] getDeclaredFields()
获取所有的成员变量 不考虑修饰符
Field getDeclaredField(String name)
获取指定名称的成员变量 不考虑修饰符
16.2.3 获取clas对象的构造方法
Constructor getConstructor(类<?>… parameterTypes)
获取指定名称的public修饰的构造方法
Constructor<?>[] getConstructors()
获取所有public修饰的构造方法
Constructor getDeclaredConstructor(类<?>… parameterTypes)
获取指定名称的构造方法 不考虑修饰符
Constructor<?>[] getDeclaredConstructors()
获取所有构造方法 不考虑修饰符
16.2.4获取class对象的成员方法
Method getMethod(String name, 类<?>… parameterTypes)
获取指定名称的public修饰的成员方法
Method[] getMethods()
获取所有public修饰的成员方法
Method getDeclaredMethod(String name, 类<?>… parameterTypes)
获取指定名称的成员方法 不考虑修饰符
Method getDeclaredMethods()
获取所有成员方法 不考虑修饰符
16.3 反射相关的类
16.3.1 Field 成员变量类
操作:
1.设置值
void set(Object obj,Object value)
2.获取值
get(Object obj)
3.忽略访问权限修饰符的安全检查
setAccessible(true);暴力反射 成员变量对象有 构造对象也有 成员方法对象也有
16.3.2 Constructor 构造方法类
创建对象
T newInstance(Object…initargs)
如果使用空参构造方法创建对象 可以简化 Class对象newInstance的方法
16.3.3 Method 成员方法类
执行方法
method对象.invoke(Obejct obj);
获取方法的名称
String getName()
用例
public class Test01Reflect {
public static void main(String[] args) throws Exception {
//创建Properties集合对象
Properties pp = new Properties();
//加载文件数据
pp.load(new FileInputStream("day22_code\\src\\com\\qianfeng\\demo02\\db.properties"));
//获取全限定类名
String beanClass = pp.getProperty("beanClass");
//通过反射创建类对象
Class<?> clazz = Class.forName(beanClass);
//获取指定的构造方法
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
//通过构造方法对象创建类对象
Object o1 = constructor.newInstance("张三",20);
//获取文件中的方法名
String methodName = pp.getProperty("methodName");
//获取类对象中指定方法的对象
Method method = clazz.getMethod(methodName);
//通过方法对象执行方法
method.invoke(o1);
}
}
十七、注解
17.1 概念
概念:说明程序的 给计算机看的
注释:用文字描述程序的 给程序员看的
注解(Annotation),也叫元数据。
一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。
它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
概述描述:
jdk1.5以后的新特性
说明程序
使用注解:@注解名称
17.2 注解的属性
注解的属性类型:
属性是由两个部分组成
key=value
注解属性的类型:
1.基本数据类型
2.String
3.枚举
4.注解
5.以上类型的数组
属性在定义后使用注解的时候需要赋值:
多个属性之间逗号隔开
17.3 注解的使用
1.如果定义属性时 使用default关键字给属性默认初始化值 则使用注解时 可以不进行属性的赋值。
2.如果只有一个属性需要赋值 并且属性的名称是value 则value可以省略 直接定义值即可
3.数组赋值时 使用{}包裹 如果数组中只有一个值 则{} 省略
@Target(value = {ElementType.FIELD,ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyAnno {
//定义属性
//1.基本数据类型
int age() default 20;
byte heigth() default 127;
//2.String
String name() default "";
//3.枚举
MyMath myMath() default MyMath.PI;
//4.注解
MyAnnoSun addr() default @MyAnnoSun(address = "二仙桥");
//5.以上类型的数组
String[] month() default "{}";
//如果属性的名称叫value 而且在使用注解的时候只需要使用value属性 属性名称可以不写
String value();
}
17.4 注解的解析
//使用自定义注解
@ClassName(value = "com.qianfeng.demo04.Animal", address = "肖家河")
public class UseAnno {
//使用自定义注解
@MethodName("eat")
public void excetorMethod(){
System.out.println("吃饭饭");
}
}
public class TestAnno {
public static void main(String[] args) throws Exception {
//获取类对象
Class clazz = UseAnno.class;
//判断是否有指定注解,如果有就操作注解
if (clazz.isAnnotationPresent(ClassName.class)){
//获取指定注解
ClassName anno = (ClassName) clazz.getAnnotation(ClassName.class);
//得到注解的值
String value = anno.value();
//通过注解值获取类对象
Class aClass = Class.forName(value);
//以下代码执行类方法
Method[] methods = aClass.getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(MethodName.class)) {
// MethodName methodName = method.getAnnotation(MethodName.class);
// String name = methodName.value();
Object o = aClass.newInstance();
method.invoke(o);
}
}
}
}
}
十八、线程
18.1 概述
并发与并行
并行:多个事件同一时刻发生(同时发生)
并发:多个事件同一时间段内发生
在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单 CPU系统中,每 一时刻只能有一道程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分 时交替运行的时间是非常短的。
而在多个 CPU 系统中,则这些可以并发执行的程序便可以分配到多个处理器上(CPU),实现多任务并行执行, 即利用每个处理器来处理一个可以并发执行的程序,这样多个程序便可以同时执行。目前电脑市场上说的多核CPU,便是多核处理器,核 越多,并行处理的程序越多,能大大的提高电脑运行的效率。
注意:单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同 理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个 线程一个线程的去运行,当系统只有一个CPU时,进程会以某种顺序执行多个线程,我们把这种情况称之为 线程调度。
结论:cpu的核数类似于电脑的脑子数量,核数越高,意味着能够同一时间做的事(并行)就越多
线程与进程
线程:进程内部的一个独立执行单元;一个进程可以同时并发的运行多个线程,可以理解为一个进程便相当 于一个单 CPU操作系统,而线程便是这个系统中运行的多个任务。
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多 个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创 建、运行到消亡的过程。
区别:
- 进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。
- 线程:堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小的多。
注意:
-
因为一个进程中的多个线程是并发运行的,那么从微观角度看也是有先后顺序的,哪个线程执行完全取决于CPU 的调度,程序员是可以干涉的。默认情况下是没有干预,而这也就造成的多线程的随机性。
-
Java程序的进程里面至少包含两个线程,主进程也就是 main()方法线程,另外一个是垃圾回收机制线程。每当使用java命令执行一个类时,实际上都会启动一个JVM,每一个JVM实际上就是在操作系统中启动了一个进程,java本身具备了垃圾的收集机制,所以在 Java运行时至少会启动两个线程。
-
由于创建一个线程的开销比创建一个进程的开销小的多,那么我们在开发多任务运行的时候,通常考虑创建 多线程,而不是创建多进程。
线程调度:
计算机通常只有一个CPU时,在任意时刻只能执行一条计算机指令,每一个进程只有获得CPU的使用权才能执行指令。所谓多进程并发运行,从宏观上看,其实是各个进程轮流获得CPU的使用权,分别执行各自的任务。那么,在可运行池中,会有多个线程处于就绪状态等到CPU,JVM就负责了线程的调度。JVM采用的是抢占式调度,没有采用分时调度,因此可以能造成多线程执行结果的的随机性。
18.2 创建方式
18.2.1 继承Thread类
18.2.2 实现Runnable接口
继承Thread类和实现Runnable接口的区别
- 实现接口打破了继承的单一性
- 创建一个实现接口的线程任务,可以使用多个Thread类启用,实现线程的复用
- 线程池中只能存放实现了接口的线程对象
18.3 理论
18.3.1 线程的执行原理
18.2.2 Thread类
18.2.2.1 构造方法
public Thread():分配一个新的线程对象。
public Thread(String name):分配一个指定名字的新的线程对象
public Thread(Runnable target):分配一个带有指定目标新的线程对象。
public Thread(Runnable target,Stringname):分配一个带有指定目标新的线程对象并指定名字。
18.2.2.2 常用方法
public String getName()
获取当前线程名称。
public void start()
导致此线程开始执行; Java虚拟机调用此线程的run方法。
public void run()
此线程要执行的任务在此处定义代码。
public static void sleep(long millis)
使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
join()
优雅的结束线程
yield()
线程谦让
setPriority()
设置线程优先级,最大为10,最小为1,默认为5
作用
-
模拟网络延时:放大问题的发生性
-
倒计时
-
不会释放锁
public static Thread currentThread()
返回对当前正在执行的线程对象的引用。
18.2.3 线程状态
线程状态 | 导致状态发生的条件 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动。还没调用start方法。 |
Runnable(可运行) | 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操 作系统处理器(cpu)。 |
Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状 态;当该线程持有锁时,该线程将变成Runnable状态。 |
Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个 状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
TimedWaiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep、Object.wait。 |
Teminated(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。 |
18.4 线程安全
18.4.1 并发问题
模拟线程安全问题场景:买票
public class Demo01 implements Runnable{
private int ticket = 100;//票数
@Override
public void run() {
while (true){
buyTicket();
}
}
private void buyTicket(){
if (ticket > 0){
try {
//模拟网络延时:放大问题的发生性
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "买到了第" + ticket-- + "张票");
}
}
public static void main(String[] args) {
Demo01 demo01 = new Demo01();
Thread t1 = new Thread(demo01, "张三");
Thread t2 = new Thread(demo01, "李四");
Thread t3 = new Thread(demo01, "王五");
t1.start();
t2.start();
t3.start();
}
}
/*
李四买到了第98张票
王五买到了第99张票
张三买到了第100张票
......
张三买到了第0张票
王五买到了第-1张票
*/
发现问题
不仅出现了重复的票数,还出现了0票和负票
出现原因
t1、t2和t3三个线程都在买票,并且互不干涩,但是它们都在抢占CPU的资源,这时谁抢到了CPU的资源谁就能够执行。
假如t1强到了资源,然后执行到了如下的这行代码,而这行代码中包含了非原子性的代码语句(ticket–);
System.out.println(Thread.currentThread().getName() + "买到了第" + ticket-- + "张票");
如果这时t1刚执行完这条语句,ticket还等于100。然后不巧的是又有其它线程例如t2抢占了CPU资源,执行到了这个地方,t2读取到的ticket还是未被(–)的100,这时问题就很明显了,两个线程都拿到的是同一张票,这就是线程中的并发问题。
凡是写操作,都不是原子性操作
18.4.2 同步机制
线程同步就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池,形成等待队列
形成条件:队列+锁(synchronized)
18.4.2.1 synchronized
synchronized锁又叫悲观锁、重量级锁、互斥锁、排它锁、(重入锁)
同步代码块
同步方法
注意:
- 这个关键字如果明确指定了锁对象,那么就是给指定的对象上锁,否则就是给本身的对象上锁
- 锁对象可以是除了基本类型和null之外的任意对象
18.4.2.2 Lock锁
通过显示定义同步锁,能够看到锁的开始和结束
ReentrantLock(可重入锁)类实现了Lock,拥有和synchronized相同的并发性和内存语义
18.5 方法
wait:表示线程会一直等待,直到其他线程通知,会释放锁
wait(long timeout):指定等待的毫秒数
notify:
唤醒一个处于等待状态的线程
唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。
直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。
实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。)
此方法只应由作为此对象监视器的所有者的线程来调用。通过以下三种方法之一,线程可以成为此对象监视器的所有者:
-
通过执行此对象的同步实例方法。
-
通过执行在此对象上进行同步的 synchronized 语句的正文。
-
对于 Class 类型的对象,可以通过执行该类的同步静态方法。
一次只能有一个线程拥有对象的监视器。
notifyAll:唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度
注意:
均是object类的方法,都只能在同步方法或同步块中使用,否则会抛出异常
抛出IllegalMonitorStateException - 如果当前线程不是此对象监视器的所有者,调用wait()方法和notifyAll()方法都必须是锁对象,如果不写默认为this。
实例:
//面包店
public class BreadShop {
public int number = 0;
public final int maxNumber = 20;
public final int minNumber = 5;
public static void main(String[] args) {
BreadShop breadShop = new BreadShop();
Thread producer = new Thread(new Producer(breadShop));
Thread consumer = new Thread(new Consumer(breadShop));
producer.start();
consumer.start();
}
}
//生产者
public class Producer implements Runnable{
BreadShop breadShop;
Producer() {}
Producer(BreadShop breadShop) {
this.breadShop = breadShop;
}
@Override
public void run() {
while (true) {
synchronized (breadShop) {
if (breadShop.number < breadShop.maxNumber) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
addBread();
System.out.println("生产一个面包,库存剩余" + breadShop.number + "个");
}else {
System.out.println("商店库存已满,请购买");
breadShop.notifyAll();
try {
breadShop.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public void addBread() {
breadShop.number++;
}
}
public class Consumer implements Runnable{
BreadShop breadShop;
public Consumer() {
}
public Consumer(BreadShop breadShop) {
this.breadShop = breadShop;
}
@Override
public void run() {
while (true) {
synchronized (breadShop) {
if (breadShop.number > breadShop.minNumber) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
buy();
System.out.println("购买一个面包,库存剩余" + breadShop.number + "个");
}else {
System.out.println("面包不够,需要等待");
try {
breadShop.notifyAll();
breadShop.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public void buy() {
breadShop.number--;
}
}
//消费者
十九、网络编程
19.1 网络通信
计算机之间的信息交互是通过服务器作为媒介的
数据传输离不开IO流
网络通信协议:计算机通信必须遵守的通信规则,不遵守则不能进行通信。通信协议对数据的传输格式、传输速率、传输步骤等做了同意规定。
TCP/IP协议:定义了计算机如何连入Internet网,以及数据之间传输的标准
通信协议:
TCP:面向连接,安全、可靠、数据大小没有限制
三次握手
1.客户端给服务器发送连接请求,等待服务器确认
2.服务器回复一个响应,通知客户端收到了请求
3.客户端再次向服务器发送信息,确认连接
UDP:面向无连接,不安全、不可靠、数据大小有限制
网络三要素
IP地址: 本机IP地址 127.0.0.1 、localhost
端口号: 一般四位
协议:TCP/UDP
19.2 TPC通信程序
19.2.1 两端通信时步骤
1.服务器程序,需要先启动,等待客户端的连接
2.客户端主动连接服务器,连接成功才能通信。服务器不能主动连接客户端
使用两个类实现TCP通信
1.客户端:java.net.Socket
//两个参数:1.服务器的ip地址;2.程序的端口号
public Socket(String host, int port) throws UnknownHostException, IOException{
this(host != null ? new InetSocketAddress(host, port) :
new InetSocketAddress(InetAddress.getByName(null), port),
(SocketAddress) null, true);
}
2.服务器:java.net.ServerSocket
//参数:指定程序的端口号,等待客户端的连接
public ServerSocket(int port) throws IOException {
this(port, 50, null);
}
19.2.2 代码实现
- 客户端与服务器通信
//客户端
public class Demo03Socket {
public static void main(String[] args) throws IOException {
//创建客户端对象
Socket socket = new Socket("127.0.0.1", 9999);
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();
Scanner scanner = new Scanner(System.in);
while (true) {
//发送消息
String msg = scanner.next();
out.write(msg.getBytes());
//接受消息
byte[] bytes = new byte[1024];
int len = in.read(bytes);
System.out.println(new String(bytes));
}
// in.close();
// out.close();
// socket.close();
}
}
//服务器
public class Demo03ServerSocket {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9999);
//等待客户端的连接,返回值是客户端对象
Socket accept = serverSocket.accept();
InputStream in = accept.getInputStream();
OutputStream out = accept.getOutputStream();
Scanner scanner = new Scanner(System.in);
System.out.println("客户端连接");
while (true) {
//接受消息
byte[] b = new byte[1024];
// int len;
in.read(b);
System.out.println(new String(b));
//发送消息
String message = scanner.next();
out.write(message.getBytes());
}
// out.close();
// in.close();
// serverSocket.close();
}
}
- 多个客户端与服务器通信(客户端代码没变化,只需要创建多个类就行)
//服务器
public class Demo04ServerSocket {
public static void main(String[] args) throws IOException {
//创建服务器
ServerSocket serverSocket = new ServerSocket(9999);
while (true) {
Socket accept = serverSocket.accept();
System.out.println("客户端连接");
/*
这为什么要用线程呢
因为线程会从新开辟一个运行路线,main方法的程序执行到这就会跳过,然后又会回到上面的循环
如果没有线程,就会一直陷入在下面的无限循环对话中
*/
new Thread(() -> {
try {
//等待客户端的连接,返回值是客户端对象,具有阻塞的效果
InputStream in = accept.getInputStream();
OutputStream out = accept.getOutputStream();
Scanner scanner = new Scanner(System.in);
//接受消息
byte[] b = new byte[1024];
// int len;
while (true) {
in.read(b);
System.out.println(new String(b));
//发送消息
String message = scanner.next();
out.write(message.getBytes());
}
} catch (Exception e) {
}
}).start();
}
// out.close();
// in.close();
// serverSocket.close();
}
}
注意事项
- 线程的使用。在服务器端使用多线程的时候需要创建一个死循环,然后利用服务器创建客户端对象的阻塞来控制循环的执行次数。所以客户端对象的创建放在循环的里面。线程要放在创建客户端对象的代码下面,这样将服务器与客户端对象的通信代码脱离了主线程,也不会影响到其他客户端的创建。
- 客户端输出流的使用。当服务器接收从客户端传来的资源时(反之也一样),使用到了while循环,这时只有当客户端的输出流关闭了,服务器的whhile循环才不会阻塞,否则就会默认客户端还会传来资源,然后处于一直等待的状态。所以要记得客户端使用输出流后要关闭。关闭的方法有三:
- 直接关闭客户端对象:client.close();
- 关闭输出流:out.close();
- showdownOutput()方法。
二十、类加载器
代码分为三个阶段
- 源代码阶段
- 类加载器阶段
- class类对象阶段
类加载器的种类
- AppClassLoader 主要是帮助我们加载我们自己写的代码或者class文件
- ExtClassLoader 主要是加载E:\develop\Java\jdk1.8.0_192\jre\lib\ext下面的jar包或者class文件
- BootStrapClassLoader 主要是E:\develop\Java\jdk1.8.0_192\bin下面的jar包或者class文件
双亲委派
AppClassLoader的父类加载器是ExtClassLoader,ExtClassLoader的父类加载器是BootStrapClassLoader。
JVM在加载一个类的时候,会调用AppClassLoader的loadClass方法来加载这个类,不过在这个方法中,会先使用ExtClassLoader的loadClass方法来加载类,同样ExtClassLoader会先使用BootStrapClassLoader来加载类,如果BootStrapClassLoader加载到了就直接成功,如果没有加载到,那么ExtClassLoader就会自己加载,如果ExtClassLoader也没加载到,再由AppClassLoader自己加载这个类。
所以,双亲委派就是,JVM在加载类时,会先委派给ExtClassLoader 和BootStrapClassLoader 加载,如果没有加载到才由AppClassLoader 自己加载。
扩展
相同的类加载器加载相同的类,内存中只会有一个class对象
不同的类加载器加载相同的类,内存中会有多个class对象
例如,热部署
热部署的原理就是通过监听器监听代码是否被改动,如果被改动,就由不同的类加载器加载出另一个类对象。这样就实现了不用重启服务器并且整改了系统。
二十一、枚举
与泛型一样是jdk1.5之后出现的新特性
枚举就是有限实现个数的类型,一般最多也就十多个实例,再多就不会定义为枚举了
枚举的本质就是类
枚举不能创建对象
枚举默认是final修饰的,不能被继承
如果有抽象方法,枚举类就不再是final修饰的,枚举值可以看做是枚举类的子类,并且必须重写抽象方法
public enum Direction {
//枚举值,可以理解为实例对象,枚举对象无法在外面创建,只能从枚举值获取
FRONT,BEHIND,LEFT,RIGHT;
//默认为私有的无参构造,且必须为私有不能被修改
private Direction(){
System.out.println("执行无参构造");
}
}
public class DirectionTest {
public static void main(String[] args) {
Direction front = Direction.FRONT;
System.out.println(front);
}
}
/**
运行结果:
执行无参构造
执行无参构造
执行无参构造
执行无参构造
FRONT
*/
枚举类中可以写抽象方法
public enum Direction {
//枚举值,可以理解为实例对象,枚举对象无法在外面创建,只能从枚举值获取
//可以把枚举值当做枚举类的子类,枚举值必须重写枚举类的抽象方法,普通方法可重写也可以不重写
FRONT() {
@Override
public void show() {
System.out.println("show方法");
}
@Override
public void fun() {
System.out.println("执行fun方法");
}
},BEHIND(20){
@Override
public void show() {
}
},LEFT(18,"静静"){
@Override
public void show() {
}
},RIGHT(){
@Override
public void show() {
}
};
//默认为私有的无参构造,且必须为私有不能被修改
private Direction(){
System.out.println("执行无参构造");
}
public int age;
public String name;
private Direction(int age) {
this.age = age;
}
private Direction(int age, String name) {
this.age = age;
this.name = name;
}
public void fun() {
System.out.println("fun方法");
}
public abstract void show();
}