欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JAVASE基础

程序员文章站 2022-07-14 11:09:57
...

第一章 Java概述

Java语言的特点

  • **完全面向对象:**Java语言支持封装、继承、多态,面向对象编程,让程序更好达到高内聚低耦合的标准。
  • **支持分布式:**Java语言支持Internet应用的开发,在基本的Java应用编程接口中有一个网络应用编程接口(java net),它提供了用于网络应用编程的类库,包括URL、URLConnection、Socket、ServerSocket等。Java的RMI(远程方法**)机制也是开发分布式应用的重要手段。
  • **健壮型:**Java的强类型机制、异常处理、垃圾的自动收集等是Java程序健壮性的重要保证。对指针的丢弃是Java的明智选择。
  • **安全:**Java通常被用在网络环境中,为此,Java提供了一个安全机制以防恶意代码的攻击。如:安全防范机制(类ClassLoader),如分配不同的名字空间以防替代本地的同名类、字节代码检查。
  • **跨平台性(平台无关性):**Java程序(后缀为java的文件)在Java平台上被编译为体系结构中立的字节码格式(后缀为class的文件),然后可以在实现这个Java平台的任何系统中运行。
  • 多线程

1.5新特性 新增了 枚举 自动拆装箱 增强for循环 静态方法导入 泛型 变长参数组

Java语言的跨平台原理

1.跨平台:任何软件的运行,都必须要运行在操作系统之上,而我们用Java编写的软件可以运行在任何的操作系统上,这个特性称为Java语言的跨平台特性。该特性是由JVM实现的,我们编写的程序运行在JVM上,而JVM运行在操作系统上。

2.JVM(Java Virtual Machine ):Java虚拟机,简称JVM,是运行所有Java程序的假想计算机,是Java程序的运行环境之一,也是Java 最具吸引力的特性之一。我们编写的Java代码,都运行在JVM 之上。

**3.JRE ** (Java Runtime Environment) :是Java程序的运行时环境,包含JVM 和运行时所需要的核心类库

4.JDK (Java Development Kit):是Java程序开发工具包,包含JRE 和开发人员使用的工具。

dos命令

进入目录命令:cd

查看当前目录下有什么命令:dir

新建目录命令:md (make directory)

新建空文件命令:type nul

追加内容到文件命令:echo

复制(copy)或移动(move)文件

删除文件命令:del

删除目录命令:rd(remove directory)

查看某个目录的下一级目录结构:tree

清屏命令:cls

退出命令:exit

配置环境变量

为什么配置path?

希望在命令行使用javac.exe等运行的工具时,任意目录下都可以找到这个工具所在的目录。

第一个程序

1、源文件名与类名一致问题?

**(1)**源文件名是否必须与类名一致?public呢?

如果这个类不是public,那么源文件名可以和类名不一致。

如果这个类是public,那么要求源文件名必须与类名一致。

我们建议大家,不管是否是public,都与源文件名保持一致,而且一个源文件尽量只写一个类,目的是为了好维护。

**(2)**一个源文件中是否可以有多个类?public呢?

一个源文件中可以有多个类,编译后会生成多个.class字节码文件。

但是一个源文件只能有一个public的类。 class

**(3)**main必须在public的类中吗?

不是。

但是后面写代码时,基本上main习惯上都在public类中。

**(4)**运行工具

javac HelloWord.java 后面运行源文件

java HelloWord 运行.class字节码文件。 后面跟类名

第二章 Java基础知识

1.注释

(1)

// 注释内容

(2)

/*
多行注释
*/

(3)

/**
文档注释
*/

2.关键字

是指在程序中,Java已经定义好的单词,具有特殊含义。publicclassstaticvoid

3.标识符

1、标识符的命名规则(必须遵守)

(1)Java的标识符只能使用26个英文字母大小写,0-9的数字,下划线_,美元符号$

(2)不能使用Java的关键字(包含保留字)和特殊值

(3)数字不能开头

(4)不能包含空格

(5)严格区分大小写

类名、接口名等:每个单词的首字母都大写,形式:XxxYyyZzz

变量、方法名等:从第二个单词开始首字母大写,其余字母小写,形式:xxxYyyZzz

常量名等:每一个单词都大写,单词之间使用下划线_分割,形式:XXX_YYY_ZZZ,

例如:MAX_VALUE,PI

4.数据类型

Int m = 3,n=5;

  • 基本数据类型:包括 整数浮点数字符布尔
  • 引用数据类型:包括 数组接口

byte short int(默认) long float double(默认) char boolean 1个字节

5.常量

字符串常量 ”HelloWorld“ 整数常量 12,-23 浮点常量 12.34 字符常量 ‘a’,‘0’,‘我’ 布尔常量 true,false

空常量 null

6.变量

三要素 变量数据类型 变量名 当前值

7.在代码中如何表示四种进制的值

(1)十进制:正常表示

System.out.println(10);

(2)二进制:0b或0B开头

System.out.println(0B10);

(3)八进制:0开头

System.out.println(010);

(4)十六进制:0x或0X开头

System.out.println(0X10);

8.Java的基本数据类型的存储范围

(1)byte:字节类型

  • 占内存:1个字节

  • 存储范围:-128~127

(2)short:短整型类型

  • 占内存:2个字节

  • 存储范围:-32768~32767

(3)int:整型

  • 占内存:4个字节

  • 存储范围:-2的31次方 ~ 2的31次方-1

(4)long:整型

  • 占内存:8个字节

  • 存储范围:-2的63次方 ~ 2的63次方-1

  • 1)float:单精度浮点型

    • 占内存:4个字节

    • 精度:科学记数法的小数点后6~7位

    注意:如果要表示某个常量小数是float类型,那么需要在数字后面加F或f,否则就是double类型

    (2)double:双精度浮点型

    • 占内存:8个字节

    • 精度:科学记数法的小数点后15~16位

      char

  • 占内存:2个字节

补码与符号位

正数 原码 补码 反码 一样 负数 原码 最高位为1 符号位 补码 原码取反,符号位不变 补码 原码加一

1000 0000 ==> -128(特殊规定)

0 48

A 65

a 97

自动类型转换

取值范围小的类型自动提升为取值范围大的类型

(1)(int) 强制类型转换:将取值范围大的类型强制转换成取值范围小的类型。String类型不能通过强制类型()转换,转为其他的类型.

(2)当存储范围小的数据类型与存储范围大的数据类型一起混合运算时,会按照其中最大的类型运算

(3)当byte,short,char数据类型进行算术运算时,按照int类型处理

运算符(Operator)

(1)算术运算符 + - * / % ++ –

第一种:对于+两边都是数值的话,+就是加法的意思

System.out.println(5   +  20   +  str2); // 25java    //从左运算   20 +5 =25  后面才是字符拼接

a = a++;
/**
(1)先取a的值“1”放   操作数栈  
(2)a再自增,a=2(3)再把操作数栈中的"1"赋值给a,
a=1,
中间无其他操作 
只有算术运算时才会出现 操作数栈
*/

第二种:对于+两边至少有一边是字符串得话,+就是拼接的意思

(2)赋值运算符 = += -= *= /= %=

**等号优先级最低 ** 自动强制类型转换

int i = 10;
i *=i+1;   //   i = i +(i+1);     等号优先级最低
int f = 10;
f *=f++ + 1;  //f = f* (f +1); 

(3)关系运算符/比较运算符 > < == >= <= !=

(4)逻辑运算符 & | ! ^ && ||

2、&&和&区别,||和|区别

  • **&&&**区别:
    • &&&结果一样
    • &&左边为false,右边不执行;
    • &左边无论是什么,右边都会执行。
  • **|||**区别:
    • |||结果一样
    • ||左边为true,右边不执行;|
    • 左边无论是什么,右边都会执行。

(5)条件运算符

​ 条件表达式?结果1:结果2

char x = 'x';
int i = 10;
System.out.println(true? x : i);  都是变量  默认自动类型转换成高的类型  转成int
System.out.println(true? 97:'x');   全是常量  且  有char    另一个常量数字[0~65535]之间的整数按char处理;
System.out.println(true? i:'x');       若是string   就是原来自己的类型  是啥就是啥
  /*

 * 如果其中有一个是变量,按照自动类型转换规则处理成一致的类型;
 * 如果都是常量,如果一个是char,如果另一个是[0~65535]之间的整数按char处理;
 * 如果一个是char,另一个是其他,按照自动类型转换规则处理成一致的类型;
    */

(6)位运算符

& | ~ ^ << >> >>>

对二进制数操作 只能整数 不能浮点数二进制计算 都是使用补码+

左移:<<

​ 运算规则:左移几位就相当于乘以2的几次方 补0

右移:>>

​ 运算规则:右移几位就相当于除以2的几次方 右移 正数补0 负数 补 1

无符号右移:>>>

​ 运算规则:往右移动后,左边空出来的位直接补0,不看符号位

(7)优先级

算术->位–>比较–>逻辑–>三元–>赋值 =

第三章 流程控制语句

3.1顺序结构

3.2输入语句

java.util.Scanner

Scanner input = new Scanner(System.in);

int num1 = input.nextInt();

char num2 = input.next().charAt(0);

long bigNum = input.nextLong();
double d = input.nextDouble();
boolean b = input.nextBoolean();
String s = input.next();

/*
next()方法:
遇到空格等空白符,就认为输入结束
nextLine()方法:
遇到回车换行,就认为输入结束

3.3分支结构

1.if(条件表达式){

语句体;

2.if(关系表达式) {

语句体1;

}else {
语句体2;
}

3.if (判断条件1) {

执行语句1;

} else if (判断条件2) {
执行语句2;
}

}else if (判断条件n) {
执行语句n;
} else {
执行语句n+1;
}

判断条件需要从大到小!

4.分支结构:switch

switch(表达式){
case 常量值1:
语句块1;
【break;】
case 常量值2:
语句块2;
【break;】
。。。
【default: default
语句块n+1;
【break;】

},

(1)switch(表达式)的值的类型,只能是:4种基本数据类型(byte,short,int,char),两种引用数据类型(JDK1.5之后枚举、JDK1.7之后String)

(2)case后面必须是常量值,而且不能重复

就会顺序往下执行,直到遇到“出口”,即可能发生贯穿

使用break 退出

3.4. continue break 对比

break:跳出循环,当break运行时将结束整个循环,程序从循环后开始向下执行,break也可以控制switch
continue:结束本次循环,开始下一次循环。continue运行,则返回循环起点再次判断循环条件。continue只能控制循环。

3.5 循环结构

1.while循环 循环操作依赖循环条件

while(条件true/false){
语句
}

先判断再执行

2.do…while循环 循环条件依赖循环操作

do {
循环体语句①;
} while (循环条件语句②);

先执行语句,再判断条件;

3.6 while do while for 对比

while循环:执行循环次数不确定,先判断后执行的循环。

do-while循环:执行循环次数不确定,先执行后判断。

for循环:执行循环次数确定的,先判断后执行。|

(1)for(;;)中的两个;是不能多也不能少

(2)循环条件必须是boolean类型

(3)如果循环条件语句②省略的话,就默认为循环条件成立

for(赋值;循环条件;迭代语句)

3.7增强for循环

for(Student s : stus){System. out. println(s);}

(数组类型 数组名 : 原数组名) 输出数组名

第四章 数组

1.定义

​ 保存多个数据的容器

2.四要素

数组类型 数组名 数组元素 下标 ;

数组类型:一个数组只能保存同类型数据,不能保存其他类型数据。

数组名:使用数组名访问数组

数组元素:使用数组参与运算时一定是数组的元素参与运算,一个该数组的元素就形同于一个给数组类型的变量。访问下表:访问数组元素 的方式是数组名[访问下标],访问下标有合法访问下标和非法访问下标,合法访问下标的区间是0到数组长度-1。|

3.数组的声明

数组没有length()方法,但是有length属性

//动态声明
int[] one = new int[5];  //声时确定数组长度,但没有为每一个数组元素赋值,数组元素在不赋值的情况下是保存黑默认初始值。
one[0] =100;  //数组名[下标]访问数组元素
//静态声明
int[] two = {10,20,30};  //必须在一行之内完成,每个数组元素都是初始值,数组的长度靠大括号中的元素数来决定
int[] three;
three = new int[]{40,50,60}; //可以断成两行完成,其他和第一种静态声明一样

4.数组遍历

//一维数组的遍历
for(int i = 0; i < arr.length;i++){
}

//二维数组的遍历
for(int i = 0;i < nums.length; i++){// 循环遍历一维数组
    for(int j = 0;j < nums[i].length; j++){
        System.out.println(nums[i][j]);
    }
}

5.多维数组

int[][] nums = new int[5][3];// 同时给出一维和二维的数组长度。

int[][] nums = new int[5][];// 只给出一维的长度,不给二维的长度。此时一维元素为null。
nums[0] = new int[3];// 创建新数组给一维元素赋值。

int[][] nums = {{35,87,90},{35,77,20,17},{90,87,65,100,40}};// 静态声明二维数组,必须在声明数组的同时完成赋值

int[][] nums;
nums = new int[][]{{1,2,3},{1,2,3},{1,2,3}};// 静态初始化二维数组,允许在数组声明之后赋值。


对象数组  
 Object nums = new Object[3];

第五章 面向对象

1.定义

面向过程:考虑问题主要是分步,步骤不可逆,顺序执行。

面向对象:考虑问题主要是找到解决问题的类,用类创建对象,用对象调用属性或者方法。

​ 类:是一个规范,是一个模板,规定了一类事物必须有的属性和方法。一组相关属性行为的集合。 抽象描述 类是对象的类型

​ 对象:使用类创建出来的具体的客观存在的实体。使用对象调用属性,使用对象调用方法。 具体实体。

public   class Chinese{
    char gender = '男';//显式赋值
	static String country;
	private String name;
    public  String sayHello(){
    public  Chinese1 chinese;
    }
}
public   class Chinese1{
    char gender = '男';//显式赋值    成员变量  对象属性,属于某个对象的,通过对象来使用
	static String country;    //类变量   静态成员变量,属于整个类的,不是属于某个实例  
	private String name;      // 声明私有变量,私有类自己的类可以使用(只能本类之中使用),其它类不可使用。
    public  String sayHello(){   //声明公共方法,公共方法其他类可以调用 (其它类中也可以调用)
    }                         //成员方法
}

public static void main(String[] args) {
		//类名.静态成员变量
		System.out.println(Chinese.country);
		Chinese c1 = new Chinese();
		//对象名.普通成员变量
		System.out.println(c1.name);
		//静态的成员变量也可以通过对象.进行访问
		//对象名.普通成员变量
		System.out.println(c1.country);
        System.out.println(c1.gender);
}

2.成员变量的特点

​ 创建对象 属性在堆里分配空间 对象在栈里空间, 方法入栈分配空间 ,不是在方法区

1成员变量有默认值

2 类变量的值是所有对象共享的 ,而实例变量的值是每个对象独立的

static String country;    //类变量   静态成员变量,属于整个类的,不是属于某个实例  

用类名调用 Chinese.country

Math.random();

public static void methodOne(){
    // 静态成员只能访问静态成员,不能访问普通成员。
    // 普通成员可以访问静态成员,也可以访问普通成员。
    System.out.println(country);
    // System.out.println(this);// 静态方法中不能使用this关键字
    // System.out.println(name);
    // methodTwo();
    System.out.println("in static methodOne~~~~~~~~~~~~~~~~~~~~~");
}

3.一个普通的方法

由五个常规部分组成

1.修饰符: 修饰符后面一一介绍,例如:public,static等都是修饰符

// default   不写修饰符  只能在本包使用
//protected修饰: 本包及非本包的子类可以访问
// public为访问修饰符,访问修饰符修饰类的成员,public为公开模式,任何位置都可以访问
// private为私有访问,只能本类使用,其他位置不能访问
// void位置是返回类型,规定了该方法结束是返回的值的类型。void为该方法无返回

2.返回值类型

  • 基本数据类型
  • 引用数据类型
  • 无返回值类型:void

3.方法名

4.参数列表

5.方法体

除了方法体 其他的 为 方法签名

​ return:结束方法,并将方法的结果返回去

​ 在 void中 属于 结束方法

4.对象数组

​ Student[] s = new Student[3];

​ s[0] = new student();

​ s[1] = new student();

​ s[2] = new student();

​ for(int i=0; i<arr.length; i++){
​ arr[i] = new Circle();//有对象才有半径
​ arr[i].radius = Math.random()*9+1;
​ }

5.方法的参数传递机制:

实参给形参赋值

  • 方法的形参是基本数据类型时,形参值的改变不会影响实参;
  • 方法的形参是引用数据类型时,形参地址值的改变不会影响实参,但是形参地址值里面的数据的改变会影响实参,例如,修改数组元素的值,或修改对象的属性值。

值传递 :

int a= 3;

int b =3;

ma(a,b){}

不会改变 a,b的值

引用传递: 但如果是string类型 是重新new 对象 所以不会改变原值

Student student=new Student();

student.a = 4;

student.b =5;

ma(a,b){}

会改变a,b的值;

6.方法重载

​ 指在同一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可,与修饰符和返回值类型无关。(多个方法)

​ 数据类型个数不同,数据类型不同,数据类型顺序不同。

重载:在同一个类中,方法名相同,参数组不同。
重写:在父子类中,子类编写和父类方法签名一模一样的方法。

7.变长参数组

public static void main(Stringl args) {
         methodOne(10,5.5,"hello","good","ok","baibai");
}
//变长参数组,同种类型的参数可以不传,也可以传任意数量个参数
//任何一个方法,变长参数组只能有一个
//变长参数组必须在参数列表的最后
//stu.showInfo("a"" "b", "c", "d");// 变长参数组传参,依次传入多个同类型的参数,数量没有限制。
//stu.showInfo();//可以在定义变长参数的方法调用时,不传递任何参数。
//stu.showInfo(10,5.5, "a", "b","c");// 调用带有普通参数和变长参数的方法时先传递普通参数,在传递变长参数组列表。

public static void methodOne(int one, double two,String... words){
for(int i =0;i<words. length; i++){
Svstem.out.println(words[i]);
}

8.static

修饰类的成员

: 修饰属性,属性变成类所有对象公共的属性,类变量。 用类名调用,也可以用对象名调用,不推荐。

修饰方法,静态方法,静态方法可以用类名直接调用,也可以对象名调用,不推荐。

static关键字修饰的静态成员,只能访问静态成员,普通成员可以访问静态成员,也可以访问普通成员。

静态属性 在方法区 分配空间

第六章 面向对象基础 – 中

1.封装

概念:隐藏具体细节,对外暴露尽量少的信息。
使用JavaBean实现封装:
JavaBean: 1、所有的属性都是用private修饰,提供public的get/set方法。2、必须有无参构造方法。3、拥有一个可以打印所有属性信息的方法。

2.构造方法

1.构造方法的作用就是为实例变量初始化, 但不能初始化静态变量。

  1. 构造器名必须与它所在的类名必须相同。

  2. 如果你不提供构造器,系统会给出无参数构造器,并且该构造器的修饰符默认与类的修饰符相同

  3. 如果你提供了构造器,系统将不再提供无参数构造器,除非你自己定义。

  4. 构造器是可以重载的,既可以定义参数,也可以不定义参数。

  5. 构造器的修饰符只能是权限修饰符,不能被其他任何修饰

  6. public Student(){

  7. }

    publc Student(String a){

    }

    6.如果类中有任何一个显示有参的构造方法,需要把无参构造方法写出来
    /构造方法只能在创建对象时调用,创建对象必须调用构造方法。
    //如果一个java类没有任何一个显示的构造方法,那么这个类拥有一个隐示无参的构造方法

    //如果一个java类中编写了任何一个显示的构造方法,那么这个隐示无参的构造方法就会消失

第六章 面向对象基础 – 中

1.继承

  • 提高代码的复用性

  • 提高代码的扩展性

  • 类与类之间产生了关系,是学习多态的前提

extends 一个子类继承一个父类,父类可以有多个子类,子类只能一个父类

super 调用父类公共属性 和方法

2.特点

1.成员变量
  • 父类中的成员,无论是公有(public)还是私有(private),均会被子类继承,构造方法不继承。
  • 子类虽会继承父类私有(private)的成员,但子类不能对继承的私有成员直接进行访问,可通过继承的get/set方法进行访问。

​ 若 父子类成员变量重名

​ 创建的是子类对象,子类优先访问自己的属性.
​ 但是子类没有,所以向*问父类/子类独有的属性.

使用的是父类方法 就近找 父类属性。

2.成员方法(重写)

重写后 new谁调谁的方法。

注意事项:

子类编写一个和父类方法签名一模一样的方法,子类重写了父类的方法

子类一旦且重写父类的方法,那么再调用的一定是子类重写的方法
子类一旦重写父类的方法,只有一个机会可以调用父类的方法,在子类中使用super关键字调用

因为此时在创建子类的对象过程中,所以这个method(100)方法是子类对象再调用,那么又因为子类重写了method(int)方法,所以执行子类的方法

[email protected]:写在方法上面,用来检测是不是有效的正确覆盖重写。这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。建议保留
3.子类方法的返回值类型必须【小于等于】父类方法的返回值类型(小于其实就是是它的子类,例如:Student < Person)。

父类方法 返回类型是引用类型 , 子类返回类型 可以返回 引用类型的子类。

注意:如果返回值类型是基本数据类型和void,那么必须是相同

4.子类方法的权限必须【大于等于】父类方法的权限修饰符。
小扩展提示:public > protected > 缺省 > private

5.几种特殊的方法不能被重写

  • 静态方法不能被重写
  • 私有等在子类中不可见的方法不能被重写
  • final方法不能被重写
3.构造方法

子类构造方法中必须在构造方法的第一行,调用父类的构造方法
如果没有显示写出调用哪个构造方法,

则默认 **super()**调用父类无参的构造方法; **this();**调用本类 无参构造

若父类只写了有参构造,子类必须写构造方法调用父类有参构造。

super();
public  Student(String name,int age ,int gender){
super(name,age,gender)
}

无法继承 父类构造方法。

4.单继承限制

1.Java只支持单继承,不支持多继承。 默认继承 object

2.Java支持多层继承(继承体系)。

3.子类和父类是一种相对的概念。

4.一个父类可以同时拥有多个子类

类变量显式赋值>静态代码块>非静态成员变量的显式赋值>非静态代码块>父类的无参构造

3.类初始化与实例初始化

类初始化肯定优先于实例初始化。

类初始化只做一次。

1.创建类时 就类初始化:目的是为了给静态变量赋值。

先给静态类成员变量的显式赋值语句,后者给静态代码块中的语句

需要new 实例对象时

2.实例初始化 父类中非静态成员变量赋值

先选择构造器

​ 非静态实例变量的显示赋值语句

​ 非静态代码块

​ 对应构造器中的代码

4.final关键字

修饰类不能被继承,

修饰方法不能被子类重写

修饰属性 为常量

修饰参数 基本数据类型 (final int a) 只能赋值一次

引用数据类型 (final string【】 args) 只能赋值

pbulic static final PI = 3.14;

常量为啥 加 static 因为 静态属性,在方法区分配空间。

5.代码块

static{
静态初始化
}

{
实例初始化块
}

第七章 多态

1.概念: 实现代码的灵活性,多态体现在重载和重写方法

任何一个需要父类引用的位置都可以传递一个子类对象。

2.抽象

abstract关键字:修饰类,修饰方法修饰类:类不能创建对象
修饰方法:方法没有方法体
抽象的类中可以有抽象的方法

也可以用普通的方法, 普通属性 私有属性

抽象的方法必须在抽象的类中。

【权限修饰符】 abstract class 类名{
}

3.多态的三个重要步骤

1、继承,父类定义方法,子类重写方法

2、父类的引用指向子类的对象

3、创建对象时多态参数传递时多态

//参数传递时多态:子类对象 变量名 = 子类对象;  需要啥传啥 
//创建对象时多态:父类类型 变量名 = 子类对象;
  //但是形参这边需要使用父类引用。这样就写一个方法传父类形参

方法重写:在父子类中,父类定义方法,子类编写一个和父类方法签名一摸一样的方法,被称作方法重写。子类重写父类方法时,访问修饰符,返回类型,抛出异常三个部分可以和父类不同:
1、子类重写父类方法时,子类的访问修饰符只能比父类的访问修饰符访问级别更宽,不能更窄。
2T返回类型,父类的方法返回类型是void或者基础数据类型时,子类重写的方法必须和父类的方法的返回类型一致。父类的方法的返回类型如果是引用数据类型,子类重写父类方法时,返回类型可以是父类返回类型的子类型。3、抛出异常,父类的方法抛出的异常,子类重写时可以抛出父类异常的子异常。

4、编译时类型与运行时类型不一致问题

  • 编译时,看“父类”,只能调用父类声明的方法,不能调用子类扩展的方法;

  • 运行时,看“子类”,一定是执行子类重写的方法体;

5.多态的应用

1、多态参数

​ 父类类型作为方法形式参数,传递子类对象给方法,进行方法的调用。

2、多态数组

   Animal[] all = new Animal[4];//可以存储各种Animal子类的对象
		all[0] = new Cat();
		all[1] = new Cat();
		all[2] = new Dog();
		all[3] = new Dog();

6. 父子类之间的类型转换

1、向上转型

​ 当父类引用指向一个子类对象时,便是向上转型。这个过程是自动完成的

Animal a = new Cat();

2、向下转型

父类类型向子类类型向下转换的过程,这个过程是强制的。 目的是调用子类独有的方法

Animal a = new Cat();
Cat   b = (Cat)a;

3.转型的异常:ClassCastException

​ 两个类型并没有任何继承关系,不符合类型转换的定义。

​ 使用instanceof运算符判断

if (a instanceof Cat){}

7.多态引用时关于成员变量与成员方法引用的原则

​ Father f = new Son();

​ 左边为编译时类型 new 为运行时类型

1、成员变量:只看编译时类型

​ 父子类 定义同样名的变量,只看编译时类型

2、非虚方法:只看编译时类型

​ 静态方法、私有方法、final方法、实例构造器、通过super调用的父类方法都是非虚方法

3.虚方法:静态分派与动态绑定

​ Animal a = new Cat();
​ a.eat();

在编译期间先进行静态分派:即确定是调用Animal类中的public void eat()方法,如果Animal类或它的父类中没有这个方法,将会报错。

运行期间动态的在进行动态分派:即确定执行的是Cat类中的public void eat()方法,因为子类重写了eat()方法,如果没有重写,那么还是执行Animal类在的eat()方法.

方法的分派有两个需要考虑的因素:

  • 方法的参数:在编译期间确定,根据编译器时类型,找最匹配的

  • 方法的所有者:

    • 如果没有重写,就按照编译时类型处理
    • 如果有重写,就按照运行时类型处理
(1)示例一:重写
abstract class Animal {  
    public abstract void eat();  
}  
class Cat extends Animal {  
    public void eat() {  
        System.out.println("吃鱼");  
    }  
}  

class Dog extends Animal {  
    public void eat() {  
        System.out.println("吃骨头");  
    }  
}

public class Test{
    public static void main(String[] args){
        Animal a = new Cat();
        a.eat();
    }
}

如上代码在编译期间先进行静态分派:即确定是调用Animal类中的public void eat()方法,如果Animal类或它的父类中没有这个方法,将会报错。

而在运行期间动态的在进行动态分派:即确定执行的是Cat类中的public void eat()方法,因为子类重写了eat()方法,如果没有重写,那么还是执行Animal类在的eat()方法

(2)示例二:重载
class MyClass{
	public void method(Father f) {
		System.out.println("father");
	}
	public void method(Son s) {
		System.out.println("son");
	}
	public void method(Daughter f) {
		System.out.println("daughter");
	}
}
class Father{
	
}
class Son extends Father{
	
}
class Daughter extends Father{
	
}
public class TestOverload {
	public static void main(String[] args) {
		MyClass my = new MyClass();
		Father f = new Father();
		Father s = new Son();
		Father d = new Daughter();
        
		my.method(f);//father
		my.method(s);//father
		my.method(d);//father
	}
}

如上代码在编译期间先进行静态分派:即确定是调用MyClass类中的method(Father f)方法,如果Animal类或它的父类中没有这个方法,将会报错。

而在运行期间动态的在进行动态分派:即确定执行的是MyClass类中的method(Father f)方法,因为my对象的运行时类型还是MyClass类型。

有些同学会疑问,不是应该分别执行method(Father f)、method(Son s)、method(Daughter d)吗?

因为此时f,s,d编译时类型都是Father类型,因此method(Father f)是最合适的。

(3)示例三:重载
class MyClass{
	public void method(Father f) {
		System.out.println("father");
	}
	public void method(Son s) {
		System.out.println("son");
	}
}
class Father{
	
}
class Son extends Father{
	
}
class Daughter extends Father{
	
}
public class TestOverload {
	public static void main(String[] args) {
		MyClass my = new MyClass();
		Father f = new Father();
		Son s = new Son();
		Daughter d = new Daughter();
		my.method(f);//father
		my.method(s);//son
		my.method(d);//father
	}
}

如上代码在编译期间先进行静态分派:即确定是分别调用MyClass类中的method(Father f),method(Son s),method(Father f)方法。

而在运行期间动态的在进行动态分派:即确定执行的是MyClass类中的method(Father f),method(Son s),method(Father f)方法,因为my对象的运行时类型还是MyClass类型。

有些同学会疑问,这次为什么分别执行method(Father f)、method(Son s)?

因为此时f,s,d编译时类型分别是Father、Son、Daughter,而Daughter只能与Father参数类型匹配

(4)示例四:重载与重写
class MyClass{
	public void method(Father f) {
		System.out.println("father");
	}
	public void method(Son s) {
		System.out.println("son");
	}
}
class MySub extends MyClass{
	public void method(Daughter d) {
		System.out.println("daughter");
	}
}
class Father{
	
}
class Son extends Father{
	
}
class Daughter extends Father{
	
}
public class TestOverload {
	public static void main(String[] args) {
		MyClass my = new MySub();
		Father f = new Father();
		Son s = new Son();
		Daughter d = new Daughter();
		my.method(f);//father
		my.method(s);//son
		my.method(d);//father
	}
}

如上代码在编译期间先进行静态分派:即确定是分别调用MyClass类中的method(Father f),method(Son s),method(Father f)方法。

而在运行期间动态的在进行动态分派:即确定执行的是MyClass类中的method(Father f),method(Son s),method(Father f)方法。

有些同学会疑问,my对象不是MySub类型吗,而MySub类型中有method(Daughter d)方法,那么my.method(d)语句应该执行MySub类型中的method(Daughter d)方法?

  • my变量在编译时类型是MyClass类型,那么在MyClass类中,只有method(Father f),method(Son s)方法,

  • f,s,d变量编译时类型分别是Father、Son、Daughter,而Daughter只能与Father参数类型匹配

  • 而在MySub类中并没有重写method(Father f)方法,所以仍然执行MyClass类中的method(Father f)方法

8.Object根父类

java.lang.Object是类层次结构的根类,即所有类的父类。每个类都使用 Object 作为超类。

9.Object类的API

API(Application Programming Interface),应用程序编程接口。Java API是一本程序员的字典 ,是JDK中提供给我们使用的类的说明文档。所以我们可以通过查询API的方式,来学习Java提供的类,并得知如何使用它们。在API文档中是无法得知这些类具体是如何实现的,如果要查看具体实现代码,那么我们需要查看src源码

​ 根据JDK源代码及Object类的API文档,Object类当中包含的方法有11个。今天我们主要学习其中的5个:

(1)toString()

public String toString()

①默认情况下,toString()返回的是“对象的运行时类型 @ 对象的hashCode值的十六进制形式"

②通常是建议重写,如果在eclipse中,可以用Alt +Shift + S–>Generate toString()

③如果我们直接System.out.println(对象),默认会自动调用这个对象的toString()

因为Java的引用数据类型的变量中存储的实际上时对象的内存地址,但是Java对程序员隐藏内存地址信息,所以不能直接将内存地址显示出来,所以当你打印对象时,JVM帮你调用了对象的toString()。

例如自定义的Person类:

public class Person {  
    private String name;
    private int age;

    @Override
    public String toString() {
        return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
    }

    // 省略构造器与Getter Setter
}

(2)getClass()

public final Class<?> getClass():获取对象的运行时类型

因为Java有多态现象,所以一个引用数据类型的变量的编译时类型与运行时类型可能不一致,因此如果需要查看这个变量实际指向的对象的类型,需要用getClass()方法

	public static void main(String[] args) {
		Object obj = new String();
		System.out.println(obj.getClass());//运行时类型
	}

(3)finalize()

protected void finalize():用于最终清理内存的方法

public class TestFinalize {
	public static void main(String[] args) {
		for (int i = 0; i < 10; i++) {
			MyData my = new MyData();
		}
		
		System.gc();//通知垃圾回收器来回收垃圾
		
		try {
			Thread.sleep(2000);//等待2秒再结束main,为了看效果
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
class MyData{

	@Override
	protected void finalize() throws Throwable {
		System.out.println("轻轻的我走了...");
	}
	
}

面试题:对finalize()的理解?

  • 当对象被GC确定为要被回收的垃圾,在回收之前由GC帮你调用这个方法,不是由程序员手动调用。

  • 这个方法与C语言的析构函数不同,C语言的析构函数被调用,那么对象一定被销毁,内存被回收,而finalize方法的调用不一定会销毁当前对象,因为可能在finalize()中出现了让当前对象“复活”的代码

  • 每一个对象的finalize方法只会被调用一次。

  • 子类可以选择重写,一般用于彻底释放一些资源对象,而且这些资源对象往往时通过C/C++等代码申请的资源内存

(4)hashCode()

public int hashCode():返回每个对象的hash值。

hashCode 的常规协定:

  • ①如果两个对象的hash值是不同的,那么这两个对象一定不相等;
  • ②如果两个对象的hash值是相同的,那么这两个对象不一定相等。

主要用于后面当对象存储到哈希表等容器中时,为了提高存储和查询性能用的。

	public static void main(String[] args) {
		System.out.println("Aa".hashCode());//2112
		System.out.println("BB".hashCode());//2112
	}

(5)equals()

public boolean equals(Object obj):用于判断当前对象this与指定对象obj是否“相等”

①默认情况下,equals方法的实现等价于与“==”,比较的是对象的地址值

②我们可以选择重写,重写有些要求:

A:如果重写equals,那么一定要一起重写hashCode()方法,因为规定:

​ a:如果两个对象调用equals返回true,那么要求这两个对象的hashCode值一定是相等的;

​ b:如果两个对象的hashCode值不同的,那么要求这个两个对象调用equals方法一定是false;

​ c:如果两个对象的hashCode值相同的,那么这个两个对象调用equals可能是true,也可能是false

B:如果重写equals,那么一定要遵循如下几个原则:

​ a:自反性:x.equals(x)返回true

​ b:传递性:x.equals(y)为true, y.equals(z)为true,然后x.equals(z)也应该为true

​ c:一致性:只要参与equals比较的属性值没有修改,那么无论何时调用结果应该一致

​ d:对称性:x.equals(y)与y.equals(x)结果应该一样

​ e:非空对象与null的equals一定是false

第八章 1.接口

1.定义

​ 它与定义类方式相似,但是使用 interface 关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。

2.声明格式

【修饰符】 interface 接口名{
//接口的成员列表:
     // 静态常量  接口中声明的属性默认修饰为公开静态常量  public static final
     //(1)其中public static final可以省略 
    long MAX_SPEED = 500*1024*1024;//500MB/s
    // 抽象方法  接口中的方法默认修饰为抽象方法  public abstract
    void A();
    // (2) 公共的抽象的方法:其中public abstract可以省略   
    在JDK1.8时,接口中允许声明默认方法和静态方法:
    // 默认方法      其中public 可以省略,建议保留,但是default不能省略    创建实现类对象,调用方法,方法体现接口的特征
    public default void a(){        
    }
    // 静态方法      其中public 可以省略,建议保留,但是static不能省略     必须由接口名调用。 
    public static    void  c(){
        
    }
    在JDK1.9时,接口又增加了: 
    // 私有方法
    private void A(){
        
    }
}

3.实现接口

//一个类通过implements关键字实现一个接口,那么它必须实现这个接口中的所有抽象方法

//一个实现了接口,并实现了接口中所有抽象方法的类叫做这个接口的实现类

//java语言单继承多实现,一个java类只能继承一个父类,但是可以实现多个接口,实现接口不影响继承。

接口的使用,它不能创建对象,但是可以被实现(implements ,类似于被继承)。

4.实现要求

  1. 如果接口的实现类是非抽象类,那么必须重写接口中所有抽象方法。
  2. 默认方法可以选择保留,也可以重写。 但是不需要default

5.接口多实现

【修饰符】 class 实现类 extends 父类 implements 接口1,接口2,接口3。。。{
    // 重写接口中所有抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
  	// 重写接口中默认方法【可选】
}

//接口中,有多个抽象方法时,实现类必须重写所有抽象方法。如果抽象方法有重名的,只需要重写一次。

6.默认方法冲突问题

1.当一个类,既继承一个父类,又实现若干个接口时,父类中的成员方法与接口中的抽象方法重名,可以不重写抽象方法 因为继承父类的方法默认重写了

子类就近选择执行父类的成员方法

2.当实现接口是 多个接口有相同的默认方法default 需要作出选择

接口名.super.方法名

或者直接重写

若父类中的成员变量与接口中的变量重名 要么写super.x,要么写A.x

7.接口能多继承

接口继承接口

子接口重写默认方法时,default关键字可以保留。 子接口:接口继承 多继承 不需要构造方法。 也可以不需要重写

子类重写默认方法时,default关键字不可以保留。

当使用

class 实现类 extends 父类 implements 接口1(){

}

接口名 n =new 实现类();

调用继承 父类方法

向下转型

实现类 b = (实现类)n;

若重写方法 则调用自己的方法

父类 s = (父类) n;

2.枚举类

1.定义 enum

枚举类型本质上也是一种类,只不过是这个类的对象是固定的几个,而不能随意让用户创建。

JDK1.5之前

  • 构造器加private私有化
  • 本类内部创建一组常量对象,并添加public static修饰符,对外暴露这些常量对象
  • 公开静态常量本来属性 public static final 类名 = new 类名;
  • 类体中提供若干公开静态常量的该类对象作为属性

JDK1.5之后

语法格式: enum声明的枚举类中,所有的构造方法默认是私有访问修饰,不能改成其他访问修饰

【修饰符】 enum 枚举类名{
    常量对象列表 
        //enum声明的枚举类中,必须在类体的第一部分声明该类型的常量属性
        //类在类体的第一部分声明常量属性,多个常量属性间需要用逗号分隔,最后一个常量属性后用分号结束
    MAN("春"),
    WOMEN("秋");
     其他成员列表;
    private final String description;
    
    private void 枚举类名(String description){
        this.description = description;
    }
}

枚举类的要求和特点:

  • 枚举类的常量对象列表必须在枚举类的首行,因为是常量,所以建议大写。
  • 如果常量对象列表后面没有其他代码,那么“;”可以省略,否则不可以省略“;”。
  • 编译器给枚举类默认提供的是private的无参构造,如果枚举类需要的是无参构造,就不需要声明,写常量对象列表时也不用加参数,
  • 如果枚举类需要的是有参构造,需要手动定义private的有参构造,调用有参构造的方法就是在常量对象名后面加(实参列表)就可以。
  • 枚举类默认继承的是java.lang.Enum类,因此不能再继承其他的类型。
  • JDK1.5之后switch,提供支持枚举类型,case后面可以写枚举常量名。
  • 枚举类型如有其它属性,建议(不是必须)这些属性也声明为final的,因为常量对象在逻辑意义上应该不可变。
  • 可以有普通方法 和静态方法

3.包装类

序号 基本数据类型 包装类(java.lang包)
1 byte Byte
2 short Short
3 int Integer
4 long Long
5 float Float
6 double Double
7 char Character
8 boolean Boolean
9 void Void

包装类数组 默认值为null

1.装箱和拆箱

基本数值---->包装对象

Integer i1 = new Integer(4);//使用构造函数函数
Integer i2 = Integer.valueOf(4);//使用包装类中的valueOf方法

包装对象---->基本数值

Integer i1 = new Integer(4);
int num1 = i1.intValue();

JDK1.5之后,可以自动装箱与拆箱。

自动向同类型的装箱 拆箱

常量池, 方法区 共用

包装类 缓存对象
Byte -128~127
Short -128~127
Integer -128~127
Long -128~127
Float 没有
Double 没有
Character 0~127
Boolean true和false

第九天 内部类 注解

1.定义

将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类

2.为什么要写内部类呢?

内部类因为在外部类的里面

当非静态内部类 调用 外部类私有方法或属性时,可以直接调用 若重名 使用 外部类名.this.属性或方法

当在同一类时 需要访问内部类普通方法 需要创建对象

2.静态内部类

【修饰符】 class 外部类{
    【其他修饰符】 static class 内部类{
    }
}

静态内部类的特点: 里面属性方法 都属于静态

只能访问 外部类的 静态属性,方法 不管啥修饰符修饰

方法体 跟外部类写法一样

可以声明静态成员 普通成员

可以继承父类 实现接口 且与外部类无关

也可以用 abstract修饰 可以被其他类继承

可以写final 修饰 表示不能被继承

与外部类不同的是

外部类 只能 使用default 或者 publc

自己可以 public,protected,缺省,private

在外部类外创建对象

外部类类名.内部类类名 x = new 外部类类名.内部类类名();

在外部类使用 类名调用 不需要创建对象

3.非静态内部类

跟普通类一样

不同的是

不能声明静态成员 可以继承父类静态成员 但是能声明静态常量

可以调用外部类所有成员。 直接调用

可以继承父类 实现接口 且与外部类无关

也可以用 abstract修饰 可以被其他类继承

可以写final 修饰 表示不能被继承

与外部类不同的是

外部类 只能 使用default 或者 publc

自己可以 public,protected,缺省,private

在外部类外创建对象

跟外部类声明 变量名一样 使用this表示自己的 用 外部类名.this

 //先创建外部类 对象   再用外部类对象 创建内部类
Outer out = new Outer();
Outer.Inner in= out.new Inner();
//一句之内创建外部类对象同时创索内部类对象
//Outer.Inner in= new Outer().new Inner();

外部类内创建对象

正常类创建

4.局部内部类

跟外部类一样

不同的是

​ **不能声明静态成员 可以继承父类静态成员 **

前面不能有权限修饰符

可以继承父类 实现接口 且与外部类无关

也可以用 abstract修饰 可以其他内部类继承

可以写final 修饰 表示不能被继承

访问成员取决于自己所在的方法

局部内部类只能在定义它的方法使用不能超过这个方法直接创建对象。

5.匿名内部类

假如有接口或 抽象类 但不想写类重新继承或实现

创建对象时 使用匿名内部类

interface A{
	void a();
}
public class Test{
    public static void main(String[] args){
    	new A(){
			@Override
			public void a() {
				System.out.println("aaaa");
			}
    	}.a();
    }
}
//当参数

interface A{
	void method();
}
public class Test{
    public static void test(A a){
    	a.method();
    }
    
    public static void main(String[] args){
    	test(new A(){

			@Override
			public void method() {
				System.out.println("aaaa");
			}
    		
    	});
    }   
}

6.注解

概念:由@开头的一组结构,作用是在程序中标注或者体现某种功能

1、@Override

​ 用于检测被修饰的方法为有效的重写方法,如果不是,则报编译错误!

​ 只能标记在方法上。

​ 它会被编译器程序读取。

2、@Deprecated

​ 用于表示被标记的数据已经过时,不建议使用。

​ 可以用于修饰 属性、方法、构造、类、包、局部变量、参数。

​ 它会被编译器程序读取。

3、@SuppressWarnings

​ 抑制编译警告。

​ 可以用于修饰类、属性、方法、构造、局部变量、参数

​ 它会被编译器程序读取。

7.JUnit单元测试

​ 回归测试框架

8.自定义注解

【@元注解】
【修饰符】 @interface 注解名{
    【配置参数列表;】    String value() default "atguigu";默认value   default赋初值、、
        
        若有多个参数
        需要  “value=}

1、元注解

JDK1.5在java.lang.annotation包定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。

(1)@Target:用于描述注解的使用范围

  • 可以通过枚举类型ElementType的10个常量对象来指定
  • TYPE,METHOD,CONSTRUCTOR,PACKAGE…

(2)@Retention:用于描述注解的生命周期

  • 可以通过枚举类型RetentionPolicy的3个常量对象来指定
  • SOURCE(源代码)、CLASS(字节码)、RUNTIME(运行时)
  • 唯有RUNTIME阶段才能被反射读取到。

(3)@Documented:表明这个注解应该被 javadoc工具记录。

(4)@Inherited:允许子类继承父类中的注解

第10天 异常处理

1.定义:

程序在执行过程中,出现的非正常的情况,如果不处理最终会导致JVM的非正常停止。

异常流程: java程序会在出现异常的那行代码中止,创建一个该类异常的对象并向上抛出。如果异常对象抛给虚拟机,则虚拟机会立即终止当前进程。如果没有抛给虚拟机,则程序会继续执行。

2.异常体系

Throwable体系:

  • Error:严重错误Error,无法通过处理的错误,只能事先避免,好比绝症。
    • 例如:*Error和OOM(OutOfMemoryError)。
  • Exception:表示异常,其它因编程错误或偶然的外在因素导致的一般性问题,程序员可以通过代码的方式纠正,使程序继续运行,是必须要处理的。好比感冒、阑尾炎。

方法 :

getMessage () 获取异常的原因

printStackTrace()`:打印异常的详细信息。

3.异常分类

RuntimeException及其子类被称作末检查异常:这类异常可以通过程序员对代码的优化避免,所以这类异常系统不强制必须处理。但是如果这类异常中也有异常对象的产生和抛出,那么还是会走异常处理流程。
非RuntimeException及其子类异常,被称作已检查异常:这类异常是没有办法通过程序优化来避免的异常,这类异常系统强制必须处理。

1 编译时异常 : checked异常。在编译时期,就会检查,如果没有处理异常,则编译失败。(如文件找不到异常)

2 运行是异常: runtime异常。在运行时期,检查异常.在编译时期,运行异常不会被编译器检测到(不报错)

  • 编译前可以不处理
  • 也就是
  • throw new runtime异常 可以不处理 不强制处理
throw new 异常类名(参数);    自己写抛出异常

使用throws 声明异常

如果方法内通过throw抛出了编译时异常,而没有捕获处理(稍后讲解该方式),那么必须通过throws进行声明,让调用者去处理。

修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2…{   }	
xxxxxxxxxx try{   
编写可能会出现异常的代码}catch(异常类型1  e){ 
处理异常的代码     
//记录日志/打印异常信息/继续抛出异常
}catch(异常类型2  e){
处理异常的代码     
//记录日志/打印异常信息/继续抛出异常}....

**try:**该代码块中编写可能产生异常的代码。

**catch:**用来进行某种异常的捕获,实现对捕获到的异常进行处理。

finally:有一些特定的代码无论异常是否发生,都需要执行。 包括return

可以 try catch 可以 try finally try必须出现 切不能单独

异常类如何定义:

  1. 自定义一个编译期异常: 自定义类 并继承于java.lang.Exception。 必须使用 throws声明异常 重写getMessage方法或者利用构造方法传递异常信息。
  2. 自定义一个运行时期的异常类:自定义类 并继承于java.lang.RuntimeException 可以不处理throws声明异常 重写getMessage方法或者利用构造方法传递异常信息。
// 业务逻辑异常
public class RegisterException extends Exception {
    /**
     * 空参构造
     */
    public RegisterException() {
    }

    /**
     *
     * @param message 表示异常提示
     */
    public RegisterException(String message) {
        super(message);
    }
    
    重写 getMessage方法  比构造方法优先级高!!
}

throw new RegisterException(“亲”+name+“已经被注册了!”); 使用有参构造

第11天 多线程

  • 并行(parallel):指两个或多个事件在同一时刻发生(同时发生)。指在同一时刻,有多条指令在多个处理器上同时执行。
  • 并发(concurrency):指两个或多个事件在同一个时间段内发生。指在同一个时刻只能有一条指令执行,但多个进程的指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。

1.定义

程序:为了完成某个任务和功能,选择一种编程语言写的指令的集合

软件:有一个或多个程序+相关的资源文件构成的软件。

进程:是指在内存中·运行的应用程序,每个进程都有独立的内存空间进程是软件的一次执行过程 ,从创建运行到消亡。

线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程至少一个线程,可以有多个线程,多线程。

进程是操作系统调度和分配资源的最小单位,线程是CPU调度的最小单位。不同的进程之间是不共享内存的。进程之间的数据交换和通信的成本是很高。不同的线程是共享同一个进程的内存的。当然不同的线程也有自己独立的内存空间。对于方法区,堆中中的同一个对象的内存,线程之间是可以共享的,但是栈的局部变量永远是独立的。。

2.线程调度

  • 分时调度

    所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

  • 抢占式调度

    优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

3.另行创建和启动线程

1.两种方法 和区别

继承Thread类

创建线程类继承自Thread类,可以继承到Thread类中的所有方法,重写run实现代码,调用start开始新的线程。

class MyThreadone extends Thread{//自定义线程类继承自Thread类
public void run{//重写run方法,将需要新开启的子线程执行的代码写在run方法中
//子线程代码
}
class Test{
public static void main(String[] args){
      MyThreadone mt=new MyThreadone();
    mto.start();// start方法开启新的子线程,调用run方法
 }
}

实现Runnable接口

创建线程类实现Runnable接口,实现run方法。除了run方法没有其他方法,需要创建Thread类对象才能有start开启新线程。

class MyThreadTwo implements Runnable{//自定义线程类实现Runnable接口,保留继承的机会
public void run(){//实现接口中的run方法,将子线程运行的代码写在run方法中
          }
}
class Test{
public static void main(string[] args){
MyThreadTwo mtt = new MyThreadTwo();//实现接口的方式,类中只有run方法,需要new Thread对象才能调用start方法,将自定义线程类对象作为参数传递给Thread对象。
//Thread thread = new Thread(mtt,"线程名");
Thread thread = new Thread(mtt);// new Thread对象时,放入的参数对象的run方法被start调用thread.start()
   }
}

匿名对象

new Thread("新的线程!"){
	@Override
	public void run() {
	}
}.start();
new Thread(new Runnable(){
			@Override
			public void run() {
			}
		},"新的线程").start();

4.常用方法

run()// 开启新线程需要完成代码写在run方法中start()// 开启新的子线程,并且调用run方法
**static**  s1eep(int time)//使当前线程休眠,参数是休眠的毫秒数,休眠时间到期,线程唤醒
getName(// 获取当前线程名称
setName (string name)//设置当前线程名称
setPriority(int i)// 设置当前线程优先级,优先级从低到高分别是1-10,默认优先级是5
getPriority()// 获取当前线程优先级
isAlive()//检查当前线程是否开启
join()// 谁调用 就是让它线程执行完毕
 public static void yie1d()//让当前线程退出cpu,使cpu重新选择。(让步)
public static Thread currentThread() //返回对当前正在执行的线程对象的引用。  Thread.currentThread();
 interrupt()  //中断

5.线程安全

产生线程不安全的原因:首先多线程同时运行,其次多个线程访问同一个共享资源。解决线程安全问题的方式:
1、同步代码块

public void run(){
//被同步代码块扩中的代码,必须有锁对象才能进入同步代码块执行,没有锁对象的线程不能执行。
//锁对象必须保证内存中唯一,如果锁对象不唯一,则无法完成同步代码块的功能。
synchronized(锁对象){//同步代码块锁对象
//同步代码块代码
    //里面操作的是锁对象
   // 即只能调用锁对象的方法。wait  等等
 } 
}

//Object类
//字符串
//this
//当前类的类类对象:MyThreadone.class

2、同步方法同步方法中的所有代码在同一个同步代码块中。

class  MyThreadone{
//由synchronzed关键字修饰的方法被称作同步方法,普通同步方法的锁对象是: this
   public synchronized void methodone(){
}
    
//静态同步方法的锁对象是当前类的类类对象:MyThreadone.class
MyThreadone.classpublic static synchronized void methodTwo(){
   }
}

6.线程通信

wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了

  1. notify:则选取所通知对象的 wait set 中的一个线程释放;
  2. 唤醒正在此锁对象的监视器上等待的单个线程。如果有任何线程在该对象上等待,则选择其中一个唤醒。该选择是任意的,并且在实现时自行决定。
  3. notifyAll:则释放所通知对象的 wait set 上的全部线程。

调用wait和notify方法需要注意的细节

  1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
  2. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
  3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。

7 生产者与消费者问题

懒汉式和饿汉式单例模式

1.区分饿汉式和懒汉式
饿汉式:
坏处:对象加载时间过长。好处:饿汉式是线程安全的
懒汉式:好处:延迟对象的创建。
目前的写法坏处:线程不安全。

      //1.私有化类的构造器private Bank(){}
      //2.内部创建类的对象
      //3.提供公共的静态的方法,返回类的对象
      //4.要求此对象也必须声明为静态的

//饿汉式设计模式

class Bank{

    private Bank(){

    }

  private static Bank instance = new Bank();

  public  static Bank getInstance(){
      return instance;
  }
}


//1.私有化类的构造器
//2.声明当前类对象,没有初始化
//4.此对象也必须声明为static的
//3.声明public. static的返回当前类对象的方法
//懒汉式单例模式

class Order{

   private  Order(){

   }
    private static Order instance = null;
   public static Order getInstance(){
       //判断对象是否创建 若有就不在创建
       synchronized (Order.class) {   //需要线程安全  如果多线程调用会出现
           if (instance == null) {
               instance = new Order();
           }
       }
       return instance;
   }
}

一个厨师一个服务员问题

使用if 判断条件 是否进入 wait队列

多个厨师多个服务员问题

while (true) {

}

//商店提供消费和生产功能
public class Stone {

    private int items = 5;

    // 生产商品
    public void add(){
        synchronized (this) {
            while(items >= 20) {
                System.out.println("商品数量已达上限,生产线程暂停,让消费线程消费");
                try {
                    this.wait();// 当前线程处于等待状态,一个线程处于wait状态后,需要其他线程唤醒。调用wait和notify的应该是锁对象
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("生产线程正在生产,现在的产品数量是:" + (++items) + "~~~~~~~~~~~~~~~~~~~~~~~~");
            this.notifyAll();// 唤醒所有处于wait的线程
        }
    }

    // 消费商品
    public void get(){
        synchronized (this) {
            while (items <= 0) {
                System.out.println("商品数量已达到下线,消费线程暂停,让生产线程生产");
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("消费线程正在消费,现在的产品数量是:" + (--items) + "~~~~~~~~~~~~~~~~~~~~~~~~");
            this.notifyAll();
        }
    }


}


//生产者调用生产功能生产
public class Producter extends Thread {
    private Stone stone;

    public Producter(Stone stone,String name){
        super(name);
        this.stone = stone;
    }
    @Override
    public void run() {
       while(true){
           stone.add();
       }
    }
}
//顾客使用消费功能消费
public class Coustmer extends Thread {
    private Stone stone;

    public Coustmer(Stone stone,String name){
        super(name);//给线程命名
        this.stone = stone;
    }

    @Override
    public void run() {
       while(true){
           stone.get();
       }
    }
}

使用while循环 每次判断条件

8.线程生命周期

简单来说,线程的生命周期有五种状态:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(Dead)。CPU需要在多条线程之间切换,于是线程状态会多次在运行、阻塞、就绪之间切换。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-thbcQUhs-1616546841659)(E:\QQPCmgr\笔记\线程生命周期.png)]

9.死锁

两个或两个以上线程 分别锁住对方需要的锁对象 不释放,都在等待对方放弃锁对象时 形成的线程的死锁。

不同的线程分别锁住对方需要的同步监视器对象不释放,都在等待对方先放弃时就形成了线程的死锁。一旦出现死锁,整个程序既不会发生异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。

10、面试题:sleep()和wait()方法的区别

(1)sleep()不释放锁,wait()释放锁

(2)sleep()指定休眠的时间,wait()可以指定时间也可以无限等待直到notify或notifyAll

(3)sleep()在Thread类中声明的静态方法,wait方法在Object类中声明

因为我们调用wait()方法是由锁对象调用,而锁对象的类型是任意类型的对象。那么希望任意类型的对象都要有的方法,只能声明在Object类中。

11、synchronized和lock用途区别

synchronized原语和ReentrantLock在一般情况下没有什么区别,但是在非常复杂的同步应用中,请考虑使用ReentrantLock,特别是遇到下面2种需求的时候。

1.某个线程在等待一个锁的控制权的这段时间需要中断
2.需要分开处理一些wait-notify,ReentrantLock里面的Condition应用,能够控制notify哪个线程
3.具有公平锁功能,每个到来的线程都将排队等候

第12天基础API与常见算法

1.和数学相关的常见类

1.java.lang.Math

  • public static double abs(double a) :返回 double 值的绝对值。

  • public static double ceil(double a) :返回大于等于参数的最小的整数。

  • public static double floor(double a) :返回小于等于参数最大的整数。

  • public static long round(double a) :返回最接近参数的 long。(相当于四舍五入方法)

  • public static double pow(double a,double b):返回a的b幂次方法
  • public static double sqrt(double a):返回a的平方根
  • public static double random():返回[0,1)的随机值
  • public static final double PI:返回圆周率
  • public static double max(double x, double y):返回x,y中的最大值
  • public static double min(double x, double y):返回x,y中的最小值

2.BigInteger 整数长度太多 相加

BigDecimal 浮点数相加

.3 java.util.Random

用于产生随机数(均匀分布 )

  • boolean nextBoolean():返回下一个伪随机数,它是取自此随机数生成器序列的均匀分布的 boolean 值。
  • void nextBytes(byte[] bytes):生成随机字节并将其置于用户提供的 byte 数组中。
  • double nextDouble():返回下一个伪随机数,它是取自此随机数生成器序列的、在 0.0 和 1.0 之间均匀分布的 double 值。
  • float nextFloat():返回下一个伪随机数,它是取自此随机数生成器序列的、在 0.0 和 1.0 之间均匀分布的 float 值。
  • double nextGaussian():返回下一个伪随机数,它是取自此随机数生成器序列的、呈高斯(“正态”)分布的 double 值,其平均值是 0.0,标准差是 1.0。
  • int nextInt():返回下一个伪随机数,它是此随机数生成器的序列中均匀分布的 int 值。
  • int nextInt(int n):返回一个伪随机数,它是取自此随机数生成器序列的、在 0(包括)和指定值(不包括)之间均匀分布的 int 值。
  • long nextLong():返回下一个伪随机数,它是取自此随机数生成器序列的均匀分布的 long 值。

两种形成随机数组下标

int index = random.nextInt(nums.length);

int index = (int)(Math.random() * nums.length);

2.时间API

JDK1.8之前

1 . Date d = new Date();

​ new Date(long 毫秒):把该毫秒值换算成日期时间对象

​ long time = d.getTime(); 返回该日期时间对象距离1970-1-1 0.0.0 0毫秒之间的毫秒值

2 .TimeZone 获取时区

TimeZone t = TimeZone.getTimeZone(“America/Los_Angeles”);

3.calendar 抽象类

​ Calendar calendar = Calendar.getInstance():获取时间

​ Date date = c.getTime(); 获取Date类型

​ int year = c.get(Calendar.YEAR); 中间放枚举

​ calendar.add(Calendar.YEAR,100); 枚举为类型 即 可以知道100年后的时间。

SimpleDateFormat s = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);

​ String H = s.format(new Date()); 只能转换Date格式

// jdk1.8之后
LocalDate localDate = LocalDate.now();
int year = localDate.getYear();
int month = localDate.getMonth().getValue();
int day = localDate.getDayOfMonth();

LocalTime localTime = LocalTime.now();
int hour = localTime.getHour();
int mins = localTime.getMinute();
int second = localTime.getSecond();
int naon = localTime.getNano();

LocalDateTime localDateTime = LocalDateTime.now();

DateTimeFormatter:日期时间格式化

该类提供了三种格式化方法:

预定义的标准格式。如:ISO_DATE_TIME;ISO_DATE

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

自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)

1.DateTimeFormatter df = DateTimeFormatter.ISO_DATE_TIME;

2.DateTimeFormatter df = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);

3.DateTimeFormatter df = DateTimeFormatter.ofPattern(“yyyy年MM月dd日 HH时mm分ss秒 SSS毫秒 E 是这一年的D天”);

df.format(要转的格式);

持续日期/时间:Period和Duration

public static void main(String[] args) {
		LocalDate t1 = LocalDate.now();
		LocalDate t2 = LocalDate.of(2018, 12, 31);
		Period between = Period.between(t1, t2);
		System.out.println(between);
		
		System.out.println("相差的年数:"+between.getYears());//1年
		System.out.println("相差的月数:"+between.getMonths());//又7个月
		System.out.println("相差的天数:"+between.getDays());//零25天
		System.out.println("相差的总数:"+between.toTotalMonths());//总共19个月
	}

3.系统类

  • static long currentTimeMillis() :返回当前系统时间距离1970-1-1 0:0:0的毫秒值

  • static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length):

    从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。常用于数组的插入和删除

  • static void exit(int status) :退出当前系统

  • static void gc() :运行垃圾回收器。

  • static String getProperty(String key):获取某个系统属性

4.数组的算法升华

1.数组的反转
int[] arr = {1,2,3,4,5,6,7,8,9};
        for (int i = 0; i < arr.length/2; i++) {
            int t = arr[i];
            arr[i] = arr[arr.length-1-i];
            arr[arr.length-1-i] = t;
        }
      //  for(int lift =0,right = arr.length-1;lift<right;lift++,right-- ){
      //     int t = arr[lift];
      //     arr[lift] = arr[right];
      //     arr[right] = t;
      // }

        for (int i : arr) {
            System.out.println(i);
        }
    }
2.数组的扩容
  Integer[] arr1 = new Integer[arr.length*2];
        for (int i = 0; i < arr.length; i++) {//复制数组
             arr1[i] = arr[i];
        }
        //加入新的值
        for (int i = 0; i < arr1.length; i++) {
            if(arr1[i] ==null){
                arr1[i] = 10;
                break;
            }
        }
3.数组元素的插入
int  a= 3;//在第三个插入
int  b= 10; //插入值
for (int i = arr.length-1; i >= a-1; i--) {
    if(arr[i] == 0){
        continue;
    }
    arr[i+1] = arr[i];
}
 arr[a-1] = b;
//若数组满的  则需要先扩容。
移动2个元素,需要移动的起始元素下标是[1],它需要移动到[2],一共移动了几个
//System.arraycopy(arr,1,arr,2, 6);
4.数组元素的删除
for (int i = index-1;i<nums.length-1;i++){
        if (nums[i + 1] != 0) {
            nums[i] = nums[i + 1];
        } else {
            nums[i] = 0;
            break;                       //从删除那个往前移动,到元素为0停止,且把当前num【i】的值赋0(最后一个值)
        }
        if(i+1==nums.length-1){          //若数组满的   直接最后一个元素赋值为0
            nums[i + 1] = 0;
        }
}


移动2个元素,需要移动的起始元素下标是[1],它需要移动到[2],一共移动了几个
//System.arraycopy(arr,2,arr,1, 6);
    //(2)因为数组元素整体往左移动,这里本质上是复制,原来最后一个元素需要置空
   arr[2]=null;//使得垃圾回收尽快回收对应对象的内存

5.数组的二分查找

int[] arr = {2,5,7,8,10,15,18,20,22,25,28};
int value = 18;
int index = -1;
int left = 0;
int right = arr.length - 1;
int mid = (left + right)/2;
while(left<=right){
    //找到结束
    if(value == arr[mid]){
        index = mid;
        break;
    }//没找到
    else if(value > arr[mid]){//往右继续查找
        //移动左边界,使得mid往右移动
        left = mid + 1;
    }else if(value < arr[mid]){//往左边继续查找
        right = mid - 1;
    }

    mid = (left + right)/2;
}

if(index==-1){
    System.out.println(value + "不存在");
}else{
    System.out.println(value + "的下标是" + index);
}
6.数组的直接选择排序

)找出本轮未排序元素中的最值

与未排序的第一个值交换

第13天String

字符串特点:

1。本身是final修饰的 不能被继承 也是不可变对象

2.每次修改意味着新对象

3.String对象内部是用字符数组进行保存的

  • 方法的参数:引用数据类型传递的是对象地址
    • 但是字符串对象不可变

//JDK1.9之前有一个char[] value数组,JDK1.9之后byte[]数组

String s1 = “abc”;
String s2 = “abc”; //在方法区里 有字符串常量池

String s3 = new String(s1); //在堆里

String s4 = “abcabc”;

String s5 = s3+s1; 堆加方法区 默认到堆里

System.out.println(s1 == s2); true

System.out.println(s1 == s3); false

System.out.println(s4 == s5); false

(1)常量+常量:结果是常量池

(2)常量与变量 或 变量与变量:结果是堆

(3)拼接后调用intern方法:结果在常量池

s1 = s1.concat(s2);// 拼接字符串
//**concat方法拼接,哪怕是两个常量对象拼接,结果也是在堆。**

String s4 = (s1 + "world").intern();//把拼接的结果放到常量池中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ElfAyasM-1616546841670)(E:\QQPCmgr\笔记\字符串池.png)]

若消耗较高,使用stringbuffer类 默认长度16

StringBuffer:老的,线程安全的(因为它的方法有synchronize d修饰)

StringBuilder:线程不安全的

(1)StringBuffer append(xx):拼接,追加

(2)StringBuffer insert(int index, xx):在[index]位置插入xx

(3)StringBuffer delete(int start, int end):删除[start,end)之间字符

StringBuffer deleteCharAt(int index):删除[index]位置字符

(4)void setCharAt(int index, xx):替换[index]位置字符

(5)StringBuffer reverse():反转

(6)void setLength(int newLength) :设置当前字符序列长度为newLength

(7)StringBuffer replace(int start, int end, String str):替换[start,end)范围的字符序列为str

(8)int indexOf(String str):在当前字符序列中查询str的第一次出现下标

​ int indexOf(String str, int fromIndex):在当前字符序列[fromIndex,最后]中查询str的第一次出现下标

​ int lastIndexOf(String str):在当前字符序列中查询str的最后一次出现下标

​ int lastIndexOf(String str, int fromIndex):在当前字符序列[fromIndex,最后]中查询str的最后一次出现下标

(9)String substring(int start):截取当前字符序列[start,最后]

(10)String substring(int start, int end):截取当前字符序列[start,end)

(11)String toString():返回此序列中数据的字符串表示形式

常用方法

1)boolean isEmpty():字符串是否为空

(2)int length():返回字符串的长度

(3)String concat(xx):拼接,等价于+

(4)boolean equals(Object obj):比较字符串是否相等,区分大小写

(5)boolean equalsIgnoreCase(Object obj):比较字符串是否相等,区分大小写

(6)int compareTo(String other):比较字符串大小,区分大小写,按照Unicode编码值比较大小

(7)int compareToIgnoreCase(String other):比较字符串大小,不区分大小写

(8)String toLowerCase():将字符串中大写字母转为小写

(9)String toUpperCase():将字符串中小写字母转为大写

(10)String trim():去掉字符串前后空白符

(11)boolean contains(xx):是否包含xx

(12)int indexOf(xx):从前往后找当前字符串中xx,即如果有返回第一次出现的下标,要是没有返回-1

(13)int lastIndexOf(xx):从后往前找当前字符串中xx,即如果有返回最后一次出现的下标,要是没有返回-1

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

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

28)boolean matchs(正则表达式):判断当前字符串是否匹配某个正则表达式

Greedy 数量词

X?X,一次或一次也没有

X*X,零次或多次

X+X,一次或多次

X{n}X,恰好 n

X{n,}X,至少 n

X{n,m}X,至少 n 次,但是不超过 m

(29)String replace(xx,xx):不支持正则

(30)String replaceFirst(正则,value):替换第一个匹配部分

(31)String repalceAll(正则, value):替换所有匹配部分

(32)String[] split(正则):按照某种规则进行拆分数组

(16)char charAt(index):返回[index]位置的字符

(17)char[] toCharArray(): 将此字符串转换为一个新的字符数组返回

getBytes  返回byte【】  中文  3个字节

(18)String(char[] value):返回指定数组中表示该字符序列的 String。

(19)String(char[] value, int offset, int count):返回指定数组中表示该字符序列的 String。

String s = new String();构造函数

(20)static String copyValueOf(char[] data): 返回指定数组中表示该字符序列的 String

(21)static String copyValueOf(char[] data, int offset, int count):返回指定数组中表示该字符序列的 String

(22)static String valueOf(char[] data, int offset, int count) : 返回指定数组中表示该字符序列的 String

(23)static String valueOf(char[] data) :返回指定数组中表示该字符序列的 String

public static void main(String[] args) {
	char[] data = {'h','e','l','l','o','j','a','v','a'};
	String s1 = String.copyValueOf(data); //数组变字符串
	String s2 = String.copyValueOf(data,0,5);
	int num = 123456;
	String s3 = String.valueOf(num);
	System.out.println(s1);
	System.out.println(s2);
	System.out.println(s3);
}

泛型

1.5引入

​ 既能保证安全,又能简化代码。

1.参数类型:泛型类与泛型接口

【修饰符】 class 类名<类型变量列表>{
    
}
【修饰符】 interface 接口名<类型变量列表>{
    
}
  • <类型变量列表>:可以是一个或多个类型变量,一般都是使用单个的大写字母表示。例如:、<K,V>等。
  • <类型变量列表>中的类型变量不能用于静态成员上。
public class Student<T>{
	private String name;
	private T score;
	}
2.继承父类泛型
// 如果子类继承了一个泛型的父类,并没有在继承时指定类型,那么将默认使用Object
// public class Teacher extends Person {
// 子类在继承父类时,直接指定继承父类的泛型的具体类型,那么子类将不需要在实现泛型
// public class Teacher extends Person<Integer> {

// 子类继承父类泛型,子类也是泛型的类,那么必须在创建子类对象时给出泛型具体类型,否则使用Object
public class Teacher<T> extends Person<T> {
}
3.实现接口
// 如果一个实现类实现接口是没有指定接口的泛型的具体类型则,使用Object
// public class InterOneImpl implements InterOne {

// 实现类实现接口时直接指定接口泛型的具体类型
// public class InterOneImpl implements InterOne<Integer> {

    // 实现类继承接口的泛型,实现类也是泛型的类,创建实现类对象时指定具体的泛型类型,如果不指定则使用Object
    public class InterOneImpl<T> implements InterOne<T> {}

4.使用泛型

Student<String> stu1 = new Student<String>("张三", "良好");

public class Student<T>{
	private String name;
	private T score;
	}


 // 虽然集合泛型了,但是迭代器也需要泛型,否则迭代器读取的元素还是Object
        Iterator<String> iter = list.iterator();

        while(iter.hasNext()){
            String s1 = iter.next();
            System.out.println(s1);
        }

        for (String s1 : list) {
            System.out.println(s1);
        }
4.泛型方法
public static <T> void method( T[] arr,int a,int b){}



4.<?>任意类型(当参数时)
//任意类型
Student<?>[] arr = new Student[3];

//上限  即父类  父类父类
Student<? extends Comparable<>>  arr = new Student[3];
 <? super 下限>

		Student<? super Number> stu3 = new Student<>();
		stu3.setScore(56);//可以设置Number或其子类的对象



第14天集合

集合是java中提供的一种容器,可以用来存储多个数据。

集合和数组既然都是容器,它们有啥区别呢?

  • 数组的长度是固定的。集合的长度是可变的。
  • 数组中可以存储基本数据类型值,也可以存储对象,
  • 而集合中只能存储对象
  • 没有使用泛型时。
  • 若添加基础数据类型 则自动装箱为包装类 int Integer对象
  • 都是object对象,自动向上转型

集合主要分为两大系列:Collection和Map,Collection 表示一组对象,Map表示一组映射关系或键值对。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IExgIu98-1616546841672)(E:\QQPCmgr\笔记\集合.png)]

1.collection

Collection是所有单列集合的父接口,因此在Collection中定义了单列集合(List和Set)通用的一些方法

Collection cl = new ArrayList();
Collection co = new ArrayList();
  cl.add(1);//添加元素对象到当前集合中
  cl.add(2);
  co.add(1);
  co.add(2);
  //cl.addAll(co);//添加co集合中的所有元素对象到当前集合中,即this = this ∪ other
  //cl.remove(1);//从当前集合中删除第一个找到的与obj对象equals返回true的元素。
  //cl.removeAll(co);//从当前集合中删除所有与coll集合中相同的元素。即this = this - this ∩ coll
  //col.clear();//将集合中所有元素清空
 boolean b1 = cl.isEmpty();//判断当前集合是否为空集合。
 boolean b2 = cl.equals(co);//判断两个集合是否相等
 boolean b3 = cl.containsAll(co);//判断两个集合是否相等
 boolean b4 = cl.contains(1);//判断当前集合中是否存在一个与obj对象equals返回true的元素。
System.out.println(co.size());//返回当前集合中实际存储的元素个数
 co.retainAll(cl);//取交集,即  取两个集合的相同元素赋值给co  若无,则为空集合
co.toArray();//返回包含当前集合中所有元素的数组

2.迭代器iterator

// 获取迭代器对象
Iterator<String> iter = col.iterator();
// 哪个集合调用iterator方法,获取的迭代器对象就用于遍历那个集合

while (iterator.hasNext()){  //自动检查下一个元素,若元素存在,则返回true
       String o= iterator.next();//返回迭代的下一个元素
         System.out.println(s);
       }
        //增强for循环
       for(String o:cl){
            System.out.println(s);
     }


3.使用Iterator迭代器删除元素

java.util.Iterator迭代器中有一个方法:

​ void remove() ;

那么,既然Collection已经有remove(xx)方法了,为什么Iterator迭代器还要提供删除方法呢?

因为Collection的remove方法,无法根据条件删除。

所以建议在迭代器中,只进行遍历

coll.remove(?)//无法编写  会报错   

不要在遍历的过程中对集合元素进行增加、删除、替换操作

增强for循环本质上就是使用Iterator迭代器进行遍历的。

注意:不要在使用Iterator迭代器进行迭代时,调用Collection的remove(xx)方法,否则会报异常

在迭代器中使用过一次next()方法后 不能在使用一次 不能频繁使用

4.List

list接口继承collection接口

List除了从Collection集合继承的方法外,List 集合里添加了一些根据索引来操作集合元素的方法。

List集合类中元素有序、且可重复

有序(新增顺序和遍历顺序一样)即有集合索引

ArrayList:动态数组 线程不安全

Vector:动态数组 线程安全

LinkedList:双向链表

Stack:栈

1.ArrayList

遍历查询的效率高,频繁增删的效率低。

//ArrayList底层使用可变长度数组实现
//ArrayList从jdA1.8开始构造方法将不会为保存元素的数组初始化长度
List list = new ArrayList();
// ArrayList从jdk1.8开始的第一次调用添加方法,将初始化长度为10
//ArrayList底层使用可变长度的Object数组,当数组元素已满时,数组需要扩容。
//扩容按照数组原长度的1.5倍扩容

Arrays类的静态方法

1.void Array.sort(Object[] array) 从小到大排序

2…Arrays.fill(Object[] array,Object object) 替换数组里的值 为一样的值

Arrays.fill(Object[] array,int from,int to,Object object) 替换指定索引的值 为一样的值

3.Arrays.toString(Object[] array) 数组的字符串形式

4.数组比较 (Arrays.equals(deepArray1, deepArray2)两个数组比较

5.数组复制 arrays.copyOf()

6.二分查找返回下标 Arrays.binarySearch(array, 10)

Arrays.asList()返回的是arrays中私有的终极ArrayList类型,它有set,get,contains方法,但没有增加和删除元素的方法,所以大小固定,会报错

 

List<String> list = new ArrayList();
 List<String> listOne = new ArrayList();
//第一个参数的索引位置不能大于当前list的size()
//在执行添加后,size()会加1,插入位置原元素及以后元素,向后移动一位
// 使用指定索引插入数据的方式,不允许插队索引大于当前集合的长度
list.add(4,"小明");
list.addAll(2,listOne);
// get方法使用参数索引获取该索引位置的集合元素
    String s = list.get(1);
    System.out.println(s);
  for(int i = 0;i < list.size(); i++){
      String o = list.get(i);
      System.out.println(o);
  }
// 返回参数元素在集合中第一次出现的索引位置,如果集合中不包含该元素则返回-1
 int index = list.indexOf("liudehua");
 // 按照参数对象在集合中删除第一次出现的元素
   list.remove("Mary");
   System.out.println(list);
 // 移除参数下标位置的元素
 //这个方法用于有比以元素为参数移除的方法更高的优先级
  list.remove(1);//删除指定位置元素  返回被删除元素
list.remove(new  integer(1))//  删除基础数据类型时(new基础数据类型)
    
返回替换的元素 list.set(2,"黑猫警长");// 用第二个参数元素,替换第一个参数位置的元素,size不变
List subList = list.subList(2,5);读取下标[2,5)元素
                                      

                                      
                                      
// 跟size() 方法一起用  来 遍历的 
    	for(int i = 0;i<list.size();i++){
    		System.out.println(list.get(i));
    	}
    	//还可以使用增强for
    	for (String string : list) {
			System.out.println(string);
		}    
                                      

2.LinkedList

底层实现使用 双向链表

遍历查询的效率低,频繁增删的效率高。

//双向链表的特点决定了LinekedList频繁存取的效率高,遍历查询的效率低。

//数组的特点决定了ArrayList频繁存取的效率低,遍历查询的效率高。
//有一套独有的方法
LinkedList   linkedList = new LinkedList();
linkedList.addFirst(1);//设置第一个元素
linkedList.addLast(5);//设置最后一个元素

3. ListIterator

List 集合额外提供了一个 listIterator() 方法,该方法返回一个 ListIterator 对象, ListIterator 接口继承了 Iterator 接口,提供了专门操作 List 的方法:

  • void add():通过迭代器添加元素到对应集合
  • void set(Object obj):通过迭代器替换正迭代的元素
  • void remove():通过迭代器删除刚迭代的元素
  • boolean hasPrevious():如果以逆向遍历列表,往前是否还有元素。
  • Object previous():返回列表中的前一个元素。
  • int previousIndex():返回列表中的前一个元素的索引
  • boolean hasNext()
  • Object next()
  • int nextIndex()

5.set

特点(没有独有的方法)

无序(新增顺序和遍历顺序不一致),不允许元素重复

Set接口是Collection的子接口,set接口没有提供额外的方法。但是比Collection接口更加严格了。

1.HashSet

Set 典型实现类

构造方法new hashMap

java.util.HashSet底层的实现其实是一个java.util.HashMap支持,然后HashMap的底层物理实现是一个Hash表。(什么是哈希表,下一节在HashMap小节在细讲,这里先不展开)长度16

自定义实体类在默认情况下只能去掉内存地址相同的重复记录

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bR9qz1mE-1616546841675)(E:\QQPCmgr\笔记\hashMap.png)]

HashSet去重时,首先调用hashCode方法,在hashCode方法判定两个对象不相等时,直接将新元素存储到集合中
当hashCode方法判定两个对象相等时,调用equals方法判定两个对象是否相等,equals方法返回true则判定两个对象相等,当equals方法返回false,则判断两个对象不相等,将元素添加进集合

所以实体类需要重写hashcode 和equals方法

2.linkedHashSet

特点:新增顺序和遍历顺序一致

linkedHashSet 是HashSet子类

在HashSet的基础上,在结点中增加两个属性before和after维护了结点的前后添加顺序。

3.TreeSet

顶层接口set collection

底层结构:里面维护了一个TreeMap,都是基于红黑树实现的!

特点:
1、不允许重复
2、实现排序(排序的时候去重)
自然排序或定制排序

//String它实现了java.lang.Comparable接口 一个一个字母比较

方式一:自然排序
让待添加的元素类型实现Comparable接口,并重写compareTo方法

方式二:定制排序
创建Set对象时,指定Comparator比较器接口,并实现compare方法

自然排序:实体类实现comparable接口 重写 compareTo方法

// 一个类的对象向比较大小需要实现Comparable接口
public class Student implements Comparable{

 // 调用方法的对象和参数对象比较大小
 // 如果返回值为0,则代表两个对象相等,返回负数代表调用方法的对象小,参数对象大
    // 返回正数则代表调用方法的对象大参数对象小
    @Override
    public int compareTo(Object o) {
        int result = 0;
        Student stu = (Student)o;

        result =  stu.getStuAge() -this.getStuAge();
        if(result == 0){
            result = stu.getStuName().compareTo(this.getStuName());
        }
        if(result == 0){
            result = this.getStuGender().compareTo(stu.getStuGender());
        }
        return result;
    }
}

2.定制排序

创建set对象时 使用匿名内部类实现 comparator 重写compare方法

典型 数组对象 无法实现comparable类 使用定制排序

public class Test{

    public static void main(String[] args) {
        int[] str={1,3,4,5};
        int[] str1={3,3,4,5};
        int[] str2={4,3,4,5};
        int[] str3={5,3,4,5};
        Set set = new TreeSet(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                int[] a= (int[])o1;
                int[] b= (int[])o2;
                int result = 0;
                for (int i = 0; i < a.length; i++) {
                    result = a[i] - b[i];
                    if(result!=0) {
                        break;
                    }
                }
                return result;
            }
        });
        set.add(str);
        set.add(str1);
        set.add(str2);
        set.add(str3);
        System.out.println(set.size());
        for (Object o : set) {
            int[] a = (int[])o;
            for (int i = 0; i < a.length; i++) {
                System.out.println(a[i]);
            }
        }
    }
}

6.Map

特点 (这HashMap类大致相当于Hashtable,除了它是不同步的,允许空值。)

// HashMap在创建对象时不会为哈希表初始化长度,仅对加载因子进行赋值,默认值为0.75
// HashMap底层存储数据的哈希表初始化容量为16,在当前元素数超过数组长度*负载因此时扩容,按照原长度两个库容
// 哈希表一旦扩容则将所有原元素重新打散。
Map map = new HashMap();

// 当元素放入HashMap集合中时,会调用键值对当中的键,按照键的hashCode方法来确定当前键值对在哈希表中的索引。
// 如果该索引处无元素,则将键值对象存储在该索引处,如果该索引处有元素则调用equals方法判断新键和老键是否相等
// 如果equals返回true,则不执行添加动作,如果equals方法返回false,则将新键值对链接到老键值对的next属性。
// 从jdk1.8开始,如果一个链表的元素达到8个则判断当前哈希表是否长度超过64,如果没超过64,则扩容,重新打散数据。
// 如果数组此时已经超过64,则将链表结构变换称为红黑树结构,jdk1.8之前哈希表为数组+链表,jdk1.8之后哈希表为数组+链表+红黑树
map.put("","");

对HashMap实例有影响其性能的两个参数:初始容量(16)和负载因子(0.75L 浮点数)。

1.常用方法

Map<T,T> map = new HashMap<>();

map.put("班长","美国");// 将元素存储到Map集合中,两个参数,前键后值。
map.put("班长","联合国");// 当向Map集合中添加键相同的元素时,新的值会替换老的值
String value = map.get("班长");// 使用键作为参数获取值
map.remove("班长");// 使用键作为参数,移除整个键值对
map.isEmpty();// 判断当前集合是否为空
 System.out.println(map.containsKey("学委"));// 检索当前集合中是否存在参数键
        System.out.println(map.containsValue("中国"));// 检索当前集合中是否存在参数值
 Set<String> keys = map.keySet();// 返回当前Map集合中所有的键集
Collection<String> keys = map.values();// 返回当前Map集合中所有的value
//也就是  key集
Iterator iter = keys.iterator();
        while(iter.hasNext()){
            String key = (String)iter.next();
            String val = (String)map.get(key);
            System.out.println(key+"="+val);
        }
Set<Map.entry<string,string>> entrySets = map.entrySet();// 获取当前Map集合中的键值对对象集
//有两个 Map.Entry   getKey  getValue方法
 for (Map.entry<string,string> o : entrySets) {
            String key = entry.getKey();
            String val = entry.getValue();
            System.out.println(key+"="+val);
        }
2.遍历

Map的遍历,不能支持foreach,因为Map接口没有继承java.lang.Iterable接口,也没有实现Iterator iterator()方法

只能获取

(1)分开遍历:

  • 单独遍历所有key 利用 get(key)
  • 单独遍历所有value

或者获取:

所有的键值对 entrySet()获取

转换 Map.Entry类型 //有两个 Map.Entry getKey getValue方法

  • 遍历的是映射关系Map.Entry类型的对象,Map.Entry是Map接口的内部接口。每一种Map内部有自己的Map.Entry的实现类。在Map中存储数据,实际上是将Key---->value的数据存储在Map.Entry接口的实例中,再在Map集合中插入Map.Entry的实例化对象
System.out.println("所有的映射关系");
		Set<Map.Entry<String,String>> entrySet = map.entrySet();
		for (Map.Entry<String,String> entry : entrySet) {
//			System.out.println(entry);
			System.out.println(entry.getKey()+"->"+entry.getValue());
		}
3.实现类

HashMap底层使用链表数组保存数据,在构造方法创建HashMap对象时,为加载因子赋值0.75,在第一次调用put
方法存放元素时初始化数组长度为16,在当前集合中键值对数量超过加载因子乘以当前数组长度时,扩容,按照原长度
两倍扩容,扩容后所有元素重新分散。当存放键值对时,使用键的hashCode计算该键值对在数组中索引,如果该索引处
无元素,则直接存储。如果有元素则将新元素链接到老元素的next属性上,当一个链表的元素数达到8个时,并且此时数组长度
超过64,则链表变红黑树,当前数组长度不足64位时,数组扩容。

HashMap 和 HashTable
  • HashMap和Hashtable都是哈希表。

  • HashMap和Hashtable判断两个 key 相等的标准是:两个 key 的hashCode 值相等,并且 equals() 方法也返回 true。因此,为了成功地在哈希表中存储和获取对象,用作键的对象必须实现 hashCode 方法和 equals 方法。

  • Hashtable是线程安全的,任何非 null 对象都可以用作键或值。

  • HashMap是线程不安全的,并允许使用 null 值和 null 键。

2、LinkedHashMap

LinkedHashMap 是 HashMap 的子类。此实现与 HashMap 的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序通常就是将键插入到映射中的顺序(插入顺序)。

3、TreeMap

基于红黑树(Red-Black tree)的 NavigableMap 实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。

4、Properties

Properties 类是 Hashtable 的子类,Properties 可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。

存取数据时,建议使用setProperty(String key,String value)方法和getProperty(String key)方法。

properties.getProperty("file.encoding");//当前源文件字符编码

第15天 IO流

特点

流的的特点:流有两端,一端一定是程序,另一端不一定,可以是File对象,也可以是其他。
流的方向以程序端为参照点,数据从外界进入程序被称为输入,也叫读。从程序向外界流转数据被称为输出,也叫写。

1.File类

File file1= new File("D:\\temp\\hello1.txt");// 绝对路径构建File对象
    File file = new File("day19\\hello.txt");// 相对路径构建File对象,使用idea的project相对路径起点是project根目录
    System.out.println(file.exists());// 判断当前路径是否存在文件,true表示该文件存在,false表示不存在
    System.out.println(file.getName());// 返回当前File对象代指的文件的文件名
    String path = file.getAbsolutePath();// 返回当前文件的绝对路径
    File fileOne = file.getAbsoluteFile();// 返回当前文件的绝对路径文件
    if(!file1.exists()) {
        file1.createNewFile();// 创建一个新的文件
    }
    file1.delete();// 删除一个文件
//API中说明:delete方法,如果此File表示目录,则目录必须为空才能删除。

    System.out.println(file.isFile());// 判断当前File对象是否为文件,是文件返回true,不是文件返回false
    System.out.println(file.isDirectory());// 判断当前File对象是否为一个文件夹,是文件夹返回true,不是文件夹返回false
        file.canRead();//判断File对象对应的文件或目录是否可读
        file.canWrite();//判断File对象对应的文件或目录是否可写
        file.isHidden();//判断File对象对应的文件或目录是否是否隐藏
    if(file.isDirectory()){
        if(!file.exists()){
            file.mkdir();// 创建文件夹
            //mkdirs()可以建立多级文件夹, mkdir()只会建立一级的文件夹
        }
    }
    System.out.println(file.length());// 当前文件的大小  字节数
    System.out.println(file.getParent());// 当前文件或文件夹的上一级文件夹名
    File file2 = file.getParentFile();// 当前文件或文件夹的上一级文件夹对象
    if(file.isDirectory()) {
        String[] subFileString = file.list();// 返回当前文件夹下所有的子文件及子文件夹的名字
        File[] subFiles = file.listFiles();// 返回当前文件夹下所有的子文件及子文件夹的文件对象
    }
System.out.println("文件最后修改时间:" + LocalDateTime.ofInstant(Instant.ofEpochMilli(f.lastModified()),ZoneId.of("Asia/Shanghai")));

2、IO的分类

根据数据的流向分为:输入流输出流

  • 输入流 :把数据从其他设备上读取到内存中的流。
    • 以InputStream,Reader结尾
  • 输出流 :把数据从内存 中写出到其他设备上的流。
    • 以OutputStream、Writer结尾

根据数据的类型分为:字节流字符流

  • 字节流 :以字节为单位,读写数据的流。
    • 以InputStream和OutputStream结尾
  • 字符流 :以字符为单位,读写数据的流。
    • 以Reader和Writer结尾

根据IO流的角色不同分为:节点流处理流

  • 节点流:可以从或向一个特定的地方(节点)读写数据。如FileReader.

  • 处理流:是对一个已存在的流进行连接和封装,通过所封装的流的功能调用实现数据读写。如BufferedReader.处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。2、IO的分类

    根据数据的流向分为:输入流输出流

    • 输入流 :把数据从其他设备上读取到内存中的流。
      • 以InputStream,Reader结尾
    • 输出流 :把数据从内存 中写出到其他设备上的流。
      • 以OutputStream、Writer结尾

    根据数据的类型分为:字节流字符流

    • 字节流 :以字节为单位,读写数据的流。
      • 以InputStream和OutputStream结尾
    • 字符流 :以字符为单位,读写数据的流。
      • 以Reader和Writer结尾

    根据IO流的角色不同分为:节点流处理流

    • 节点流:可以从或向一个特定的地方(节点)读写数据。如FileReader.

    • 处理流:是对一个已存在的流进行连接和封装,通过所封装的流的功能调用实现数据读写。如BufferedReader.处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。

3.字节输入(输出)节点流

// 使用File对象创建流对象
    File file = new File("a.txt");
    FileOutputStream fos = new FileOutputStream(file);
  
    // 使用文件名称创建流对象
    FileOutputStream fos = new FileOutputStream("b.txt");

FileOutputStream fos = new FileOutputStream(file,true); 加true 写入文件不覆盖

File file = new File("day19\\hello.txt");

// 字节输入节点流
FileInputStream fis = new FileInputStream(file);
 // fis.read();// 一次读取一个字节,将该字节的整数返回,如果返回-1则表示没有读到任何内容

     /*
     while(true){
      int code = fis.read();
         if(code == -1){
               break;
            }
           char c = (char)code;
           System.out.print(c);
          }
        */
      byte[] data = new byte[(int)file.length()];
      fis.read(data);
// 字节输出节点流
if(!file.exists()){
                file.createNewFile();
            }
            FileOutputStream fos = new FileOutputStream(file,true);
            fos.write(97);
            String word = "hello kitty";
            byte[] data = word.getBytes();
            fos.write(data);

4.字节流 复制文件

try{
    File fileOne = new File("day19\\尚硅谷_张锐_JavaSE_day19【IO流】.md");
    File fileTwo = new File("day19\\尚硅谷_张锐_JavaSE_day19【IO流】(副本).md");

    if(!fileTwo.exists()){
        fileTwo.createNewFile();
    }

    byte[] datas = new byte[1024];

    FileInputStream fis = new FileInputStream(fileOne);
    FileOutputStream fos = new FileOutputStream(fileTwo);

    while(true){
        int length = fis.read(datas);
        if(length == -1){
            break;
        }
        fos.write(datas,0,length);
    }

    fis.close();
    fos.close();
    }catch{}

5.字符输入输出 节点流 使用缓存区 (处理流)

File file = new File("day19\\hello.txt");

// 字符输入节点流
FileReader fr = new FileReader(file);
// 带有缓冲区的字符输入处理流
BufferedReader br = new BufferedReader(fr);
while(true) {
    String s = br.readLine();
    if(s == null){
        break;
    }
    System.out.println(s);
}

br.close();
fr.close();


// 字符输出节点流

 File file = new File("day19\\hello2.txt");

            if(!file.exists()){
                file.createNewFile();
            }

            // 字符输出节点流
            FileWriter fw = new FileWriter(file,true);// 设置追加模式在节点流上设置
            // 带缓冲区的字符输出处理流
            BufferedWriter bw = new BufferedWriter(fw);
            bw.newLine();
            bw.write("床前明月光");
            bw.flush();// 清空缓冲区,清空缓冲区会立即进行读写
            bw.newLine();
            bw.write("疑是地上霜");
            bw.flush();
            bw.newLine();
            bw.write("举头望明月");
            bw.flush();
            bw.newLine();
            bw.write("低头思故乡");
            bw.flush();
            bw.close();
            fw.close();

6.字节输入节点流 使用缓存区 (处理流)

File fileOne = new File("day19\\day19-05-字节输入节点流.mp4");
File fileTwo = new File("day19\\day19-05-字节输入节点流(副本).mp4");

if(!fileTwo.exists()){
    fileTwo.createNewFile();
}

BufferedInputStream bis = new BufferedInputStream(new FileInputStream(fileOne));//输入
BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(fileTwo));//输出
byte[] data = new byte[1024];
while(true){
    int length = bis.read(data);
    if(length == -1){
        break;
    }
    fos.write(data,0,length);
    fos.flush();
}

bis.close();
fos.close();

7.中转输入(输出)流

// 创建字节节点输入流对象
FileInputStream fis = new FileInputStream(file);

// 创建中转输入流对象
InputStreamReader isr = new InputStreamReader(fis);

// 利用中转输入流对象创建字符输入处理流对象
BufferedReader br = new BufferedReader(isr);

   // 使用字节节点流创建字符处理流
  BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file,true)));

8.对象流

实体类对象必须实例化Serializable 属性是引用类型 也需要序列化 , 所有属性都需要序列化 静态变量的值不会序列化

  • 该类必须实现java.io.Serializable 接口,Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException

    • 如果对象的某个属性也是引用数据类型,那么如果该属性也要序列化的话,也要实现Serializable 接口
  • 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient 关键字修饰。

  • 静态变量的值不会序列化

  • // 加入序列版本号
    private static final long serialVersionUID = 1L;

  • public class SerializeDemo{
       	public static void main(String [] args)   {
        	Employee e = new Employee();
        	e.name = "zhangsan";
        	e.address = "beiqinglu";
        	e.age = 20; 
        	try {
          		// 创建序列化流对象 out对象输出流
              ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("employee.txt"));
                //in对象输入流
              ObjectInputStream in = new ObjectInputStream(new FileInputStream("employee.txt"));
            	// 写出对象
            	out.writeObject(e);
                //读入对象
                employee e = (employee)in.readOnject();
            	// 释放资源
            	out.close();
            	fileOut.close();
            	System.out.println("Serialized data is saved"); // 姓名,地址被序列化,年龄没有被序列化。
            } catch(IOException i)   {
                i.printStackTrace();
            }
       	}
    }
    
           // 集合序列化操作
    		// serializ(arrayList);
    private static void serializ(ArrayList<Student> arrayList) throws Exception {
    		// 创建 序列化流 
    		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("list.txt"));
    		// 写出对象
    		oos.writeObject(arrayList);
    		// 释放资源
    		oos.close();
    	}
    
    

    printWriter printstrem
    字符打印流 字节打印流

第16天 网络编程

1、相关概念:

通讯协议:

在网络间传递数据的一种格式或者约定。网络通讯各个层之间,有着不同的协议。例如HTTP协议和FTP协议就属于应用层协议。TCP和UDP协议属于传输层协议。

IP地址:

在网络中唯一确定一台终端的地址。现行使用IPv4地址,这种地址也被称为点分十进制。IP地址实际上是32位的二进制数,每8位一组,由三个点分开。将8位二进制数转成10进制整数,所以称作点分十进制。这四组十进制整数中,有网络IP和主机JIP两类,第一组是网络IP剩下三组是主机JIP则被称为A类地址,第一组和第二个组是网络IP则是B类IP,前三组是网络IP则是C类IP。
端口号:在一台终端上,运行这多个进程,每个进程运行在一个独立的端口号上,类似于门牌号码。端口号不能冲突,端口号是必须唯一的。

端口号:

2.socket (套接字)

  • 流套接字(stream socket):使用TCP提供可依赖的字节流服务
    • ServerSocket:此类实现TCP服务器套接字。服务器套接字等待请求通过网络传入。
    • Socket:此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器间通信的端点。
  • 数据报套接字(datagram socket):使用UDP提供“尽力而为”的数据报服务

3、 相关API

ServerSocket类的构造方法:

  • ServerSocket(int port) :创建绑定到特定端口的服务器套接字。

ServerSocket类的常用方法:

  • Socket accept():侦听并接受到此套接字的连接。

Socket类的常用构造方法

  • public Socket(InetAddress address,int port):创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
  • public Socket(String host,int port):创建一个流套接字并将其连接到指定主机上的指定端口号。

Socket类的常用方法

  • public InputStream getInputStream():返回此套接字的输入流,可以用于接收消息
  • public OutputStream getOutputStream():返回此套接字的输出流,可以用于发送消息
  • public InetAddress getInetAddress():此套接字连接到的远程 IP 地址;如果套接字是未连接的,则返回 null。
  • public InetAddress getLocalAddress():获取套接字绑定的本地地址。
  • public int getPort():此套接字连接到的远程端口号;如果尚未连接套接字,则返回 0。
  • public int getLocalPort():返回此套接字绑定到的本地端口。如果尚未绑定套接字,则返回 -1。
  • public void close():关闭此套接字。套接字被关闭后,便不可在以后的网络连接中使用(即无法重新连接或重新绑定)。需要创建新的套接字对象。 关闭此套接字也将会关闭该套接字的 InputStream 和 OutputStream。
  • public void shutdownInput():如果在套接字上调用 shutdownInput() 后从套接字输入流读取内容,则流将返回 EOF(文件结束符)。 即不能在从此套接字的输入流中接收任何数据。
  • public void shutdownOutput():禁用此套接字的输出流。对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列。 如果在套接字上调用 shutdownOutput() 后写入套接字输出流,则该流将抛出 IOException。 即不能通过此套接字的输出流发送任何数据。

**注意:**先后调用Socket的shutdownInput()和shutdownOutput()方法,仅仅关闭了输入流和输出流。

通信客户端

public static void main(String[] args) {
    try {
        Socket socket = new Socket("localhost",8182);

        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        bw.write("你好  猪");
        bw.flush();
        socket.shutdownOutput();
        BufferedReader br= new BufferedReader(new InputStreamReader(socket.getInputStream()));
        System.out.println(br.readLine());
        bw.close();
        br.close();
        socket.close();
    }catch (Exception e){
        e.printStackTrace();
    }

服务端线程

public class TsetServerThread extends Thread{
    private Socket socket;

    public  TsetServerThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public  void run(){
        try {

            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            System.out.println(br.readLine());
            socket.shutdownInput();
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
             bw.write("你好客户端,我是聂蝶");
             bw.flush();
             bw.close();
             br.close();
             socket.close();

        }catch (Exception e){
            e.printStackTrace();
        }

    }

}

服务端

 public static void main(String[] args) {
try {
    ServerSocket ss = new ServerSocket(8182);
    while (true){
        Socket socket = ss.accept();
        TsetServerThread tsetServerThread = new TsetServerThread(socket);
        tsetServerThread.start();
    }
}catch (Exception e){

}

第17天 反射

1.类加载

类加载有三步

1.先加载load

把类型为.class字节码文件读取到内存 output

2.连接 link

验证合法性

准备:准备对应的内存(方法区),创建Class对象,为类变量赋默认值,为静态常量赋初始值。

解析:把字节码中的符号引用替换为对应的直接地址引用

3.初始化

初始化是为类的静态变量赋予正确的初始值,准备阶段和初始化阶段看似有点矛盾,其实是不矛盾的,如果类中有语句:private static int a = 10,它的执行过程是这样的,首先字节码文件被加载到内存后,先进行链接的验证这一步骤,验证通过后准备阶段,给a分配内存,因为变量a是static的,所以此时a等于int类型的默认初始值0,即a=0,然后到解析(后面在说),到初始化这一步骤时,才把a的真正的值10赋给a,此时a=10。

二、类加载时机

  1. 创建类的实例,也就是new一个对象
  2. 访问某个类或接口的静态变量,或者对该静态变量赋值
  3. 调用类的静态方法
  4. 反射(Class.forName(“com.lyj.load”))
  5. 初始化一个类的子类(会首先初始化子类的父类)
  6. JVM启动时标明的启动类,即文件名和类名相同的那个类
静态加载

通过new关键字来创建Test的实例对象。

Test test = new Test();

动态加载

通过获取class对象加载(4种方式)

Class classOne = Student.class;
 Object  obj = classOne.newInstance();

//有参构造
Constructor cs = classOne.getDeclaredConstructor((String.class,int.class,String.class);


2.类加载器

根类加载器(bootstrap class loader):它用来加载 Java 的核心类,是用原生代码来实现的

扩展类加载器(extensions class loader):它负责加载JRE的扩展目录

应用程序类加载器(system class loader):被称为系统(也称为应用)类加载器,负责加载Java应用程序类路径下的内容

**自定义类加载器 ** 继承java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求

类加载机制

**双亲委派机制 ** 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己才想办法去完成。

3.获取Class对象的四种方式

// 使用类名获取该类的类类对象
Class classOne = Student.class;
System.out.println(classOne.getName());

// 使用对象获取该对象的类的类类对象
Student stu = new Student();
Class classTwo = stu.getClass();

// 使用完整类名字符串获取该类的类类对象
try {
    Class classThree = Class.forName("com.atguigu.test1.Student");
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

// 使用类加载器获取类类对象
try {
    Class classFour = Student.class.getClassLoader().loadClass("com.atguigu.test1.Student");
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

4.获取对象属性

public static void main(String[] args) {
    try{
        Class classOne = Student.class;// 获取类类对象
        System.out.println("父类:" + classOne.getSuperclass().getName());
        System.out.println(classOne.getName());// 获取当前类类对象代表的类的完整类名
        System.out.println(classOne.getPackage().getName());// 获取当前类类对象代表的类的所在包对象再获取包名
        System.out.println(classOne.getSimpleName());// 获取当前类类对象代表的类的类名不包含包名
        System.out.println("----------------------------------------");
        // Field[] fields = classOne.getFields();// 获取当前类类对象的所有public修饰的属性,但不仅限于本类,包括从父类继承到的public属性
        Field[] fields = classOne.getDeclaredFields();// 获取当前类类对象的所有的访问修饰符的属性,只能获取本类的属性
        for (Field field : fields) {
            System.out.println(Modifier.toString(field.getModifiers()));// 获取属性的访问修饰符
            Class type = field.getType();// 获取该属性的类型
            System.out.println(type.getSimpleName());
            System.out.println(field.getName());// 获取属性名
        }

        // Field field1 = classOne.getField("one");// 获取本类及继承到指定参数名的public修饰的属性
        Field field1 = classOne.getDeclaredField("stuAge");// 获取本类的所有的访问修饰的指定参数名的属性
        System.out.println(field1.getName());

    }catch (Exception ex){
        ex.printStackTrace();
    }
}

5.获取对象的方法和构造方法

public static void main(String[] args) {
    try{
        Class classOne = Student.class;

        Method[] methods = classOne.getMethods();// 获取所有public修饰的方法,不仅限于本类,包括继承到的方法
        // Method[] methods = classOne.getDeclaredMethods();// 获取本类所有修饰符修饰的方法,仅限本类。
        for (Method method : methods) {
            String mod = Modifier.toString(method.getModifiers());//获取方法的访问修饰符
            Class type = method.getReturnType();  //获取方法的返回类型 
            String typeName = type.getSimpleName();  
            String methodName = method.getName();  //获取方法名
            Parameter[] parameters = method.getParameters();//获取形参数组
            String info = mod+" "+typeName+" "+methodName+"(";
            for (int i = 0; i < parameters.length; i++) {

                if(i == parameters.length-1){
                    info += parameters[i].getName();
                }else{
                    info += parameters[i].getName()+",";
                }
            }
            info += ")";
            System.out.println(info);
        }
        // 精确获取一个方法,需要方法名和参数组两部分,如果没有参数,则只需要方法名,如果有参数则需要将每个参数的类类对象传入
        Method method = classOne.getDeclaredMethod("study",String.class,String.class);
        System.out.println(method);
        System.out.println("----------------------------------------------");
        // Constructor[] constructors = classOne.getConstructors();// 获取本类所有public修饰的构造方法
        Constructor[] constructors = classOne.getDeclaredConstructors();// 获取班类所有修饰的构造方法
        Constructor constructor = classOne.getConstructor(String.class,int.class,String.class);
        String mod = Modifier.toString(constructor.getModifiers());// 获取构造方法的访问修饰符
        Parameter[] parameters = constructor.getParameters();// 获取构造方法的参数组
        System.out.println(constructor);


    }catch (Exception ex){
        ex.printStackTrace();
    }
}

6.方法和构造方法的使用

public static void main(String[] args) {
    try{
        Class classOne = Student.class;
        // Object obj = classOne.newInstance();// 调用类的默认无参构造方法创建对象
        // 如何在反射状态下利用有参构造方法
        // 利用参数列表获取相应的构造方法对象创建对象
        Constructor constructor = classOne.getDeclaredConstructor(String.class,int.class,String.class);
        Object obj = constructor.newInstance("xiaoming",22,"女");
        System.out.println(obj);
        Field field = classOne.getDeclaredField("stuName");
        String s = (String)field.get(obj);
        System.out.println("----------------------------");
        Method methodOne = classOne.getDeclaredMethod("study");// 获取无参的方法,只需要传递方法名
        methodOne.setAccessible(true);
        methodOne.invoke(obj);// 运行方法
        Method methodTwo = classOne.getDeclaredMethod("study",int.class,String.class);
        methodTwo.setAccessible(true);
        methodTwo.invoke(obj,10,"hello");
        System.out.println("------------------------------------------");
        Method methodThree = classOne.getDeclaredMethod("sayHello",String.class,int.class,boolean.class);
        methodThree.invoke(null,"nichilema",101,false);

    }catch (Exception ex){
        ex.printStackTrace();
    }
}

7.class类对象创建对象

try {
    Class classOne = Student.class;
    // Student stu = new Student();
    // Student stu1 = new Student();
    Object obj = classOne.newInstance();//创建实例对象:如果操作的是非静态属性,需要创建实例对象
    Object obj1 = classOne.newInstance(); //返回类型为object
    // stu.stuName获取属性值,fieldOne是属性本身
    Field fieldOne = classOne.getDeclaredField("stuName");
    Field fileTwo = classOne.getDeclaredField("school");//使用静态属性
    Field fieldThree = classOne.getDeclaredField("stuAge");
    // stu.stuName = "Tom";
    fieldOne.set(obj,"Tom");//设置obj对象的 name属性  属性.方法
    fieldOne.set(obj1,"Jerry");
    fileTwo.set(obj,"尚硅谷");  //静态属性赋值  可以使用对象.属性
    fileTwo.set(null,"尚硅谷");//也可以属性赋值
    fieldThree.setAccessible(true);//设置私有属性可访问
    fieldThree.set(obj,20);
    // String name = stu.getStuName();
    String name = (String)fieldOne.get(obj1);
    System.out.println(fileTwo.get(obj1));//获取静态属性值   可以用get其他对象   共享
    System.out.println(fileTwo.get(null));//也可以不使用其他对象
    System.out.println(name);
    System.out.println(fieldThree.get(obj));
} catch (Exception e) {
    e.printStackTrace();
}

第18天 Jdk8新特性

28、Lambda表达式

1、函数式接口:只有一个方法的接口称作函数式接口

标记了@FunctionalInterface的函数式接口

@FunctionalInterface
interface MyInterOne{
    public void methodOne();
}

2、lambda的功能是实现函数式接口:

public void test1(){
    	// 匿名内部类的方式实现函数式接口
        MyInterOne mio = new MyInterOne() {
            @Override
            public void methodOne() {
                System.out.println("in myinterone~~~~~~~~~~~~~~~");
            }
        };
        mio.methodOne();
		
    	// lambda表达式的方式实现函数式接口
        MyInterOne mio1 = () -> System.out.println("in myinterone----------------");
        mio1.methodOne();
}

3、lambda表达式的多种形式

public void test2(){
  	MyInterTwo mit1 = (String s) -> System.out.println("in methodTwo1 s="+s+"------");
    // 由于有效推断,可以确定参数的类型,所以lambda可以删除掉参数类型,如果只有一个参数,还可以删除小括号。
    MyInterTwo mit2 = s -> System.out.println("in methodTwo1 s="+s+"----------");
    // 如果实现的方法体中只有一行代码,那么可以省略大括号,也可以写大括号,但是如果有多行代码则必须写大括号。
    MyInterTwo mit3 = s -> {
       System.out.println("in methodTwo2 s="+s+"=============");
       System.out.println("hello world");
       System.out.println("nihao~~~~~~~~~~~~~~~~~~~~~~~");
    };

    mit1.methodTwo("nihao");
    mit2.methodTwo("xiexie");
    mit3.methodTwo("haha");
}

 public void test3(){
        // 如果方法体只有一行代码,可以省略大括号,由于有返回值,所以一行代码必须返回值
        // 一行代码可以不写大括号,那么不写大括号就不能写return.
        MyInterThree mit1 = (s,i) -> s+i;
        String word = mit1.methodThree("haha",200);
        System.out.println(word);
        // 只有一行代码,代码是返回值,可以写大括号,写大括号就需要写return
        MyInterThree mit2 = (s,i) -> {return s+i;};
        String word1 = mit2.methodThree("heihei",300);
        System.out.println(word1);
        // 如果方法体有多行代码,那么必须写大括号,写大括号就必须写return
        MyInterThree mit3 = (s,i) -> {
            System.out.println("in myinterthree s="+s+",i="+i+"-------------");
            System.out.println("nichilema");
            return s+i;
        };
        String word2 = mit3.methodThree("baibai",400);
        System.out.println(word2);
 }

4、四大函数式接口:消费型,供给型,判断型,功能型

消费型接口的抽象方法特点:有形参,但是返回值类型是void Consumer void accept(T t)

BiConsumer<T,U> void accept(T t, U u)

这类接口的抽象方法特点:无参,但是无返回值

Supplier T get()

这里接口的抽象方法特点:有参,但是返回值类型是boolean结果。

Predicate boolean test(T t) 接收一个对象

BiPredicate<T,U> boolean test(T t, U u) 接收两个对象

Function<T,R> R apply(T t)

这类接口的抽象方法特点:既有参数又有返回值

29、StreamAPI

1、Stream流的特点:

Stream 自己不会存储元素。

Stream 不会改变源对象。每次处理都会返回一个持有结果的新Stream。

Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

2、使用Stream流的三个关键步骤

创建 Stream:通过一个数据源(如:集合、数组),获取一个流

中间操作:中间操作是个操作链,对数据源的数据进行n次处理,但是在终结操作前,并不会真正执行。

终止操作:一旦执行终止操作,就执行中间操作链,最终产生结果并结束Stream。

3、获取Stream流对象的方式

List<Student> list = StudentData.getList();
Stream<Student> stream = list.stream();// 通过集合获取Stream流对象

List<Student> list = StudentData.getList();
Object[] stus = list.toArray();// 通过数组获取Stream流对象
Stream stream = Arrays.stream(stus);

Stream<String> stringStream = Stream.of("Tom","Jerry","Mary","Rose","Jack");// 通过散列数据获取Stream流

4、中间操作:中间操作是个操作链,对数据源的数据进行n次处理,但是在终结操作前,并不会真正执行。

常用中间操作:过滤操作

Stream<Student> stream = list.stream();// 获取Stream流对象
Stream<Student> stream1 = stream.filter((Student s) -> s.getScore() >= 60);// 中间操作,过滤掉低于60分的学员
Stream<Student> stream2 = stream1.filter(s -> s.getName().startsWith("王"));
Stream<Student> stream3 = stream2.filter(s -> s.getGrade() == 3);
stream3.forEach((Student s) -> System.out.println(s));// 终结操作流中的学员打印在控制台上

常用中间操作:跳过N条记录和截取N条记录

Stream<Student> stream = list.stream();
Stream<Student> stream1 = list.stream().distinct();// 过滤重复记录
Stream<Student> stream2 = stream1.limit(15);// 在源集合中截取参数位以前的记录
Stream<Student> stream3 = stream2.skip(10);// 从源集合中跳过参数条目后返回其他的条目
stream3.forEach(s -> System.out.println(s));

5、终结操作:一旦执行终止操作,就执行中间操作链,最终产生结果并结束Stream。

常用终结操作:

List<Student> list = StudentData.getList();
long l = list.stream().filter(s -> s.getGrade() == 3).count();// 统计流中的条目数

List<Student> list = StudentData.getList();
Stream<Student> stream = list.stream().filter(s -> s.getScore() >= 60);
List<Student> list1 = stream.collect(Collectors.toList());// 使用收集终结操作,将Stream流中的数据转成我们习惯的集合框架

Stream<Student> stream = list.stream();// 获取Stream流对象
stream.forEach((Student s) -> System.out.println(s));// 终结操作流中的学员打印在控制台上
相关标签: javase基础 javase

上一篇: myhome.sql

下一篇: JavaSE_08 流程控制