文章目录

  • Java基础编程
    • Java语言概述
      • Java语言简述
        • 1.基础图解
        • 2.常识
        • 3.计算机语言的发展迭代
        • 4.Java语言版本迭代概述
        • 5. Java语言应用的领域
        • 6.Java语言的特点
      • 开发环境的搭建
        • 1. JDK、JRE、JVM的关系
        • 2. JDK的下载安装
      • 注释与API文档
        • 1. 注释Comment
        • 2. Java API 文档
        • 3. 良好的编程风格
    • 基本语法
      • 关键字与标识符
        • 关键字的使用
        • 保留字
        • 标识符的使用
      • 变量的使用(重点)
        • 变量的分类:
        • 定义变量的格式:
        • 变量使用的注意点:
        • 基本数据类型变量间运算规则:
          • 涉及到的基本数据类型:除了boolean之外的其他7种
          • 自动类型转换(7种)
          • 强制类型转换(7种)
          • String与8种基本数据类型间的运算
      • 进制(了解
        • 1. 编程中涉及的进制及表示方式:
        • 2. 二进制的使用说明:
        • 3. 进制间的转换:
          • 图示二进制转换为十进制:
          • 图示十进制转换为二进制:
          • 图示二进制与八进制、十六进制的转换:
      • 运算符
        • 算数运算符
        • 赋值运算符
        • 比较运算符
        • 逻辑运算符
        • 位运算符
        • 三元运算符
      • 流程控制
        • 分支结构
          • if-else
          • switch-case
        • 循环结构
        • 关键字:break和continue
        • 补充:Scanner类的使用
    • 数组
      • 数组的概述
        • 数据结构:
      • 一维数组
      • 二维数组
      • 数组的常见算法
        • 一、数组元素的赋值(杨辉三角)//面试中考察较多
        • 二、求数值型数组元素中的最大值、最小值、平均数、总和等等
        • 三、数组的复制、反转、查找(线性查找、二分法查找)
        • 四、数组元素的排序算法//面试中考察较多
      • Arrays工具类的使用
      • 数组的常见异常
    • 面向对象-上
      • 类与对象
        • JVM的内存结构
      • 类的结构之一:属性
      • 类的结构之二:方法
        • 关键字:return
        • 方法的重载
        • 可变个数形参的方法
        • Java的值传递机制
        • 递归方法
      • 面向对象的特征一:封装性
      • 类的结构:构造器Corstructor
        • 属性赋值的顺序
        • JavaBean的概念
      • 关键字:this
      • 关键字:package/import
    • 面向对象-中
      • 面向对象的特征二:继承性
      • 方法的重写
      • 关键字:super
      • 面向对象的特征三:多态性
      • Object类的使用
      • 单元测试方法(Java中的JUnit单元测试)
      • 包装类的使用
    • 面向对象-下
      • 关键字:static(静态的)
        • 单例模式
      • main()的使用说明
      • 类的结构:代码块
        • 属性的赋值顺序
      • 关键字:final(最终的)
      • 关键字:abstract(抽象的)
        • 模板方法的设计模式
      • 关键字:interface(接口)
      • 类的结构:内部类(类的第五个成员)
    • 异常处理
      • 异常
        • 认识异常
        • 异常体系结构
        • 从程序执行过程,看编译时异常和运行时异常
      • 异常处理
        • Java异常处理的抓抛模型
        • 异常处理方式一:try-catch-finally
        • 异常处理方式二:throws关键字
        • 对比俩种处理方式:
      • 手动抛出异常对象
      • 自定义异常
  • Java高级编程
    • 多线程
      • 程序、进程、线程的理解
      • 并行与并发
      • 创建多线程的两种方式
      • Thread类中的常用方法
      • Thread的生命周期
      • 线程的同步机制
        • 线程安全的单例模式(懒汉式)
        • 死锁问题
      • 线程通信
        • 面试题:sleep() 和 wait() 的异同?
      • JDK5.0新增线程创建的方式
        • 实现 Callable 接口
        • 使用线程池
    • Java常用类
      • String类(java.lang.String类的使用)
      • StringBuffer、StringBuilder
      • JDK 8 之前日期时间API
      • JDK 8 中新日期时间API
      • Java比较器
      • 其他类
    • 枚举类和注解
      • 枚举类的使用
      • 注解的使用
    • Java集合
      • 数组与集合
      • Collection接口
      • Iterator接口与foreach循环
      • Collection子接口:List接口
      • Collection子接口:Set接口
      • Map接口(双列集合框架:Map)
      • Collections工具类的使用
      • 数据结构简述
    • 泛型
      • 泛型的理解
      • 泛型在集合中的使用
      • 自定义泛型类、泛型接口、泛型方法
      • 泛型在继承上的体现
      • 通配符
    • IO流
      • File类的使用
      • IO流概述
      • 节点流(或文件类)
      • 缓冲流的使用
      • 转换流的使用
      • 其他流的使用
      • 对象流的使用
      • RandomAccessFile的使用
      • Path、Paths、Files的使用
    • 网络编程
      • InetAddress类的使用
      • TCP网络编程
      • UDP网络编程
      • URL编程
    • Java反射机制
      • Java反射的概述
      • Class类的理解与获取Class的实例
      • 了解ClassLoader
      • 反射应用一:创建运行时类的对象
      • 反射应用二:获取运行时类的完整结构
      • 反射应用三:调用运行时类的指定结构
        • 调用指定的属性:
        • 调用指定的方法:
        • 调用指定的构造器:
      • 反射应用四:动态代理
    • Java8的其他新特性
      • Java8新特性概述
      • Lambda表达式
      • 函数式接口
      • 方法引用
      • 构造器引用与数组引用
      • Stream API
      • Optional类的使用(*java.util.Optional*)

Java基础编程

Java语言概述

Java语言简述

1.基础图解

2.常识

软件:即一系列按照特定顺序组织的计算机数据和指令的集合。分为:系统软件和应用软件

  • 1.系统软件:windows,mac os,linux,unix,ios…
  • 2.应用软件:word,ppt…

人机交互方式:图形化界面 vs 命令行方式

常用DOS命令:

dir:  列出当前目录下的文件以及文件夹
md:   创建目录
rd:   删除目录
cd:   进入指定目录
cd..: 退回到上一级目录
cd\:  退回到根目录
del:  删除文件
exit: 退出dos命令行

3.计算机语言的发展迭代

  • 第一代: 机器语言。指令以二进制代码形式存在。
  • 第二代: 汇编语言。使用助记符表示一条机器指令。
  • 第三代: 高级语言
    • 1.面向过程:C,Pascl、Fortran
    • 2.面向对象:Java,Js,Python,Scala,…

4.Java语言版本迭代概述

5. Java语言应用的领域

  • ava Web开发:后台开发
  • 大数据开发
  • Android应用程序开发:客户端开发

6.Java语言的特点

  • 面向对象性:

    • 两个要素:类、对象
    • 三个特征:封装、继承、多态
  • 健壮性:
    • 1.去除了C语言中的指针
    • 2.自动的垃圾回收机制—>仍然会出现内存溢出、内存泄漏
  • 跨平台性:
    • once ,run anywhere :一次编译,到处运行
    • 归功于强大Jvm

开发环境的搭建

1. JDK、JRE、JVM的关系

2. JDK的下载安装

  • 官网:Oracle官方网站( https://www.oracle.com)

  • 配置环境变量

    1. 进入高级系统设置

      • 方法1:选择桌面电脑鼠标右击此电脑,点击属性进入系统信息界面
      • 方法2:通过“控制面板”,选择“系统和安全”,再选择“系统”,在系统信息界面左侧点击 高级系统设置
    2. 在高级系统设置界面,点击环境变量

    3. 环境变量的系统变量设置3项属性,JAVA_HOME,Path,CLASSPATH。若存在则在此基础上“编辑”,不存在则“新建”

      • 新建JAVA_HOME,变量值为JDK安装目录
      • 编辑 Path系统变量,新建值%JAVA_HOME%\bin
  • 验证JDK是否安装成功

    1. win+R快捷键打开运行,输入cmd进入命令提示符
    2. 输入 java -version,(\Java\Javac)查看java版本,有版本信息表示安装成功

注释与API文档

1. 注释Comment

  • 分类:
单行注释://
多行注释:/*     */
文档注释:/**    */
  • 作用:

    • 单行注释和多行注释的作用:

      1. 对所写的程序进行解释说明,增强可读性。方便自己,方便别人
      2. 调试所写的代码
    • 文档注释的使用:

      • 注释内容可以被JDK提供的工具 javadoc 所解析,生成一套以网页文件形式体现的该程序的说明文档。
  • 特点:

    1. 单行注释和多行注释,注释了的内容不参与编译。
      换句话说,编译以后生成的.class结尾的字节码文件中不包含注释掉的信息
    2. 多行注释不可以嵌套使用

2. Java API 文档

API:application programming interface。习惯上:将语言提供的类库,都称为API。
API文档:针对于提供的类库如何使用,给的一个说明书。

3. 良好的编程风格

基本语法

关键字与标识符

关键字的使用

  • 定义:被Java语言赋予了特殊含义,用做专门用途的字符串(单词)
  • 特点:关键字中所有字母都为小写
  • 具体有那些关键字:

保留字

(现Java版本未使用,但以后版本可能会作为关键字使用。)

  • 具体哪些保留字:goto、const(注意:自己命名标识符时要避免使用这些保留字)

标识符的使用

定义:

  1. Java对各种变量、方法和类等要素命名时使用的字符序列称为标识符。
  2. 凡是自己可以起名字的地方都叫标识符。

规则:(必须要遵守,否则编译不通过)

  1. 由26个英文字母大小写,0-9,_或$组成
  2. 数字不可以开头
  3. 不可以使用关键字和保留字,但能包含关键字和保留字
  4. Java中严格区分大小写,长度无限制
  5. 标识符不能包含空格

规范:(可以不遵守,不影响编译运行,但是要求大家遵守)

  • 包名:多单词组成时所有字母都小写:xxxyyyzzz
  • 类名、接口名:多单词组成时,所有单词的首字母大写:XxxYyyZxx
  • 变量名、方法名:多单词组成时,第一个单词首字母小写,第二个单词开始每个单词首字母大写:xxxYyyZzz
  • 常量名:所有字母都大写。多单词每个单词用下划线连接:XXX_YYY_ZZZ
  • 注意点:在起名字时为了提高阅读性,要尽量意义,“见名知意”。

变量的使用(重点)

变量的分类:

按数据类型分类:

详细说明:

  1. 整型:byte(1字节=8bt) \ short(2字节) \ int (4字节) \ long(8字节)
//1.byte范围:-128 ~ 127
byte b1 = 12;
byte b2 = -128;
//b3 = 128;编译不通过//2.声明long型变量,必须以"l" \ "L"结尾
//3.通常定义整型时,使用int型。
short s1 = 128;
int i1 = 1234;
long l1 = 341434562L;
  1. 浮点型:float(4字节) \ double(8字节)
//1.浮点型,表示带小数点的数值
//2.float表示数值的范围比long还大
double d1 = 123.3;
//3.定义float类型变量时,变量要以"f" \ "F"结尾
float f1 = 12.3f;
//4.通常,定义浮点型变量时,使用double型
  1. 字符型:char(1字符=2字节)
//1.定义char型变量,通常使用一对 '' ,内部只能写一个字符
char c1 = 'a';
//2.表示方式:1.声明一个字符 2.转义字符 3.直接使用 Unicode 值来表示字符型常量
char c = '\n'; //换行符
c = '\t'; //制表符
  1. 布尔型:boolean
//只能两个值之一:true、false
//常常在条件判断、循环结构中使用

按声明位置分类:

定义变量的格式:

数据类型 变量名 = 变量值;或
数据类型 变量名;
变量名 = 变量值;

变量使用的注意点:

  • 变量必须先声明,后使用
  • 变量都定义在其作用域内。在作用域内,它是有效的。换句话说,出了作用域,就失效
  • 同一个作用域内,不可以声明两个同名的变量

基本数据类型变量间运算规则:

涉及到的基本数据类型:除了boolean之外的其他7种
自动类型转换(7种)

结论:当容量小的数据类型的变量与容量大的数据类型的变量做运算时,结果自动提升为容量大的数据类型。

byte、char、short --> int --> long --> float --> double
特别的:当byte、char、short三种类型的变量做运算时,结果为int型

说明:此时的容量大小指的是,表示数的范围大小。比如float容量要大于long的容量。

强制类型转换(7种)

自动类型提升运算的逆运算。

  • 需要使用强转符:()
  • 注意点:强制类型转换,可能导致精度损失
//精度损失1
double d1 = 12.9;
int i1 = (int)d1;//截断操作
//精度损失2
int i2 = 128;
byte b = (byte)i2;//b = -128
//没有精度损失
long l1 = 123;
short s2 = (short)l1;
String与8种基本数据类型间的运算
  • String属于引用数据类型,翻译为:字符串
  • 声明String类型变量时,使用一对 “”
  • String可以和8种基本数据类型变量做运算,且运算只能是连接运算:+
  • 运算的结果仍然是String类型
//避免:
String s = 123;//编译错误
String s1 = "123";
int i = (int)s1; //编译错误

进制(了解

1. 编程中涉及的进制及表示方式:

2. 二进制的使用说明:

  • 计算机底层的存储方式:所有数字在计算机底层都以二进制形式存在。
  • 二进制数据的存储方式:所有的数值,不管正负,底层都以补码的方式存储。
  • 原码、反码、补码的说明:
正数:三码合一
负数:源码:直接将一个数值换成二进制数。最高位是符号位。负数的反码:是对原码按位取反,只是最高位(符号位)确定为1。负数的补码:其反码加1。

3. 进制间的转换:

图示:

图示二进制转换为十进制:


图示十进制转换为二进制:

图示二进制与八进制、十六进制的转换:


运算符

算数运算符

+ - * / % (前)++ (后)++ (前)-- (后)--

典型代码

//除号:/int num1 = 12;int num2 = 5;int result1 = num1 / num2; //result1 = 2
//取余运算:%//结果的符号与被模数的符号相同//开发中,经常使用%判断能否被除尽的情况int m1 = 12;int n1 = 5;System.out.println(m1 % n1);//2int m2 = -12;int n2 = 5;System.out.println(m2 % n2);//-2int m3 = 12;int n3 = -5;System.out.println(m3 % n3);//2int m4 = -12;int n4 = -5;System.out.println(m4 % n4);//-2//(前)++ :先自增1;后运算。(后)++ :先运算;后自增1int a1 = 10;int b1 = ++a1;System.out.println("a1 = "+ a1 + ",b1 = "+b1);//11,11int a2 = 10;int b2 = a2++;System.out.println("a2 = "+ a2 + ",b2 = "+b2);//11,10int a3 = 10;++a3;//a3++;int b3 = a3;//注意点:short s1 = 10;//s1 = s1 + 1;//编译失败//s1 = (short) (s1 + 1);//正确的s1++;//自增1不会改变本身的数据类型System.out.println(s1);//11byte bb1 = 127;bb1++;System.out.println(bb1);//-128//(前)-- :先自减1;后运算。(后)-- :先运算;后自减1int a4 = 10;int b4 = a4--;System.out.println("a4 = "+ a4 + ",b4 = "+b4);//9,10int a5 = 10;int b5 = --a5;System.out.println("a5 = "+ a5 + ",b5 = "+b5);//9,9

赋值运算符

=  +=  -=  *=  /=  %=

典型代码

//=int i1 = 10;int j1 = 10;int i2 , j2;//连续赋值i2 = j2 = 10;int i3 = 10 , j3 = 20;int num = 10;num += 2;//num = num + 2;num %= 5;//num = num % 5;short s1 = 10;//s1 = s1 + 2;编译失败s1 += 2;//不会改变变量本身的数据类型//开发中,如果希望变量实现+2的操作,一般推荐num += 2;//开发中,如果希望变量实现+1的操作,一般推荐num++;int m = 3;int n = 6;//n *= m++;n *= ++m;System.out.println(n);System.out.println(m);

比较运算符

或关系运算符:==  !=  >  <  >=  <= instanceof

典型代码

        int i = 10;int j = 20;System.out.println(i == j);//falseSystem.out.println(i = j);//20boolean b1 = true, b2 = false;System.out.println(b2 == b1);//falseSystem.out.println(b2 = b1);//true//**说明**1. 比较运算符的结果是boolean类型。2. > < >= <= :只能使用在数值类型的数据之间。3. ==和!= :不仅可以使用在数值类型数据之间,还可以使用在其他引用类型变量之间。

逻辑运算符

 &  &&  |  ||  !  ^

典型代码

    //区分& 与 &&//相同点:1.& 与 && 的运算结果相同。 2.当符号左边是true时,二者都会执行符号右边的运算//不同点:当符号左边是false时,&继续执行符号右边的运算,&&不再执行符号右边的运算。//开发中,推荐使用&&boolean b1 = true;b1 = false;int num1 = 10;if (b1 & (num1++ > 0)){System.out.println(1);}else {System.out.println(2);}System.out.println(num1);//11boolean b2 = true;b2 = false;int num2 = 10;if (b2 && (num2++ > 0)){System.out.println(1);}else {System.out.println(2);}System.out.println(num2);//10//区分| 与 ||//相同点:1.| 与 || 的运算结果相同。 2.当符号左边是false时,二者都会执行符号右边的运算//不同点:当符号左边是true时,|继续执行符号右边的运算,||不再执行符号右边的运算。//开发中,推荐使用||boolean b3 = false;b3 = true;int num3 = 10;if (b3 | (num3++ > 0)){System.out.println(1);}else {System.out.println(2);}System.out.println(num3);//11boolean b4 = false;b4 = true;int num4 = 10;if (b4 || (num4++ > 0)){System.out.println(1);}else {System.out.println(2);}System.out.println(num4);//10

位运算符

 <<   >>   >>>  &  |  ^  ~

典型代码

     /*0000 0000 最高位为0,是正数,最高位为1,是负数A = 0011 1100b = 0000 1101A&B = 0000 1100   有一个为0,则结果为0,同时为1时结果才是1A|B = 0011 1101   有一个为1,则结果为1,同时为0时结果才是0A^B = 0011 0001   相同时结果为0,不相同结果为1~B = 1111 0010    与B完全相反>>   右移<<   左移*/System.out.println(2<<3);/*2的二进制为  0000 0010左移相当于把数字往做移动3位得到        0001 0000    相当于16*//*面试题:最高效2 * 8 的实现方式?2 << 3 或 8 << 11.位运算符操作的都是整型的数据2.<<:在一定范围内,每向左移1位,相当于 *2>>:在一定范围内,每向右移1位,相当于 /2*/

三元运算符

 (条件表达式) ? 表达式1 : 表达式2

典型代码

    //1.如果条件表达式为true,运算后的结果是表达式1;//2.如果条件表达式为false,运算后的结果是表达式2;int a = 99;int b = 10;int result = a > b ? a++ : b--;  //表达式1  先赋值后自增System.out.println(result);//输出99System.out.println("a = " + a);//输出 100System.out.println("b = " + b);//输出10//表达式1和表达式2要为可以赋给接受变量的类型(或可以自动转换)int a = 3;int b = 8;int c = a > b? 2.2 : 3.3;//改为    int c = a > b?(int) 2.2 : (int)3.3;System.out.println(c);double d = a > b ? a : b + 3;//满足 int --> double//可以嵌套使用(实现三个数的最大值)int a = 56;int b = 122;int c = 100;int max1 = a > b ? a : b;int max2 = max1 > c ? max1 : c;//推荐使用第一种// int max = (a > b? a : b) > c ?(a > b? a : b):c;System.out.println(max2);//凡是可以用三元运算符的地方,都可以改if-else,反之不成立//开发中,即可用三元运算符又可以用if-else的地方,优先用三元运算符。原因:简介、执行效率高。

流程控制

  1. 顺序结构:程序从上到下执行
  2. 分支结构:if-else switch-case
  3. 循环结构:for while do-while

分支结构

if-else
==结构一:==
if(条件表达式){执行表达式
}==结构二:==
if(条件表达式){执行表达式1
}else{执行表达式2
}==结构三:==
if(条件表达式){执行表达式1
}else if(条件表达式){执行表达式2
}else if(条件表达式){执行表达式3
}
...
else{执行表达式n
}

说明:

  1. else结构是可选的。
  2. 针对条件表达式:
    • 如果多个条件表达式之间“互斥”关系(或没有交集的关系),哪个判断和执行语句说明在上面或下面无所谓。
    • 如果多个条件表达式之间有交集的关系,需要根据实际情况,考虑清楚应该将哪个结构声明在上面。
    • 如果多个条件表达式之间有包含的关系,通常情况下,需要把范围小的声明在范围大的上面。否则,范围小的就没机会执行了。
  3. if-else结构是可以相互嵌套的。
  4. 如果if-else结构中的执行语句只有一行时,对应的{}可以省略的。但是,不建议。
switch-case

结构

switch(表达式){case 常量1:执行语句1;//break;case 常量2:执行语句2;//break;...default:执行语句n;//break;
}

说明:

  1. 根据switch表达式中的值,依次匹配case中的常量,一旦匹配成功,则进入相应的case结构中,调用执行语句。当执行完以后,则仍然继续向下执行case结构,直到遇到break关键字或结构末尾结束为止。
  2. break,可以在switch-case结构中使用,表示一旦执行到break,则跳出结构。
  3. switch结构中的表达式,只能是6种类型之一:byte、short、char、int、枚举(5.0)、String类型(7.0) 。
  4. case后声明的只能是常量,不能声明范围。
  5. break关键字是可选的。
  6. default:相当于if-else结构中的else;default结构是可选的,而且位置是灵活的。
    注意:
//当case执行语句一样是可以考虑合并的,如下:
switch(表达式){case 0:case 1:case 2:执行语句;break;case 3:case 4:case 5:执行语句;break;    }

循环结构

循环结构的四要素:

  1. 初始化条件
  2. 循环条件 —》是boolean类型
  3. 循环体
  4. 迭代条件
    说明:通常情况下,循环结束都是因为循环条件中返回false了。

for循环结构

    for(1;2;4){3}执行过程:1-2-3-4-2-3-4-...-2

while循环结构

   1while(2){3;4;}执行过程:1-2-3-4-2-3-4-...-2说明:1. 写while循环千万不要丢了迭代条件。一旦丢失,就可能导致死循环!2. 我们写程序要避免程序出现死循环。3. for循环和while循环时可以相互转换的!区别:for循环和while循环的初始化条件部分的作用范围不同。

do-while循环结构

    1do{3;4;}while(2);执行过程:1-3-4-2-3-4-...-2说明:do-while循环至少会执行一次循环体!

无限循环结构:while(true)或for(; ; )
如何结束一个循环结构:
1. 当循环条件是false时
2. 在循环体中,执行break

嵌套循环

  1. 嵌套循环:将一个循环结构A声明在另一个循环结构B的循环体中,就构成了嵌套循环。

    • 内层循环:循环结构A
    • 外层循环:循环结构B
  2. 说明:
    1. 内层循环结构遍历一遍,只相当于外层循环循环体执行了一次。
    2. 假设外层循环需要执行m次,内存循环需要执行n次。此时内层循环的循环体一共执行了m*n次。
    3. 外层循环控制行数,内层循环控制列数。

关键字:break和continue

使用范围 循环中使用的作用(不同点) 相同点
break: switch-case
循环结构中 结束当前循环 关键字后面不能声明执行语句
continue: 循环结构中 结束当次循环 关键字后面不能声明执行语句

补充:带标签的break和continue的使用

补充:Scanner类的使用

具体实现步骤:

  1. 导包:import java.util.Scanner;
  2. 实例化:Scanner scan = new Scanner(System.in);
  3. 调用Scanner类的相关方法(next() / nextXxx()),来获取指定类型的变量。
  4. 注意:需要根据相应的方法,输入指定类型的值。如果输入数据类型与要求的类型不匹配,会报异常:.InputMismatchException ,导致程序终止。

数组

数组的概述

定义:数组是相同类型数据的有序集合,每一个数据称作一个数组元素,每个数组元素可以通过一个下标来访问它们。
特点:

  1. 数组是有序排列的;
  2. 数组属于引用数据类型变量。数组的元素,既可以是基本数据类型,也可以是引用数据类型;
  3. 创建数组对象会在内层中开辟一整块连续的空间;
  4. 数组的长度一旦确定,就不能更改。
    数组的分类:
  5. 照维数:一维数组、二维数组、。。。
  6. 照数组元素的类型:基本数据类型元素的数组、引用数据类型元素的数组

数据结构:

  1. 数据与数据之间的逻辑关系:集合、一对一、一对多、多对多
  2. 数据的存储结构:
    • 线性表:顺序表(比如:数组)、链表、栈、队列
    • 树形结构:二叉树

一维数组

声明与初始化

//声明:dataType[] arrayRefVar;         //首选的方法dataType arrayRefVar[];
//创建dataType[] arrayRefVar=new dataType[arraySize];
//静态初始化int[] array=new int[]{1,2,3};//int[] array={1,2,3};//类型推断Man[] mans={new Man(1,1),new Man(2,2)};  //Man是类
//动态初始化int[] array=new int[2];array[0]=1;array[1]=2;
//注意错误的方式:int[] arr1 = new int[];int[5] arr2 = new int[5];int[] arr3 = new int[3]{1,2,3};

一维数组元素的引用
通过角标的方式调用:下标合法区间—[0,length-1]

属性:length
数组一旦初始化,其长度就是确定的。arr.length

一维数组的遍历

    for (int i = 0; i < arr.length; i++){System.out.println(arr[i]);}

一维数组元素的默认初始化

  • 数组元素是整型:0
  • 数组元素是浮点型:0.0
  • 数组元素是char型:0或’\u0000’,而非’0’
  • 数组元素是boolean型:false
  • 数组元素是引用数据类型:null

一维数组的内存解析

二维数组

概述

  1. 二维数组其实就是一个特殊的一维数组,一维数组中每个元素就是一个一维数组.
  2. 三维数组又是一个特殊的二维数组.

声明与初始化

    int[][] a = new int[10][10];String[][] b = new String[10][10];//静态初始化int[][] a = {{1,2,3},{4,5},{6,7,8}};
//动态初始化
//1.String[][] b = new String[3][2];
//2.String[][] b1 = new String[3][];
//也是正确的写法int[] arr[] = {{1,2,3,4},{4,5,6,7},{8,9,10,11}};

如何调用二维数组元素

     System.out.println(a[0][1]);//2System.out.println(b[1][1]);//nullb1[1] = new String[4];System.out.println(b1[1][0]);

二维数组的属性

     System.out.println(arr.length);//3System.out.println(arr[0].length);//4

二维数组的遍历

public class Text0 {public static void main(String[] args) {//静态初始化int[][] a={{1,2,3,4},{4,5,6,7},{8,9,10,11}};//遍历for (int i = 0; i < a.length; i++) {for (int j = 0; j < a[i].length; j++) {System.out.print(a[i][j]+" ");}System.out.println();}}
}

二维数组元素的默认初始化

  1. 规定:二维数组分为外层数组的元素,内层数组的元素

    • int[][] arr = new int[4][3];
    • 外层元素:arr[0],arr[1]等
    • 内层元素:arr[0][0],arr[1][2]等
  2. 数组元素的默认初始值
    1. 针对于初始化方式一:比如:int[][] arr = new int[4][3];

      • 外层元素的初始化值为:地址值
      • 内层元素的初始化值为:与一维数组初始化情况相同
    2. 针对于初始化方式二:比如:int[][] arr = new int[4][];
      • 外层元素的初始化值为:null
      • 内层元素的初始化值为:不能调用,否则报错。

二维数组的内存解析

数组的常见算法

一、数组元素的赋值(杨辉三角)//面试中考察较多

     Scanner number=new Scanner(System.in);System.out.println("输入杨辉三角形的行数");int numbers= number.nextInt();int[][] arr1=new int [numbers][numbers];for(int i=0;i<numbers;i++){for(int j=0;j<=i;j++){if(i==j||j==0){arr1[i][j]=1;}else if(i>=2){arr1[i][j]=arr1[i-1][j]+arr1[i-1][j-1];}System.out.print(arr1[i][j]);}System.out.print("\n");}

二、求数值型数组元素中的最大值、最小值、平均数、总和等等

算法的考查:求数值型数组中元素的最大值、最小值、平均值、总和等
题目:
定义一个int型的一维数组,包含10个元素,分别赋一些随机数,然后求出所有元素的最大值、最小值、和值 、平均值并输出。
要求:所有的随机数都是两位数。
提示:获取[a,b]范围内的随机数 Math.random()*(b-a+1)+10 ; //radom 获取的值为double类型可以根据需求进行强制转换。

     int [] arr =new int [10];for(int i=0;i<arr.length;i++){arr[i]=(int)(Math.random()*(99-10+1)+10);}for(int i=0;i<arr.length;i++){System.out.print(arr[i]+" ");}System.out.print("\n");//求数组元素的最大值int maxValue=arr[0];for(int i=0;i<arr.length;i++){if(maxValue<arr[i]){maxValue=arr[i];}}System.out.println("最大值为:"+maxValue);//求数组元素的最小值int minValue=arr[0];for(int i=0;i<arr.length;i++){if(minValue>arr[i]){minValue=arr[i];}}System.out.println("最小值为:"+minValue);//求数组元素的总和int sum=0;for(int i=0;i<arr.length;i++){sum+=arr[i];}System.out.println("总和"+sum);//求数组元素的平均值int value=sum/arr.length;System.out.println("平均值"+value);

三、数组的复制、反转、查找(线性查找、二分法查找)

1. 数组的复制(区别于数组的赋值)

    //声名arr1和arr2两个int型的数组。int [] arr1,arr2;//使用静态初始化将arr1中的8个元素初始化为:2,3,5,7,11,13,17,19.arr1=new int []{2,3,5,7,11,13,19};//显示arr1中的内容。for(int i=0;i<arr1.length;i++){System.out.print(arr1[i]+" ");}//赋值arr1变量等于arr2,修改arr2中的偶索引元素使其等于索引值。打印出arr1。arr2=arr1;for(int i=0;i<arr2.length;i++){if(i%2==0){arr2[i]=i;}}System.out.println();for(int i=0;i<arr1.length;i++){System.out.print(arr1[i]+" ");}//思考问题:arr1和arr2有什么关系//我们可以发现该程序的输出结果和变化后的arr2中的值相同,所以arr2=arr1并不是一个数组间的复制操作,而是通过arr1对arr2进行了一步赋值操作。//但是经过arr2=arr1这一操作后,使arr1和arr2的地址值相同,都指向了堆空间内的唯一的一个数组实体。//那么下面我们修改代码实现arr2对arr1数组的复制int [] arr1,arr2;arr1=new int []{2,3,5,7,11,13,19};for(int i=0;i<arr1.length;i++){System.out.print(arr1[i]+" ");}//修改处://如果想要arr有独立的数值空间 就先用new给arr2开辟一个堆空间arr2=new int[arr1.length];for(int i=0;i<arr1.length;i++){arr2[i]=arr1[i];}//for(int i=0;i<arr2.length;i++){if(i%2==0){arr2[i]=i;}}System.out.println();for(int i=0;i<arr1.length;i++){System.out.print(arr1[i]+" ");}

2. 数组的反转

     String [] arr=new String []{"AA","BB","CC","DD","GG","EE"};String temp;for(int i=0;i<arr.length/2;i++){temp=arr[i];arr[i]=arr[arr.length-1-i];arr[arr.length-1-i]=temp;}for(int i=0;i<arr.length;i++){System.out.print(arr[i]+" ");}

3. 数组的查找

 //查找//线性查找:String dest="BB";boolean isflage=true;String [] arr=new String []{"AA","BB","CC","DD","GG","EE"};for(int i=0;i<arr.length;i++){if(dest.equals(arr[i])){System.out.println("找到了  ,指定元素的位置为:"+i);isflage=false;break;}}if(isflage){System.out.println("未找到指定元素的位置");}//二分法查找//前提:所要查找的数组必须有序int [] arr2=new int []{-98,-32,2,34,54,66,79,105,210,333};int dest1=-32;int head=0;//初始的首索引int end=arr2.length-1;boolean isFalse=true;while(head<=end){int middle=(head+end)/2;if(dest1==arr2[middle]){System.out.println("找到了指定元素位置为:"+middle);isFalse=false;break;}else if(arr2[middle]>dest1){end=middle-1;}else {head=middle+1;}}if(isFalse){System.out.println("未找到指定元素");}

四、数组元素的排序算法//面试中考察较多

  • 通常来说,排序的目的是快速查找
  • 衡量排序算的优劣:1.时间复杂度 2.空间复杂度3.稳定性

比如冒泡排序的排序思想:

  1. 比较相邻的元素。如果第一个比第二个大(升序),交换他们两个。
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这个步骤做完后,最后的元素会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何邮递数字需要比较为止。
    //冒泡排序的实现int temp;int [] arr=new int[]{43,32,76,-98,0,64,33,-21,32,99};for(int i=0;i<arr.length-1;i++){for(int j=0;j<arr.length-i-1;j++){if(arr[j]>arr[j+1]){temp=arr[j];arr[j]=arr[j+1];arr[j+1]=temp;}}}for(int i=0;i<arr.length;i++){System.out.print(arr[i]+" ");}

Arrays工具类的使用

  1. 理解:

    1. 定义在java.util包下。
    2. Arrays:提供了很多操作数组的方法。
  2. 使用:
    //boolean equals( int[] a, int[]b):判断两个数组是否相等int[] arr1 = new int[]{1,2,3,4};int[] arr2 = {2,1,3,4};boolean isEquals = Arrays.equals(arr1, arr2);System.out.println(isEquals);//输出数组信息System.out.println(Arrays.toString(arr1));//将指定值填充到数组之中Arrays.fill(arr1,10);System.out.println(Arrays.toString(arr1));//对数组进行排序Arrays.sort(arr2);System.out.println(Arrays.toString(arr2));//int binarySearch(int[] a, int key),查找int[] arr3 = {-98,-34,2,34,54,66,76,87,99,145};int index = Arrays.binarySearch(arr3,87);if (index >= 0){System.out.println(index);}else {System.out.println("-1");}

数组的常见异常

.ArrayIndexOutOfBoundsException数组角标越界异常

    int[] a = {1,2,3,4};System.out.println(a[4]);

.NullPointerException空指针异常

   /* String[][] str = new String[4][];System.out.println(str[0][0]);*//*int[] a = {1,2,3,4};a = null;System.out.println(a[0]);*/String[] b = {"AA","BB"};b[0] = null;System.out.println(b[0].toString());

面向对象-上

类与对象

  1. 面向对象学习的三条主线:

    1. Java类及类的成员:属性、方法、构造器:代码块、内部类
    2. 面向对象的大特征:封装性、继承性、多态性、(抽象性)
    3. 其他关键字:this、super、static、final、abstract、interface、package、import等
  2. 面向对象和面向过程的理解:

    1. 面向过程:强调的是功能行为,以函数为最小单位,考虑怎么做。
    2. 面向对象:强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。
  3. 完成一个项目(或功能)的思路:

    1. 根据问题需要,选择问题所针对的现实世界中的实体。
    2. 从实际中寻找解决问题相关的属性和功能,这些属性和功能就形成了概念世界中的类。
    3. 把抽象的实体用计算机语言进行描述,形成计算机世界中类的定义。即借助某种程序语言,把类构成计算机能够识别和处理的数据结构。
    4. 将类实例化成计算机世界中的对象。对象是计算机世界中解决问题的最终工具。
  4. 面向对象中两个重要的概念:

    • 类:对一类事物的描述,是抽象的、概念上的定义
    • 对象:是实际存在的该类事物的每个个体,因而也称为实例(instance)
      • 面向对象程序设计的重点是类的设计
      • 设计类,就是设计类的成员
    • 二者的关系:对象,是由类new出来的,派生出来的。
  5. 类和对象的使用(面向对象思想落地实现的规则一)

    1. 创建类,设计类的成员
    2. 创建类的对象
    3. 通过“对象.属性”或“对象.方法”调用对象的结构

补充:设计类,就是设计类的成员(几个概念的使用说明:)

  • 属性 = 成员变量 = field = 域、字段
  • 方法 = 成员方法 = 函数 = method
  • 创建类的对象 = 类的实例化 = 实例化类
  1. 对象的创建于对象的内存解析

典型代码:

Person p1 = new Person();
Person p2 = new Person();
Person p3 = p1;//没有新创建一个对象,共用一个堆空间中的对象实体

说明:
如果创建了一个类的多个对象,则每个对象都独立的拥有一套类的属性。(非static的)
意味着:如果我们修改一个对象的属性a,则不影响另外一个对象属性a的值。
内存解析:

  1. 匿名对象:我们创建的对象,没显示的赋给一个变量名。即为匿名对象
    特点:匿名对象只能调用一次。
    举例:
new Phone().sendEmail();
new Phone().price = 1999;

应用场景:

PhoneMall mall = new PhoneMall();
//匿名对象的使用
mall.show(new Phone());其中,
class PhoneMall{public void show(Phone phone){phone.sendEmail();phone.playGame();}
}
  1. 理解“万事万物皆对象”

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

      • Scanner,String等
      • 文件:File
      • 网络资源:URL
    2. 涉及到Java语言与前端Html、后端的数据库交互时,前后端的结构在Java层面交互时,都体现为类、对象。

JVM的内存结构

  • 编译完源程序以后,生成一个或多个字节码文件。
  • 我们使用JVM中的类的加载器和解析器对生成的字节码文件进行解析运行。意味着,需要将字节码文件对应的类加载到内存中,涉及到内存解析。

内存解析

类的结构之一:属性

  • 对比:属性 VS 局部变量

    1. 相同点:

      1. 定义变量的格式:数据类型 变量名 = 变量值
      2. 先声明,后使用
      3. 变量都其对应的作用域
    2. 不同点:
      1. 在类中声明的位置不同

        • 属性:直接定义在类的一对{}内
        • 局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量
      2. 关于权限修饰符的不同
        • 属性:可以在声明属性时,指明其权限,使用权限修饰符。常用的权限修饰符:private、public、缺省、protected --》封装性
        • 局部变量:不可以使用权限修饰符。
      3. 默认初始化值的情况:
        • 属性:类的属性,根据其类型,都默认初始化值。

          • 整型(byte、short、int、long:0
          • 浮点型(float、double:0.0
          • 字符型(char:0(或 ‘\u0000’
          • 布尔型(boolean:false
          • 引用数据类型(类、数组、接口:null
        • 局部变量:没默认初始化值。意味着,我们在调用局部变量之前,一定要先赋值。特别的:形参在调用时,我们赋值即可。
      4. 在内存中加载的位置:
        • 属性:加载到堆空间中(非static
        • 局部变量:加载到栈空间

类的结构之二:方法

方法:描述类应该具备的功能。
比如:Math类:sqrt()\random() \ … Scanner类:nextXxx()…
举例:

public void eat(){}
public void eat(int hour){}
public String getName(){}方法的声明:权限修饰符 返回值类型 方法名(形参列表){方法体
}
注意:static、final、abstract来修饰方法的

说明:

  1. 关于权限修饰符:默认方法的权限修饰符先都使用public

    • Java规定的4种权限修饰符:private、public、缺省、protected
  2. 返回值类型:有返回值 VS 没有返回值
    • 如果方法有返回值,则必须在方法声明时,指定返回值的类型。同时,方法中,需要使用return关键字来返回指定类型的变量或常量:“return 数据”。
    • 如果方法没有返回值,则方法声明时,使用void来表示。通常,没有返回值的方法中,就不需要使用return,都是,如果使用的话,只能“return;”表示结束此方法的意思。
    • 我们定义方法时该不该返回值?
      1. 题目要求
      2. 凭经验:具体情况具体分析
  3. 方法名:属于标识符,遵循标识符的规则和规范,“见名知意”
  4. 形参列表:方法可以声明0个,1个或多个形参。
    • 格式:数据类型 形参1 ,数据类型 形参2,…
    • 我们定义方法时该不该定义形参?
      1. 题目要求
      2. 凭经验:具体情况具体分析
  5. 方法体:方法功能的体现
  6. 方法的使用中,可以调用当前类的属性或方法
    • 特殊的:方法A中调用了方法A:递归方法。
    • 方法中:不可以定义方法。

关键字:return

return关键字:

  1. 使用范围:使用在方法体中
  2. 作用:
    1. 结束方法
    2. 针对于返回值类型的方法,使用“return 数据”方法返回所有的数据。
  3. 注意点:return关键字后面不可以声明执行语句。

方法的重载

  1. 方法的重载概念

    • 定义:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
    • 总结:“两同一不同”:同一个类、相同方法名;参数列表不同,参数类型不同。
  2. 举例
    public void getSum(int i,int j){System.out.println(1);}public void getSum(double d1,double d2){System.out.println(2);}public void getSum(String s,int i){System.out.println(3);}
  1. 如何判断是否构成方法的重载?

    • 严格按照定义判断:两同一不同。
    • 跟方法的权限修饰符、返回值类型、形参变量名、方法体都没关系!
  2. 如何确定类中某一个方法的调用:
    • 方法名 ----》参数列表
    • 面试题:方法的重载与重写的区别?

可变个数形参的方法

  1. 使用说明

    1. jak5.0新增的内容
    2. 具体使用:
      1. 可变个数形参的格式:数据类型 … 变量名。
      2. 当调用可变个数形参的方法时,传入的参数个数可以是:0个,1个,2个,。。。
      3. 可变个数形参的方法与本类中方法名相同,形参不同的方法之间构成重载。
      4. 可变个数形参的方法与本类中方法名相同,形参类型也相同的数组之间不构成重载。换句话说,二者不能共存。
      5. 可变个数形参在方法的形参中,必须声明末尾。
      6. 可变个数形参在方法的形参中,最多只能声明一个可变形参。
  2. 举例
    public void show(int i){}public void show(String s){System.out.println("show(String)");}public void show(String ... strs){System.out.println("show(String ... strs)");for (int i = 0;i < strs.length;i++){System.out.print(strs[i]);}}//不能与上一个同时存在
//    public void show(String[] strs){}调用时ObjectOrientedTest test = new ObjectOrientedTest();test.show("hello","ad");test.show();test.show(new String[]{"AA","aa","dd"});

Java的值传递机制

  1. 针对于方法内变量的赋值举例:
    //基本数据类型int m = 10;int n = m;System.out.println("m=" + m + ",n=" + n);n = 20;System.out.println("m=" + m + ",n=" + n);//引用数据类型ValTest v1 = new ValTest();v1.valId = 1001;ValTest v2 = v1;//赋值以后,v1和v2的地址值相同,都指向堆空间中同一个对象实体System.out.println(v1.valId+","+v2.valId);v2.valId = 1002;System.out.println(v1.valId+","+v2.valId);

规则:

  • 如果变量是基本数据类型,此时赋值的是变量所保存的数据值。
  • 如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值。
  1. 针对于方法的参数概念

    • 形参:方法定义时,声明的小括号内的参数
    • 实参:方法调用时,实际传递给形参的数据
  2. Java中参数传递机制:值传递
    • 规则:

      • 如果参数是基本数据类型,此时实参赋给形参的是实参真实存储的数据值。
      • 如果参数是引用数据类型,此时实参赋给形参的是实参存储数据的地址值。
  3. 典型例题与内存解析

递归方法

  1. 定义:一个方法体内调用它自身。
  2. 如何理解递归方法?
    • 方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无需循环控制。
    • 递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环。
  3. 举例:
    public int getSum(int n){//计算1-n之间所有自然数的和if (n == 1){return 1;}else {return n + getSum(n - 1);}}

面向对象的特征一:封装性

封装与隐藏

  1. 为什么要引入封装性?

    • 我们程序设计追求 “高内聚,低耦合”。

      • 高内聚:类的内部数据操作细节自己完成,不允许外部干涉;
      • 低耦合:仅对外暴露少量的方法用于使用。
    • 隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说,把改隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。
  2. 问题引入:
    • 当我们创建一个类的对象以后,我们可以通过“对象.属性”的方式,对对象的属性进行赋值。这里,赋值操作要受到属性的数据类型和存储范围的制约。除此之外,没其他制约条件。但是,在实际问题中,我们往往需要给属性赋值加入额外的限制条件。这个条件就不能在属性声明时体现,我们只能通过方法进行限制条件的添加。(比如:setLegs()同时,我们需要避免用户再使用“对象.属性”的方式对属性进行赋值。则需要将属性声明为私有的(private))。—》此时,针对于属性就体现了封装性。
  3. 封装性思想具体的代码体现:
    1. 将类的属性xxx私有化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)此属性的值。
    private double r;
    public void setR(double r){this.r = r;
    }
    public double getR(){return r;
    }
    
    1. 不对外暴露的私有的方法。
    2. 单例模式。(将构造器私有化)
    3. 如果不希望类在包外被调用,可以将类设置为缺省的。
  4. Java规定的四种权限修饰符
    1. 权限从小到大顺序为:private 《 缺省 《 protected 《 public

    2. 具体的修饰范围:

    3. 权限修饰符可用来修饰的结构说明:4种权限都可以用来修饰类的内部结构:属性、方法、构造器、内部类;修饰类的话,只能使用:缺省、public。

类的结构:构造器Corstructor

  1. 构造器(或构造方法):Constructor

    • 构造器的作用:1.创造对象 ;2.初始化对象信息。
  2. 使用说明:
    1. 如果没显示的定义类的构造器的话,则系统默认提供一个空参的构造器;
    2. 定义构造器的格式:权限修饰符 类名(形参列表){};
    3. 一个类中定义的多个构造器,彼此构成重载;
    4. 一旦我们显示的定义了类的构造器之后,系统就不再提供默认的空参构造器;
    5. 一个类中,至少会有一个构造器。
  3. 举例:
    public PackageTest(){System.out.println(1);
    }
    public PackageTest(double n){r = n;
    }
    public PackageTest(double n,int a){r = n;i = a;
    }
    

属性赋值的顺序

总结:属性赋值的先后顺序:1.默认初始化;2.显示初始化;3.构造器中初始化;4.通过 “对象.方法” 或 "对象.属性"的方式,赋值。
以上操作的先后顺序:1–2–3–4 。

JavaBean的概念

  1. JavaBean是一种Java语言写成的可重用组件。
  2. 所谓JavaBean,是指符合如下标准的Java类:
    • 类是公共的
    • 有一个无参的公共构造器
    • 有属性,且有对应的get、set方法

关键字:this

  1. 可以调用的结构:属性、方法、构造器
  2. this调用属性、方法:this理解为当前对象 或 当前正在创建的对象。
    1. 在类的方法中,我们可以使用 “this.属性” 或 "this.方法"的方式,调用当前对象属性或方法。但是,通常情况下,我们都选择省略 “this.” 。特殊情况下,如果方法的形参和类的属性同名时,我们必须显示的使用 "this.变量"的方式,表明此变量是属性,而非形参。
    2. 在类的构造器中,我们可以使用 “this.属性” 或 "this.方法"的方式,调用当前正在创建的对象属性或方法。但是,通常情况下,我们都省略 “this.”。特殊情况下,如果构造器的形参和类的属性同名时,我们必须显示的使用 "this.变量"的方式,表明此变量是属性,而非形参。
  3. this调用构造器:
    1. 我们在类的构造器中,可以显示的使用“this(形参列表)”方式,调用本类中指定的其他构造器
    2. 构造器中不能通过“this(形参列表)”方式调用自己
    3. 如果一个类中有n个构造器,则最多有n - 1构造器中使用“this(形参列表)”
    4. 规定:“this(形参列表)”必须声明在当前构造器的首行
    5. 构造器内部,最多只能声明一个“this(形参列表)”,用来调用其他的构造器

关键字:package/import

package的使用

  1. 使用说明:

    1. 为了更好的实现项目中类的管理,提供包的概念;
    2. 使用package声明类或接口所属的包,声明在源文件的首行;
    3. 包,属于标识符,遵循标识符的命名规则、规范(xxxyyyzzz)、“见名知意”;
    4. 每“.”一次,就代表一层文件目录。
  2. 举例:
    举例一:莫航运软件系统包括:一组域对象、GUI和reports子系统

举例二:MVC设计模式

  1. JDK中的主要包的介绍:

import的使用:

面向对象-中

面向对象的特征二:继承性

  1. 为什么要有类的继承性?(继承性的好处)

    1. 减少了代码的冗余,提高了代码的复用性
    2. 便于功能的扩展
    3. 为之后多态性的使用,提供了前提

图示:

  1. 继承性的格式:
class A extends B{}A:子类、派生类、subclassB:父类、超类、基类、superclass
  1. 子类继承父类以后有哪些不同?

    1. 体现:一旦子类A继承父类B以后,子类A中就获得了父类B中声明的所有的属性和方法。

      • 特别的,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。只因为封装性的影响,使得子类不能直接调用父类的结构而已。
    2. 子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的扩展。
      • 子类和父类的关系,不同于子集和集合的关系。extends:延展、扩展
  2. Java中继承性的说明
    1. 一个类可以被多个子类继承;
    2. Java中类的单继承性:一个类只能有一个父类;
    3. 子父类是相对的概念;
    4. 子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类;
    5. 子类继承父类以后,就获得了直接父类以及所有间接父类中声明的属性和方法。

图示:

  1. java.lang.Object类的理解

    1. 如果我们没显示的声明一个类的父类的话,则此类继承于java.lang.Object类
    2. 所有的java类(除java.lang.Object类之外都直接或间接的继承于java.lang.Object类
    3. 意味着,所有的java类具有java.lang.Object类声明的功能

方法的重写

  1. 什么是方法的重写(override或overwrite)?
    子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作。

  2. 应用:重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法。

  3. 举例:

public class InheritTest {public double sum(){return 0;}
}
class Inher extends InheritTest{@Overridepublic double sum() {return super.sum();}}
  1. 重写的规则:
    方法的声明:权限修饰符 返回值类型 方法名(形成列表) throws 异常类型{
    //方法体
    }
    约定俗称:子类中的叫重写的方法,父类中的叫被重写的方法

    1. 子类重写的方法的方法名和形成列表与父类被重写的方法名和形成列表相同
    2. 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符
      • 特殊情况:子类不能重写父类声明private权限的方法
    3. 返回值类型:
      • 父类被重写的方法返回值类型是void,则子类重写的方法返回值类型只能是void
      • 父类被重写的方法返回值类型是A类型,则子类重写的方法返回值类型可以是A类或A类的子类
      • 父类被重写的方法返回值类型是基本设计类型,则子类重写的方法返回值类型必须是相同的基本数据类型
    4. 子类重写的方法抛出的异常类型 <= 父类被重写的方法抛出的异常类型
    5. 子类和父类中的同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static的(不是重写)
  2. 面试题:区分方法的重写和重载?

关键字:super

  1. super关键字可以理解为:父类的
  2. 可以用来调用的结构:属性、方法、构造器
  3. super调用属性、方法:
    1. 我们可以在子类的方法或构造器中,通过使用“super.属性”或“super.方法”方式,显示的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略“super.”
    2. 特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类声明的属性,则必须显示的使用“super.属性”的方式,表明调用的是父类中声明的属性
    3. 特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显示的使用“super.方法”的方式,表明调用的是父类中被重写的方法
  4. super调用构造器:
    1. 我们可以在子类的构造器中显示的使用“super(形成列表)”的方式,调用父类中声明的指定的构造器
    2. “super(形成列表)”的使用,必须声明在子类构造器的首行!
    3. 我们在类的构造器中,针对于“this(形成列表)”或“super(形成列表)”只能二选一,不能同时出现
    4. 在构造器的首行,没显示的声明“this(形成列表)”或“super(形成列表)”,则默认调用的是父类中空参的构造器:super()
    5. 在类的多个构造器中,至少一个类的构造器中使用了“super(形成列表)”,调用父类中的构造器

面向对象的特征三:多态性

  1. 多态性的理解:可以理解为一个事物的多种形态。
  2. 何为多态性:
    • 对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)
    • 举例:
    Person p = new Man();
    Object obj = new Date();
    
  3. 多态性的使用:虚拟方法调用
    • 有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。
    • 总结:编译,看左边;运行,看右边。
  4. 多态性的使用前提:1.类的继承关系;2.方法的重写
  5. 多态性的应用举例:
多态:多态:多种状态在面向对象的语言中,接口的不同的实现即为多态多态性:允许你将你的父类引用设置成为一个或者多个子类对象class F{}class Z extends F{}public class Test{public static void main(String[] args){//第一种:创建父类对象 (只能调用父类中的方法)F f = new F();//第二种:创建子类对象//1、父类中继承的//2、自己定义的//3、如果存在重写,调用的是重写之后的Z z = new Z();//第三种:父类的引用,指向之类对象//1、父类中自己写的//2、子类中重写的父类的方法调用重写之后的方法,屏蔽了自己特有的方法F f = new Z();// 等价于 Z z = new Z();   F f = z;}}多态的格式:普通的父类 自定义变量 = new 子类();抽象类抽象类 自定义变量 = new 抽象类的子类();public abstract class Hero {public abstract void ATK();//攻击}public class HouYi extends Hero{//一个类继承一个抽象类,就必须实现抽象类中的所有的抽象方法//如果不想实现,就要将子类变成一个抽象类@Overridepublic void ATK() {System.out.println("发动一个射日的技能");}public static void main(String[] args) {//我们非常清楚,抽象类不能创建对象//我们只能通过子类进行创建对象Hero hero = new HouYi();hero.ATK();}}接口public interface Skill {public void useSkill();//使用技能的抽象方法}/*** 惩戒的技能*/public class ChengJie implements Skill{@Overridepublic void useSkill() {System.out.println("后羿使用惩戒技能");}}public class Test_02 {public static void main(String[] args) {Skill s1 = new ChengJie();//接口不能创建对象,所以使用实现类对象s1.useSkill();}}一句话总结多态每种事务都有多种体现的方式,父类的引用指向子类对象 或者 接口的引用指向实现类对象
  1. 多态性使用的注意点:对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)
  2. 关于向上转型与向下转型:
    1. 向上转型:多态
    2. 向下转型:
      1. 为什么使用向下转型:

        • 有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。
        • 如何才能调用子类特有的属性和方法?使用向下转型。
      2. 如何实现向下转型:使用强制类型转换符:()
      3. 使用时的注意点:
        • 使用强转时,可能出现ClassCastException的异常。
        • 为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,一旦返回true,就进行向下转型。如果返回false,不进行向下转型。
      4. instanceof的使用:
        1. a instanceof A:判断对象a是否是类A的实例。如果是,返回true;不是,返回false。
        2. 如果a instanceof A返回true,则a instanceof B也返回true,其中,类B是类A的父类。
        3. 要求x所属的类与类A必须是子类和父类的关系,否则编译错误。

Object类的使用

  1. java.lang.Object类的说明:

    1. Object类是所有Java类的根父类
    2. 如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类
    3. Object类中的功能(属性、方法)就具通用性
      • 属性:无
      • 方法:equals() / toString() / getClass() /hashCode() /clone() /finalize() /wait()、notify()、notifyAll()
    4. Object类只声明了一个空参的构造器
  2. equals()方法

    1. equals()的使用:

      1. 是一个方法,而非运算符
      2. 只能适用于引用数据类型
      3. Object类中equals()的定义: public boolean equals(Object obj){ return (this == obj); }
        • 说明:Object类中定义的equals()和 == 的作用是相同的:比较俩个对象的地址值是否相同,即两个引用是否指向同一个对象实体。
      4. 像String、Date、File、包装类等都重写了/Object类中的equals()方法。重写以后,比较的不是两个引用的地址值是否相同,而是比较两个对象的“实体内容”是否相同。
      5. 通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的“实体内容”是否相同。那么,我们就需要对Object类中的equals()进行重写。
        • 重写的原则:比较两个对象的实体内容是否相同。
    2. 如何重写equals():在开发中,自动生成equals()方法。
    3. 回顾 == 运算符的使用:
      1. 可以使用在基本数据类型和引用数据类型变量中。
      2. 如果比较的是基本数据类型变量:比较两个保存的数据是否相等。(不一定类型相同)
      3. 如果比较的是引用数据类型变量:比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体
      4. 补充:== 符号使用时,必须保证符号左右两边的变量类型一致
  3. toString()方法

    1. toString()的使用:

      1. 当我们输出一个对象的引用时,实际上就是调用当前对象的toString()
      2. Object类中toString()的定义:public String toString(){ return getClass().getName() + “@” + Integer.toHexString(hashCode());}
      3. 像String、Date、File、包装类等都重写了Object类中的toString()方法。使得在调用对象的toString()时,返回:实体内容信息。
      4. 自定义也可以重写toString()方法,当调用此方法时,返回对象的“实体内容”
    2. 如何重写toString():开发中,自动生成toString()方法。

单元测试方法(Java中的JUnit单元测试)

  • 步骤:

    1. 选中当前工程 》 右键选择:build path 》 add libraries 》 JUnit4 - 下一步;
    2. 创建Java类,进行单元测试。此时的Java类要求:1.此类是public的;2.此类提供公共的无参的构造器
    3. 此类中声明单元测试方法。此时的单元测试方法:方法的权限是public,没有返回值,没有形参
    4. 此单元测试方法上需要声明注解:@Test,并在单元测试类中导入:import org.junit.Test;
    5. 声明好单元测试方法以后,就可以在方法体内测试相关的代码。
    6. 写完代码以后,左键双击单元测试方法名,右键:run as-JUnit Test
  • 说明:
    1. 如果执行结果没有任何异常:绿条
    2. 如果执行结果出现异常:红条

包装类的使用

  1. 为什么要有包装类(或封装类)

    • 为了使基本数据类型的变量具有类的特征,引入包装类。
  2. 基本数据类型与对应的包装类:

  3. 需要掌握的类型间的转换:(基本数据类型、包装类、String)

  • 基本数据类型《—》包装类:JDK5.0 新特性:自动装箱与自动拆箱
  • 基本数据类型、包装类—》String:调用String重载的valueof(Xxx xxx)
  • String—》基本数据类型、包装类:调用包装类的parseXxx(String s)
  • 注意:转换时,可能会报NumberFormatEeception
  • 应用场景举例:Vector类中关于添加元素,只定义了形参为Object类型的方法:v.addElement(Object obj);//基本数据类型—》包装类—》使用多态

面向对象-下

关键字:static(静态的)

  1. 可以用来修饰的结构:属性、方法、代码块、内部类

  2. static修饰属性:静态变量(或类变量)

    1. 属性,按是否使用static修饰,又分为:静态属性 VS 非静态属性(实际变量)

      • 实际变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改。
      • 静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过。
    2. static修饰属性的其他说明:
      1. 静态变量随着类的加载而加载。可以通过“类.静态变量”的方式进行调用
      2. 静态变量的加载要早于对象的创建
      3. 由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中
      4. 调用分别
        类变量 实例变量
        yes no
        对象 yes yes
  3. 静态变量内存解析:

  4. static修饰方法:静态方法、类方法

    1. 随着类加载而加载,可以通过类.静态方法的方式进行调用
    2. 调用分别
      静态方法 非静态方法
      yes no
      对象 yes yes
    3. 静态方法中,只能调用静态的方法或属性;非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性
  5. static的注意点:

    1. 在静态的方法内,不能使用this关键字、super关键字
    2. 关于静态属性和静态方法的使用,大家都从生命周期的角度去理解
  6. 如何判定属性和方法应该使用static关键字:

    1. 关于属性

      • 属性是可以被多个对象所共享的,不会随着对象的不同而不同的
      • 类中的常量也常常声明为static
    2. 关于方法
      • 操作静态属性的方法,通常设置为static的
      • 工具类中的方法,习惯上声明为static的。比如:Math、Arrays、Collections

单例模式

  1. 设计模式的说明

    1. 理解:设计模式是在大量的实践中总结和理论化之后的代码结构、编程风格、以及解决问题的思考方法。
    2. 常用设计模式—23种经典的设计模式
      • 创建型模式(5种):工厂方法模式、抽象工厂模式、单列模式、建造者模式、原型模式。
      • 结构型模式(7种):适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
      • 行为模式(11种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、访问者模式、中介者模式、解释器模式。
  2. 单例模式
    1. 要解决的问题:所谓的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例。
    2. 具体代码的实现:
      //饿汉式:坏处:对象加载时间长。好处:饿汉式是线程安全的
      class A{
      private A(){//1.私有类的构造器}
      //2.内部创建类的对象
      //4.要求此对象也必须声明为静态的
      private static A instance = new A();
      //3.提供公共的静态的方法,返回类的对象
      public static A getInstance(){return instance;
      }
      }
      //懒汉式:好处:延迟对象的创建。坏处:当前写法是线程不安全
      class B{
      //1.私有类的构造器
      private B(){}
      //2.声明当前类对象,没初始化
      //4.要求此对象也必须声明为静态的
      private static B instance = null;
      //3.声明public、static的返回当前类对象的方法
      public static B getInstance(){
      if (instance == null){instance = new B();
      }return instance;
      }
      }
      

main()的使用说明

main()的使用说明:

  1. main()方法作为程序的入口
  2. main()方法也是一个普通的静态方法
  3. main()方法可以作为我们与操作台交互的方式。(之前:使用Scanner)

如何将控制台获取的数据传给形参:String[] args
运行时:java 类名 “aa” “AA” “123”

类的结构:代码块

类的成员之一:代码块(初始化块)(重要性较属性、方法、构造器差 一些)

{//直接用{}定义
}
static {//静态代码块
}
  1. 代码块的作用:用来初始化类、对象的信息
  2. 分类:代码块要是使用修饰符,只能使用static(静态代码块 VS 非静态代码块)
    • 静态代码块:

      • 内部可以输出语句
      • 随着类的加载而执行,而且执行一次
      • 作用:初始化类的信息
      • 如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行
      • 静态代码块的执行要优先于非静态代码块的执行
      • 静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构
    • 非静态代码块
      • 内部可以输出语句
      • 随着对象的创建而执行
      • 每创建一个对象,就执行一次非静态代码块
      • 作用:可以在创建对象时,对对象的属性等进行初始化
      • 如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
      • 非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法
  3. 实例化子类对象时,涉及到父类、子类中静态代码块、非静态代码块、构造器的加载顺序:由父及子,静态先行。

属性的赋值顺序

  • 1.默认初始化;2.显示初始化/5.在代码块中赋值;3.构造器中初始化;4.有了对象以后,可以通过 "对象.属性"或 "对象.方法"的方式,进行赋值
  • 执行的先后顺序:1–>2/5–>3–>4

关键字:final(最终的)

  1. 可以用来修饰:类、方法、变量
  2. 具体的:
    1. final用来修饰一个类:此类不能被其他类所继承。比如:String类、System类、StringBuffer类
    2. final用来顺序方法:表明此方法不可以被重写。比如:Object类中的getClass();
    3. final用来修饰变量:此时的变量就称为是一个常量
      1. final修饰属性:可以考虑赋值的位置有:显示初始化、代码块初始化、构造器中初始化
      2. final修饰局部变量:尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋值以后,就只能在方法体内使用此形参,但不能重新赋值。
    4. static final:用来修饰属性:全局变量

关键字:abstract(抽象的)

  1. 可以用来修饰:类、方法
  2. 具体的:
    • abstract修饰类:抽象类(抽象类属于引用数据类型)

      • 类本身是不存在的,所以抽象类无法创建对象(无法实例化)
      • 理解:类和类之间具有共同的特征,将这些共同特征提取出来,形成就是抽象类
      • 抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
      • 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作—>抽象的使用前提:继承性
    • abstract修饰方法:抽象方法
      • 抽象方法只有方法的声明,没有方法体
      • 包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法
      • 若子类重写了父类中的所有的抽象方法后,此子类方可实例化
      • 若子类没有重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
  3. 注意点:
    1. abstract不能用来修饰:属性、构造器等结构
    2. abstract不能用来修饰私有方法、静态方法、final的方法、final的类
  4. abstract的应用举例:
//抽象类的语法
权限修饰符 abstract class 类名{//抽象方法的语法public abstract void eat();
}
class A extends 抽象类名{public void eat(){//写出具体方法的实现}
}

模板方法的设计模式

  1. 解决的问题

    • 在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好。但是有某些易变的部位,这些易变的部位可以抽象出来,供不同子类实现,这就是一种模板模式。
  2. 举例
abstract class Template{//计算某代码执行所需的时间public void spendTime(){long start = System.currentTimeMillis();this.code();//不确定的部分long end = System.currentTimeMillis();System.out.println("花费的时间为:" + (end - start));}public abstract void code();
}class SubTemplate extends Template{@Overridepublic void code() {for (int i = 2;i <= 1000;i++){boolean isFlag = true;for (int j = 2;j <= Math.sqrt(i);j++){if (i % j == 0){isFlag = false;break;}}if (isFlag){System.out.println(i);}}}
}
public class A {public static void main(String[] args) {SubTemplate sub = new SubTemplate();sub.spendTime();}
}
  1. 应用场景
    模板方法设计模式是编程中经常用得到的模式。各个框架、类库中都有他的影子,比如常见的有:

    • 数据库访问封装
    • Junit单元测试
    • JavaWeb的Servlet中关于doGet/doPost方法调用
    • Hibernate中模板程序
    • Spring中JDBCTemlate、HibernateTemplate等

关键字:interface(接口)

  1. 接口使用说明:

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

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

class Computer{public void transferData(USB usb){//USB usb = new Flash();usb.start();System.out.println("执行过程");usb.stop();}
}
interface USB{void start();void stop();
}
class Flash implements USB{@Overridepublic void start() {System.out.println("U盘开始工作");}@Overridepublic void stop() {System.out.println("U盘结束工作");}
}
class Printer implements USB{@Overridepublic void start() {System.out.println("打印机开启工作");}@Overridepublic void stop() {System.out.println("打印机结束工作");}
}
  1. Java8关于接口的新规范

    1. 接口中定义的静态方法,只能通过接口来调用
    2. 通过实现类的对象,可以调用接口中的默认方法。如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法
    3. 如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,那么子类在没重写方法的情况下,默认调用父类的方法—>类优先原则
    4. 如果实现类实现多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没重写此方法的情况下,报错。—>接口冲突。这就需要我们必须在实现类中重写此方法
    5. 如何在子类(或实现类)的方法中调用父类、接口中被重写的方法
    method();//调用自己定义的重写方法
    super.method();//调用父类声明的
    接口A.super.method();//调用接口中的默认方法
    
  2. 抽象类和接口的异同?
    • 相同点:不能实例化,包含抽象方法
      1. 抽象类中的方法可以有方法体,就是能实现方法的具体功能, 但是接口中的方法不行。
      2. 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
      3. 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
      4. 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
      5. 抽象类是对一类事物的抽象,接口则是对行为的抽象。一个类继承一个抽象类代表“是不是”的关系,而一个类实现一个接口则表示“有没有”的关系。
    • 注意:
      1. JDK 1.8以后,接口里可以有静态方法和方法体了。
      2. JDK 1.8以后,接口允许包含具体实现的方法,该方法称为"默认方法",默认方法使用 default 关键字修饰。
      3. JDK 1.9以后,允许将方法定义为private,使得某些复用的代码不会把方法暴露出去。

类的结构:内部类(类的第五个成员)

  1. 定义:Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B称为外部类。
  2. 内部类的分类:成员内部类(静态、非静态) VS 局部内部类(编写在方法内、代码块内、构造器内)
  3. 成员内部类的理解:
    • 一方面,作为外部类的成员:

      • 调用外部类的结构
      • 可以被static修饰
      • 可以被4种不同的权限修饰
    • 另一方面,作为一个类:
      • 类内可以定义属性、方法、构造器等
      • 可以被final修饰,表示此类不能被继承。言外之意,不使用final,就可以被继承
      • 可以被abstract修饰
  4. 成员内部类:
    1. 如何创建成员内部类的对象?(静态的,非静态的)
    //创建静态的Dog内部类的实例(静态的成员内部类)
    Person.Dog dog = new Person.Dog();//创建非静态的Bird内部类的实例(非静态的成员内部类)
    //Person.Bird bird = new Person.Bird();//错误的
    Person p = new Person();
    Person.Bird bird = p.new Bird();
    
    1. 如何在成员内部类中调用外部类的结构?
    class Person{String name = "小";//非静态成员内部类class Bird{String name = "小鸟";public void display(String name){System.out.println(name);//方法的形参System.out.println(this.name);//内部类的属性System.out.println(Person.this.name);//外部类的属性}}
    }
    
  5. 局部内部类:
public class PartialDemo {String name = "王五";static int age = 10;public static void show() {System.out.println("掉用外部类中的show方法");}public void printf() {System.out.println("调用外部类中的打印方法");}public void demo() {String name = "张三";double height = 1.8;//编写在方法的内部的类称之为局部内部类//局部内部类不可使用权限修饰符 静态修饰符进行修饰 同局部变量相同//局部内部类与局部变量使用范围一样 在此方法内部//局部内部类可以直接访问方法中的属性 重名时使用参数传递完成访问//局部内部类 可以访问方法外部类中属性和方法  class Inner{String name = "李四";               public void showInner(String name) {show();printf();System.out.println(age);System.out.println(height); System.out.println("这是:"+PartialDemo.this.name);System.out.println("这是:"+name);System.out.println("这是:"+this.name);}}//局部内部类 创建对象 要在方法内部 局部内部类的外部声明Inner inner=new Inner();inner.showInner(name);}public static void main(String[] args) {PartialDemo partialDemo = new PartialDemo();partialDemo.demo();}
}
  1. 匿名内部类:
    匿名内部类特点:匿名内部类不能定义任何静态成员、方法和类,只能创建匿名内部类的一个实例。一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
interface A{void show();
}public class AnonymousDemo {           //编写回调方法 参数类型为接口Apublic void calllnner(A a) {a.show();}public static void main(String[] args) {AnonymousDemo anonymousDemo = new AnonymousDemo();//匿名内部类 监听事件使用较多anonymousDemo.calllnner(new A() {//接口回调//实现子类 但是没有名字 所以叫匿名内部类@Overridepublic void show() {// TODO Auto-generated method stubSystem.out.println("show");}});}
}
  1. 注意点:成员内部类和局部内部类,在编译以后,都会生成字节码文件。

    • 格式:成员内部类:外部类$内部类名.class

      • 局部内部类:外部类$数字 内部类名.class

异常处理

异常

认识异常

  • 异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。
  • 比如说,你的代码少了一个分号,那么运行出来结果是提示是错误 java.lang.Error;如果你用System.out.println(11/0),那么你是因为你用0做了除数,会抛出 java.lang.ArithmeticException 的异常。
  • 异常发生的原因有很多,通常包含以下几大类:
    • 用户输入了非法数据。
    • 要打开的文件不存在。
    • 网络通信时连接中断,或者JVM内存溢出。
  • 这些异常有的是因为用户错误引起,有的是程序错误引起的,还有其它一些是因为物理错误引起的。
  • 在程序中的意思就是
    • 异常:即指在程序执行的过程中,出现非正常情况,最终导致JVM的非正常停止。
    • 在Java等面向对象的编程语言中,异常本身是一个类,产生异常就是创建一个异常对象并抛出一个异常对象。Java虚拟机处理异常的方式就是中断处理。
    • 异常指的不是语法错误,语法错误时,编译不通过,不会产生字节码文件,根本不能运行。

异常体系结构

java.lang.Throwable|---java.lang.Error:一般不编写针对性的代码进行处理。必须修改源代码,程序才能继续执行。|---java.lang.Exception:可以进行异常的处理|---编译时异常(checked)|---IOException|---FileNotFoundException|---ClassNotFoundException|---运行时异常(unchecked,RuntimeException)|---NullPointerException(当应用程序试图在需要对象的地方使用 null 时,抛出该异常)|---ArrayIndexOutOfBoundsException(用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引)|---ClassCastException(当试图将对象强制转换为不是实例的子类时,抛出该异常)|---NumberFormatException(当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常)|---ArithmeticException(当出现异常的运算条件时,抛出此异常。例如,一个整数"除以零"时,抛出此类的一个实例)|---InputMismatchException(输入不匹配异常,即输入的值数据类型与设置的值数据类型不能匹配)

从程序执行过程,看编译时异常和运行时异常

  • 编译时异常:Java源文件执行javac.exe命令时,可能出现的异常。
  • 运行时异常:字节码文件执行java.exe命令时,出现的异常。

异常处理

Java异常处理的抓抛模型

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

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

try
{// 程序代码
}catch(ExceptionName e1)
{//Catch 块
}
//多重捕获
try{// 程序代码
}catch(异常类型1 异常的变量名1){// 程序代码
}catch(异常类型2 异常的变量名2){// 程序代码
}catch(异常类型3 异常的变量名3){// 程序代码
}
...
finally{//一定会执行的代码
}

说明:

  1. 在 try/catch 后面添加 finally 块并非强制性要求的。
  2. 使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据对象的类型,去catch中一一匹配;
  3. 一旦try中的异常对象匹配到某个catch,就进入catch中进行异常处理。处理完成后就跳出当前的 try/catch 结构(在没写finally的情况,继续执行其后的代码)。
  4. catch中的异常类型如果没子父类关系,则谁声明在上,谁声明在下无所谓;
  5. catch中的异常类型如果满足子父类关系,则要求子类一定声明在父类的上面。否则,报错。
  6. 常用的异常对象处理的方式:1.String getMessage(); 2.printStackTrace() 。
  7. 在try结构中声明的变量,再出了try结构以后,就不能再被调用。
  8. try-catch-finally结构可以嵌套。

总结:如何看待代码中的编译时异常和运行时异常?

  1. 使用 try-catch-finally 处理编程时异常,是使得程序在编译时就不再报错,但是运行时仍可能报错。相等于我们使用 try-catch-finally 将一个编译时可能出现的异常,延迟到运行时出现。
  2. 开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常编写 try-catch-finally 了。针对于编程时异常,一定要考虑异常处理。

finally关键字:

  1. finally是可选的;
  2. finally中声明的代码一定会被执行。即使catch中出现了异常,try中的return语句,catch中的return语句。
  3. 像数据库连接、输入输出流、网络编程Socket等资源,JVM是不能自动回收的,我们需要自己手动的进行资源的释放。资源的释放就需要声明在 finally中。

异常处理方式二:throws关键字

  1. “throws+异常类型” 关键字放在方法签名的尾部。
  2. 指明此方法执行时,可能会抛出的异常类型。
  3. 当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws后异常类型时,就会被抛出。异常代码后续的代码,就不再执行!

对比俩种处理方式:

  • try-catch-finally:真正的将异常给处理掉了。
  • throws 的方式只是将异常抛给了方法的调用者。并没有真正将异常处理掉。

在开发中怎么去选择这两种方式?

  1. 如果父类中被重写的方法没有throws方式处理,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中有异常,必须使用 try-catch-finally 方式处理。
  2. 执行的方法a中,先后又调用了另外的几个方法,这几个方法是递进关系执行的。我们建议这几个方法使用throws的方式进行处理。而执行的方法a可以考虑使用 try-catch-finally 方式进行处理。

补充: 方法重写的规则之一:子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型。

手动抛出异常对象

1. 使用说明:
在程序执行中,除了自动抛出异常对象的情况之外,我们还可以手动的 throw 一个异常类的对象。
2. throw/throws区别:

  • throw 表示抛出一个异常类的对象,生成异常对象的过程。声明在方法体内。
  • throws 属于异常处理的一种方式,声明在方法的声明处。

3. 举例:

public class ExceptionTest {private int id;public void regist(int id) throws Exception {if (id > 0){this.id = id;}else {//手动的抛出异常throw new Exception("你输入的数据错误!");//throw new MyException("你输入的数据错误!");}}@Overridepublic String toString() {return "ExceptionTest{" +"id=" + id +'}';}
}

自定义异常

如何自定义异常类?

  1. 继承于现有的异常结构:RuntimeException、Exception。
  2. 提供全局常量:serialVersionUID。
  3. 提供重载的构造器。
public class MyException extends Exception {static final long serialVersionUID = -7034897193245843L;public MyException() {}public MyException(String message) {super(message);}
}

Java高级编程

多线程

程序、进程、线程的理解

  1. 程序(program)

    • 概念:是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码。
  2. 进程(process)
    • 概念:程序的一次执行过程,或是正在运行的一个程序。
    • 说明:进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域。
    • 进程可以细化为多个线程。
      • 每个线程,拥有自己独立的:栈、程序计数器。
      • 多个线程,共享同一个进程中的结构:方法区、堆。
  3. 线程(thread)
    • 概念:进程进一步细化为线程,是一个程序内部的一条执行路径。
    • 说明:
      • 若一个进程同一时间并行执行多个线程,就是支持多线程的。
      • 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计算器(pc),线程切换的开销小。
      • 由于进程多个线程共享相同的内存单元/内存地址空间,它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。

并行与并发

CPU的单核与多核:

  1. 单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。比如收费站虽然有多个车道,但只有一个收银员,如果遇到某个不想交钱的,会通过挂起,等他想好再去收费。但是CPU的时间单元特别短,因此会感觉不出来。
  2. 如果是多核的CPU,才能更好的发挥多线程的效率。比如服务器:都是多核的。
  3. 一个Java应用程序java.exe,其实至少有三个线程:main()主程序,gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主程序。

并行与并发的理解:

  • 并行:多个CPU同时执行多个任务。
  • 并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。

创建多线程的两种方式

创建线程的方式一(4步):

//1.创建继承Thread的子类
class MyThread extends Thread{//2.重写Thread类的run()-->将此线程要执行的操作声明在run()中@Overridepublic void run() {for (int i = 0; i < 100; i++){if (i % 2 == 0){System.out.println(i);}}}
}
public class ThreadTest{public static void main(String[] args) {//3.创建Thread子类的对象MyThread myThread = new MyThread();//4.通过对象调用start():1.启动当前线程;2.调用当前线程的run()myThread.start();//System.out.println("aa");}
}

说明两个问题:

  • 问题一:我们启动一个线程,必须调用start(),不能调用run()的方式启动线程。
  • 问题二:如果再启动一个线程,必须重新创建一个Thread子类的对象,调用此对象的start()。

创建线程方式二:实现Runnable接口的方式(5步)

public class RunnableTest {public static void main(String[] args) {MyRunnable myRunnable = new MyRunnable();//3.创建实现了的对象Thread t = new Thread(myRunnable);//4.将实现类的对象作为参数传递到Thread类的构造器中,创建Thread类的对象t.start();//5.通过Thread类的对象调用start()}
}
class MyRunnable implements Runnable{//1.创建一个实现Runnable接口的类@Overridepublic void run() {//2.实现类去实现Runnable中的抽象方法:run()for (int i = 0; i < 100; i++){if (i % 2 != 0){System.out.println(i);}}}
}

两种方式的对比:
开发中:优先选择:实现Runnable接口的方式
原因:1.实现的方式没类的单继承性的局限性;2.实现的方式更适合来处理多个线程共享数据的情况。
联系:public class Thread implements Runnable
相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。目前两种方式,要想启动线程,都是调用的Thread中的start()。

Thread类中的常用方法

  1. start():启动当前线程;调用当前线程的run()。
  2. run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中。
  3. currentThread() :静态方法,返回执行当前代码的线程。
  4. getName():获取当前线程的名字。
  5. setName():设置当前线程的名字。
  6. yield():释放当前CPU的执行权。
  7. join():在 线程A 中调用 线程B 的join(),此时 线程A 就进入阻塞状态,直到 线程B 完全执行完以后,线程A 才结束阻塞状态。
  8. stop():已过时。当执行此方法时,强制结束当前线程。
  9. sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态。
  10. isAlive():判断当前线程是否存活。

线程的优先级:

  1. 什么是线程优先级:

    • 在操作系统中,线程可以划分优先级,线程优先级越高,获得 CPU 时间片的概率就越大,但线程优先级的高低与线程的执行顺序并没有必然联系,优先级低的线程也有可能比优先级高的线程先执行。
  2. 线程的优先级的获取与设置:
    • 在 Java 中,可以通过 setPriority(int newPriority) 方法来设置线程的优先级。
    • Java 提供了 3 个常量值可以用来定义优先级:
    public final static int MIN_PRIORITY = 1;
    public final static int NORM_PRIORITY = 5;//默认优先级
    public final static int MAX_PRIORITY = 10;
    
    • 在 Java 中,可以通过 getPriority() 方法来获取线程的优先级。
    • 在 Java 中,线程的优先级具有继承性,如果线程 A 启动了线程 B,则线程 B 的优先级与线程 A 的优先级是一样的。

线程的分类: 一种是守护线程,一种是用户线程。

Thread的生命周期

图示:

说明:

  1. 生命周期关注的两个概率:状态、相应的方法
  2. 关注:状态a -->状态b:哪些方法执行了(回调方法);某个方法主动调用:状态a -->状态b
  3. 阻塞:临时状态,不可以作为最终状态。死亡:最终状态。

线程的同步机制

  • 处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象。这时需要线程同步,线程同步是一种等待机制,多个线程需要同时访问此对象时,线程进入此对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。–》线程的安全问题

Java解决方案:同步机制

  • 同步代码块
synchronized(同步监视器){//需要被同步的代码
}
说明:  1.操作共享数据的代码,即为需要被同步的代码。-->不能包多也不能包少了。2.共享数据:多个程序共同操作的变量。3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。要求:多个线程必须要共用同一把锁。4.在实现Runnable接口创建多线程的方式中,我们可以考虑使用 this 充当同步监视器。5.在继承Thread类创建多线程的方式中,慎用 this 充当同步监视器,考虑使用当前类充当同步监视器。
  • 同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明为同步的。
public synchronized void method(int args){}
  • 同步的方式,解决了线程安全的问题。(好处)
  • 操作同步代码时,只能有一个线程参与,其他线程等待。相当于单线程过程,效率低。
  • Lock锁(JDK5.0新增)
class Win implements Runnable{private int ticket = 100;private ReentrantLock lock = new ReentrantLock();//1public void run(){while (true){try {lock.lock();//2if (ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() + "票号为:" + ticket);ticket--;} else {break;}} finally {lock.unlock();//3}}}
}
  • synchronized 与 Lock的异同?

    • 相同:二者都可以解决程序安全问题;
    • 不同:synchronized 机制在执行完相应的同步代码以后,自动的释放同步监视器;Lock需要手动的启动同步(lock(),同时结束同步也需要手动的实现 unlock() )。
  • 使用的优先顺序:Lock–》同步代码块(已经进入了方法体,分配了相应资源)–》同步方法(在方法体外)

线程安全的单例模式(懒汉式)

class B{ //懒汉式private B(){ //1.私有类的构造器}//2.声明当前类对象,没初始化//4.要求此对象也必须声明为静态的private static B instance = null;//3.声明public、static的返回当前类对象的方法public static B getInstance(){if (instance == null){synchronized (B.class){if (instance == null){instance = new B();}}}return instance;}
}

死锁问题

  1. 死锁的理解:不同线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
  2. 说明:
    1. 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
    2. 我们使用同步时,要避免出现死锁。
    3. 某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。
//造成死锁的代码synchronized (lipstick){//获得口红的锁System.out.println(this.girlName+"获得口红的锁");Thread.sleep(1000);synchronized (mirror){//一秒钟后想获得镜子System.out.println(this.girlName+"获得镜子的锁");}
}
//应该改成synchronized (lipstick){//获得口红的锁System.out.println(this.girlName+"获得口红的锁");Thread.sleep(1000);}synchronized (mirror){//一秒钟后想获得镜子System.out.println(this.girlName+"获得镜子的锁");}

线程通信

  1. 线程通信涉及到的三个方法的调用:

    • wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
    • notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
    • notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
  2. 说明:
    1. wait(),notify(),notifyAll() 三个方法必须使用在同步方法和同步代码块中。
    2. wait(),notify(),notifyAll() 三个方法的调用者必须是同步方法或同步代码块中的同步监视器;否则,会报IllegalMonitorStateException异常。
    3. wait(),notify(),notifyAll() 三个方法是定义在java.long.Object类中。
  3. 小结:
    • 释放锁的操作

      • 当前线程的同步方法、同步代码块执行结束;
      • 当前线程的同步方法、同步代码块中遇到break、return 终止了该代码块、该方法的继续执行;
      • 当前线程的同步方法、同步代码块中出现了未处理的 Error 或Exception,导致代码异常结束;
      • 当前线程的同步方法、同步代码块中执行了线程对象的 wait() 方法,当前线程暂停,并释放锁。
    • 不会释放锁的操作
      • 线程执行同步代码块或同步方法时,程序调用 Thread.sleep()、Thread.yield() 方法暂停当前线程的执行;
      • 线程执行同步代码块时,其他线程调用了该线程的 suspend() 方法将该线程挂起,该线程不会释放锁(同步监视器)。
        • 应尽量避免使用suspend() 和 resume() 来控制线程。

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

  1. 相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
  2. 不同的:
    1. 两个方法声明的位置不同:sleep() 声明在 Thread 类中,wait() 声明在 Object 类中。
    2. 调用方法不同:sleep() 可以在任何场景下调用,wait() 必须在同步方法或同步代码块中。
    3. 关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep() 不会释放锁,wait() 会释放锁。

JDK5.0新增线程创建的方式

实现 Callable 接口

class NumThread implements Callable {//1.创建一个实现Callable的实现类@Overridepublic Object call() throws Exception {//2.实现call方法,将此线程需要执行的操作声明在call中int sum = 0;for (int i = 1; i <= 100; i++){if (i % 2 == 0){System.out.println(i);sum += i;}}return sum;}
}public class ThreadCal {public static void main(String[] args) {NumThread n = new NumThread();//3.创建实现类的对象FutureTask f = new FutureTask(n);//4.将实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象new Thread(f).start();//5.将FutureTask的对象传递到Thread(f)构造器中,创建Thread对象,并调用start()
//6. 获取Callable中call方法的返回值//get() 返回值即为 FutureTask构造器 参数 实现类重写的 call() 的返回值Object sum = null;try {sum = f.get();} catch (InterruptedException e) {throw new RuntimeException(e);} catch (ExecutionException e) {throw new RuntimeException(e);}System.out.println("总和为:" + sum);}
}
  • 如何理解实现 Callable接口 的方式创建多线程比实现 Runnable接口 创建多线程方式的强大?

    • call() 可以有返回值的。
    • call() 可以抛出异常,被外面的操作捕获,获取异常信息。
    • Callable 是支持泛型的。

使用线程池

class NumberThread implements Runnable{@Overridepublic void run() {for (int i = 0; i <= 100; i++){if (i % 2 == 0){System.out.println(Thread.currentThread().getName() + ":" + i);}}}
}
class NumberThread1 implements Runnable{public void run() {for (int i = 0; i <= 100; i++){if (i % 2 == 0){System.out.println(Thread.currentThread().getName() + ":" + i);}}}
}public class ThreadNew {public static void main(String[] args) {//1.提供指定线程数量的线程池ExecutorService service = Executors.newFixedThreadPool(10);//ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;//接口没有方法,需要强转成实现类才能调用设置线程池的方法//设置线程池的属性//System.out.println(service.getClass());获取对象是那个类造的//service1.setCorePoolSize(15);//service1.setKeepAliveTime();//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象service.execute(new NumberThread());//适用于Runnableservice.execute(new NumberThread1());// service.submit(Callable callable);//适用于Callable//3.关闭线程池service.shutdown();}
}

说明:

  • 好处:

    1. 提高响应速度(减少了创建新线程的时间)。
    2. 降低资源消耗(重复利用线程池中的线程:不需要每次都创建)。
    3. 便于线程管理:
      • corePoolSize:核心池的大小
      • maximumPoolSize:最大线程池
      • keepAliveTime:线程没任务时最多保持多长时间后会终止

Java常用类

String类(java.lang.String类的使用)

概述

  1. String:字符串,使用一对 “” 引起来表示。
  2. String 声明为final的,不可被继承。
  3. String 实现了 Serializable 接口:表示字符串是支持序列化的;实现了 Comparable 接口:表示 String 可以比较大小。
  4. String 内部定义了 final char[] value 用于存储字符串数据。
  5. 通过字面量的方式(区别于 nuw 给一个字符串赋值,此时的字符串值声明在字符串常量池中。
  6. 字符串常量池中是不会存储相同内容(使用String类的equals()比较,返回true)的字符串的。

String的不可变性

  1. 说明

    1. 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
    2. 当对现有的字符串进行连续操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
    3. 当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
  2. 代码举例
    @Testpublic void test1() {String s1 = "abc";//字面量的定义方式String s2 = "abc";s1 = "hello";System.out.println(s1 == s2);//比较地址值String s3 = "abc";s3 += "def";System.out.println(s3);//abcdefSystem.out.println(s2);//abcString s4 = "abc";String s5 = s4.replace('a','m');System.out.println(s5);//mbcSystem.out.println(s4);//abc}
  1. 图示

String实例化的不同方式

  1. 方式说明

    • 方式一:通过字面量定义的方式
    • 方式二:通过new + 构造器的方式
  2. 代码举例
@Testpublic void test2(){String s1 = "abc";//字面量的定义方式String s2 = "abc";//通过 new + 构造器的方式:此时的s3和s4保存的地址值,是数据在堆空间开辟空间以后对应的地址值String s3 = new String("abc");String s4 = new String("abc");System.out.println(s1 == s2);//trueSystem.out.println(s1 == s3);//falseSystem.out.println(s1 == s4);//falseSystem.out.println(s3 == s4);//false}
  1. 面试题

    • String s3 = new String(“abc”); 方式创建对象,在内存中创建了几个对象?
    • 答:两个:一个是堆空间中nwe的结构,另一个是char[]对应的常量池中的数据:“abc”
  2. 图示

字符串拼接方式赋值的对比

  1. 说明

    1. 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
    2. 只要其中一个是变量,结果就在堆中。
    3. 如果拼接的结果调用intern()方法,返回值就在常量池中。
  2. 代码举例
 @Testpublic void teat3(){String s1 = "javaEE";String s2 = "java";String s3 = s2 + "EE";System.out.println(s1 == s3);//ffinal String s4 = "java";String s5 = s4 + "EE";System.out.println(s1 == s5);//tString s6 = "EE";String s7 = "java" + s6;String s8 = s7.intern();System.out.println(s1 == s8);//t}

常用方法

  1. int length():返回字符串的长度:return value.length
  2. char charAt(int index):返回某索引处的字符:return value[index]
  3. boolean isEmpty():判断字符串是否为空:return value.length == 0
  4. String toLowerCase():使用默认语言环境,将 String 中的所有字符转换为小写
  5. String toUpperCase():使用默认语言环境,将所有的字符转换为大写
  6. String trim():返回字符串的副本,忽略前导空白和尾巴空白
  7. boolean equals(Object obj):比较字符串的内容是否相同
  8. boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写
  9. String concat(String str):将指定字符串连接到此字符串的结尾,等价于用 “+”
  10. int compareTo(String anotherString):比较两字符串的大小
  11. String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。
  12. String substring(int beginIndex, int endIndex):返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串
  13. boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
  14. boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
  15. boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
  16. boolean contains(CharSequence s):当且仅当此字符串包含指定的char值序列时,返回true
  17. int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引(未找到返回-1)
  18. int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定索引开始(未找到返回-1)
  19. int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引(未找到返回-1)
  20. int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索(未找到返回-1)
  21. 替换:String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有oldChar得到的
  22. String replace(CharSequence target , CharSequence replacement):使用指定的字面值替换序列替换此字符串所匹配字面值目标序列的子字符串
  23. String replaceAll(String regex, String replacement):使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串
  24. String replaceFirst(String regex, String replacement):使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串
  25. 匹配:boolean matches(String regex):告知此字符串是否匹配给定的正则表达式
  26. 切片:String[] split(String regex):根据给定正则表达式的匹配拆分此字符串
  27. String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串

String与其他结构的转换

  1. String–》基本数据类型、包装类:调用包装类的静态方法:parseXxx(str)。
  2. 基本数据类型、包装类–》String:调用String重载的valueOf(xxx)。
    @Testpublic void test4(){String str = "123";int i = Integer.parseInt(str);System.out.println(i);String s = String.valueOf(i);System.out.println(s);String s1 = i + "";System.out.println(str == s);//fSystem.out.println(s1 == s);//f}
  1. 与字符数组之间的转换

    • String --》 char[]:调用String的toCharArray();
    • char[] --》String:调用String的构造器。
    @Testpublic void test5(){String s = "abc123";char[] c = s.toCharArray();for (int i = 0; i < c.length; i++){System.out.println(c[i]);}char[] arr = {'h','e','l','l','o'};String s1 = new String(arr);System.out.println(s1);}
  1. 与字节数组之间的转换

    • 编码:String -->byte[]:调用String的getBytes();
    • 解码:byte[] -->String:调用String构造器;
    • 解码时,要求解码使用的字符集必须与编码的一致,否则会出现乱码。
    @Testpublic void test6() throws UnsupportedEncodingException {String s = "abc123啊啊啊";byte[] b = s.getBytes();//使用默认字符集,进行编译System.out.println(Arrays.toString(b));byte[] gbks = s.getBytes("gbk");//使用gbk字符集进行编译System.out.println(Arrays.toString(gbks));String s1 = new String(b);//默认System.out.println(s1);String gbk = new String(gbks, "gbk");System.out.println(gbk);}
  1. 与StringBuffer、StringBuilder之间的转换

    • String --》StringBuffer、StringBuilder:调用StringBuffer、StringBuilder构造器;
    • StringBuffer、StringBuilder --》String:1.调用String构造器;2.StringBuffer、StringBuilder的toString()。

JVM中字符串常量池存放位置说明:

  • jdk 1.6:字符串常量池存储在方法区(永久区);
  • jdk 1.7:字符串常量池存储在堆空间;
  • jdk 1.8:字符串常量池存储在方法区(元空间)。

StringBuffer、StringBuilder

String、StringBuffer、StringBuilder三者的对比

  • String:不可变的字符序列;底层使用char[]存储。
  • StringBuffer:可变的字符序列;线程安全的,效率低;底层使用char[]存储。
  • StringBuilder:可变的字符序列;jdk 5.0新增的,线程不安全,效率高;底层使用char[]存储。

StringBuffer与StringBuilder的内存解析
以StringBuffer为例:

    @Testpublic void test(){String s = new String();//char[] value = new char[0];String s1 = new String("abc");//char[] value = new char[]{'a','b','c'};StringBuffer buffer = new StringBuffer();//char[] value = new char[16];底层创建了一个长度是16的数组。System.out.println(buffer.length());//0buffer.append('a');buffer.append('b');System.out.println(buffer);//abStringBuffer buffer1 = new StringBuffer("abc");//char[] value = new char["abc".length() + 16];System.out.println(buffer1.length());//3}
  • 扩容问题:如果要添加的数据底层数组放不下了,那就需要扩容底层的数组。默认情况下,扩容为原来容量的2倍 + 2,同时将原数组的元素复制到新的数组中。
  • 开发中建议大家使用:StringBuffer(int copacity) 或 StringBuilder(int copacity).

对比String、StringBuffer、StringBuilder三者的执行效率

  • 从高到低排列:StringBuilder > StringBuffer > String

StringBuffer、StringBuilder中的常用方法

  • 增:append(xxx)
  • 删:delete(int start , int end)
  • 改:setCharAt(int n , char ch) / replace(int start , int end , String str)
  • 查:charAt(int n)
  • 插:insert(int offset , xxx)
  • 长度:length();
  • 遍历:for() + charAt() / toString()

JDK 8 之前日期时间API

  1. 获取系统当前时间:System类中的currentTimeMillis()
    @Testpublic void test(){long time = System.currentTimeMillis();//返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。//称为时间戳System.out.println(time);}
  1. java.util.Date类与java.sql.Date类
    /*java.util.Date类|---java.sql.Date类1. 两个构造器的使用构造器一:Date() : 创建一个对应当前时间的Date对象构造器二:创建指定毫秒数的Date对象2. 两方法的使用toString(): 显示当前的年、月、日、时、分、秒getTime(): 获取当前Date对象对应的和毫秒数。3. java.sql.Date对应着数据库中的日期类型的变量如何实例化如何将java.util.Date对象转化为java.sql.Date对象*/@Testpublic void test1() {//构造器一:Date() : 创建一个对应当前时间的Date对象Date date = new Date();System.out.println(date.toString());//Sun Jul 24 17:35:11 CST 2022System.out.println(date.getTime());//1658655311678//构造器二:创建指定毫秒数的Date对象Date date1 = new Date(1658655311678L);//Sun Jul 24 17:35:11 CST 2022System.out.println(date1.toString());//创建java.sql.Date对象java.sql.Date date2 = new java.sql.Date(658655311678L);System.out.println(date2);//1990-11-15//如何将java.util.Date对象转化为java.sql.Date对象/* //情况一:Date date3 = new java.sql.Date(1458655311678L);java.sql.Date date4 = (java.sql.Date) date3;*///情况二:Date date3 = new Date();java.sql.Date date4 = new java.sql.Date(date3.getTime());System.out.println(date4);//2022-07-24}
  1. java.text.SimpleDateFormat类
/*SimpleDateFormat对日期Data类的格式化和解析1. 两个操作:格式化:日期---》字符串解析:格式化的逆过程,字符串---》日期2. SimpleDateFormat的实例化:new + 构造器* */@Testpublic void test2() throws ParseException {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");//格式化Date date = new Date();System.out.println(date);String format = sdf.format(date);System.out.println(format);//解析Date parse = sdf.parse(format);System.out.println(parse);}
  1. Calendar类:日历类、抽象类
 @Testpublic void test3(){//1.实例化//方式一:创建其子类(GregorianCalendar的对象//方式二:调用其静态方法getInstance()Calendar calendar = Calendar.getInstance();//System.out.println(calendar.getClass());//2.常用方法//get()int i = calendar.get(Calendar.DAY_OF_MONTH);System.out.println(i);//set()//calendar可变性calendar.set(Calendar.DAY_OF_MONTH,22);i = calendar.get(Calendar.DAY_OF_MONTH);System.out.println(i);//add()calendar.add(Calendar.DAY_OF_MONTH,-3);i = calendar.get(Calendar.DAY_OF_MONTH);System.out.println(i);//getTime():日历类---》DateDate date = calendar.getTime();System.out.println(date);//steTime():Date---》日历类Date date1 = new Date();calendar.setTime(date1);i = calendar.get(Calendar.DAY_OF_MONTH);System.out.println(i);}

JDK 8 中新日期时间API

  1. 日期时间API的迭代:

    1. jak 1.0 Data类
    2. jak 1.1 Calendar类,一定程度上替换Data类
    3. jdk 1.8 提出了新的一套API
  2. 前两代存在的问题举例:

    • 可变性:像日期和时间这样的类应该是不可变的。
    • 偏移性:Date中的年份是从1900开始的,而月份都从0开始。
    • 格式化:格式化只对Data用,Calendar则不行。
    • 此外,它们也不是线程安全的,不能处理闰秒等。
  3. Java 8中新的日期时间API涉及到的包:

  4. 本地日期、本地时间、本地日期时间的使用:LocalDate/LocalTime/LocalDateTime

    1. 说明

    2. 常用方法

  5. 时间点:Instant

    1. 说明:

      1. 时间线上的一个瞬时点。概念上讲,它只是简单的表示自1970年1月1日0时0分0秒(UTC开始的秒数)。
      2. 类似于 java.util.Date类。
    2. 常用方法:
  6. 日期时间格式化类:DateTimeFormatter

    1. 说明:

      1. 格式化或解析日期、时间
      2. 类似于 SimpleDateFormat
    2. 常用方法:
    @Test
    public void test4(){//自定义格式化,ofPattern("yyyy-MM-dd hh:mm:ss")DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");//格式化String s = formatter.format(LocalDateTime.now());System.out.println(s);//解析TemporalAccessor parse = formatter.parse(s);System.out.println(parse);
    }
    

Java比较器

  1. Java比较器的使用背景:

    • Java中的对象,正常情况下,只能进行比较:== 或 != ,不能使用 > 或 < 的。
    • 但是在开发场景中,我们需要对多个对象进行排序,言外之意,就需要比较对象的大小。
    • 使用两个接口中的任何一个:Comparable 或 Comparator 来实现。
  2. 自然排序:Comparable接口的使用
public class ComparaTest {/*** 1.像String、包装类等实现了Comparable接口,* 重写了compareTo(obj)方法,给出了比较两个对象大小的方式。* 2.像String、包装类重写compareTo()方法以后,进行了从小到大的排列。* 3.重写compareTo(obj)的规则:*      如果当前对象this大于形参对象obj,则返回正整数;*      如果当前对象this小于形参对象obj,则返回负整数;*      如果当前对象this等于形参对象obj,则返回零;* 4.对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,* 重写compareTo(obj)方法。在compareTo(obj)方法中指明如何排序。*/@Testpublic void test(){ComparableTest[] arr = new ComparableTest[4];arr[0] = new ComparableTest("CC",22);arr[1] = new ComparableTest("AA",35);arr[2] = new ComparableTest("DD",14);arr[3] = new ComparableTest("BB",22);Arrays.sort(arr);System.out.println(Arrays.toString(arr));}
}class ComparableTest implements Comparable {private String name;private double price;public ComparableTest(String name, double price) {this.name = name;this.price = price;}public ComparableTest() {}@Overridepublic String toString() {return "ComparableTest{" +"name='" + name + '\'' +", price=" + price +'}';}@Overridepublic int compareTo(Object o) {//指明比较大小的方式:按照价格从低到高排序if (o instanceof ComparableTest){ComparableTest comparableTest = (ComparableTest) o;//方式一if (this.price > comparableTest.price){return 1;}else if (this.price < comparableTest.price){return -1;}else {//return 0;//再按照产品名称 从低到高 排return this.name.compareTo(comparableTest.name);//再按照产品名称 从高到低 排//return -this.name.compareTo(comparableTest.name);}//方式二//return Double.compare(this.price , comparableTest.price);}//return 0;throw new RuntimeException("传入的数据不一致!");}
}
  1. 定制排序:Comparator 接口的使用
 @Testpublic void test1(){/*** 1.背景:当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码,* 或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,* 那么可以考虑使用 Comparator 的对象来排序。* 2.重写cpmpare(Object o1,Object o2)方法,比较o1和o2的大小:* 如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;如果返回负整数,表示o1小于o2。*/Comparator comparator = new Comparator() {@Overridepublic int compare(Object o1, Object o2) {if (o1 instanceof ComparableTest && o2 instanceof ComparableTest){ComparableTest c1 = (ComparableTest) o1;ComparableTest c2 = (ComparableTest) o2;if (c1.getName().equals(c2.getName())){return Double.compare(c1.getPrice() , c2.getPrice());}else {return c1.getName().compareTo(c2.getName());}}return 0;}};//数组排序String[] str = new String[5];Arrays.sort(str, comparator);//集合排序ArrayList<String> list = new ArrayList<String>();Collections.sort(list, comparator);}
  1. 两种排序方式对比

    • Comparable 接口的方式一定,保证Comparable接口实现类的对象在任何位置都可以比较大小。
    • Comparator 接口属于临时比较。

其他类

System类

Math类

BigInteger类、BigDecimal类

 @Testpublic void testBig(){BigInteger b1 = new BigInteger("1234564112345");BigDecimal bd = new BigDecimal("12432");BigDecimal bd1 = new BigDecimal("2");System.out.println(b1);System.out.println(bd.divide(bd1));System.out.println(bd.divide(bd1, BigDecimal.ROUND_HALF_UP));System.out.println(bd.divide(bd1, 15, BigDecimal.ROUND_HALF_UP));}

枚举类和注解

枚举类的使用

  1. 枚举类的说明:

    1. 枚举类的理解:类的对象只有有限个,确定的。我们称此类为枚举类。
    2. 当需要定义一组常量时,强烈建议使用枚举类。
    3. 如果枚举类类中只有一个对象,则可以作为单例模式的实现方式。
  2. 自定义枚举类:
class Season{//1.声明Season对象的属性为 private finalprivate final String seasonName;private final String seasonDesc;//2.私有化类的构造器,并给对象属性赋值private Season(String seasonName, String seasonDesc) {this.seasonName = seasonName;this.seasonDesc = seasonDesc;}//3.提供当前枚举类的多个对象:public static finalpublic static final Season ONE = new Season("一","一个");public static final Season TWO = new Season("二","二个");public static final Season THREE = new Season("三","三个");//4.其他诉求:获取枚举类对象的属性public String getSeasonName() {return seasonName;}public String getSeasonDesc() {return seasonDesc;}@Overridepublic String toString() {return "Season{" +"seasonName='" + seasonName + '\'' +", seasonDesc='" + seasonDesc + '\'' +'}';}
}
  1. jdk 5.0新增使用enum定义枚举类:
enum Season1{//1.提供当前枚举类的多个对象,","隔开ONE("一","一个"),TWO("二","二个"),THREE("三","三个");//2.声明Season对象的属性为 private finalprivate final String seasonName;private final String seasonDesc;//3.私有化类的构造器,并给对象属性赋值private Season1(String seasonName, String seasonDesc) {this.seasonName = seasonName;this.seasonDesc = seasonDesc;}public String getSeasonName() {return seasonName;}public String getSeasonDesc() {return seasonDesc;}
}
  1. 使用enum定义枚举类之后,枚举类常用方法:(继承于java.lang.Enum类)
        Season1 season1 = Season1.TWO;//toString()返回枚举类对象的名称System.out.println(season1.toString());System.out.println("***********");//values()返回所有的枚举类对象构成的数组Season1[] values = Season1.values();for (int i = 0; i < values.length; i++){System.out.println(values[i]);}System.out.println("***********");Thread.State[] values1 = Thread.State.values();for (int i = 0; i < values1.length; i++) {System.out.println(values1[i]);}System.out.println("***********");//valueOf(String objName):返回枚举类中对象名是objName的对象Season1 three = Season1.valueOf("THREE");System.out.println(three);}
  1. 使用enum定义枚举类之后,如何让枚举类对象分别实现接口:
interface Info{void show();
}
enum Season1 implements Info{//1.提供当前枚举类的多个对象,","隔开ONE("一","一个"){@Overridepublic void show() {System.out.println("这是一个");}},TWO("二","二个"){@Overridepublic void show() {System.out.println("这是二个");}},THREE("三","三个"){@Overridepublic void show() {System.out.println("这是三个");}};
}

注解的使用

  1. 注解的理解

    1. jak 5.0 新增的功能。
    2. 注解(Annotation)其实就是代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过使用注解(Annotation)程序员可以在不改变原逻辑的情况下,在源文件中嵌入一些补充信息。
    3. 在JavaSE中,注解(Annotation)的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android中注解(Annotation)占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等。
  2. 注解的使用示例

    1. 示例一:生成文档相关的注解。
    /*** @Author:* @Date: 2022-08-03 16:26**/
    
    1. 示例二:在编译时进行格式检查(JDK内置的三个基本注释)。

      • @Override – 只能标注方法,表示该方法覆盖父类中的方法;
      • @Deprecated – 所标注内容不再被建议使用;
      • @SuppressWarnings – 所标注内容产生的警告,编译器会对这些警告保持静默。
    2. 示例三:跟踪代码依赖性,实现替代配置文件功能。
  3. 如何定义注解:参照 @@SuppressWarnings定义

@Inherited
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotation {String[] value() default "hello";
}
/*
1.注解声明为:@interface
2.内部定义成员,通常使用value表示
3.可以指定成员的默认值,使用default定义
4.如果自定义注解没有成员,表明是一个标识作用*/

说明:

  • 如果注解有成员,在使用注解时,需要指明成员的值。
  • 自定义注解必须配上注解的信息处理流程(使用反射)才有意义。
  • 自定义注解通过都会指明两个元注解:@Retention、@Target
  1. 元注解:对现有的注解进行解释说明的注解

@Retention – 只能被用来标注“Annotation类型”,而且它被用来指定Annotation的RetentionPolicy属性

Retention英文意思有保留、保持的意思,它表示注解存在阶段是保留在源码(编译期),字节码(类加载)或者运行期(JVM中运行)。在@Retention注解中使用枚举RetentionPolicy来表示注解保留时期:@Retention(RetentionPolicy.SOURCE),注解仅存在于源码中,在class字节码文件中不包含.
@Retention(RetentionPolicy.CLASS), 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得.
@Retention(RetentionPolicy.RUNTIME), 注解会在class字节码文件中存在,在运行时可以通过反射获取到.如果我们是自定义注解,则通过前面分析,我们自定义注解如果只存着源码中或者字节码文件中就无法发挥作用,而在运行期间能获取到注解才能实现我们目的,
所以自定义注解中肯定是使用@Retention(RetentionPolicy.RUNTIME)。

@Target – 只能被用来标注“Annotation类型”,而且它被用来指定Annotation的ElementType属性

Target的英文意思是目标,这也很容易理解,使用@Target元注解表示我们的注解作用的范围就比较具体了,
可以是类,方法,方法参数变量等,同样也是通过枚举类ElementType表达作用类型:@Target(ElementType.TYPE) 作用接口、类、枚举、注解
@Target(ElementType.FIELD) 作用属性字段、枚举的常量
@Target(ElementType.METHOD) 作用方法
@Target(ElementType.PARAMETER) 作用方法参数
@Target(ElementType.CONSTRUCTOR) 作用构造函数
@Target(ElementType.LOCAL_VARIABLE)作用局部变量
@Target(ElementType.ANNOTATION_TYPE)作用于注解(@Retention注解中就使用该属性)
@Target(ElementType.PACKAGE) 作用于包
@Target(ElementType.TYPE_PARAMETER) 作用于类型泛型,即泛型方法、泛型类、泛型接口 (jdk1.8加入)
@Target(ElementType.TYPE_USE) 类型使用.可以用于标注任意类型除了 class (jdk1.8加入)

@Documented – 所标注内容可以出现在javadoc中
@Inherited – 只能被用来标注“Annotation类型”,它所标注的Annotation具有继承性
@Repeatable – 只能被用来标注“Annotation类型”,Repeatable的英文意思是可重复的。顾名思义说明被这个元注解修饰的注解可以同时作用一个对象多次,但是每次作用注解又可以代表不同的含义。

  1. 如何获取注解信息:通过反射来进行获取、调用。
    前提:要求此注解的元注解Retention中声明的生命周期状态为:RUNTIME。
  2. jak 8 中注解的新特性:可重复注解、类型注解
    1. 可重复注解:

      • 在MyAnnotation上声明@Repeatable,成员值为MyAnnotation.class。
      • MyAnnotation的Target和Retention等元注解与MyAnnotations相同
    2. 类型注解:
    • @Target(ElementType.TYPE_PARAMETER) 作用于类型泛型,即泛型方法、泛型类、泛型接口 (jdk1.8加入)
    • @Target(ElementType.TYPE_USE) 类型使用.可以用于标注任意类型除了 class (jdk1.8加入)
  3. 注解的作用
    1. 生成文档,通过代码里标识的元数据生成javadoc文档。
    2. 编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证。
    3. 编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码。
    4. 运行时动态处理,运行时通过代码里标识的元数据动态处理,例如使用反射注入实例。

Java集合

数组与集合

集合与数组存储数据的概述

  • 集合、数组都是对多个数据进行存储操作的结构,简称Java容器。
  • 说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt / .jpg / .avi / 数据库中)。

数据存储的特点

  • 初始化完成后,其长度就确定了。
  • 数组定义好后,其元素的类型也就确定。我们也就只能操作指定类型的数据。

数据存储的弊端

  • 初始化后,其长度就不可更改。
  • 数组中提供的操作非常有限,对于添加、删除、插入数据等操作非常不便,同时效率不高。
  • 获取数据中实际的元素的个数需求,数组没有线程的属性和方法可调用。
  • 数组存储数据的特点:有序、可重复。对于,无序、不可重复的需求,不能满足。

集合存储的优点
解决数组存储数据方面的弊端。

Collection接口

单列集合框架结构

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

    • List接口:存储有序的、可重复的数据。
    • Set接口:存储无序的、不可重复的数据。

Collection接口常用的方法

//添加方法:
add(Object o) //添加指定元素
addAll(Collection c) //添加指定集合
//删除方法:
remove(Object o) //删除指定元素
removeAll(Collection c) //输出两个集合的交集
retainAll(Collection c) //保留两个集合的交集
clear() //清空集合
//查询方法:
size() //集合中的有效元素个数
toArray() //将集合中的元素转换成Object类型数组
//判断方法:
isEmpty() //判断是否为空
equals(Object o) //判断是否与指定元素相同
contains(Object o) //判断是否包含指定元素
containsAll(Collection c) //判断是否包含指定集合

Collection集合与数组间的转换

public static void main(String[] args) {Collection coll = new ArrayList();coll.add(123);coll.add("AA");coll.add(new String("BB"));//集合---》数组:toArray()Object[] arr = coll.toArray();for (int i = 0; i < arr.length; i++){System.out.println(arr[i]);}System.out.println("*********");//数组---》集合:调用Arrays类的静态方法asList()List<String> list = Arrays.asList(new String[]{"AA", "BB", "CC"});System.out.println(list);//[AA, BB, CC]List<int[]> asList = Arrays.asList(new int[]{123, 456});System.out.println(asList.size());//1List<Integer> asList1 = Arrays.asList(new Integer[]{123, 456});System.out.println(asList1.size());//2}

使用Collection集合存储对象,要求对象所属的类满足
向Collection接口的实现类对象中添加数据obj时,要求obj所在类要重写equals()。

Iterator接口与foreach循环

遍历Collection的两种方式:

  1. 使用迭代器Iterator
  2. foreach循环(或增强for循环)

java.utils包下定义的迭代器接口:Iterator

  1. 说明:

    • Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素。
    • GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需要暴露该对象的内部细节。迭代器模式,就是为容器而生。
  2. 作用:遍历集合 Collection 元素。
  3. 如何获取实例:coll.iterator() 返回一个迭代器实例。
  4. 遍历的代码实现:
 @Testpublic void iteratorTest(){Collection coll = new ArrayList();coll.add(123);coll.add("AA");coll.add(new String("BB"));Iterator iterator = coll.iterator();//hasNext():判断是否还有下一个元素while (iterator.hasNext()){//next():1.指针下移 2.将下移以后集合位置上的元素返回System.out.println(iterator.next());}}
  1. remove()的使用:
@Testpublic void removeTest(){/*测试Iterator中的remove()如果还未调用next()或在上一次调用next方法之后已经调用了 remove 方法,再调用remove都会报.IllegalStateException内部定义了remove(),可以在遍历的时候,删除集合中的元素,此方法不同于集合直接调用remove()*/Collection coll = new ArrayList();coll.add(123);coll.add("AA");coll.add(new String("BB"));coll.add(233);coll.add(false);Iterator iterator = coll.iterator();while (iterator.hasNext()){//iterator.remove();Object obj = iterator.next();if ("AA".equals(obj)){iterator.remove();}}iterator = coll.iterator();//hasNext():判断是否还有下一个元素while (iterator.hasNext()){//next():1.指针下移 2.将下移以后集合位置上的元素返回System.out.println(iterator.next());}}

jdk 5.0新特性–增强for循环:(foreach循环)

 //集合遍历@Testpublic void foreachTest(){Collection coll = new ArrayList();coll.add(123);coll.add("AA");coll.add(new String("BB"));coll.add(233);coll.add(false);//for(集合元素的类型 局部变量 :集合对象)//内部仍然调用了迭代器。for (Object obj : coll) {System.out.println(obj);}}//数组遍历@Testpublic void test(){int[] arr = {1,2,3,4,5,6};
//        for (int i = 0; i < arr.length; i++){
//            arr[i] = 2;
//        }for (int i : arr){i = 2;}for (int i : arr){System.out.println(i);}}

Collection子接口:List接口

存储数据的特点

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

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

      • ArrayList:作为List接口的主要实现类:线程不安全,效率高:底层使用Object[] elementData存储。
      • LinkedList:对于频繁的插入,删除操作,使用此类效率比ArrayList高:底层使用双向链表存储。
      • Vector:作为List接口的古老实现类:线程安全的,效率低;底层使用Object[] elementData存储。

常用方法
增 :add(Object obj)
删 :remove(int index) / remove(Object obj)
改 :set(int index , Object ele)
查 :get(int index)
插 :add(int index , Object ele)
长度 :size()
遍历 :Iterator迭代器方式 / 增强for循环 / 普通的循环

源码分析

  1. ArrayList的源码分析:

    1. jdk 7下

      • ArrayList list = new ArrayList();//底层创建了长度为10的Object[]数组elementData
      • list.add(123);//elementData[0] = new Integer(123);
      • 如果添加导致底层elementData数组容量不够,则扩容。
      • 默认情况下,扩容为原来容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
      • 建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity);//直接创建需要的容量长度,避免频繁扩容
    2. jdk 8中的变化
      • ArrayList list = new ArrayList();//底层Object[] elementData初始化为{},并不创建长度为10的数组。
      • list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]中。
      • 后续的添加扩容操作与jdk 7 无异。
    3. 小结jdk 7 中的ArrayList的对象的创建类似于单例的饿汉式,而jdk 8 的创建类似单例的懒汉式,延迟了数组的创建,节省内存。
  2. LinkedList的源码分析
     LinkedList<Object> list = new LinkedList<>();//内部声明了Node类型的first和last属性,默认值为nulllist.add(123);//将123封装到Node中,创建Node对象//其中:Node定义体现了LinkedList的双向链表的说法private static class Node<E> {E item;Node<E> next;Node<E> prev;Node(Node<E> prev, E element, Node<E> next) {this.item = element;this.next = next;this.prev = prev;}
    }
    
  3. Vector的源码分析
    • jdk 7 和 jdk 8 中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。在扩容方面,默认扩容为原来的数组长度的2倍。
  4. 存储的元素的要求:在添加的对象,所在的类要重写equals()方法。

Collection子接口:Set接口

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

  • 具体的:以HashSet为例说明:

    • 无序性:不等于随机性。存储的数据在底层数组中并非照数组索引的顺序添加,而是根据数据的哈希值决定的。
    • 不可重复性:保证添加的元素照equals()判断时,不能返回true,即相同的元素只能添加一个。

元素添加过程:(以HashSet为例)

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

    • 如果此位置上没有其他元素,则元素a添加成功。
    • 如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的的哈希值:
      • 如果hash值不相同,则元素a添加成功。
      • 如果hash值相同,进而需要调用元素a所在类的equals()方法:
        • equals()返回true,元素a添加失败。
        • equals()返回false,则元素a添加成功。
  • 对于添加成功的情况2和情况3而言:元素a与已经存在指定索引位置上数据以链表的方式存储。
  • jdk 7 :元素a放到数组中,指向原来的元素。
  • jdk 8 :原来的元素在数组中,指向元素a。
  • 总结:HashSet底层:数组+链表的结构。

常用方法
Set接口中没额外定义新的方法,使用的都是Collection中声明过的方法。

常用实现类

  • Collection接口:单例集合,用来存储一个一个的对象

    • Set接口:存储无序的,不可重复的数据。

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

        • LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历,对于频繁的遍历操作,LinkedHashSet效率高于HashSet。(在添加数据的同时,每个数据还维护了两个引用,记录前一个数据和后一个数据。)
      • TreeSet:可以按照添加对象的指定属性,进行排序。

存储对象所在类的要求

  • HashSet/LinkedHashSet:

    • 向Set(主要指HashSet/LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()。
    • 重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码;
    • 重写两个方法的小技巧:对象中用作equals()方法比较的Field,都应该用来计算hashCode值。
  • TreeSet:
    1. 自然排序中,比较两个对象是否相同的标准为:compareTo()返回0,不再是equals()。
    2. 定制排序中,比较俩个对象是否相同的标准为:compare()返回0,不再是equals()。

TreeSet的使用

  1. 向TreeSet中添加的数据,要求是相同类的对象。
  2. 两种排序方式:自然排序(实现Comparable接口)和定制排序(Comparator)。
 @Testpublic void test(){//自然排序TreeSet set = new TreeSet();//        set.add(123);
//        set.add("AA");//失败:不能添加不同类的对象set.add(123);set.add(233);set.add(123);set.add(43);set.add(199);Iterator iterator = set.iterator();while (iterator.hasNext()){System.out.println(iterator.next());}}@Testpublic void test1(){//定制排序Comparator comparator = new Comparator() {@Overridepublic int compare(Object o1, Object o2) {if (o1 instanceof User && o2 instanceof User){User u1 = (User) o1;User u2 = (User) o2;return Integer.compare(u1.getAge() , u2.getAge());}else {throw new RuntimeException("数据类型不匹配");}}};TreeSet treeSet = new TreeSet(comparator);treeSet.add(new User("AA",11));treeSet.add(new User("BB",2));treeSet.add(new User("BCB",29));treeSet.add(new User("BB",24));treeSet.add(new User("AB",2));Iterator iterator = treeSet.iterator();while (iterator.hasNext()){System.out.println(iterator.next());}}

Map接口(双列集合框架:Map)

常用实现类的结构

  • Map:双列数据,存储key-value对的数据(y = f(x))

    • HashMap:作为Map的主要实现类:线程不安全,效率高;能存储null的key和value。

      • LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。原有:在原有的HashMap底层数据结构基础上,添加了一对指针,指向前一个和后一个的元素。(对于频繁的遍历操作,此类执行效率高于HashMap。
    • TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序。
    • Hashtable:作为古老的实现类:线程安全的,效率低;不能存储null的key和value。
      • Properties:常用来处理配置文件。key和value都是String类型。
  • HashMap的底层:
    • 数组+链表 (jdk 7及之前)
    • 数组+链表+红黑树(jdk 8)

存储结构的理解

  • Map中的key:无序的、不可重复的,使用Set存储所有的key。(以Hash Map为例:key所在的类要重写equals()和hashCode())
  • Map中的value:无序的、可重复的,使用Collection存储所有的value。(value所在的类要重写equals())
  • 一个键值对:key-value构成了一个Entry对象。
  • Map中的entry:无序的、不可重复的,使用Set存储所有的entry。

常用方法
添加:put(Object key , Object value)
删除:remove(Object key)
修改:put(Object kry , Object value)
查询:get(Object key)
长度:size()
遍历:keySet()/values()/entrySet()

内存结构说明

  1. HashMap在jdk 7 中实现原理:

    • HashMap map = new HashMap():在实例化以后,底层创建了长度为16的一维数组Entry[] table。
    • map.put(key1,value1):首先,调用key1所在的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。
      • 如果此位置上的数据为空,key1-value1添加成功。
      • 如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:
        • 如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。
        • 如果key1的哈希值和已经存在的某个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:
          • 如果equals()返回false:此时key1-value1添加成功。
          • 如果equals()返回true:使用value1替换value2 。
    • 补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
    • 在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置为非空)时,默认扩容原来的容量2倍,并将原有的数据复制过来。
  2. HashMap在jdk 8 中相较于jdk 7 在底层实现方面的不同:
    1. new HashMap():底层没创建一个长度为16的数组。
    2. jdk 8 底层的数组是:Node[],而非Entry[] 。
    3. 首次调用put()方法时,底层创建长度为16的数组。
    4. jdk 7 底层结构:数组+链表。jdk 8 中底层结构:数组+链表+红黑树。
      1. 当数组的某个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时该索引位置上的所有数据改为使用红黑树存储。
      2. 形成链表时,遵循七上八下(即jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
  3. HashMap底层典型属性的说明:
    • DEFAULT_INITIAL_CAPACITY:HashMap的默认容量为16 。
    • DEFAULT_LOAD_FACTOR = 0.75f:HashMap的默认加载因子0.75 。
    • threshold:扩容的临界值 = 容量 * 填充因子:16 * 0.75 = 12 。
    • TREEIFY_THRESHOLD = 8:Bucket中链表长度大于该默认值,转化为红黑树。
    • MIN_TREEIFY_CAPACITY = 64:Node被树化时最小的hash表容量。
  4. LinkedHashMap的底层实现原理(了解)
    • LinkedHashMap底层使用的结构与HashMap相同,因为LinkedHashMap继承与HashMap,区别就在于:LinkedHashMap内部提供了Entry,替换HashMap中的Node 。
    //HashMap中的内部类:
    static class Node<K,V> implements Map.Entry<K,V> {final int hash;final K key;V value;Node<K,V> next;Node(int hash, K key, V value, Node<K,V> next) {this.hash = hash;this.key = key;this.value = value;this.next = next;}
    //LinkedHashMap中的内部类:
    static class Entry<K,V> extends HashMap.Node<K,V> {Entry<K,V> before, after;Entry(int hash, K key, V value, Node<K,V> next) {super(hash, key, value, next);}
    }
    

TreeMap的使用

  • 向TreeMap中添加key-value,要求key必须是由同一个类创建的对象;
  • 因为要按照key进行排序:自然排序、定制排序。

使用Properties读取配置文件

@Testpublic void test2() {FileInputStream fileIn = null;try {Properties pros = new Properties();fileIn = new FileInputStream("jdbc.properties");pros.load(fileIn);String name = pros.getProperty("name");String password = pros.getProperty("password");System.out.println("name = " +name+ "; password = "+password);} catch (IOException e) {throw new RuntimeException(e);} finally {if (fileIn != null) {try {fileIn.close();} catch (IOException e) {throw new RuntimeException(e);}}}}

Collections工具类的使用

Collections工具类的作用:操作Collection和Map的工具类。

常用方法


说明:ArrayList和HashMap都是线程不安全的,如果程序要求线程安全,我们可以将ArrayList、HashMap转换为线程安全的。(使用synchronizedList(List list)和synchronizedMap(Map map)

数据结构简述

泛型

泛型的理解

1. 泛型的概念

  • 所谓的泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如:继承或实现这个接口,用这个类型声明变量、创建对象时确定(即传入实际的类型参数,也称为类型实参))。

2. 泛型的引入背景

  • 集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存储的是什么类型的对象,所以在JDK 5 之前只能把元素类型设计为Object,JDK 5 之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数就叫做泛型。Collection\List,这个就是类型参数,即泛型。

泛型在集合中的使用

1.在集合中使用泛型之前的例子

@Testpublic void test() {ArrayList list = new ArrayList();list.add(78);list.add(45);list.add(66);//问题一:类型不安全//list.add("TT");for (Object score:list) {//问题二:强转时,可能出现ClassCastExceptionint stuScore = (Integer) score;System.out.println(stuScore);}}

图示:

2. 在集合中使用泛型例子

    @Testpublic void test2() {ArrayList<Integer> list = new ArrayList<>();list.add(33);list.add(55);//添加泛型在编译时,就会进行类型检测,保证数据安全//list.add("AA");//方式一
//        for (Integer score : list) {
//            //避免了强转操作
//            int stuScore = score;
//            System.out.println(stuScore);
//        }//方式二Iterator<Integer> iterator = list.iterator();while (iterator.hasNext()){Integer next = iterator.next();System.out.println(next);}}@Testpublic void test1() {//集合使用泛型的实例//jdk7新特性:类型推断Map<String,Integer> map = new HashMap<String,Integer>();Map<String,Integer> map = new HashMap<>();map.put("Tom",87);map.put("Jerry",55);//map.put(123,"ABC");//泛型的嵌套Set<Map.Entry<String, Integer>> set = map.entrySet();Iterator<Map.Entry<String, Integer>> iterator = set.iterator();while (iterator.hasNext()){Map.Entry<String, Integer> next = iterator.next();String key = next.getKey();Integer value = next.getValue();System.out.println(key + "---" + value);}}

图示:

3. 集合中使用泛型总结

  1. 集合接口或集合类在jdk 5 时都修改为带泛型的结构。
  2. 在实例化集合类时,可以指明具体的泛型类型。
  3. 指明泛型类型后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。比如:add(E e)—>实例化以后:add(Integer e)。
  4. 注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型时用包装类替换。
  5. 如果实例化时,没指定泛型的类型,默认类型为java.lang.Object类型。

自定义泛型类、泛型接口、泛型方法

1. 泛型类

public class Order<T> {//定义多个泛型可以“,”隔开String orderName;int orderId;//类的内部结构就可以使用类的泛型T orderT;public Order() {//编译不通过
//        T[] arr = new T[10];T[] arr = (T[]) new Object[10];}public Order(String orderName, int orderId, T orderT) {this.orderName = orderName;this.orderId = orderId;this.orderT = orderT;}//如下的方法不是泛型方法public T getOrderT() {return orderT;}public void setOrderT(T orderT) {this.orderT = orderT;}
//    public static void show()(T orderT){
//       静态方法中不能使用类的泛型
//    }public void show(){//编译不通过
//        try {
//
//        }catch (T t){
//
//        }}
}
class Order1 extends Order<Integer>{//Order1不是泛型类
}
class Order2<T> extends Order<T>{//Order2是泛型类
}
  • 注意:泛型类的引用:泛型类型必须是引用类型(非基本数据类型)

2. 泛型方法

//public <泛型类型> 返回类型 方法名(泛型类型 变量名) {    }
public static <E> List<E> copy(E[] arr){ArrayList<E> list = new ArrayList<>();for (E e : arr){list.add(e);}return list;}
@Testpublic void test(){Integer[] arr = {1, 2, 3, 4};//泛型方法调用时指明泛型参数类型List<Integer> list = Order.copy(arr);System.out.println(list);
//        Order<String> order = new Order<>();
//        Integer[] arr = {1, 2, 3, 4};
//        List<Integer> list = order.copy(arr);
//        System.out.println(list);}
  • 泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没任何关系。(泛型方法所属的类是不是泛型类都没关系)
  • 泛型方法,可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定的。

3. 泛型接口

public interface 接口名<泛型类型> {    }/** * 泛型接口的定义格式:修饰符  interface 接口名<数据类型> {} */
public interface Inter<T> {    public abstract void show(T t);
} /** * 子类是泛型类 */
public class InterImpl<E> implements Inter<E> {    @Override    public void show(E t) {System.out.println(t);    }
}
Inter<String> inter = new InterImpl<String>() ;
inter.show("hello") ;

泛型在继承上的体现

类A是类B的父类,G<A>和G<B>二者不具备子父类关系,二者是并列关系。
@Testpublic void test1(){Object obj = null;String str = null;obj = str;List<Object> list1 = null;List<String> list2 = null;//编译错误//list1 = list2;}
类A是类B的父类(或接口),A<G>是B<G>父类。
@Testpublic void test2(){List<String> list1 = null;ArrayList<String> list2 = null;list1 = list2;}

通配符

<?>

类A是类B的父类,G<A>和G<B>二者不具备子父类关系,二者的共同父类是G<<?>。
@Testpublic void test3(){List<Object> list1 = null;List<String> list2 = null;List<?> list = null;list = list1;list = list2;List<String> list3 = new ArrayList<>();list3.add("AA");list3.add("BB");list = list3;//错误:添加(写入):对于List<?>就不能向其内部添加除了null之外的数据。
//        list.add(123);
//        list.add("AA");
//        list.add('?');list.add(null);//获取(读取):允许读取数据,读取的数据类型为ObjectObject o = list.get(0);System.out.println(o);}

<? extends T> 上界通配符
(无穷小,T】,只允许泛型为T及T子类的引用调用。
<? extends Comparable>
只允许泛型为实例Comparable接口的实现类的引用调用。

<? super T> 下界通配符
【T,无穷大),只允许泛型为T及T父类的引用调用。

IO流

File类的使用

1. File类的理解

  1. File类的一个对象,代表一个文件或一个文件目录(文件夹)。
  2. File类声明在java.io包下。
  3. File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,并未涉及到文件的读取与写入内容的操作。如需要读取与写入文件内容,则要用到IO流来完成。
  4. File类的对象常常作为参数传递到流的构造器中,指明读取与写入的终端。

2. File类的实例化

  1. 常用构造器:

    • File(String filePath)
    • File(String parentPath,String childPath)
    • File(File parentPath,String childPath)
  2. 路径的分类:
    • 相对路径:相较于某个路径下,指明的路径。
    • 绝对路径:包含盘符在内的文件或文件目录的路径。
    @Test
    public void test4(){File file = new File("he.txt");//相对路径File file1 = new File("F:\\IdeaProjects\\JavaNote\\day01\\he.txt");//绝对路径
    }
    
    • 说明:

      • 在IDEA中:如果大家开发使用JUnit中的单元测试方法测试,相对路径为当前Module下。如果使用的是main()方法测试,相对路径即为当前的Project下。
      • 在Eclipse中:不管使用的是JUnit中的单元测试还是main()方法测试,相对路径都是当前的Project下。
  3. 路径分割符:
    • Windows和DOS系统默认使用“\”表示;UNIX和URL使用“/”表示。
    • 为了解决Java跨平台使用,要慎用分隔符,所以File类提供了一个常量:separator:根据操作系统,动态的提供分隔符。
    File file1 = new File("F:\\IdeaProjects\\JavaNote\\day01\\he.txt");
    File file2 = new File("F:" + File.separator + "IdeaProjects" + File.separator + "he.txt");
    File file3 = new File("F:/IdeaProjects/IdeaProjects");
    

3. File类的常用方法


IO流概述

1. 流的分类

  1. 操作数据单位:字节流、字符流。
  2. 数据的流向:输入流、输出流。
  3. 流的角色:节点流、处理流。
    图示:

2. 流的体系结构

说明: 蓝框为重点关注流,重点关注4大抽象基类。

3. 重点几个流结构的说明

抽象基类 节点流(或文件流) 缓冲流(处理流的一种)
InputStream FileInputStream (read(byte[] buffer)) BufferedInputStream (read(byte[] buffer))
OutputStream FileOutputStream (write(byte[] buffer,0,len)) BufferedOutputStream (write(byte[] buffer,0,len) / flush())
Reader FileReader (read(char[] cbuf)) BufferedReader (read(char[] cbuf) / readLine())
Writer FileWriter (write(char[] cbuf,0,len)) BufferedWriter (write(char[] cbuf,0,len) / flush())

4. 输入、输出标准化过程

  1. 输入过程:

    1. 创建File类的对象,指明读取的数据来源(要求文件一定要存在)。
    2. 创建相应的输入流,将File类的对象作为参数,传入流的构造器中。
    3. 具体的读取过程:创建相应的byte[] 或 char[]。
    4. 关闭流的资源:调用close()方法。
    5. 说明:程序中出现的异常要使用try-catch-finally处理。
  2. 输出过程:
    1. 创建File类的对象,指明写出的数据位置(不要求文件一定要存在)。
    2. 创建相应的输出流,将File类的对象作为参数,传入流的构造器中。
    3. 具体的写出过程:write(char[]/byte[] cbuf,0,len)。
    4. 关闭流的资源:调用close()方法。
    5. 说明:程序中出现的异常要使用try-catch-finally处理。

节点流(或文件类)

1. FileReader/FileWriter的使用:

  1. FileReader的使用

    • 说明点:

      • read()的理解:返回读入的一个字符。如果达到文件末尾,返回-1 。
      • 异常的处理:为了保证流资源一定可以执行关闭操作。需要使用try-catch-finally处理。
      • 读入的文件一定要存在,否则就会报FileNotFoundException。
    @Test
    public void test(){//读取数据//1.2.FileReader fr = null;try {fr = new FileReader(new File("he.txt"));//3.char[] chars = new char[5];int len;while ((len = fr.read(chars)) != -1){//方式一
    //                for (int i = 0; i < len; i++) {
    //                    System.out.print(chars[i]);
    //                }//方式二:错误的:
    //                String s = new String(chars);
    //                System.out.print(s);//正确的写法String s = new String(chars, 0, len);System.out.print(s);}} catch (IOException e) {throw new RuntimeException(e);} finally {if (fr != null){try {//4.fr.close();} catch (IOException e) {throw new RuntimeException(e);}}}
    }
    
  2. FileWriter的使用

    • 说明:从内存中写出数据到硬盘的文件里。

      1. 输出操作,对应的File可以不存在的。并不会报异常
      2. File对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件。
      3. File对应的硬盘中的文件如果存在:
        • 如果流使用的构造器是:FileWriter(file,false) / FileWriter(file):对原文件的覆盖;
        • 如果流使用的构造器是:FileWriter(file,true):不会对原文件覆盖,而是在原文件基础上追加内容。
    @Test
    public void test1(){//写入数据//1.2.FileWriter fw = null;try {fw = new FileWriter(new File("hello.txt"),true);//3.fw.write("I hava a dream!\n");fw.write("12345");} catch (IOException e) {throw new RuntimeException(e);} finally {if (fw != null){//4try {fw.close();} catch (IOException e) {throw new RuntimeException(e);}}}}
    
  3. 文本文件的复制(注意:不能用字符流处理图片等字节数据)

@Testpublic void test2(){FileReader fr = null;FileWriter fw = null;try {fr = new FileReader(new File("hello.txt"));fw = new FileWriter(new File("hello2.txt"));char[] chars = new char[5];int len;//记录每次读入到chars数组中的字符个数while ((len = fr.read(chars)) != -1){fw.write(chars,0,len);//每次写成len个字符}} catch (IOException e) {throw new RuntimeException(e);} finally {//方式一
//            try {
//                if (fw != null)
//                  fw.close();
//            } catch (IOException e) {
//                throw new RuntimeException(e);
//            } finally {
//                try {
//                    if (fr != null)
//                      fr.close();
//                } catch (IOException e) {
//                    throw new RuntimeException(e);
//                }
//            }//方式二try {if (fw != null)fw.close();} catch (IOException e) {throw new RuntimeException(e);}try {if (fr != null)fr.close();} catch (IOException e) {throw new RuntimeException(e);}}}

2. FileInputStream / FileOutputStream的使用:

  1. 对于文本文件(.txt / .java / .c / .cpp),使用字符流处理。
  2. 对于非文本文件(.jpg / .mp3 / .mp4 / .avi / .doc / .ppt /…),使用字节流处理。(使用字节流处理文本文件,可能会出现乱码)
  3. 实现对图片的复制
@Testpublic void test(){FileInputStream fi = null;FileOutputStream fo = null;try {fi = new FileInputStream(new File("01.jpg"));fo = new FileOutputStream(new File("02.jpg"));byte[] buffer = new byte[5];int len;while ((len = fi.read(buffer)) != -1){fo.write(buffer,0,len);}} catch (IOException e) {throw new RuntimeException(e);} finally {try {if (fo != null)fo.close();} catch (IOException e) {throw new RuntimeException(e);}try {if (fi != null)fi.close();} catch (IOException e) {throw new RuntimeException(e);}}}

缓冲流的使用

1. 缓冲流涉及到的类

  • BufferedInputStream
  • BufferedOutputStream
  • BufferedReader
  • BufferedWriter

2. 作用

  • 作用:提供流的读速、写入的速度。
  • 提高读写速度的原因:内部提供了一个缓冲区。默认情况下是8kb。
private static int DEFAULT_BUFFER_SIZE = 8192;

3. 典型代码

  1. 使用BufferedInputStream 和 BufferedOutputStream 实现处理非文本文件的复制。
private void copyFileTime(String srcPath,String destPath){BufferedInputStream bi = null;BufferedOutputStream bo = null;try {FileInputStream fi = new FileInputStream(new File(srcPath));FileOutputStream fo = new FileOutputStream(new File(destPath));bi = new BufferedInputStream(fi);bo = new BufferedOutputStream(fo);byte[] b = new byte[1024];int len;while ((len = bi.read(b)) != -1){bo.write(b,0,len);}} catch (IOException e) {throw new RuntimeException(e);} finally {//关闭资源 要求:先关闭外层的流,再关内层的流if (bo != null){try {bo.close();} catch (IOException e) {throw new RuntimeException(e);}}if (bi != null){try {bi.close();} catch (IOException e) {throw new RuntimeException(e);}}}//说明:关闭外层的流的同时,内层流也会自动关闭。所以内层流的关闭我们可以省略
//        fo.close();
//        fi.close();}
  1. 使用BufferedReader 和 BufferedWriter实现处理文本文件的复制。
@Testpublic void test(){BufferedReader br = null;BufferedWriter bw = null;try {FileReader fr = new FileReader(new File("hello.txt"));FileWriter fw = new FileWriter(new File("heh.txt"));br = new BufferedReader(fr);bw = new BufferedWriter(fw);//方式一:使用char[]数组读写操作
//            char[] c = new char[5];
//            int len;
//            while ((len = br.read(c)) != -1){
//                bw.write(c,0,len);
//            }//方式二:使用StringString data;while ((data = br.readLine()) != null){//方法一:
//                bw.write(data + "\n");//data中不包含换行操作//方法二bw.write(data);//data中不包含换行操作bw.newLine();//提供换行操作}} catch (IOException e) {throw new RuntimeException(e);} finally {if (bw != null){try {bw.close();} catch (IOException e) {throw new RuntimeException(e);}}if (br != null){try {br.close();} catch (IOException e) {throw new RuntimeException(e);}}}}

转换流的使用

1. 转换流涉及到的类:属于字符流

  • InputStreamReader:将一个字节的输入流转换为字符的输入流。

    • 解码:字节、字节数组 --> 字符数组、字符串
  • OutputStreamWriter:将一个字符的输出流转换为字节的输出流。
    • 编码:字符数组、字符串 --> 字节、字节数组
  • 说明:编码决定了解码的方式。

2. 作用: 提供字节流与字符流之间的转换。
3. 图示:

4. 典型实现:

@Testpublic void test(){InputStreamReader isr = null;try {FileInputStream fis = new FileInputStream("hello.txt");
//        InputStreamReader isr = new InputStreamReader(fis);//使用系统默认的字符集//指明字符集,具体使用那个字符集,取决于文件保存时使用的字符集isr = new InputStreamReader(fis,"UTF-8");char[] c = new char[1024];int len;while ((len = isr.read(c)) != -1){String s = new String(c, 0, len);System.out.println(s);}} catch (IOException e) {throw new RuntimeException(e);} finally {if (isr != null){try {isr.close();} catch (IOException e) {throw new RuntimeException(e);}}}}
//综合使用
@Testpublic void test1(){InputStreamReader isr = null;OutputStreamWriter osw = null;try {FileInputStream fis = new FileInputStream(new File("hello.txt"));FileOutputStream fos = new FileOutputStream(new File("hello_gbk.txt"));isr = new InputStreamReader(fis,"UTF-8");osw = new OutputStreamWriter(fos,"GBK");char[] c = new char[1024];int len;while ((len = isr.read(c)) != -1){osw.write(c,0,len);}} catch (IOException e) {throw new RuntimeException(e);} finally {if (osw != null){try {osw.close();} catch (IOException e) {throw new RuntimeException(e);}}if (isr != null){try {isr.close();} catch (IOException e) {throw new RuntimeException(e);}}}}

5. 说明: 文件编码的方式,决定了解析时使用的字符集。

其他流的使用

  1. 标准的输入输出流:

    • System.in:标准的输入流,默认从键盘输入。
    • System.out:标准的输出流,默认从控制台输出。
    • 修改默认的输入和输出的行为:System类的setIn(InputStream is) / setOut(PrintStream ps)方式重新指定输入和输出的流。
    //实现从键盘输入,转换成大写进行输出,继续执行到输入e或exit结束。
    //方式一:Scanner实现,调用next()返回一个字符
    //方式二:使用System.in实现,System.in --> 转换流 ---> BufferedReader的readLine()。
    public static void main(String[] args) {BufferedReader br = null;try {InputStreamReader isr = new InputStreamReader(System.in);br = new BufferedReader(isr);while (true){System.out.println("请输入:");String s = br.readLine();if ("e".equalsIgnoreCase(s) || "exit".equalsIgnoreCase(s)){System.out.println("输入结束");break;}String s1 = s.toUpperCase();System.out.println(s1);}} catch (IOException e) {throw new RuntimeException(e);} finally {if (br != null){try {br.close();} catch (IOException e) {throw new RuntimeException(e);}}}
    }
    
  2. 打印流:

    • PrintStream 和 PrinWriter.
    • 实现将基本数据类型的数据格式转化为字符串输出。
    • 提供了一系列重载的print()和println()方法,用于多种数据类型的输出。
    • System.out返回的是PrintStream的实例。
  3. 数据流:

    • DataInputStream 和 DataOutputStream 。
    • 作用:用于读取或写出基本数据类型的变量或字符串。
    //将内存中的字符串、基本数据类型的变量写出到文件中
    @Test
    public void test3(){DataOutputStream dos = null;try {dos = new DataOutputStream(new FileOutputStream("he1.txt"));dos.writeUTF("你好");dos.flush();dos.writeInt(1234);dos.flush();dos.writeBoolean(true);dos.flush();} catch (IOException e) {throw new RuntimeException(e);} finally {if (dos != null){try {dos.close();} catch (IOException e) {throw new RuntimeException(e);}}}
    }
    //将文件中存储的基本数据类型变量和字符串读取到内存中,保存在变量中。
    //读取不同类型的数据的顺序与当初写入文件时的顺序一致。
    @Test
    public void test4(){DataInputStream dis = null;try {dis = new DataInputStream(new FileInputStream("he1.txt"));String s = dis.readUTF();int i = dis.readInt();boolean b = dis.readBoolean();System.out.println(s);System.out.println(i);System.out.println(b);} catch (IOException e) {throw new RuntimeException(e);} finally {if (dis != null){try {dis.close();} catch (IOException e) {throw new RuntimeException(e);}}}
    }
    

对象流的使用

1. 对象流: ObjectInputStream 和 ObjectOutputStream 。
2. 作用:

  • ObjectInputStream:存储中的文件、通过网络接收过来 --> 内存中的对象。(反序列化过程)
  • ObjecrtOutputStream:内存中的对象 --> 存储中的文件、通过网络传输出去。(序列化过程)

3. 对象的序列化机制: 对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。反序列化:当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。

4. 序列化过程及代码实现

@Testpublic void test(){ObjectOutputStream oos = null;try {oos = new ObjectOutputStream(new FileOutputStream("Object.dat"));oos.writeObject(new String("程序运行了"));oos.flush();} catch (IOException e) {throw new RuntimeException(e);} finally {if (oos != null){try {oos.close();} catch (IOException e) {throw new RuntimeException(e);}}}}

5. 反序列化过程及代码实现

@Testpublic void test1(){ObjectInputStream ois = null;try {ois = new ObjectInputStream(new FileInputStream("Object.dat"));Object o = ois.readObject();String s = (String) o;System.out.println(s);} catch (IOException e) {throw new RuntimeException(e);} catch (ClassNotFoundException e) {throw new RuntimeException(e);} finally {if (ois != null){try {ois.close();} catch (IOException e) {throw new RuntimeException(e);}}}}

6. 实现序列化的对象所属的类需要满足

  1. 需要实现接口:Serializable 。
  2. 当前类提供一个全局常量:serialVersionUID 。
  3. 除了当前Person类需要实现Serializable接口外,还必须保证其内部所有属性也必须是可序列化的。(默认情况下,基本数据类型可序列化)
  4. ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量。

RandomAccessFile的使用

  1. 随机存取文件流:RandomAccessFile
  2. 说明:
    1. RandomAccessFile直接继承于java.lang.Object类,实现了DataInput和DataOutput接口。
    2. RandomAccessFile既可以作为一个输入流,又可以作为一个输出流。
    3. 如果RandomAccessFile作为输出流时,写出到的文件如果不存在,则在执行过程中自动创建。如果写出到的文件存在,则会对原文件内容进行覆盖。(默认情况下:从头覆盖)
    4. 可以通过相关的操作,实现RandomAccessFile“插入”数据的效果。seek(int pos)
  3. 典型代码
//文件的复制
@Testpublic void test(){RandomAccessFile r = null;RandomAccessFile rw = null;try {r = new RandomAccessFile(new File("01.jpg"),"r");rw = new RandomAccessFile(new File("03.jpg"), "rw");byte[] b = new byte[1024];int len;while ((len = r.read(b)) != -1){rw.write(b,0,len);}} catch (IOException e) {throw new RuntimeException(e);} finally {if (rw != null){try {rw.close();} catch (IOException e) {throw new RuntimeException(e);}}if (r != null){try {r.close();} catch (IOException e) {throw new RuntimeException(e);}}}}//实现数据插入的效果
@Testpublic void test1(){RandomAccessFile rw = null;try {rw = new RandomAccessFile("he.txt", "rw");rw.seek(2);//将指针角标调到2的位置//保存后面的数据StringBuilder sb = new StringBuilder((int) new File("he,txt").length());byte[] b = new byte[1024];int len;while ((len = rw.read(b)) != -1){sb.append(new String(b,0,len));}rw.seek(2);//调回指针rw.write("qwe".getBytes());//插入的数据//将保存的数据写入文件rw.write(sb.toString().getBytes());} catch (IOException e) {throw new RuntimeException(e);} finally {if (rw != null){try {rw.close();} catch (IOException e) {throw new RuntimeException(e);}}}}

Path、Paths、Files的使用

1. NIO的使用说明:

  • 随着 JDK 7 的发布,Java对NIO进行了极大的扩展,增强了对文件处理和文件系统特性的支持,以至于我们称它们为NIO.2。
  • 因为NIO提供的一些功能,NIO已经成为文件处理中越来越重要的部分。

2. Path的使用 (jdk 7 提供)

  1. Path的说明: Path替换原有的File类

  2. 如何实例化:

  3. 常用方法:

3. Files工具类 (jdk 7 提供)

网络编程

InetAddress类的使用

1. 实现网络通信需要解决的两个问题

  1. 如何准确地定位网络上一台或多台主机:定位主机上的特定的应用
  2. 找到主机后如何可靠高效地进行数据传输

2. 网络通信的两个要素

  1. 对应问题一:IP和端口号。
  2. 对应问题二:提供网络通信协议:TCP/IP参考模型(应用层、传输层、网络层、物理+数据链路层)。

3. 通信要素一:IP和端口号

  1. IP的理解

    1. IP:唯一的标识 Internet 上的计算机(通信实体)
    2. 在Java中使用InetAddress类代表IP。
    3. IP分类:IPv4 和 IPv6;万维网 和 局域网。
    4. 域名:比如:www.baidu.com 、www.jd.com 。
      • 域名解析:域名容易记忆,当在连接网络时输入一个主机的域名后,域名服务器(DNS)负责将域名转化成IP地址,这样才能和主机建立连接。
    5. 本地回路地址:127.0.0.1 对应着:localhost 。
  2. InetAddress类:此类的一个对象就代表着一个具体的IP地址。
    1. 实例化: getByName(String host) / getLocalHost()
    2. 常用方法: getHostName() / getHostAddress()
  3. 端口号:正在计算机上运行的进程
    • 要求:不同的进程不同的端口号。
    • 范围:被规定为一个16位的整数0~65535
    • 端口号与IP地址的组合得出一个网络套接字:Socket。

4. 通信要素二:网络通信协议

  1. 模型

  2. TCP和UDP的区别

    • TCP协议

      • 使用TCP协议前,须先建立TCP连接,形成传输数据通道。
      • 传输前,采用“三次握手”方式,点对点通信,是可靠的
      • TCP协议进行通信的两个应用进程:客户端、服务端。
      • 在连接中可进行大数据量的传输
      • 传输完毕,需释放已建立的连接,效率低
    • UDP协议
      • 将数据、源、目的封装成数据包,不需要建立连接
      • 每个数据包的大小限制在64k内
      • 发送不管对方是否准备好,接收方收到也不确认,所以是不可靠的
      • 可以广播发送。
      • 发送数据结束时无需释放资源,开销小,速度快
  3. TCP三次握手和四次挥手

TCP网络编程

public class TCPTest {//实现客服端发送信息到服务端,服务端将数据显示在控制台@Testpublic void client() {//客户端Socket s = null;OutputStream os = null;try {//创建Socket对象指明需要连接的服务端IP和端口号InetAddress inet = InetAddress.getByName("127.0.0.1");//指明服务端的IPs = new Socket(inet,8899);//服务端的端口号//输出操作os = s.getOutputStream();os.write("我是客户端".getBytes());} catch (IOException e) {throw new RuntimeException(e);} finally {if (os != null){//关闭流资源try {os.close();} catch (IOException e) {throw new RuntimeException(e);}}if (s != null){try {s.close();} catch (IOException e) {throw new RuntimeException(e);}}}}@Testpublic void server() {//服务端ServerSocket ss = null;Socket as = null;InputStream is = null;ByteArrayOutputStream baos = null;try {ss = new ServerSocket(8899);//创建服务端的ServerSocket对象,指明自己的端口号as = ss.accept();//调用accept()方法,表示接受来自客户端的Socketis = as.getInputStream();//获取输入流//读取输入流的数据baos = new ByteArrayOutputStream();byte[] b = new byte[5];int len;while ((len = is.read(b)) != -1){baos.write(b,0,len);}System.out.println(baos.toString());System.out.println("接收来自:" + as.getInetAddress().getHostAddress() + "的信息");} catch (IOException e) {throw new RuntimeException(e);} finally {//关闭资源if (baos != null){try {baos.close();} catch (IOException e) {throw new RuntimeException(e);}}if (is != null){try {is.close();} catch (IOException e) {throw new RuntimeException(e);}}if (as != null){try {as.close();} catch (IOException e) {throw new RuntimeException(e);}}if (ss != null){try {ss.close();} catch (IOException e) {throw new RuntimeException(e);}}}}
}
//客户端发送文件给服务端,服务端保存到本地
public class TCPTest1 {@Testpublic void client() {Socket socket = null;OutputStream os = null;FileInputStream fis = null;try {socket = new Socket(InetAddress.getByName("127.0.0.1"), 9090);os = socket.getOutputStream();fis = new FileInputStream("01.jpg");byte[] b = new byte[10];int len;while ((len = fis.read(b)) != -1){os.write(b,0,len);}} catch (IOException e) {throw new RuntimeException(e);} finally {if (fis != null){try {fis.close();} catch (IOException e) {throw new RuntimeException(e);}}if (os != null){try {os.close();} catch (IOException e) {throw new RuntimeException(e);}}if (socket != null){try {socket.close();} catch (IOException e) {throw new RuntimeException(e);}}}}@Testpublic void server() {ServerSocket ss = null;Socket socket = null;InputStream is = null;FileOutputStream fos = null;try {ss = new ServerSocket(9090);socket = ss.accept();is = socket.getInputStream();fos = new FileOutputStream("04.jpg");byte[] b = new byte[10];int len;while ((len = is.read(b)) != -1){fos.write(b,0,len);}} catch (IOException e) {throw new RuntimeException(e);} finally {if (fos != null){try {fos.close();} catch (IOException e) {throw new RuntimeException(e);}}if (is != null){try {is.close();} catch (IOException e) {throw new RuntimeException(e);}}if (socket != null){try {socket.close();} catch (IOException e) {throw new RuntimeException(e);}}if (ss != null){try {ss.close();} catch (IOException e) {throw new RuntimeException(e);}}}}
}
public class TCPTest2 {//客户端发送文件给服务端,服务端保存到本地后,并返回“发送成功”给客户端@Testpublic void client() {Socket socket = null;OutputStream os = null;FileInputStream fis = null;InputStream is = null;ByteArrayOutputStream baos = null;try {socket = new Socket(InetAddress.getByName("127.0.0.1"), 9090);os = socket.getOutputStream();fis = new FileInputStream("01.jpg");byte[] b = new byte[10];int len;while ((len = fis.read(b)) != -1){os.write(b,0,len);}//关闭数据输出socket.shutdownOutput();//接收服务端发送的数据is = socket.getInputStream();baos = new ByteArrayOutputStream();byte[] b1 = new byte[5];int len1;while ((len1 = is.read(b1)) != -1){baos.write(b1,0,len1);}System.out.println(baos.toString());} catch (IOException e) {throw new RuntimeException(e);} finally {if (baos != null){try {baos.close();} catch (IOException e) {throw new RuntimeException(e);}}if (is != null){try {is.close();} catch (IOException e) {throw new RuntimeException(e);}}if (fis != null){try {fis.close();} catch (IOException e) {throw new RuntimeException(e);}}if (os != null){try {os.close();} catch (IOException e) {throw new RuntimeException(e);}}if (socket != null){try {socket.close();} catch (IOException e) {throw new RuntimeException(e);}}}}@Testpublic void server() {ServerSocket ss = null;Socket socket = null;InputStream is = null;FileOutputStream fos = null;OutputStream os = null;try {ss = new ServerSocket(9090);socket = ss.accept();is = socket.getInputStream();fos = new FileOutputStream("04.jpg");byte[] b = new byte[10];int len;while ((len = is.read(b)) != -1){fos.write(b,0,len);}//发送数据给客户端os = socket.getOutputStream();os.write("发送成功".getBytes());} catch (IOException e) {throw new RuntimeException(e);} finally {if (os != null){try {os.close();} catch (IOException e) {throw new RuntimeException(e);}}if (fos != null){try {fos.close();} catch (IOException e) {throw new RuntimeException(e);}}if (is != null){try {is.close();} catch (IOException e) {throw new RuntimeException(e);}}if (socket != null){try {socket.close();} catch (IOException e) {throw new RuntimeException(e);}}if (ss != null){try {ss.close();} catch (IOException e) {throw new RuntimeException(e);}}}}
}

UDP网络编程

public class UDPTest {//实现发送数据@Testpublic void sender(){//发送端DatagramSocket socket = null;try {//创建DatagramSocket()对象socketsocket = new DatagramSocket();//封装packet数据包String str = "UDP方式发送的数据包";byte[] b = str.getBytes();InetAddress host = InetAddress.getLocalHost();DatagramPacket packet = new DatagramPacket(b,0,b.length,host,9090);//发送socket.send(packet);} catch (IOException e) {throw new RuntimeException(e);} finally {if (socket != null){socket.close();}}}@Testpublic void receiver(){//接收端DatagramSocket socket = null;try {//创建DatagramSocket()对象socket,指明端口号socket = new DatagramSocket(9090);//接收的数据封装在packetbyte[] b = new byte[1024];DatagramPacket packet = new DatagramPacket(b,0,b.length);//阻塞,等待接收socket.receive(packet);System.out.println(new String(packet.getData(), 0, packet.getLength()));} catch (IOException e) {throw new RuntimeException(e);} finally {if (socket != null){socket.close();}}}
}

URL编程

1. URL(Uniform Resource Locator)的理解: 统一资源定位符,对应着互联网的某一资源地址。
2. URL的5个基本结构:

  • http://localhost:8080/examples/beauty.jpg?username=Tom
  • 《传输协议》: //《主机名》 :《端口号》 /《文件名》 # 片段名?参数列表

3. 如何实例化:

URL url = new URL("http://localhost:8080/examples/beauty.jpg?username=Tom")

4. 常用方法:

方法 方法作用
public String getProtocol() 获取该URL的协议名
public String getHost() 获取该URL的主机名
public String getPort() 获取该URL的端口号
public String getPath() 获取该URL的文件路径
public String getFile() 获取该URL的文件名(端口号后的都算)
public String getQuery() 获取该URL的查询名(参数列表)

5. 可以读取、下载对应的url资源:

public static void main(String[] args) {HttpURLConnection connection = null;InputStream is = null;FileOutputStream fos = null;try {URL url = new URL("http://localhost:8080/examples/beauty.jpg?username=Tom");connection = (HttpURLConnection) url.openConnection();//获取到一个关于服务器的连接connection.connect();//连接is = connection.getInputStream();//获取输入fos = new FileOutputStream("1.jpg");//本地保存地址//写入数据byte[] b = new byte[1024];int len;while ((len = is.read(b)) != -1){fos.write(b,0,len);}} catch (IOException e) {throw new RuntimeException(e);} finally {if (fos != null){try {fos.close();} catch (IOException e) {throw new RuntimeException(e);}}if (is != null){try {is.close();} catch (IOException e) {throw new RuntimeException(e);}}if (connection != null){connection.disconnect();//断开连接}}}

Java反射机制

Java反射的概述

  1. 反射的理解

    • Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
    • 框架 = 反射 + 注解 + 设计模式。
  2. 体会反射机制的“动态性”
@Testpublic void test(){for (int i = 0; i < 100; i++) {int num = new Random().nextInt(3);String classPath = "";switch (num){case 0:classPath = "java.util.Date";break;case 1:classPath = "java.lang.Object";break;case 2:classPath = "day10.Stu";break;}try {Object o = getInstance(classPath);System.out.println(o);} catch (Exception e) {throw new RuntimeException(e);}}}/*创建指定类的对象。classPath:指定类的全名*/public Object getInstance(String classPath) throws Exception {Class<?> clazz = Class.forName(classPath);return clazz.newInstance();}
  1. Java反射机制能提供的功能

    1. 在运行时判断任意一个对象所属的类。
    2. 在运行时构造任意一个类的对象。
    3. 在运行时判断任意一个类所具有的成员变量和方法。
    4. 在运行时获取泛型信息。
    5. 在运行时调用任意一个对象的成员变量和方法。
    6. 在运行时处理注解。
    7. 生成动态代理。
  2. 相关API
    1. java.lang.Class :反射的源头
    2. java.lang.reflect.Field :属性
    3. java.lang.reflect.Method :方法
    4. java.lang.reflect.Constructor :构造器

Class类的理解与获取Class的实例

  1. Class类的理解

    1. 类的加载过程:

      • 程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。
      • 接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载
      • 加载到内存中的类,我们就称为运行时类,此运行时类,就称为Class的一个实例
    2. 换句话说,Class的实例就对应着一个运行时类。
    3. 加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。
  2. 获取Class实例的几种方式

@Testpublic void test1() throws ClassNotFoundException {//方式一:调用运行时类的属性:.classClass<Stu> stuClass = Stu.class;System.out.println(stuClass);//方式二:通过运行时类的对象,调用getClass()Stu stu = new Stu();Class<? extends Stu> stuClass1 = stu.getClass();System.out.println(stuClass1);//方式三:调用Class的静态方法:forName(String classPath)Class<?> stuClass2 = Class.forName("ReflctionTest.Stu");System.out.println(stuClass2);//方式四(了解):使用类的加载器:ClassLoaderClassLoader classLoader = NemInstanceTest.class.getClassLoader();Class<?> stuClass3 = classLoader.loadClass("ReflctionTest.Stu");System.out.println(stuClass3);System.out.println(stuClass == stuClass1);//tSystem.out.println(stuClass == stuClass2);//tSystem.out.println(stuClass == stuClass3);//t}
  1. 总结:创建类的对象的方式

    • 方式一:new + 构造器
    • 方式二:要创建Xxx类的对象,可以考虑:Xxx、Xxxs、XxxFactory、XxxBuilder类中查看有没有静态方法的存在。可以通过调用静态方法,创建Xxx对象。
    • 方式三:通过反射
  2. Class实例可以时哪些结构
    • class:外部类,成员(成员内部类,静态内部类),局部内部类、匿名内部类。
    • interface :接口。
    • [] :数组。
    • enum :枚举类。
    • annotation :注解@interface
    • primitive type : 基本数据类型
    • void

了解ClassLoader

  1. 类的加载过程(了解)

  2. 类的加载器的作用

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

  4. Java类编译、运行的执行的流程

  5. 使用ClassLoader加载src目录下的配置文件

@Testpublic void test1() throws Exception {Properties properties = new Properties();//方式一
//        FileInputStream fis = new FileInputStream("jdbc.properties");
//        properties.load(fis);//方式二:配置文件默认识别:当前module的src下ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();InputStream is = classLoader.getResourceAsStream("jdbc1.properties");properties.load(is);String name = properties.getProperty("name");String password = properties.getProperty("password");System.out.println("name = " + name + "\n" + "password = " + password);}

反射应用一:创建运行时类的对象

  1. 代码举例
    Class<Stu> stuClass = Stu.class;Stu instance = stuClass.newInstance();System.out.println(instance);
  1. 说明

    • newInstance():调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。
    • 要想此方法正常的创建运行时类的对象,要求:
      1. 运行时类必须提供空参的构造器。
      2. 空参的构造器的访问权限得够。通常,设置为public。
    • 在javabean中要求提供一个public的空参构造器。原有:
      1. 便于通过反射,创建运行时类的对象。
      2. 便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器。

反射应用二:获取运行时类的完整结构

我们可以通过反射,获取对应的运行时类中所有的属性、方法、构造器、父类、接口、包、父类泛型、注解、异常等。。。

//属性
@Testpublic void test(){Class clazz = Stu.class;//getFields();获取当前运行时类及其父类声明为public访问权限的属性Field[] f = clazz.getFields();for (Field field : f) {System.out.println(field);}System.out.println("******");//getDeclaredFields();获取当前运行时类中所有属性。(不包含父类声明属性);for (Field field : clazz.getDeclaredFields()) {System.out.println(field);}}//方法
@Testpublic void test1(){Class clazz = Stu.class;//getMethods();获取当前运行时类及其所有父类中声明为public权限的方法for (Method method : clazz.getMethods()) {System.out.println(method);}System.out.println("******");//getDeclaredMethods();获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)for (Method declaredMethod : clazz.getDeclaredMethods()) {System.out.println(declaredMethod);}}//构造器
@Testpublic void test2() {Class clazz = Stu.class;//getConstructors();获取当前运行时类中声明为public的构造器Constructor[] constructors = clazz.getConstructors();for (Constructor constructor : constructors) {System.out.println(constructor);}//getDeclaredConstructors();获取当前运行时类中所有的构造器Constructor[] declaredConstructors = clazz.getDeclaredConstructors();for (Constructor c : declaredConstructors) {System.out.println(c);}}/*获取运行时类的父类*/
@Testpublic void test3() {Class clazz = Stu.class;Class superclass = clazz.getSuperclass();System.out.println(superclass);}/*获取运行时类带泛型的父类*/
@Testpublic void test4() {Class clazz = Stu.class;Type genericSuperclass = clazz.getGenericSuperclass();System.out.println(genericSuperclass);}/*获取运行时类带泛型的父类的泛型*/
@Testpublic void test5() {Class clazz = Stu.class;Type genericSuperclass = clazz.getGenericSuperclass();ParameterizedType parameterType = (ParameterizedType) genericSuperclass;//获取泛型类型Type[] actualTypeArguments = parameterType.getActualTypeArguments();System.out.println(actualTypeArguments[0].getTypeName());}/*获取运行时类的接口、所在包、注解*/
@Testpublic void test6() {Class clazz = Stu.class;//获取运行时类的接口Class[] interfaces = clazz.getInterfaces();for (Class c : interfaces){System.out.println(c);}//获取运行时类父类的接口Class[] interfaces1 = clazz.getSuperclass().getInterfaces();for (Class c : interfaces1){System.out.println(c);}//获取运行时类的所在包Package clazzPackage = clazz.getPackage();System.out.println(clazzPackage);//获取运行时类的注解for (Annotation annotation : clazz.getAnnotations()) {System.out.println(annotation);}}

反射应用三:调用运行时类的指定结构

调用指定的属性:

/*操作运行时类中的指定属性*/@Testpublic void test() throws Exception {Class clazz = Stu.class;//创建运行时类的对象Stu stu = (Stu) clazz.newInstance();//获取指定变量名的属性Field name = clazz.getDeclaredField("name");//保证当前的属性是可访问的name.setAccessible(true);name.set(stu,"QQ");Object o = name.get(stu);System.out.println(o);}

调用指定的方法:

/*操作运行时类中的指定方法*/@Testpublic void test1() throws Exception {Class clazz = Stu.class;//创建运行时类的对象Stu stu = (Stu) clazz.newInstance();Method m = clazz.getDeclaredMethod("show", String.class);m.setAccessible(true);Object o = m.invoke(stu, "CHN");System.out.println(o);//如何调用静态方法System.out.println("如何调用静态方法");Method show1 = clazz.getDeclaredMethod("show1", String.class);show1.setAccessible(true);Object o1 = show1.invoke(Stu.class,"CHN");System.out.println(o1);}

调用指定的构造器:

/*操作运行时类中的指定构造器*/@Testpublic void test2() throws Exception {Class clazz = Stu.class;//获取指定的构造器Constructor declaredConstructor = clazz.getDeclaredConstructor(String.class);//保证此构造器是可访问的declaredConstructor.setAccessible(true);//调用此构造器创建运行时类的对象Stu o = (Stu) declaredConstructor.newInstance("Tom");System.out.println(o);}

反射应用四:动态代理

1. 代理模式的原理:

  • 使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。

2. 静态代理:

  • 缺点:

    • 代理类和被代理类编译期间,就确定了。不利于程序的扩展。
    • 每个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。
  • 代码举例
//1. 创建接口ClothFactory
interface ClothFactory {//服装工厂void produceCloth();//生产服装
}//代理类实现接口ClothFactory
class ProxyClothFactory implements ClothFactory{private ClothFactory factory;//用代理类的对象进行实例化public ProxyClothFactory(ClothFactory factory) {this.factory = factory;}@Overridepublic void produceCloth() {System.out.println("代理工厂开始准备工作");factory.produceCloth();System.out.println("代理工厂后续收尾工作");}
}//被代理类实现接口ClothFactory
class BrandClothFactory implements ClothFactory {@Overridepublic void produceCloth() {System.out.println("知名品牌生成一批运动服");}
}public class StaticProxyTest {public static void main(String[] args) {//创建被代理类对象BrandClothFactory brand = new BrandClothFactory();//创建代理类对象,将被代理类的对象传入代理类构造器中ProxyClothFactory f = new ProxyClothFactory(brand);f.produceCloth();}
}

3. 动态代理的特点:动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。

4. 动态代理的实现

  • 要实现动态代理,需要解决的问题?

    • 问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。(通过Proxy.newProxyInstance()实现)
    • 问题二:当通过代理类的对象调用方法时,如何动态的去调用被代理类中的同名方法。(通过InvocationHandler接口的实现类及其方法invoke())
  • 代码实现:

interface Factory {//工厂接口void produce(String s);
}class AFactory implements Factory {//被代理@Overridepublic void produce(String s) {System.out.println("生产" + s);}
}/*要实现动态代理,需要解决的问题?问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。问题二:当通过代理类的对象调用方法时,如何动态的去调用被代理类中的同名方法。*/
class ProxyF {//调用此方法,返回一个代理类的对象。解决问题一public static Object getProxyInstance(Object obj) { //obj:被代理类的对象MyInvocationHandler handler = new MyInvocationHandler();handler.bind(obj);return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler);}
}class MyInvocationHandler implements InvocationHandler {private Object obj; //需要使用被代理类的对象进行赋值public void bind(Object obj) {this.obj = obj;}//当我们通过代理类的对象,调用方法a时(produce()),就会自动的调用如下的方法//将代理类要执行的方法a的功能就声明在invoke()中@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("代理工厂开始准备工作");//method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法。Object invoke = method.invoke(obj, args);System.out.println("代理工厂后续收尾工作");//方法的返回值作为当前类invoke()方法的返回值return invoke;}
}public class DProxy {public static void main(String[] args) {AFactory a = new AFactory();Factory proxyInstance = (Factory) ProxyF.getProxyInstance(a);proxyInstance.produce("5000件T恤");BrandClothFactory brand = new BrandClothFactory();ClothFactory proxyInstance1 = (ClothFactory) ProxyF.getProxyInstance(brand);proxyInstance1.produceCloth();}
}

Java8的其他新特性

Java8新特性概述

Lambda表达式

  1. Lambda表达式使用前后的对比:
 @Testpublic void test() {Runnable r1 = new Runnable() {@Overridepublic void run() {System.out.println("R1");}};r1.run();System.out.println("**************");Runnable r2 = () -> System.out.println("R2");r2.run();}@Testpublic void test1() {Comparator<Integer> com1 = new Comparator<Integer>() {@Overridepublic int compare(Integer o1, Integer o2) {return Integer.compare(o1,o2);}};System.out.println(com1.compare(12, 21));//Lambda表达式Comparator<Integer> com2 = (o1, o2) -> Integer.compare(o1,o2);System.out.println(com2.compare(12, 6));//方法引用Comparator<Integer> com3 = Integer :: compare;System.out.println(com3.compare(12, 12));}
  1. Lambda表达式的基本语法:

    1. 举例:(o1, o2) -> Integer.compare(o1,o2);
    2. 格式:
      • -> :Lambda操作符 或 箭头操作符
      • ->左边 :Lambda形参列表 (接口中的抽象方法的形参列表)。
      • ->右边 :Lambda体(重写的抽象方法的方法体)。
  2. 如何使用:分为六种情况

函数式接口

  1. 函数式接口的使用说明:如果一个接口中,只声明了一个抽象方法,则此接口就称为函数式接口。我们可以在一个接口上使用@FunctionalInterface 注解,这样可以检查它是否为一个函数式接口。

    • Lambda表达式的本质就作为函数式接口的实例
  2. Java8中关于Lambda表达式提供的4个基本的函数式接口:

  3. 总结

    1. 何时使用Lambda表达式?

      • 当需要对一个函数式接口实例化时,可以使用Lambda表达式。
    2. 何时使用给定的函数式接口?
      • 如果我们开发中需要定义一个函数式接口,首先看看在已有的jdk提供的函数式接口是否提供了能满足需求的函数式接口。
      • 有,则直接调用即可,不需要自己再自定义了。

方法引用

  1. 理解:方法引用可以看作是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法。

  2. 使用情景:当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!

  3. 格式:类(或对象)::方法名

  4. 分为如下的三种情况:

    • 情况一 对象 ::非静态方法
    • 情况二 类 ::静态方法
    • 情况三 类 ::非静态方法
  5. 要求:

    • 要求接口中的抽象方法的形参列表返回值类型与方法引用的方法的形参列表和返回值类型相同!(针对情况一和情况二)
    @Test
    public void test() {//Consumer<T> 中的 void accept(T t);//PrintStream 中的 void println()Consumer<String> con1 = str -> System.out.println(str);con1.accept("con1");PrintStream ps = System.out;Consumer<String> con2 = ps::println;//对象 ::非静态方法con2.accept("con2");
    }
    @Test
    public void test1() {//Comparator<T> 中的 int compare(T o1, T o2);//Integer 中的 static int compare(int x, int y)Comparator<Integer> com1 = (o1, o2) -> Integer.compare(o1, o2);System.out.println(com1.compare(12, 3));Comparator<Integer> com2 = Integer::compare;//类 ::静态方法System.out.println(com2.compare(12, 3));
    }
    • 当函数式接口方法的第一个参数是需要引用方法的调用者,并且第二个参数是需要引用方法的参数(或无参)时:ClassName :: methodName 。(针对情况三)
    @Test
    public void test2() {//Comparator<T> 中的 int compare(T o1, T o2);//String 中的 int o1.compareTo(o2)Comparator<String> com1 = (o1, o2) -> o1.compareTo(o2);System.out.println(com1.compare("abc", "acb"));Comparator<String> com2 = String::compareTo;//类 ::非静态方法System.out.println(com2.compare("abc", "abc"));
    }
    
  6. 使用建议:如果需要给函数式接口提供实例,恰好满足方法引用的使用情景,就可以考虑使用方法引用给函数式接口提供实例。不熟悉方法引用,还可以使用Lambda表达式。

构造器引用与数组引用

  1. 构造器引用格式:类名 :: new
  2. 构造器引用使用要求:和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致。抽象方法的返回值类型即为构造器所属的类的类型
  3. 构造器引用举例:
public class ConstructorRefTest {@Testpublic void test() {//Supplier<T> 中的 T get();//Employee()空参构造器Supplier<Employee> sup = new Supplier<Employee>() {@Overridepublic Employee get() {return new Employee();}};System.out.println(sup.get());Supplier<Employee> sup1 = () -> new Employee();System.out.println(sup1.get());Supplier<Employee> sup2 = Employee::new;System.out.println(sup2.get());}@Testpublic void test1() {//BiFunction<T, U, R> 中的  R apply(T t, U u)//Employee(int id, String name)BiFunction<Integer, String, Employee> bif = new BiFunction<Integer, String, Employee>() {@Overridepublic Employee apply(Integer id, String name) {return new Employee(id, name);}};System.out.println(bif.apply(12, "AA"));BiFunction<Integer, String, Employee> bif1 = (id, name) -> new Employee(id, name);System.out.println(bif1.apply(13, "AA"));BiFunction<Integer, String, Employee> bif2 = Employee::new;System.out.println(bif2.apply(14, "EE"));}
}
  1. 数组引用格式:数组类型[] :: new
  2. 数组引用举例:
@Testpublic void test2() {Function<Integer, Integer[]> fun = len -> new Integer[len];Integer[] apply = fun.apply(5);System.out.println(Arrays.toString(apply));Function<Integer, Integer[]> fun1 = Integer[]::new;Integer[] apply1 = fun1.apply(6);System.out.println(Arrays.toString(apply1));}

Stream API

  1. Stream API的理解:

    1. Stream关注的是对数据的运算与CPU打交道;集合关注的是数据的存储,与内存打交道。
    2. Java8提供了一套API,使用这套API可以对内存中的数据进行过滤、排序、映射、归约等操作。类似于sql对数据库中表的相关操作。
  2. 注意点:

    • Stream 自己不会存储元素。
    • Stream 不会改变源对象。相反,它们会返回一个持有结果的新Stream。
    • Stream 操作是延迟执行的。这意味着它们会等到需要结果的时候才执行。(延迟执行:需要调用终止操作,中间操作才会执行)
  3. Stream的使用流程:

    1. Stream的实例化;
    2. 一系列的中间操作(过滤、映射、、、);
    3. 终止操作。
  4. 使用流程的注意点:

    1. 一个中间操作链,对数据源的数据进行处理。
    2. 一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会在被使用。
  5. 步骤一:Stream实例化

    1. 通过集合
    List<String> list = new ArrayList<>();
    //返回一个顺序流
    Stream<String> stream = list.stream();
    //返回一个并行流
    Stream<String> parallelStream = list.parallelStream();
    
    1. 通过数组
    int[] arr = {1, 2, 3, 4, 5};
    IntStream stream1 = Arrays.stream(arr);//返回一个流
    
    1. 通过Stream.of
    Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);
    
    1. 创建无限流
        //public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) 迭代
    Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out::println);//public static<T> Stream<T> generate(Supplier<T> s) 生成
    Stream.generate(Math::random).limit(10).forEach(System.out::print);
    
  6. 步骤二:中间操作


  7. 步骤三:终止操作



    Collector需要使用Collectors提供实例:

Optional类的使用(java.util.Optional)

  1. 理解:为了解决Java中空指针的问题!

    • Optional类(java.util.Optional)是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。原来用null表示一个值不存在,现在Optional可以更好的表达这个概念。并且可以避免空指针异常。
  2. 常用方法

JavaSE(尚硅谷视频学习笔记)相关推荐

  1. Docker_尚硅谷视频学习笔记

    文章目录 1 Docker 简介 前提知识+课程定位 Docker 是什么? 问题:为什么会有docker出现 docker理念 总结 能干嘛 之前的虚拟机技术 容器虚拟化技术 开发/运维(DevOp ...

  2. 【javaScript】学完js基础,顺便把js高级语法学了(尚硅谷视频学习笔记)

    文章目录 [1]基本总结深入 一.什么是数据 1.数据类型 基本(值)类型 对象(引用)类型 2.判断 相关问题 二.什么是内存 1.什么是数据 2.什么是内存? 3.什么是变量 4.内存.数据.变量 ...

  3. 尚硅谷SpringBoot学习笔记

    目录 简介​编辑 快速搭建一个SpringBoot的网页 自动配置原理 容器功能 组件添加 原生配置文件引入 配置绑定 自动配置原理 1.引导加载自动配置类 2.按需配置开启自动配置项 开发小技巧 1 ...

  4. 尚硅谷Vue2学习笔记分享

    前言 这里是尚硅谷Vue2的学习笔记分享. 原视频是尚硅谷Vue2.0+Vue3.0全套教程丨vuejs从入门到精通 Vue3的笔记链接 文章目录 前言 初识Vue 模板语法 数据绑定 el和data ...

  5. Dubbo之《尚硅谷》学习笔记

    一.基础知识 1.分布式基础理论 1.1 什么是分布式系统? <分布式系统原理与范型>定义: "分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统" ...

  6. 尚硅谷MySQL学习笔记

    MySQL笔记目录 一.MySQL数据库基础篇大纲 1.数据库概述与MySQL安装篇 第01章:数据库概述 第02章:MySQL环境搭建 2.SQL之SELECT使用篇 第03章:基本的SELECT语 ...

  7. 尚硅谷Netty学习笔记

    Netty 一些问题 1.阻塞与非阻塞 阻塞和非阻塞指的是执行一个操作是等操作结束再返回,还是马上返回 举例:在 BIO 案例的 handler 方法中,如果读取不到数据就会阻塞在 read() 方法 ...

  8. JavaWeb尚硅谷【学习笔记】(整合)未完成

    文章目录 一.HTML 1.网页的组成部分 2.HTML简介 3.HTML文件的书写规范 4.HTML标签的介绍 5.标签的语法 6.常用标签介绍 二.CSS技术 1.CSS技术介绍 2.CSS语法规 ...

  9. 尚硅谷JVM学习笔记:1.JVM与Java体系结构

    JVM上篇:内存与垃圾回收篇(一) 笔记来源:尚硅谷宋红康JVM全套教程(详解java虚拟机)_哔哩哔哩_bilibili 1.JVM与Java体系结构 1.1前言 开发人员如何看待上层框架 一些有一 ...

最新文章

  1. OpenCV中检测ChArUco的角点(2)
  2. bezier曲线_Bezier算法
  3. ActiveMQ与xml rpc
  4. 【转载】python 编码问题 u'汉字'
  5. python pop check mail_python初学者,用python3实现基本的学生管理系统代码实例
  6. eclipse从svn导入maven项目变成普通项目解决办法
  7. MyEclipse配置Tomcat(图解)
  8. php云服务器买什么系统吗,php云服务器买什么系统
  9. 操作系统之I/O管理:4、缓冲区管理(单缓冲、双缓冲、循环缓冲、缓冲池)
  10. pythonista ios_Pythonista 3 ios版下载_Pythonista 3苹果版
  11. zktime 协议_ZKtime5.0考勤软件说明书
  12. 冰点等文库下载器无法使用的解决办法
  13. 游戏是怎么赚钱的 - 科普篇
  14. 使用RawImage播放视频不清晰(改变视频比例后不清晰)的问题解决
  15. 平板电脑如何刷linux,平板电脑刷windows的方法是什么_如何把平板刷windows图文步骤...
  16. proxy 状态代码503_HTTP状态503错误代码及其解决方法?
  17. 如何删除设备和驱动器中的百度网盘
  18. Genessential获近千万元天使轮融资,明年推出AGEs 手持检测设备...
  19. JavaScript 页面资源加载:onload,onerror
  20. 用什么语言开发的Matlab

热门文章

  1. div+css实现个人简历
  2. ORA-64203: 目标缓冲区太小, 无法容纳字符集转换之后的 CLOB 数据。
  3. 【日志】20220414
  4. 有趣的搬砖工 No.2 cout格式化输出
  5. 练习之彩票三 添加号码相关代码
  6. 路由器接口及连接(1)
  7. Mysql数据库技术——MEB备份技术
  8. pycharm如何使用?
  9. 由一个骂评引发的作者产品体验报告-----我要集气
  10. 对于新手来说怎么动漫人物手伸出去的动作?该怎么画?