IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Java基础学习 -> 正文阅读

[Java知识库]Java基础学习

详细笔记

第一章:Java语言概述

一、Java编译运行过程与跨平台

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

二、JDK JRE JVM解释

在这里插入图片描述
JDK(Java Development Kit) — Java开发工具包 — JRE+开发工具

开发java程序最小的环境为JDK,所以JDK是JAVA语言的核心

JRE(Java Runtime Environment) — Java运行时环境 — JVM+运行java程序所必需的环境

运行java程序最小的环境为JRE

JVM(Java Virtual Machine)—负责加载.class并运行.class文件

JVM(JAVA虚拟机)将JAVA代码转换为对应的操作系统可以理解的指令,不同的操作系统有不同虚拟机与之对应,同一段代码交给虚拟机之后,虚拟机再转化给操作系统

什么是将java代码翻译成当前操作系统所理解的指令?

这指的就是编译的过程,将.java文件编译成.class字节码文件.编译完成会产生一个.class文件,这个文件称为字节码文件,操作系统看的指令文件就是字节码文件.

第二章:基本语法

标识符

标识符可以简单的理解成一个名字。在Java中,我们需要标识代码的很多元素,包括类名、方法、字段、变量、包名等等。我们选择的那个名称就称为标识符,一个正确的标识符需要遵循以下规则:

  1. 标识符可以由字母、数字、下划线(_)、美元符($)组成,但不能包含 @、%、空格等其它特殊字符

  2. 不能以数字开头。如:123name不合法

  3. 标识符严格区分大小写。如:tmooc 和 Tmooc是两个不同的标识符

  4. 标识符的命名最好能反映出其作用,做到见名知意。

  5. 标识符不能是Java的关键字

ASCII编码表

ASCII(American Standard Code for Information Interchange)编码表,美国标准信息交换代码。

在计算机中,所有的数据在存储和运算时都要使用二进制数表示。

a、b、c、d这样的52个字母(包括大写)、以及0、1等数字还有一些常用的符号, 在计算机中存储时也要使用二进制数来表示,而具体用哪些二进制数字表示哪个符号,当然每个人都可以约定自己的一套(这就叫编码),而大家如果要想互相通信而不造成混乱,那么大家就必须使用相同的编码规则。

于是美国有关的标准化组织就出台了ASCII编码,统一规定了上述常用符号用哪些二进制数来表示。

中国 gb2312【字少】 àgbk【识别4万+汉字】à万国码表unicode【ISO制定】

所有码表都兼容ASCALL

附:ASCII码表
在这里插入图片描述

关键字

在Java中,有一些单词被赋予了特定的意义,一共有53个关键字。这53个单词都是全小写,其中有两个保留字:const和goto。
在这里插入图片描述

注释

几乎所有编程语言都允许程序员在代码中输入注释,因为编译器会忽略注释,所以注释并不会影响程序的运行结果。

注释的真正作用是: 它可以向任何阅读代码的人描述或者解释程序的实现思路,如何使用以及其它任何相关信息,提高代码的可读性,方便后期的维护与复用。Java的注释有3种:

  1. 单行注释: 注释单行内容.

格式: 每行都以”//”开头.

快捷方式:Ctrl+/添加注释,同样的快捷键,再按一次,取消注释

  1. 多行注释:注释多行内容,虽然叫多行注释,也可注释单行内容.

格式: 以/* 开头,以*/ 结束.

快捷方式:Ctrl+Shift+/添加注释,Ctrl+Shift+\取消注释,也可以输入”/*”之后按回车添加注释

  1. 文档注释: 一般用来注释类和方法,通过注释内容来记录类或者方法的信息.

格式: 以 /** 开头。 以 */ 结尾.

快捷方式:输入 /** 之后按回车添加注释

变量

在JAVA中,我们需要记录一些数据,但这些数据的值是不固定的,总在变,我们可以把这些数据理解为变量。

我们通过三个元素来描述变量:变量类型 变量名以及变量值。

int age = 18; //声明int类型的变量并赋值

String tel ; //声明String类型的变量

数据类型

1、基本类型介绍(八种)

变量就是申请内存来存储值。也就是说,当创建变量的时候,需要在内存中申请空间。

java是一个强类型的语言,会把日常见到的数据,分成好多类型.

内存管理系统根据变量类型来分配存储空间,分配的空间只能用来储存该类型数据。

位 bit,来自英文bit,音译为”比特”,表示二进制位

1Byte = 8 bits (1字节 = 8 位)

1KB = 1024Bytes 1MB = 1024KB 1GB=1024M
在这里插入图片描述
变量交换

      int t ;

      //t = 1;//把数字1交给变量t保存

      t = a;//把变量a中保存的值交给变量t来保存

      a = b;//把变量b中保存的值交给变量a来保存

      b = t;//把变量t中保存的值交给变量b来保存

2、 引用类型

引用类型是一个对象类型,值是什么呢?它的值是指向内存空间的引用,就是地址,所指向的内存中保存着变量所表示的一个值或一组值。如:类,接口,数组

3、基本类型的字面值

3.1整数字面值是int类型(byte1 short2 int4 long8 float4 double8)

int x = 99999;//对,右面数据的字面值是int类型

int x = 99999999999;//错,右面数据的字面值是int类型,但是已经超出int的取值范围。

3.2 byte,short,char三种比int小的整数可以用范围内的值直接赋值

byte b1=127;//对,可以用范围内的值直接赋值

byte b2=128;//错,超出byte范围

3.3浮点数的字面值是double类型

double r =3.14;//对,小数字面值类型就是double

float r =3.14;//错,右面的数据字面值是double,float是4字节存不下double类型的数据

3.4字面值后缀L D F

long x =99999999999L;//字面值是int类型,需转成long类型的数据,加字面值后缀L即可

float b = 3.0F;//3.0字面值是double类型,加后缀F会变成float类型

double d = 3D;//3字面值是int类型,加后缀D,会变成double类型

3.5进制前缀
0b - 标识这是2进制 ,如:0b0101

0 - 标识这是8进制, 8进制是三位,如: 023

0x - 标识这是16进制,如: 0x0001

\u -标识这是char类型,属于16进制

4 、 基本类型的类型转换

在这里插入图片描述
箭头开始的地方是小类型,箭头指向的地方是大类型
4.1 小到大(隐式转换)

byte m = 120;

int n = m;//小转大,右面的m是小类型,给左面的n大类型赋值,可以直接使用

 

float  f = 3.2f; double d = f; -->可以执行

4.2 大到小(显示转换)

int x = 999;

byte y =(byte)x;//大转小,右面x给左面的y小类型赋值,不可以,需要强制类型转换

注意
容量大的类型转换为容量小的类型时必须使用强制类型转换。

  1. 转换过程中可能导致溢出或损失精度

例如:int i =128; byte b = (byte)i; //打印的结果是-128

因为 byte 类型是 8 位,最大值为127,所以当 int 强制转换为 byte 类型时,值 128 时候就会导致溢出。

  1. 浮点数到整数的转换是通过舍弃小数得到,而不是四舍五入

例如:float f = 32.7f; int a2 =(int) f; //打印的结果是32

  1. 不能对boolean类型进行类型转换。

4.3 口诀:
小到大,直接转 大到小,强制转 浮变整,小数没

------------------------------------>byte,short,char> int> long> float> double 

5、 运算规则

5.1 计算结果的数据类型,与最大数据类型一致

System.out.println(3/2);//1,int/int,得到的结果类型还是int

System.out.println(3/2d);//1.5,int/double。得到的结果是double

5.2 byte,short,char三种比int小的整数,运算时会先自动转换成int

byte a = 1;

byte b = 2;

byte c = (byte)(a+b);

//a+b会自动提升成int类型,右面得运算结果就是int大类型

//给左面的byte小类型赋值,不可以,需要强转。

5.3 整数运算溢出
整数运算,类似于一个钟表,转到最大时,再转会回到最小。

计算:光速运行一年的长度是多少米?3亿m/s

//溢出的现象:

//因为做了大的整型数据的运算,会超出int取值范围。解决方法:把整数提升成long类型。

System.out.println(300000000L*60*60*24*365);

5.4 浮点数运算不精确
使用Java 的 BigDecimal

 public static void main(String[] args){
        BigDecimal a = new BigDecimal("4.5");
        BigDecimal b = new BigDecimal("1.5");
 
        System.out.println("a + b =" + a.add(b));
        System.out.println("a - b =" + a.subtract(b));
        System.out.println("a * b =" + a.multiply(b));
        System.out.println("a / b =" + a.divide(b));
    }

5.5浮点数的特殊值

 System.out.println(3.14/0);//Infinity--无穷大

      System.out.println(0/0.0);//NaN-Not a Number

      System.out.println(0.0/0);//NaN-Not a Number

运算符

1.1 概述

运算符 用于连接 表达式 的 操作数,并对操作数执行运算。

例如,表达式num1+num2,其操作数是num1和num2,运算符是”+”。

在java语言中,运算符可分为5种类型:

算术运算符、赋值运算符、关系运算符、逻辑运算符、位运算符。

根据操作数的不同,运算符又分为单目运算符、双目运算符和三目运算符。

单目运算符只有一个操作数,双目运算符有两个操作数,三目运算符则有三个操作数。

位运算符涉及到二进制位的运算,在java 程序中运用不是很多。

1.2 运算符速查表

在这里插入图片描述
a是操作数,++是自增运算符–是自减运算符,自增和自减运算符即可以放在变量的前面,也可以放在变量的后面,例如:a++、++a、a–、–a等。
自增(++):将变量的值加1
分前缀式(如++a)和后缀式(如a++)。前缀式是先加1再使用;后缀式是先使用再加1。
自减(–):将变量的值减1
分前缀式(如–a)和后缀式(如a–)。前缀式是先减1再使用;后缀式是先使用再减1。

逻辑运算符连接两个关系表达式或布尔变量,用于解决多个关系表达式的组合判断问题
注意逻辑运算符返回的运算结果为布尔类型
通常,我们用0表示false,用1表示true
与:表示并且的关系
&单与: 1 & 2 ,结果想要是true,要求1和2都必须是true
&&双与(短路与):1 && 2 ,当1是false时,2会被短路,提高程序的效率
或:表示或者的关系
|单或: 1 | 2,结果想要是true,要求1和2只要有一个为true就可以
||双或(短路或):1 || 2,当1是true时,2会被短路,提高程序效率
TIPS:当一个表达式包含多个运算符时,就需要考虑运算符的优先级,优先级高的运算符先参与运算,优先级低的运算符后参与运算。在实际的开发中,不需要特别去记忆运算符的优先级别,也不要刻意的使用运算符的优先级别,对于不清楚优先级的地方使用小括号辅助进行优先级管理。

  //定义变量max来保存比较的最大值

        int max = a > b ? a : b;

分支结构

       //判断用户的打折段位并打折

        if(price >= 5000) {//满5000

            count = price *0.5;//打5折

        }else if(price >= 2000) {//满2000

            count = price * 0.8;//打折8折

        }else if(price >= 1000) {//满1000

            count = price *0.9;//打9折

        }

switch结构

1、概述:

switch case 语句用来判断一个变量与一系列值中某个值是否相等,每个值称为一个分支。

当一个case成立,从这个case向后穿透所有case,包括default,直到程序结束或者遇到break程序才结束

			 int a = 5;
        /**总结1:a 可以支持5种数据类型:byte short char int
         * jdk1.5以后支持String*/
        switch(a) {
            case 1 : System.out.println(1);break;
            case 2 : System.out.println(2);break;
            /**总结2:break表示结束当前的case,如果不加break,
             * 会向后穿透所有的case,包括default*/
            case 3 : System.out.println(3);break;
            case 4 : System.out.println(4);break; 
            /**总结3:default是保底选项,可加可不加
             * default是如果没有case被匹配到时执行的默认选项
             * 如果在default之前遇到了break,default才不会执行*/
            default : System.out.println(0);
        }

2、switch结构的注意事项

  1. switch 语句中的变量类型可以是: byte、short、int 、char、String(jdk1.5以后支持)
  2. switch 语句可以拥有多个 case 语句
  3. 每个 case 后面跟一个要比较的值和冒号,且此值的数据类型必须与变量的数据类型一致
  4. 当变量值与 case 语句值相等时,开始执行此case 语句的内容,执行完会判断此行代码是否有break,如果有,结束执行,如果没有,会继续向后执行穿透所有case,包括default
  5. switch 语句可以包含一个 default 分支,该分支一般是写在switch 语句的最后
    6)如果在default之前的case有break,则default不会执行

循环结构

for

for(开始条件;循环条件;更改条件){
syso循环体;
} 

在这里插入图片描述

//需求:打印0到10
        //for(开始条件;循环条件;更改条件){循环体;}
        //0 1 2 3 4 5 6 7 8 9 10
        //从哪开始:0
        //到哪结束:10
        //怎么变化:+1 ++
        for(int i = 0;i<=10;i++) {
            System.out.println(i);
        }
        System.out.println("**********************");
        //需求:打印10到0
        //10 9 8 7 6 5 4 3 2 1 0
        //从哪开始:10
        //到哪结束:0
        //怎么变化:-1  --
        //for(开始条件;循环条件;更改条件){循环体}
        for(int i = 10 ;i >= 0;i--) {
            System.out.println(i);
        }
        //需求:打印8,88,888,8888,
        //8 88 888 8888
        //从何开始:8
        //到哪结束:8888
        //如何变化:*10+8
        for(int i =8 ; i <= 8888 ; i=i*10+8) {
            System.out.print(i+",");//使用的是print(),打印后不换行
        }

嵌套for循环

概述:

存在至少2层for循环,根据外层的条件,判断里层能否执行
如果能执行,就把里层代码都循环完毕后,再继续判断是否执行外层循环的下一轮循环

for(开始条件;循环条件;更改条件){ //外层循环
	for(开始条件;循环条件;更改条件){ //内层循环
		循环体;
	} 
} 
//需求:打印矩形(3*5)
        //*****
        //*****
        //*****
        for(int i = 1; i<=3 ; i++) {//外循环--行数
            for(int j = 1; j<=5;j++) {//内循环--列数
                System.out.print("*");
            }
            System.out.println();//空白行用来换行
        }

增强for

  public static void print(Integer[] a) {
      //使用普通循环遍历数组比较复杂,引入高效for循环
      //普通循环的好处是可以控制循环的步长(怎么变化)
      for (int i = 0; i < a.length; i=i+2) {
          System.out.println(a[i]);
      }
      /**
      * 高效for/foreach循环--如果只是单纯的从头到尾的遍历,使用增强for循环
      * 好处:比普通的for循环写法简便,而且效率高
      * 坏处:没有办法按照下标来操作值,只能从头到尾依次遍历
      * 语法:for(1 2 : 3){代码块} 3是要遍历的数据  1是遍历后得到的数据的类型 2是遍历起的数据名
      */
      for(Integer i : a) {
         System.out.print(i);
      }
  }

break与continue

break: 直接结束当前循环,跳出循环体,简单粗暴
TIPS: break以后的循环体中的语句不会继续执行,循环体外的会执行
continue: 跳出本轮循环,继续下一轮循环
TIPS:continue后本轮循环体中的语句不会继续执行,但是会继续执行下轮循环,循环体外的也会执行

for(){
代码1
if(条件){
       代码3break;//如果成立,直接跳出这个for循环
}
代码2}
continue:跳出本次循环,进入下一轮
for(){
   代码1
if(条件){
        代码3continue;//如果成立,跳出本次for循环,进入下一轮
}
代码2}

while

形式(先判断,再执行)

while(执行条件){
循环体;
} 

do-while

形式(先执行,再判断,循环体代码保证最少执行一次)

do{
循环体;
} while (执行条件);

三种循环的区别:

三种循环都可以互相代替
1、 for:知道循环次数
2、 while/do while:当循环次数不确定时
3、 while:先判断,不符合规则,不执行代码
4、 do while:代码最少被执行一次,再去判断,符合规则,再次执行代码

变量

概念:
可以改变的数,称为变量。在Java语言中,所有的变量在使用前必须声明。
一般通过“变量类型 变量名 = 变量值 ;”这三部分来描述一个变量。如:int a = 3 ;
变量的使用原则:就近原则,即尽量控制变量的使用范围到最小

局部变量

位置:定义在方法里或者局部代码块中

注意:必须手动初始化来分配内存.如:int i = 5;或者int i; i = 5;
作用域:也就是方法里或者局部代码块中,方法运行完内存就释放了

成员变量

位置:定义在类里方法外
注意:不用初始化,也会自动被初始化成默认值
作用域:整个类中,类消失了,变量才会释放

方法

概述: 被命名的代码块,方法可以含参数可以不含参数,可以提高代码的复用性。
形式:

方法的修饰符 方法的返回值 方法名([参数列表…]){方法体;}
public static void main(String[] args){.   }

方法调用顺序图:

顺序执行代码,调用指定方法,执行完毕,返回调用位置
在这里插入图片描述

方法的重载

方法的重载是指在一个类中定义多个同名的方法,但是每个方法的参数列表不同(也就是指参数的个数和类型不同),程序在调用方法时,可以通过传递给他们的不同个数和类型的参数来决定具体调用哪个方法.

public static void main(String[] args) {
        //重载:在一个类中有多个同名的方法&方法的参数列表不同(个数不同/类型不同)
        //参数列表指的是参数的类型顺序和个数,而不是参数名
        //(int a,int b)和(int b,int a)—不属于重载
        //(int a,String b)和(String b,int a)—属于重载
        //方法的调用是根据方法名+参数列表来调用的
        method();
        method(20);
        method("海绵宝宝",3);
        method(100,100);
    }
    //创建method(int i,int j)
    public static void method(int i, int j) {
        System.out.println(i+j);
    }
    //创建method(String s,int i)
    public static void method(String s, int i) {
        System.out.println(s+"今年"+i+"岁啦");
    }
   //创建method()
    public static void method() {
        System.out.println("哈哈哈哈我没有参数");
    }
    //创建method(int num)
    public static void method(int num) {
        System.out.println(num*num);
    }

第三章:数组

概念:

数组Array,标志是[ ] ,用于储存多个相同类型数据的集合
想要获取数组中的元素值,可以通过脚标(下标)来获取
数组下标是从0开始的,下标的最大值是数组的长度减1
在这里插入图片描述

创建数组

数组的创建方式一般分为动态初始化和静态初始化

//1. 动态初始化
        int[] a = new int[5];
//2. 静态初始化
        int[] b = new int[]{1,2,3,4,5};
        int[] c = {1,2,3,4,5};

创建数组过程分析

程序创建数组 int[] a = new int[5]; 时发生了什么?

  1. 在内存中开辟连续的空间,用来存放数据,长度是5
    
  2. 给数组完成初始化过程,给每个元素赋予默认值,int类型默认值是0
    
  3. 数组完成初始化会分配一个唯一的地址值
    
  4. 把唯一的地址值交给引用类型的变量a去保存
    

TIPS: 数组名是个引用类型的变量,它保存着的是数组的地址,不是数组中的数据

向数组中存入数据hello

在这里插入图片描述

数组的长度

数组的长度用 length属性来表示,数组一旦创建,长度不可改变,数组的长度允许为0

数组的遍历

遍历:从头到尾,依次访问数组每一个位置,获取每一个位置的元素.形式如下:

我们通过数组的下标操作数组,所以for循环变量操作的也是数组下标
开始:开始下标0  结束:结束下标length-1 如何变化:++
for(从下标为0的位置开始;下标的取值<=数组的长度-1;下标++){
    循环体;
}

冒泡排序

概念:

冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果他们的顺序(如从大到小、首字母从A到Z)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素已经排序完成。
这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。

形式:

相邻比较,从小到大排序,如果小的就往前换一 代表了从头到尾遍历循环数据
在这里插入图片描述

实现冒泡排序

//1.外层循环,控制比较的轮数,假设有n个数,最多比较n-1次
        //开始值:1 结束值:<= a.length-1 变化:++
        //控制的是循环执行的次数,比如5个数,最多比较4轮,<= a.length-1,最多取到4,也就是[1,4]4次
        for(int i = 1 ; i <= a.length-1 ; i++) {
            System.out.println("第"+i+"轮:");
            //2.内层循环:相邻比较+互换位置
            for(int j=0; j < a.length-1 ; j++) {
                //相邻比较,a[j]代表的就是前一个元素,a[j+1]代表的就是后一个元素
                if(a[j] > a[j+1]) {
                    //交换数据
                    int t = a[j];
                    a[j] = a[j+1];
                    a[j+1] = t;
                    System.out.println("第"+(j+1)+"次比较交换后:"+Arrays.toString(a));
                }
            }
            System.out.println("第"+i+"轮的结果:"+Arrays.toString(a));
        }

冒泡排序优化

//1.外层循环,控制比较的轮数,假设有n个数,最多比较n-1次
        //开始值:1 结束值:<= a.length-1 变化:++
        //控制的是循环执行的次数,比如5个数,最多比较4轮,<= a.length-1,最多取到4,也就是[1,4]4次
        for(int i = 1 ; i <= a.length-1 ; i++) {
            boolean flag = false;//优化2
            System.out.println("第"+i+"轮:");
            //2.内层循环:相邻比较+互换位置
            //结束值:a.length-i -i是因为之前轮中找到的最大值无序参与比较,i轮会产生i个最大值,所以需要
            for(int j=0; j < a.length-i ; j++) {//优化1
                //相邻比较,a[j]代表的就是前一个元素,a[j+1]代表的就是后一个元素
                if(a[j] > a[j+1]) {
                    //交换数据
                    int t = a[j];
                    a[j] = a[j+1];
                    a[j+1] = t;
                    flag = true;
                    //System.out.println("第"+(j+1)+"次比较交换后:"+Arrays.toString(a));
                }
            }
            if(flag == false) {
                return a;
            }
            System.out.println("第"+i+"轮的结果:"+Arrays.toString(a));
        }

优化的点有2个 :
第1个就是前面几轮排序产生的最大值不需要参与后面轮的比较,执行过几轮就会产生几个值不参与比较,i轮i个,所以需要-i
第2个优化的点就是我们要设置一个量,这个量用来检测在当前这一轮的相互比较中究竟有没有发生元素的互换位置,如果发生了互换,说明顺序还没排好,flag就改成true,进行下一轮比较,但是如果在当前轮,所有元素都进行了相互比较,并没有互换位置,这就说明顺序已经排好序了,无需下一轮比较,直接return结束方法即可

数组工具类Arrays

Arrays.toString(数组)
把数组里的数据,用逗号连接成一个字符串[值1,值2]

Arrays.sort(数组)
对数组进行排序,对于基本类型的数组使用的是优化后的快速排序算法,效率高
对引用类型数组,使用的是优化后的合并排序算法

Arrays.copyOf(数组,新的长度)
把数组赋值成一个指定长度的新数组
新数组的长度 大于 原数组, 相当于复制,并增加位置
新数组的长度 小于 原数组, 相当于截取一部分数据

二维数组

概念:存放数组的数组,也就是说数组里存的还是数组的数据形式。
在这里插入图片描述

创建二维数组

int[][] a = {{3,5},{7,9},{1,2}};
--创建外部数组,长度是3
--给每个外部数组的位置创建内部数组,每个内部数组的长度是2
--给每个内部数组进行数据初始化
--二维数组生成唯一的地址值
--把地址值交给引用类型变量a来保存

遍历二维数组

for (int i = 0; i < a.length; i++) {//遍历外部数组
            for (int j = 0; j < a[i].length; j++) {//遍历内部数组
                System.out.println(a[i][j]);//依次打印二维数组中每个元素的值
            }
        }

第四章:面向对象

概念:

面向对象其实是一种编程思想,通过它可以把生活中复杂的事情变得简单化,从原来的执行者变成了指挥者。
面向对象是基于面向过程而言的。

  • 面向过程强调的是过程,比如:
    1. 打开冰箱门 2. 把大象放进去 3. 关上冰箱门
  • 面向对象强调的是结果,比如:
    什么样的冰箱?什么样的大象?谁负责把大象装进去? 而不是关注那个负责的人怎么把大象装冰箱里.
    衣服脏了,直接让女盆友去处理,等着穿干净的就可以了。你不关注中间的过程,只要找好对象就可以了~
    再比如.我们想吃一道菜,无需考虑是怎么传菜,怎么做菜的,只需点菜即可.传菜和做菜都有具体的对象来帮我们完成具体的功能.我们不需要关注实现的过程,只需要关注结果就好
    这就是我们所说的面向对象的编程实现(OOP,Object Oriented Programming)
    在这里插入图片描述

面向对象的三大特征

  1. 封装性,把相关的数据封装成一个“类”组件
  2. 继承性,是子类自动共享父类属性和方法,这是类之间的一种关系
  3. 多态,增强软件的灵活性和重用性

封装

概述:
封装是隐藏对象的属性和实现细节,仅仅对外提供公共的访问方式,比如类和方法
好处:

  1. 提高安全性
    
  2. 提高重用性
    

private关键字:
是一个权限修饰符 ,可以用来修饰成员变量和成员方法.被私有化的成员只能在本类中访问

/**本类用于测试封装*/
public class Test4_Private 函数main
    public static void main(String[] args) {
       //5.创建学生类对象--通过new关键字创建学生类对象
       Student s = new Student();
       //6.初步测试Student类对象s
       System.out.println(s.name);//可以通过"."来调用s对象的name属性,查看它的值
       s.study();//可以通过"."来调用s对象的study()
       //7.给s对象的属性赋值
       s.name = "程序猿";
       s.sno = 666;
       //s.subject = "Java培优";
       //8.查看赋值后的属性值
       System.out.println(s.name);
       System.out.println(s.sno);
       //System.out.println(s.subject);
       //10.通过Student类中提供 的公共的subject属性的设置与访问方法来给subject属性赋值并查看
       s.setSubject("JavaCGB");
       System.out.println(s.getSubject());
       //eat();
       s.study();
    }
}
//1.通过class关键字创建学生类--用来描述学生这一类型--属性+行为
/***
 * 封装:通过private关键字(权限修饰符)来修饰成员变量/成员方法
 * 被修饰的成员就实现了私有化,访问权限只能在本类中访问
 */
class Student{
    //2.定义属性--成员变量
    String name;//姓名
    int sno;//学号
    //9.对成员变量进行封装
    private String subject;//科目
    /**对外提供公共的属性值设置方式*/
    public void setSubject(String s) {
       subject = s;
    }
    /**对外提供公共的属性值查看方式*/
    public String getSubject() {
       return subject;
    }
    //3.定义行为--方法
    public void study() {
       System.out.println("正在学习JAVA");
       /**我们可以在公共的方法里调用私有方法*/
       eat();
    }
    //11.封装方法
    private void eat() {
       System.out.println("干饭人 干饭魂");
    }
}

TIPS:如何封装?封装后的资源如何访问?
我们可以使用private关键字来封装成员变量与方法
如何访问私有资源?
关于成员变量:

  1. setXxx – 对外提供公共的设置值方式
  2. getXxx – 对外提供公共的获取值方式
    关于成员方法:
    把私有方法放在公共方法里供外界调用即可

继承

概念:
继承是面向对象最显著的一个特征
继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并扩展新的能力.
Java继承是会用已存在的类的定义作为基础建立新类的技术
新类的定义可以增加新的数据或者新的功能,也可以使用父类的功能,但不能选择性的继承父类(超类/基类)
这种继承使得复用以前的代码非常容易,能够大大的缩短开发的周期,降低开发费用.

特点:

  1. 使用extends关键字来表示继承关系
  2. 相当于子类把父类的功能复制了一份
  3. Java只支持单继承
  4. 继承可以传递(爷爷/儿子/孙子这样的关系)
  5. 不能继承父类的私有成员
  6. 继承多用于功能的修改,子类可以在拥有父类功能的同时,进行功能拓展
  7. 像是is a的关系
/**本类是继承的入门案例*/
public class Demo {
    //5.创建入口函数main()
    public static void main(String[] args) {
        //6.创建Cat类对象进行测试
        Cat c = new Cat();
        c.eat();
        //7.创建DingDang类对象进行测试
        DingDang d = new DingDang();
        d.eat();
        System.out.println(d.a);
        //System.out.println(d.b);//子类不能继承父类的私有资源
    }
}
//1.创建动物类Animal--爷爷类
class Animal{
    //4.定义父类中的普通方法
    public void eat() {
        System.out.println("吃啥都行~");
    }
}
//2.创建小猫类Cat--爸爸类
class Cat extends Animal{
    //8.定义属性
    int a = 10;
    private int b = 200;/**5.父类的私有资源,子类不能继承*/
}
//3.创建DingDang类--子类
class DingDang extends Cat{
}

super

可以通过这个关键字使用父类的内容,Super代表的是父类的一个引用对象
注意:在构造方法里,出现的调用位置必须是第一行

方法重写Override

  1. 继承以后,子类就拥有了父类的功能
  2. 在子类中,可以添加子类特有的功能,也可以修改父类的原有功能
  3. 子类中方法的签名与父类完全一样时,会发生覆盖/复写的现象
  4. 格式要求:方法的返回值 方法名 参数列表 要完全一致,就方法体是重写的
    TIPS: 父类的私有方法不能被重写,子类在重写父类方法时,修饰符
    子类重写父类方法时,子类修饰符要大于等于父类修饰符的权限

重载Overload 与重写Override的区别

  1. 重载: 是指在一个类中的现象,是指一个类中有很多同名的方法,但是方法的参数列表不同
  2. 重写: 是指发生了继承关系以后(两个类),子类去修改父类原有的功能,子类中有一个方法签名(返回值类型 方法名(参数列表) )和父类的一模一样
  3. 重载的意义: 是为了方便外界对方法进行调用,什么样的参数程序都可以找到对应的方法来执行,体现的是程序的灵活性
  4. 重写的意义:是在不修改源码的前提下,进行功能的修改和拓展
    (OCP原则:面向修改关闭,面向拓展开放)
  5. 重写要求方法的修饰符: 子类权限 >= 父类的权限

继承的用法

成员变量的使用
/**本类用于测试继承中成员变量的使用*/
public class Test6_Extends1 {
    //5.创建入口函数
    public static void main(String[] args) {
        //6.创建子类对象进行测试
        Son s = new Son();
        s.eat();
    }
}
//1.创建父类
class Father{
    //7.在父类中定义两个属性
    int count = 0;
    int sum = 300;
}
//2.创建子类
class Son extends Father{
    //4.2定义子类的成员变量
    int sum = 100;
    //3.创建子类的普通方法
    public void eat() {
        //4.1定义子类的局部变量
        int sum = 10;
        /**
         * 在子类中使用父类的资源sum,需要使用关键字super指定
         * super是表示父类的一个引用,可以理解成:
         * Father super = new Father();
         * */
        System.out.println(sum);//打印局部变量
        System.out.println(this.sum);//打印成员变量
        System.out.println(count);
        System.out.println(super.sum);
    }
}
成员方法的使用
/**本类用于测试继承中成员方法的使用*/
public class Test7_Extends2 {
    public static void main(String[] args) {
        //4.创建子类对象进行测试
        Son2 s = new Son2();
        s.eat();//调用重写后子类的eat()
        //6.创建父类对象进行测试
        Father2 f = new Father2();
        f.eat();//代用父类的功能
        s.study();//调用子类特有的功能
    }
}
//1.创建父类
class Father2{
    //3.创建父类的方法
    public void eat() {
        System.out.println("爸爸爱吃肉");
    }
}
//2.创建子类Son2
class Son2 extends Father2{
/**OCP原则:面向功能修改关闭,面向功能拓展开放
 * --只允许拓展,不允许修改原来的代码*/
/**重写的规则:和父类的方法签名保持一致
       [返回值类型&方法名&参数列表]
       子类的权限修饰符必须大于等于父类的权限修饰符
   void eat()一模一样,改的是方法体
       */
    //5.重写父类的eat()
    public void eat() {
        System.out.println("儿子爱吃饺子");
    }
    //7.子类还可以拥有自己特有的功能
    public void study() {
        System.out.println("来学习JAVA");
    }
}
构造方法的使用
/**本类用于测试继承中构造方法的使用*/
//总结:
//1.子类创建对象时,默认会访问父类的无参构造
//2.在构造方法的第一行,都有一条默认语句super();--代表的是调用父类的无参构造
//3.当父类没有无参构造时,可以通super调用父类的其他构造方法
/**本类用于测试继承中构造方法的使用*/
public class Test8_Extends3 {
    //5.创建程序的入口函数main
    public static void main(String[] args) {
        //6.创建子类对象进行测试
        Son3 s = new Son3();
    }
}
//1.创建父类
class Father3{
    //3.创建父类的无参与含参构造
    public Father3() {
        System.out.println("我是父类的无参构造");
    }
    public Father3(String s) {
        System.out.println("我是父类的含参构造"+s);
    }
}
//2.创建子类
class Son3 extends Father3{
    /**1.构造方法可以被继承吗?--不可以!!!
     * 语法要求天然就不符合:构造方法需要与类名同名
     * */
//  public Father3() {
//      System.out.println("我是父类的无参构造");
//  }
    //4.创建子类的无参构造
    public Son3() {
        /**2.子类的构造方法中,默认存在super()
         * 所以创建子类对象调用子类的构造方法时,默认先去调用父类的无参构造
         * 3.在子类创建对象时,会自动调用子类的无参构造,
         * 但是继承后会先去调用父类的无参构造
         * */
        super("噜噜噜");
        System.out.println("我是子类的无参构造");
    }
}

多态

概念:
多态指同一个实体同时具有多种形式
它是面向对象程序设计(OOP)的一个重要特征。
主要是指同一个对象,在不同时刻,代表的对象不一样,指的是对象的多种形态。
好处是:可以把不同的子类对象都当作父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,统一调用标准。
水果有两种形态:水果和苹果,不关心买回来的是苹果还是西瓜,只要是水果就行

class Animal{//1.定义父类Animal
     ....eat(){syso("吃啥都行")}
}
class Cat extends Animal{//2.1定义子类Cat
      ....eat(){syso("吃小鱼干")}
}
class Dog extends Animal{//2.2定义子类Dog
      ....eat(){syso("吃肉骨头")}
}
class Pig extends Animal{//2.3定义子类Pig
      ....eat(){syso("吃菜叶子")}
}
main(){
      //3.创建子类对象
      Cat c = new Cat();//小猫是小猫
      Dog d = new Dog();//小狗是小狗
      Pig p = new Pig();//小猪是小猪
      //4.创建多态对象(父类引用指向子类对象/编译看左边,运行看右边)
      Animal a1 = new Cat();//小猫是小动物
      Animal a2 = new Dog();//小狗是小动物
      Animal a3 = new Pig();//小猪是小动物
}

特点:

  1. 多态的前提1:是继承
  2. 多态的前提2:要有方法的重写
  3. 父类引用指向子类对象,如:Animal a = new Cat();
  4. 多态中,编译(保存)看左边,运行(测试)看右边
    在这里插入图片描述
    多态的好处:
  5. 多态可以让我们不用关心某个对象到底具体是什么类型,就可以使用该对象的某些方法------可以写出更加通用的代码
  6. 提高了程序的可扩展性和可维护性

多态的使用

特点:
1) 成员变量: 使用的是父类的
2) 成员方法: 由于存在重写现象,所以使用的是子类的(成员方法使用的是父类的声明,子类的实现)
3) 静态成员: 随着类的加载而加载,谁调用就返回谁的
4)如果父子类都有静态方法,使用的是父类的

/**本类用于多态中的元素测试*/
public class DuoTai {
    public static void main(String[] args) {
        //7.创建子类对象进行测试
        Dog2 d = new Dog2();
        System.out.println(d.sum);//20
        d.eat();//小狗要吃肉骨头
        d.play();//小狗爱打滚儿~~~
        //10.创建多态对象进行测试
        /**口诀1:父类引用指向子类对象*/
        /**口诀2:编译(保存)看左边,运行(测试)看右边*/
        Animal2 a = new Dog2();
        /**2.多态中,成员变量使用的都是父类的*/
        System.out.println(a.sum);//10
        /**3.多态中,成员方法使用的是父类的声明,子类的实现*/
        a.eat();
        /**4.多态中,如果父子类都有静态方法,使用的是父类的*/
        a.play();//玩啥都行
    }
}
//1.创建父类
class Animal2{
    //2.创建成员变量
    int sum = 10;
    //3.创建成员方法
    public void eat() {
        System.out.println("吃啥都行");
    }
    //8.父类中定义静态方法play()
    public static void play() {
        System.out.println("玩啥都行");
   }
}
/**1.多态的前提:继承+重写*/
//4.定义子类Dog2
class Dog2 extends Animal2{
    //5.定义子类的成员变量
    int sum = 20;
    //6.重写父类的eat()
    /**@Override 这个注解加在方法上,表示这是一个重写的方法*/
    @Override //注解--标签
    public void eat() {
        System.out.println("小狗要吃肉骨头");
    }
    //9.定义子类的静态方法play()
    //@Override --不是重写,不能加这个注解
    public static void play() {
        System.out.println("小狗爱打滚儿~~~");
    }
}

注意!!!静态资源属于类,不存在重写现象,只是两个类中有同样声明的方法而已,不属于重写

向上转型和向下转型

在JAVA中,继承是一个重要的特征,通过extends关键字,子类可以复用父类的功能,如果父类不能满足当前子类的需求,则子类可以重写父类中的方法来加以扩展。
在应用中就存在着两种转型方式,分别是:向上转型和向下转型。
比如:父类Parent,子类Child
向上转型:父类的引用指向子类对象Parent p=new Child();
说明:向上转型时,子类对象当成父类对象,只能调用父类的功能,如果子类重写了父类的方法就根据这个引用指向调用子类重写方法。
向下转型(较少):子类的引用的指向子类对象,过程中必须要采取到强制转型。
Parent p = new Child();//向上转型,此时,p是Parent类型
Child c = (Child)p;//此时,把Parent类型的p转成小类型Child

//其实,相当于创建了一个子类对象一样,可以用父类的,也可以用自己的
说明:向下转型时,是为了方便使用子类的特殊方法,也就是说当子类方法做了功能拓展,就可以直接使用子类功能。

类和对象

  1. 类:
  1. Java语言最基本单位就是类,类似于类型。
  2. 类是一类事物的抽象。
  3. 可以理解为模板或者设计图纸。
  1. 对象:
    每个对象具有三个特点:对象的状态,对象的行为和对象的标识。
  1. 对象的状态用来描述对象的基本特征。
  2. 对象的行为用来描述对象的功能。
  3. 对象的标识是指对象在内存中都有一个唯一的地址值用来和其他对象区分开来。
  4. 类是一类事物的抽象,对象是具体的实现。
  1. 类和对象的关系:
  1. 计算机语言来怎么描述现实世界中的事物的? 属性 + 行为
  2. 那怎么通过java语言来描述呢?通过类来描述一类事物,把事物的属性当做成员变量,把行为当做方法

类的创建使用

分析手机事物:
属性:颜色,尺寸,品牌,价格
功能:打电话,发短信,听音乐
类:手机类,抽取相同的属性和行为
对象:可以按照模板生产很多个手机,比如1号手机对象,包含特有的成员变量和方法
通过class关键字创建类,通过new关键字创建对象。

//在一个java文件中可以写多个class,但是被public修饰的只能有一个,而且这个类的名字就是文件名
public class Test3_ClassExec {
    public static void main(String[] args) {
       //2.在main()中通过new关键字来创建对应类的对象
       Phone p = new Phone();
       //3.通过.来完成对象功能的调用
       p.call();
       p.message();
       p.learn();
       //4.通过.来查看对象的属性值
       System.out.println(p.brand);
       System.out.println(p.price);
       System.out.println(p.size);
       System.out.println(p.color);
    }
}
//1.通过class关键字创建手机类--用来描述手机这一类事物--特征+行为
//类是一类事物的抽象,只抽象的规定这一类事物的特征和行为
class Phone{
    //特征(属性)--类的成员变量来描述--位置:类里方法外
    String brand;//品牌
    double price;//价格
    double size;//尺寸
    String color;//颜色
    //行为(功能)--类的方法来描述--修饰符 返回值类型 方法名(参数列表){方法体}
    public void call() {
       System.out.println("正在打电话");
    }
    public void message() {
       System.out.println("正在发短信");
    }
    public void learn() {
       System.out.println("正在看直播");
    }
}

对象在内存中的存储

创建对象的流程

Person p = new Person();//短短这行代码发生了很多事情

  1. 把Person.class文件加载进内存
    
  2. 在栈内存中,开辟空间,存放引用变量p
    
  3. 在堆内存中,开辟空间,存放Person对象
    
  4. 对成员变量进行默认的初始化
    
  5. 对成员变量进行显示初始化
    
  6. 执行构造方法(如果有构造代码块,就先执行构造代码块再执行构造方法)
    
  7. 堆内存完成
    
  8. 把堆内存的地址值赋值给变量p ,p就是一个引用变量,引用了Person对象的地址值
    

Java把内存分成5大区域,我们重点关注栈和堆。

  1. 一般来讲局部变量存在栈中,方法执行完毕内存就被释放
  2. 对象(new出来的东西)存在堆中,对象不再被使用时,内存才会被释放
  3. 每个堆内存的元素都有地址值
  4. 对象中的属性都是有默认值的
    TIPS: 栈与队列指的是一种数据的结构。
    栈:先进后出(FILO – First In Last Out)
    队列:先进先出(FIFO – First In First Out)

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

创建多个对象

在这里插入图片描述
p2.brand = “HUAWEI”;
就是先到栈内存中找到p2中保存的唯一的地址值
然后根据地址值找到Phone对象,并对其对应的属性值进行修改

p2.learn();
也是先到栈内存中找到p2中保存的唯一的地址值
然后根据地址值找到Phone对象,执行Phone对象的lean()方法

匿名对象

没有名字的对象,是对象的简化表示形式。
使用场景:
当被调用的对象只调用一次时(多次会创建多个对象浪费内存)

Demo d = new Demo();
d.sleep();
d.game();
//这个d就是对象的名字。
//也可以写成:
new Demo().show();//创建了一个对象调方法
new Demo().game();//又创建了一个对象调方法

访问控制符

用来控制一个类,或者类中的成员的访问范围。
在这里插入图片描述
TIPS:default是表示不写修饰符,默认,如果写default单词来修饰会报错

构造方法

概念:

构造方法是一种特殊的方法,它是一个与类同名且没有返回值类型的方法
对象创建就是通过构造方法完成的,主要功能是完成对象的创建或者对象的初始化
当类创建对象(实例化)时,会自动调用构造方法
构造方法与普通方法一样也可以重载.

形式:

   与类同名,且没有返回值类型,可以含参也可以不含参

修饰符 方法名([参数列表]){ 注意:方法名与类名一样
代码……
}

TIPS:关于构造函数怎么记忆
特点:方法名与类名相同,且没有返回值类型
执行时机:创建对象时立即执行
默认会创建无参构造,但是,如果自定了含参构造,默认的无参构造会被覆盖,注意要手动添加哦

构造代码块与局部代码块

构造代码块的特点

  1. 位置: 在类的内部,在方法的外部
  2. 作用: 用于抽取构造方法中的共性代码
  3. 执行时机: 每次调用构造方法前都会调用构造代码块
  4. 注意事项: 构造代码块优先于构造方法加载

局部代码块

  1. 位置: 在方法里面的代码块
  2. 作用: 通常用于控制变量的作用范围,出了花括号就失效
  3. 注意事项: 变量的作用范围越小越好,成员变量会存在线程安全的问题

代码块的加载顺序

/**
 * 总结:
 * 1.当创建对象时,程序会自动调动构造方法,但是如果有构造代码块,会先去执行构造代码块,再执行构造方法
 * 2.当通过对象调用方法时,会执行方法中的功能,如果方法中有局部代码块,就会执行局部代码块
 * 3.执行顺序: 构造代码块-->构造方法-->局部代码块[前提:调用方法(如果方法有局部代码块,局部代码块才会执行)]
 * 4.构造代码块的功能: 用于提取构造方法中的共性
 * 5.局部代码块的功能:控制变量的作用范围
 * */
/**本类用来测试构造代码块与局部代码块*/
public class Test3_Block {
    //5.创建main并创建对象测试
    public static void main(String[] args) {
        /**每次创建对象时,都会调用对应的构造方法*/
        Teacher t = new Teacher();//通过调用无参构造创建对象
        Teacher t2 = new Teacher("12312","JAVA");//通过调用全参构造创建对象
        System.out.println(t2.name);
        System.out.println(t2.subject);
        t.teach();
        t.study();
    }
}
//1.定义Teacher类用于测试
class Teacher{
    //2.定义属性--成员变量--全局生效
    String name;//名字
    String subject;//科目
    //7.构造代码块
    //1)位置:类里方法外
    //2)作用:用来提取构造方法的共性功能
    //3)执行时机:在调用构造方法之前调用
    {
        subject = "JAVA";
       System.out.println("哈哈哈哈要调用构造方法啦");
    }
    //构造方法的定义:修饰符 方法名(参数列表){方法体}
    //4.1创建无参构造方法
    public Teacher() {
        System.out.println("无参构造"+subject);
    }
    //4.2创建全参构造方法
    public Teacher(String n,String s) {
        name = n;
        subject = s;
        System.out.println("全参构造"+subject);
    }
    //3.定义普通方法
    //方法的定义:修饰符 返回值类型 方法名(参数列表){方法体}
    public void study() {//备课方法
        //6.创建局部代码块
        //1)位置:方法里
        //2)作用:用来控制变量的作用范围(作用范围越小越好,因为越小越安全)
        //3)执行时机:调用本方法时
        {
            int i =10;
            System.out.println(i);
        }
        System.out.println("正在备课ing");
    }
    public void teach() {//上课方法
        System.out.println("正在上课ing");
    }
}

this

概念:
this 代表本类对象的一个引用对象
形式:
this.name = name ;

/**
 * this可以用来进行构造方法之间的调用\
 * 但是注意!!!!!调用是单向的,不是双向来回调用,会死循环
 * */
//1.创建类Dog
class Dog{
    //2.定义属性name
    String name;
    //3.提供无参构造
    public Dog() {
        /**在无参构造中调用含参构造的功能*/
        /**规定:this关键字必须在构造方法中的第一行*/
        this("拉拉");
        System.out.println("无参构造");
    }
    //4.提供含参构造
    public Dog(String s) {
        /**在含参构造中调用无参构造的功能*/
        /**规定:this关键字必须在构造方法中的第一行*/
        //this();
        System.out.println("含参构造"+s);
    }
}

this与super的区别

  1. this代表本类对象的引用
    class Father3{ this.XXX } //this – Father3 this = new Father3();
  2. super代表父类对象的引用
    class Father3{ super.XXX } //this – Father3 super = new Father3();
    就相当于创建了一个父类对象
  3. this可以在两个变量名相同时,用于区分成员变量和局部变量
  4. this 可以在本类的构造方法之间调用,位置必须是第一条语句,注意,不能相互调用,会死循环
  5. super是发生了继承关系以后,子类如果想用父类的功能,可以通过super调用
  6. 如果发生了重写,还想用父类的功能,需要使用super来调用
  7. Super在调用父类构造方法时,必须出现在子类构造方法的第一条语句,而且如果父类中没有提供无参构造,子类可以通过super来调用父类其他的含参构造

static

概念:
是java中的一个关键字,用于修饰成员(成员变量和成员方法)

特点:

  1. 可以修饰成员变量与成员方法
  2. 随着类的加载而加载,优先于对象加载
  3. 只加载一次,就会一直存在,不再开辟新空间, 直到类消失才一起消失
  4. 静态资源也叫做类资源,全局唯一,被全局所有对象共享
  5. 可以直接被类名调用
  6. 静态只能调用静态,非静态可以随意调用
  7. static不能和this或者super共用,因为有static时可能还没有对象
/**本类用于测试静态的入门案例*/
public class Test_Static1 {
    //4.创建程序的入口函数main()
    public static void main(String[] args) {
        /**3.静态资源可以通过类名直接调用*/
        /**4.静态资源是优先于对象进行加载的,随着类的加载而加载
         * 比对象先加载进入内存,没对象时也可以被类名直接调用
         * */
        //9.通过类名直接调用静态资源进行测试
        System.out.println(Student.name);
        Student.study();
        //5.创建Student类对象进行测试
        Student s = new Student();
        //6.更改并查看name属性的值
        s.name = "黄桃罐头";
        System.out.println(s.name);
        //7.调用学生类的方法
        s.study();
        s.speak();
        //8.创建多个对象进行测试
        Student s2 = new Student();
        /**5.静态全局共享*/
        System.out.println(s2.name);
        System.out.println(Student.name);
    }
}
//1.创建学生类
class Student{
    /**1.可以通过static关键字将普通资源修饰成静态资源*/
    /**2.static可以用来修饰成员变量和方法,一般写在权限修饰符之后*/
    //2.定义属性--成员变量--无需手动初始化
    int sno;
    static String name;
    //3.定义方法--修饰符 返回值类型 方法名(参数列表){方法体}
    public static void study() {
        System.out.println("别闹~学JAVA呢~");
    }
    public void speak() {
        System.out.println("大声说出来~");
    }
}

在这里插入图片描述

static静态调用关系

1.静态资源只能调用静态资源
2.非静态资源既可以调用静态资源,也可以调用非静态资源

静态代码块、构造代码块、局部代码块

静态代码块格式:
static {}
静态资源随着类的加载而加载,并且只被加载一次,一般用于项目的初始化
特点: 被static修饰,位置在类里方法外

三种代码块的比较

  1. 静态代码块:在类加载时就加载,并且只被加载一次,一般用于项目的初始化
  2. 构造代码块:在创建对象时会自动调用,每次创建对象都会被调用,提取构造共性
  3. 局部代码块:方法里的代码块,限制局部变量的范围

几种代码块的关系:
1.代码之间的执行顺序:
静态代码块–>构造代码块–>构造方法–>局部代码块
2.为什么是这样的顺序呢?
静态代码块要优先于对象进行加载
是随着类的加载而加载到内存中的
只加载一次,并且一直存在,直到类消失,它才会消失
3.每个元素的作用:
1)静态代码块:专门用来完成一些需要第一时间加载并且只加载一次的资源
2)构造代码块:创建对象时才会触发,用来提取构造方法中的共性内容
3)构造方法:创建对象时调用,用来创建对象,在构造代码块执行后执行
4)局部代码块:调用所在的方法时才会调用,用来控制变量的作用范围

静态变量和实例变量的区别

在语法定义上的区别:静态变量前要加static关键字,而实例变量前则不加。
在程序运行时的区别:实例变量属于某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。静态变量不属于某个实例对象,而是属于类,所以也称为类变量,只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。总之,实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接使用类名来引用。

final

概念:

  1. 是java提供的一个关键字
  2. final是最终的意思
  3. final可以修饰类,方法,成员变量
    初衷:java出现继承后,子类可以更改父类的功能,当父类功能不许子类改变时,可以利用final关键字修饰父类。

特点:

  1. 被final修饰的类,不能被继承
  2. 被final修饰的方法,不能被重写
  3. 被final修饰的变量是个常量,值不能被改变
  4. 常量的定义形式:final 数据类型 常量名 = 值
/**本类用于测试final的入门案例*/
public class Demo {
    public static void main(String[] args) {
        //5.创建子类对象进行测试
        Son4 s = new Son4();
        s.work();
//      s.name = "干饭人";//报错:常量的值不可以被修改
    }
}
/**1.final表示最终,可以用来修饰类
 * 但是被final修饰的类无法被继承
 * 也就是没有子类,它自己就是最终类
 * */
//1.创建父类
//final class Father4{//报错:Son4类不能是final修饰的Father4的子类
class Father4{
    //3.创建成员变量并赋值
    /**2.final可以用来修饰成员变量,被final修饰的变量值不可以被修改--常量*/
    final String name = "打工人";
    //4.创建成员方法
    /**3.final可以用来修饰成员方法,但是被final修饰的方法是最终实现,子类无法重写*/
    final public void work() {
        System.out.println("Father4...打工人,打工魂");
    }
}
//2.创建子类
class Son4 extends Father4{
    //子类重写父类的work方法   //父类方法为final  报错
//  public void work() {
//      System.out.println("Son4...打工人要用计算机打工~");
//  }
}

抽象类

概念:

Java中可以定义被abstract关键字修饰的方法,这种方法只有声明,没有方法体,叫做抽象方法.
Java中可以定义被abstract关键字修饰的类,被abstract关键字修饰的类叫做抽象类
如果一个类含有抽象方法,那么它一定是抽象类
抽象类中的方法实现交给子类来完成

抽象方法的格式:

权限修饰符 abstract 返回值类型 方法名(参数列表);

特点:

  1. abstract 可以修饰方法或者类
  2. 被abstarct修饰的类叫做抽象类,被abstract修饰的方法叫做抽象方法
  3. 抽象类中可以没有抽象方法
  4. 如果类中有抽象方法,那么该类必须定义为一个抽象类
  5. 子类继承了抽象类以后,要么还是一个抽象类,要么就把父类的所有抽象方法都重写
  6. 多用于多态中
  7. 抽象类不可以被实例化

抽象类中的构造函数通常在子类对象实例化时使用

/**本类用于测试抽象类中的成员*/
public class Test4_Abstract3 {
    public static void main(String[] args) {
        //7.创建多态对象进行测试
        Fruit f = new Apple();
        System.out.println(f.sum);//10
        //f.name = "lemon";//常量的值不可以被修改The final field Fruit.name can not be assigned
        System.out.println(f.name);
        f.eat();//父类的功能
        f.eat2();//子类实现的功能
        f.clean();//子类实现的功能
    }
}
//1.创建抽象父类--水果类
abstract class Fruit{
    /**1.抽象类中可以有成员变量吗?--可以!!!*/
    //3.1创建抽象父类的成员变量
    int sum = 10;
    /**2.抽象类中可以有成员常量吗?--可以!!!*/
    //3.2创建抽象父类的成员常量,注意初始化
    final String name ="banana";
    /**3.抽象类中可以有普通方法吗?--可以!!!
     * 抽象类中可以都是普通方法吗?--可以!!!
     * */
    /**4.如果一个类中都是普通方法,为啥还要被声明成抽象类呢?
     * 原因:抽象类不可以创建对象
     * 如果不想让外界创建本类对象,可以把普通类声明成抽象类
     * */
    //4.创建抽象父类的普通方法
    public void eat() {
        System.out.println("吃啥水果都行");
    }
    //5.创建抽象父类中的抽象方法
    /**5.抽象类中可以有抽象方法
     * 一旦类中有抽象方法,这个类必须被声明成一个抽象类*/
    public abstract void eat2() ;
    public abstract void clean();
}
/**
 * 6.当一个类继承了父类,并且父类是抽象父类时
 * 子类需要重写(实现)父类中的所有抽象方法或者把自己变成抽象子类
 * */
//2.创建子类--苹果类
//6.1解决方案一:把自己变成一个抽象子类
//abstract class Apple extends Fruit{
//6.2解决方案二:重写抽象父类中的所有抽象方法
class Apple extends Fruit{
    @Override
    public void eat2() {
        System.out.println("水果中最爱吃苹果");
    }
    @Override //注解,相当于标记,标记了这个方法是一个重写的方法
    public void clean() {
        System.out.println("苹果还是要好好洗洗再吃的");
    }
}

abstract注意事项

抽象方法要求子类继承后必须重写。
那么,abstract关键字不可以和哪些关键字一起使用呢?以下关键字,在抽象类中。用是可以用的,只是没有意义了。

  1. private:被私有化后,子类无法重写,与abstract相违背。
  2. static:静态优先于对象存在。而abstract是对象间的关系,存在加载顺序问题。
  3. final:被final修饰后,无法重写,与abstract相违背。

拓展:

递归案例:

//需求:求用户输入数字的阶乘结果
//f(int n)--用来求阶乘
//规律:
//f(n)= n*f(n-1)
//f(5)= 5*4*3*2*1 = 5*f(4)
//f(4)= 4*3*2*1 = 4*f(3)
//f(3)= 3*2*1 = 3*f(2)
//f(2)= 2*1 = 2*f(1)
//f(1)= 1
//
//5!=5*4*3*2*1=120
//4!=4*3*2*1
//3!=3*2*1
//2!=2*1
//1!=1

在这里插入图片描述

接口

概念:

Java里面由于不允许多重继承,所以如果要实现多个类的功能,则可以通过实现多个接口来实现,Java接口和Java抽象类代表的就是抽象类型,就是我们需要提出的抽象层的具体表现
OOP面向对象编程,如果要提高程序的复用率,增加程序的可维护性,可扩展性,就必须是面向接口编程,面向抽象的变成,正确的使用接口/抽象类这些抽象类型作为java结构层次上的顶层.

接口格式:

 interface 接口名{ 代码… }

接口的特点:

  1. 接口中都是抽象方法
  2. 通过interface关键字来定义接口
  3. 通过implements让子类来实现接口
  4. 可以理解成,接口是一个特殊的抽象类(接口里的方法都是抽象方法)
    .在JDK1.8以后不仅仅只有常量和抽象方法,还有默认方法和静态方法
    默认方法用来直接给子类使用,如果子类想重写也可以自行根据需求重写,不会强制重写
    静态方法用来直接通过接口名访问
  5. 接口突破了java单继承的局限性
  6. 接口和类之间可以多实现,接口与接口之间可以多继承
  7. 接口是对外暴露的规则,是一套开发规范
  8. 接口提高了程序的功能拓展,降低了耦合性
    9)常量: 默认使用 public static final 方法: 默认使用 public abstract修饰
/**本接口是创建接口测试*/
/**1.通过interface关键字来定义接口*/
public interface Inter {
      /**2.接口中可以有普通方法吗?--不可以!!*/
      //public void eat() {}
      /**3.接口中可以有抽象方法吗?--可以,接口中的方法都是抽象方法!!!*/
      public abstract void eat();
      public abstract void play();
}
/**本类作为Inter接口的实现类*/
/**1.实现类如果想用接口的功能,要和接口建立实现关系,通过关键字implements来实现*/
/**2.1 方案一:如果实现类与接口建立关系以后,可以选择不实现接口中的抽象方法,而是把自己变成一个抽象子类*/
//abstract public class InterImpl implements Inter{
/**2.2 方案二:如果实现类实现了接口以后,可以重写接口中的所有抽象方法*/
public class InterImpl implements Inter{
      @Override //作为标记,表示实现了父接口的抽象方法
      public void eat() {
           System.out.println("吃火锅");
      }
      @Override//作为标记,表示实现了父接口的抽象方法
      public void play() {
           System.out.println("玩代码");
      }
}
/**本类用于测试接口的实现类*/
public class InterTests {
    //6.创建入口函数main()
    public static void main(String[] args) {
        //7.测试接口创建对象
        /**接口可以创建对象吗?--不可以!!!*/
        //Inter i = new Inter();
        //8.创建多态对象进行测试
        Inter i = new InterImpl();
        //9.通过对象调用方法测试
        i.eat();
        i.play();
        //10.创建子类对象并进行测试
        InterImpl i2 = new InterImpl();
        i2.eat();
        i2.play();
    }
}

接口的用法

/**本类用于进一步测试接口的使用*/
public class UserInter {
     //5.创建入口函数main()
    public static void main(String[] args) {
        //6.创建多态对象进行测试
        /**问题:子类创建对象时,默认会调用父类的构造方法
         * 目前接口实现类的父级是一个接口,而接口没有构造方法
         * 那实现类构造方法中的super()调用的是谁呢?
         * 结论1:如果一个类没有明确指定父类,那么默认继承顶级父类Object
         * 所以super()会自动调用Object类中的无参构造
         * */
        /**查看类的继承结构:Ctrl+T*/
        Inter2 i = new Inter2Impl();
        /**结论2:接口中的变量实际上都是静态常量,可以通过类名直接调用*/
        System.out.println(Inter2.age);
        /**结论3:接口中的变量实际上都是静态常量,值不可以被修改*/
        //Inter2.age = 200;
    }
}
//1.创建接口
interface Inter2{
    /**1.接口中有构造方法吗?--不可以!!!*/
    //2.测试接口是否可以有构造方法
//  public Inter2() {}
    /**2.接口里可以有成员变量吗?--没有!!!
     * 是一个静态常量,实际上的写法是public static final int age = 10;
     * 只不过在接口中可以省略不写
     * */
    int age  = 10;
     /**3.接口中可以有抽象方法吗?--可以!!!*/
      abstract public void eat2();
      void eat();//可以简写--会自动拼接public abstarct
}
//3.创建接口的实现类
//class Inter2Impl extends Object implements Inter2{
class Inter2Impl implements Inter2{
    //4.创建实现类的构造函数
    public Inter2Impl() {
        super();//默认先调用顶级父类Object的无参构造方法
        System.out.println("我是Inter2Impl的无参构造");
    }
     /**4.如果接口中添加了抽象方法,实现类中需要实现所有未实现的抽象方法*/
      @Override
      public void eat2() {
      }
      @Override
      public void eat() {
      }
}

总结:
接口里是没有构造方法的。在创建实现类的对象时默认的super(),是调用的默认Object的无参构造。
接口里没有成员变量,都是常量。所以,你定义一个变量没有写修饰符时,默认会加上:public static final
接口里的方法,默认就都是抽象的,如果你不写明是abstract的,那会自动补齐。例如:public abstract void save

接口的多继承多实现

public class Test4 {
    //5.创建入口函数
    public static void main(String[] args) {
        //6.创建实现类对象进行测试
        Inter3Impl i3 = new Inter3Impl();
        i3.update();
        //7.创建多态对象进行测试
        Inter3 i4 = new Inter3Impl();
        i4.find();
    }
}
//1.创建接口1
interface Inter1{
    void save();//保存功能
    void delete();//删除功能
}
//2.创建接口2
interface Inter2{
    void update();//更新功能
    void find();//查询功能
}
//3.创建接口3用来测试接口与接口的继承关系
/**1.接口之间可以建立继承关系,而且还可以多继承
 * 接口与接口之间用逗号隔开
 * */
interface Inter3 extends Inter1,Inter2{
}
//4.创建Inter3接口的实现类并添加未实现的方法
/**2.接口和实现类之间可以建立实现关系,通过implments关键字来完成
 * 注意,java类是单继承,而接口不限,写接口时,我们一般先继承再实现
 * */
class Inter3Impl implements Inter3{
    @Override
    public void save() {
        System.out.println("稍等...正在保存中...");
    }
    @Override
    public void delete() {
        System.out.println("稍等...正在删除中....");
    }
    @Override
    public void update() {
        System.out.println("客官,马上就更新好啦~~");
    }
    @Override
    public void find() {
        System.out.println("小二正在马不停蹄的查询~~~");
    }
}

总结

1.类与类的关系

–继承关系,只支持单继承
–比如,A是子类 B是父类,A具备B所有的功能(除了父类的私有资源和构造方法)
–子类如果要修改原有功能,需要重写(方法签名与父类一致 + 权限修饰符>=父类修饰符)

2.类和接口的关系
–实现关系.可以单实现,也可以多实现
–class A implements B,C{}
–其中A是实现类,B和C是接口,A拥有BC接口的所有功能,只是需要进行方法的重写,否则A就是抽象类

3.接口与接口的关系
–是继承关系,可以单继承,也可以多继承
–interface A extends B,C{}
–其中ABC都是接口,A是子接口,具有BC接口的所有功能(抽象方法)
–class X implements A{}
–X实现类需要重写ABC接口的所有方法,否则就是抽象类
–class A extends B implements C,D{}
–其中A是实现类,也是B的子类,同时拥有CD接口的所有功能
–这时A需要重写CD接口里的所有抽象方法

4.抽象类与接口的区别

  1. 抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。
  2. 抽象类要被子类继承,接口要被子类实现。
  3. 接口只能做方法声明,抽象类中可以做方法声明,也可以做方法实现
  4. 接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
  5. 抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。
  6. 抽象方法只能声明,不能实现,接口是设计的结果 ,抽象类是重构的结果
  7. 抽象类里可以没有抽象方法
  8. 如果一个类里有抽象方法,那么这个类只能是抽象类
  9. 抽象方法要被实现,所以不能是静态的,也不能是私有的。
  10. 接口可继承接口,并可多继承接口,但类只能单继承。

拓展

类:
对事物/算法/逻辑/概念等等的抽象,可以把它理解成”模板/图纸”
封装:相关的数据/运算代码封装成一个”类”组件

对象:
从”类”出发创建具体的”实例”
每个对象,占用独立的内存空间,保存各自的属性数据
可以独立控制一个对象,来执行指定的方法代码

软件设计的开闭原则OCP:

开放功能扩展,关闭源码修改。等
开闭原则的英文全称是Open Close Principle,缩写是OCP,它是Java世界里最基础的设计原则,它指导我们如何建立一个稳定的、灵活的系统。
开闭原则的定义是:软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是对于修改是封闭的。
开闭原则,是一种设计模式,随着面向对象程序设计的思想,应运而生。
开,指的是可以在源代码的基础上进行扩展,比如继承,接口,抽象类等。在JAVA中,之所以用继承,是在可以直接调用类库的前提下,对其功能进行扩展。不需要应用者去了解封装类的内部逻辑就可以做开发。
闭:指不允许对原有的代码进行修改。以免影响其他现有功能,造成功能瘫痪。

第五章:异常

概述: 用来封装错误信息的对象

异常的继承结构:

   Throwable : 顶级父类
           --Error : 系统错误,无法修复
           --Exception : 可以修复的错误
                 -- RunTimeException
                       -- ClassCastException
                       -- ClassNotFoundException
                       -- ...

异常处理

当程序中遇到了异常,通常有两种处理方式:捕获或者向上抛出
当一个方法抛出异常,调用位置可以不做处理继续向上抛出,也可以捕获处理异常

  1. 捕获方式:
    try{
    需要捕获的代码
    }catch(异常类型 异常名){
    处理方案
    }
  2. 抛出方式:
    在会发生异常的方法上添加代码:throws 异常类型
    例如:public static void main(String[] args) throws Exception{
/**本类用于测试异常的入门案例*/
/**
 * 总结8:
 * 如果方法抛出异常,那么谁调用,谁需要解决(继续抛出/捕获解决)
 * 所以main()调用了method2(),如果main()不捕获处理,就也需要抛出异常
 * 注意:我们一般在main()调用之前捕获解决异常,而不是把问题抛给main()
 * 因为没人解决了
 * */
public class ExceptionDemo {
    //1.创建入口函数main()
    public static void main(String[] args) throws Exception {
        //method();//调用method()--暴露异常
        //method1();//调用method1()--异常捕获
        method2();//调用method2()--异常抛出
    }
    //4.创建method2(),练习BUG解决方案二
    /**抛出的语法规则:
     * 在可能会出现异常的方法上加 throws 异常类型
     * 在抛出时,也可以使用多态,不管是发生了什么异常,通通被Exception抛出去
     * */
    //  public static void method2() throws InputMismatchException
    //  ,ArithmeticException,Exception{
    public static void method2() throws Exception{
        //4.复写刚刚可能会发生异常的代码
        System.out.println("请输入您要计算的第一个数:");
        int a = new Scanner(System.in).nextInt();
        System.out.println("请输入您要计算的第二个数:");
        int b = new Scanner(System.in).nextInt();
        System.out.println( a / b );
    }
    /**捕获的语法规则:
     * try{
     *      可能会出现异常的代码
     * }catch(异常类型 异常参数名){
     *      如果捕获到异常的对应解决方案
     * }
     * */
    //3.创建method1(),练习BUG解决方案一
    public static void method1() {//11:15继续~,代码已上传至code网站
        //3.1按照学习的捕获语法编写try-catch结构
        /**总结4:try中放着的是可能会出现异常的代码*/
        try {
            //3.2复写刚刚可能会发生异常的代码
            System.out.println("请输入您要计算的第一个数:");
            int a = new Scanner(System.in).nextInt();
            System.out.println("请输入您要计算的第二个数:");
            int b = new Scanner(System.in).nextInt();
            System.out.println( a / b );
            /**总结5:如果发生异常被捕获,就执行此处被匹配到的解决方案*/
        }catch(InputMismatchException e) {//3.3异常捕获1
            System.out.println("输入的类型不正确,请输入正确的整数类型");
            /**总结6:由于程序中可能出现多种异常,所以catch可以配合多次使用*/
        }catch(ArithmeticException e) {//3.4异常捕获2
            System.out.println("除数不能为0!!");
            /**总结7:如果还有上述两种异常之外的异常,用Exception匹配捕获
             * 这个就是多态最为经典的一种用法,我们不关心子类的子类
             * 只要是可解决的异常,都是Exception的子类,多态会把这些异常当做父类型来看
             * 进而捕获,使用通用异常解决方案来解决
             * */
        }catch(Exception e) {
            System.out.println("请输入正确的整数~");
        }
    }
    //2.创建method()用来人为的暴露异常
    public static void method() {
        //2.1提示并接收用户输入的两个整数
        System.out.println("请输入您要计算的第一个数:");
        int a = new Scanner(System.in).nextInt();
        System.out.println("请输入您要计算的第二个数:");
        int b = new Scanner(System.in).nextInt();
        //2.2 输出除法运算的结果
        //输入8和2.2,报错:InputMismatchException[输入不匹配异常]
        //输入9和0,报错:ArithmeticException: / by zero[算数异常,除数不能为0]
        System.out.println( a / b );
        /**1.不要害怕BUG,真正的勇士敢于直面自己写的BUG*/
        /**2.学会看报错信息的错误提示,确定自己错误的方向*/
        /**3.学会看报错信息的行号提示,哪里报错点哪里
         * 源码不会错,要看的是自己写的代码*/
    }
}

第六章 :常用API

概念:

API(Application Programming Interface,应用程序接口)是一些预先定义的函数。目的是提供应用程序与开发人员基于某软件可以访问的一些功能集,但又无需访问源码或理解内部工作机制的细节.
API是一种通用功能集,有时公司会将API作为其公共开放系统,也就是公司制定自己的系统接口标准,当需要进行系统整合,自定义和程序应用等操作时,公司所有成员都可以通过该接口标准调用源代码.

Java.util包是java中的工具包,包含各种实用工具类/集合类/日期时间工具等各种常用工具包
import java.util.Scanner;
import java.util.Arrays;

java.lang包是java的核心,包含了java基础类,包括基本Object/Class/String/基本数学类等最基本的类
这个包无需导入,默认会自动导入
import java.lang.Object;
import java.lang.String;
import java.lang.StringBuilder/StringBuffer;
正则表达式
包装类等等

Object

概念:

Object类是所有Java类的祖先,也就是说我们所说的”顶级父类”
存在于java.lang.Object,这个包不需要我们手动导包
每个类都使用Object作为超类.所有对象(包括数组)都实现这个类的方法.在不明确给出超类的情况下,Java会自动把Object类作为要定义类的超类.

常用方法介绍:

toString()

本方法用于返回对应对象的字符串表示.

hashCode()

本方法用于返回对应对象的哈希码值
TIPS:哈希码是一种算法,使不同的对象有不同的哈希码值,但是也有相同的情况,我们称之为”哈希碰撞”

equals()

本方法用于指示其他某个对象是否与当前对象”相等”

String

特点:

String是一个封装char[]数组的对象,字符串不可变
通过下图中的底层实现可以看出:被final修饰,是常量
String str = “abc”; 等效于:char data[] = {‘a’, ‘b’, ‘c’};

创建String对象的方式

方式一:
String(char[] value) 分配一个新的 String,使其表示字符数组参数中当前包含的字符序列。
方式二: String str = “abc”;

  1. 如果是第一次使用字符串,java会在字符串常量池创建一个对象。
  2. 再次使用相同的内容时,会直接访问常量池中存在的对象。

常见方法

length()-查看字符串的长度
charAt()—定位某个字符,返回它的位置
lastIndexOf()-某个字符最后一次出现的位置
substring()-截取子串,如果参数有两个左闭右开[1,5)
equals()-判断两个串是否相等,注意String重写了Object的此方法,所以内容相同就返回true
startsWith()-判断是不是以参数开头
endsWith()--判断是不是以参数结尾
split()—以指定字符分割
trim()-去掉首尾两端的空格
getBytes()-把串转换成数组
toUpperCase()-变成全大写
toLowerCase()-变成全小写
String.valueOf(10)-int类型的10转换成String类型

StringBuilder/StringBuffer

特点:

  1. 封装了char[]数组
  2. 是可变的字符序列
  3. 提供了一组可以对字符内容修改的方法
  4. 常用append()来代替字符串做字符串连接”+”
  5. 内部字符数组默认初始容量是16:super(str.length() + 16);
  6. 如果大于16会尝试将扩容,新数组大小原来的变成2倍+2,容量如果还不够,直接扩充到需要的容量大小。int newCapacity = value.length * 2 + 2;
  7. StringBuffer 线程安全,StringBuilder线程不安全

StringBuilder和StringBuffer的区别

1.在线程安全上 :
–StringBuffer是旧版本就提供的,线程安全的。@since JDK1.0
–StringBuilder是jdk1.5后产生,线程不安全的。@since 1.5
2. 在执行效率上,StringBuilder > StringBuffer > String
3.源码体现:本质上都是在调用父类抽象类AbstractStringBuilder来干活,只不过Buffer把代码加了同步关键字,使得程序可以保证线程安全问题。
abstract class AbstractStringBuilder implements Appendable, CharSequence {

正则表达式Regex

概述:

正确的字符串格式规则。
常用来判断用户输入的内容是否符合格式的要求,注意是严格区分大小写的。

String提供了支持正则表达式的方法

Matches(正则) : 当前字符串能否匹配正则表达式
replaceAll(正则,子串) : 替换子串
split(正则) : 拆分字符串
/**本类用于测试正则表达式
 * 需求:测试输入身份证号,测试用户输入是否正确
 * */
public class Test4_Regex {
    public static void main(String[] args) {
        //1.提示并接收用户输入的身份证号:
        System.out.println("请您输入您的身份证号:");
        String input = new Scanner(System.in).nextLine();
        //2.编辑正则表达式
        //身份证号的规律:一般都是18位,前17位都是数字,最后一位可能是数字,也有可能是xX
        String regex = "[0-9]{17}[0-9xX]";
        //3.判断,是否符合正则表达式的规则(也就是输入的是正确的身份证号吗?)
        if( input.matches(regex) ) {//matches()是String类提供的功能,可以用来判断字符串是否符合正则表达式的要求
            System.out.println("输入正确!");
        }else {
            System.out.println("输入不正确,请重新输入!");
        }
    }
}

BigDecimal/BigInteger

概述:

BigDecimal:常用来解决精确的浮点数运算
BigInteger: 常用来解决超大的整数运算

创建对象:

BigDecimal(double val)
将double转换为BigDecimal,后者是double的二进制浮点值十进制表示形式,有坑!
BigDecimal(String val)
将String类型字符串的形式转换为BigDecimal

常用方法

Add(BigDecimal bd) : 做加法运算
Subtract(BigDecimal bd) : 做减法运算
Multiply(BigDecimal bd) : 做乘法运算
Divide(BigDecimal bd) : 做除法运算,除不尽时会抛异常
Divide(BigDecimal bd,保留位数,舍入方式) : 除不尽时使用
setScale(保留位数,舍入方式) : 同上
pow(int n) : 求数据的几次幂

 public static void main(String[] args) {
      method2();//使用BigDecimal来解决浮点数运算不精确的现象
   }
   public static void method2() {
      //1.提示并接收用户输入的两个小数
      System.out.println("请输入您要计算的两个小数:");
      double a = new Scanner(System.in).nextDouble();
      double b = new Scanner(System.in).nextDouble();
      //2.创建工具类对象,把基本类型的a和b交给工具类对象BigDecimal来保存
      /**1.最好不要使用double作为构造函数的参数,不然还会产生不精确的现象,有坑!!!!*/
      /**2.最好使用重载的,参数类型是String的构造函数,double转String,直接拼个空串就可以*/
      BigDecimal bd1 = new BigDecimal(a+"");
      BigDecimal bd2 = new BigDecimal(b+"");
      //3.通过BigDecimal上的方法,做精确运算
      //3.1定义对象来保存结果
      BigDecimal bd3;
      //3.2 add(BigDecimal bd) : 做加法运算
      bd3 = bd1.add(bd2);
      System.out.println(bd3);
      //3.3 subtract(BigDecimal bd) : 做减法运算
      bd3 = bd1.subtract(bd2);
      System.out.println(bd3);
      //3.4 multiply(BigDecimal bd) : 做乘法运算
      bd3 = bd1.multiply(bd2);
      System.out.println(bd3);
      //3.5 add(BigDecimal bd) : 做除法运算
      /**java.lang.ArithmeticException,除法运算,除不尽时会抛异常*/
      //bd3 = bd1.divide(bd2);--方案一
      /**divide(m,n,o) --m是要除以哪个对象保存的值,n要保留几位,o是摄入方式,最常使用的是四舍五入*/
      bd3 = bd1.divide(bd2, 3, BigDecimal.ROUND_HALF_UP);//方案二:
      System.out.println(bd3);
   }

包装类

把基本类型进行包装,提供更加完善的功能。
基本类型是没有任何功能的,只是一个变量,记录值,而包装类可以有更加丰富的功能

Integer

创建对象

1. new Integer(5);
2. Integer.valueOf(5);Integer类中,包含256Integer缓存对象,范围是 -128127。
使用valueOf()时,如果指定范围内的值,访问缓存对象,而不新建;如果指定范围外的值,直接新建对象。
public static void main(String[] args) {
        //1.创建int基本类型的包装类Integer的对象方式1
        /**1.Integer包装类的默认值是null*/
        Integer i0;
        Integer i1 = new Integer(5);
        //2.创建int基本类型的包装类Integer的对象方式2
        /**2.Integer有一个高效的效果(-128~127)
         * 因为静态的valueOf(),相同的数据只会存一次,后续再存都会使用已经存过的数据
         * */
        Integer i2 = Integer.valueOf(127);
        Integer i3 = Integer.valueOf(127);
        Integer i4 = Integer.valueOf(300);
        Integer i5 = Integer.valueOf(300);
        System.out.println(i1==i2);//false,==比较的是地址值
        System.out.println(i2==i3);//true,是Integer,并且是在(-128~127)范围内,相同数据只存一次
        System.out.println(i4==i5);//false,是Integer,但是不在(-128~127)范围内,所以存了两次,两个对象
    }

Double

创建对象

1. new Double(3.14)
2. Double.valueOf(3.14)//和 new 没有区别
 //3.创建Double对象
        Double d1 = new Double(3.4);
        Double d2 = Double.valueOf(3.4);
        Double d3 = Double.valueOf(3.4);
        System.out.println(d2==d3);//false,只有Integer包装类有高效的效果
        //4.测试常用方法
        //原因:parseInt()已经把字符串8000转换成了int类型的数字8000,可以参与运算
        System.out.println(i1.parseInt("8000")+10);//8010,执行了加法运算
        System.out.println(d1.parseDouble("2.2")+1);//3.2,执行了加法运算

自动装箱和自动拆箱

概述:

自动装箱:把 基本类型 包装成对应的 包装类型 的过程
Integer a = 5;//a是引用类型,引用了包装对象的地址。
编译器会完成对象的自动装箱:Integer a = Integer.valueOf(5);

自动拆箱:从包装类型的值,自动变成 基本类型的值
int i = a;//a现在是包装类型,没法给变量赋值,需要把5取出来。
编译器会完成自动拆箱:int i = a.intValue();

 public static void main(String[] args) {
        //1.定义包装类型的数据
        //之前的方式:创建包装类型的两种方式:
        Integer i11 = new Integer(127);
        Integer i22 = Integer.valueOf(127);
        //现在的方式:
        /**1.自动装箱:编译器会自动把基本类型int数据5,包装成包装类型Integer,然后交给i1保存
         * 自动装箱底层发生的代码:Integer.valueOf(5);
         * valueOf()的方向:int --> Interger
         * */
        Integer i1 = 5;//不会报错,这个现象就是自动装箱
        /**2.自动拆箱:编译器会自动把包装类型的5,拆掉箱子,变回到基本类型数据5
         * 自动拆箱底层发生的代码:i1.intValue()
         * intValue()的方向:Integer --> int
         * */
        int i2 = i1;//不会报错,这个现象就是自动拆箱
    }

拓展:

==和equals的区别

1.当使用 = = 比较时,如果相比较的两个变量是引用类型,那么比较的是两者的物理地值(内存地址),如果相比较的两个变量都是数值类型,那么比较的是具体数值是否相等。
2.当使用equals()方法进行比较时,比较的结果实际上取决于equals()方法的具体实现
众所周知,任何类都继承自Object类,因此所有的类均具有Object类的特性,比如String、integer等,他们在自己的类中重写了equals()方法,此时他们进行的是数值的比较,而在Object类的默认实现中,equals()方法的底层是通过 = =来实现的。
System.out.println(p1.name = = p2.name);//true,name是String类型,保存在常量池中,值是同一个

/**本类用于测试==与equlas的区别*/
public class Test7 {
    public static void main(String[] args) {
        Person p1 = new Person("凹凸曼",16);
        Person p2 = new Person("凹凸曼",16);
        System.out.println(p1 == p2);//false,new了两个对象,p1和p2保存的地址值不同

        System.out.println(p1.name == p2.name);//true,name是String类型,保存在常量池中,值是同一个

        System.out.println(p1.age == p2.age);//true,age是int基本类型,保存的值都是18
        //第一次测试:结果false,使用的是Object默认的逻辑,也是用==来比较的
        //第二次测试:结果true,重写equals()后,就会使用子类重写后的功能,也就是说此时比较的不再是地址,而是类型&属性值
        System.out.println(p1.equals(p2));
    }
}
//1.创建Person类
class Person {
    String name;
    int age;
    public Person() {
        System.out.println("我是手动添加的无参构造");
    }
    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
    //需求:比较两个对象的属性值,如果属性值都一样,就是"同一个对象",要求equals返回true
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Person other = (Person) obj;
        if (age != other.age)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }
}

第七章:IO

继承结构

In/out 相对于程序而言的输入(读取)/输出(写出)的过程.
在java中,根据处理的数据单位不同,分为字节流和字符流
字节流 : 针对二进制文件
字符流 : 针对文本文件,读写容易出现乱码的现象,在读写时,最好指定编码集为UTF-8

  Java.io包下:
   File
字节流:针对二进制文件
   InputStream
      --FileInputStream
      --BufferedInputStream
      --ObjectInputStream
   OutputStream
      --FileOutputStream
      --BufferedOutputStream
      --ObjectOutputStream
字符流:针对文本文件
Reader
   --FileReader
   --BufferedReader
   --InputStreamReader
Writer
   --FileWriter
   --BufferedWriter
   --OutputStreamWriter
   --PrintWriter一行行写出

Stream流的概念

数据的读写可以抽象成数据在管道中流动

  • 流只能单方向流动
  • 输入流用来读取à in
  • 输出流用来写出 àout
  • 数据只能从头到尾顺序的读写一次

File文件流

概述:

封装一个磁盘路径字符串,对这个路径可以执行一次操作
可以封装文件路径、文件夹路径、不存在的路径

创建对象:

 File(String pathname)通过将给定路径名字符串转换为抽象路径名来创建一个新的File实例
new File(“d:/abc/a.txt”); 
new File(“d:/abc”,”a.txt”);

常用方法:

 //1.创建File对象
      //参数是具体的路径,可以是文件的路径,也可以是文件夹的路径
      //注意:需要手动在windows中创建D:\ready\1.txt并添加内容
      /**1.\在代码中具有特殊的意义,转义的作用,
       * 所以表示这个是一个真正的斜杠,需要转义一下*/
      File file = new File("D:\\ready\\1.txt");//创建的是java对象
      //2.测试常用方法
      //2.1文件与文件夹属性
      System.out.println(file.length());//15,获取指定文件的字节量
      System.out.println(file.exists());//true,判断指定文件是否存在
      System.out.println(file.isFile());//true,判断指定内容是否是文件
      System.out.println(file.isDirectory());//false,判断指定内容是否是文件夹
      System.out.println(file.getName());//1.txt,获取文件名
      System.out.println(file.getParent());//D:\ready,获取父级目录
      System.out.println(file.getAbsolutePath());//D:\ready\1.txt,获取绝对路径
 //2.2创建与删除
      file = new File("D:\\\\ready\\\\2.txt");
      /**如果指定创建文件的文件夹不存在
       * 会抛出异常:java.io.IOException:系统找不到指定的路径
       * 所以需要处理,目前的方式是抛出异常*/
      System.out.println(file.createNewFile());//在win中创建不存在的文件2.txt
      file = new File("D:\\ready\\m");
      System.out.println(file.mkdir());//在win中创建不存在的单级目录
      file = new File("D:\\ready\\a\\b\\c");
      System.out.println(file.mkdirs());//在win中创建不存在的多级目录
      System.out.println(file.delete());//删除文件或者空文件夹,c文件夹被删除
      file = new File("D:\\ready\\a");
      System.out.println(file.delete());//由于a目录里还有b目录,所以删除不了
 //2.3文件列表
      file = new File("D:\\ready");
      //查看文件夹下所有文件的名称,返回值类型是String[]
      String[] list = file.list();
      System.out.println(Arrays.toString(list));
      /**列出文件夹中所有的文件夹和文件对象,返回值是File[]
       * 数组的每个元素都是File对象,可进一步操作
       * */
      File[] listFiles = file.listFiles();
      System.out.println(Arrays.toString(listFiles));
      System.out.println(listFiles[0].length());

递归求目录总大小

需求:递归求目录的总大小 D:\\ready,步骤分析如下:
1.列出文件夹中的所有资源--listFiles()-->File[]
2.判断,当前资源是文件还是文件夹--文件夹大小为0,文件大小需要累加
      --是文件,求文件的字节量大小length(),累加就行
      --是文件夹,继续列出文件夹下的所有资源--listFiles()-->File[]
           --判断,是文件,求文件的字节量大小length(),累加就行
           --判断,是文件夹,再一次列出文件夹下的所有资源
                 --......重复操作
也就是说,规律就是:只要是文件夹,就需要重复步骤1 2
 private static long size(File file) {
        //1.列出文件夹中的所有资源--listFiles()-->File[]
        File[] fs = file.listFiles();
        //2.遍历数组,获取每file对象
        //2.1定义变量,记录总和
        long sum = 0;
        for(int i = 0;i < fs.length ; i++) {
            //2.2通过下标操作当前遍历到的资源
            File f = fs[i];
            //2.3判断,当前资源是文件还是文件夹--文件夹大小为0,文件大小需要累加
            if(f.isFile()) {
                //--是文件,求文件的字节量大小length(),累加就行
                sum += f.length();//相当于:sum = sum + f.length();
            }else if(f.isDirectory()) {
                //--是文件夹,继续列出文件夹下的所有资源,1 2步骤--listFiles()-->File[]
                /**方法的递归,递归现象,就是在方法的内部调用方法自身*/
                sum += size(f);
            }
        }
        return sum;//把sum记录的值返回调用位置
    }

字节流读取

字节流是由字节组成的,字符流是由字符组成的.
Java里字符由两个字节组成.字节流是基本,主要用在处理二进制数据。
流式传输主要指将整个音频和视频及三维媒体等多媒体文件经过特定的压缩方式解析成一个个压缩包,由视频服务器向用户计算机顺序或实时传送。在采用流式传输方式的系统中,用户不必像采用下载方式那样等到整个文件全部下载完毕,而是只需经过几秒或几十秒的启动延时即可在用户的计算机上利用解压设备对压缩的A/V、3D等多媒体文件解压后进行播放和观看。此时多媒体文件的剩余部分将在后台的服务器内继续下载。

InputStream抽象类

此抽象类是表示字节输入流的所有类的超类/抽象类,不可创建对象
常用方法:

abstract  int read() 从输入流中读取数据的下一个字节
int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中
int read(byte[] b, int off, int len) 将输入流中最多 len 个数据字节读入 byte 数组,off表示存时的偏移量
void close() 关闭此输入流并释放与该流关联的所有系统资源

FileInputStream子类

直接插在文件上,直接读取文件数据
创建对象
FileInputStream(File file)—直接传文件对象
通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的 File 对象 file 指定FileInputStream(String pathname)—传路径
通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的路径名 name 指定

BufferedInputStream子类

BufferedInputStream 为另一个输入流添加一些功能,即缓冲输入以及支持 mark 和 reset 方法的能力。在创建 BufferedInputStream 时,会创建一个内部缓冲区数组(默认8k大小)。在读取或跳过流中的字节时,可根据需要从包含的输入流再次填充该内部缓冲区,一次填充多个字节。
创建对象
BufferedInputStream(InputStream in)
创建一个 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。

 public static void main(String[] args) {
      method();//字节流的读取
      method2();//高效字节流的读取
   }
public static void method() {
      InputStream in = null;
      try {
         //1.创建字节流读取对象
         //new InputStream();//报错:抽象父类不可以创建对象
         //FIS传入的参数是一个File对象
         //InputStream in2 = new FileInputStream(new File("D:\\ready\\4.txt"));
         //FIS传入的参数是一个路径,保证此路径下的文件是存在且有内容的
         in = new FileInputStream("D:\\ready\\4.txt");
         //2.开始读取,read()每次读取1个字节,如果读到了数据的末尾,返回-1
         //          System.out.println(in.read());//97
         //          System.out.println(in.read());//98
         //          System.out.println(in.read());//99
         //          System.out.println(in.read());//-1
         //3.1定义变量,记录读到的数据
         int b;
         while((b = in.read()) != -1) {//3.2返回值为-1的时候表示没有数据了,循环结束
            //3.3打印每次读取到的内容
            System.out.println(b);
         }
      } catch (IOException e) {
         e.printStackTrace();//打印错误信息到控制台
      } finally {//try-catch-finally,finally中的代码一定会被执行,常用于关流
         try {
            //4.释放资源
            in.close();
         } catch (IOException e) {
            e.printStackTrace();
         }
      }
   }
public static void method2() {
      BufferedInputStream in = null;
      try {
         //1.创建高效字节流读取对象
         //new InputStream();//报错:抽象父类不可以创建对象
         //BIS是高效的读取流
//       in2 = new BufferedInputStream(
//             new FileInputStream(new File("D:\\ready\\4.txt")));
         in = new BufferedInputStream(
                new FileInputStream("D:\\ready\\4.txt"));
         //2.开始读取,read()每次读取1个字节,如果读到了数据的末尾,返回-1
         //          System.out.println(in.read());//97
         //          System.out.println(in.read());//98
         //          System.out.println(in.read());//99
         //          System.out.println(in.read());//-1
         //3.1定义变量,记录读到的数据
         int b;
         while((b = in.read()) != -1) {//3.2返回值为-1的时候表示没有数据了,循环结束
            //3.3打印每次读取到的内容
            System.out.println(b);
         }
      } catch (IOException e) {
         e.printStackTrace();//打印错误信息到控制台
      } finally {//try-catch-finally,finally中的代码一定会被执行,常用于关流
         try {
            //4.释放资源
            in.close();
         } catch (IOException e) {
            e.printStackTrace();
         }
      }
   }

字符流读取

常用于处理纯文本数据

Reader抽象类

用于读取字符流的抽象类。
常用方法:

int read() 读取单个字符
int read(char[] cbuf) 将字符读入数组
abstract  int read(char[] cbuf, int off, int len) 将字符读入数组的某一部分
int read(CharBuffer target) 试图将字符读入指定的字符缓冲区
abstract  void close() 关闭该流并释放与之关联的所有资源

FileReader子类

用来读取字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是适当的。要自己指定这些值,可以先在 FileInputStream 上构造一个 InputStreamReader。

创建对象
FileReader(String fileName) 在给定从中读取数据的文件名的情况下创建一个新 FileReader
FileReader(File file) 在给定从中读取数据的 File 的情况下创建一个新 FileReader

BufferedReader子类

从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。
可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。

创建对象
BufferedReader(Reader in) 创建一个使用默认大小输入缓冲区的缓冲字符输入流

 public static void main(String[] args) {
        //method();//普通字符流读取
        method2();//高效字符流读取
    }
 public static void method() {
        try {
            //1.创建字符流的读取对象
            //Reader in = new Reader();//Reader是字符流读取的父类,而且是一个抽象类,不能new
            //Reader in = new FileReader(new File("D:\\ready\\1.txt"));//传入的是file对象
            Reader in = new FileReader("D:\\ready\\1.txt");//传入的是路径
            //2.开始读取,read()每次读取一个字符,如果读取到了数据的末尾,返回-1
//          System.out.println(in.read());//97
//          System.out.println(in.read());//98
//          System.out.println(in.read());//99
//          System.out.println(in.read());//100
//          System.out.println(in.read());//-1
            //3.需求:重复的读取文件中的所有内容
            //3.1定义变量,记录读取到的数据
            int b;
            //3.2循环读取文件中所有内容,只要不是-1,就表示还有数据,继续循环
            while( (b = in.read()) != -1) {
                System.out.println(b);
            }
            //4.释放资源
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 public static void method2() {
        try {
            //1.创建高效的字符流的读取对象
            //Reader in = new Reader();//Reader是字符流读取的父类,而且是一个抽象类,不能new
            //BufferedReader是高效的字符读取流,原因是底层维护了一个char[],默认的容量也是8*1024字节8k
            Reader in = new BufferedReader(new FileReader("D:\\ready\\1.txt"));//传入的是路径
            //2.开始读取,read()每次读取一个字符,如果读取到了数据的末尾,返回-1
//          System.out.println(in.read());//97
//          System.out.println(in.read());//98
//          System.out.println(in.read());//99
//          System.out.println(in.read());//100
//          System.out.println(in.read());//-1
            //3.需求:重复的读取文件中的所有内容
            //3.1定义变量,记录读取到的数据
            int b;
            //3.2循环读取文件中所有内容,只要不是-1,就表示还有数据,继续循环
            while( (b = in.read()) != -1) {
                System.out.println(b);
            }
            //4.释放资源
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

字节流写出

OutputStream抽象类

此抽象类是表示输出字节流的所有类的超类.输出流接受输出字节并将这些字节发送到某个接收器.

常用方法:
Void close() 关闭此输出流并释放与此流相关的所有系统资源
Void flush() 刷新此输出流并强制写出所有缓冲的输出字节
Void write(byte[ ] b) 将b.length个字节从指定的byte数组写入此输出流
Void write(byte[ ] b,int off ,int len) 将指定byte数组中从偏移量off开始的len个字节写入输出流
Abstract void write(int b) 将指定的字节写入此输出流

FileOutputStream 子类

直接插在文件上,直接写出文件数据
构造方法(创建对象):
FileOutputStream(String name)
创建一个向具有指定名称的文件中写入数据的文件输出流
FileOutStream(File file)
创建一个向指定File对象表示的文件中写入数据的文件输出流
FileOutStream(File file,boolean append)—如果第二个参数为true,表示追加,不覆盖
创建一个向指定File对象表示的文件中写入数据的文件输出流,后面的参数是指是否覆盖原文件内容

BufferedOutputstream 子类

该类实现缓冲的输出流,通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必每次针对字节写出调用底层系统
构造方法(创建对象):
BufferedOutputStream(OutputStream out)
创建一个新的缓冲输出流,用以将数据写入指定的底层输出流

 public static void main(String[] args) {
        //method();//1.创建使用普通字节输出流对象输出数据
        method2();//2.创建使用高效字节输出流对象输出数据
    }
 public static void method2() {
        //5.声明在此方法内部都生效的局部变量,并且局部变量需要初始化,对象的默认值是null
        OutputStream out = null;
        try {
            //0.用来测试的路径必须是文件路径,不是文件夹路径,而且文件得存在
            //1.创建高效字节输出流对象
            //OutputStream out =new BufferedOutputStream(new FileOutputStream(new File("D:\\ready\\out.txt")));
            out =new BufferedOutputStream(new FileOutputStream("D:\\ready\\out.txt"));
            //2.开始写出数据
            out.write(100);//ascii码表存在对应关系:100-d
            out.write(100);//ascii码表存在对应关系:100-d
            out.write(100);//ascii码表存在对应关系:100-d
            out.write(100);//ascii码表存在对应关系:100-d
        } catch (IOException e) {
            e.printStackTrace();
        } finally {/**3.要保证代码一定会被执行,就通通放在finally代码块中*/
            //4.释放资源
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
  public static void method() {
        //4.声明在此方法内部都生效的局部变量,并且局部变量必须初始化,对象的默认值是null
        OutputStream out=null;
        try {
            //0.需要在windows环境下创建一个文件,路径:D:\ready\out.txt用来查看输出的数据
            /**注意:指定的路径是文件路径,不是文件夹路径,而且文件得存在*/
            //1.创建字节输出流对象
            //new OutputStream();//报错:原因:OutputStream是字节输出流的抽象父类,不能实例化
            //OutputStream out = new FileOutputStream(new File("D:\\ready\\out.txt"));
            out = new FileOutputStream("D:\\ready\\out.txt");
            //2.开始写出数据
            out.write(97);//ascii码表对应的关系:97--a
            out.write(98);//ascii码表对应的关系:98--b
            out.write(99);//ascii码表对应的关系:99--c
        } catch (IOException e) {
            e.printStackTrace();
        } finally {/**如果要保证代码一定会执行,就通通放在finally代码块中*/
            //3.释放资源
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

字符流写出

Writer 抽象类

写入字符流的抽象类
常用方法:

Abstract void close() 关闭此流,但要先刷新它
Void write(char[ ] cbuf) 写入字符数组
Void write(int c) 写入单个字符
Void write(String str) 写入字符串
Void write(String str,int off,int len) 写入字符串的某一部分
Abstract void write(char[] cbuf,int off,int len)写入字符数组的某一部分

FileWriter 子类

用来写入字符文件的便捷类,此类的构造方法假定默认字符编码和默认字节缓冲区大小都是可接受的.如果需要自己自定义这些值,可以先在FileOutputStream上构造一个OutputStreamWriter.

构造方法(创建对象):
FileWriter(String filename)
根据给定的文件名构造一个FileWriter对象
FileWriter(String filename,boolean append)
根据给定的文件名以及指示是否附加写入数据的boolean值来构造FileWriter

BufferedWriter子类

将文本写入字符输出流,缓冲各个字符,从而提供单个字符,数组和字符串的高效写入.可以指定缓冲区的大小,或者接受默认的大小,在大多数情况下,默认值就足够大了

构造方法(创建对象):
BufferedWriter(Writer out)
创建一个使用默认大小输出缓冲区的缓冲字符输出流

public static void main(String[] args) {
      method1();//普通字符输出流
      method2();//高效字符输出流
   }
/**高效字符输出流*/
   public static void method2() {
      Writer out = null;
      try {
         //1.创建高效字符输出流对象,并且数据输出的方式是追加
         //默认状态是不追加,也就是覆盖原有数据
         //out2 = new BufferedWriter(new FileWriter(new File("D:\\ready\\out.txt")));
   out = new BufferedWriter(new FileWriter("D:\\ready\\out.txt",true));
         //2.输出数据
         out.write(97);
         out.write("hello");
         out.write("io");
      } catch (IOException e) {
         e.printStackTrace();
      }finally {
         //3.释放资源
         try {
            out.close();
         } catch (IOException e) {
            e.printStackTrace();
         }
      }
   }
 /**普通字符输出流*/
   public static void method1() {
      Writer out = null;
      try {
         //0.在路径D:\\ready\\out.txt下创建对应的文件.查看输出的数据
         //1.创建流对象
         //new Writer();//报错:Writer是抽象父类,不可以创建对象
         //Writer out2 = new FileWriter(new File("D:\\ready\\out.txt"));
         //out = new FileWriter("D:\\ready\\out.txt");//默认覆盖
         /**需求:保持原有的数据
          * 在不改变原数据的基础上,在它的末尾添加新数据*/
         /**
          * 此构造函数的第二个参数表示,是否覆盖写出文件中的原有数据
          * 默认覆盖,如果不想覆盖,就把这个参数的值设置为true
          * */
         out = new FileWriter("D:\\ready\\out.txt",true);
         //2.输出数据
         out.write(97);
         out.write(98);
         out.write(99);
         out.write(100);
      } catch (IOException e) {
         e.printStackTrace();
      }finally {
         //3.释放资源
         try {
            out.close();
         } catch (IOException e) {
            e.printStackTrace();
         }
      }
   }

文件复制测试

 public static void main(String[] args) {
      //1.提示并接收用户输入要复制的原文件的路径--复制啥
      System.out.println("请输入原文件路径:");
      String f = new Scanner(System.in).nextLine();
      //2.提示并接收用户输入的目标文件所在的路径--复制到哪
      System.out.println("请输入目标文件路径:");
      String t = new Scanner(System.in).nextLine();
      //3.根据原文件路径封装File对象from
      File from = new File(f);
      //4.根据目标文件路径封装File对象to
      File to = new File(t);
      //5.根据用户提供的路径完成文件的复制操作
      //5.1自定义字符流文件复制方法
      //ZFCopy(from,to);//字符流--只能操作字符相关文件
      ZJCopy(from,to);//字节流--什么文件都能操作
   }
/**自定义字节复制文件的方法*/
   public static void ZJCopy(File from, File to) {
      //1.定义整个方法都生效的局部变量,需要手动初始化,默认值null
      InputStream in = null;//字节输入流--抽象父类
      OutputStream out = null;//字节输出流--抽象父类
      try {
         //2.1创建字节输入流对象
         in = new BufferedInputStream(new FileInputStream(from));
         //2.2创建字节输出流对象
         out = new BufferedOutputStream(new FileOutputStream(to));
         //3.完成复制操作--边读边写
         int b ;
         while((b=in.read()) != -1) {
            out.write(b);
         }
         System.out.println("恭喜您!复制成功!");
      } catch (IOException e) {
         System.out.println("很抱歉!复制失败!");
         e.printStackTrace();
      }finally {
         //4.释放资源
         /**1.流资源必须释放,释放的是之前使用过程中的所有流对象
          * 2.关流是有顺序的,注意,后面出现的流先释放,为了不影响代码* */
         try {
            out.close();//用来关闭字符输出流
         } catch (IOException e) {
            e.printStackTrace();
         }
         try {
            in.close();//用来关闭字符输入流
         } catch (IOException e) {
            e.printStackTrace();
         } 
      }
   }
/**自定义字符复制文件的方法*/
   public static void ZFCopy(File from, File to) {
      //1.定义整个方法都生效的局部变量,需要手动初始化,默认值null
      Reader in = null;//字符输入流--抽象父类
      Writer out = null;//字符输出流--抽象父类
      try {
         //2.读取原文件from--获取字符输入流对象
         in = new BufferedReader(new FileReader(from));
         //3.写出到目标文件to--获取字符输出流对象
         out = new BufferedWriter(new FileWriter(to));
         //4.开始复制操作--边读边写
         //4.1 定义变量用来保存读到的数据
         int b;
         while( (b = in.read()) != -1 ) {//4.2while循环边读边写
            out.write(b);//4.3把当前循环读到的数据写到目标文件中
         }
         System.out.println("恭喜您!文件复制完成!");
      } catch (IOException e) {
         System.out.println("很抱歉!文件复制失败!");
         e.printStackTrace();//打印错误信息
      }finally {//finally代码块是try-catch最后且一定会指定的部分
         //5.释放资源
         /**1.流资源必须释放,释放的是之前使用过程中的所有流对象
          * 2.关流是有顺序的,注意,后面出现的流先释放,为了不影响代码* */
         try {
            out.close();//用来关闭字符输出流
         } catch (IOException e) {
            e.printStackTrace();
         }
         try {
            in.close();//用来关闭字符输入流
         } catch (IOException e) {
            e.printStackTrace();
         }
      }
   }

文件批量读写

 public static void ZJcopy(File from, File to) {
        InputStream in = null;//定义在整个方法中都生效的字节输入流对象,注意是局部变量,需要初始化,对象的默认值是null
        OutputStream out = null;//定义在整个方法中都生效的字节输出流对象,注意是局部变量,需要初始化,对象的默认值是null
        try {
            //1.读取from文件--操作文件的是字节输入流
            in = new BufferedInputStream(new FileInputStream(from));
            //2.写出到to文件--操作文件的是字节输出流
            out = new BufferedOutputStream(new FileOutputStream(to));
            //3.边读边写
            int b = 0;//定义变量b,记录读取到的数据
            /**需求:想要实现批量读取,使用的是read(byte[] b)重载的形式,可以按照数组的方式来读 */
            /**可以自定义数组,长度建议与源码保持一致,8*1024 = 8192*/
            byte[] bs = new byte[8*1024];
            while( (b=in.read(bs)) != -1 ) {//只有没有数据时,才返回-1,跳出循环,读写结束
                out.write(bs);//将读到的数据写出到文件
            }
            System.out.println("恭喜您!文件复制成功!");
        } catch (IOException e) {
            System.out.println("很抱歉!文件复制失败!");
            e.printStackTrace();//打印错误信息
        }finally {与上面相同略…}
    }
}

IO的继承结构

1. 主流分类
1) 按照方向进行分类:输入流 输出流(相对于程序而言,从程序写数据到文件中是输出)
2) 按照传输类型进行分类:字节流 字符流
3) 组合: 字节输入流 字节输出流 字符输入流 字符输出流
2. 学习方法:在抽象父类中学习通用的方法,在子类中学习如何创建对象
3.字节输入流:
--InputStream 抽象类,不能new,可以作为超类,学习其所提供的共性方法
       --FileInputStream 子类,操作文件的字节输入流,普通类
       --BufferedInputStream 子类,缓冲字节输入流,普通类
4.字符输入流
--Reader 抽象类,不能new,可以作为超类,学习其所提供的共性方法
       --FileReader,子类,操作文件的字符输入流,普通类
       --BufferedReader,子类,缓冲字符输入流,普通类
5.字节输出流:
--OutputStream 抽象类,不能new,可以作为超类,学习其所提供的共性方法
       --FileOutputStream 子类,操作文件的字节输出流,普通类
       --BufferedOutputStream 子类,缓冲字节输出流,普通类
6.字符输出流
--Writer 抽象类,不能new,可以作为超类,学习其所提供的共性方法
       --FileWriter,子类,操作文件的字符输出流,普通类
       --BufferedWriter,子类,缓冲字符输出流,普通类

BIO、NIO、AIO的区别

阻塞IO,BIO 就是传统的 java.io 包,它是基于流模型实现的,交互的方式是同步、阻塞方式,也就是说在读入输入流或者输出流时,在读写动作完成之前,线程会一直阻塞在那里,它们之间的调用时可靠的线性顺序。它的有点就是代码比较简单、直观;缺点就是 IO 的效率和扩展性很低,容易成为应用性能瓶颈。

非阻塞IO,NIO 是 Java 1.4 引入的 java.nio 包,提供了 Channel、Selector、Buffer 等新的抽象,可以构建多路复用的、同步非阻塞 IO 程序,同时提供了更接近操作系统底层高性能的数据操作方式。

异步IO,AIO 是 Java 1.7 之后引入的包,是 NIO 的升级版本,提供了异步非堵塞的 IO 操作方式,所以人们叫它 AIO(Asynchronous IO),异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。但目前还不够成熟,应用不多。

序列化/反序列化

概述:
序列化(Serialization)是将对象的状态信息转换为可以存储或传输形式的过程.
在序列化期间,对象将其当前状态写入到临时或持久性存储区.以后可以通过从存储区中读取或者反序列化对象的状态,重新创建该对象.
序列化:利用ObjectOutputStream,把对象的信息,按照固定的格式转成一串字节值输出并持久保存到磁盘
反序列化:利用ObjectInputStream,读取磁盘中之前序列化好的数据,重新恢复成对象
TIPS: 序列化: 对象 à 字节值 反序列化:字节值(之前序列化生成的) à 对象

特点/应用场景

  1. 需要序列化的文件必须实现Serializable接口,用来启用序列化功能
    
  2. 不需要序列化的数据可以修饰成static,原因:static资源属于类资源,不随着对象被序列化输出
    
  3. 每一个被序列化的文件都有一个唯一的id,如果没有添加此id,编译器会自动根据类的定义信息计算产生一个
    
  4. 在反序列化时,如果和序列化的版本号不一致,无法完成反序列化
    
  5. 常用与服务器之间的数据传输,序列化成文件,反序列化读取数据
    
  6. 常用使用套接字流在主机之间传递对象
    
  7. 不需要序列化的数据也可以被修饰成transient(临时的),只在程序运行期间在内存中存在,不会被序列化持久保存
    

ObjectOutputStream

ObjectOutputStream将Java对象的基本数据类型和同行写入OutputStream.可以使用ObjectInputStream读取(重构)对象.通过在流中使用文件可以实现对象的持久存储. 如果流是网络套接字流,则可以在另一台主机上或另一个进程中重构对象.

构造方法:
ObjectOutputStream(OutputStream out)
创建写入指定 OutputStreamObjectOutputStream
方法:
writeObject(Object obj) 将指定的对象写入 ObjectOutputStream

ObjectInputStream

ObjectInputStream对以前使用ObjectOutputStream写入的基本数据和对象进行反序列化

构造方法:
ObjectInputStream(InputStream in)
       创建从指定 InputStream 读取的 ObjectInputStream
方法:
readObject()ObjectInputStream 读取对象
/**本类用来封装学生类*/
/**
 * 如果本类想要完成序列化,必须实现可序列化接口,否则会报错:
 * 报错信息:java.io.NotSerializableException: cn.tedu.serializable.Student
 * Serializable接口是一个空接口,里面一个方法都没有,作用是用来当做标志,标志这个类可以序列化/反序列化
 * */
public class Student implements Serializable{
    /**需要给每个进行序列化的文件分配唯一的UID值*/
    //The serializable class Student does not declare a static final serialVersionUID field of type long
    //private static final long serialVersionUID = 1L;
    private static final long serialVersionUID = -3193364654654535741L;
    //1.定义学生的相关属性 + private封装
    private String name;//姓名
    private int age;//年龄
    private String addr;//地址
    private char gender;//性别
}
 public static void main(String[] args) {
        //method1();//测试序列化
        method2();//测试反序列化
    }
    /**创建用来测试反序列化的方法*/
    public static void method2() {
        ObjectInputStream in = null;
        try {
            //1.创建ObjectInputStream来完成反序列化
            in = new ObjectInputStream(new FileInputStream("D://ready//1.txt"));
            //2.完成反序列化操作
            Object o = in.readObject();
            System.out.println(o);
            System.out.println("恭喜您!反序列化成功!");
        } catch (Exception e) {
            System.out.println("很抱歉!反序列化失败!");
            e.printStackTrace();
        }finally {
            //3.释放资源
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    /**创建用来测试序列化的方法*/
    public static void method1() {
        ObjectOutputStream out = null;
        try {
            //1.创建ObjectOutputStream流对象来完成序列化
            out = new ObjectOutputStream(
                    new FileOutputStream("D://ready//1.txt"));
            //2.完成序列化操作
            //2.1创建学生对象
            Student s = new Student("陈子枢",3,"男生","BeiJing");
            //2.2通过OOS完成对象的序列化输出操作
            out.writeObject(s);
            System.out.println("恭喜您!序列化成功");
        } catch (IOException e) {
            System.out.println("很抱歉!序列化失败!");
            e.printStackTrace();
        } finally {
            //3.释放资源
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

编码转换流

字节流:针对二进制文件
字符流:针对文本文件,读写容易出现乱码的现象,在读写时,最好指定编码集为UTF-8

概述: 编码转换流(InputStreamReader/OutputStreamWriter)主要进行编码的转换,用来解决字符流读写乱码的问题
api:

OutputStreamWriter :
OutputStreamWriter(OutputStream out)把传入的字节流转成字符流
OutputStreamWriter(OutputStream out ,String charsetName)Unicode转成其他编码输出

InputStreamReader :
InputStreamReader(InputStream in) 把传入的字节流转成字符流
InputStreamReader(InputStream in,String charsetName)读取其他编码转成Unicode

第八章:泛型

概念:

public class LinkedList extends AbstractSequentialList implements List
public interface Deque extends Queue {}
public interface Queue extends Collection {}
public interface Collection extends Iterable {}

我们上面的代码中出现的<?>是什么东西呢?它叫泛型,常用来和集合对象一同使用,所以在开始学习集合之前,必须先了解下什么是泛型。而且泛型概念非常重要,它是程序的增强器,它是目前主流的开发方式。

泛型是(Generics)JDK1.5 的一个新特性,其实就是一个『语法糖』,本质上就是编译器为了提供更好的可读性而提供的一种小手段,小技巧,虚拟机层面是不存在所谓『泛型』的概念的。是不有点神奇,不知所云,别着急等我讲完你就清楚了。

作用:

通过泛型的语法定义<>,约束集合元素的类型,编译器可以在编译期提供一定的类型安全检查
这样可以避免程序运行时才暴露BUG,代码的通用性也会更强
泛型可以提升程序代码的可读性,但是它只是一个==『语法糖』==(编译后这样的部分会被删除,不出现在最终的源码中),所以不会影响JVM后续运行时的性能.

泛型声明:

泛型可以在接口 类 方法上使用

public interface Collection<E>{}
public class TestStudy<Student>{}
public <E> void print(E e){}

在方法的返回值前声明了一个<E>,表示后面出现的E是泛型,而不是普通的java变量

常用名称:

  • E - Element ( 在集合中使用,因为集合中存放的是元素 )
  • T - Type ( Java类 )
  • K - Key ( 键 )
  • V - Value ( 值 )
  • N - Number ( 数值类型 )
  • ? - 表示不确定的java类型
 public static void main(String[] args) {
        /**1.泛型是怎么来的?--想要模拟数组的数据类型检查*/
        String[] a = new String[5];//创建一个用来存放String类型数据的数组,长度为5
        a[2] = "马凯";
        a[4] = "义天";
        //数组的好处:在编译时期检查数据的类型,如果不是要求的类型会在编译器就报错
        //a[0] = 1;
        //a[1] = 8.8;
        //a[3] = 'c';
        /**2.泛型通常会结合着集合一起使用*/
        List list = new ArrayList();//注意导包:java.util...
        //没有泛型,数据类型根本没有约束 -- 太自由!!!
        list.add("景峰峰");
        list.add(1);
        list.add(8.8);
        list.add('a');
        System.out.println(list);//通过打印查看集合中的元素
        /**3.引入泛型--主要目的是想通过泛型来约束集合中元素的类型<?>*/
        /**4.泛型的好处:可以把报错的时机提前,在编译期就报错,而不是运行后抛出异常
         * 在向集合中添加元素时,会先检查元素的数据类型,不是要求的类型就编译失败
         * */
        List<String> list2 = new ArrayList<String>();//注意导包:java.util...
        list2.add("朱斐斐");//约束了类型以后,只可以传String参数
        //list2.add(1);
        //list2.add(8.8);
        //list2.add('d');
        /**5.<type>--type的值应该如何写?
         * 需要查看要存放的数据类型是什么,根据类型进行定义
         * 但是type必须是引用类型,不是基本类型
         */
        //List<int> list3 = new ArrayList<int>();//注意导包:java.util...
        List<Integer> list3 = new ArrayList<Integer>();//注意导包:java.util...
        list3.add(100);
        list3.add(200);
        System.out.println(list3);
    }
 public static void main(String[] args) {
        //需求:打印指定数组中的所有元素
        Integer[] a = {1,2,3,4,5,6,7,8,9,10};
        print(a);
        String[] b = {"大哥","二哥","三哥","四哥","五哥","六哥","小弟"};
        print(b);
        Double[] c = {6.0,6.6,6.66,6.666,6.6666};
        print(c);
    }
    /**1.泛型可以实现通用代码的编写,使用E表示元素的类型是Element类型 -- 可以理解成神似多态*/
    /**2.泛型的语法要求:如果在方法上使用泛型,必须两处同时出现,一个是传入参数的类型,一个是返回值前的泛型类型,表示这是一个泛型*/
    private static <E> void print(E[] e) {
        for(E d :e) {
            System.out.println(d);
        }
    }
//  public static void print(Double[] c) {
//      for(Double d : c) {
//          System.out.println(d);
//      }
//  }
//
//  public static void print(String[] b) {
//      for(String s : b) {
//          System.out.println(s);
//      }
//  }

第九章:Collection接口

前言:
Java语言的java.util包中提供了一些集合类,这些集合类又称之为容器
提到容器不难想到数组,集合类与数组最主要的不同之处是,数组的长度是固定的,集合的长度是可变的
数组的访问方式比较单一,插入/删除等操作比较繁琐,而集合的访问方式比较灵活
常用的集合类有List集合,Set集合,Map集合,其中List集合与Set集合继承了Collection接口,各个接口还提供了不同的实现类.

概述:

集合的英文名称是Collection,是用来存放对象的数据结构,而且长度可变,可以存放不同类型的对象,并且还提供了一组操作成批对象的方法.Collection接口层次结构 中的根接口,接口不能直接使用,但是该接口提供了添加元素/删除元素/管理元素的父接口公共方法.
由于List接口与Set接口都继承了Collection接口,因此这些方法对于List集合和Set集合是通用的.

集合的继承结构:

Collection接口
–List 接口 : 数据是有下标的,所以数据是有序的,可以存重复值
–ArrayList子类
–LinkedList子类
–Set 接口 : 数据是没有下标的,所以数据是无序的,不可以存重复的值
–HashSet子类
–Map 接口 : 键值对的方式存数据
–HashMap子类
在这里插入图片描述

常用方法

在这里插入图片描述

 public static void main(String[] args) {
        //1.创建Collection接口相关对象
        //Collection c = new Collection();//报错,因为Collection是接口不能实例化new
        /**1.<Integer>是泛型,用来约束集合中的元素的类型,只能写引用类型,不能是基本类型*/
        Collection<Integer> c = new ArrayList<Integer>();
        //2.1测试常用方法--对于单个集合的方法
        c.add(100);//向集合中添加元素
        c.add(200);//向集合中添加元素
        c.add(300);//向集合中添加元素
        c.add(400);//向集合中添加元素
        c.add(500);//向集合中添加元素
        System.out.println(c);//直接打印查看集合中的元素
        //c.clear();//清空集合中的元素
        //System.out.println(c);
        System.out.println( c.contains(300) );//true,判断集合中是否包含元素300
        System.out.println( c.hashCode() );//127240651,返回集合对应的哈希码值
        System.out.println( c.isEmpty() );//false,判断集合是否为空
        System.out.println( c.remove(100) );//true,移出集合中的元素100,移出成功返回true
        System.out.println( c );//[200, 300, 400, 500],100被成功移除
        System.out.println( c.size() );//4,获取集合的元素个数/类似数组长度
        System.out.println( c.equals(200) );//false,判断是否与100相等
        Object[] array = c.toArray();//把集合中的元素放入数组
        System.out.println(Arrays.toString(array));//使用数组的工具类查看数组中的元素内容
        //2.2测试常用方法--集合间的操作
        Collection<Integer> c2 = new ArrayList<Integer>();
        c2.add(2);//给c2集合添加元素
        c2.add(4);//给c2集合添加元素
        c2.add(6);//给c2集合添加元素
        System.out.println(c2);//[2, 4, 6],直接打印查看c2集合的内容
        c.addAll(c2);//把c2集合添加到c集合中
        System.out.println(c);//[200, 300, 400, 500, 2, 4, 6],追加操作
        System.out.println(c.contains(c2));//false
        System.out.println(c.containsAll(c2));//true,查看c集合是否包含c2集合中的所有元素
        System.out.println(c.removeAll(c2));//true,删除c集合中属于c2集合的所有元素
        System.out.println(c);//[200, 300, 400, 500],查看c集合删除c2集合后的结果,正确删除
        //System.out.println(c.retainAll(c2));//true,删除c集合
        //System.out.println(c);//[]
        //2.3 用来遍历/迭代集合中的元素 Iterator<E> iterator()
        /**
         * 1.如何获取迭代器 c.iterator()
         * 2.判断集合是否有下个元素 it.hasNext()
         * 3.获取当前迭代到的元素 it.next()
         */
        Iterator<Integer> it = c.iterator();
        //通过iterator迭代器,循环获取集合中的元素
        while(it.hasNext()) {
            //hasNext()用来判断集合中是否有下个元素,有就返回true,继续循环取值
            Integer num = it.next();//next()用来获取迭代到的元素
            System.out.println(num);
        }
    }

List接口

概述:

有序的colletion(也称为序列).此接口的用户可以对列表中的每个元素的插入位置进行精确的控制,用户可以根据元素的整数索引(在列表中的位置)来访问元素,并搜索列表中的元素.

特点:

  1. 元素都有下标
  2. 数据是有序的
  3. 允许存放重复的元素

常用方法:在这里插入图片描述

 public static void main(String[] args) {
        //1.创建List接口对象,注意此处创建的多态对象,List是接口,不能直接实例化
        List<String> list = new ArrayList<String>();
        //2.测试继承自Colletion接口的方法
        list.add("大力娃");//向集合中添加元素
        list.add("千顺娃");
        list.add("头铁娃");
        list.add("喷火娃");
        list.add("喷水娃");
        list.add("隐身娃");
        list.add("小紫娃");
        System.out.println(list);//查看集合中的所有元素
        //list.clear();//清空集合
        //System.out.println(list);//[],集合已清空
        System.out.println("**********我是一个无情的分界线**************");
        System.out.println( list.contains("大力娃"));//判断是否包含元素"大哥"
        System.out.println( list.equals("头铁娃"));//判断list对象是否与"二哥"相等
        System.out.println( list.hashCode());//获取集合的哈希码值
        System.out.println( list.isEmpty());//判断集合是否为空
        System.out.println( list.remove("喷水娃"));//移除集合中的元素"五哥"
        System.out.println( list );//查看集合中的内容
        System.out.println( list.size());//获取集合的元素个数,类似于数组的长度
        //将集合中的元素存入数组中,打印需要使用数组的工具类Arrays
        System.out.println( Arrays.toString(list.toArray()));//[大哥, 二哥, 三哥, 四哥, 六哥, 七弟]
        //3.List接口的特有方法  -- 都是可以根据索引来操作的方式
        list.add("小蝴蝶");//追加在最后
        System.out.println(list);
        list.add(1,"蝎子精");//在指定索引处添加指定的元素
        System.out.println(list);
        System.out.println(list.get(2));//获取指定下标对应的元素
        list.add(3,"小蝴蝶");
        System.out.println(list);
        System.out.println(list.indexOf("小蝴蝶"));//获取指定元素第一次出现的索引
        System.out.println(list.lastIndexOf("小蝴蝶"));//获取指定元素最后一次出现的索引
        System.out.println(list.remove(6));//移除指定索引的元素
        System.out.println(list.set(0, "妖精蛇"));//重置指定索引处位置的值
        System.out.println(list);
        List<String> subList = list.subList(2, 6);//截取子串,[2,6)含头不含尾
        System.out.println(subList);
        //4.集合间的操作
        List<String> list2 = new ArrayList<String>();
        list2.add("1");
        list2.add("2");
        list2.add("3");
        System.out.println( list.addAll(list2) );//把list2集合添加到list集合中
        System.out.println( list.addAll(1,list2) );//把list2集合添加到list集合的指定位置处
        System.out.println( list );
        System.out.println( list.contains(list2) );//判断list集合是否有一个元素是list2
        System.out.println( list.containsAll(list2) );//判断list集合是否包含list2集合中的所有元素
        System.out.println( list.removeAll(list2) );//删除list集合中list2集合中的所有元素
        System.out.println( list);
    }
 //3.测试集合的迭代
        /**
         * 集合迭代的方式:
         * 1.for循环
         * 2.增强for循环
         * 3.iterator
         * 4.listIterator
         * */
        //方式一:因为List集合是有序的,元素有下标,所以可以根据下标进行遍历
        //从何开始:0 到哪结束:list.size() 如何变化:i++
        for(int i = 0 ; i< list.size() ; i++) {
            //根据对应的下标来获取集合对应位置上的元素
            String s = list.get(i);
            System.out.println(s);
        }
        System.out.println("***********我是一个无情的分割线1***************");
        //方式二:普通for循环遍历效率低,可以通过foreach提高遍历效率
        //好处:语法简洁效率高 坏处:不能按照下标来处理数据
        //格式:for(1 2 : 3){循环体} 3是要遍历的数据 1和2是遍历得到的单个数据的类型 和 名字
        for(String s : list) {//s就是本次循环/遍历得到的集合中的元素
            System.out.println(s);
        }
        System.out.println("***********我是一个无情的分割线2***************");
        //方式三:iterator() 是继承自父接口Collection的
        /**1.获取当前集合的迭代器*/
        Iterator<String> it = list.iterator();
        /**由于不清楚要遍历的集合中有多少元素,所以我们使用的循环结构是While*/
        while(it.hasNext()) {//判断集合中是否有下个元素,如果有,返回true,继续遍历
            String s = it.next();//获取对应的元素
            System.out.println(s);
        }
        System.out.println("***********我是一个无情的分割线3***************");
        //方式四:listIterator()是List接口特有的
        //Iterator<E> Iterator --父接口 --hasNext() --next() --remove()
        //ListIterator<E> ListIterator --子接口,拥有父接口的方法,也有自己特有的方法(逆向迭代)
        //public interface ListIterator<E> extends Iterator<E>
        ListIterator<String> it2 = list.listIterator();
        while(it2.hasNext()) {//判断是否还有数据
            String s = it2.next();//获取当前遍历得到的数据
            System.out.println(s);
        }
        //思考:方式3和方式4有什么区别?
        //3使用的是父接口中的Iterator 4使用的是子接口中的ListIterator
        //子接口拥有父接口的所有方法,除此之外,子接口也可以拥有自己特有的方法,目前是向前/逆向遍历

ArrayList

概述:

  1. 存在java.util包中
  2. 内部是用数组结构存放数据,封装数组的操作,每个对象都有下标
  3. 内部数组默认的初始容量是10,如果不够会以1.5倍的容量增长
  4. 查询快,增删数据效率会低

创建对象:
ArrayList() 构造一个初始容量为10的空序列
源码摘抄:int newCapacity = oldCapacity + (oldCapacity >> 1);
解释:数组的新容量 = 旧容量/2的一次方 --相当于原来的1.5倍扩容

 //1.创建对象,使用的是无参构造
        //底层会自动帮我们创建数组存放对象,并且数据的初始容量是10
        ArrayList<Integer> list = new ArrayList();//简写方式
        //2.放入数据进行测试常用方法
        list.add(100);//向集合中添加元素
        list.add(200);//向集合中添加元素
        list.add(300);//向集合中添加元素
        list.add(400);//向集合中添加元素
        list.add(300);//向集合中添加元素
        list.add(200);//向集合中添加元素
        list.add(0,777);//在指定下标处新增元素
        System.out.println(list);
        //list.clear();//清空集合
        //System.out.println(list);
        System.out.println(list.contains(300));//true,判断集合是否包含元素300
        System.out.println(list.get(0));//777,获取集合中指定下标位置上的元素
        System.out.println(list.indexOf(200));//2,判断集合中指定元素第一次出现的下标
        System.out.println(list.lastIndexOf(200));//6,判断集合中指定元素最后一次出现的下标
        System.out.println(list.isEmpty());//false,判断集合是否为空
        System.out.println(list.remove(1));//100,移除集合中指定下标对应着的元素,移除成功,返回被移除的元素
        /**
         *Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 300, Size: 6
          *这是根据下标来删除元素的,而此集合没有下标300,最大下标为6,所以会数组下标越界
         *System.out.println(list.remove(300));--错误
         *如果想根据具体的元素内容移除元素,需要先把int类型的数据转成Integer数据类型
         * * */
        System.out.println(list.remove(Integer.valueOf(300)));//true
        System.out.println(list);
        System.out.println(list.set(2, 77));//更改集合中对应下标上元素的值
        System.out.println(list);
        System.out.println(list.size());
        System.out.println(Arrays.toString(list.toArray()));//将集合元素存入数组,打印数组的具体值

4种迭代方式

        //方式1:for循环
        System.out.println("方式1");
        //开始:0   结束:最大下标(集合长度-1)  变化:++
        for (int i = 0; i < list.size(); i++) {
            System.out.print(list.get(i));//根据下标获取对应下标位置上的元素
        }
        //方式2:增强for循环
        System.out.println("方式2");
        for (Integer i : list) {//遍历list集合,每次循环得到的元素是Integer类型的i
           System.out.print(i);//打印每次循环到的集合元素
        }
        //方式3:Iterator
        /**1.获取迭代器  2.判断是否还有元素(一般用来做循环条件)  3.获取当前遍历到的元素*/
        System.out.println("方式3");
        Iterator<Integer> it = list.iterator();//获取集合用来迭代的迭代器,此迭代器是继承自Collection接口中的
        while(it.hasNext()) {//通过迭代器来判断集合中是否还有元素,如果有,继续迭代,如果没有,结束循环
            Integer num = it.next();//获取当前遍历到的集合元素
            System.out.print(num);//打印当前遍历到的集合元素
        }
        //方式4:ListIterator
        System.out.println("方式4");
        ListIterator<Integer> it2 = list.listIterator();//获取集合用来迭代的迭代器,此迭代器是List接口中的迭代器
        while(it2.hasNext()) {//通过迭代器来判断集合中是否还有元素,如果有继续迭代,如果没有,结束循环
            Integer s2 = it2.next();//获取当前遍历到的集合元素
            System.out.print(s2);//打印当前遍历到的集合元素
        }

ArrayList扩容

ArrayList相当于在没指定initialCapacity时就是会使用延迟分配对象数组空间,当第一次插入元素时才分配10(默认)个对象空间。假如有20个数据需要添加,那么会分别在第一次的时候,将ArrayList的容量变为10;之后扩容会按照1.5倍增长。也就是当添加第11个数据的时候,Arraylist继续扩容变为10*1.5=15;当添加第16个数据时,继续扩容变为15 * 1.5 =22个

ArrayList没有对外暴露其容量个数,查看源码我们可以知道,实际其值存放在elementData对象数组中,那我们只需拿到这个数组的长度,观察其值变化了几次就知道其扩容了多少次。怎么获取呢?只能用反射技术了。

LinkedList

概述:

链表,两端效率高,底层就是链表实现的

总结:
ArrayList底层是数组结构,查询快,增删慢,适合查询较多的场景
LinkedList底层是链表结构,查询慢,增删快,适合增删操作较多的场景
注意:LinkedList查询慢是指数据量大时,查询中间要慢,首位操作还是比较快的

创建对象:
LinkedList() 构造一个空列表

常用方法

void addFirst(E e) 将指定元素插入此列表的开头
void addLast(E e) 将指定元素添加到此列表的结尾
       E getFirst() 返回此列表的第一个元素
E getLast() 返回此列表的最后一个元素
E removeFirst()移除并返回此列表的第一个元素
E removeLast() 移除并返回此列表的最后一个元素
E element() 获取但不移除此列表的头(第一个元素)
boolean offer(E e) 将指定元素添加到此列表的末尾(最后一个元素)
boolean offerFirst(E e) 在此列表的开头插入指定的元素
boolean offerLast(E e) 在此列表末尾插入指定的元素
       E peek() 获取但不移除此列表的头(第一个元素)
E peekFirst() 获取但不移除此列表的第一个元素;如果此列表为空,则返回 null
E peekLast() 获取但不移除此列表的最后一个元素;如果此列表为空,则返回 null
E poll()获取并移除此列表的头(第一个元素)
E pollFirst() 获取并移除此列表的第一个元素;如果此列表为空,则返回 null
E pollLast() 获取并移除此列表的最后一个元素;如果此列表为空,则返回 null
 //1.创建对象
        LinkedList<String> list = new LinkedList();
        //2.添加数据
        list.add("孙悟空");
        list.add("猪八戒");
        list.add("唐三藏");
        list.add("沙师弟");
        list.add("白龙马");
        System.out.println(list);
        //3.1自行测试从collection继承过来的共性方法测试
        //3.2 LinkedList特有方法测试
        list.addFirst("蜘蛛精");//添加首元素
        list.addLast("玉兔精");//添加尾元素
        System.out.println(list);
        System.out.println(list.getFirst());//获取首元素
        System.out.println(list.getLast());//获取尾元素
        System.out.println(list.removeFirst());//移除首元素,成功移除会返回移除的数据
        System.out.println(list);
        System.out.println(list.removeLast());//移除尾元素,成功移除会返回移除的数据
        System.out.println(list);
//4.1创建对象
        LinkedList<String> list2 = new LinkedList();
        //4.2添加数据
        list2.add("水浒传");
        list2.add("三国演义");
        list2.add("西游记");
        list2.add("红楼梦");
        System.out.println(list2);
        System.out.println(list2.element());//获取但不移除此列表的首元素(第一个元素)
        /**别名:查询系列*/
        System.out.println(list2.peek());//获取但不移除此列表的首元素(第一个元素)
        System.out.println(list2.peekFirst());//获取但不移除此列表的首元素(第一个元素)
        System.out.println(list2.peekLast());//获取但不移除此列表的尾元素(最后一个元素)
        /**别名:新增系列*/
        System.out.println(list2.offer("遮天"));//将指定元素添加到列表末尾
        System.out.println(list2.offerFirst("斗罗大陆"));//将指定元素插入列表开头
        System.out.println(list2.offerLast("斗破苍穹"));//将指定元素插入列表末尾
        System.out.println(list2);
        /**别名:移除系列*/
        System.out.println(list2.poll());//获取并且移除此列表的首元素(第一个元素),成功移除,返回移除元素
        System.out.println(list2.pollFirst());//获取并且移除此列表的首元素(第一个元素),成功移除,返回移除元素,如果此列表为空,则返回null
        System.out.println(list2.pollLast());//获取并且移除此列表的尾元素(最后一个元素),成功移除,返回移除元素,如果此列表为空,则返回null
        System.out.println(list2);

set接口

概述:

  1. Set是一个不包含重复数据的Collection
    
  2. Set集合中的数据是无序的(因为Set集合没有下标)
    
  3. Set集合中的元素不可以重复 – 常用来给数据去重
    

Set集合的特点:

  1. 数据无序且数据不允许重复
    
  2. HashSet : 底层是哈希表,包装了HashMap,相当于向HashSet中存入数据时,会把数据作为K,存入内部的HashMap中。当然K仍然不许重复。
    
  3. TreeSet : 底层是TreeMap,也是红黑树的形式,便于查找数据
    

常用方法:

学习Collection接口中的方法即可

//1.创建对象
        //Set s = new Set();//报错,Set是接口,接口不可以实例化,也就是创建对象
        Set<String> set = new HashSet<String>();
        //2.set集合数据存放测试
        set.add("牛气冲天");//向set集合添加数据
        set.add("牛气冲天");//向set集合添加重复的数据
        set.add("牛气冲天");//向set集合添加重复的数据
        set.add("虎虎生威");//向set集合添加数据
        //set.add("null");//向set集合添加字符串类型的null数据
        set.add(null);//向set集合添加数据null
        /**总结1:set集合中的元素都是无序的*/
        /**总结2:set集合中的元素不能重复*/
        /**总结3:set集合中可以存放null元素,也只允许存放0-1个*/
        System.out.println(set);//查看set集合中的元素
        //3.set集合常用方法测试
        //set.clear();//清空Set集合
        System.out.println(set.contains("小兔纸"));//false,判断set集合中是否包含指定元素"小兔纸"
        System.out.println(set.equals("牛气冲天"));//false,判断set集合对象与指定元素是否相等
        System.out.println(set.hashCode());//1961052313,获取当前set集合对象的哈希码
        System.out.println(set.isEmpty());//false,判断当前集合是否为空
        System.out.println(set.remove("null"));//false,移除指定元素,没有"null"元素,所以返回false
        System.out.println(set.remove(null));//true,成功移除指定元素null,所以返回true
        System.out.println(set);
        System.out.println(set.size());//2,获取当前set集合的元素个数,类似数组长度
        Object[] array = set.toArray();//把集合中的元素放入数组中
        System.out.println(Arrays.toString(array));//使用数组工具类查看数组中的元素
 //4.集合间的操作
        Set<String> set2 = new HashSet();
        set2.add("小老鼠");//给set2集合添加指定元素
        set2.add("小牛犊");//给set2集合添加指定元素
        set2.add("小脑斧");//给set2集合添加指定元素
        set2.add("小兔纸");//给set2集合添加指定元素
        System.out.println(set2);//查看set2集合中的元素
        System.out.println(set.addAll(set2));//true,把集合set2中的元素添加到set集合中,成功返回true
        System.out.println(set.containsAll(set2));//true,判断set集合中是否包含set2集合中的所有元素,如果包含返回true
        System.out.println(set.removeAll(set2));//ture,移除set集合中属于set2集合的所有元素
        System.out.println(set.containsAll(set2));//false,判断set集合中是否包含set2集合中的所有元素,不包含返回false
        System.out.println(set.retainAll(set2));
        /**retainAll()方法是取两个集合直接的公共部分,谁调用,影响谁*/
//      set.add("小海滕");
//      set2.add("小海滕");
//      System.out.println(set.retainAll(set));//set没变
//      System.out.println(set.retainAll(set2));//set剩set与set2的交集
//      System.out.println(set2.retainAll(set));//set2剩set2与set的交集
//      System.out.println(set);
//      System.out.println(set2);
        //5.集合的迭代
        Iterator<String> it = set2.iterator();//5.1获取集合的迭代器
        while(it.hasNext()) {//5.2判断集合是否有下个元素
            String s = it.next();//5.3如果有,进循环获取当前遍历到的元素
            System.out.println(s);
        }
    }

HashSet

概述:

底层是哈希表,包装了HashMap,相当于向HashSet中存入数据时,会把数据作为K存入内部的HashMap中,其中K不允许重复,允许使用null.

//1.创建HashSet对象
        HashSet<Integer> set = new HashSet();
        //2.向HashSet集合添加元素
        set.add(100);
        set.add(200);
        set.add(300);
        set.add(200);
        set.add(200);
        /**总结1:HashSet中的元素没有顺序,且不允许重复*/
        System.out.println(set);
        //3.测试常用方法
        //set.clear();//清空set集合
        System.out.println(set.contains(200));//true,判断集合是否包含指定元素
        System.out.println(set.isEmpty());//false,判断集合是否为空
        System.out.println(set.remove(100));//true,移除集合中的指定元素
        System.out.println(set.size());
        //4.迭代set集合
        Iterator<Integer> it = set.iterator();//获取集合的迭代器用来遍历
        while(it.hasNext()) {//判断集合中是否有下一个元素,没有则跳出循环
            Integer num = it.next();//获取当前遍历到的元素
            System.out.println(num);//打印当前遍历到的元素
        }

去重:

//总结:

//1.如果想用set集合给自定义的对象去重,那么需要在自己的类中同时提供重写的hashCode()与equals()
//底层源码: if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
//重写hashCode():我们并不想使用自动计算出的哈希值,而是要根据对象的属性值进行计算,如果两个对象的属性值都相同,想生成同一个哈希码
//重写equals():我们想比较的不是对象的地址值(==),而是如果两个对象的属性值都一样,则返回true

  //1.创建set集合对象
        Set<Student> set = new HashSet<Student>();
        //新版JDK中后面的泛型类型与尖括号都可以不写,三种方式皆可,想用哪个用哪个
//      Set<Student> set = new HashSet<>();
//      Set<Student> set = new HashSet();
        //2.1创建自定义对象
        Student s1 = new Student("tony",38,"BeiJing");
        Student s2 = new Student("susan",20,"ShangHai");
        Student s3 = new Student("Jack",3,"ShenZhen");
        //创建对象,与之前对象的属性值完全一致
        Student s4 = new Student("susan",20,"ShangHai");
        Student s5 = new Student("Jack",3,"ShenZhen");
        //3.查看两个对象的哈希码
        System.out.println("s2对象的哈希码:"+s2.hashCode());
        System.out.println("s4对象的哈希码:"+s4.hashCode());
        //2.2把自定义的student对象添加到set集合中
        set.add(s1);
        set.add(s2);
        set.add(s3);
        set.add(s4);
        set.add(s5);
        System.out.println(set);

总结:
重复的给set集合添加了属性相同的对象,为什么没有像之前那样去重呢?
翻阅源码,得知:需要保证两个条件:
1.保证对象拥有相同的哈希码值
底层默认使用的是Object提供的hashCode()来计算哈希码值,每次new对象,默认的哈希码值是不同的
解决方案:如果想根据两个对象的属性值来计算哈希值,就需要重写hashCode()
2.保证两个对象的equals()返回true
–底层默认使用的是Object提供的逻辑,==比较,也就是说当地址值相同时,才返回true
解决方案:重写equals()

Map接口

概述:

Java.util接口Map<K,V>
类型参数 : K - 表示此映射所维护的键 V – 表示此映射所维护的对应的值
也叫做哈希表、散列表. 常用于键值对结构的数据.其中键不能重复,值可以重复
在这里插入图片描述

特点:

  1. Map可以根据键来提取对应的值
  2. Map的键不允许重复,如果重复,对应的值会被覆盖
  3. Map存放的都是无序的数据
  4. Map的初始容量是16,默认的加载因子是0.75

TIPS:源码摘抄:

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
初始容量1<<4,相当于1*(2^4),也就是16
static final float DEFAULT_LOAD_FACTOR = 0.75f;
默认的加载因子是0.75f,也就是存到75%开始扩容,按照2的次幂进行扩容
 

继承结构

在这里插入图片描述

常用方法:

学习Collection接口中的方法即可
void clear() 从此映射中移除所有映射关系(可选操作)。
boolean containsKey(Object key) 如果此映射包含指定键的映射关系,则返回 true。
boolean containsValue(Object value) 如果此映射将一个或多个键映射到指定值,则返回 true。
Set<Map.Entry<K,V>> entrySet() 返回此映射中包含的映射关系的 Set 视图。

boolean equals(Object o) 比较指定的对象与此映射是否相等。
V get(Object key) 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null。
int hashCode() 返回此映射的哈希码值。
boolean isEmpty() 如果此映射未包含键-值映射关系,则返回 true。
Set<K> keySet() 返回此映射中包含的键的 Set 视图。
V put(K key, V value) 将指定的值与此映射中的指定键关联(可选操作)。
void putAll(Map<? extends K,? extends V> m)从指定映射中将所有映射关系复制到此映射中(可选操作)。
V remove(Object key) 如果存在一个键的映射关系,则将其从此映射中移除(可选操作)。
int size() 返回此映射中的键-值映射关系数。
Collection values() 返回此映射中包含的值的 Collection 视图。

 //1.创建Map对象
        //map中的数据要符合映射规则,一定注意要同时指定K和V的数据类型
        //至于K和V需要指定成什么类型的数据,取决于你的具体需求
        Map<Integer,String> map = new HashMap();//注意导包:java.util
        //2.常用方法测试
        //添加数据,需要同时指定K和V
        map.put(9527, "白骨精");//向map集合添加数据
        map.put(9528, "黑熊精");//向map集合添加数据
        map.put(9528, "唐三藏");
        map.put(9529, "者行孙");
        /**
         * 总结1:Map存放的都是无序数据
         * 总结2:Map中的key不可以重复,如果重复,此Key对应的值会被覆盖
         *map打印结果: {9527=白骨精, 9528=唐三藏, 9529=者行孙}
         */
        //查看map集合中的元素
        System.out.println(map);
        //map.clear();//清空map集合
        System.out.println(map.containsKey(9527));//true,判断当前map集合是否包含指定的key
        System.out.println(map.containsValue("土地老儿"));//false,判断当前map集合是否包含指定的value
        System.out.println(map.equals("者行孙"));//false,判断"者行孙"与map是否相等
        System.out.println(map.get(9529));//者行孙,根据对应的key来获取对应的value
        System.out.println(map.hashCode());//84598429,获取当前map集合的哈希码
        System.out.println(map.isEmpty());//false,判断当前map集合是否为空
        System.out.println(map.remove(9529));//者行孙,删除map中key对应的value,正确删除后返回被删除元素
        System.out.println(map.get(9529));//null,没有拿到任何元素,根据指定的key获取对应value
        System.out.println(map.size());//2,获取集合中元素的个数
        Collection<String> values = map.values();//把map中的所有value收集起来放到collection中
        System.out.println(values);//[白骨精, 唐三藏]

在这里插入图片描述

Map集合迭代:

//对map集合进行迭代
        /**方式一
         * 遍历map中的数据,需要把map集合转换成set集合
         * Set<K> keySet() : 把map集合中的所有的key存到set集合中
         * */
        Set<Integer> keySet = map.keySet();
        //想要遍历set集合,需要先拿到集合的迭代器对象
        Iterator<Integer> it = keySet.iterator();
        while(it.hasNext()) {//判断集合中是否有下个元素,如果有,继续迭代,如果没有,跳出循环
            Integer key = it.next();//依次获取/set集合中的每一个key
            String value = map.get(key);//通过key获取对应的value
            System.out.println("{" + key + "," + value + "}");
        }
        /**方式二
         * 遍历map中的数据,需要把map集合转换成set集合
         * Set<Entry<Integer,String>> entrySet() : 把map集合中的一组key&value数据整体放入set中
         * 一对儿 K,V 是一个Entry
         */
        Set<Entry<Integer, String>> entrySet = map.entrySet();
        //遍历set集合,得到每个Entry对象
        //获取此set集合对应的迭代器对象it2
        Iterator<Entry<Integer, String>> it2 = entrySet.iterator();
        while(it2.hasNext()) {
            Entry<Integer, String> entry = it2.next();
            Integer key = entry.getKey();
            String value = entry.getValue();
            System.out.println("[" + key + ":" + value + "]");
        }

HashMap

前言

HashMap的键要同时重写hashCode()和equlas()
hashCode()用来判定二者的hash值是否相同,重写后根据属性生成
equlas()用来判断属性的值是否相同,重写后,根据属性判断
–equlas()判断数据如果相等,hashCode()必须相同
–equlas()判断数据如果不等,hashCode()尽量不同

概述:

HashMap底层是一个Entry[ ]数组,当存放数据时,会根据hash算法来计算数据的存放位置
算法:hash(key)%n , n就是数组的长度,其实也就是集合的容量
当计算的位置没有数据的时候,会直接存放数据
当计算的位置,有数据时,会发生hash冲突/hash碰撞,解决的办法就是采用链表的结构,在对应的数据位置存放链表的头节点,对于这个链表来说,每次新加的节点会从头部位置开始加入,也就是说,数组中的永远是新节点.

在这里插入图片描述

HashMap<Integer,String> map = new HashMap();
        /**
         * 源码摘抄:
         * static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
         * 初始容量为1<<4,相当于1*(2^4)=16
         * static final float DEFAULT_LOAD_FACTOR = 0.75f;
         * 默认的加载因子是0.75,也就是说存到75%开始扩容,按照2的次幂进行扩容
         */
        /*
         * 达到容量的加载因子后,就会重新开辟空间,重新计算所有对象的存储位置,也叫做rehash
         * 设置初始容量与加载因子要讲求相对平衡,如果加载因子过低,则rehash过于频繁,影响性能
         * 如果初始容量设置太高或者加载因子设置太高,影响查询效率
         */

拓展

加载因子:
static final float DEFAULT_LOAD_FACTOR = 0.75f;
前面的讲述已经发现,当你空间只有仅仅为10的时候是很容易造成2个对象的hashcode 所对应的地址是一个位置的情况。这样就造成 2个 对象会形成散列桶(链表)。这时就有一个加载因子的参数,值默认为0.75 ,如果你hashmap的 空间有 100那么当你插入了75个元素的时候 hashmap就需要扩容了,不然的话会形成很长的散列桶结构,对于查询和插入都会增加时间,因为它要一个一个的equals比较。但又不能让加载因子很小,如0.01,这样显然是不合适的,频繁扩容会大大消耗你的内存。这时就存在着一个平衡,jdk中默认是0.75,当然负载因子可以根据自己的实际情况进行调整。

第十章:进程与线程

进程

概念:

进程就是正在运行的程序,它代表了程序所占用的内存区域

特点:

== 独立性==
进程是系统中独立存在的实体,它可以拥有自己独立的资源,每个进程都拥有自己私有的地址空间,在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间.
== 动态性==
进程与程序的区别在于,程序只是一个静态的指令集合,而进程一个正在系统中活动的指令集合,程序加入了时间的概念以后,称为进程,具有自己的生命周期和各种不同的状态,这些概念都是程序所不具备的.
并发性
多个进程可以在单个处理器CPU上并发执行,多个进程之间不会互相影响.

并行和并发

在这里插入图片描述
TIPS: HA–高可用 在高并发的环境下,系统如何完成正常功能供给

线程

概念:

线程是操作系统OS能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.
一个进程可以开启多个线程,其中有一个主线程来调用本进程中的其他线程
我们看到的进程的切换,切换的也是不同进程的主线程
多线程扩展了多进程的概念,使的同一个进程可以同时并发处理多个任务
简而言之,一个程序运行后至少一个进程,一个进程里包含一个线程(单线程)或者多个线程(多线程)
在这里插入图片描述

进程与线程的关系

一个操作系统中可以有多个进程,一个进程中可以有多个线程
每个线程有自己独立的内存,每个线程共享一个进程中的内存,每个线程又有自己独立的内存(这个非常重要!!!)
所以想使用线程技术,得先有进程,进程的创建是OS操作系统创建的.我们不能实现,一般都是C或者C++完成
在这里插入图片描述

多线程的特性

线程的随机性指的是同一时刻,只有一个程序在执行
我们宏观上觉得这些程序像是同时运行,但是实际上微观时间是因为CPU在高效的切换着,这使得各个程序从表面上看是同时进行的,也就是说,宏观层面上,所有的进程/线程看似同时运行,但是微观层面上,同一时刻,一个CPU只能处理一件事.
时间单位:1/ms甚至更快,切换的速度是纳秒级别的,非常快
在这里插入图片描述

CPU分时调度

在这里插入图片描述
时间片,即CPU分配给各个线程的一个时间段,称作它的时间片,即该线程被允许运行的时间,如果在时间片用完时线程还在执行,那CPU将被剥夺并分配给另一个线程,将当前线程挂起,如果线程在时间片用完之前阻塞或结束,则CPU当即进行切换,从而避免CPU资源浪费,当再次切换到之前挂起的线程,恢复现场,继续执行。
注意:我们无法控制OS如何选择以及选择哪些线程来执行,OS底层有自己规则:
FCFS(First Come First Service 先来先服务算法) SJS(Short Job Service短服务算法)

线程的状态

由于线程状态比较复杂,所以我们由易到难,先学习基础模型,线程的三种状态及其转换,简称”三态模型”
在这里插入图片描述
l 就绪(可运行)状态:线程已经准备好运行,只要获得CPU,就可立即执行
l 执行(运行)状态:线程已经获得CPU,其程序正在运行的状态
l 阻塞状态:正在运行的线程由于某些事件(I/O请求等)暂时无法执行的暂停状态,即线程执行阻塞。
? 就绪–>执行:为就绪线程分配CPU即可变为执行状态
? 执行–>就绪:正在执行的线程由于时间片用完被剥夺CPU暂停执行,就变为就绪状态
? 执行–>阻塞:由于发生某事件,使正在执行的线程受阻,无法执行,则由执行变为阻塞
(例如线程正在访问临界资源,而资源正在被其他线程访问)
反之,如果获得了之前需要的资源,则由阻塞变为就绪状态,等待分配CPU再次执行
在这里插入图片描述
创建状态:线程的创建比较复杂,需要先申请PCB,然后为该线程运行分配必须的资源,并将该线程转为就绪状态插入到就绪队列中
终止状态:等待OS进行善后处理,最后将PCB清零,并将PCB返回给系统

TIPS:PCB(Process Control Block):为了保证参与并发执行的每个线程都能独立运行,OS配置了特有的数据结构PCB来描述线程的基本情况和活动过程,进而控制和管理线程
在这里插入图片描述

线程生命周期,主要有五种状态:

  1. 新建状态(New) : 当线程对象创建后就进入了新建状态.如:Thread t = new MyThread();
  2. 就绪状态(Runnable):当调用线程对象的start()方法(t.start()😉,线程即为进入就绪状态.
    处于就绪(可运行)状态的线程,只是说明线程已经做好准备,随时等待CPU调度执行,并不是执行了t.start()此线程立即就会执行
  3. 运行状态(Running):当CPU开始调度处于就绪状态的线程时,此线程才是真正的执行,即进入到运行状态
    注意:就绪状态是进入运行状态的唯一入口,也就是线程想要进入运行状态状态执行,先得处于就绪状态
  4. 阻塞状态(Blocked):处于运状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入就绪状态才有机会被CPU选中再次执行.
  5. 根据阻塞状态产生的原因不同,阻塞状态又可以细分成三种:
    a) 等待阻塞:运行状态中的线程执行wait()方法,本线程进入到等待阻塞状态
    b) 同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用),它会进入同步阻塞状态
    c) 其他阻塞:调用线程的sleep()或者join()或发出了I/O请求时,线程会进入到阻塞状态.当sleep()状态超时.join()等待线程终止或者超时或者I/O处理完毕时线程重新转入就绪状态
  6. 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期

多线程代码创建方式

继承Thread

概述:
Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例
启动线程的唯一方法就是通过Thread类的start()实例方法
start()方法是一native方法,它将通知底层操作系统,.最终由操作系统启动一个新线程,操作系统将执行run()
这种方式实现的多线程很简单,通过自己的类直接extends Thread,并重写run()方法,就可以自动启动新线程并执行自己定义的run()方法
模拟开启多个线程,每个线程调用run()方法.
常用方法:
构造方法:

Thread() 分配新的Thread对象
Thread(String name) 分配新的Thread对象
Thread(Runnable target) 分配新的Thread对象
Thread(Runnable target,String name) 分配新的Thread对象

普通方法:

static Thread currentThread( )
返回对当前正在执行的线程对象的引用
long getId()
返回该线程的标识
String getName()
返回该线程的名称
void run()
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法
static void sleep(long millions)
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
void start()
使该线程开始执行:Java虚拟机调用该线程的run()

 public static void main(String[] args) {
        //4.创建线程对象进行测试
        /*4.new对应的是线程的新建状态
        * 5.要想模拟多线程,至少得启动2个线程,如果只启动1个,是单线程程序*/
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        MyThread t4 = new MyThread();
        /*6.这个run()如果直接这样调用,是没有多线程抢占执行的效果的
        * 只是把这两句话看作普通方法的调用,谁先写,就先执行谁*/
        //t1.run();
        //t2.run();
        /*7.start()对应的状态就是就绪状态,会把刚刚新建好的线程加入到就绪队列之中
        * 至于什么时候执行,就是多线程执行的效果,需要等待OS选中分配CPU
        * 8.执行的时候start()底层会自动调用我们重写的run()种的业务
        * 9.线程的执行具有随机性,也就是说t1-t4具体怎么执行
        * 取决于CPU的调度时间片的分配,我们是决定不了的*/
        t1.start();//以多线程的方式启动线程1,将当前线程变为就绪状态
        t2.start();//以多线程的方式启动线程2,将当前线程变为就绪状态
        t3.start();//以多线程的方式启动线程3,将当前线程变为就绪状态
        t4.start();//以多线程的方式启动线程4,将当前线程变为就绪状态
    }

//1.自定义一个多线程类,然后让这个类继承Thread
class MyThread extends Thread{
    /*1.多线程编程实现的方案1:通过继承Thread类并重写run()来完成的 */
    //2.重写run(),run()里是我们自己的业务
    @Override
    public void run() {
        /*2.super.run()表示的是调用父类的业务,我们现在要用自己的业务,所以注释掉*/
        //super.run();
        //3.完成业务:打印10次当前正在执行的线程的名称
        for (int i = 0; i < 10; i++) {
            /*3.getName()表示可以获取当前正在执行的线程名称
            * 由于本类继承了Thread类,所以可以直接使用这个方法*/
            System.out.println(i+"="+getName());
        }
    }
}

实现Runnable接口

概述:
如果自己的类已经extends另一个类,就无法多继承,此时,可以实现一个Runnable接口

常用方法:
void run()使用实现接口Runnable的对象创建线程时,启动该线程将导致在独立执行的线程中调用对象的run()方法

 public static void main(String[] args) {
        //5.创建自定义类的对象--目标业务类对象
        MyRunnable target = new MyRunnable();
        //6.如何启动线程?自己没有,需要与Thread建立关系
        Thread t1 = new Thread(target);
        Thread t2 = new Thread(target);
        Thread t3 = new Thread(target);
        Thread t4 = new Thread(target);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
//1.自定义多线程类
class MyRunnable implements Runnable{
    //2.添加父接口中的抽象方法run(),里面是自己的业务
    @Override
    public void run() {
        //3.写业务,打印10次当前正在执行的线程名称
        for (int i = 0; i < 10; i++) {
            /*问题:自定义类与父接口Runnable中都没有获取名字的方法
            * 所以还需要从Thread中找:
            * currentThread():静态方法,获取当前正在执行的线程对象
            * getName():获取当前线程的名称*/
            System.out.println(i+"="+Thread.currentThread().getName());
        }
    }
}

Callable接口

概述:
Callable接口是一种能够返回计算结果并且可以抛出异常的任务
Callable接口的实现类需要定义一个无参数的方法:call()。
执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。 FutureTask 是 Future 接口的实现类

public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();
        //1.执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。
        FutureTask<Integer> result = new FutureTask<>(td);
        new Thread(result).start();
        //2.接收线程运算后的结果
        try {
            Integer sum = result.get();  //FutureTask 可用于 闭锁 类似于CountDownLatch的作用,在所有的线程没有执行完成之后这里是不会执行的
            System.out.println(sum);
            System.out.println("------------------------------------");
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
class ThreadDemo implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= 100000; i++) {
            sum += i;
        }
        return sum;
    }
}

线程创建的其他方式

ExecutorService/Executors
ExecutorService:用来存储线程的池子,把新建线程/启动线程/关闭线程的任务都交给池来管理

execute(Runnable任务对象) 把任务丢到线程池

Executors 辅助创建线程池的工具类

newFixedThreadPool(int nThreads) 最多n个线程的线程池
newCachedThreadPool() 足够多的线程,使任务不必等待
newSingleThreadExecutor() 只有一个线程的线程池

 public static void main(String[] args) {
        //5.创建接口实现类TicketR3类的对象作为目标业务对象
        TicketR3 target = new TicketR3();
        /*Executors是用来辅助创建线程池的工具类对象
        * 常用方法是newFixedThreadPool(int)这个方法可以创建指定数目的线程池对象
        * 创建出来的线程池对象是ExecutorService:用来存储线程的池子,负责:新建/启动/关闭线程*/
        //6.使用Executors工具创建一个最多有5个线程的线程池对象ExecutorService池对象
        ExecutorService pool = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            /*execute()让线程池中的线程来执行业务,每次调用都会将一个线程加入到就绪队列*/
            pool.execute(target);/*本方法的参数就是你要执行的业务,也就是目标业务类对象*/
        }
    }
//同步锁问题解决方案笔记:1.4.1从26行复制到58行,TicketR2改成TicketR3
//1.创建自定义多线程类
class TicketR3 implements Runnable {
    //3.定义成员变量,保存票数
    int tickets = 100;
    //创建锁对象
    Object o = new Object();

    //2.实现接口中未实现的方法,run()中放着的是我们的业务
    @Override
    public void run() {
        //4.通过循环结构完成业务
        while (true) {
            /*3.同步代码块:synchronized(锁对象){会出现安全隐患的所有代码}
             * 同步代码块在同一时刻,同一资源只会被一个线程独享*/
            /*这种写法不对,相当于每个线程进来的时候都会new一个锁对象,线程间使用的并不是同一把锁*/
            //synchronized (new Object()){
            //修改同步代码块的锁对象为成员变量o,因为锁对象必须唯一
            synchronized (o) {//同步代码块解决的是重卖的问题
                //如果票数>0就卖票
                if (tickets > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //4.1打印当前正在售票的线程名以及票数-1
                    System.out.println(Thread.currentThread().getName() + "=" + tickets--);
                }
                //4.2退出死循环--没票的时候就结束
                if (tickets <= 0) break;
            }
        }
    }
}

票超卖

每次创建线程对象,都会生成一个tickets变量值是100,创建4次对象就生成了400张票了。不符合需求,怎么解决呢?能不能把tickets变量在每个对象间共享,就保证多少个对象都是卖这100张票。
解决方案: 用静态修饰
产生超卖,0 张 、-1张、-2张。
产生重卖,同一张票卖给多人。
多线程安全问题是如何出现的?常见情况是由于线程的随机性+访问延迟。
以后如何判断程序有没有线程安全问题?

同步锁

前言

经过前面多线程编程的学习,我们遇到了线程安全的相关问题,比如多线程售票情景下的超卖/重卖现象
在多线程程序中 + 有共享数据 + 多条语句操作共享数据

多线程的场景和共享数据的条件是改变不了的(就像4个窗口一起卖100张票,这个是业务)
所以思路可以从第3点"多条语句操作共享数据"入手,既然是在这多条语句操作数据过程中出现了问题
那我们可以把有可能出现问题的代码都包裹起来,一次只让一个线程来执行

同步与异步

那怎么"把有可能出现问题的代码都包裹起来"呢?我们可以使用synchronized关键字来实现同步效果
也就是说,当多个对象操作共享数据时,可以使用同步锁解决线程安全问题,被锁住的代码就是同步的

接下来介绍下同步与异步的概念:
同步:体现了排队的效果,同一时刻只能有一个线程独占资源,其他没有权利的线程排队。
坏处就是效率会降低,不过保证了安全。
异步:体现了多线程抢占资源的效果,线程间互相不等待,互相抢占资源。
坏处就是有安全隐患,效率要高一些。

synchronized同步关键字

1.写法:

synchronized (锁对象){
需要同步的代码(也就是可能出现问题的操作共享数据的多条语句);
}

2.同步效果的使用有两个前提:

前提1:同步需要两个或者两个以上的线程(单线程无需考虑多线程安全问题)
前提2:多个线程间必须使用同一个锁(我上锁后其他人也能看到这个锁,不然我的锁锁不住其他人,就没有了上锁的效果)

3.特点
1)synchronized同步关键字可以用来修饰代码块,称为同步代码块,使用的锁对象类型任意,但注意:必须唯一!
2)synchronized同步关键字可以用来修饰方法,称为同步方法
3)同步的缺点是会降低程序的执行效率,但我们为了保证线程的安全,有些性能是必须要牺牲的
4)但是为了性能,加锁的范围需要控制好,比如我们不需要给整个商场加锁,试衣间加锁就可以了

为什么同步代码块的锁对象可以是任意的同一个对象,但是同步方法使用的是this呢?
因为同步代码块可以保证同一个时刻只有一个线程进入
但同步方法不可以保证同一时刻只能有一个线程调用,所以使用本类代指对象this来确保同步

在这里插入图片描述

拓展:线程锁

悲观锁和乐观锁

悲观锁:像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态.
悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。

乐观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态.
乐观锁认为竞争不总是会发生,因此它不需要持有锁,将”比较-替换”这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。

两种常见的锁

synchronized 互斥锁(悲观锁,有罪假设)
采用synchronized修饰符实现的同步机制叫做互斥锁机制,它所获得的锁叫做互斥锁。
每个对象都有一个monitor(锁标记),当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池。任何一个对象系统都会为其创建一个互斥锁,这个锁是为了分配给线程的,防止打断原子操作。每个对象的锁只能分配给一个线程,因此叫做互斥锁。

ReentrantLock 排他锁(悲观锁,有罪假设)
ReentrantLock是排他锁,排他锁在同一时刻仅有一个线程可以进行访问,实际上独占锁是一种相对比较保守的锁策略,在这种情况下任何“读/读”、“读/写”、“写/写”操作都不能同时发生,这在一定程度上降低了吞吐量。然而读操作之间不存在数据竞争问题,如果”读/读”操作能够以共享锁的方式进行,那会进一步提升性能。

ReentrantReadWriteLock 读写锁(乐观锁,无罪假设)
因此引入了ReentrantReadWriteLock,顾名思义,ReentrantReadWriteLock是Reentrant(可重入)Read(读)Write(写)Lock(锁),我们下面称它为读写锁。
读写锁内部又分为读锁和写锁,读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。
读锁和写锁分离从而提升程序性能,读写锁主要应用于读多写少的场景。

class SaleTicketsV3 implements Runnable{
	static int tickets = 100;
	//1.定义可重入读写锁对象,静态保证全局唯一
	static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
	@Override
	public void run() {
		while(true) {
			//2.在操作共享资源前上锁
			lock.writeLock().lock();
			try {
				if(tickets > 0) {
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "=" + tickets--);
				}
				if(tickets <= 0) break;
			} catch (Exception e) {
				e.printStackTrace();
			}finally {
				//3.finally{}中释放锁,注意一定要手动释放,防止死锁,否则就独占报错了
				lock.writeLock().unlock();
			}
		}
	}
} 

两种方式的区别

需要注意的是,用sychronized修饰的方法或者语句块在代码执行完之后锁会自动释放,而是用Lock需要我们手动释放锁,所以为了保证锁最终被释放(发生异常情况),要把互斥区放在try内,释放锁放在finally内!
与互斥锁相比,读-写锁允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程)从理论上讲,与互斥锁定相比,使用读-写锁允许的并发性增强将带来更大的性能提高。

第十一章:注解与枚举类

枚举类

枚举类的实现

  1. JDK1.5之前需要自定义枚举类
  2. JDK 1.5 新增的 enum 关键字用于定义枚举类
  3. 若枚举只有一个对象, 则可以作为一种单例模式的实现方式。

枚举类的属性

  1. 枚举类对象的属性不应允许被改动, 所以应该使用 private final 修饰
  2. 枚举类的使用 private final 修饰的属性应该在构造器中为其赋值
  3. 若枚举类显式的定义了带参数的构造器, 则在列出枚举值时也必须对应的传入参数

自定义枚举类

  1. 使用 enum 定义的枚举类默认继承了 java.lang.Enum类,因此不能再继承其他类
  2. 枚举类的构造器只能使用 private 权限修饰符
  3. 枚举类的所有实例必须在枚举类中显式列出(, 分隔 ; 结尾)。列出的实例系统会自动添加 public static final 修饰
  4. 必须在枚举类的第一行声明枚举类对象
    JDK 1.5 中可以在 switch 表达式中使用Enum定义的枚举类的对象
    作为表达式, case 子句可以直接使用枚举值的名字, 无需添加枚举
    类作为限定。
public enum SeasonEnum {
SPRING("春天","春风又绿江南岸"),
SUMMER("夏天","映日荷花别样红"),
AUTUMN("秋天","秋水共长天一色"),
WINTER("冬天","窗含西岭千秋雪");
private final String seasonName;
private final String seasonDesc;
private SeasonEnum(String seasonName, String seasonDesc) {
this.seasonName = seasonName;
this.seasonDesc = seasonDesc; }
public String getSeasonName() {
return seasonName; }
public String getSeasonDesc() {
return seasonDesc; } }

Enum类的主要方法

1. values()方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的
枚举值。
2. valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符
串必须是枚举类对象的“名字”。如不是,会有运行时异常:
IllegalArgumentException。
3. toString():返回当前枚举类对象常量的名称

在这里插入图片描述

实现接口的枚举类

  1. 和普通 Java 类一样,枚举类可以实现一个或多个接口
  2. 若每个枚举值在调用实现的接口方法呈现相同的行为方式,则只
    要统一实现该方法即可。
  3. 若需要每个枚举值在调用实现的接口方法呈现出不同的行为方式,
    则可以让每个枚举值分别来实现该方法
/**
 * 定义接口
 */
interface Info {
    void describe();
}
/**
 *  枚举类实现接口的抽象方法
 */
public enum EnumImplement implements Info{

    /**
     *  需求:接口的抽象方法,在不同的对象实现不同的行为。
     *  实现方法:将接口的抽象方法在每个对象中重写实现不同的行为
     */
    SPRING("春天"){
        @Override
        public void describe() {
            System.out.println("春江水阿暖鸭先知");
        }
    },
    SUMMER("夏天"){
        @Override
        public void describe() {
            System.out.println("我爱山中夏,空冥花雨下。");
        }
    },
    AUTUMN("秋天"){
        @Override
        public void describe() {
            System.out.println("停车坐爱枫林晚,霜叶红于二月花。");
        }
    },
    WINTER("冬天"){
        @Override
        public void describe() {
            System.out.println("北国风光,千里冰封,万里雪飘。");
        }
    };
    //申明 EnumTest 对象的属性
    final String name;
    //提供有参构造器
    EnumImplement(String name) {
        this.name = name;
    }
    // 获取类对象属性
    public String getName() {
        return name;
    }
}

注解(Annotation)

概述:

  1. 从 JDK 5.0 开始, Java 增加了对元数据(MetaData) 的支持, 也就是
    Annotation(注解)

  2. Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加
    载, 运行时被读取, 并执行相应的处理。通过使用 Annotation, 程序员
    可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。代
    码分析工具、开发工具和部署工具可以通过这些补充信息进行验证
    或者进行部署。

  3. Annotation 可以像修饰符一样被使用, 可用于修饰包,类, 构造器, 方 法, 成员变量, 参数, 局部变量的声明, 这些信息被保存在 Annotation
    的 “name=value” 对中。

  4. 在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,
    忽略警告等。在JavaEE/Android中注解占据了更重要的角色,例如
    用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗
    代码和XML配置等。

  5. 未来的开发模式都是基于注解的,JPA是基于注解的,Spring2.5以
    上都是基于注解的,Hibernate3.x以后也是基于注解的,现在的
    Struts2有一部分也是基于注解的了,注解是一种趋势,一定程度上
    可以说:框架 = 注解 + 反射 + 设计模式。

示例:

使用 Annotation 时要在其前面增加 @ 符号, 并把该 Annotation 当成一个修饰符使用。用于修饰它支持的程序元素

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

@author 标明开发该类模块的作者,多个作者之间使用,分割
@version 标明该类模块的版本
@see 参考转向,也就是相关主题
@since 从哪个版本开始增加的
@param 对方法中某参数的说明,如果没有参数就不能写
@return 对方法返回值的说明,如果方法的返回值类型是void就不能写
@exception 对方法可能抛出的异常进行说明 ,如果方法没有用throws显式抛出的异常就不能写
其中
@param @return 和 @exception 这三个标记都是只用于方法的。
@param的格式要求:@param 形参名 形参类型 形参说明
@return 的格式要求:@return 返回值类型 返回值说明
@exception的格式要求:@exception 异常类型 异常说明
@param和@exception可以并列多个

/**
* @author shkstart
* @version 1.0
* @see Math.java
*/
public class JavadocTest {
/**
* 程序的主方法,程序的入口
* @param args String[] 命令行参数
*/
public static void main(String[] args) {
}
/**
* 求圆面积的方法
* @param radius double 半径值
* @return double 圆的面积
*/
public static double getArea(double radius){
return Math.PI * radius * radius; } }

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

1.@Override: 限定重写父类方法, 该注解只能用于方法
2.@Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为
所修饰的结构危险或存在更好的选择
3.@SuppressWarnings: 抑制编译器警告

public class AnnotationTest{
public static void main(String[] args) {
@SuppressWarnings("unused")
int a = 10;
}
@Deprecated
public void print(){
System.out.println("过时的方法");
}
@Override
public String toString() {
return "重写的toString方法()"; } }

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

Servlet3.0提供了注解(annotation),使得不再需要在web.xml文件中进行Servlet的部署

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException { }
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException {
doGet(request, response);
} }
<servlet> 
<servlet-name>LoginServlet</servlet-name>
 <servlet-class>com.servlet.LoginServlet</servlet-class>
</servlet>
 <servlet-mapping>
  <servlet-name>LoginServlet</servlet-name>
 <url-pattern>/login</url-pattern>
</servlet-mapping>

spring框架中关于“事务”的管理

@Transactional(propagation=Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED,readOnly=false,timeout=3)
public void buyBook(String username, String isbn) {
//1.查询书的单价
int price = bookShopDao.findBookPriceByIsbn(isbn);
//2. 更新库存
bookShopDao.updateBookStock(isbn);
//3. 更新用户的余额
bookShopDao.updateUserAccount(username, price); }
<!-- 配置事务属性 --> <tx:advice transaction-manager="dataSourceTransactionManager" id="txAdvice">
<tx:attributes>
<!-- 配置每个方法使用的事务属性 --> <tx:method name="buyBook" propagation="REQUIRES_NEW"
isolation="READ_COMMITTED" read-only="false" timeout="3" />
</tx:attributes>
</tx:advice>

自定义 Annotation

●定义新的 Annotation 类型使用 @interface 关键字
●自定义注解自动继承了java.lang.annotation.Annotation接口
●Annotation 的成员变量在 Annotation 定义中以无参数方法的形式来声明。其
方法名和返回值定义了该成员的名字和类型。我们称为配置参数。类型只能
是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、
以上所有类型的数组

●可以在定义 Annotation 的成员变量时为其指定初始值, 指定成员变量的初始
值可使用 default 关键字
●如果只有一个参数成员,建议使用参数名为value
●如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认
值。格式是“参数名 = 参数值”,如果只有一个参数成员,且名称为value,
可以省略“value=”
●没有成员定义的 Annotation 称为标记; 包含成员变量的 Annotation 称为元数
据 Annotation
注意:自定义注解必须配上注解的信息处理流程才有意义

@MyAnnotation(value="企鹅企鹅")
public class MyAnnotationTest {
public static void main(String[] args) {
Class clazz = MyAnnotationTest.class;
Annotation a = clazz.getAnnotation(MyAnnotation.class);
MyAnnotation m = (MyAnnotation) a;
String info = m.value();
System.out.println(info);
} }
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface MyAnnotation{
String value() default "啦啦啦"; }

JDK 中的元注解

●JDK 的元 Annotation 用于修饰其他 Annotation 定义
●JDK5.0提供了4个标准的meta-annotation类型,分别是:
●Retention
●Target
●Documented
●Inherited
元数据的理解:
String name = “啦啦啦”;

@Retention : 只能用于修饰一个 Annotation 定义, 用于指定该 Annotation 的生命
周期, @Rentention 包含一个 RetentionPolicy 类型的成员变量, 使用
@Rentention 时必须为该 value 成员变量指定值:
RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的
注释
RetentionPolicy.CLASS:在class文件中有效(即class保留) , 当运行 Java 程序时, JVM
不会保留注解。 这是默认值
RetentionPolicy.RUNTIME:在运行时有效(即运行时保留),当运行 Java 程序时, JVM 会
保留注释。程序可以通过反射获取该注释。
在这里插入图片描述
●==@Target==: 用于修饰 Annotation 定义, 用于指定被修饰的 Annotation 能用于
修饰哪些程序元素。 @Target 也包含一个名为 value 的成员变量。
在这里插入图片描述
●@Documented: 用于指定被该元 Annotation 修饰的 Annotation 类将被
javadoc 工具提取成文档。默认情况下,javadoc是不包括注解的。
●定义为Documented的注解必须设置Retention值为RUNTIME。
●@Inherited: 被它修饰的 Annotation 将具有继承性。如果某个类使用了被
@Inherited 修饰的 Annotation, 则其子类将自动具有该注解。
●比如:如果把标有@Inherited注解的自定义的注解标注在类级别上,子类则可以
继承父类类级别的注解
●实际应用中,使用较少

JDK8中注解的新特性

Java 8对注解处理提供了两点改进:可重复的注解及可用于类型的注解。此外,
反射也得到了加强,在Java8中能够得到方法参数的名称。这会简化标注在方法
参数上的注解。
在这里插入图片描述
●JDK1.8之后,关于元注解@Target的参数类型ElementType枚举值多了两个:
TYPE_PARAMETER,TYPE_USE。
●在Java 8之前,注解只能是在声明的地方所使用,Java8开始,注解可以应用
在任何地方。
●ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语
句中(如:泛型声明)。
●ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。

public class TestTypeDefine<@TypeDefine() U> {
private U u;
public <@TypeDefine() T> void test(T t){
} }
@Target({ElementType.TYPE_PARAMETER})
@interface TypeDefine{ }

第十二章: 反射

Java反射机制概述

●Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期
借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内
部属性及方法。
●加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个
类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可
以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看
到类的结构,所以,我们形象的称之为:反射。

在这里插入图片描述

动态语言 vs 静态语言

1、动态语言
是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以
被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在
行时代码可以根据某些条件改变自身结构。

主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang。
2、静态语言
与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、
C++。

Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动
态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。
Java的动态性让编程的时候更加灵活!

Java反射机制提供的功能

●在运行时判断任意一个对象所属的类
●在运行时构造任意一个类的对象
●在运行时判断任意一个类所具有的成员变量和方法
●在运行时获取泛型信息
●在运行时调用任意一个对象的成员变量和方法
●在运行时处理注解
●生成动态代理

理解Class类并获取Class实例

在Object类中定义了以下的方法,此方法
将被所有子类继承:
● public final Class getClass()
以上的方法返回值的类型是一个Class类,
此类是Java反射的源头,实际上所谓反射
从程序的运行结果来看也很好理解,即:
可以通过对象反射求出类的名称
在这里插入图片描述

Class 类

对象照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接
口。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含
了特定某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息。
● Class本身也是一个类
● Class 对象只能由系统建立对象
● 一个加载的类在 JVM 中只会有一个Class实例
● 一个Class对象对应的是一个加载到JVM中的一个.class文件
● 每个类的实例都会记得自己是由哪个 Class 实例所生成
● 通过Class可以完整地得到一个类中的所有被加载的结构
● Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的
Class对象

Class类的常用方法

方法名功能说明
static Class forName(String name)返回指定类名 name 的 Class 对象
Object newInstance()调用缺省构造函数,返回该Class对象的一个实例
getName()返回此Class对象所表示的实体(类、接口、数组类、基本类型或void)名称
Class getSuperClass()返回当前Class对象的父类的Class对象
Class [] getInterfaces()获取当前Class对象的接口
ClassLoader getClassLoader()返回该类的类加载器
Class getSuperclass()返回表示此Class所表示的实体的超类的Class
Constructor[] getConstructors()返回一个包含某些Constructor对象的数组
Field[] getDeclaredFields()返回Field对象的一个数组
Method getMethod(String name,Class … paramTypes)返回一个Method对象,此对象的形参类型为paramType

应用举例

? String str = “test4.Person”;
? Class clazz = Class.forName(str);
? Object obj = clazz.newInstance();
? Field field = clazz.getField(“name”);
? field.set(obj, “Peter”);
? Object name = field.get(obj);
? System.out.println(name);

获取Class类的实例(四种方法)

1)前提:若已知具体的类,通过类的class属性获取,该方法最为安全可靠,
程序性能最高
实例:Class clazz = String.class;
2)前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象
实例:Class clazz = “www.xx.com”.getClass();
3)前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方
法forName()获取,可能抛出ClassNotFoundException
实例:Class clazz = Class.forName(“java.lang.String”);
4)其他方式(不做要求)
ClassLoader cl = this.getClass().getClassLoader();
Class clazz4 = cl.loadClass(“类的全类名”);

哪些类型可以有Class对象?

(1)class: 外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
(2)interface:接口
(3)[ ]:数组
(4)enum:枚举
(5)annotation:注解@interface
(6)primitive type:基本数据类型
(7)void

Class c1 = Object.class;
Class c2 = Comparable.class;
Class c3 = String[].class;
Class c4 = int[][].class;
Class c5 = ElementType.class;
Class c6 = Override.class;
Class c7 = int.class;
Class c8 = void.class;
Class c9 = Class.class;
int[] a = new int[10];
int[] b = new int[100];
Class c10 = a.getClass();
Class c11 = b.getClass();
// 只要元素类型与维度一样,就是同一个Class
System.out.println(c10 == c11);

类的加载与ClassLoader的理解

类的加载过程

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化
在这里插入图片描述
●加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时
数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问
入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的
过程需要类加载器参与。
●链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
●验证:确保加载的类信息符合JVM规范,例如:以cafe开头,没有安全方面的问题
●准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存
都将在方法区中进行分配。
●解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
●初始化:
●执行类构造器<clinit>()方法的过程。类构造器()方法是由编译期自动收集类中
所有类变量的赋值动作和静态代码块中的语句合并产生的。
(类构造器是构造类信
息的,不是构造该类对象的构造器)。
●当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
●虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。

public class ClassLoadingTest {
	public static void main(String[] args) {
		System.out.println(A.m);
	} 
}
class A {
	static {
	 m = 300;
	}
	static int m = 100;
}
//第二步:链接结束后m=0
//第三步:初始化后,m的值由<clinit>()方法执行决定
// 这个A的类构造器<clinit>()方法由类变量的赋值和静态代码块中的语句按照顺序合并
产生,类似于
// <clinit>(){
// m = 300;
// m = 100;
// }

什么时候会发生类初始化?

● 类的主动引用(一定会发生类的初始化)
●当虚拟机启动,先初始化main方法所在的类 ? new一个类的对象
●调用类的静态成员(除了final常量)和静态方法
●使用java.lang.reflect包的方法对类进行反射调用
●当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类
●类的被动引用(不会发生类的初始化)
●当访问一个静态域时,只有真正声明这个域的类才会被初始化
●当通过子类引用父类的静态变量,不会导致子类初始化
●通过数组定义类引用,不会触发此类的初始化
●引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常
量池中了)

class Father {
	static int b = 2;
	static {
		System.out.println("父类被加载");
	}
}
class A extends Father {
		static {
			System.out.println("子类被加载");
			m = 300;
		}
	static int m = 100;
	static final int M = 1;
}
public class ClassLoadingTest {
public static void main(String[] args) {
// 主动引用:一定会导致A和Father的初始化
// A a = new A();
// System.out.println(A.m);
// Class.forName("com.atguigu.java2.A");
// 被动引用
A[] array = new A[5];//不会导致A和Father的
初始化
// System.out.println(A.b);//只会初始化
Father
// System.out.println(A.M);//不会导致A和
Father的初始化
}
static {
System.out.println("main所在的类");
} }

在这里插入图片描述
类加载器的作用:
● 类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方
法区的运行时数据结构
,然后在堆中生成一个代表这个类的java.lang.Class对象,作为
方法区中类数据的访问入口。
● 类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器
中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。

了解:ClassLoader

类加载器作用是用来把类(class)装载进内存的。JVM 规范定义了如下类型的类的加载器。
在这里插入图片描述

? //1.获取一个系统类加载器
? ClassLoader classloader = ClassLoader.getSystemClassLoader();
? System.out.println(classloader);
? //2.获取系统类加载器的父类加载器,即扩展类加载器
? classloader = classloader.getParent();
? System.out.println(classloader);
? //3.获取扩展类加载器的父类加载器,即引导类加载器
? classloader = classloader.getParent();
? System.out.println(classloader);
? //4.测试当前类由哪个类加载器进行加载
? classloader = Class.forName(“exer2.ClassloaderDemo”).getClassLoader();
? System.out.println(classloader);
? //5.测试JDK提供的Object类由哪个类加载器加载
? classloader =
? Class.forName(“java.lang.Object”).getClassLoader();
? System.out.println(classloader);
? //*6.关于类加载器的一个主要方法:getResourceAsStream(String str):获取类路
径下的指定文件的输入流
? InputStream in = null;
? in = this.getClass().getClassLoader().getResourceAsStream(“exer2\test.properties”);
? System.out.println(in);

创建运行时类的对象

有了Class对象,能做什么?

创建类的对象:调用Class对象的newInstance()方法
要 求:
1)类必须有一个无参数的构造器。
2)类的构造器的访问权限需要足够。
难道没有无参的构造器就不能创建对象了吗?
不是!只要在操作的时候明确的调用类中的构造器,并将参数传递进去之后,才可以实例化操作。
步骤如下:
1)通过Class类的==getDeclaredConstructor(Class … parameterTypes)==取得本类的指定形参类
型的构造器
2)向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。
3)通过Constructor实例化对象。

//1.根据全类名获取对应的Class对象
String name =atguigu.java.Person";
Class clazz = null;
clazz = Class.forName(name);
//2.调用指定参数结构的构造器,生成Constructor的实例
Constructor con = clazz.getConstructor(String.class,Integer.class);
//3.通过Constructor的实例创建对应类的对象,并初始化类属性
Person p2 = (Person) con.newInstance("Peter",20);
System.out.println(p2);

获取运行时类的完整结构

使用反射可以取得:

  1. 实现的全部接口
    ● public Class<?>[] getInterfaces() 确定此对象所表示的类或接口实现的接口
  2. 所继承的父类
    ●public Class<? Super T> getSuperclass()返回表示此 Class 所表示的实体(类、接口、基本类型)的父类的Class
  3. 全部的构造器
    ●public Constructor<T>[] getConstructors()
    返回此 Class 对象所表示的类的所有public构造方法。
    ●public Constructor<T>[] getDeclaredConstructors()
    返回此 Class 对象表示的类声明的所有构造方法。
    ●Constructor类中:
    ●取得修饰符: public int getModifiers();
    ●取得方法名称: public String getName();
    ●取得参数的类型:public Class<?>[] getParameterTypes();
  4. 全部的方法
    ●public Method[] getDeclaredMethods()
    返回此Class对象所表示的类或接口的全部方法
    ==●public Method[] getMethods() ==
    返回此Class对象所表示的类或接口的public的方法
    ●Method类中:
    ●public Class<?> getReturnType()取得全部的返回值 ●public Class<?>[] getParameterTypes()取得全部的参数
    ●public int getModifiers()取得修饰符
    ●public Class<?>[] getExceptionTypes()取得异常信息
  5. 全部的Field
    ●public Field[] getFields()
    返回此Class对象所表示的类或接口的public的Field。
    ==●public Field[] getDeclaredFields() ==
    返回此Class对象所表示的类或接口的全部Field。
    ●Field方法中:
    ●public int getModifiers() 以整数形式返回此Field的修饰符
    ●public Class<?> getType() 得到Field的属性类型
    ●public String getName() 返回Field的名称。
  6. Annotation相关
    ●get Annotation(Class<T> annotationClass)
    ●getDeclaredAnnotations()
  7. 泛型相关
    获取父类泛型类型:Type getGenericSuperclass()
    泛型类型:ParameterizedType
    获取实际的泛型类型参数数组:getActualTypeArguments()
  8. 类所在的包 Package getPackage()

调用运行时类的指定结构

调用指定方法

通过反射,调用类中的方法,通过Method类完成。步骤:
1.通过Class类的 getMethod(String name,Class…parameterTypes) 方法取得
一个Method对象,并设置此方法操作时所需要的参数类型。
2.之后使用 Object invoke(Object obj, Object[] args) 进行调用,并向方法中
传递要设置的obj对象的参数信息。
在这里插入图片描述
Object invoke(Object obj, Object … args) 说明:
1.Object 对应原方法的返回值,若原方法无返回值,此时返回null
2.若原方法若为静态方法,此时形参Object obj可为null
3.若原方法形参列表为空,则Object[] args为null
4.若原方法声明为private,则需要在调用此invoke()方法前,显式调用
方法对象的setAccessible(true)方法,将可访问private的方法。

调用指定属性

在反射机制中,可以直接通过Field类操作类中的属性,通过Field类提供的set()和
get()方法就可以完成设置和取得属性内容的操作。
public Field getField(String name) 返回此Class对象表示的类或接口的指定的public的Field。
public Field getDeclaredField(String name) 返回此Class对象表示的类或接口的指定的Field
●在Field中:
public Object get(Object obj) 取得指定对象obj上此Field的属性内容
public void set(Object obj,Object value)
设置指定对象obj上此Field的属性内容

关于setAccessible方法的使用

●Method和Field、Constructor对象都有setAccessible()方法。
● setAccessible启动和禁用访问安全检查的开关。
● 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。
● 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true。
● 使得原本无法访问的私有成员也可以访问
● 参数值为false则指示反射的对象应该实施Java语言访问检查。

反射的应用:动态代理

Proxy :

专门完成代理的操作类,是所有动态代理类的父类。通过此类为一
个或多个接口动态地生成实现类。
●提供用于创建动态代理类和动态代理对象的静态方法
在这里插入图片描述

动态代理步骤

1.创建一个实现接口InvocationHandler的类,它必须实现invoke方法,以完成代理的具体操作。
在这里插入图片描述
2.创建被代理的类以及接口
在这里插入图片描述
3.通过Proxy的静态方法
newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) 创建一个Subject接口代理
在这里插入图片描述
4.通过 Subject代理调用RealSubject实现类的方法
在这里插入图片描述

动态代理与AOP(Aspect Orient Programming)

前面介绍的Proxy和InvocationHandler,很难看出这种动态代理的优势,下面介绍一种更实用的动态代理机制
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
● 使用Proxy生成一个动态代理时,往往并不会凭空产生一个动态代理,这样没有
太大的意义。通常都是为指定的目标对象生成动态代理
● 这种动态代理在AOP中被称为AOP代理,AOP代理可代替目标对象,AOP代理
包含了目标对象的全部方法。但AOP代理中的方法与目标对象的方法存在差异:
AOP代理里的方法可以在执行目标方法之前、之后插入一些通用处理
在这里插入图片描述

第十三章: 网络编程

网络编程概述:

●Java是 Internet 上的语言,它从语言级上提供了对网络应用程序的支持,程序员能够很容易开发常见的网络应用程序。
●Java提供的网络类库,可以实现无痛的网络连接,联网的底层细节被隐藏在 Java 的本机安装系统里,由 JVM 进行控制。
并且 Java 实现了一个跨平台的网络库,程序员面对的是一个统一的网络编程环境。

网络基础

●计算机网络:
把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规
模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息、
共享硬件、软件、数据信息等资源。
●网络编程的目的:
直接或间接地通过网络协议与其它计算机实现数据交换,进行通讯。
●网络编程中有两个主要的问题:
●如何准确地定位网络上一台或多台主机;定位主机上的特定的应用
●找到主机后如何可靠高效地进行数据传输

网络通信要素概述

如何实现网络中的主机互相通信

●通信双方地址
●IP
●端口号
●一定的规则(即:网络通信协议。有两套参考模型)
●OSI参考模型:模型过于理想化,未能在因特网上进行广泛推广
●TCP/IP参考模型(或TCP/IP协议):事实上的国际标准。

网络通信协议

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

通信要素

IP和端口号

  1. IP 地址:InetAddress
    1)唯一的标识 Internet 上的计算机(通信实体)
    2)本地回环地址(hostAddress):127.0.0.1 主机名(hostName):localhost
    3)IP地址分类方式1:IPV4 和 IPV6
    IPV4:4个字节组成,4个0-255。大概42亿,30亿都在北美,亚洲4亿。2011年初已
    经用尽。以点分十进制表示,如192.168.0.1
    IPV6:128位(16个字节),写成8个无符号整数,每个整数用四个十六进制位表示,
    数之间用冒号(:)分开,如:3ffe:3201:1401:1280:c8ff:fe4d:db39:1984
    4) IP地址分类方式2:公网地址(万维网使用)和私有地址(局域网使用)。192.168.
    开头的就是私有址址,范围即为192.168.0.0–192.168.255.255,专门为组织机
    构内部使用
    5)特点:不易记忆

  2. 端口号标识正在计算机上运行的进程(程序)
    1) 不同的进程有不同的端口号
    2)被规定为一个 16 位的整数 0~65535。
    3)端口分类:
    ① 公认端口:0~1023。被预先定义的服务通信占用(如:HTTP占用端口80,FTP占用端口21,Telnet占用端口23)
    ② 注册端口:1024~49151。分配给用户进程或应用程序。(如:Tomcat占
    用端口8080,MySQL占用端口3306,Oracle占用端口1521等)。
    ③ 动态/私有端口:49152~65535。

  3. 端口号与IP地址的组合得出一个网络套接字:Socket
    在这里插入图片描述

InetAddress类

  1. Internet上的主机有两种方式表示地址:
    ① 域名(hostName):www.baidu.com
    ② IP 地址(hostAddress):202.108.35.210
  2. InetAddress类主要表示IP地址,两个子类:Inet4Address、Inet6Address。
  3. InetAddress 类 对 象 含 有 一 个 Internet 主 机 地 址 的 域 名 和 IP 地 址 :
    www.baidu.com 和 202.108.35.210。
  4. 域名容易记忆,当在连接网络时输入一个主机的域名后,域名服务器(DNS)
    负责将域名转化成IP地址,这样才能和主机建立连接。== -------域名解析==
  5. InetAddress类没有提供公共的构造器,而是提供了如下几个静态方法来获取InetAddress实例
public static InetAddress getLocalHost()
public static InetAddress getByName(String host)
  1. InetAddress提供了如下几个常用的方法
public String getHostAddress()//返回 IP 地址字符串(以文本表现形式)。 
public String getHostName()//获取此 IP 地址的主机名
public boolean isReachable(int timeout)//测试是否可以达到该地址

网络协议

  1. 网络通信协议:
    计算机网络中实现通信必须有一些约定,即通信协议,对速率、传输代码、代码结构、传输控制步骤、出错控制等制定标准。
  2. 问题:网络协议太复杂
    计算机网络通信涉及内容很多,比如指定源地址和目标地址,加密解密,压缩
    解压缩,差错控制,流量控制,路由控制,如何实现如此复杂的网络协议呢?
  3. 通信协议分层的思想
    在制定协议时,把复杂成份分解成一些简单的成份,再将它们复合起来。最常
    用的复合方式是层次方式,即同层间可以通信、上一层可以调用下一层,而与
    再下一层不发生关系
    。各层互不影响,利于系统的开发和扩展。

TCP/IP协议簇

  1. 传输层协议中有两个非常重要的协议:
    传输控制协议TCP(Transmission Control Protocol)
    用户数据报协议UDP(User Datagram Protocol)
  2. TCP/IP 以其两个主要协议:传输控制协议(TCP)和网络互联协议(IP) 而得
    名,实际上是一组协议,包括多个具有不同功能且互为关联的协议。
  3. IP(Internet Protocol)协议是网络层的主要协议,支持网间互连的数据通信
  4. TCP/IP协议模型从更实用的角度出发,形成了高效的四层体系结构,即
    物理链路层、IP层、传输层和应用层。

TCP 和 UDP介绍

TCP协议:

1.使用TCP协议前,须先建立TCP连接,形成传输数据通道
2. 传输前,采用“三次握手”方式,点对点通信,是可靠的
3. TCP协议进行通信的两个应用进程:客户端、服务端。
4. 在连接中可进行大数据量的传输
5. 传输完毕,需释放已建立的连接,效率低
在这里插入图片描述
在这里插入图片描述

UDP协议:
  1. 将数据、源、目的封装成数据包,不需要建立连接
  2. 每个数据报的大小限制在64K内
  3. 发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
  4. 可以广播发送
  5. 发送数据结束时无需释放资源,开销小,速度快

Socket

  1. 利用套接字(Socket)开发网络应用程序早已被广泛的采用,以至于成为事实
    上的标准。
    2. 网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标
    识符套接字。
  2. 通信的两端都要有Socket,是两台机器间通信的端点。
  3. 网络通信其实就是Socket间的通信。
  4. Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输。
  5. 一般主动发起通信的应用程序属客户端,等待通信请求的为服务端。
  6. Socket分类:
    ① 流套接字(stream socket):使用TCP提供可依赖的字节流服务
    ② 数据报套接字(datagram socket):使用UDP提供“尽力而为”的数据报服务

Socket类的常用构造器:

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

Socket类的常用方法:

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

TCP网络编程

Java语言的基于套接字编程分为服务端编程和客户端编程,其通信模
型如图所示:
在这里插入图片描述

基于Socket的TCP编程

客户端Socket的工作过程包含以下四个基本的步骤:

  1. 创建 Socket:根据指定服务端的 IP 地址或端口号构造 Socket 类对象。若服务器端
    响应,则建立客户端到服务器的通信线路。若连接失败,会出现异常。
  2. 打开连接到 Socket 的输入/出流: 使用 getInputStream()方法获得输入流,使用
    getOutputStream()方法获得输出流,进行数据传输
  3. 按照一定的协议对 Socket 进行读/写操作:通过输入流读取服务器放入线路的信息
    (但不能读取自己放入线路的信息),通过输出流将信息写入线程。
  4. 关闭 Socket:断开客户端到服务器的连接,释放线路

客户端创建Socket对象

  1. 客户端程序可以使用Socket类创建对象,创建的同时会自动向服务器方发起连
    。Socket的构造器是:
    ①Socket(String host,int port)throws UnknownHostException,IOException:向服务器(域名是
    host。端口号为port)发起TCP连接,若成功,则创建Socket对象,否则抛出异常。
    ②Socket(InetAddress address,int port)throws IOException:根据InetAddress对象所表示的
    IP地址以及端口号port发起连接。
  2. 客户端建立socketAtClient对象的过程就是向服务器发出套接字连接请求
Socket s = new Socket(192.168.40.165,9999);
OutputStream out = s.getOutputStream();
out.write(" hello".getBytes());
s.close();

服务器程序的工作过程包含以下四个基本的步骤:

  1. 调用 ServerSocket(int port) :创建一个服务器端套接字,并绑定到指定端口
    上。用于监听客户端的请求。
  2. 调用 accept():监听连接请求,如果客户端请求连接,则接受连接,返回通信
    套接字对象。
  3. 调用 该Socket类对象的 getOutputStream() 和 getInputStream ():获取输出
    流和输入流,开始网络数据的发送和接收。
  4. 关闭ServerSocket和Socket对象:客户端访问结束,关闭通信套接字

服务器建立 ServerSocket 对象

  1. ServerSocket 对象负责等待客户端请求建立套接字连接,类似邮局某个窗口
    中的业务员。也就是说,服务器必须事先建立一个等待客户请求建立套接字
    连接的ServerSocket对象。
  2. 所谓“接收”客户的套接字请求,就是accept()方法会返回一个 Socket 对象
ServerSocket ss = new ServerSocket(9999);
Socket s = ss.accept ();
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int num = in.read(buf);
String str = new String(buf,0,num);
System.out.println(s.getInetAddress().toString()+:+str);
s.close();
ss.close();

UDP网络编程

  1. 类 DatagramSocket 和 DatagramPacket 实现了基于 UDP 协议网络程序。
  2. UDP数据报通过数据报套接字 DatagramSocket 发送和接收,系统不保证
    UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。
  3. DatagramPacket 对象封装了UDP数据报,在数据报中包含了发送端的IP
    地址和端口号以及接收端的IP地址和端口号。
  4. UDP协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和
    接收方的连接。如同发快递包裹一样

DatagramSocket 类的常用方法

  1. public DatagramSocket(int port)创建数据报套接字并将其绑定到本地主机上的指定端口。套接字将被
    绑定到通配符地址,IP 地址由内核来选择。
  2. public DatagramSocket(int port,InetAddress laddr)创建数据报套接字,将其绑定到指定的本地地址。
    本地端口必须在 0 到 65535 之间(包括两者)。如果 IP 地址为 0.0.0.0,套接字将被绑定到通配符地
    址,IP 地址由内核选择。
  3. public void close()关闭此数据报套接字。
  4. public void send(DatagramPacket p)从此套接字发送数据报包。DatagramPacket 包含的信息指示:将
    要发送的数据、其长度、远程主机的 IP 地址和远程主机的端口号。
  5. public void receive(DatagramPacket p)从此套接字接收数据报包。当此方法返回时,DatagramPacket
    的缓冲区填充了接收的数据。数据报包也包含发送方的 IP 地址和发送方机器上的端口号。 此方法
    在接收到数据报前一直阻塞。数据报包对象的 length 字段包含所接收信息的长度。如果信息比包的
    长度长,该信息将被截短。
  6. public InetAddress getLocalAddress()获取套接字绑定的本地地址。
  7. public int getLocalPort()返回此套接字绑定的本地主机上的端口号。
  8. public InetAddress getInetAddress()返回此套接字连接的地址。如果套接字未连接,则返回 null。
  9. public int getPort()返回此套接字的端口。如果套接字未连接,则返回 -1。

DatagramPacket类的常用方法

  1. public DatagramPacket(byte[] buf,int length)构造 DatagramPacket,用来接收长
    度为 length 的数据包。 length 参数必须小于等于 buf.length。
  2. public DatagramPacket(byte[] buf,int length,InetAddress address,int port)构造数
    据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。length
    参数必须小于等于 buf.length。
  3. public InetAddress getAddress()返回某台机器的 IP 地址,此数据报将要发往该
    机器或者是从该机器接收到的。
  4. public int getPort()返回某台远程主机的端口号,此数据报将要发往该主机或
    者是从该主机接收到的。
  5. public byte[] getData()返回数据缓冲区。接收到的或将要发送的数据从缓冲区
    中的偏移量 offset 处开始,持续 length 长度。
  6. public int getLength()返回将要发送或接收到的数据的长度。

UDP网络通信流程:

  1. DatagramSocket与DatagramPacket
  2. 建立发送端,接收端
  3. 建立数据包
  4. 调用Socket的发送、接收方法
  5. 关闭Socket

发送端与接收端是两个独立的运行程序

发送端:

DatagramSocket ds = null;
try {
ds = new DatagramSocket();
byte[] by = "hello,baidu.com".getBytes();
DatagramPacket dp = new DatagramPacket(by, 0, by.length, 
InetAddress.getByName("127.0.0.1"), 10000);
ds.send(dp);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (ds != null)
ds.close();
}

接收端
在接收端,要指定监听的端口。

DatagramSocket ds = null;
try {
ds = new DatagramSocket(10000);
byte[] by = new byte[1024];
DatagramPacket dp = new DatagramPacket(by, by.length);
ds.receive(dp);
String str = new String(dp.getData(), 0, dp.getLength());
System.out.println(str + "--" + dp.getAddress());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (ds != null)
ds.close();
}

URL编程

URL类

  1. == URL(Uniform Resource Locator):统一资源定位符,它表示 Internet 上某一
    资源==的地址。
  2. 它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate
    这个资源。
  3. 通过 URL 我们可以访问 Internet 上的各种网络资源,比如最常见的 www,ftp
    站点。浏览器通过解析给定的 URL 可以在网络上查找相应的文件或其他资源。
  4. URL的基本结构由5部分组成:
    <传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表
    ①例如:
    http://192.168.1.100:8080/helloworld/index.jsp#a?username=shkstart&password=123
    ②#片段名:即锚点,例如看小说,直接定位到章节
    ③参数列表格式:参数名=参数值&参数名=参数值…

URL类构造器

  1. 为了表示URL,java.net 中实现了类 URL。我们可以通过下面的构造器来初
    始化一个 URL 对象:
    ①public URL (String spec):通过一个表示URL地址的字符串可以构造一个URL对象。例
    如:URL url = new URL (“http://www. baidu.com/”);
    ②public URL(URL context, String spec):通过基 URL 和相对 URL 构造一个 URL 对象。
    例如:URL downloadUrl = new URL(url, “download.html")
    ③public URL(String protocol, String host, String file); 例如:new URL(“http”,
    “www.atguigu.com”, “download. html");
    ④public URL(String protocol, String host, int port, String file); 例如: URL gamelan = new
    URL(“http”, “www.baidu.com”, 80, “download.html");
  2. URL类的构造器都声明抛出非运行时异常,必须要对这一异常进行处理,通
    常是用 try-catch 语句进行捕获

URL类常用方法

一个URL对象生成后,其属性是不能被改变的,但可以通过它给定的
方法来获取这些属性:

  1. public String getProtocol( ) 获取该URL的协议名
  2. public String getHost( ) 获取该URL的主机名
  3. public String getPort( ) 获取该URL的端口号
  4. public String getPath( ) 获取该URL的文件路径
  5. public String getFile( ) 获取该URL的文件名
  6. public String getQuery( ) 获取该URL的查询名
URL url = new URL("http://localhost:8080/examples/myTest.txt");
System.out.println("getProtocol() :"+url.getProtocol());
System.out.println("getHost() :"+url.getHost());
System.out.println("getPort() :"+url.getPort());
System.out.println("getPath() :"+url.getPath());
System.out.println("getFile() :"+url.getFile());
System.out.println("getQuery() :"+url.getQuery());

针对HTTP协议的URLConnection类

  1. URL的方法 openStream():能从网络上读取数据
  2. 若希望输出数据,例如向服务器端的 CGI (公共网关接口-Common Gateway
    Interface-的简称,是用户浏览器和服务器端的应用程序进行连接的接口)程序发送一
    些数据,则必须先与URL建立连接,然后才能对其进行读写,此时需要使用
    URLConnection 。
  3. URLConnection:表示到URL所引用的远程对象的连接。当与一个URL建立连接时,
    首先要在一个 URL 对象上通过方法 ==openConnection() ==生成对应的URLConnection
    对象。如果连接过程失败,将产生IOException.
URL netchinaren = new URL ("http://www.baidu.com/index.shtml");
URLConnectonn u = netchinaren.openConnection( );
  1. 通过URLConnection对象获取的输入流和输出流,即可以与现有的CGI
    程序进行交互
public Object getContent( ) throws IOException
public int getContentLength( )
public String getContentType( )
public long getDate( )
public long getLastModified( )
public InputStream getInputStream( )throws IOException
public OutputSteram getOutputStream( )throws IOException

URI、URL和URN的区别

URI,是uniform resource identifier,统一资源标识符,用来唯一的标识一个
资源。
而==URL是uniform resource locator,统一资源定位符,==它是一种具体
的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。
而URN,uniform resource name,统一资源命名,是通过名字来标识资源,
比如mailto:java-net@java.sun.com。也就是说,URI是以一种抽象的,高层
次概念定义统一资源标识,而URL和URN则是具体的资源标识的方式。URL
和URN都是一种URI。

在Java的URI中,一个URI实例可以代表绝对的,也可以是相对的,只要它符
合URI的语法规则。而URL类则不仅符合语义,还包含了定位该资源的信息,因此它不能是相对的
在这里插入图片描述

小 结

  1. 位于网络中的计算机具有唯一的IP地址,这样不同的主机可以互相区分。
    2.== 客户端-服务器==是一种最常见的网络应用程序模型。服务器是一个为其客户端提供某种特定
    服务的硬件或软件。客户机是一个用户应用程序,用于访问某台服务器提供的服务。端口号
    是对一个服务的访问场所,它用于区分同一物理计算机上的多个服务。套接字用于连接客户
    端和服务器,客户端和服务器之间的每个通信会话使用一个不同的套接字。TCP协议用于实
    现面向连接的会话。
  2. Java 中有关网络方面的功能都定义在 java.net 程序包中。Java 用 InetAddress 对象表示== IP
    地址==,该对象里有两个字段:主机名(String) 和 IP 地址(int)。
  3. 类 Socket 和 ServerSocket 实现了基于TCP协议的客户端-服务器程序。Socket是客户端
    和服务器之间的一个连接,连接创建的细节被隐藏了。这个连接提供了一个安全的数据传输
    通道,这是因为 TCP 协议可以解决数据在传送过程中的丢失、损坏、重复、乱序以及网络
    拥挤等问题,它保证数据可靠的传送。
  4. 类 URL 和 URLConnection 提供了最高级网络应用。URL 的网络资源的位置来同一表示
    Internet 上各种网络资源。通过URL对象可以创建当前应用程序和 URL 表示的网络资源之
    间的连接,这样当前程序就可以读取网络资源数据,或者把自己的数据传送到网络上去。
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-06-14 22:21:09  更:2022-06-14 22:25:17 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 19:33:56-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码