admin管理员组

文章数量:1531433

文章目录

    • **>> 基础**
      • **~ 内存的划分**
      • **~ 交互方式**
      • **~ JAVA靠语言的三种技术架构**
      • **~ Java语言特点 - 跨平台性 - JVM**
      • **~ JAVA环境配置—系统变量**
      • **~ cmd运行`.java`文件**
      • **~ Java编码**
      • **~ Java 基础**
      • **~ Java语言基础组成**
      • **~ 自动类型提升与强制类型转换**
      • **~ 程序的流程控制**
    • **>> 函数**
      • **~ 定义**
      • **~ 主函数**
      • **~ 主函数内存图解**
      • **~ 底层运算原理**
      • **~ 函数的重载overload**
    • **>> 数组**
      • **~ 定义**
      • **~ 数组在内存中的分布**
      • **~ 数组中常见问题**
      • **~ 数组的常见操作**
      • **~ 数组在实际开发中的应用-查表法**
      • **~ 查表法应用**
      • **~ 二维数组**
    • **>> 面向对象**
      • **~ 面向过程与面向对象**
      • **~ 类与对象的关系**
      • **~ 匿名对象**
      • **~ 面向对象特征1 - 封装**
      • **~ 构造函数**
      • **~ this关键字**
      • **~ static关键字**
      • **~ 静态代码块、构造代码块、局部代码块区别**
      • **~ 成员变量和局部变量的区别**
      • **~ 成员变量(实例变量)和静态变量(类变量)的区别**
      • **~ 单例设计模式**
      • **~ 面向对象特征2 - 继承**
      • **~ 一个对象实例化过程**
      • **~ final(最终)关键字**
      • **~ 继承 - 抽象类**
      • **~ 接口**
      • **~ 抽象类和接口的异同点**
      • **~ 面向对象特征3 - 多态**
      • **~ 内部类**
      • **~ 异常**
      • **~ Object类**
      • **~ 包 package**
      • **~ 4种权限**
    • **>> 多线程**
      • **~ 多线程概述**
      • **~ 线程的四种状态**
      • **~ 多线程 - wait和sleep的区别**
      • **~ 多线程的创建方法一:继承Thread类**
      • **~ 多线程的创建方法二:实现Runnable接口 - 常用**
      • **~ 线程安全问题 - synchronized**
      • **~ 同步代码块、同步函数、静态同步函数**
      • **~ 多线程下的单例模式**
      • **~ 死锁代码**
      • **~ 线程间通信 - 等待唤醒机制**
      • **~ 线程间通信 - 多生产者、多消费者问题**
      • **~ 多生产者、多消费者 - JDK5.0新特性 - Lock、Condition**
      • **~ 停止线程方式 - 定义标记 - Interrupt**
      • **~ 其他方法 - join、setPriority(优先级)**
    • **>> 常用对象API**
      • **~ String 类**
      • **~ StringBuffer 类**
      • **~ StringBuilder 类**
      • **~ System 类**
      • **~ Runtime 类**
      • **~ Math 类**
      • **~ Date 类**
      • **~ Calendar 类**
    • **>> 集合框架**
      • **~ 基础 概述**
      • **~ List 列表集合**
      • **~ LinkedList 集合**
      • **~ ArrayList 集合 - 存储自定对象**
      • **~ Set 集合 - HashSet、TreeSet**
      • **~ HashSet 集合 - 存储自定对象**
      • **~ LinkedHashSet 集合**
      • **~ TreeSet 集合**
      • **~ List列表与Set集合选择**
      • **~ Map 集合**
      • **~ HashMap - 存储自定义对象**
      • **~ TreeMap - 存储自定义对象**
      • **~ LinkedHashMap**
      • **~ Map 集合练习**
      • **~ 工具类 Collections**
      • **~ 工具类 Arrays - 数组转成集合**
      • **~ 工具类 Collection的toArray方法 - 集合转成数组**
      • **~ foreach - 高级for循环**
      • **~ 函数可变参数**
    • **>> 泛型**
      • **~ 基础 概述**
      • **~ 泛型类**
      • **~ 泛型方法**
      • **~ 泛型接口**
      • **~ 泛型限定**

>> 基础

~ 内存的划分

1、寄存器:CPU来处理;

2、本地方法区:与所在系统相关;分版本;

3、方法区(共享数据区、数据区、共享区):里面放方法;也是代码存放区;
(1)方法区里面包括静态方法区、非静态方法区;成员方法存放在非静态方法区,静态方法存放在静态区;静态区、非静态区方法都是共享的,只是成员方法和静态方法的调用方式和所属不一样 (非静态区里面的成员都有一个this所属,因为非静态区的成员只能被对象调用,静态区的成员都所属与类名,可以被对象调用也可以通过类名调用)
(2)运行类的时候,类就加载到内存,内存划分区域,方法存放在方法区;

4、栈内存:代码运行区;存储的都是局部变量;凡是定义在方法中的变量都是局部变量;而且变量所处作用域一旦结束,该变量就自动释放;

5、堆内存:存储的是数组和对象(数组就是对象);凡是new建立的都在堆中;
特点
(1)每一个实体都有一个首地址值;
(2)堆内存中的每一个变量都有默认初始化值,根据类型的不同而不同;整数是0、小数是0.0或者0.0f、boolean型是false、char型是‘\u0000’;
(3)垃圾回收机制;

~ 交互方式

1、GUI(Graphical User Interface)图形化界面;
2、CLI(Command Line Interface)命令行方式;

~ JAVA靠语言的三种技术架构

~ Java语言特点 - 跨平台性 - JVM

1、因为有JVM,所以具有良好的可移植性;

2、一次编译随处运行;

3、JVM不跨平台,分版本的,具体的平台安装指定的JVM版本:Win版的JVM适用于Windows操作系统,Linux版的JVM适用于Linux操作系统,Mac版的JVM适用于Mac操作系统;

4、JVM用来解析Java程序;

5、JRE包括JVM、核心类库;安装JRE就可以运行一个开发好的Java程序;

6、JDK包括JRE、Java开发工具(编译工具javac.exe,打包工具jar.exe),使用JDK开发的程序交给JRE运行;

~ JAVA环境配置—系统变量

1、 JAVA_HOME: 里面放JDK安装目录,bin前的路径;
D:\Java\JDK

2、CLASSPATH: 里面加上JDK里面的bin的路径;
%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;

3、PATH: path里面存放可执行文件的路径;
%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;

4、在当前路径下运行非当前路径下的class文件,需要设置classpath环境变量;path里面存放可执行文件.exe的路径,所以设置一个新的环境变量,方便虚拟机找到要运行的文件,类文件路径classpath;

5、classpath环境变量的作用: 是将Java的运行文件的所在的路径告诉系统,虚拟机在执行时会按照指定的classpath路径进行类文件的查找并执行;规定JVM该到什么地方去执行Java程序;

6、设置classpath的目的: 是告诉虚拟机该到哪里去找要运行的类文件;
若没有配置classpath路径,则虚拟机在当前路径下查找类文件,若没有则报错;若配置了,虚拟机在指定位置查找class文件;
若当前路径下和classpath路径下都有相同名字的class文件,则虚拟机在classpath路径下查找class文件,若classpath路径下没有需要的class文件,则报错;

7、classpath=f:\myclass; 若结尾处没有分号,则报错;若加了分号则在指定路径没找到,就会再到当前路径下查找所需class文件;(最好不加分号,指定路径没找到就要报错)

8、classpath可以在dos命令行下下临时设置:set classpath=class文件路径

~ cmd运行.java文件

1、使用记事本,编辑文件,另存为.java格式:

// 加上public之后,保存文件时文件名与类名Demo不一样,编译就会失败;
public class Demo 
{  public static void main(String[] args)
	{   System.out.println("Hello World");}
}

2、cmd进入到.java文件所在文件夹下,输入命令javac Demo.java,将java文件编译为.class文件;
3、输入命令java Demo,运行.class文件;

~ Java编码

Java用底层的Unicode国际标准码表,包括ASCII表、GBK2312-GBK-GBK18030;

~ Java 基础

1、Java程序都定义在类中,Java程序都是以类的形式存在的,类的形式其实就是一个字节码文件最终体现;
用class关键字来完成类的定义,并起一个阅读性强的类名;

2、定义一个主函数,为了保证程序的独立运行,public static void main(String[] args) 这是主函数的固定格式,JVM认识;

~ Java语言基础组成

关键字、标识符、注释、变量与常量、运算符、语句、函数、数组;

1、关键字:被赋予特殊Java含义的单词;
特点:关键字中所有字母都是小写;类名首字母最好大写,多个单词组成首字母大写;一般函数名首字母小写,构造函数名首字母大写,与类名一样;
(1)用于定义数据类型的关键字: class、interface、byte、short、int、long、float、double、char、boolean、void;
(2)用于定义数据类型值的关键字: true、false、null
(3)用于定义流程控制的关键字: if~else、switch~case、while~do、default、for、break、continue、return
(4)用于定义访问权限修饰符的关键字: private、protect、public
(5)用于定义类、函数、变量 修饰符的关键字: abstract、final、static、synchronized
(6)用定义类与类之间关系的关键字: extends、implement
(7)用于定义建立实例、引用案例、判断实例的关键字: new、this、super、instanceof
(8)用于异常处理的关键字: try、catch、finally、throw、throws
(9)用于包的关键字: package、import
(10)其他修饰符关键字: native、strictfp、transient、volatile、assert

2、标识符:在程序中自定义的一些名称(用于标识某些东西的符号);
要求:由26个英文字母大小写,数字,下划线_,美元符号$;不可以数字开头;不可以使用关键字;

3、注释:用于注解、说明、解释程序中的文字,提高了代码的阅读性;可用于调试程序;
(1)注释的格式
单行注释: // 注释文字
多行注释: /*注释文字*/
文档注释: /**注释文字*/

(2)文档注释 是Java特有的,可以对写的源代码进行说明性文字的体现,它跟多行注释最大的不同在于,文档中所写的文字注释可以通过Java中的javadoc.exe工具进行提取,生成一个说明书,把文档注释和源代码都放到一个网页文件中,这个网页文档记录了说明性文字和程序代码,就是程序的说明书;

(3)注意:单行注释中可以嵌套单行、多行注释;多行注释中不能嵌套多行注释,报错;注释不编译到类文件中;

(4)使用 javadoc.exe 提取文档注释,文档注释提取的是public、protect,private不提取;所以private限定的方法 使用多行注释即可;

4、变量与常量
常量:不能改变的数值;
在 Java 中, 利用关键字 final 指示常量;关键字 final 表示这个变量只能被赋值一次,一旦被赋值之后, 就不能够再更改了,习惯上, 常量名使用全大写;
在 Java 中, 经常希望某个常量可以在一个类中的多个方法中使用, 通常将这些常量称为类常量; 可以使用关键字 static final设置一个类常量;
(1)整数常量
(2)小数常量
(3)布尔型常量,只有两个数值:true,false
(4)字符常量:将一个数字字母或符号用单引号(’’)标识: ‘a’
(5)字符串常量:将一个或多个字符用双引号("")标识:"a", "", " "
(6)null常量,只有一个数值:null;

变量: 内存中的一个存储区域,该存储区域有自己的名称(变量名)和类型(数据类型),该区域的数据可以在同一类型范围内不断变化;
(1)变量用来不断地存放同一类型的常量,并可以重复使用;
(2)变量的作用范围:一对{}之间;
(3)初始化值:数据类型 变量名=初始化值

5、运算符
(1)算数运算符: + - * / %(取余) +(连接符)
注意:字符串与数据相加,+是连接符;任何数据与字符串用+相加都是相连接,拼成一个更大的字符串;
整数被 0 除将会产生一个异常, 而浮点数被 0 除将会得到无穷大或 NaN 结果;

(2)自增运算符++ --

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

(4)比较运算符> < == >= <= !=
注意:运算符运算完肯定有结果!比较运算符结果为布尔型:true、false

(5)逻辑运算符&(与) |(或) !(非) ^(异或--同0异1) &&/||(与/或 短路计算)
逻辑就是指的一种关系,逻辑运算符用于连接两个boolean类型的表达式;

&与&&、 |与|| 区别: 运算结果一样,运算过程不一样;双 与/或 对数据进行短路计算,左边结果影响右边是否参与运算;单“与”“或”还能做位运算;

(6)位运算符<<左移 >>右移 >>>无符号右移 & 按位与 |按位或 ^按位异或 ~非(反码)
位运算符:直接对二进制位进行运算;

注意:一个数异或同一个数两次结果还是这个数,633=6;异或一次相当于加密,再异或一次相当于解密;ukey里面有程序自动读取这个数,不用知道;

<<左移右边补0;左移几位相当于该数据*2的几次方;所以左移可完成2的次幂运算;
>>右移最高位是0左边补0,是1左边补1;右移几位相当于该数据除以2的几次幂;
>>>无符号右移,实际进行右移时 高位出现的空位,补0;

(7)三元运算符(条件表达式)?表达式1:表达式2;
三元运算符就是if—else的简化格式
简写格式什么时候用:当if-else运算后 有一个具体结果时,可以简化写成三元运算符;

(8)运算符级别
&& 的优先级比 || 的优先级高:a && b || c 等价于 (a && b) || c
+=是右结合运算符:a += b += c 等价于 a += (b += c)

6、语句
7、函数
8、数组

~ 自动类型提升与强制类型转换

自动类型提升:只有数值型变量之间 不同类型能运算,占用内存较小的类型做一次自动类型提升;

强制类型转换:最好不用,因为把前三位丢弃,容易丢失精度;

~ 程序的流程控制

(1)顺序结构
(2)判断结构 if—else、 if~else
(3)选择结构 switch-case
(4)循环结构 for while do-while
(5)其他流程控制语句 break、 continue

switch(表达式)                 // 四种类型的值:byte,short,int,char;
{
     case 取值1:执行语句;break// case无顺序;
     case 取值2:执行语句;breakdefault:执行语句;break}
for(初始化表达式;循环条件表达式;循环后的操作表达式)
{    执行语句;(循环体)  }

(6)if和switch比较:(常用if)
if
(1)对具体的值进行判断;
(2)对区间进行判断;
(3)对运算结果是布尔类型的表达式进行判断;
switch
(1)对具体的值进行判断;
(2)值的个数通常是固定的;
对于几个固定的值判断,建议使用switch语句,因为switch语句会将具体的答案都加载进内存,效率相对高一点;不过if相对简单,常用;

(7)for和while特点:(常用for)
1、for和while可以互换;
2、格式不同,在在使用上有点小区别:若需要通过变量来对循环进行控制,该变量只作为循环增量存在时,区别就体现出来了;

int x=1;
while(x<5){ System.out.println(“y”);x++}
forint  y=1;y<5;y++(System.out.println(“y”);)
循环结束后还可以对x进行操作,但不能对y进行操作,因为y在for循环体定义的,
循环体循环结束后,y就消失;x在主函数体内定义的,循环结束后,while用完后,
x还驻留在内存中,所以浪费内存;

(7)break:跳出当前循环体;,若出现了循环嵌套,默认跳出内循环,break想要跳出指定的循环,可以通过标号(for循环的名字)来完成;
作用范围:要么是switch语句,要么是循环语句;
注意:当break单独存在时,下面不要定义其他语句,应为执行不到;

(8)continue:结束本次循环,继续下次循环;
作用范围:循环结构
注意:如果continue单独存在时,下面不要有任何语句,因为执行不到;


>> 函数

~ 定义

1、定义:就是定义在类中的具有特定功能的一段独立的小程序,也称为方法;

2、格式

修饰符 返回值类型 函数名(参数类型 形参1,参数类型 形参2...{
     执行语句;
      return 返回值;
}
  • 修饰符:static,public;
  • 返回值类型:函数运行后的结果的数据类型;
  • 函数名:小驼峰格式;
  • 参数类型:是形式参数的数据类型;
  • 形式参数:是一个变量,用于存储调用函数时传递给函数的实际参数;
  • 实际参数:传递给实际参数的具体数值;
  • return:用于结束函数;
  • 返回值:该函数运算后的结果,该结果会返回给调用者;
  • 特殊情况:void 返回值类型为空;
    功能没有具体非返回值;这时return后面直接用分号;结束;因为没有具体值所以不能写具体数据类型,在Java中只能用一个关键字来表示:void;
    注意:若返回值类型为void,则函数中的return语句可以不写;

3、函数特点
(1)定义函数可以将功能代码进行封装;
(2)便于对该功能进行复用;
(3)函数只有被调用才会被执行;
(4)函数的出现提高了代码的复用性;
(5)对于函数没有具体的返回值的情况,返回值类型用关键字void表示,函数中return语句如果在最后一行,可以省略不写;

注意:函数中只能调用函数,不能在函数内部定义函数; 定义函数时,函数的结果应该返回给调用者,交由调用者处理;

4、函数两个特性:
①重载overload,同一个类中;
②覆盖override,子父类中;覆盖也称为重写,覆写;

~ 主函数

public static void main(String[] args){ ...... }

1、主函数特殊之处:
(1)格式是固定的:public static void main(int[] x){ }不是主函数,是与主函数重名的重载函数;
(2)被jvm所识别和调用;

2、public:因为权限必须是最大的;
static:虚拟机在调用函数的时候不需要对象的,直接用主函数所属类名调用即可;
void:主函数没有具体的返回值;
main:函数名,不是关键字,只是一个jvm识别的固定的名字;
String[] args:主函数的参数列表,是一个数组类型的参数,而且元素都是字符串类型; new String[0] 虚拟机创建了一个数组实体,传给主函数;虚拟机在调用主函数时传值给主函数;

~ 主函数内存图解

1、执行test类的时候,这个类就进内存了;

2、先在方法区划分空间,存放类的方法:成员方法存放在非静态区,静态方法存放在静态区;
所有的类的默认构造函数加载到非静态方法区,非静态区所有成员都有一个this所属,只能被对象调用;主函数作为静态方法加载到静态方法区;

3、类加载完成之后,jvm通过类名调用test类的main方法,mian方法进栈;

4、执行main方法的第一句话:Person.method();,这时Person类才进行加载;JVM会去classpath路径下查找是否有Person.class文件,若没有设置classpath,默认在当前路径下查找;找到以后,就会将class文件加载进内存:在方法区中分配空间;

5、Person类加载完成之后,执行Person.method();语句;通过类名调用,只能去静态方法区去找;在静态区查找是否有Person类,找到之后查找Person类的区域中是否有method静态方法,有的话method方法就进栈;

6、method方法进栈,方法里面有局部变量就划分空间存放变量,没有局部变量就直接执行代码,执行完代码之后,方法弹栈;

7、执行主函数的下一句代码;Person p = new Person("java",20);

8、首先Person p在栈区的main方法中定义定义变量p,然后new Person("java",20)在堆内存中开辟空间创建Person对象,内存空间有一个首地址,Person中的成员变量在这片空间中进行默认初始化赋值 (不包括静态变量);

9、类的成员变量默认初始化之后,就要进行构造函数初始化,这时相应的构造函数进栈,这个构造函数要调用对象中的数据,要给对象中的数据进行初始化,所以该构造函数就持有一个this所属,指向堆内存中调用它的那个对象;

10、初始化操作完成之后,构造函数弹栈,堆中对象的内存地址 赋给栈中的p变量;

11、p.show();show方法进栈,有一个this所属,this指向调用它的对象在堆中的内存地址;

12、show方法执行完之后弹栈,主函数语句执行完毕,弹栈,主函数弹栈之后JVM执行完毕;

~ 底层运算原理

/>javac FunctionDemo.java --> 没报错时生成FunctionDemo.class字节码文件;
/>java FunctionDemo

1、javac启动Java的编译器程序,对给定的.Java文件进行编译(检查),若都通过,生成Java指定格式的运行程序;

2、Java命令启动虚拟机,目的是让它运行Java应用程序FunctionDemo;启动虚拟机执行一个类的时候自动先找类里面有没有一个名称为main的函数,找到从这儿执行,未找到运行时报错;

3、虚拟机启动以后,在执行一个应用程序的时候,任何应用程序在启动后都要在内存中划分空间;先让主函数进栈,然后执行主函数的第一句代码,主函数调用其他函数,函数运算完就从栈内存中释放,再次调用再次加载进栈内存;主函数执行完也出内存,程序结束;

栈的特点:先进后出;

~ 函数的重载overload

1、定义:在同一个类中,允许存在一个以上的同名函数,只要它们的参数个数或者参数类型不同即可;

2、特点:与返回值类型无关,只看参数列表;
java是严谨性语言,若函数出现的调用的不确定性,会编译失败;

3、好处:便于阅读,优化了程序设计;

int add(int x,int y) {return x+y;}
int add(int x,int y,int z) {return x+y+z;}
double add(double x,double y){return x+y;}

4、重载代码通常会重复,因为功能一样;参数个数不一样,一般都可以复用,参数类型不同一般不能复用;

public static int add(int a,int b)        // 加法运算:两个整数的和;
{    return a+b;    }
public static int add(int a,int b,int c)  //加法运算:三个整数的和; 
{    return add(a,b)+c;    }

>> 数组

~ 定义

1、定义:同一种类型数据的集合;其实数组就是一个容器;

2、好处:可以自动给数组中的元素从0开始编号,方便操作这些元素;

3、初始化
(1)int[] arr = new int[5]; :需要一个容器但是不明确容器中的具体数据;
(2)int[] arr = new int[]{3,5,7,1}; :常规初始化数组的的方式;
(3)int arr = {3,5,7,1}; :静态初始化方式;需要一个容器存储已知的具体数据;
注意:数组一旦定义,就要确定长度!不初始化,默认值是0;

~ 数组在内存中的分布

public static void main(String[] args){
    int[] arr = new int[3];
}

1、程序开始执行,主函数进栈,然后顺序执行主函数中代码;
2、int[] arr:arr在主函数中,是局部变量,存储在栈内存中;所以在占内存中开辟空间,创建arr变量;
3、new int[3]:是数组对象,存储在堆中;所以在堆内存中开辟空间,创建对象;
堆中存储的都是实体,数组叫实体,对象也叫实体;
实体:实实在在存在的个体;
实体的作用:封装多个数据;
4、实体或对象把在堆内存中的地址赋值给栈内存中创建的变量arr,arr通过地址指向数组;
5、引用数据类型:arr引用了堆内存中的一个实体;
6、arr = null; 此时堆中的arr实体不消失,而是视为垃圾,不定时回收;

~ 数组中常见问题

1、ArrayIndexOutOfBoundsException:数组角标超出范围异常,当访问到数组中不存在的角标时,就会发生该异常;
2、NullPointerException:空指针异常,当引用型变量没有任何实体指向时,还在用其操作实体,就会发生该异常;
3、[I@c17164 : [数组型 , i整形;

~ 数组的常见操作

对数组最基本的操作就是存、取;
核心思想:就是对角标的操作;角标即索引;

1、遍历:Arrays.toString(array);Arrays.deepToString(matrix)

public static void printfArray(int[] arr)
{    System.out.print("[");
     for(int i=0;i<arr.length;i++)
     {    if(i!=arr.length-1)   System.out.print(arr[i]+", ");
	      else   System.out.println(arr[i]+"]");
     }
}

2、获取最大、小值:
思路
(1)需要进行比较,并定义变量记录住每次比较后较大的值;
(2)对数组中的元素进行遍历取出,和变量中记录的元素进行比较;如果遍历到的元素大于变量中记录到元素,就用变量记录住该大的值;
(3)遍历结果,改变量记录的就是最大值;

public static int getMax(int[] arr)
{   int max = 0;
	for(int i=1;i<arr.length;i++)
	{    if(arr[i]>arr[max])   max=i;  }
	return arr[max];
}

3、排序:选择排序、冒泡排序: Java定义好了排序方法,可以直接拿来用:import java.util.*;Array.sort(arr);

(1)选择排序:
第一次循环,a[0]与a[1~n]进行比较,若a[0]>a[x],a[0]=a[x];循环结束后,a[0]值最小,固定,不参与下次循环;
第二次循环,a[1]与a[2~n]进行比较,固定a[1]值第二小,不参与下次循环;
第三次循环…

public static void selectSort(int[] arr) {
	for (int x = 0; x < arr.length - 1; x++) {  // 层循环从0开始,到长度-1结束;
		for (int y = x + 1; y < arr.length; y++) { 
			if (arr[x] > arr[y]) {
				int temp = arr[x];
				arr[x] = arr[y];
				arr[y] = temp;
			}
		}
	}
}

(2)冒泡排序: 每次固定最大值;

public static void bubbleSort(int[] arr) {
	for (int x = 0; x < arr.length - 1; x++) {
		for (int y = 0; y < arr.length - 1 - x; y++) {
			if (arr[y] > arr[y + 1]) {
				int temp = arr[y];
				arr[y] = arr[y + 1];
				arr[y + 1] = temp;
			}
		}
	}
}
public static void buddleSort(int[] arr) {
	for (int x = arr.length - 1; x > 0; x--) {
		for (int y = 0; y < x; y++) {
			if(){}  ......
		}
	}
}

4、查找: 查找的是这个元素第一次出现的位置的索引;

// 普通查找法,适用于无序数组;
public static int getIndex(int[] arr, int key) {
	for (int i = 0; i < arr.length; i++) {
		if (arr[i] == key) return i;
	}
	return -1;
}

5、折半查找(二分查找): 查找有序数组;
import java.util.*;int index = Array.binarySearch(arr,50); 如果存在,返回的是具体的角标位置;如果不存在,返回的是(-插入点-1)

public static int binarySearch(int[] arr, int key) {
	int min = 0;
	int max = arr.length - 1;
	int mid = (min + max) / 2;
	while (arr[mid] != key) {
		if (key > arr[mid])       min = mid + 1;
		else if (key < arr[mid])  max = mid - 1;
		if (max < min)  return -1;
		mid = (min + max) / 2;
	}
	return mid;
}
public static int binarySearch_2(int[] arr, int key) {
	int min, mid, max;
	min = 0;
	max = arr.length - 1;
	while (max > min) {
		mid = (max + min) >> 1;
		if (key > arr[mid])      min = mid + 1;
		else if (key < arr[mid]) max = mid - 1;
		else  return mid;
	}
	return -1;
}

~ 数组在实际开发中的应用-查表法

什么时候使用数组?
如果数据出现了对应关系,而且对应关系的一方是有序的数字编号,并可作为角标使用;这时就必须要想到数组的使用;就可以将这些数据存储到数组中;根据运算的结果作为角标直接去查数组中对应的元素即可;这种方式成为查表法;

0,1,2,3,4,5,6,7,8,9, A, B, C, D, E, F
0,1,2,3,4,5,6,7,8,9,10, 11, 12, 13, 14, 15

1、获取一个整数的16进制表现形式,原始方法:

public static void toHex(int num) {
	for (int i = 0; i < 8; i++) {
		int temp = num & 15;  // 取二进制的后四位,是16进制的一位;
		if (temp > 9)
			System.out.print((char) (temp - 10 + 'A'));
		else
			System.out.print(temp);
		num = num >>> 4;   // 无符号右移二进制的4位;
	}
}

2、查表法:

public static void toHex_1(int num) {
	// 定义一个对应关系表
	char[] chs = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
	for (int x = 0; x < 8; x++) {
		int temp = num & 15;   // 取二进制的4位,对应16进制的一位
		System.out.print(chs[temp]);
		num = num >>> 4;
	}
}
public static void toHex_2(int num) {
	if (num == 0) {
		System.out.println("0");
		return;  // 若不写return,则继续执行下面程序
	}
	// 定义一个对应关系表
	char[] chs = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
	/* 一会查表会查到比较多的数据;数据一多就先存储起来,再进行操作;所以定义一个数组:临时容器 */
	char[] arr = new char[8];  
	int pos = arr.length;
	while (num != 0) {
		int temp = num & 15;  // 二进制&1,八进制&7
		arr[--pos] = chs[temp];
		num = num >>> 4; // 二进制右移4位 
	}
	for (int x = pos; x < arr.length; x++) {
		System.out.print(arr[x]);
	}
}

3、Java中提供的方法:import java.util.*;int I = Interger.toBinaryString(8); //转换为二进制;

~ 查表法应用

public class test {
	public static void main(String[] args) {
		String week = getWeek(7);
		System.out.println(week);
	}
	public static String getWeek(int num) {
		if (num > 7 || num <= 0)
			return "无效的星期!";
		String[] week = new String[] { "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日" };
		return week[num - 1];
	}
}

~ 二维数组

格式1:int[][] arr = new int[3][2];
格式2:int[][] arr = new int[3][];
定义二维数组,指定二维数组的长度就可,一维数组长度不用管,后期存哪个一维数组,长度就是这个一维数组的长度;一维数组长度可不一样;

public static void main(String[] args) {
	int[][] arr = new int[3][2];  // 创建一个二维数组,该数组中有三个一维数组,每个一维数组中有两个元素;
	System.out.println(arr);      // [[I@4e4d1abd 直接打印二维数组在堆内存中的地址;
	System.out.println(arr[0]);   // [I@28169674  直接打印二维数组中的角标为0 的一维数组内存地址;
	System.out.println(arr[0][0]);// 0   直接打印二维数组中的角标为0 的一维数组中角标为0的元素值;
	
	int[][] arr1 = new int[3][];   // 创建一个二维数组,该数组中有三个一维数组,每个一维数组中元素个数不确定;
	System.out.println(arr1);      // [[I@7676438d 直接打印二维数组在堆内存中的地址;
	System.out.println(arr1[0]);   // null  直接打印二维数组中的角标为0 的一维数组;
	System.out.println(arr1[0][0]);// java.lang.NullPointerException
	
	System.out.println(arr.length);   //打印二维数组的长度,其实就是一一维数组的个数;
	System.out.println(arr[1].length);//打印二维数组中的角标为1 的一维数组的长度;
}

>> 面向对象

~ 面向过程与面向对象

1、面向过程思想:强调的是过程(动作), C语言-函数(对函数调用执行);打开冰箱—存储大象—关上冰箱

2、面向对象思想:强调的是对象(实体), C++、Java、C#; 冰箱打开—冰箱存储—冰箱关上

3、面向对象特点
(1)面相对象是一种常见的思想,符合人们的思想习惯;
(2)面向对象的出现,将复杂的问题简单化;
(3)面向对象的出现,让在过程中的执行者,变成了对象中的指挥者;

4、面向对象三个特征:封装、继承、多态;

~ 类与对象的关系

1、:是用Java这种语言对现实生活中的事物进行描述;用类的形式来体现的;

2、怎么描述呢:对于事物的描述通常只关注两个方面:属性、行为;只要明确该事物的属性和行为,并定义在类中即可;
定义类其实就是在定义类中的成员(成员变量<–>属性,成员函数<–>行为)

3、对象:其实就是该类事物实实在在存在的个体;(在Java中万物皆对象)

4、类与对象之间的关系:类是事物的描述;对象是该类事物的实例,在Java中通过new来创建;

~ 匿名对象

匿名对象:没有名字的对象,定义对象的简写格式: new Car();
new Car().run() 等价于 Car c = new Car(); c.run();

1,当对象对方法仅进行一次调用的时候,就可以简化成匿名对象;
2,匿名对象可以作为实际参数进行传递:show(new Car()); 等价于 Car c1 = new Car(); show(c1);

~ 面向对象特征1 - 封装

1、封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式:隐藏属性age,提供setAge() getAge() 两个方法;

2、好处:提高重用性和安全性、将变化隔离(里面变化与外界无关)、便于使用;

3、封装原则:将不需要对外提供的内容都隐藏起来:把属性都隐藏,提供公共方法对其访问;

4、最小的封装体函数—>类—>框架

~ 构造函数

1、构造函数:构建创造对象时调用的函数;创建对象都必须要通过构造函数初始化;

2、作用:对对象进行初始化;

3、格式定义

public class test {
	int i;
	 // 函数名称与类名相同;不用定义返回值类型;没有具体的返回值;
	public test(int i) {  this.i = i;  } 
}

4、特点
(1)函数名称与类名相同;
(2)不用定义返回值类型;
(3)没有具体的返回值;
(4)多个构造函数在类中是以重载的形式来体现的;
(5)一个类中若没有定义过构造函数,那么该类中会有一个默认的空参数构造函数;如果在类中定义了指定的构造函数,那么类中的默认构造函数就没有了;
(6)构造函数可以有多个,用于对不同的对象进行针对性的初始化;

5、一般函数和构造函数区别
构造函数:对象创建时,就会调用与之对应的构造函数,对对象进行初始化,调用只调用一次;
一般函数:对象创建后,需要函数功能时才调用,可以被调用多次;

6、什么时候定义构造函数:在描述事物时,该事物一存在就具备的一些内容,这些内容都定义在构造函数中;

7、细节
(1)构造函数如果完成了set功能,set方法是否需要? 需要
(2)一般函数不能直接调用构造函数;
(3)构造函数如果前面加了void就变成了一般函数;
(4)构造函数中是有return语句的,少见写return但是有;

~ this关键字

1、this : 代表当前对象;this就是所在函数所属对象的引用;
简单说:哪个对象调用了this所在的函数,this就代表哪个对象;

2、特点
(1)当成员变量和局部变量重名,可以用关键字this来区分:Person(String name){ this.name = name; },把局部变量的值赋给成员变量;
(2)this也可以用于在构造函数中调用其他构造函数,以提高代码重用性:
注意:只能定义在构造函数的第一行;因为初始化动作要先执行;

Person(String name){ this.name = name;  }
Person(String name, int age){
    this(name);     // 调用带有一个参数name的构造函数;
    this.age = age;
}

(3)在本类中调用本类的对象都用this;

3、this内存原理图解

(1)程序执行开始,主函数进栈;
(2)Person p:main里面创建一个变量p,堆里面创建一个新的对象,对应一个内存地址;对象中有两个属性:name默认值null,age默认值0;
(3)new Person("旺财"):创建一个Person对象,并往里面传值,所以要调用带参构造函数,所以Person(String name){}构造函数进栈;
(4)构造函数中默认有一个this指针,指向调用该构造函数的对象在内存中的地址:哪个对象调用了this所在的函数,this就代表哪个对象;
还有一个变量:该构造函数形参传入的局部变量,该局部变量的值在构造函数执行时,赋给构造函数所在类的成员变量;
(5)完成对象创建并初始化操作之后,构造函数弹栈;
(6)堆内存中创建的对象 地址赋给栈内存中的main中的p变量;
(7)执行代码下一句:p.speak();,speak()方法进栈;
(8)speak方法是对某个具体对象进行操作的方法,里面有一个this指针,哪个对象调用它,它就指向哪个对象;
(9)speak方法里面没有定义变量,里面的name/age变量都是对象的成员变量,所以方法里面的变量值,就是对象的变量的值;
(10)执行完speak方法之后,speak方法弹栈,继续执行下一句代码;

4、构造函数间调用

~ static关键字

1、static的特点:
(1)static是一个修饰符,用于修饰成员(成员变量/成员函数);
(2)static修饰的成员被所有的对象所共享;
(3)static优先于对象存在,因为static的成员随着类的加载就已经存在了;
(4)static修饰的成员多了一种调用方式:可以直接被类名所调用:类名.静态成员(对象可以用,类也可以用)
(5)static修饰的数据是共享数据,对象中的存储的是特有数据;

2、静态使用的注意事项:
(1)静态方法只能访问静态成员(方法+变量);非静态既可以访问静态,又可以访问非静态;(不new对象的时候,静态变量存在而非静态变量不存在,非静态变量随着对象的创建而创建)
(2)静态方法中不可以使用this或者super关键字;(没有对象,this没有代表谁)
(3)主函数是静态的;
(4)静态变量前面省略类名,成员变量前面省略this;Syso(Person.country+":"+this.name);

3、静态变量什么时候使用:
(1)当分析对象中所具备的成员变量的值都是相同的,这时这个成员就可以被静态修饰;(有一个值不同就不能是静态的)
(2)只要数据在对象中都是不同的,就是对象的特有数据,必须存储在对象中,是非静态的;
(3)如果是相同的数据,对象不需要做修改,只需要使用即可,不需要存储在对象中,定义成静态的;

4、静态方法什么时候使用:(方法/函数就是功能)
函数是否用静态修饰,就参考一点:该函数功能是否有访问到对象中的特有数据 (非静态数据):若访问特有数据就用非静态;

简单点说,从源代码看,该功能是否需要访问非静态的成员变量,如果需要,该功能就是非静态的;如果不需要,就可以将该功能定义成静态的;当然,也可以定义成非静态,但是非静态需要被对象调用,而仅创建对象调用非静态的没有访问特有数据的方法,该对象的创建是没有意义,只是占用堆内存;所以最好定义成静态的;

5、静态代码块:static{ System.out.println("我是静态代码块"); }
在java中使用static关键字声明的一段独立的代码区间,在JVM加载类时,随着类的加载而加载并执行,所以静态代码块先于主方法执行,而且只执行一次(因为只加载一次);

类里面全是静态成员时,不用创建对象,这时候需要静态代码块;

作用:用于给类的属性进行初始化;有些类不用创建对象,这时候不用调用构造函数,所以就没法对类进行初始化,这时候就用静态代码块;构造函数给对象进行初始化;

注意:(1)静态代码块不能存在于任何方法体内;(2) 静态代码块不能直接访问静态实例变量和实例方法,需要通过类的实例对象来访问;

class StaticCode {
	static int num;
	static {   // 静态代码块
		num = 10;
		num *= 3;
		System.out.println("hahahah");
	}
	StaticCode(){}  // 无参构造函数
	static void show() { System.out.println(num); }
}
class Person {
	private String name;
	{          // 构造对象的代码块,可以给所有对象进行初始化的;
		System.out.println("constructor code ");
	}
	static {   // 静态代码块
		System.out.println("static code");
	}
	Person() // 构造函数:给对应的对象进行针对性的初始化;
	{
		name = "baby";
		cry();
	}
	Person(String name) {
		this.name = name;
		cry();
	}
	public void cry(){ System.out.println("哇哇"); }
	public void speak(){System.out.println("name:"+name);}
	static void show(){ System.out.println("show run"); }
}
class test {
	// 静态代码块,随类的加载而加载并执行,只执行一次;
	static { System.out.println("a"); } 
	public static void main(String[] args) {
		// Person p = null;
		// p.speak();  // 没有创建对象,不能调用类的非静态方法,空指针异常;
		Person p1 = new Person();
		p1.speak();
		Person p2 = new Person("旺财");
		p2.speak();
		new Person();
		new StaticCode().show();
		new StaticCode().show();
		StaticCode.show();
		System.out.println("b");
	}
}
// ---------------------------------------------------------------------------------------------------------
a  // 执行开始,test类加载进内存,静态代码块随类的加载而加载进内存并执行;
static code  // 执行主函数第一句代码:Person p1,Person类加载进内存,
                同时加载并执行类中静态代码块,给类进行初始化;

constructor code  // p1:执行主函数第一句代码:new Person(),调用对应的
                         构造函数之前先执行构造代码块,给对象进行初始化;
哇哇    // 构造代码块执行完之后,继续执行构造函数的代码;
name:baby  // p1.speak()

constructor code     // p2
哇哇
name:旺财

constructor code     // new person(); 调用无参构造函数,开始执行第一句代码之前,
                        先执行构造代码块,然后继续执行构造函数的代码;
哇哇

hahahah      // new StaticCode().show(); 第一次使用该类,先加载进内存,
                同时加载并执行执行构静态代码块;
30     // 创建对象,调用构造函数,然后调用show方法;
30     // 创建对象,调用构造函数,然后调用show方法;
30     // 直接类名调用静态show方法;
b

~ 静态代码块、构造代码块、局部代码块区别

1、静态代码块:
(1)java类中使用static关键字声明的代码块:static{ ...... },不能在方法体内定义;
(2)在JVM加载类时,随着类的加载而加载并执行,所以静态代码块先于主方法执行,而且只执行一次(因为只加载一次);
(3)用于给类的属性进行初始化;

2、构造代码块:
(1)java类中定义的,没有使用static关键字进行声明的代码块;
(2)构造代码块会在构造函数被调用时执行,且优先于this()语句执行;(java编译器在编译时会先将构造代码块中的代码移到构造函数中执行,构造函数中原有的代码最后执行)
(3)用于给对象进行初始化;

3、局部代码块:
(1)在方法中定义的代码块;(不能使用static关键字进行声明)
(2)作用:在方法中,如果要缩短变量的寿命,可以使用;
方法中,某段代码之后,都不再使用某个变量(这个变量有可能是一个很大的Map集合,很占内存),则可以将其定义到局部代码块中,及时结束其生命周期,释放空间;

~ 成员变量和局部变量的区别

1、成员变量定义在类中,整个类中都可以访问;
局部变量定义在方法、语句、局部代码块中,只在所属的区域有效;

2、成员变量存在于堆内存的对象中;
局部变量存在于栈内存的方法中;

3、成员变量随着对象的创建而存在,随着对象的消失而消失;
局部变量随着所属区域的执行而存在,随着所属区域的结束而释放;

4、成员变量都有默认初始化值;
局部变量没有默认初始化值;

~ 成员变量(实例变量)和静态变量(类变量)的区别

1、两个变量的生命周期不同:
成员变量随着对象的创建而存在,随着对象的被回收而释放;
静态变量随着类的加载而存在,随着类的消失而消失;(虚拟机结束,类就结束了;类本身就是一个对象)
2、调用方式不同:
成员变量只能被对象调用;
静态变量可以被对象调用,还可以被类名调用;
3、别名不同:
成员变量也称为实例变量;
静态变量称为类变量;
4、数据存储位置不同:
成员变量数据存储在堆内存的对象中,所以也叫对象的特有数据;
静态变量数据存储在方法区(共享数据区)的静态区,所以也叫对象的共享数据;

~ 单例设计模式

设计模式:23种;是对问题行之有效的解决方式;其实它是一种解决问题的思想;

1、解决的问题: 单例,即单个实例, 可以保证一个类在内存中的对象的唯一性:一个类在内存中只能有一个实例对象;

2、什么时候需要单例: 对于多个程序,必须使用同一个配置信息对象时,就需要保证该对象的唯一性;

3、如何保证对象唯一性:
(1)不允许其他程序用new创建该类对象;
(2)在该类中创建一个本类实例;
(3)对外提供一个公共方法让其他程序可以获取该对象;

4、实现步骤:
(1)私有化该类构造函数;
(2)通过new在本类中创建一个本类对象;
(3)定义一个公有的方法,将创建的对象返回;

5、单例设计模式的两种表现方式:
(1)饿汉式: 类一加载,对象就已经存在了;(开发时用的较多)

class Single   
{   // s是引用类型的静态成员变量,定义成private ,可控
	private static Single s = new Single();
	private Single() {}
	public static Single getInstance() {
		return s;
	}
}

(2)懒汉式: 类加载进来,没有对象,只有调用了getInstance方法时,才会创建对象;是单例设计模式的延迟加载形式;
存在安全隐患:多线程调用时,不能保证对象唯一;

class Single2 {
	private static Single2 s = null;
	private Single2() {}
	public static Single2 getInstance() {
		if (s == null)  s = new Single2();
		return s;
	}
}

~ 面向对象特征2 - 继承

子类(基类)继承父类(超类)

1、继承的好处、弊端:
(1)提高了代码的复用性;
(2)让类与类之间产生了关系,给第三个特征多态提供了前提:没继承就没有多态;
(3)继承弊端:打破了封装性;

2、java中支持单继承,不直接支持多继承,但对C++中的多继承机制进行改良;
单继承:一个子类只能有一个直接父类;
多继承:一个子类可以有多个直接父类 (java中不允许,进行了改良)
不直接支持多继承,因为多个父类中有相同成员,会产生调用的不确定性;在java中是通过“多实现”的方式来体现;

3、java支持多层(多重)继承: C继承B,B继承A;就会出现继承体系;

4、当要使用一个继承体系时:
(1)查看该体系中的顶层类A,了解该体系的基本功能;
(2)创建体系中的最子类C对象,完成功能的使用;

5、什么时候定义继承:
当类与类之间存在着所属关系的时候,就定义继承;xx是yy中的一种 xx extends yy

6、子、父类中成员变量的特点:super
当本类的成员和局部变量同名用this区分;
当子、父类中的成员变量同名用super区分父类;

注意
①this和super的用法很相似:
this代表一个本类对象的引用,super代表一个父类空间,不代表父类对象,因为没创建父类对象就可使用父类成员;
②子类不能直接访问父类中private私有成员,需通过public方法间接访问;

7、子、父类中成员函数的特点:
当子、父类中出现成员函数一模一样的情况,会运行子类的函数;这种现象,称为 覆盖 操作;这是函数在子父类中的特性;

覆盖注意事项:
(1)子类方法覆盖父类方法时,子类权限必须要大于等于父类的权限:public>空;
父类是private,子类是public是,不能覆盖,因为private不能访问;
(2)静态只能覆盖静态,或被静态覆盖:父类方法是static,子类必须是static;
(3)什么时候使用覆盖操作:当对一个类进行子类的扩展时,子类需要保留父类的功能声明,但要定义子类中该功能的特有内容时,就使用覆盖操作完成;

8、子、父类中构造函数的特点:
(1)构造函数不能覆盖,因为没继承过来;
(2)在子类构造对象时,发现,访问子类构造函数时,父类也运行了;
原因是:在子类的构造函数中第一行有一个默认的隐式语句:super();
(3)子类的实例化过程:子类中所有的构造函数默认都会访问父类中空参数的构造函数;

9、为什么子类实例化的时候要访问父类中的构造函数:
(1)那是因为子类继承了父类,获取到了父类中内容(属性),所以在使用父类内容之前,要先看父类是如何对自己的内容进行初始化的;所以子类在构造对象时,必须访问父类中的构造函数;
(2)为了完成这个必须的动作,就在子类的构造函数中加入了super()语句;
(3)如果父类中没有定义空参数构造函数,那么子类的构造函数必须用super明确要调用父类中哪个构造函数;同时子类构造函数中如果使用this调用了本类构造函数时,那么super就没有了,因为super和this都只能定义在第一行所以只能有一个;
但是可以保证的是,子类中肯定会有其他的构造函数访问父类的构造函数;

注意:supre语句必须要定义在子类构造函数第一行;因为父类的初始化动作要先完成;

~ 一个对象实例化过程

Person p = new Person();
(1)JVM会读取指定的路径下的Person.class文件,并加载进内存,并会先加载Person的父类(如果有直接的父类的情况下);
(2)在堆内存中的开辟空间,分配地址;
(3)并在对象空间中,对对象中的属性进行默认初始化;
(4)调用对应的构造函数进行初始化;
(5)在构造函数中,第一行会先到调用父类中构造函数进行初始化;
(6)父类初始化完毕后,再对子类的属性进行显示初始化(执行成员变量的赋值操作);
(7)再进行子类构造函数的特定初始化;
(8)初始化完毕后,将地址值赋值给引用变量;

~ final(最终)关键字

1、final关键字特点:
(1)final是一个修饰符,可以修饰类,方法,变量;一般与static结合使用;
(2)final修饰的类不可以被继承(最终类);
(3)final修饰的方法不可以被覆盖(最终方法);
(4)final修饰的变量是一个常量(最终值),只能赋值一次,必须初始化;

2、为什么要用final修饰变量:
其实在程序如果一个数据是固定的,那么直接使用这个数据就可以了,但是这样阅读性差,所以它该数据起个名称,而且这个变量名称的值不能变化,所以加上final固定;

3、写法规范: 常量所有字母都大写,多个单词,中间用_连接;

~ 继承 - 抽象类

1、特点:
(1)方法只有声明没有实现(方法体)时,该方法就是抽象方法,需要被abstract修饰;
(2)抽象方法必须定义在抽象类中,该类必须也被abstract修饰;
(3)抽象类不可以被实例化:因为调用抽象方法没意义;
(4)抽象类必须有其子类覆盖了所有的抽象方法后,该子类才可以实例化,否则,这个子类还是抽象类;

2、注意:
(1)抽象类中有构造函数吗?
有,用于给子类对象进行初始化;
(2)抽象类可以不定义抽象方法吗?
可以的,但是很少见,目的就是不让该类创建对象; 通常这个类中的方法有方法体,但是却没有内容;
(3)抽象方法只有方法声明,没有方法体;

abstract class Demo
{    void show1()//抽象方法
	 void show2(){}  //方法体
}

3、抽象关键字不可以和那些关键字共存:
(1)private不行,因为抽象方法需被子类覆盖,private类型方法子类看不到,无法覆盖;
(2)static不行,静态成员不需要对象;
(3)final不行,final方法不能被覆盖,abstract必须被覆盖;

4、抽象类和一般类的异同点:
相同点:抽象类和一般类都是用来描述事物的,都在内部定了成员;
不同:
(1)一般类有足够的信息描述事物;抽象类描述事物的信息有可能不足;
(2)一般类中不能定义抽象方法,只能定非抽象方法;抽象类中可定义抽象方法,同时也可以定义非抽象方法;
(3)一般类可以被实例化; 抽象类不可以被实例化;

5、抽象类一定是个父类: 因为需要子类覆盖其方法后才可以对子类实例化;

~ 接口

1、接口: 当一个抽象类中的方法都是抽象的时候,这时可以将该抽象类用另一种形式定义和表示:就是接口 interface;接口里的方法都是抽象的;

2、定义接口使用的关键字不是class,是interface;

abstract class AbsDemo      // 抽象类:该类中的方法都是抽象的,可以定义成接口;
{   abstract void show1();
    abstract void show2();   
}
interface Demo    // 接口:使用interface关键字定义;
{     public static final int NUM = 4;
      public abstract void show1();
      public abstract void show2();
}
class DemoImpl  implements Demo  // 接口实现类:实现接口中所有的方法;
{    public void show1(){ 实现 }
     public void show2(){ 实现 }
}

3、接口中常见的成员 定义格式:都有固定的修饰符
(1)全局常量: public static final 常量类型 常量名;(不写public static final系统默认加上)
(2)抽象方法public abstract 返回值类型 方法名(参数列表);

4、接口注意事项:
(1)接口中的成员都是公共的权限;
(2)类与类之间是继承关系,类与接口之间是实现关系,接口之间是继承关系,而且可以多继承;
(3)接口不可以实例化,只能由实现了接口的子类并覆盖了接口中所有的抽象方法后,该子类才可以实例化,否则,这个子类就是一个抽象类;
(4)在java中不直接支持多继承,因为会出现调用的不确定性;所以java将多继承机制进行改良,在java中变成了多实现:一个类可以实现多个接口;

5、接口的特点:
(1)接口是对外暴露的规则;
(2)接口是程序的功能扩展;
(3)接口的出现降低耦合性;
(4)接口可以用来多实现;
(5)类与接口之间是实现关系,而且类可以继承一个类的同时实现多个接口;
(6)接口与接口之间可以有继承关系;
(7)接口的出现避免了单继承的局限性,因为一个类只能有一个父类;接口与接口之间是继承关系,而且接口可以多继承;

~ 抽象类和接口的异同点

相同点:都是不断向上抽取而来的;
不同点
(1)抽象类需要被继承,而且只能单继承;接口需要被实现,而且可以多实现;
(2)抽象类中可以定义抽象方法和非抽象方法,子类继承后,可以直接使用非抽象方法;接口中只能定义抽象方法,必须由子类去实现;
(3)抽象类的继承,是is a关系,在定义该体系的基本共性内容;接口的实现是 like a 关系,在定义体系额外功能;

~ 面向对象特征3 - 多态

多态:某一类事物的多种存在形态;简单说:就是一个对象对应着不同类型;

1、多态在代码中的体现: 父类或者接口的引用指向其子类的对象;动物 x = new 猫();

2、多态的好处: 提高了代码的扩展性,前期定义的代码可以使用后期的内容;

3、多态的弊端: 前期定义的内容不能使用(调用)后期子类的特有内容;

4、多态的前提: ①必须有关系:继承或实现; ②要有覆盖;

5、Animal a = new Cat();:自动类型提升(向上转型), 将子类型隐藏;就不用使用子类的特有方法;作用:限制对特有功能的访问;
猫对象提升了动物类型,但是猫的特有功能无法访问;

如果还想用具体动物猫的特有功能, 可以将该对象进行向下转型:Cat c = (Cat)a;向下转型目的 是为了使用子类中的特有方法;

注意:对于转型,自始自终都是子类对象在做着类型的变化;

6、instanceof: 用于判断对象的具体类型;只能用于 引用数据类型 判断,通常在向下转型前用于健壮性的判断;

abstract class Animal{ abstract void eat(); }
class Dog extends Animal {
	void eat(){ System.out.println("啃骨头"); }
	void lookHome(){ System.out.println("看家"); }
}
class Cat extends Animal {
	void eat(){ System.out.println("吃鱼"); }
	void catchMouse(){ System.out.println("抓老鼠"); }
}
class test {
	public static void main(String[] args) {
		Animal a = new Cat(); // 父类的引用指向子类对象;
		a.eat();
		// a.catchMouse();  // 不能使用子类的特有方法;
		Cat c1 = (Cat)a; 
		c1.eat();
		c1.catchMouse();
		// Animal a1 = new Animal(); // 错,接口不能创建实例对象;
		// Animal a2 = new Dog();    // 错,a2指向的是狗类对象,不能向下转型为猫类对象;
		// Cat c1 = (Cat)a2;         // ClassCastException类型转换异常
	}
	public static void method(Animal a) // Animal a = new Dog();
	{   a.eat();
		if (a instanceof Cat) {
			Cat c = (Cat) a;
			c.catchMouse();
		} else if (a instanceof Dog) {
			Dog d = (Dog) a;
			d.lookHome();
		}
		// else{ ;;;;;}
	}
}

7、多态时,成员变量的特点:
(1)编译时:参考引用型变量所属的类中的是否有调用的成员变量,有,编译通过,没有,编译失败;
(2)运行时:参考引用型变量所属的类中的是否有调用的成员变量,并运行该所属类中的成员变量;
(3)简单说:编译和运行都参考等号的左边;

8、多态时,成员函数的特点:
成员函数(非静态函数):动态绑定
(1)编译时:参考引用型变量所属的类中的是否有调用的函数;有,编译通过,没有,编译失败;
(2)运行时:参考的是对象所属的类中是否有调用的函数;
(3)简单说:编译看左边,运行看右边,因为成员函数存在覆盖特性;

9、多态时,静态函数的特点:
静态函数:静态绑定
(1)编译时:参考引用型变量所属的类中的是否有调用的静态方法;
(2)运行时:参考引用型变量所属的类中的是否有调用的静态方法;
(3)简单说,编译和运行都看左边;
其实对于静态方法,是不需要对象的,所以不涉及多态性,直接用类名调用即可;

class Fu {
	int num = 3;
	void show(){ System.out.println("fu show"); }
	static void method() {
		System.out.println("fu static method");
	}
}
class Zi extends Fu {
	int num = 4;
	void show(){ System.out.println("zi show"); }
	static void method(){ System.out.println("zi static method"); }
}
class DuoTaiDemo3 {
	public static void main(String[] args) {
		Fu f = new Zi(); // 3   输出父类的成员变量:成员变量编译、运行都看等号左边;
		Zi z = new Zi(); // 4
		System.out.println(z.num); 
		Fu f1 = new Zi();
		f.show();        // 输出子类方法:成员方法运行看右边
		Fu f2 = new Zi();
		f.method();      // 输出父类方法:静态方法运行看左边
		Fu.method();
		Zi.method();     // 静态方法不涉及多态,因为不需要创建对象,直接通过类名调用
	}
}

~ 内部类

1、内部类: 将一个类定义在另一个类的里面,里面那个类称为内部类(内置类,嵌套类);

2、内部类访问特点:
(1)内部类可以直接访问外部类中的成员;
(2)外部类要访问内部类,必须建立内部类的对象;

3、内部类的用处:
一般用于类的设计;分析事物时,发现该事物描述中还有事物,而且这个事物还在访问被描述事物的内容;这时就把还有的事物定义成内部类来描述;

class Outer{
	private static int num = 31;
	static class Inner{
		void show(){ System.out.println("show run..."+num); }
		// 如果内部类中定义了静态成员,该内部类也必须是静态的;
		static void function(){ System.out.println("function run ...."+num); }
	}
	public void method(){
		Inner in = new Inner();
		in.show();   // 外部类要访问内部类,必须建立内部类的对象
	}
}
class DuoTaiDemo3 {
	public static void main(String[] args) {
		Outer out = new Outer();
		out.method();
		// 直接访问外部类中的内部类中的成员;
		Outer.Inner in1 = new Outer().new Inner();
		in1.show();
		// 如果内部类是静态的, 相当于一个外部类;
		Outer.Inner in2 = new Outer.Inner();
		in2.show();
		// 如果内部类是静态的,成员是静态的;
		Outer.Inner.function();

	}
}

4、为什么内部类能直接访问外部类中成员: 因为内部类持有了外部类的引用:外部类名.this

class Outer{
	int num = 3;
	class Inner{
		int num = 4;
		void show(){
			int num = 5;
			System.out.println(num);            // 5
			System.out.println(this.num);       // 4
			System.out.println(Inner.this.num); // 4
			System.out.println(Outer.this.num); // 3
		}
	}
	void method(){ new Inner().show(); }
}
class test {
	public static void main(String[] args) {
		new Outer().method();
	}
}

5、内部类可以存放在局部位置上: 内部类在局部位置上只能访问局部中被final修饰的局部变量;

class Outer{
    int num = 3;
	Object method(final int y){
		final int x = 9;
		class Inner{
			public String toString(){ return "show..." + x + y; }
		}
		Object in = new Inner();
		return in;
	}
}

6、匿名内部类: 就是内部类的简写格式,其实就是一个匿名子类对象;
格式new 父类or接口(){子类内容}
必须有前提:内部类必须继承或者实现一个外部类或者接口;

abstract class Demo{ abstract void show(); }  // 抽象类
class Outer{
	int num = 3;
	public void method(){
		new Demo(){     // 匿名内部类
			void show(){ Syso("show.." + num); }
		}.show();
	}
}
main: new Outer().method();

7、匿名内部类 通常的使用场景之一: 当函数参数是接口类型时,而且接口中的方法不超过三个(不包括3),可以用匿名内部类作为实际参数进行传递;

interface A // 接口
{   void show1();
	void show2();
}
class Outer {
	public void method()   
	{   A in = new A()  // 创建一个匿名内部类对象,赋值给接口Inter类型的引用
		{   public void show1(){}
			public void show2(){}
		};
		in.show1();  // 通过A类型的引用名调用匿名内部类的方法
		in.show2();
	}
}
class test {
	class Inner {}
	public static void main(String[] args) {
		show(new A(){ public void show1(){}
					  public void show2(){}
					});
		// new Inner();// 编译失败,因为主函数是static型,Inner不是
	}
	public void method(){new Inner();}   // 编译成功
	public static void show(A in) // 接收接口型的参数,赋值给in
	{   in.show1();
		in.show2();
	}
}

8、匿名内部类注意问题: 匿名内部类的子类对象被向上转型成其他类 类型,这样就不能再使用子类特有的方法了;

~ 异常

1、异常: 是在运行时期发生的不正常情况;
异常类:描述不正常的情况的类,需要继承异常体系;

(1)在java中用类的形式对不正常情况进行了描述和封装对象;
(2)其实异常就是java通过面向对象的思想将问题封装成了对象,用异常类对其进行描述;
(3)不同的问题用不同的类进行具体的描述, 比如角标越界、空指针等;问题很多,意味着描述的类也很多;将其共性进行向上抽取,形成了异常体系;
(4)异常体系的特点:Throwable及其所有的子类都具有可抛性;子类的后缀名都是用其父类名作为后缀,阅读性很强;
(5)可抛性:是通过两个关键字来体现的:throwsthrow;凡是可以被这两个关键字所操作的类和对象都具备可抛性;

2、异常体系: 以前正常流程代码和问题处理代码相结合,现在将正常流程代码和问题处理代码分离,提高阅读性;所以最终问题(不正常情况)就分成了两大类:ErrorException ,都继承Throwable

(1)一般不可处理的:Error
特点:是由jvm抛出的严重性的问题;这种问题发生一般不针对性处理,因为处理不了,需要直接修改程序;
(2)可以处理的:Exception

3、Throwable: 无论是error,还是异常,都是问题,问题发生就应该可以抛出让调用者知道并处理;

**4、自定义异常:**自定义的问题描述;
对于角标是整数不存在,可以用角标越界表示;对于负数为角标的情况,准备用负数角标异常来表示;负数角标这种异常在java中并没有定义过,那就按照java异常的创建思想:面向对象,将负数角标进行自定义描述,并封装成对象;这种自定义的问题描述成为自定义异常;

注意:如果让一个类称为异常类,必须要继承异常体系,因为只有称为异常体系的子类才有资格具备可抛性,才可以被两个关键字所操作:throws、throw;

自定义异常时,要么继承Exception:在函数声明处throws抛出;要么继承RuntimeException;

5、编译时检测异常和运行时异常的区别:
(1)编译时被检测异常: Exception和其子类都是,除了特殊子类RuntimeException体系;

这种问题一旦出现,希望在编译时就进行检测,让这种问题有对应的处理方式;这样的问题都可以针对性的处理;

编译型检测异常需要在函数声明处声明throws,运行时异常不需要throws;

(2)运行时异常 (编译时不检测): 就是Exception中的RuntimeException和其子类;

这种问题的发生,无法让功能继续,运算无法进行,更多是因为调用者的原因
导致的,或者引发了内部状态的改变导致的;
那么这种问题一般不处理,直接编译通过,在运行时,让调用者调用时的程序
强制停止,让调用者对代码进行修正;

6、throw和throws的区别:
(1)throws使用在函数上,throw使用在函数内;
(2)throws抛出的是异常类,可以抛出多个,用逗号隔开;throw抛出的是异常对象;

7、异常 - 原理 & 异常对象的抛出 throw:

class FuShuIndexException extends Exception{  // 自定义异常类
	FuShuIndexException(){}  // 无参构造函数;
	FuShuIndexException(String msg){super(msg);} // 有参构造函数,直接调用父类的方法;
}
class Demo {
	public int method(int[] arr, int index) throws FuShuIndexException {
		                      // 自定义异常继承Exception时需要在函数声明处throws抛出;
		if (arr == null)  // 这个对象中会包含着问题的名称,信息,位置等信息,将其反馈给调用者;
			throw new NullPointerException("数组的引用不能为空!");
		if (index >= arr.length)  
			throw new ArrayIndexOutOfBoundsException("数组的角标越界:" + index);
		if (index < 0)  
			throw new FuShuIndexException("数组的角标不能为负:" + index);
		return arr[index];
	}
}
class test {
	public static void main(String[] args) throws FuShuIndexException {
		int[] arr = new int[3];
		Demo d = new Demo();
		int num = d.method(null, -30);
		System.out.println("num=" + num);
		System.out.println("over");
	}
}

8、异常捕捉 try - catch - finally
(1)异常处理的捕捉形式: 这是可以对异常进行针对性处理的方式;
(2)具体格式是:

try { 需要被检测异常的代码 }
catch(异常类 变量){ 处理异常的代码}  //该变量用于接收发生的异常对象
finally{  一定会被执行的代码,通常用于关闭(释放)资源  }

(3)异常处理的原则:
① 函数内部如果抛出需要检测的异常,那么函数上必须要声明,否则必须在函数内用try-catch捕捉,否则编译失败;
② 如果调用到了声明异常的函数,要么try-catch,要么throws,否则编译失败;
③ 什么时候catch,什么时候throws 呢:功能内部可以解决,用try-catch,解决不了,用throws告诉调用者,由调用者解决;
④ 一个功能若抛出多个异常,则调用时,必须有对应多个catch进行针对性的处理; 内部有几个需要检测的异常,就抛几个异常,抛出几个,就catch几个;有多catch时父类的catch放在最下面;

(4)try - catch - finally 代码块组合特点:
try - catch - finally
try - catch(多个catch):当没有必要资源需要释放时,可以不用定义finally;
try - finally :异常无法直接catch处理,但是资源需要关闭;

9、异常的应用:异常的转换、封装 ;
异常的转换: 接收的异常 与抛出的异常 不一样;
异常的封装: 内部异常进行处理的外部异常转换;不该暴露出去的问题没有必要暴露出去,因为暴露出去对方也处理不了;

class MaoYanException extends Exception{ // 自定义异常类:蓝屏异常;
	MaoYanException(String msg){ super(msg); } //构造函数,调用父类的方法,输出信息;
}
class LanPingException extends Exception{ // 自定义异常类:冒烟异常
	LanPingException(String msg){ super(msg); }
}
class NoPlanException extends Exception{ // 自定义异常类:计划无法完成
	NoPlanException(String msg){ super(msg); }
}
class Computer{
	private int state = 2;    
	public void run() throws LanPingException, MaoYanException{ //抛出自定义异常类
		if(state==1) throw new LanPingException("电脑蓝屏!");
		if(state==2) throw new MaoYanException("电脑冒烟!");
		System.out.println("电脑正常运行");
	}
	public void reset(){  // 方法:电脑重启
		state = 0;
		System.out.println("电脑重启!");
	}
}
class Person{
	private String name;
	private Computer comp; // 创建电脑类引用
	Person(String name){   // 构造函数
		this.name = name;
		comp = new Computer(); // 创建电脑对象
	}
	public void test(){ System.out.println("大家自习!"); }
	public void teach() throws NoPlanException{
		try{
			comp.run();
			System.out.println(name + "讲课!");
		}catch (LanPingException e) {
			System.out.println(e.toString());
			comp.reset();
			teach();
		}catch (MaoYanException e) {
			System.out.println(e.toString());
			test();
			//throw e;  // 可以直接抛出catch的异常, 可以对电脑进行维修;
			// 异常转换:接收的异常与抛出的异常不一样
			throw new NoPlanException("课时进度无法完成," + e.toString());
		}
	}
}
class test {
	public static void main(String[] args) {
		Person p = new Person("毕老师");
		try {
			p.teach();
		} catch (NoPlanException e) {
			System.out.println(e.toString() + "......");
			System.out.println("换人");
		}
	}
}
> MaoYanException: 电脑冒烟!
> 大家自习!
> NoPlanException: 课时进度无法完成,MaoYanException: 电脑冒烟!......
> 换人

10、异常的注意事项:
(1)子类在覆盖父类方法时,父类的方法如果抛出了异常,那么子类的方法只能抛出父类的异常或者该异常的子类;
(2)如果父类抛出多个异常,那么子类只能抛出父类异常的子集;

简单说:子类覆盖父类,只能抛出父类的异常,或者父类异常的子类,或者子集; 子类问题在父类问题范围内,不能抛出父类以外的异常;

注意:如果父类的方法没有抛出异常,那么子类覆盖时绝对不能抛,只能捕捉try;

class A extends Exception{}
class B extends A{}
class C extends Exception{}
class Fu{
	public void show() throws A {}
}
class Zi extends Fu {
	public void show() throws C {} // 不抛,或抛出A或抛出B, 抛出C不行;
}
class T {
	void method(Fu f)  // Fu f = new Zi();
	{   try { f.show(); }  
	    catch (A a) { }
	}
}
class test {
	public static void main(String[] args) {
		T t = new T();
	    t.method(new Zi());
	}
}

~ Object类

1、Object: Java中所有类的根类,默认继承;Object是不断抽取而来,具备着所有对象都具备的共性内容;

2、常用的共性功能:
public boolean equals(Object obj) :比较对象的地址,用时重写此方法;
public int hashCode():重写equal时,同时重写hashCode;
getClass:返回object的运行时类,当前对象所属的字节码文件.class
toString()

class Person{
	private int age;
	Person(int age){ this.age = age; }
	// public boolean compare(Person p){ return this.age==p.age;}
	// 一般不用重新定义此方法,直接重写父类Object的equal方法,根据对象的特有内容,建立判断对象是否相同的依据;
	@Override
	public boolean equals(Object obj) { // 传入的参数类型是Person,向上转型为Object型;
		if(!(obj instanceof Person)){ // 如果传入的对象 不是Person类向上转型的,
			// return false;             // 返回false, 或抛出异常;
			throw new ClassCastException("类型错误!");  
		} // 注意:此处不能抛出编译时异常,只能抛出运行时异常,因为重写的是父类的方法,父类没有抛异常;
		Person p = (Person)obj;   //向下转型,获取子类的特有成员;
		return this.age == p.age;
	}
	public int hashCode(){ return age;}
	public String toString(){ return "Person: " + age; }
}
class test {
	public static void main(String[] args) {
		Person p1 = new Person(10);
		Person p2 = new Person(20);
		System.out.println(p1.equals(p2)); // false
		System.out.println(p1 == p2); // false
		Person p3 = p1;      // 输出true,因为地址值一样
		System.out.println(Integer.toHexString(p1.hashCode())); // a
		Class c1 = p1.getClass();
		Class c2 = p2.getClass();
		System.out.println(c1 == c2);  // true
		System.out.println(c1.getName()); // Person
		System.out.println(p1.toString()); // Person: 10
		System.out.println(p1.getClass().getName() + "$" + Integer.toHexString(p1.hashCode()));
	}
}

~ 包 package

1、包的声明: package mypack; //包名都是小写字母;

2、包导入import: 为了简化类名的书写;
import packa.DemoA; //导入了packa包中的DemoA类
import packa.DemoAA;
import packa.*; //导入了packa包中所有的类
import packa.abc.*; //import只能导入类,包里面的子包不能导入

3、导包的原则: 用到哪个类,就导入哪个类;需要20个类,就导入20个;

4、不同包中类与类之间的访问:
(1)protected修饰的方法,只有不同包中的子类能调用;只有继承父类,才能访问父类的protected方法;
(2)包与包之间的类进行访问,被访问的包中的类必须是public的,被访问的包中的类的方法也必须是public的;

package packb;
public class DemoB {
	protected void method(){ System.out.println("DemoB method"); }
}
package packa;  // packa包继承packb包
public class DemoA extends packb.DemoB {
	public void show() // packa继承了packb,所以能访问packb中的protected方法method()
	{   method();
		System.out.println("DemoA show ");
	}
}
package mypack;
public class PackageDemo {
	public static void main(String[] args) {
		packa.DemoA a = new packa.DemoA(); // 创建packa包里面DemoA的对象
		a.show();   // 不同包中的方法,可以直接调用其他包中public方法;
		packb.DemoB b = new packb.DemoB();
		// packb包里面的method方法定义成了protect型,此包没继承packb包,所以不能直接访问method
		// b.method();
	}
}

~ 4种权限

private:私有,是一个权限修饰符;用于修饰成员;私有的内容只在本类中有效;
注意:私有仅仅是封装的一种体现而已;


>> 多线程

~ 多线程概述

1、进程: 正在进行中的程序(直译); 在内存中开辟空间; eg:QQ、微信;

2、线程: 就是进程中一个负责程序执行的控制单元(执行路径);

3、多线程: 一个进程中可以多执行路径,称之为多线程;其实应用程序的执行都是cpu在做着快速的切换完成的,这个切换是随机的;

4、多线程特点: 并发、随机;

5、注意:
(1)一个进程中至少要有一个线程;
(2)开启多个线程是为了同时运行多部分代码;
(3)每一个线程都有自己运行的内容,这个内容可以称为线程要执行的任务;

6、多线程好处: 解决了多部分 同时(并发)运行的问题;

7、多线程的弊端: 线程太多回到效率的降低;

8、JVM启动时就启动了多个线程,至少有两个线程可以分析的出来:
①执行main函数的线程 - 主线程,该线程的任务代码都定义在main函数中;
②负责垃圾回收的线程:回收垃圾;

~ 线程的四种状态

~ 多线程 - wait和sleep的区别

(1)wait可以指定时间也可以不指定,sleep必须指定时间;
(2)在同步中时,对cpu的执行权和锁的处理不同:
wait:释放执行权,释放锁;
sleep:释放执行权,不释放锁;
一个同步里面可以有多个活线程,但只有一个能运行:拿到锁的;

~ 多线程的创建方法一:继承Thread类

1、步骤:
(1)定义一个类继承Thread类;
(2)覆盖Thread类中的run方法;
(3)直接创建Thread的子类 创建线程对象;
(4)调用 start 方法开启线程并调用线程的任务run方法执行;

2、注意: 可以通过Thread的getName获取线程的名称、Thread-编号(从0开始),主线程的名字就是main;

3、创建线程的目的: 是为了开启一条执行路径,去运行指定的代码和其他代码实现同时运行;而运行的指定代码就是这个执行路径的任务;

4、jvm创建的主线程的任务都定义在了主函数中;而自定义的线程它的任务定义在run方法中;

(1)Thread类用于描述线程,线程是需要任务的,所以Thread类也是对任务的描述;这个任务就通过Thread类中的run方法来体现;也就是说,run方法就是封装自定义线程运行任务的函数;

(2)run方法中定义的就是线程要运行的任务代码;

(3)开启线程是为了运行指定代码,所以只有继承Thread类,并复写run方法,将运行的代码定义在run方法中即可;

5、代码:

class Demo extends Thread {  //定义一个类,继承Thread类
	private String name;
	Demo(String name) { // 构造函数
		super(name);    // 调用父类的方法,传值name
		this.name = name;
	}
	@Override
	public void run() {  // 重写父类run方法
		for (int i = 0; i < 10; i++) {                   // 获取当前线程名称
			System.out.println(name+"...i="+i+"...name="+Thread.currentThread().getName());
		}
	}
}
class test {
	public static void main(String[] args) {
		Thread t1 = new Thread();
		Demo d1 = new Demo("wangcai");   // 创建一个线程
		Demo d2 = new Demo("xiaoqiang");
		d1.start();  // 开启线程,调用run方法
		d2.start();  // 调用run和调用start有什么区别?调用run和普通没有创建线程一样;
		System.out.println("Over..." + Thread.currentThread().getName());
	}
}

6、多线程运行图:

~ 多线程的创建方法二:实现Runnable接口 - 常用

1、步骤:
(1)定义类实现Runnable接口;
(2)覆盖接口中的run方法,将线程的任务代码封装到run方法中;
(3)通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递;
为什么?因为线程的任务都封装在Runnable接口 子类对象的run方法中,所以要在线程对象创建时就必须明确要运行的任务;
(4)调用线程对象的start方法开启线程;

ThreadImpl i = new ThreadImpl();  //Runnable接口的子类对象i
Thread t = new Thread(i);         // 通过Thread类创建线程对象
t.start();                        //调用线程对象的start方法开启线程

2、实现Runnable接口的好处: (Thread继承Runnable)
(1)将线程的任务从线程的子类中分离出来,进行了单独的封装;
按照面向对象的思想将任务的封装成对象;
(2)避免了java单继承的局限性;(有父类就不能再继承Thread类了)
(3)Runnable的出现仅仅是将线程的任务进行了对象的封装;

3、Thread中有run,传入的d 中也有run,为什么调用的是d.run?
因为Thread类中有一个Runnable接口类型的成员变量r,public void run(){ if(r!=null) r.run(); }

4、代码:

// 准备扩展Demo类的功能,让其中的内容可以作为线程的任务执行,通过实现接口的形式完成;
class Demo implements Runnable{
	@Override  // 实现Runnable接口的run方法;
	public void run(){ show(); }
	public void show(){
		for(int i=0; i<10; i++) 
			System.out.println(Thread.currentThread().getName() + "....." + i);
	}
}
class test {
	public static void main(String[] args) {
		Demo d = new Demo();       // 创建接口的子类Demo 的对象d
		Thread t1 = new Thread(d); // 是线程对象,传入的参数是实现Runnable接口的子类 对象;
		Thread t2 = new Thread(d);
		t1.start();
		t2.start();
		// Demo d1 = new Demo();  //不是线程对象
		// Demo d2 = new Demo();
		// d1.start();            //开启不了,编译失败
		// d2.start();
	}
}

~ 线程安全问题 - synchronized

1、线程安全问题产生的原因:
(1)多个线程在操作共享的数据;
(2)操作共享数据的线程代码有多条;
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,
就会导致线程安全问题的产生;
if(num>0){System.out.println(num);} 是两条代码:if判断+println输出;

2、解决思路: 就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程时不可以参与运算的;必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算;

3、同步代码块 / 同步函数: 在java中,用同步代码块就可以解决这个问题;

4、同步的好处: 解决了线程的安全问题;

5、同步的弊端: 相对降低了效率,因为同步外的线程的都会判断同步锁;

6、同步的前提: 同步中必须有多个线程并使用同一个同步锁;

7、通过继承Thread类实现的多线程,创建的是每个线程类的实例,每个线程操作一个线程实例;通过实现Runnable接口的方式实现的多线程,创建一个实例对象和多个线程对象,将此实例对象作为参数传递给Thread类的构造函数,每个线程都操作同一个实例对象;

~ 同步代码块、同步函数、静态同步函数

1、格式:
(1)同步代码块: synchronized(对象) { 需要被同步的代码 ;}
(2)同步函数: public synchronized void add(int num){ 代码 }
(3)静态同步函数: public static synchronized void show()

2、不使用同步代码块,会出线程安全问题:

3、同步代码块 - 代码:

class Ticket implements Runnable{
	private int num = 100;
	Object obj = new Object();  // 创建对象作为Synchronized的参数 - 同步锁
	@Override
	public void run() {
		while(true){
			synchronized (obj) {
				if(num>0) System.out.println(Thread.currentThread().getName()+"...sale..."+num--);
			}
		}
	}
}
class test {
	public static void main(String[] args) {
		Ticket t = new Ticket();
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		t1.start();
		t2.start();
	}
}

3、同步函数 - 代码块:

// 需求: 储户,两个,每个都到银行存钱每次存100,共存三次;
class Bank{
	private int sum;
	Object obj = new Object();
	public synchronized void add(int num){ // 同步函数
		sum = sum + num;
		System.out.println("sum = " + sum);
	}
}
class Cus implements Runnable{
	private Bank b = new Bank();
	public void run() {
		for(int i=0; i<3; i++){ b.add(100); }
	}
}
class test {  public static void main(String[] args) {}   }

4、静态 / 同步函数和同步代码块的区别:
(1)同步函数的锁是固定的this;
(2)同步代码块的锁是任意的对象;(可以使用静态同步函数的锁)
(3)静态的同步函数使用的锁是:该函数所属字节码文件对象;
可以用 getClass方法获取,也可以用当前类名.class 表示:Ticket.classthis.getClass()
(4)同步函数可以看成是同步代码块的简写形式;
当同步代码块的锁是this时,可以简写成同步函数;
(5)建议使用同步代码块
如果一个类中只需要一个锁,建议使用同步函数,使用this作为同步锁,写法简单;
如果一个类中有多个锁,或者多个类中使用同一个锁,那么只能使用同步代码块;

~ 多线程下的单例模式

// 饿汉式:操作共享数据的代码只有一条,没有线程安全问题;
class Single{ 
	private static final Single s = new Single(); // 2、在本类中创建本类对象
	private Single(){}    // 1、私有化构造函数,只能在本类中创建对象并初始化;
	public static Single getInstance(){ return s; }// 3、共有方法返回实例对象;
}
// 懒汉式:getInstance方法中,操作的是共享数据,且有多条代码;
class Single{
	private static Single s = null;
	private Single(){}
	// 使用同步函数 的方式:每次调用该方法,都要先判断同步锁,效率低;
//	public static synchronized Single getInstance(){
//		if(s == null) s = new Single();
//		return s;
//	}
	public static Single getInstance(){
		if(s == null){   // 加入同步为了解决多线程安全问题,加入双重判断是为了解决效率问题;
			synchronized (Single.class) { // 静态同步函数,没有this,只能通过类名获取class文件对象;
				if(s==null) s=new Single();
			}
		}
		return s;
	}
}

~ 死锁代码

死锁常见情景之一 - 同步的嵌套: 同步函数里面嵌套同步代码块:同步代码块的锁是obj,同步函数的锁是this;

public synchronized void show(){
		synchronized (obj) {
			 if(num>0)Syso(Thread.currentThread().getName()+".....sale...."+num--);
		}
	}

创建两个线程任务类的实例对象,再创建两个线程对象,分别执行两个线程任务,线程1执行if流程,线程2执行else流程;
当线程1拿到locka锁之后,等待lockb锁,若此时线程2拿到了lockb锁,等待locka锁,就会发生死锁:互相等待对方持有的锁;

class Deadlock implements Runnable{
	private boolean flag;
	public Deadlock(boolean flag){ this.flag=flag;}
	public void run() {
		if(flag){
			synchronized (MyLock.locka) {
				System.out.println(Thread.currentThread().getName()+"...if..locka");
				synchronized (MyLock.lockb) {
					System.out.println(Thread.currentThread().getName()+"...if..lockb");
				}
			}
		}else {
			synchronized (MyLock.lockb) {
				System.out.println(Thread.currentThread().getName()+"...else..lockb");
				synchronized (MyLock.locka) {
					System.out.println(Thread.currentThread().getName()+"...else..locka");
				}
			}
		}
	}
}
class MyLock{ // 直接使用创建好的对象作为同步锁;
	public static final Object locka= new Object();
	public static final Object lockb= new Object();
}
class test {
	public static void main(String[] args) {
		Deadlock d1 = new Deadlock(true);
		Deadlock d2 = new Deadlock(false);
		Thread t1 = new Thread(d1);
		Thread t2 = new Thread(d2);
		t1.start();
		t2.start();
	}
}

~ 线程间通信 - 等待唤醒机制

线程间通讯: 多个线程在处理同一资源,但是任务却不同;要在同一个锁上的线程之间才能进行通信,不同的锁上的线程之间操作互补干扰;

1、涉及的方法:
(1)wait():让线程处于冻结状态,被wait的线程会被存储到线程池中;
(2)notify():唤醒线程池中一个线程(任意).
(3)notifyAll():唤醒线程池中的所有线程;

2、注意: 这些方法都必须定义在同步中,因为这些方法是用于操作线程状态的方法,是监视线程状态的,线程状态发生改变时,需要明确改变的是哪一个锁上的线程;锁的特点是对多个线程进行同步;所以使用wait/notify方法时需要明确操作的是哪个锁上的线程;

一个类中能写的同步有很多,也能有多个锁;a锁的notify方法不能唤醒b锁的wait后的线程;

3、为什么操作线程的方法wait、notify、notifyAll定义在了Object类中?
因为这些方法是监视器的方法;监视器其实就是锁,锁是对象;
锁可以是任意的对象,任意的对象调用的方式一定定义在Object类中;

4、代码: 单生产者、单消费者问题;

class Resource{  // 资源类
	String name;
	String sex;
	boolean flag = false; // 加入判断,资源内有东西时,不input
}
class Input implements Runnable{
	Resource r;  // 创建resource对象,在初始化时接收参数用;
	// Object obj = new Object();  // 输入输出都创建对象作为同步锁,锁不一样,同步失败;
	// 构造函数,创建对象时初始化,形参是主函数创建的Resource对象,这个对象只有一个,传入 Input、Output函数;
	// 这时输入输出函数这两个线程操作的是同一个资源;
	// 以构造函数接收的r作为锁,输入输出两个线程的锁是同一个,同步成功;
	Input(Resource r){ this.r = r;}  
	public void run() {
		int x = 0;
		while(true){
			synchronized (r) {
				if(r.flag)  // true表示r对象有内容,不进行输入操作,直接进入wait状态;
					try { r.wait(); } // 只能捕捉,不能抛出;因为run方法没声明异常;
				 					  // 如果父类的方法没有抛出异常,那么子类覆盖时绝对不能抛,只能捕捉try
				    catch (InterruptedException e) { e.printStackTrace(); }
				if(x==0){ r.name="mike"; r.sex="男"; }
				else { r.name="丽丽"; r.sex="女"; }
				r.flag = true; // 操作完成之后,将状态改为true,下次input再获得执行权时直接进入等待状态;
				r.notify();    // 唤醒线程池中的线程;(唤醒对方)
			}
			x=(x+1)%2;
		}
	}
}
class Output implements Runnable{
	Resource r;
	public Output(Resource r){ this.r=r; }
	public void run() {
		while(true){
			synchronized (r) {   // r或者Resource.class作为参数传入;
				if(!r.flag) 
					try { r.wait(); } 
			    	catch (InterruptedException e) { e.printStackTrace(); }
				System.out.println(r.name+"....."+r.sex);
				r.flag = false;
				r.notify();
			}
		}
	}
}
class test {
	public static void main(String[] args) {
		Resource r = new Resource();  // 创建资源
		Input in = new Input(r); // 创建任务,将资源传入任务,使得两个任务操作的是同一个资源
		Output out = new Output(r);
		Thread t1 = new Thread(in);  // 创建线程,执行路径
		Thread t2 = new Thread(out); // 两个线程操作的是同一个锁; 
		t1.start();  // 开启线程
		t2.start();
	}
}

5、代码优化:

class Resource{  // 资源类
	String name;
	String sex;
	boolean flag = false; // 加入判断,资源内有东西时,不input
	public synchronized void set(String name, String sex){
		if(flag) try{ this.wait(); }catch (InterruptedException e){e.printStackTrace();}
		this.name = name;
		this.sex = sex;
		flag = true;
		this.notify();
	}
	public synchronized void get(){
		if(!flag) try{ this.wait(); }catch (InterruptedException e){e.printStackTrace();}
		System.out.println(name+"....."+sex);
		flag = false;
		this.notify();
	}
}
class Input implements Runnable{
	Resource r;  
	Input(Resource r){ this.r = r;}  
	public void run() {
		int x = 0;
		while(true){
			if(x==0) r.set("mike", "男");
			else r.set("丽丽", "女");
			x=(x+1)%2;
		}
	}
}
class Output implements Runnable{
	Resource r;
	public Output(Resource r){ this.r=r; }
	public void run() {
		while(true){ r.get(); }
	}
}

~ 线程间通信 - 多生产者、多消费者问题

1、程序问题: 上面的代码属于 “单生产者单消费者” 问题,用于多生产者多消费者情况会出现错误:生产者生产产品1后,可能继续生产产品2,然后消费者消费产品2,产品1就会没有被消费;

2、程序初始状态:

3、问题产生原因:
(1)程序开始运行,初始情况,线程池为空;
name=''; count=0; flag=false; 等待执行权:t0、t1、t2、t3; 线程池:空

(2)t0获得CPU执行权,开始执行操作:首先判断flag标记为false,不执行wait,继续给r赋值:给name赋值“烤鸭1”,count=1,修改flag=true,执行this.notify(); t0执行完此次循环开始下次循环:判断flag=true,执行this.wait();交出CPU执行权,进入线程池成等待状态;
name='烤鸭1'; count=1; flag=true; 等待执行权:t1、t2、t3; 线程池:t0

(3)假设t1获取执行权,首先判断flag=true,执行this.wait();交出CPU执行权,进入线程池成等待状态;
name='烤鸭1'; count=1; flag=true; 等待执行权:t2、t3; 线程池:t0、t1

(4)假设t2获取执行权,首先判断flag=true,不执行this.wait();继续执行输出语句消费烤鸭1,修改flag=false; 唤醒线程池其中一个(假设t0)线程;
t2结束本次循环,继续下次循环;首先判断flag=false,执行this.wait();交出CPU执行权,进入线程池成等待状态;
name=''; count=1; flag=false; 等待执行权:t0、t3; 线程池:t1、t2

(5)t3获得执行权,首先判断flag=false,执行this.wait();交出CPU执行权,进入线程池成等待状态;
name=''; count=1; flag=false; 等待执行权:t0; 线程池:t1、t2、t3

(6)t0重新获得执行权,由于t0冻结之前已经执行过if判断语句了,所以在此获得执行权后,不再进行flag判断,而是直接就行运行下面的赋值语句给r赋值:name“烤鸭2”,count=2,修改flag=true,执行this.notify();假设唤醒t1;
t0执行完此次循环开始下次循环:判断flag=true,执行this.wait();交出CPU执行权,进入线程池成等待状态;
name='烤鸭2'; count=2; flag=true; 等待执行权:t1; 线程池:t0、t2、t3

(7)t1重新获得执行权,由于t1冻结之前也已经执行过if判断语句了,所以在此获得执行权后,不再进行flag判断,而是直接就行运行下面的赋值语句给r赋值:name“烤鸭3”,count=3,修改flag=true,执行this.notify();

注意:此时“烤鸭2”未被消费!程序出现错误!
改进:修改if判断为while循环,使得线程每次被唤醒后都重新判断flag标记;

改成while循环重新执行1-5步之后状态:
name=''; count=1; flag=false; 等待执行权:t0; 线程池:t1、t2、t3

(6)t0重新获得执行权,重新判断flag=false,继续执行赋值语句:name=“烤鸭2”,count=2,flag=true;假设唤醒t1;
执行下次循环后判断flag=true,进入wait状态;
name='烤鸭2'; count=2; flag=true; 等待执行权:t1; 线程池:t0、t2、t3

(7)t1重新获得执行权,判断flag=true,进入wait状态;
name='烤鸭2'; count=2; flag=true; 等待执行权: ; 线程池:t1、t0、t2、t3

注意:此时线程池中冻结状态线程:t0、t1、t2、t3;所有线程都进入wait状态,程序发生死锁;
解决办法:每次唤醒线程池中所有线程;

4、总结:
(1)if判断标记,只有一次,会导致不该运行的线程运行了,出现了数据错误的情况;
(2)while判断标记,解决了线程获取执行权后,是否要运行!(重新判断)
(3)notify:只能唤醒一个线程,如果本方唤醒了本方,没有意义;
而且while判断标记+notify会导致死锁;
(4)notifyAll解决了本方线程一定会唤醒对方线程的问题;

5、修改后代码:多生产者、多消费者问题;

class Resource{
	private String name;
	private int count;
	private boolean flag = false;
	public synchronized void set(String name){
		while(flag) try{ this.wait();} catch (InterruptedException e){ e.printStackTrace(); }
		this.name = name + count;
		count++;
		System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
		flag = true;
		this.notifyAll();
	}
	public synchronized void out(){
		if(!flag) try{ this.wait();} catch (InterruptedException e){ e.printStackTrace(); }
		System.out.println(Thread.currentThread().getName()+"..消费者......"+this.name);
		flag = false;
		this.notifyAll();
	}
}
class Producer implements Runnable{
	private Resource r;
	Producer(Resource r){ this.r = r; }
	public void run(){ while(true){ r.set("烤鸭"); } }
}
class Consumer  implements Runnable{
	private Resource r;
	Consumer (Resource r){ this.r = r; }
	public void run(){ while(true){ r.out(); } }
}
class test {
	public static void main(String[] args) {
		Resource r = new Resource();
		Producer p = new Producer(r); Consumer c = new Consumer(r);
		Thread t0 = new Thread(p); Thread t1 = new Thread(p);
		Thread t2 = new Thread(c); Thread t3 = new Thread(c);
		t0.start(); t1.start(); t2.start(); t3.start();
	}
}

~ 多生产者、多消费者 - JDK5.0新特性 - Lock、Condition

1、同步函数只有一个锁,一个锁只能对应一组监听器的方法:wait、notify、notifyAll;

2、JDK5将锁封装成对象Lock接口,将监听器的方法封装成对象Condition接口;封装成对象后,一个lock锁上可以挂多个监听器对象Condition,相当于一个锁上可以有多组监听器方法;

3、Lock接口: 它的出现替代了 同步 (同步函数/同步代码块);
同步对于锁的操作是隐式的:获取锁/释放锁的操作看不见;
jdk5以后,将同步和 锁obj 封装成了对象Lock,并将操作锁的隐式方式定义到了对象中,将同步的隐式锁操作变成了显式锁操作:lock.lock();获取锁 , lock.unlock();释放锁,通常需要定义finally代码块中;
同时更为灵活:可以一个锁上加上多组监视器;

4、Condition接口: 它的出现替代了Object中的wait、notify、notifyAll方法;
将这些监视器方法单独进行了封装,变成Condition监视器对象;
可以与任意锁进行组合:Condition c1/c2 = lock.newCondition();
con.await();
con.signal();
con.signalAll();

5、使用Lock+Condition以后,每次唤醒线程就不用再唤醒线程池中所有线程了,可以通过对方的监听器对象con的signal方法,只唤醒对方的线程;

class Resource{
	private String name;
	private int count;
	private boolean flag = false;
	Lock lock = new ReentrantLock(); // 创建一个Lock对象,代替同步+锁;
	Condition producer_con = lock.newCondition(); // 通过已有的锁获取该锁上的两组监视器对象;
	Condition consumer_con = lock.newCondition(); // 一组监视生产者,一组 监视消费者;
	public void set(String name){
		lock.lock(); // 显示获取所对象;
		try {             // 使用生产者的await方法,冻结生产者的线程;
			while(flag) try{ producer_con.await();} catch (InterruptedException e){ e.printStackTrace(); }
			this.name = name + count;
			count++;
			System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
			flag = true;
			consumer_con.signal(); // 使用消费者的signal方法唤醒一个消费者的线程;
		} finally {
			lock.unlock();
		}
	}
	public synchronized void out(){
		lock.lock();
		try {               // 使用消费者的await方法,冻结消费者的线程;
			while(!flag) try{ consumer_con.await();} catch (InterruptedException e){ e.printStackTrace(); } 
			System.out.println(Thread.currentThread().getName()+"..消费者......"+this.name);
			flag = false;
			producer_con.signal(); // 使用生产者的signal方法唤醒一个生产者的线程;
		} finally {
			lock.unlock();
		}
	}
}

~ 停止线程方式 - 定义标记 - Interrupt

1、停止线程:
(1)stop方法:已过时,不再用;
(2)run方法结束(结束run方法);

2、怎么控制线程的任务结束:
任务中都会有循环结构,只要控制住循环就可以结束任务;控制循环通常就用定义标记来完成:flag=true

3、如果线程处于了冻结状态,无法读取标记;如何结束:
可以使用interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备cpu的执行资格;

4、但是强制动作会发生了InterruptedException,记得要处理:try catch;

5、守护线程 - setDaemon:
setDaemon将线程设置为守护线程(后台线程),它依附于前台线程而存在;

6、前后台线程两者区别: 前台线程需手动结束,所有前台线程都结束后,后台线程无论处于什么状态,都结束;

class StopThread implements Runnable{
	private boolean flag = true;
	public synchronized void run() {
		while(flag){
			// 通过在主线程的st.setFlag()修改标记时,两个线程刚开始while循环,就wait冻结,
			// 等不到主进程循环50次时修改flag,程序就停止不动了,无法结束;
			try { wait(); }   
			catch (InterruptedException e) {  //interrupt强行中断wait,有异常需try
				System.out.println(Thread.currentThread().getName()+"....."+e);
				flag = false;  //修改标记,线程interrupt活后,再次运行时判断;
			} 
			System.out.println(Thread.currentThread().getName()+"......++++");
		}
	}
	public void setFlag(){ flag = false; }
}
class test {
	public static void main(String[] args) {
		StopThread st = new StopThread();  // 创建线程任务
		Thread t1 = new Thread(st);  // 创建线程对象,将任务作为参数传入
		Thread t2 = new Thread(st);
		t1.start();
		// 将t2设置成为后台线程,其他的是前台线程;
		// 两者区别:前台线程需手动结束,所有前台线程都结束后,后台线程无论处于什么状态,都结束;
 		t2.setDaemon(true);
		t2.start();
		
		int num = 1;
		for(;;){  // 无限循环,num=50 时设置标记+结束主函数循环;
			// num=50时,修改flag标记的值;但是刚开始运行,num=1,线程还没来得及判断标记的值时,
			// 就wait冻结,这时用interrupt强制中断wait冻结,同时捕捉异常,catch里面修改flag值,
			// 等线程interrupt活了之后,判断flag值为FALSE,while值为假,不再执行循环体;
			if(++num==50){
				// st.setFlag(); 
				t1.interrupt();
				t2.interrupt();
				break; }
			System.out.println("main..."+num);
		}
		System.out.println("主线程结束!");
	}
}

~ 其他方法 - join、setPriority(优先级)

1、设置线程优先级: 优先级越高,获取CPU概率越大;1min--5none--10max

2、join: 临时加入一个线程运算时使用;

class Demo implements Runnable {
	public void run() {
		for (int x = 0; x < 50; x++) {
			System.out.println(Thread.currentThread().toString() + "....;;;." + x);
			Thread.yield();   // 暂停(此方法了解)
		}
	}
}
class test {
	public static void main(String[] args) {
		Demo d = new Demo();
		Thread t1 = new Thread(d);
		Thread t2 = new Thread(d);
		t1.start();
		t2.start();
		t2.setPriority(Thread.MAX_PRIORITY);
		try { t1.join(); // t1线程要申请加入进来,运行,主线程等待t1线程运行终止; 
		}catch (InterruptedException e){ e.printStackTrace(); }
		for (int x = 0; x < 50; x++) {
			System.out.println(Thread.currentThread() + ";....." + x);
		}
	}
}

>> 常用对象API

~ String 类

0、字符串拼接:使用连接符+,或者使用静态join方法,将多个字符串拼接在一起:String all = String.join(" / ", "S", "M", "L", "XL"); // "S / M / L / XL"
1、String s = "abc"; :定义一个类类型的变量;

2、String s = "abc";:创建一个字符串对象在常量池中,字符串对象一旦被初始化就不会再改变;
String s1 = new String("abc");:创建两个对象,一个new一个字符串对象在堆内存中;

3、String s = "abc";:"abc"存储在字符串常量池中,再创建一个新的字符串类常量,在常量池中查找该字符串是否已经存在,若存在,将地址值赋值给变量引用,若没有,重新创建,放到常量池中;
s = "nba";:重新创建另一个字符串s,跟上面一个不一样;

4、String 类构造函数:
(1)String s = new String();:等效于String s = "";,是对象;不等效String s = null;;常量值为空,不是对象;

(2)将字节数组或者字符数组转成字符串可以通过String类的构造函数完成;
String s = new String(arr,1,3);
String s1 = new String(arr);
String s = ""

5、String 类常见功能 - 获取、转换、判断、比较:
(1)获取:

  • ① 获取字符串中字符的个数(长度):int length();
  • ② 根据位置获取字符:char charAt(int index);
  • ③ 根据字符获取在字符串中的第一次出现的位置:
    int indexOf(int ch)
    int indexOf(int ch,int fromIndex):从指定位置进行查找ch的第一次出现位置;
    int indexOf(String str);
    int indexOf(String str,int fromIndex);
    int lastIndexOf(int ch):根据字符串获取在字符串中的最后一次出现的位置;
    int lastIndexOf(int ch,int fromIndex)从指定位置进行ch的查找最后一次出现位置;
    int lastIndexOf(String str);
    int lastIndexOf(String str,int fromIndex);
  • ④ 获取字符串中一部分字符串;也叫子串:
    String substring(int beginIndex, int endIndex):包含begin 不包含end ;
    String substring(int beginIndex);

(2)转换:

  • ① 将字符串变成字符串数组(字符串的切割):String[] split(String regex):涉及到正则表达式;
  • ② 将字符串变成字符数组:char[] toCharArray();
  • ③ 将字符串变成字节数组:byte[] getBytes();
  • ④ 将字符串中的字母转成大小写:String toUpperCase()String toLowerCase()
  • ⑤ 将字符串中的内容进行替换:String replace(char oldch,char newch);String replace(String s1,String s2);
  • ⑥ 将字符串两端的空格去除:String trim();
  • ⑦ 将字符串进行连接 :String concat(string);

(3)判断:

  • ① 两个字符串内容是否相同:boolean equals(Object obj);boolean equalsIgnoreCase(string str);:忽略大写比较字符串内容;
  • ② 字符串中是否包含指定字符串:boolean contains(string str);
  • ③ 字符串是否以指定字符串开头,是否以指定字符串结尾:boolean startsWith(string);boolean endsWith(string);

(4)比较: int compareTo(String anotherString)

6、练习:
(1)字符串数组排序: 给定一个字符串数组,按照字典顺序进行从小到大的排序:{"nba","abc","cba","zz","qq","haha"}
思路: 选择/冒泡排序 + for嵌套 + 比较compareTo() + 换位;

class test {
	public static void main(String[] args) {
		String[] arr = { "nba", "abc", "cba", "zz", "qq", "haha" };
		printArray(arr);
		sortString(arr);
		printArray(arr);
	}
	public static void sortString(String[] arr){
		for(int i=0; i<arr.length-1; i++){
			for(int j=i+1; j<arr.length; j++){ //字符串比较用compareTo方法
				if(arr[i].compareTo(arr[j])>0) swap(arr, i, j);
			}
		}
	}
	public static void swap(String[] arr, int i, int j){
		String temp = arr[i];
		arr[i] = arr[j];
		arr[j] = temp;
	}
	public static void printArray(String[] arr){
		System.out.print("[");
		for(int i=0; i<arr.length; i++){
			if(i==arr.length-1) System.out.println(arr[i] + "]");
			else System.out.print(arr[i] + ",");
		}
	}
}

(2)子串的次数: 一个子串在整串中出现的次数:"nbaernbatynbauinbaopnba"
思路:
(1)要找的子串是否存在,如果存在获取其出现的位置,这个可以使用indexOf完成;
(2)如果找到了,那么就记录出现的位置并在剩余的字符串中继续查找该子串,而剩余字符串的起始位是出现位置+子串的长度;
(3)以此类推,通过循环完成查找,如果找不到就是-1,并对 每次找到用计数器记录;

class test {
	public static void main(String[] args) {
		String str = "nbaernbatnbaynbauinbaopnba";
		String key = "nba";		
		int count = getKeyStringCount(str,key);
		System.out.println("count="+count);
	}
	private static int getKeyStringCount(String str, String key) {
		int count = 0; // 定义计数器;
		int index = 0; // 定义变量记录key出现的位置;
		// while((index = str.indexOf(key))!=-1){
		//  	str = str.substring(index+key.length());
		//  	count++;
		// }  // 在字符串变量池中生成多个字符串变量
		while((index = str.indexOf(key, index))!=-1){
			index = index + key.length();
			count++;  // 这个方法不在字符串变量池中新生成字符串
		}
		return count;
	}
}

(3)最大相同子串: 输出两个字符串中最大相同的子串: "qwerabcdtyuiop""xcabcdvbn"
思路:
(1)既然取得是最大子串,先看短的那个字符串是否在长的那个字符串中,如果存在,短的那个字符串就是最大子串;
(2)如果不是,那么就将短的那个子串进行长度递减的方式去子串,去长串中判断是否存在, 如果存在就已找到,就不用在找了;

class test {
	public static void main(String[] args) {
		String s1 = "qwerabcdtyuiop";
		String s2 = "xcabcdvbn";
		String s = getMaxSubstring(s2, s1);
		System.out.println("s=" + s);
	}
	private static String getMaxSubstring(String s2, String s1) {
		String max = (s1.length()>s2.length())?s1:s2;
		String min = max.equals(s1)?s2:s1;
		System.out.println(max+">"+min);
		for(int i=0; i<min.length(); i++){
			for(int a=0,b=min.length()-i; b!=min.length()+1; a++,b++){
				String sub = min.substring(a, b);
				if(max.contains(sub)) return sub;
			}
		}
		return null;
	}
}

(4)去除两端空白: 模拟一个trim功能一致的方法;去除字符串两端的空白;
思路: 定义两个变量:一个变量作为从头开始判断字符串空格的角标,不断++;一个变量作为从尾开始判断字符串空格的角标,不断–;判断到不是空格为止,取头尾之间的字符串即可;

class test {
	public static void main(String[] args) {
		String s = "    ab   c     ";
		s = myTrim(s);
		System.out.println("-" + s + "-");
	}
	private static String myTrim(String s) {
		int start = 0;
		int end = s.length()-1;
		while(start<=end && s.charAt(start)==' ') start++;
		while(start<=end && s.charAt(end)==' ') end--;
		return s.substring(start, end+1); // 不包含end
	}
}

~ StringBuffer 类

1、StringBuffer:就是字符串缓冲区,用于存储数据的容器;

2、特点:
(1)长度的可变的;
(2)可以存储不同类型数据;
(3)最终要转成字符串进行使用;
(4)可以对字符串进行修改;

3、功能:
(1)添加
StringBuffer append(data);
StringBuffer insert(index,data);
(2)删除
StringBuffer delete(start,end); :包含头,不包含尾;
StringBuffer deleteCharAt(int index);:删除指定位置的元素;
(3)查找
char charAt(index);int indexOf(string);int lastIndexOf(string);
(4)修改
StringBuffer replace(start,end,string);
void setCharAt(index,char);
(5)颠倒reversesb.reverse();

4、代码:

class test {
	public static void main(String[] args) {
		StringBuffer sb = new StringBuffer();  // 创建缓冲区对象;
		sb.append("a").append("b"); // 方法调用链:在尾部添加元素;
		sb.insert(1, "c");       // 在指定索引位置插入元素;
		sb.append("d");
		sb.delete(1, 3);  // 删除指定索引范围内的字符:[1,3),索引下标从0开始;
		sb.delete(0, sb.length()); // 清空缓冲区;
		sb = new StringBuffer("abcde"); // 盆都扔了,买新的;
		sb.replace(1, 3, "nba"); // 替换字符串; [1,3),相当于将索引为12的两个元素替换成nba
		sb.setCharAt(1, 'q');  // 替换单个字符
		sb.setLength(10); // 设置容器长度,字符串<长度时自动补全空格
		sb.reverse();  // 倒着输出内容 
	}
}

~ StringBuilder 类

1、jdk1.5以后出现了功能和StringBuffer一模一样的对象:StringBuilder
不同的是: StringBuffer是线程同步的,通常用于多线程;StringBuilder是线程不同步的,通常用于单线程, 它的出现提高了效率;

2、jdk升级: ①简化书写;②提高效率;③增加安全性;

3、练习: 将一个int数组变成字符串;

class test {
	public static void main(String[] args) {
		int[] arr = {3,1,5,3,8};
		String s = arrayToString2(arr);		
		System.out.println(s);
	}
	private static String arrayToString2(int[] arr) {
		StringBuilder sb = new StringBuilder();
		sb.append("[");
		for(int i=0; i<arr.length; i++){
			if(i != arr.length-1) sb.append(arr[i]+",");
			else sb.append(arr[i]+"]");
		}
		return sb.toString();
	}
	private static String arrayToString1(int[] arr) {
		String s = "[";
		for(int i=0; i<arr.length; i++){
			if(i != arr.length-1) s=s+arr[i]+",";
			else s=s+arr[i]+"]";
		}
		return s;   // 每链接一次就新创建一个字符串在字符串常量池中
	}
}

~ System 类

1、System:类中的方法和属性都是静态的;

2、常见方法:
(1)long currentTimeMillis();:获取当前时间的毫秒值;
(2)setProperty(key,value);:给系统设置一些属性信息;这些信息是全局,其他程序都可以使用;
(3)getProperties();:获取系统的属性信息;

3、获取系统的属性信息,并存储到了Properties集合中;
properties集合中存储都是String类型的键和值,最好使用它自己的存储和取出的方法来完成元素的操作;

class test {
	private static final String LINE_SEPARATOR = System.getProperty("line.separator");
	public static void main(String[] args) {
		long l1 = 1335664696656l;  //System.currentTimeMillis();
		System.out.println(l1/1000/60/60/24);  //毫秒值1335664696656、天数
		// code...   //获取一段程序的运行时间
		long l2 = System.currentTimeMillis();
		System.out.println(l2-l1);
		System.out.println("hello-"+LINE_SEPARATOR+" world");
		//给系统设置一些属性信息;这些信息是全局,其他程序都可以使用; 
		System.setProperty("myclasspath", "c:\\myclass");	
		
		Properties prop = System.getProperties();
		// Properties隶属于HashTable,有自己的操作元素的方法
		Set<String> nameSet = prop.stringPropertyNames();
		for (String name : nameSet) {
			String value = prop.getProperty(name);
			System.out.println(name + "::" + value);
		}
	}
}

~ Runtime 类

Runtime:没有构造方法,不可以创建对象;但是有非静态的方法,说明该类应该提供静态的 返回该类对象的 方法,而且只有一个,说明Runtime类使用了单例设计模式;

public static void main(String[] args) {
	// getRuntime()是静态方法,在里面调用构造函数创建对象,然后返回
	Runtime r = Runtime.getRuntime(); // 返回类型是Process类型的
	Process p = r.exec("notepad.exe");
	Thread.sleep(5000);
	p.destroy(); // 杀掉进程
}

~ Math 类

Math:提供了操作数学运算的方法,都是静态的;
常用的方法
ceil():返回大于参数的最小整数;
floor():返回小于参数的最大整数;
round():返回四舍五入的整数;
pow(a,b):a的b次方;
random():伪随机数 [0,1);

public static void main(String[] args) {
	double d1 = Math.ceil(12.56);   Sop("d1="+d1); //13.0
	double d2 = Math.floor(12.56);  Sop("d2="+d2); //12.0
	double d3 = Math.round(12.46);  Sop("d3="+d3); //12.0		
	double d = Math.pow(10, 2);     Sop("d=" + d); // 100.0
	Random r = new Random(); // 直接将随机数封装成对象,对象方法多
	for (int i = 0; i < 10; i++) {
		double d = Math.ceil(Math.random() * 10); // [1,10)
		double d = (int) (Math.random() * 6 + 1); // [1,6)
		double d = (int) (r.nextDouble() * 6 + 1);
		int d = r.nextInt(6) + 1;
		System.out.println(d);
	}
}

~ Date 类

1、日期对象和毫秒值之间的转换:
(1)毫秒值 --> 日期对象: 可通过Date对象的方法对该日期中各个字段(年月等)进行操作;
①通过Date对象的构造方法 new Date(timeMillis);
②还可以通过setTime设置;

(2)日期对象 --> 毫秒值: 可以通过具体的数值进行运算 getTime方法;

public static void main(String[] args) {
	long time = System.currentTimeMillis();  // 获取当前时间的毫秒值
	System.out.println(time); // 1546851227126
	
	Date date = new Date();   // 将当前日期和时间封装成Date对象
	System.out.println(date); // Mon Jan 07 16:56:35 CST 2019
	date.setTime(1546851227126l); // 通过setTime设置  
	System.out.println(date);
	
	Date date2 = new Date(1546851227126l); // 将指定毫秒值封装成Date对象
	System.out.println(date2); // Mon Jan 07 16:53:47 CST 2019
	
	Date date3 = new Date();   //日期对象-->毫秒值: getTime方法     
	long time3 = date.getTime();
	System.out.println(time);
}

2、将日期对象 --> 日期格式的字符串: 使用的是DateFormat类中的format方法;

public static void main(String[] args) {
	//获取日期格式对象,具备着默认的风格,FULL LONG等可以指定风格;
	Date date = new Date();
	// 创建日期格式的对象
	DateFormat dateFormat1 = DateFormat.getDateInstance(); // 2019-1-7
	DateFormat dateFormat2 = DateFormat.getDateInstance(DateFormat.LONG); // 2019年1月7日
	DateFormat dateFormat3 = DateFormat.getDateTimeInstance(DateFormat.LONG,DateFormat.LONG);
	// 将日期对象 --> 日期格式的字符串
	String str_date = dateFormat3.format(date);
	System.out.println(str_date); // 2019年1月7日 下午05时09分27秒
	// 自定义风格:创建子类SimpleDateFormat对象
	dateFormat1 = new SimpleDateFormat("yyyy---MM---dd");
	String str_date2 = dateFormat1.format(date);
	System.out.println(str_date2); // 2019---01---07
}

3、将日期格式的字符串 --> 日期对象: 使用的是DateFormat类中的parse()方法;

public static void main(String[] args) throws ParseException {
	// 默认格式
	String str_date1 = "2017-2-1";
	DateFormat dateFormat1 = DateFormat.getDateInstance();
	// 指定风格
	String str_date2 = "2012年4月19日";
	DateFormat dateFormat2 = DateFormat.getDateInstance(DateFormat.LONG);
	// 自定义风格
	String str_date3 = "2017---2---14";
	DateFormat dateFormat3 = DateFormat.getDateInstance();
	dateFormat3 = new SimpleDateFormat("yyyy---MM---ddd");
	
	Date date = dateFormat1.parse(str_date1);
	System.out.println(date);
}

4、练习: 计算 “2012-3-17"到"2012-4-6” 中间有多少天;

思路:两个日期相减;
(1)咋减呢?必须要有两个可以进行减法运算的数;能减可以是毫秒值;
(2)如何获取毫秒值?通过date对象;
(3)如何获取date对象呢?可以将字符串转成date对象;

步骤
(1)将日期格式的字符串转成Date对象;
(2)将Date对象转成毫秒值;
(3)相减,在变成天数;

class test {
	public static void main(String[] args) throws ParseException {
		String str_date1 = "2012-3-17";
		String str_date2 = "2012-4-18";
		// 1、将日期格式的字符串转成Date对象;
		DateFormat dateFormat = DateFormat.getDateInstance();
		dateFormat = new SimpleDateFormat("yyyy-MM-dd");
		Date date1 = dateFormat.parse(str_date1);
		Date date2 = dateFormat.parse(str_date2);
		Long time1 = date1.getTime();
		Long time2 = date2.getTime();
		Long time = Math.abs(time1-time2);
		int day = getDay(time);
		System.out.println(day);
	}
	public static int getDay(Long time){
		return (int) (time/1000/24/60/60);
	}
}

~ Calendar 类

class test {
	public static void main(String[] args) {
		// Calendar c = Calendar.getInstance();
		int year = 2012;   // 获取某一年2月有多少天
		showDays(year);
	}
	public static void showDays(int year) {
		Calendar c = Calendar.getInstance();
		c.set(year, 2, 1);
		c.add(Calendar.DAY_OF_MONTH, -1);
		showDate(c);
	}
	public static void showDate(Calendar c) {
		int year = c.get(Calendar.YEAR);
		int month = c.get(Calendar.MONTH) + 1;
		int day = c.get(Calendar.DAY_OF_MONTH);
		int week = c.get(Calendar.DAY_OF_WEEK);
		System.out.println(year + "年" + month + "月" + day + "日" + getWeek(week));
	}
	public static String getWeek(int i) {
		String[] weeks = { "", "星期日", "星期一", ... , "星期五", "星期六" };
		return weeks[i];
	}
}

>> 集合框架

~ 基础 概述

1、数值有很多,用数组存;数组有很多,用二维数组存;
数据有很多,用对象存;对象有很多,用集合存;

2、集合本身是对象:能存储对象的对象;

3、集合是一个容器,用以存储对象;

4、集合类的由来: 对象用于封装特有数据,对象多了需要存储,如果对象的个数不确定,就使用集合容器进行存储;

5、集合特点:
(1)用于存储对象的容器;
(2)集合的长度是可变的;
(3)集合中不可以存储基本数据类型值;

6、COllection接口: 集合容器因为内部的数据结构不同,有多种具体容器,不断的向上抽取,就形成了集合框架;框架的顶层Collection接口;

Collection是接口,不能直接new对象,所以使用其子类ArrayList创建对象,将对象再赋值给Collection类型的引用;

7、Collection 的基本方法:
(1)添加:
boolean add(Object obj);
boolean addAll(Collection coll);
(2)删除:
boolean remove(object obj);
boolean removeAll(Collection coll);
void clear();
(3)判断:
boolean contains(object obj);
boolean containsAll(Colllection coll);
boolean isEmpty();判断集合中是否有元素;
(4)获取:
int size();
Iterator iterator(); 取出元素的方式:迭代器;
(5)其他:
boolean retainAll(Collection coll); 取交集;
Object[] toArray(); 将集合转成数组;

8、迭代器Iterator接口、iterator()方法:
迭代器对象必须依赖于具体容器,因为每一个容器的数据结构都不同,所以该迭代器对象是在容器中进行内部实现的;对于使用容器者而言,具体的实现不重要,只要通过容器获取到该实现的迭代器的对象即可,也就是iterator方法;

Iterator接口 就是对所有的Collection容器进行元素取出的公共接口;

Iterator it = coll.iterator();		
while(it.hasNext()){  System.out.println(it.next());  }

9、Collection 基本方法练习:

public static void main(String[] args) {
	Collection<String> c1 = new ArrayList<String>(); // 创建一个集合类对象
	Collection<String> c2 = new ArrayList<String>();  
	// 添加元素 add
	c1.add("abc1");
	c1.add("abc2");  // c1:[abc1, abc2, abc3]
	c1.add("abc3");  // c1.size() = 3
	c2.add("abc4");  
	c2.add("abc5");  // c2:[abc4, abc5]
	// 删除元素 remove
	c1.remove("abc3");   // c1:[abc1, abc2] 会改变集合的长度:size=2
	// 清空集合 clear
	// c1.clear();  // c1: [],size=0
	c1.contains("abc3");  // false
	// 将集合2中的元素添加到集合1中
	c1.addAll(c2);  // c1:[abc1, abc2, abc4, abc5]
	// 将两个集合中的相同元素从调用removeAll的集合中删除
	boolean b1 = c1.removeAll(c2);  // true,删除成功; c1:[abc1, abc2]
	boolean b2 = c1.containsAll(c2);  // 若c1中包含c2中所有元素,则返回true;
	// 取交集,保留 和指定的集合 相同的元素,而删除不同的元素;和removeAll功能相反
	c1.addAll(c2);
	boolean b = c1.retainAll(c2);  // true,c1:[abc4, abc5]
}

10、集合框架体系结构图:

11、Collection接口的两个子借口:List / Set 接口;
List:列表,有序(存入和取出的顺序一致),元素都有索引(角标),元素可以重复;
Set:集合,无序,元素不能重复;

~ List 列表集合

1、List:列表,有序,可重复;

2、List:特有的常见方法: 有一个共性特点就是都可以操作角标;
(1)添加void add(index,element);void add(index,collection);
(2)删除Object remove(index); // 删除指定索引位置的元素;
(3)修改Object set(index,element);
(4)获取Object get(index); // 返回指定索引位置的元素;list特有的取出元素的方式之一;
int indexOf(object); // 返回指定元素在List中的第一次出现的索引值;
int lastIndexOf(object);
List subList(from,to); //获取子列表;

注意:list集合是可以完成对元素的增删改查;

public static void main(String[] args) {
	List list = new ArrayList();
	// 添加元素
	list.add("abc0");
	list.add("abc1");    // list: [abc0, abc1]
	// 插入元素
	list.add(1, "abc2"); // list: [abc0, abc2, abc1]
	// 删除元素
	Syso("remove: "+list.remove(1)); // remove: abc1,list: [abc0, abc1]
	// 修改元素
	Syso("set: "+list.set(1, "abc9")); // set: abc1,list: [abc0, abc9]
	// 获取元素
	Syso("get: "+list.get(0)); // get: abc0,list: [abc0, abc9]
	// 获取字列表
	Syso("subList: "+list.subList(0, 2)); // subList: [abc0, abc9],list: [abc0, abc9]
}

3、List:有三个子类:
(1)Vector:内部是数组数据结构,是同步的;增删,查询都很慢!
(2)ArrayList:内部是数组数据结构,是不同步的;替代了Vector;查询的速度快;
(3)LinkedList:内部是链表数据结构,是不同步的;增删元素的速度很快;

4、List集合特有的功能:
(1)list.get(index):根据指定索引值取出列表集合元素;
(2)ListIterator it = list.listIterator();:只有list集合具备listIterator迭代功能,它可以实现在迭代过程中完成对元素的增删改查;

public static void main(String[] args) {
	List<String> list = new ArrayList<String>();
	list.add("abc1");
	list.add("abc2");
	ListIterator<String> it = list.listIterator(); // 获取列表迭代器对象
	while(it.hasNext()){
		Object obj = it.next();  // 将取出来的迭代器对象放到obj对象中存储
		if(obj.equals("abc2")) it.set("abc9");
	}
	System.out.println("hasNext:"+it.hasNext()); // 正向输出
	System.out.println("hasPrevious:"+it.hasPrevious()); // 逆向输出	
//		Iterator<String> it = list.iterator(); // 获取list的迭代器对象;
//		while(it.hasNext()){
//			Object obj = it.next(); // java.util.ConcurrentModificationException
//			if(obj.equals("abc2")) list.add("abc9");
//			else System.out.println("obj: "+obj);
//		}   System.out.println("list:"+list);
}

ConcurrentModificationException 异常
(1)产生异常的原因:
list集合中每次add元素,长度都会变化;在长度为3时调用集合的迭代器,这时的迭代器对象的值是3(迭代器只知道这时候集合中有3个元素),迭代器就按照3个元素的方式开始遍历集合取出元素;当执行list.add("abc9");时,迭代器并不知道集合添加了元素,这时候是迭代器在操作元素,而在迭代器操作元素的过程中,又用集合在操作元素,这样就会引发java.util.ConcurrentModificationException异常:当方法检测到对象的并发修改(集合和迭代器同时对元素进行修改,就会导致迭代出现问题),但不允许这种修改时,抛出此异常;

(2)注意事项:
在迭代器过程中,不要使用集合操作元素,容易出现异常:java.util.ConcurrentModificationException

(3)解决方法:
Iterator接口有一个子接口ListIterator,List的ListIterator方法可以返回元素的列表迭代器,这个迭代器只有列表List有;
通过ListIterator对象的方法,可以实现在迭代器操作集合过程中,对集合进行增删改查操作;

~ LinkedList 集合

1、常用方法:

public static void main(String[] args) {
	LinkedList<String> link = new LinkedList<>();
	link.addFirst("abc1");  // link.addLast("abc0");
	link.addFirst("abc2");  // 在链表头部添加元素;
	link.addFirst("abc3");
	System.out.println(link);  // [abc3, abc2, abc1]
	Iterator<String> it = link.iterator();  
	while(it.hasNext()){
		System.out.println(it.next());  // abc3 abc2 abc1
	}
	Syso(link.getFirst());  // 获取但不删除,如果链表为空,抛出NoSuchElementException 
	Syso(link.getLast());
	Syso(link.peekFirst()); // 获取但不移除,如果链表为空,返回null
	Syso(link.peekLast());
	Syso(link.removeFirst()); // 获取并移除,如果链表为空,抛出NoSuchElementException
	Syso(link.removeFirst());
	Syso(link.pollFirst());   // 获取并移除,如果链表为空,返回null
	Syso(link.pollLast());
	while(!link.isEmpty()){
		Syso(link.removeLast()); // 取一个删除一个,取完容器空了;
	}                                    	 
}

2、面试题:使用 LinkedList 来模拟一个堆栈 或 队列数据结构;
堆栈:先进后出,First In Last Out - FILO
队列:先进先出,First In First Out - FIFO

我们应该描述这样一个容器:给使用者提供一个容器对象 完成这两种结构中的一种;

class Duilie{
	private LinkedList<Object> link; // 使用java提供的LinkedList定义一个私有成员变量;
	public Duilie(){ link = new LinkedList<Object>(); } // 构造函数,直接调用已有容器创建对象,赋值给自定义成员引用
	// 队列先进先出,每次在队列尾部添加元素;
	public void myAdd(Object obj){ link.addLast(obj); }
	// 每次在队列头部取元素;remove:获取并删除,列表为空抛异常;
	public Object myGet(){ return link.removeFirst(); }
	public boolean isNull(){ return link.isEmpty(); }
}
class test {
	public static void main(String[] args) {
		Duilie d = new Duilie(); // 用自定义容器创建一个对象
		d.myAdd("abc1"); // 调用自定义容器里面的myAdd方法增加新元素
		d.myAdd("abc2");
		d.myAdd("abc3"); // 打印输出,调用自定义容器里面的判断为空的方法
		while(!d.isNull()){ System.out.println(d.myGet()); }
	}
}

~ ArrayList 集合 - 存储自定对象

1、存自定义数据的时候,取出的时候注意考虑强转动作;
2、自动装箱:基本数据类型赋值给引用数据类型的时候进行装箱;
al.add(5); //al.add(new Integer(5));
3、自动拆箱:引用数据类型和基本数据类型做运算时候进行;

class Person{
	private String name;
	private int age;
	public Person(String name, int age){ this.name = name; this.age = age; }
	// set、 get方法......	
}
class test {
	public static void main(String[] args) {
		Person person = new Person("list1",21);
		ArrayList<Object> al = new ArrayList<>();
		al.add(person);  // 添加数据的时候,先创建对象再传参;
		al.add(new Person("list2",22)); // 直接在传参的时候创建对象;
		Iterator<Object> it = al.iterator(); // 创建迭代器对象
		while(it.hasNext()){
			Person p = (Person)it.next(); // 将获取的数据赋值给新创建的对象引用
			System.out.println("name: "+p.getName()+", age: "+p.getAge());
		}
	}
}

4、练习 - 定义功能去除ArrayList中重复元素:
(1)ArrayList判断元素是否相同依据的是equals方法,HasSet判断元素是否相同依据的是hashCode+equals方法;
(2)判断集合中是否包含某数据,通过使用contains方法判断,而contains方法原理是调用equals方法实现的,所以Person类需要重写object的equals方法,不需要重写hashCode,因为没有用到;remove底层判断相同用的也是equals方法;

class Person{
	private String name;
	private int age;
	public Person(String name, int age){ this.name = name; this.age = age; }
	// set、 get方法......
	public boolean equals(Object obj) {
		if(this	== obj) return true;
		if(!(obj instanceof Person)) throw new ClassCastException("类型错误!");
		Person p = (Person)obj;
		return this.name.equals(p.name) && this.age==p.age;
	}
	public String toString(){ return name+":"+age; }
}
public class test {
	public static void main(String[] args) {
		ArrayList<Person> al = new ArrayList<Person>();
		al.add(new Person("list1",21)); // 添加自定义对象;
		al.add(new Person("list1",22));
		al.add(new Person("list1",22));
		al.add(new Person("list1",23)); // [list1:21, list1:22, list1:22, list1:23]
		al = getSingleElement(al); // [list1:21, list1:22, list1:23]
	}
	public static ArrayList getSingleElement(ArrayList al) {
		ArrayList temp = new ArrayList(); // 定义一个临时容器;
		Iterator it = al.iterator(); // 迭代传入的al集合;
		while(it.hasNext()) {
			Object obj = it.next(); // 判断被迭代到的元素是否在临时容器中存在;
			if(!temp.contains(obj)) temp.add(obj); // 若不存在,则添加;
		}
		return temp;
	}
}

~ Set 集合 - HashSet、TreeSet

1、Set集合:无序,元素不可重复;

2、Set接口中的方法和Collection接口中的方法一致

3、有HashSet、TreeSet两个子类实现:HashSet、TreeSet;

~ HashSet 集合 - 存储自定对象

1、哈希表:

2、HashSet: 内部数据结构是哈希表,是不同步的;
是通过对象的hashCode+equals方法来完成对象的唯一性的判断的:
(1)首先判断对象的hashCode值,若对象的hashCode只值不同,不用再判断对象的equals值,直接将对象存储到哈希表中;
(2)若hashCode值相同,就要继续判断对象的equals方法返回值是否为true,若为true,则视为相同元素,不存入哈希表,若为false,则视为不同元素,存储到哈希表中;

注意: 若要将对象存储到HashSet集合中,定义对象类时,必须要重写Object的hashCode+equals方法;

3、HashSet 存储自定义对象:
(1)要求:往hashSet集合中存储Person对象,如果姓名和年龄相同,视为同一个人,视为相同元素;
(2)判断方法:HashSet集合数据结构是哈希表,所以存储元素的时候,首先使用的元素的hashCode方法来确定位置,如果位置相同,再通过元素的equals来确定值是否相同;
(3)创建Person类:Person继承Object,继承Object的hasCode方法、equals方法;Object的hasCode方法,判断的是底层系统的哈希地址,equals判断的不是内容,而是地址,每个对象都有自己的地址;所以,需要在Person类中,重写(覆盖)父类Object的两个方法;

class Person{
	private String name;
	private int age;
	public Person(String name, int age){ this.name = name; this.age = age; }
	// set、 get方法......	
	@Override
	public int hashCode() {
		System.out.println(this+".......hashCode");	
		return name.hashCode()+age*27; // 返回名字的哈希值+年龄*一个数;
	}
	@Override
	public boolean equals(Object obj) {
		if(this==obj) return true; // 若传入的是同一个对象,直接返回true
		if(!(obj instanceof Person)) throw new ClassCastException("类型错误!");
		System.out.println(this+"...equals..."+obj);
		Person p = (Person)obj;
		return this.name.equals(p.name) && this.age==p.age; // 判断名字年龄是否相同;
	}
	public String toString(){return name+":"+age;}	
}
class test {
	public static void main(String[] args) {
		HashSet<Person> hs = new HashSet<Person>();
		hs.add(new Person("hashSet1",21));
		hs.add(new Person("hashSet1",22)); // 再增加时,会调用hasCode、equals方法
		hs.add(new Person("hashSet1",23));
		Iterator<Person> it = hs.iterator();
		while(it.hasNext()){
			Person p = (Person)it.next();
			System.out.println(p);
		}
	}
}

~ LinkedHashSet 集合

1、LinkedHashSet是HashSet的子类: HashSet hs = new LinkedHashSet();

2、若既要有序,又要唯一,使用LinkedHashSet;
若要有序,使用List;
若要唯一,使用HashSet;

~ TreeSet 集合

1、TreeSet: 可以对Set集合中的元素进行指定顺序的排序;是不同步的;

2、判断元素唯一性的方式:根据比较方法compareTo的返回结果,判断是否为0,是0,就视为是相同元素,不进行存储;

3、TreeSet集合只看compareTo方法,不看hashCode+equals两个方法;

4、 TreeSet 对元素进行排序的方式:

(1)让元素自身具备比较的功能: 定义一个Person类,实现Comparable接口,覆盖compareTo方法;

如果不要按照对象中具备的自然顺序进行排序,或者对象中不具备自然顺序,怎么办?可以使用第二种方式;

(2)让集合自身具备比较功能: 定义一个比较器的类,实现Comparator接口,重写compare方法,然后创建该类的实例对象,作为参数传递给TreeSet的构造函数;

5、注意:Person具备自然排序,TreeSet具备比较器,两个都存在,以比较器为主;
字符串对象本身具备的自然排序不是我们需要的,就只能使用比较器来完成;
字符串的自然排序是java写好的;

6、练习: 以Person对象的年龄进行从小到大的排序;

// 让元素自身具备比较功能:实现Comparable接口,覆盖compareTo方法;
class Person implements Comparable{
	private String name;
	private int age;
	public Person(String name, int age){ this.name = name; this.age = age; }
	// set、 get、toString方法......
	@Override  // 实现Comparable接口的compareTo方法
	public int compareTo(Object o) { // 按照年龄进行从小到大的排序;
		Person p = (Person)o;
		int temp = this.age - p.age;
		return temp==0?this.name.compareTo(p.name):temp;
	}
}
// 创建了一个根据Person类的name进行排序的比较器:
// 让集合自身具备比较功能,定义一个类实现Comparator接口,覆盖compare方法;
class ComparatorBuName implements Comparator{
	@Override  // 覆盖Comparator接口的compare方法
	public int compare(Object o1, Object o2) {
		Person p1 = (Person)o1;
		Person p2 = (Person)o2; // compareTo是String自带的比较方法
		int temp = p1.getName().compareTo(p2.getName());
		return temp==0?p1.getAge()-p2.getAge():temp; //若temp=0时,按年龄进行二次比较,返回
	}
}
class test {
	public static void main(String[] args) {
		// 将创建的ComparatorByName()对象传给TreeSet构造器;
		// 字符串对象本身具备的自然排序不是我们需要的时候,就只能使用比较器来完成;;
		// 对象具备自然排序,集合具备比较器,两个都存在时,以比较器为主;
		TreeSet ts = new TreeSet(new ComparatorBuName()); 
		ts.add(new Person("zhangsan",28));
		ts.add(new Person("lisi",29));
		ts.add(new Person("zhouqi",29));
		ts.add(new Person("zhaoliu",25));
		ts.add(new Person("wangu",24));
		Iterator it = ts.iterator();	
		while(it.hasNext()){
			Person p = (Person)it.next();	
			System.out.println(p.getName()+":"+p.getAge());
		}	
	}
}

7、练习: 字符串长度排序;

class ComparatorByLength implements Comparator{
	public int compare(Object o1, Object o2) {
		String s1 = (String) o1;
		String s2 = (String) o2;
		int temp = s1.length() - s2.length();
		return temp==0?s1.compareTo(s2):temp;
	}   // 若temp=0,使用String自己的compareTo方法进行二次比较
}
class test {
	public static void main(String[] args) {
		TreeSet ts = new TreeSet<>();
		ts.add("aaaaa");  ts.add("zz"); ts.add("nbaq");
		ts.add("cba");  ts.add("abc");	
		Iterator it = ts.iterator();
		while(it.hasNext()){ System.out.println(it.next()); }
	}
}

~ List列表与Set集合选择

1、List列表与Set集合选择:

2、如何记录每一个容器的结构和所属体系:
看名字:
-> 后缀名就是该集合所属的体系;
-> 前缀名就是该集合的数据结构;
①看到array:就要想到数组,就要想到查询快,有角标;
②看到link:就要想到链表,就要想到增删快,就要想要 add get remove+frist last方法;
③看到hash:就要想到哈希表,就要想到唯一性、元素需覆盖hashcode和equals方法;
④看到tree:就要想到二叉树,就要想要排序、两个接口ComparableComparator ;

3、通常这些常用的集合容器都是不同步的;

~ Map 集合

1、Map: 一次添加一对元素,也称为双列集合;
其实map集合中存储的就是键值对, map集合中必须保证键的唯一性;
Collection: 一次添加一个元素,也称为单列集合;

2、常用方法:
(1)添加
value put(key,value):返回前一个和key关联的值,如果没有 返回null;若存相同键,值会覆盖;
(2)删除
void clear():清空map集合;
value remove(key):根据指定的key删除这个键值对,返回值;
(3)判断
boolean containsKey(key)
boolean containsValue(value)
boolean isEmpty()
(4)获取
value get(key):通过键获取值,如果没有该键返回null;当然可以通过返回null,来判断是否包含指定键;
int size():获取键值对的个数;

3、Map常用的子类:
(1)Hashtable :内部结构是哈希表,是同步的;不允许null作为键,null作为值;
(2)Properties:用来存储键值对型的配置文件的信息,可以和IO技术相结合;
(3)HashMap : 内部结构是哈希表,不是同步的;允许null作为键,null作为值;
(4)TreeMap : 内部结构是二叉树,不是同步的;可以对Map集合中的键进行排序;

4、取出map集合中的元素:
(1)取出map中的所有元素: 通过keySet方法获取map中所有的键所在的Set集合,再通过Set的迭代器获取到每一个键,再对每一个键通过map集合的get方法获取其对应的值即可;

(2)通过Map转成set就可以迭代: entrySet; 该方法将键和值的映射关系作为对象存储到了Set集合中,而这个映射关系的类型就是Map.Entry类型;

class test {
	public static void main(String[] args) {
		Map<Integer,String> map = new HashMap<Integer,String>();
		map.put(1, "map1");
		map.put(2, "map2");
		map.put(3, "map3");
		Collection<String> values = map.values(); // 取出map中所有的值;
		Iterator<String> it = values.iterator();
		while(it.hasNext()){ System.out.println(it.next()); }
		// 方法1:通过keySet方法获取map中所有的键所在的Set集合,
		// 再通过Set的迭代器获取到每一个键,再对每一个键通过map集合的get方法获取其对应的值即可;
		Set<Integer> keySet = map.keySet();
		Iterator<Integer> itKeySet = keySet.iterator();
		while(itKeySet.hasNext()){
			Integer key = itKeySet.next();
			String value = map.get(key);
			System.out.println(key+":"+value);
		}
		// 方法2:通过Map转成set就可以迭代:entrySet方法
		// 该方法将键和值的映射关系作为对象存储到了Set集合中,而这个映射关系的类型就是Map.Entry类型(结婚证)
		Set<Map.Entry<Integer, String>> entrySet = map.entrySet();
		Iterator<Entry<Integer, String>> itEntrySet = entrySet.iterator();
		while(itEntrySet.hasNext()){
			Map.Entry<Integer, String> mapEntry = itEntrySet.next();
			Integer key = mapEntry.getKey();
			String value = mapEntry.getValue();
			System.out.println(key+":"+value);
		}
	}
}
interface MyMap{  public static interface MyEntry{ void get();  }  //内部接口 }
class MyDemo implements MyMap.MyEntry{  public void get(){}  }
class Outer{     // Outer.Inner.show();
	    static class Inner{  static void show(){}  
 }

~ HashMap - 存储自定义对象

将学生对象和学生的归属地通过键与值存储到map集合中:
HashMap要保证键唯一,键相同的时候,值覆盖;
键存储的是对象类型时,对象需要重写hashcode+equals方法;

class Student {
	private String name;
	private int age;
	public Student(String name, int age){ this.name = name;this.age = age; }
	@Override 
	public int hashCode() { return name.hashCode()+age*27;  }
	@Override
	public boolean equals(Object obj) {
		if(this==obj) return true;
		if((obj instanceof Student)) throw new ClassCastException("类型错误!");
		Student student = (Student)obj;
		return this.name.equals(student.name) && this.age==student.age;
	}
}
class test{
	public static void main(String[] args) {
		HashMap<Student,String> hm = new HashMap<Student,String>();
		hm.put(new Student("hm11",11), "beijing");
		hm.put(new Student("hm2",22), "tianjin");
		hm.put(new Student("hm3",33), "shanghai");
		Set<Student> keySet = hm.keySet();
		Iterator<Student> it = keySet.iterator();
		while(it.hasNext()) {
			Student key = it.next();
			String value = hm.get(key);
			System.out.println(key.getName()+":"+key.getAge()+":"+value);
		}
	}
}

~ TreeMap - 存储自定义对象

class Student implements Comparable<Student>{
	private String name;
	private int age;
	public Student(String name, int age){ this.name = name;this.age = age; }
	@Override
	public int compareTo(Student o) {
		int temp = this.getName().compareTo(o.getName());
		return temp==0?this.getAge()-o.getAge():temp;
	}
}
class test{
	public static void main(String[] args) {
		TreeMap<Student,String> tm = new TreeMap<Student,String>();
		tm.put(new Student("tm1",11), "北京");
		tm.put(new Student("tm3",33), "上海");
		tm.put(new Student("tm2",22), "天津");
		Iterator<Map.Entry<Student, String>> it = tm.entrySet().iterator();
		while(it.hasNext()) {
			Map.Entry<Student, String> me = it.next();
			Student key = me.getKey();
			String value = me.getValue();
			System.out.println(key.getName()+":"+key.getAge()+":"+value);
		}
	}
}

~ LinkedHashMap

有序 指的是存入和取出数据的顺序一致;按照从大到小或从小到大的顺序指的是排序

TreeSet有序的意思指的是按照特定方式进行的排序;
HashSet是无序的,指的是存和取的顺序不一致;
LinkedHashSet是有序的:存入取出顺序一致;

public static void main(String[] args) {
	HashMap<Integer,String> hm = new LinkedHashMap<Integer,String>();
	hm.put(7, "zhangsan");
	hm.put(3, "lisi");
	hm.put(1, "wangwu");
	hm.put(5, "zhaoliu");   
	Iterator<Map.Entry<Integer, String>> it = hm.entrySet().iterator();
	while(it.hasNext()){
		Map.Entry<Integer, String> me = it.next();
		Integer key = me.getKey();
		String value = me.getValue();
		System.out.println(key+" : "+value);    // 7 3 1 5
	}
}
LinkedHashMap输出:7 3 1 5 
HashMap输出:1 3 5 7

~ Map 集合练习

1、记录字母次数: "fdgavcbsacdfs" 获取该字符串中,每一个字母出现的次数,要求打印结果是:a(2)b(1)...

思路:对于结果的分析发现,字母和次数之间存在着映射的关系,而且这种关系很多,很多就需要存储,能存储映射关系的容器有数组和Map集合;
关系–方式没有序编号吗,那就是使用Map集合; 又发现可以保证唯一性的一方具备着顺序如 a b c …,所以可使用TreeMap集合,这个集合最终应该存储的是字母和次数的对应关系;

过程
①因为操作的是字符串中的字母,所以先将字符串变成字符数组;
②遍历字符数组,用每一个字母作为键去查Map集合这个表;
如果该字母键不存在,就将该字母作为键, 1作为值存储到map集合中;
如果该字母键存在,就将该字母键对应值取出并+1,在将该字母和+1后的值
存储到map集合中,键相同值会覆盖;这样就记录住了该字母的次数;
③遍历结束,map集合就记录所有字母的出现的次数;

class test {
	 public static void main(String[] args) {
		String str = "fdg+avAdc  bs5dDa9c-dfs";	
		String s = getCharCount(str);
		System.out.println(s);
		
	}
	 public static String getCharCount(String s){
		 // 1、将字符串转换成字符数组;
		 char[] chr = s.toCharArray();
		 // 2、定义Map集合;
		 Map<Character,Integer> map = new HashMap<Character,Integer>();
		 for(int i=0; i<chr.length; i++){
			 // 过滤非字母字符;
			 if(!(chr[i]>='a'&&chr[i]<='z')||chr[i]>='A'&&chr[i]<='Z') continue;
			 int count = 1;
			 // 将数组中的字母作为键去查找map集合;
			 Integer value = map.get(chr[i]);
			 if(value!=null) count=value+1;
			 map.put(chr[i], count);
		 }
		 return mapToString(map);
	 }
	 public static String mapToString(Map<Character,Integer> map){
		 StringBuilder sb = new StringBuilder();
		 Iterator<Character> it = map.keySet().iterator();
		 while(it.hasNext()){
			 Character key = it.next();		
			 Integer value = map.get(key);
			 sb.append(key+"("+value+")");
		 }
		 return sb.toString();
	 }
}

2、Map查表法: Map在有映射关系时,可以优先考虑,在查表法中的应用较为多见;

public class MapTest2 {
	public static void main(String[] args) {
		String week = getWeek(1);
		System.out.println(week);		
		System.out.println(getWeekByMap(week));
	}
	public static String getWeekByMap(String week){		
		Map<String,String> map = new HashMap<String,String>();	
		map.put("星期一","Mon");
		map.put("星期二","Tus");
		map.put("星期三","Wes");
		map.put("星期日","Sun");
		map.put("星期天","Sun");		
		return map.get(week);
	}	
	public static String getWeek(int week){		
		if(week<1 || week>7)
			throw new RuntimeException("没有对应的星期,请您重新输入");		
		String[] weeks = {"","星期一","星期二"};		
		return weeks[week];
	}
}

~ 工具类 Collections

Collections类:是集合框架的工具类,里面的方法都是静态的,可以不创建对象,直接使用;

1、排序: Collections.sort();

class test {
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("abcde");
		list.add("nba");
		list.add("aa");
		System.out.println(list);
		// 对list进行指定集合的排序;
 		Collections.sort(list); // 已有方法,直接使用;
 		Collections.sort(list, new ComparatorByLength()); // 已有方法,带比较器;
 		mySort(list); // 自定义方法;
		mySort(list, new ComparatorByLength()); // 自定义方法带比较器;
		System.out.println(list);
	}
	public static <T extends Comparable<? super T>> void mySort(List<T> list) {
		for(int i=0; i<list.size()-1; i++){
			for(int j=i+1; j<list.size(); j++){
				if (list.get(i).compareTo(list.get(j)) > 0) {
					// T temp = list.get(i);
					// list.set(i, list.get(j));
					// list.set(j, temp);
					Collections.swap(list, i, j);
				}
			}
		}
		
	}
	private static <T> void mySort(List<T> list, Comparator<? super T> comp) {
		for(int i=0; i<list.size()-1; i++){
			for(int j=i+1; j<list.size(); j++){
				 if(comp.compare(list.get(i), list.get(j))>0){
					 Collections.swap(list, i, j);
				 }
			}
		}
	}
}
class ComparatorByLength implements Comparator<String>{
	@Override
	public int compare(String o1, String o2) {
		int temp = o1.length() - o2.length();
		return temp==0?o1.compareTo(o2):temp;
	}
}

2、折半、最值:

class test {
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("abcde");
		list.add("nba");
		list.add("aa");
		System.out.println(list);
		Collections.sort(list);  // 先排序,之后才能二分查找;
		int index = Collections.binarySearch(list, "nba");  // 二分查找
		System.out.println("index: "+index);
		String max = Collections.max(list, new ComparatorByLength());  // 最大值
		System.out.println("max: "+max);
	}
}
class ComparatorByLength implements Comparator<String>{
	@Override
	public int compare(String o1, String o2) {
		int temp = o1.length() - o2.length();
		return temp==0?o1.compareTo(o2):temp;
	}
}

3、逆序、替换:

public static void main(String[] args) {
	TreeSet<String> ts = new TreeSet<String>(new Comparator<String>() {
		@Override
		public int compare(String o1, String o2) {
			return o2.compareTo(o1);
		}
	});
//		TreeSet<String> ts = new TreeSet<String>(Collections.reverseOrder(new ComparatorByLength()));
	ts.add("abc");   	
	ts.add("hahaha");
	ts.add("zzz");	
	System.out.println(ts);		
}

4、其他方法、将非同步集合转成同步集合的方法: 给非同步的集合加锁,返回一个同步的集合

public static void main(String[] args) {
	List<String> list = new ArrayList<String>();	//新建对象	
	list.add("abcde");
	Collections.replaceAll(list, "cba", "nba"); 
	// set(indexOf("cba"),"nba");用的是set方法,将集合中元素替换
	Collections.shuffle(list);    //随机排列指定的列表,使用默认的随机性源
	Collections.fill(list, "cc");//用指定的元素替换指定列表中的所有元素
	System.out.println(list);
}
List list = new ArrayList();    //定义一个List对象,非同步的;
list = MyCollections.synList(list);  //返回一个同步的list.
class MyCollections{                 //自定义一个类
	public static  List synList(List list){   //静态方法		
		return new MyList(list);             //返回一个私有方法创建的对象
	} 
	private class MyList implements List{ //定义一个私有方法,实现List接口
	    private List list;	
	    private static final Object lock = new Object();
	    MyList(List list){	    this.list = list;	    }	
	    public boolean add(Object obj){
	    	 synchronized(lock){    return list.add(obj);    }
	    }	
	    public boolean remove(Object obj){
		    synchronized(lock){   return list.remove(obj);    }
	    }
}
}

~ 工具类 Arrays - 数组转成集合

1、Arrays: 集合框架的工具类,里面的方法都是静态的;
List<String> list = Arrays.asList(arr);:asList将数组转换成集合,然后可以使用集合的方法操作数组中的元素;

注意:数组的长度是固定的,所以对于集合的增删方法是不可以使用的
否则会发生异常UnsupportedOperationException

2、demo_2() 如果数组中的元素是对象,那么转成集合时,直接将数组中的元素作为集合中的元素进行集合存储;
如果数组中的元素是基本类型数值,那么会将该数组作为集合中的元素进行存储;

class test {
	public static void main(String[] args) {
		int[] arr = { 3, 1, 5, 6, 3, 6 };
		System.out.println(arr); // 不用toString输出的是arr哈希码
		System.out.println(Arrays.toString(arr));
	}
	public static void demo_1() {
		String[] arr = {"abc", "haha", "xixi"};
		boolean b = myContains(arr, "xixi");    // 自定义数组转集合的方法
		System.out.println("contains:" + b);
		List<String> list = Arrays.asList(arr); // 已有数组转集合的方法
		boolean b1 = list.contains("xixi");     // 直接使用集合的方法;
		System.out.println("list contaisn:=" + b1);
		// list.add("hiahia");  //UnsupportedOperationException
		System.out.println(list);
	}
	public static boolean myContains(String[] arr, String key) {
		for (int i = 0; i < arr.length; i++) {
			if (arr[i].equals(key)) return true;
		}
		return false;
	}
	public static void demo_2() {
		int[] arr = { 31, 11, 51, 61 };
		List<int[]> list = Arrays.asList(arr);
		System.out.println(list);
	}
}

~ 工具类 Collection的toArray方法 - 集合转成数组

1、集合转成数组: 可以对集合中的元素操作的方法进行限定,不允许对其进行增删;

2、toArray方法需要传入一个指定类型的数组;

3、数组长度定义:
如果长度小于集合的size,那么该方法会创建一个同类型并和集合相同size的数组;
如果长度大于集合的size,那么该方法就会使用指定的数组,存储集合中的元素,其他位置默认为null;
所以建议,最后长度就指定为集合的size;

List<String> list = new ArrayList<String>();   //注意要加泛型
list.add("abc1"); 	list.add("abc2"); 	list.add("abc3");
String[] arr = list.toArray(new String[list.size()]);		
System.out.println(Arrays.toString(arr));		

~ foreach - 高级for循环

1、foreach语句:
格式:for(类型 变量 :Collection集合|数组){ } eg: for(String s : list){}

2、传统for和高级for的区别:
传统for可以完成对语句执行很多次,因为可以定义控制循环的增量和条件;高级for是种简化形式;必须有被遍历的目标;该目标是数组或Collection单列集合;对于数组的遍历如果仅仅是获取数组中的元素,可以使用高级for;如果要对数组的角标进行操作建议使用传统for;

3、可以使用高级for遍历map集合吗?
不能直接用,但是可以将map转成单列的set,就可以用了;

public static void main(String[] args) {
	List<String> list = new ArrayList<String>();     // 遍历集合
	list.add("abc1"); list.add("abc2"); list.add("abc3");
	for (String s : list) { System.out.println(s); } // 简化书写
	
	int[] arr = { 3, 1, 5, 7, 4 }; // 遍历数组
	for (int i : arr) { System.out.println(i); }
	
	Map<Integer, String> map = new HashMap<Integer, String>();
	map.put(3, "zhagsan"); map.put(1, "wangyi"); map.put(7, "wagnwu"); 
	for (Integer key : map.keySet()) {
		String value = map.get(key);
		System.out.println(key + "::" + value);
	}
	for (Map.Entry<Integer, String> me : map.entrySet()) {
		Integer key = me.getKey();
		String value = me.getValue();
		System.out.println(key + ":" + value);
	} 
}

~ 函数可变参数

函数的可变参数,其实就是一个数组,但是接收的是数组的元素,自动将这些元素封装成数组;简化了调用者的书写;
注意:可变参数类型,必须定义在参数列表的结尾;
对:public static int newAdd(int a,int... arr)
错:public static int newAdd(int... arr,int a)
对:public static int add(int a,int[] arr)
对:public static int add(int[] arr,int a)

public static int newAdd(int a,int...  arr){		
		int sum = 0;
		for (int i = 0; i < arr.length; i++) {	sum+=arr[i];	}
		return sum;
}
int sum = newAdd(5,1,4,7,3);

>> 泛型

~ 基础 概述

1、泛型: jdk1.5出现的安全机制;

2、好处:
(1)将运行时期的问题ClassCastException转到了编译时期;
(2)避免了强制转换的麻烦;

3、<>:什么时候用: 当操作的引用数据类型不确定的时候,就使用<>,将要操作的引用数据类型传入即可,但是不能传入基本数据类型;

其实<>就是用来接收具体引用数据类型的参数范围的:ArrayList<Person> al = new ArrayList<Person>();

4、在程序中,只要用到了带有<>的类或者接口,就要明确传入的具体引用数据类型 ;

5、泛型技术是给编译器使用的技术,用于编译时期,确保了类型的安全;
运行时,会将泛型去掉,生成的class文件中是不带泛型的,这个称为泛型的擦除
为什么擦除呢:因为为了兼容运行的类加载器;
泛型的补偿:在运行时,通过获取元素的类型进行转换动作,不用使用者再强制转换了;

6、代码:

public static void main(String[] args) {
	int[] arr = new int[4]; // int型数组,创建时就指定好了数据类型[]
	arr[0] = 3.0;           //传入的不是int型的就会报错	
	ArrayList<String> al = new ArrayList<String>();//<>指定传入的数据类型		
	al.add("abc"); //public boolean add(Object obj)
	al.add(4);     //al.add(new Integer(4));  报错
	Iterator<String> it = al.iterator();
	while(it.hasNext()){
		String str = it.next(); //使用泛型后不用再强转
	}
}

7、泛型在TreeSet集合中的应用:
TreeSet集合里面的数据是有序的,所以需要比较,所以需要在比较器中加入泛型;

class Person implements Comparable<Person>{ // 限制传入比较器的数据类型是Person类型;
	private String name;
	private int age;
	public Person(String name, int age){ this.name=name; this.age=age; }
	// set、 get、toString方法......
	public int compareTo(Person p){ // 直接使用Person接收参数; 
		int temp = this.age-p.age;
		return temp==0?this.name.compareTo(p.name):temp;
	} 
}
class ComparatorByName implements Comparator<Person>{ // 加入泛型
	@Override
	public int compare(Person p1, Person p2) {
		int temp = p1.getName().compareTo(p2.getName());
		return temp==0?p1.getAge()-p2.getAge():temp;
	}
}
public class test {
	public static void main(String[] args) {
		// 加入泛型,限制集合中只能传入Person类型的参数;
		TreeSet<Person> ts = new TreeSet<Person>(); 
		ts.add(new Person("zhangsan",32));
		ts.add(new Person("lisi",22));
		ts.add(new Person("wangwu",12));
		Iterator<Person> it = ts.iterator();
		while(it.hasNext()) {
			// 使用泛型限制类迭代器中的数据类型是Person,不用再进行强转了;
			Person p = it.next(); 
			System.out.println(p);
		}
	}
}

~ 泛型类

1、在jdk1.5后,使用泛型来接收类中要操作的引用数据类型;

2、泛型类什么时候用: 当类中的操作的引用数据类型不确定的时候,就使用泛型来表示;

3、定义类时没有使用泛型:

class Tool{   // 定义类时没有使用泛型
	private Object object;
	public Object getObject(){ return object; }
	public void setObject(Object object){ this.object = object; }
}
class Student{}
class Worker{}
public class test {
	public static void main(String[] args) {
		 Tool tool = new Tool();
		 tool.setObject(new Worker());
		 // 没使用泛型的时候,Tool用object接收,set方法传入Worker对象,
		 // get方法用Student接收,编译通过,运行失败;
		 Student stu = (Student) tool.getObject();
	}
}

4、定义泛型类:

class Tool<Q>{
	private Q object;
	public Q getObject(){ return object; }
	public void setObject(Q object){ this.object = object; }
}
public class test {
	public static void main(String[] args) {
		 Tool<Student> tool = new Tool<Student>();
		 tool.setObject(new Worker()); // 使用泛型,传入Worker,编译报错;
		 Student stu = (Student) tool.getObject();
	}
}

~ 泛型方法

1、泛型<>必须放在返回值前边,修饰符后边: public <W> void show(W str){}

2、当方法静态时,不能访问类上定义的泛型;

3、如果静态方法要使用泛型,只能将泛型定义在方法上;

class Tool<Q>{
	private Q object;
	public Q getObject(){ return object; }
	public void setObject(Q object){ this.object = object; }
	// 将泛型定义在方法上;
	public <W> void show(W str) { // 泛型<>必须放在返回值前边,修饰符后边
		// show的泛型是在方法上自定义的,所以接收什么类型,str就是什么类型;
		// 不能使用length方法,因为泛型定义在方法上,接受数据类型不确定;
		System.out.println("show: "+str.toString());
	}
	// 静态方法不能访问类上定义的泛型,要使用泛型,只能将泛型定义在方法上;
	public static <Y> void method(Y obj){
		System.out.println("method:"+obj);
	}
	public void print(Q str){ // print的泛型是跟着对象走的;
		System.out.println("print : "+str);
	}
}
public class test {
	public static void main(String[] args) {
		 Tool<String> tool = new Tool<String>();
		 tool.show(new Integer(3)); // show方法定义泛型了,所以可以传入任意类型的数据;
		 tool.show("abc");
		 tool.print("haha"); // print方法上没有定义泛型,所以泛型是跟着对象走的,只能传入指定类型的数据;
		 Tool.method("static"); // 静态方法可以直接类名调用;
		 Tool.method(new Integer(2)); 
	}
}

~ 泛型接口

interface Inter<T>{ public void show(T t); }
// 实现接口时,知道接受的数据的类型,可以在类里面直接使用;
class InterImpl1 implements Inter<String>{
	public void show(String str){ System.out.println("show: "+str);}
}
// 实现接口时,不知道传入的数据的类型,需要在类上定义泛型;
class InterImpl2<T> implements Inter<T>{ // 类定义的泛型要和接口定义的泛型一样;
	public void show(T t){System.out.println("show: "+t); }
}
public class test {
	public static void main(String[] args) {
		 InterImpl1 i1 = new InterImpl1();
		 i1.show("abs");
		 InterImpl2<Integer> i2 = new InterImpl2<Integer>();
		 i2.show(5);
	}
}

~ 泛型限定

1、泛型的通配符: ? ,指的是 未知类型;

2、泛型的限定:
(1)上限 ? extends E:接收E类型或者E的子类型对象;
上限一般存储对象的时候用,比如 添加元素 addAll;

(2)下限 ? super E:接收E类型或者E的父类型对象;
下限一般取出对象的时候用,比如比较器;

3、泛型的限定:单独使用<?>,表示接受任意类型的数据;

public class test {
	public static void main(String[] args) {
		 ArrayList<String> al1 = new ArrayList<String>();
		 al1.add("abc");
		 al1.add("def");
		 printCollection1(al1);
		 ArrayList<Integer> al2 = new ArrayList<Integer>();
		 al2.add(1);
		 al2.add(2);
		 printCollection2(al2);
	}
	// 迭代 并打印集合中的元素:两种迭代器方法差不多,第一种更常用,写法简单;
	public static void printCollection1(Collection<?> coll) {
		Iterator<?> it = coll.iterator();
		while(it.hasNext()){ System.out.println(it.next().toString()); }
	}
	public static <T> void printCollection2(Collection<T> coll) {
		Iterator<T> it = coll.iterator();
		while(it.hasNext()) {
			T t = it.next();
			System.out.println(t);
		}
	}
}

4、泛型的限定:上、下限;

class Person{}
class Student extends Person{}
class Worker extends Person{}
public class test {
	public static void main(String[] args) {
		ArrayList<Person> al = new ArrayList<Person>();		
		al.add(new Person("abc1",30));
		al.add(new Person("abc2",34));		
		ArrayList<Student> al2 = new ArrayList<Student>();		
		al2.add(new Student("stu1",11));
		al2.add(new Student("stu2",22));
		ArrayList<String> al3 = new ArrayList<String>();	
		al3.add("stu3331");
		al3.add("stu33332");	
		printCollection1(al2);
		printCollection2(al);
	}
	// 当想让方法接收Person类及其子类 类型的数据时,可以使用上线进行泛型限定;
	// <Person>:Person是一个单独的类型,Worker和Student也是一个单独的类型,这个表示Collection中只能存储Person的对象;
	//           相当于 Collection<Person> al = new ArrayList<Student>(); 左右两边的泛型类型不匹配;
	// <?>:表示可以接收任意类型的数据;
	// <? extends Person>:接收Person及其子类 类型的数据;既不想只操作一个,也不想操作所有;
	// 只能传入Person类及其子类Worker/Student,不能传入String类;
	public static void printCollection1(Collection<? extends Person> al){
		Iterator<? extends Person> it = al.iterator();
		while(it.hasNext()) {
			Person p = it.next();
			System.out.println(p.getName()+":"+p.getAge());
		}
	}
	// 只能传入Student或其父类Person,不能传入Worker或String类型;
	public static void printCollection2(Collection<? super Student> al){
		Iterator<? super Student> it = al.iterator();
		while(it.hasNext()){ System.out.println(it.next()); }
	}
}

5、泛型的上、下限体现:
一般在存储元素的时候都是用上限,因为这样取出都是按照上限类型来运算的,不会出现类型安全隐患;
通常对集合中的元素进行取出操作时,可以是用下限:取出元素可以用父类型来接收;

public class test {
	public static void main(String[] args) {
		ArrayList<Person> al1 = new ArrayList<Person>();		
		al1.add(new Person("person1",30));
		al1.add(new Person("person2",34));		
		ArrayList<Student> al2 = new ArrayList<Student>();		
		al2.add(new Student("student1",11));
		al2.add(new Student("student2",22));
		ArrayList<Worker> al3 = new ArrayList<Worker>();
		al3.add(new Worker("worker1",12));
		al3.add(new Worker("worker2",13));
		ArrayList<String> al4 = new ArrayList<String>();	
		al4.add("stu3331");
		al4.add("stu33332");	
	}
	 
}
// 类里面加入的元素不确定的情况下,使用E进行泛型的限定:类里面可以存入任意类型的数据;
// al1不定义泛型时,可以接收al4类型的数据,但是取的时候有问题:al4的数据是按照Person类型还是String型?
// 若创建al1时限定<person>,这样addAll方法添加al4集合整体时,也会限定al4集合中的存入的数据类型是Person类,
// 否则就会报错:类型不匹配;这样al1集合中存储的数据就都是Person类型的类;
// 但是,若al1集合中添加入Student类型的数据,取元素时可以将Student类型的数据按照Person类型的数据取出,
// 取出元素时不存在安全隐患,所以al1集合可以接受Student类型的数据;
// 所以addAll方法接收数据的类型可以限定为E及其子类:<? extends E>
// 一般存储元素时,使用上限,因为这样取出都是按照上限类型来运算的,不会出现类型安全隐患;
class MyCollection<E>{ // 
	public void add(E e) {}
	// 类定义时加入泛型,这里方法传入集合类型的参数时也要加入相同的泛型限定;
	public void addAll(MyCollection<? extends E> e) {}
}
public class GenericAdvanceDemo4 {
	public static void main(String[] args) {
	①	TreeSet<Person> al1 = new TreeSet<Person>(new CompByName());	
		al1.add(new Person("abc4",34));
		al1.add(new Person("abc1",30));	
	②	TreeSet<Student> al2 = new TreeSet<Student>(new CompByName())	
		al2.add(new Student("stu1",11));
		al2.add(new Student("stu7",20));
	③	TreeSet<Worker> al3 = new TreeSet<Worker>();	
		al3.add(new Worker("person1",11));
		al3.add(new Worker("person2",22));			
//		al1.addAll(al2);   //存到al1以后,存储为Person型
//		al1.addAll(al3);
		Iterator<Student> it = al2.iterator();
		while(it.hasNext()){ System.out.println(it.next()); }	
	}
}
class TreeSet<Worker>{ Tree(Comparator<? super Worker> comp);   }
class CompByName implements Comparator<Person>{
	public int compare(Person o1, Person o2) {	
		int temp = o1.getName().compareTo(o2.getName());	
		return temp==0? o1.getAge()-o2.getAge():temp;
	}	
}
class CompByStuName implements Comparator<Student>{
	public int compare(Student o1, Student o2) {		
		int temp = o1.getName().compareTo(o2.getName());		
		return temp==0? o1.getAge()-o2.getAge():temp;
	}	
}

6、泛型的通配符?的体现:

public class test {
	public static void main(String[] args) {
		ArrayList<Person> al1 = new ArrayList<Person>();	
		al1.add(new Person("abc",30));
		al1.add(new Person("abc4",34));
	 	ArrayList<Person> al2 = new ArrayList<Person>();	
		al2.add(new Person("abc22222",30));
		al2.add(new Person("abc42222",34));		
	 	ArrayList<String> al4 = new ArrayList<String>();
		al4.add("abcdeef");	
		al4.add("abc");	
		// al1集合和al2集合中存储的数据类型一致,可以判断al1集合中是否包含al2集合中的元素;
		al1.containsAll(al2); 
        // al1集合和al2集合中存储的数据类型不一样,同样可以判断al1集合中是否包含al4集合中的元素;
		al1.containsAll(al4);	
	}
	public static void printCollection(Collection<?> al){
		Iterator<?> it = al.iterator();	
		while(it.hasNext()){			
			System.out.println(it.next().toString());
		}
	}
}
// containsAll原理是使用equals做判断,而equals方法任何对象都具备,equals的参数是Object,
// 可以接收任意类型的数据,所以字符串可以跟对象进行比较: "abc".equals(new Person(“ahahah",20));
// 所以containsAll比较一个集合里是否包含另一个集合里的数据时,两个集合中存储的数据类型不用保持一致,
// 所以Collection<?>接收的数据类型不确定,可以用?来限定;
class MyCollection<E>{
	public boolean containsAll(Collection<?> coll){	return true;}
}

本文标签: 基础Java