Java基础笔记

文章目录

工具类:
导包:关键字import
Util包中
从键盘输入:Scanner sc=new Scanner(System.in); Scanner类中的next方法
double score=sc.nextDouble();
取随机数:Random rn=new Random(); Random类中的next方法
Int i=rn.next(n);

       数组的输出:Arrays.tostring(数组名)
       数组的排序:Arrays.sort(数组名); 默认按照ascll码值,由小到大排序

Object类中有一个toString方法定义类时可以重写它,这样在控制台中打印对象会显示地址里面的内容

Java基础

一、Java语言概述

1、 Dos命令

调出命令窗口:cmd
tab自动补全键
切换目录:cd 
         进入目录    cd 目录的名字
         返回上一层目录  cd..
         直接进入指定目录  cd  该目录的全路径(同一盘符下复制路径右键粘贴即可,若不在同一盘符下则再加一步切换盘符即可)
         直接返回根目录 cd/
切换盘符:盘符:
查看目录: dir
           如果是目录会有一个<dir>标识  ..代表上层目录
创建文件夹: md 目录名字
删除文件夹: rd目录的名字
删除文件:  del 文件名
访问软件:1、首先访问到程序所在的位置然后 cd 程序名称.exe
          2、输入程序的绝对路径名字直接执行
 清屏:cls

2、 java是一种与计算机沟通的语言

Java SE=Java Standard Edition :SE主要用于桌面程序,控制台开发(JFC)
Java EE=Java Enterprise Edition Java :EE企业级开发(JSP,EJB)
ME=Java Mobile Edition :ME嵌入式开发(手机,小家电)

3、java的跨平台特点

通过在不同的系统上安装jvm(Java Virtual Machine),由java虚拟机来完成java语言到不同操作系统的指令翻译工作,从而实现了跨平台运行,jvm虚拟机为程序提供了方法区(数据共享区)、栈区(压栈运行)、堆区(存储对象 数组)

4、java的环境搭建

  • JDK与JRE

      Jdk :java程序的运行环境(包括java开发工具和运行环境)编译器
           下载地址:http://www.oracle.com/downloads/index.html (美国甲骨文公司)
           src.zip压缩包存的是java的源代码
      Jre :java runtime ervirence , java程序运行所需要的最小内容
        安装jdk环境变量没有自动设置好时,也就是在命令界面输入java或javac等命令时无响应
    
  • 环境变量:

    1、path :配置java程序所在目录下的bin目录(bin目录下都是java所需要的可执行文件) 作用是配置好java的path路径之后我们可以在cmd命令任意目录下使用bin目录下的程序 值为 % JAVA_HOME %\bin;
    2、JAVA_HOME:系统变量,用于其它的路径或者环境变量配置引入这个环境。
    有了JAVA_HOME,path环境变量,就可以变成 %JAVA_HOME%\bin;
    3、CLASSPATH:它的作用是指定类搜索路径,它与import、package关键字有关。当你improt.java.util.*时,编译器面对import关键字时,就知道你要引入java.util这个package中的类;
    注意:Jdk1.5以后不再需要配置class path变量

5、编写入门程序HelloWord:

  • 编写 java的入门程序

    新建文件夹重命名.java
    输入程序主体public class HelloWord { }这是程序执行的入口

public class  HelloWord {
		public static void main(String args[]){
				System.out.println("Hello World!"); 
		}
 }
  • 编译 :使用javac命令使.java文件生成 .class文件
  • 运行 :使用java 加文件名不带后缀来运行程序

6、main函数介绍

一、先说类:

HelloWorld 类中有main()方法,说明这是个java应用程序,通过JVM直接启动运行的程序。既然是类,java允许类不加public关键字约束,当然类的定义只能限制为public或者无限制关键字(默认的)。

二、再说main()方法

这个main()方法的声明为:public static void main(String args[]) 。必须这么定义,这是Java的规范。

为什么要这么定义,和JVM的运行有关系。

当一个类中有main()方法,执行命令“java 类名”则会启动虚拟机执行该类中的main方法。

由于JVM在运行这个Java应用程序的时候,首先会调用main方法,调用时不实例化这个类的对象,而是通过类名直接调用因此需要是限制为public static。

对于java中的main方法,jvm有限制,不能有返回值,因此返回值类型为void。

main方法中还有一个输入参数,类型为String[],这个也是java的规范,main()方法中必须有一个入参,类细必须String[] ,至于字符串数组的名字,这个是可以自己设定的,根据习惯,这个字符串数组的名字一般和sun java规范范例中mian参数名保持一致,取名为args。

因此,main()方法定义必须是:“public static void main(String 字符串数组参数名[]) ”。

三、main()方法中字符串参数数组作用

main()方法中字符串参数数组作用是接收命令行输入参数的,命令行的参数之间用空格隔开。

四、main()方法也可以向外抛出异常

这样写代码不会出错,在main()方法中抛出的异常,就会到虚拟机中进行处理,此时异常就会脱离程序员的控制,所以在实际开发中不会这样写

二、java数据类型与表达式

1、关键字:

重要的单词 public,class,static,void等被java赋予了特殊含义的一些单词.可以想象为指令

1.1权限修饰符

在这里插入图片描述

2、标识符 :

除了关键字以外的称作 标识符(由26个大小写英文字母,数字0-9和 _和$组成),由编程人员自己决定

  • 标识符命名规则:

    1、 数字不能作为开头和不能使用关键字
    2、注意空格也是一个字符,不能出现在标识符

  • 命名规范:

    标识符尽量要有语意,当标识符需要多个单词来表达时,每个单词的首字母要大写。(驼峰命名)
    例如HelloWord

  • 包类名时都要小写,类名和接口名时每个单词首字母大写

3、java中的注释

单行//注释内容
多行/* 注释内容 /
文档注释/
* 内容注释 */

4、常量和变量

   常量:不变的数据,java中规定常量字母必须大写  整数,小数,字符,字符串,布尔,null
   变量:可变的数据  必须指定变量的类型。

5、java的数据类型

Java的数据类型分为基本数据类型(四类八种)和引用数据类型(需要new关键字创建对象)

数据类型的转化

1、强制转化:当数据类型由大范围到小范围需要强制转换(数据类型)数值,
强转前提是数据类型兼容,值与值之间或对象与对象之间

2、对象与值之间的转换,借用java的自动装箱与拆箱
基本数据类型所对应的包装类:Byte 、Short Integer Long Character Double Float Boolen

5.1、java的基本数据类型 (四类八种)

存储范围的公式:-2(n-1)到2(n-1)-1

  • 整型

    1、 byte 字节型 占1个字节 ,
    2、short 短整型 占2字节,
    3、 int 整型 占4个字节, (整型中默认为int)
    4、long 长整型 占8个字节(赋值时需要在数值后面加L不区分大小写,否则可能报错)

  • 浮点型
    1、 float 单精度浮点数 占4个字节(赋值时需要在数值后面加f不区分大小写)
    2、double 双精度浮点数 占8个字节(默认为double型)

  • 字符型
    char 字符型 占2个字节(0-65535)存储单个字符

面试
char a=65;输出为A,此时可以参加算术运算 char a=’65’;报错,用引号括住必须是单个字符 char a=’A’; 此时可以参加算术运算
由byte类型到char类型时要使用强制转换

  • 布尔
    boolean (true , false)
5.2 引用数据类型:数组、类和接口等除基本数据类型以外的

实例化:类名/接口名 对象名 = new 类名/接口名 ();
解释:

  • 类名/接口名 ():该对象的构造方法

  • 类名/接口名 对象名:定义一个引用变量

  • new:实例化

  • 等号:将引用变量与实例化对象关联起来

    jvm虚拟机为程序提供了方法区(数据共享区)存静态变量和字节码文件、栈区(压栈运行)、堆区(存储对象 数组)

5.2.1数组

数组用来存储一组相同数据类型的容器; 一旦数组声明以后,她的存储空间就是固定

  • 1、声明数组的公式:

    一维数组:
    数据类型【】 数组名=new 数据类型【数组的长度】;
    数据类型【】 数组名={数组元素的值,,}
    二维数组:
    数据类型【】【】 数组名=new 数据类型【数组的长度行】【数组的长度列】;
    数据类型【】【】 数组名={{数组元素的值,,},{} };

Length:数组的长度,一个属性
创建数组时注意一点默认值的问题:
int型默认值为零;char默认为0对应的字符;布尔默认为false;浮点型默认为0.0;字符串默认为null

  • 2、访问数组通过下标来访问
  • 3、打印数组名返回值为该数组的首地址

栈区:函数运行的区域
堆区:存储对象,数组

  • 4 、数组的排序
    1、冒泡排序
    相邻的元素两两比较,升序(小的上冒);降序(大的上冒),利用两个内层之间的数进行比较,内层循环执行完第一次选出一个最值放在最后的位置, 对于长度为n的数组,每次排完,循环了(n(n+1))/2次;
     一维数组利用循环嵌套:
             for(int i=0;i<arr.length-1;i++){
    	             for(int j=0;j<arr.length-1-i;j++){
    		               if(arr[j])>arr[j+1]){
    			                     m=arr[j];arr[j]=arr[j+1];arr[j+1]=m;
    		                }
    	             }		
    	      }

2、 选择排序
每两个都要比较,利用外层与内层的数进行比较,内层循环执行完第一次选出一个最值放在第一个位置, 对于长度为n的数组,每次排完,循环了(n(n+1))/2次;

for(int i=0;i<arr.length-1;i++){
		    for(int j=i+1;j<arr.length;j++){
			    if(arr[i]<arr[j]){
				     m=arr[i];arr[i]=arr[j];arr[j]=m;
			      }
		      }
		}  
  • 5、 数组的二分查找:前提有序的数组(类比二叉树查询法)
    借用三个下标:star,end,mid,mid=( star+end)/2;利用mid下标所对应的值与目标进行比较选择。
  int start=0;
      int n=所要选取的值;
  int end=arr.length-1;
  int mid=(start+end)/2;
  while(start<=end){
	  if(arr[mid]<n){
		  start=mid+1;
	  }else if(arr[mid]>n){
		  end=mid-1; 
	  }else{
		  System.out.println(arr[mid]);
	  }
	  mid=(start+end)/2;
  }
  if(start>end){
	  System.out.println("没有找到");
  }
6、算术运算符
 +  、-、  *、  / 、 %(求模 )、 ++ 、 -- 、 +=、  -= 
 两个整数相除得整数 整型与浮点型的运算结果一定为浮点型
7、逻辑运算符

注意参与逻辑运算的数据类型(表达式的值)必须是布尔型的才可以否则报错

  • 与运算 && :一假必假

  • 或运算 || :一真必真

  • 非运算 !

    面试 短路情况

8、比较运算符

、 < 、 = 、 >= 、 <= 、 != 、 ==

9、转义字符
  \n :  回车
  \t : 水平制表符(横向跳格)
  \r : 换行
  \f : 换页
  \' : 单引号
 \'' : 双引号
 \\ : 反斜杠

三、流程控制语句

1、条件选择
  • If选择语句
    注意:if语句可以嵌套
    1、 if(逻辑表达式或逻辑值){语句} 其实if语句就相当于逻辑与
    2、 if(){}else{} 其实if…else语句就相当于逻辑三目运算符
    3、 if(){}else if{} else if{}………else{}

  • switch选择语句
    switch(整型、字符型、字符串){
    case(): 语句:break;
    …………
    default: 语句 ;
    }
    switch中的break:若不加break,case会穿透(即会继续执行满足条件的下一个case的语句,程序不会报错但不符合逻辑)

2、循环结构 (做一些重复性的工作)

给循环体命名:循环体的名字冒号循环体
两个关键字:break:跳出当前循环体
break:循环体的名字 跳出该循环体
continue:跳出本次循环(continue之后的语句不再循环),但不跳出循环体
continue:循环体的名字 中止执行该循环体的本次循环

  • For循环语句

    用法: 一般在知道循环次数的情况下用
    格式:for(赋初值,循环条件判断,语句){ 循环体 }
    注意:当不知道循环次数时也可用for循环,非主流方式

  • While循环语句

    用法:一般在不知道循环次数的情况下用
    格式一、While(循环条件){循环体}
    格式二、do-while循环语句 , 一般在不知道循环次数的情况下用
    格式三、do{循环体}while(循环条件);

  • for…each循环:遍历数组

    例如: int[] arr=new int[5];
    for(int r:arr){}

java面向对象核心概念及应用

四、面向对象

1、对象的概念:属性(属性即成员变量)和方法(方法即行为)
  • 1、类的定义:

    类是一组对象所具有的相同属性和方法的抽象描述,对象是类的实例,在java的类中一般称作成员变量和成员方法,在类中成员变量一般设置为私有的实现封装

  • 2、方法的声明 :权限修饰符 返回值类型(参数列表){方法}

  • 3、权限修饰符:

    public,均可访问
    protected ,该类及其子类,或在同一包的类及不同包的子类下均可访问
    默认,相同包中的类均可访问
    private(私有的)只有本类内部的可以访问

    面试题:类的修饰符有:

    权限修饰符: public 、protect、friendly(默认)、private
    其他修饰符 : abstract 、 final 、 static 、 默认(default即什么都不加)

  • 3、创建对象
    用new关键字,注意同一个包下不用导包有类才能创建对象。
    访问类的属性和方法,通过点的方式

  • 4、抽象:将一组对象相同属性和方法抽象为一个类的过程。

  • 5、类的命名规则与变量的一样,(第一个字符只能是下划线,$,字母)

  • 6、类的成员变量中的参数问题:数据类型 参数名

  • 7、对于private修饰的属性和方法,访问时使用get-set方法,使用快捷键:

    Alt+shift+s调出sourse或者右键选择sourse ,选择Generate Getters and Setters方法,选中select all,点击ok即可;

    注意:如果需要在本类中调用private修饰的成员方法,只需方法名(参数);即可

  • 8、this关键字:指的是通过该类创建的对象,当成员变量与形参一样时使用,加在成员变量前面区分。

    用法:
      1、this.成员变量:当成员变量与局部变量重名时,特指访问成员变量
       2、this.成员方法(参数):把当前对象的引用作为参数传递给另一个方法
      3、this(参数):在一个构造方法中调用同类中的另一个构造方法,此时必须放在方法体首句
      4、在静态代码段中禁止使用this关键字

  • 9、static关键字:静态,可以修饰变量和方法,存储在方法和数据的共享区,在main运行之前就被创建
    (面试)注意:对于静态代码中不能访问非静态的成员变量和成员方法

    1、用static修饰的成员变量和成员方法属于类,不需要创建对象即可调用,直接用类名调用
    2、static一般用来修饰公共的常量和一些不需要对象的情况下调用的方法
    3、static也可以用来修饰一些区域,此区域称为静态代码块优先于对象存在
    4、类变量(全局变量/静态变量):用static修饰的变量,程序运行时创建,结束时销毁
       注意: 类变量赋初值是:static{类变量=值;}

  • 10、java变量:关于变量的作用域是离它最近的花括号,

    1、局部变量:在方法中声明的变量,作用域是方法体;
          局部变量在创建时默认不会赋值,在使用时需要赋初值,数组除外(引用型的数组默认赋值为null)

    2、成员变量:在类中声明的变量描述该类所共有的属性,既可以是基本数据类型也可以是引用数据类型作用域是整个类
    /
    3、方法参数:作用域整个方法
    /
    4、异常处理的参数:作用域是catch后面跟随的处理模块

  • 11、JVM的运行过程

    程序运行时,先进入到栈区称为压栈运行,遇到new关键字在堆区里面,开辟一 个存储空间用来存储对象的成员变量,此时对象名是一个指向堆区存储此对象的首地址,然后给成员变量赋初值(当遇到私有变量时用get-set方赋初值),然后其它语句依次压栈运行,等程序结束后,在栈区先进来的后出去,new语句最后出去;对于static修饰的变量和方法存在方法区; jvm虚拟机为程序提供了方法区(数据共享区)存静态变量和字节码文件、栈区(压栈运行)、堆区(存储对象 数组)

2 java的重要方法
  • 1、构造方法:构造方法在创建对象的同时运行,多数用来进行对象的初始化

    1、权限修饰符 类名(){} 注意构造方法名首字母大写必须类名相同

    2、若在类中没有声明构造方法,jvm会自动生成一个无参的构造方法,若手动添加了,则jvm不会自动生成无参的构造方法(为了避免报错,都要有无参的构造方法)

    3、使用快捷键:
       1、在本类添加本类中成员变量的构造方法:Alt+shift+s调出sourse或者右键选择sourse ,选择Generate constructor using files
      2、在子类添加父类中成员变量的构造方法:Alt+shift+s调出sourse或者右键选择sourse ,选择Generate constructor from superclass

  • 2、成员方法:类成员的方法,实现特殊的功能

面试题 方法的重载与重写

  • 3、方法的重载:

    同一类中,方法名相同,而参数列表不同(参数列表指参数类型和个数),方法重载跟返回值类型没有关系,当同一方法的参数类型不同时,而参数的顺序不一样时也叫重载

    注意:可变参的处理,通过一个数组来存,此时方法的参数为(数据类型… 参数名称)

  • 4、方法的重写:

    前提是必须存在继承(和实现)关系,且子类方法的访问权限不能小于父类的,返回值类型也必须一样,一般用在抽象类中,在其他类中也可以重写,

  • Finalize函数 1、finallize ();java中回收垃圾的函数,jvm自动添加

3、java的三要素:

  • 1、封装:

    将对象的一些属性和方法封装到一个类中,即隐藏一些属性和方法或实现的细节,不让用户知道

  • 2、继承:

    子类可以继承所有父类非private的成员变量和成员方法,顶级父类java.lang.Object 即object类,

    1、extends关键字 子类名 extends 父类名,
         单继承:一个类只能继承一个父类,但是一个父类可以被多个子类继承。
         多重继承:c继承b继承a

    2、super关键字:实现对父类成员的访问 子类中调用父类的构造方法用super(参数); super.成员变量或方法:调用父类的成员方法和成员变量

    3、就近原则:当子类的成员变量与父类的成员变量相同时,用子类的对象访问成员变量时,访问的是子类的成员变量(成员方法也一样)

    4、final关键字:最终的,不能改变的,最终类不能被继承的类,

    5、构造器(构造方法):子类是不能继承父类的构造器的,若在父类中的构造器是有参数的则在子类的构造器中,则必须通过super关键字显示的调用父类的构造器(super()必须放在第一行);若父类中有无参的构造器,则在子类的构造器中,没有必要通过super关键字,系统会自动调用

    6、继承的优点:提高了代码的可重复性,简化了代码,扩展了类的功能,缺点是提高了耦合性

    7、当创建子类时,先调用父类的构造方法,再调用子类的构造方法

    8、子类创建对象时需要创建有参数的构造方法时,父类也必须有带参数的构造方法 ,反之若父类只有带参数的构造方法,子类也必须有带参数的构造方法,所以一般编写代码时父类,有参数的和无参数的构造方法都写上。

  • 3、多肽:(动态绑定)

    1、多肽的定义:只允许不同类的对象对同一消息做出反应,即同一消息可以根据发送对象的不同而采用多种不同的行为方式(发送消息指函数调用)【同一个对象的不同状态】

    2、多肽的作用消除类型之间的耦合关系,提升了开发效率

    3、实现多态的三个必要条件:
         1、要有继承或者实现接口(接口也是一种继承关系)
          2、要有重写和重载
          3、父类的引用指向子类对象(向上转型)即 父类的数据类型 对象名=new 子类的数据类型();

    4、多肽的调用成员方法:编译时期首先得看父类有没有此方法,如果父类没有,则会编译失败,运行时期看子类有没有重写此方法,如果子类重写了父类的方法,那么多肽的调用会走子类的方法,如果没有重写则会走父类的方法;

    5、多肽访问成员变量: 访问的是父类的成员变量,若没有则报错

    6、若多肽想调用子类中特有的方法,则需要向下转型:(子类的数据类型)父类的对象
          调用方法:((子类的数据类型)父类的对象).方法

    7、 instanceof 函数 : a instanceof b :判断a 是否是b的一个实例,返回一个布尔值

4、抽象类和接口
  • 抽象类:不完整的类(包含抽象方法的类)
    抽象方法:权限修饰符 abstract 返回值类型 方法名(); 没有方法体

    1、 abstract关键字:(抽象的)修饰抽象类和抽象方法

    2、父类是抽象类的,子类也必须定义为抽象类或者将父类所有的抽象方法重写

    3、抽象类不能创建对象,但是可以有构造方法,若父类是抽象类,子类创建对象时需要创建有参数的构造方法时,父类也必须有带参数的构造方法 ,

    4、抽象类的权限修饰符只能用public protected 或者默认

  • 接口:定义特殊功能的特殊类,用来扩充类的功能

    1、关键字interface替换了class ,接口中的成员变量都是常量,即用public static final修饰

    2、关键字implements:实现类 implements接口1,接口2….

    3、接口中的方法必须都是抽象方法:public abstract 返回值类型 方法名(参数);
    \
    4、接口不能创建对象,只能通过接口的实现类创建对象;实现类中必须重写接口中的所有抽象方法

    5、一个实现类可以实现多个接口,接口可以有继承关系,接口也为多肽的实现提供了可能

    6、接口可以多继承:一个接口可以继承多个接口。

    7、接口:功能的集合。一+般情况下我们优先选用接口,相对于抽象类降低了耦合度,如果子类需要一些共有的成员变量,则选用抽象类

5、内部类(内嵌类)

五、字符串(字符的序列类似于字符的数组)final修饰的

1、字符串的本质时是字符数组,在java中一个字符串还是一个实例对象,字符串属于引用数据类型,
String类创建的字符串是不可改变的,StringBuffer与StringBuilder类创建的字符串是可以修改的,也称作字符 串缓冲区一般用来文件的读取
2、字符串的最重要的特征是不可改变性()
Java 分为堆区(new创建的对象),栈区(指针),数据区(常量),代码区(代码)

  • 1、声明字符串:
    用关键字new声明,对象存在堆区
    直接赋值String a= “ ”;称为字符串常量或字面量,存放在数据区

    注意:
    在 String a= new String(“abc”);中有两个对象,New是一个对象,“abc”也是一个对象,占用了三个区,在java中除了基本数据类型外其他的都是对象
    字符串若不付初值编译时不会出错,运行时会报错

  • 2、字符串的拼接
    使用加号,原理在内存中并不是简单的在原位置直接连接,而是重新分配空间,原来的空间就变成垃圾将被自动回收(没有指针指向的变量是没有用的)

  • 3、字符串的几种重要的方法

返回值方法描述
intlength()取得字符串的字符长度
booleanequals(String str)判断两个字符串是否相同
StringtoLowerCase()将符串中的英文字符转换为小写
StringtoUpperCase()将符串中的英文字符转换为大写
charcharAt(int index)返回指定索引处的 char 值
Stringconcat(String str)将指定字符串连接到此字符串结尾处
booleancontains(String s)当此字符串包含 char 值的指定序列时,才返回 true。
booleanendsWith(String suffix)测试此字符串是否以指定的后缀结束
booleanstartsWith(String prefix)测试此字符串是否以指定的前缀开始
booleanequalsIgnoreCase(String str)将此 String 与另一个 String 进行比较,不考虑大小写
byte[]getBytes()将此 String 解码为字节序列,将结果存储到一个新的字节数组中。
intindexOf(String str)返回第一次出现的指定子字符串在此字符串中的索引。
intlastIndexOf(String str)返回在此字符串中最右边出现的指定子字符串的索引。
Stringreplace(char old, char new)用 newChar 替换此字符串中出现的所有 oldChar 。
String[]split(String regex)根据给定的正则表达式的匹配来拆分此字符串。
Stringsubstring(int begin, int end)返回一个新字符串,它是此字符串的一个子字符串。
char[]toCharArray()将此字符串转换为一个新的字符数组。
注意:equals与双等号有什么不同,为什么不用等号?
  两个对象型的数据变量用双等号比较的是两个对象的内存地址是否相同,而基本数据变量比较的是值。创建字面量时,如果内存里面已存在相同的字面量,就不会再重新分配内存,直接用已存在的。
  • 将字符串转化为其它数据类型的方法
返回值方法描述
byteByte.parseByte(String src)将字符串转换为 byte
shortShort.parseShort(String src)将字符串转换为 short
intInteger.parseInt(String src)将字符串转换为 int
longLong.parseLong(String src)将字符串转换为 long
floatFloat. parseFloat(String src)将字符串转换为 float
doubleDouble.parseDouble(String src)将字符串转换为 double
  • 3、 StringBuilder类与StringBuffer类:改变了字符串的不可改变性特征,高效使用内存 不同
不同StringBuffer 字符串变量StringBuilder 字符串变量
线程比较线程安全非线程安全
速度比较StringBuilder速度较快StringBuffer相对较慢

怎么理解Stringbuffer 是线程安全的 stringbuilder是线程不安全的

1、 多个线程操作同一个StringBuffer对象会顺序进行(内部很多方法是同步方法)
2、多个线程操作同一个StringBuilder是同时的,这时候可能出现与预期不符合的结果
3、StringBuilder由于是线程不安全的,所以性能更好。
4、大部分情况下都是单线程操作字符串,这时候选择StringBuilder。

常用的方法

Append():在末尾添加元素
Delete(a,b):删除从A到B
Insert(索引,值):在莫个位置插入元素
Reverse():将字符串反转输出

六、java中常用的工具类

1、system类 在java的lang包下(静态类)

System是一个静态类,直接使用,不用实例化 Property(属性)

  • 常用方法:

    1、	System.exit():设置过多少毫秒以后退出   
    2、	System.gc():清除垃圾 
    
2、math类数学工具类 在java的lang包下(静态类)
  • 常用方法
    在这里插入图片描述

  • 关于大数据的问题:
    double双精度,只能算16位以内的,但是超过了16位,就需要用BigInteger和BigDecimal
    BigInteger和BigDecimal在java.Math包下
    方法: 1、longValue() 将此BigInteger转换为 long 。
    在这里插入图片描述

  • BigInteger类:表示超大的整数

    构造方法:BigInteger(String val) 将BigInteger的十进制字符串表示形式转换为BigInteger。

  • BigDecimal类:超大的浮点数

    方法:setScale(int n) 保留几位小数

3、日期Data类和Calendar类

Java.until包中包括日期类(data),日历类(calendar),随机数类(random),堆栈(statck),向量,哈希表等

Data类用来表示日期

Java.text包下的抽象类DataFormat中的实现类SimpleDataFormat,来对时间进行格式化

  • 1、构造方法
    New Data()可以获取系统当前的时间
    在这里插入图片描述

  • 2、方法

    注意:after与before返回的是一个布尔值;compareTo返回0代表相等,1代表大于,-1代表小于

    在这里插入图片描述

Calendar日历类是一个抽象类
  • 1、定义

年(yyyy)
月份(MM)在计算机中取值范围是0-11;所以如果想获实际月份需要计算机中的月份加1
日(dd)小时(hh)分钟(mm)秒(s) 大写的H代表24进制;小写的h代表12进制

在这里插入图片描述

  • 2、构造方法
    在这里插入图片描述

  • 3、方法
    在这里插入图片描述

4、Random类生成随机数
  • 1、定义
    在这里插入图片描述

  • 2、两种构造方法
    Random() 创建一个新的随机数生成器。种子默认为当前时间的毫秒数
    Random(long seed) 使用单个 long种子创建一个新的随机数生成器。

  • 3、方法:
    1、nextInt() 产生随机整数
    2、nextFloat() 产生随机浮点数
    3、nextDouble() 产生随机双精度浮点数

Java语言的高级特性

七、java中的集合类与泛型

在这里插入图片描述

Collection:集合是一个接口,存储单个的值 List、set是一个接口,他们下面的是一个实现类
Map:以键值对的方式存储在集合里面

笔试:
Java Collections Framework

在这里插入图片描述

一、定义
  • 数组是存储相同数据类型的容器也能存储对象但是功能有限

      缺点:数组大小一旦确定不能更改,数组不能删除添加某个元素,只能索引或设置某个元素
    
  • 集合是用来存储对象的容器,每个集合类都包含很多操作方法便于操作对象

      1、优点:提供了动态数组功能和更完善的遍历方法
      2、集合下的方法:size():获取集合的长度
                       add():添加元素
                       remove(下标):删除元素,返回删除的元素
                    Iterator():返回一个在一组 T 类型的元素上进行迭代的迭代器
     3、限制集合所存储的数据类型利用泛型:ArraryList<Float>=new ArraryList();  
    
  • Java中的自动装箱与拆箱,使集合可以存储基本数据类型的数据

       装箱:将基本数据类型包装成对象
       基本数据类型所对应的包装类:Byte 、Short  Integer  Long  Character  Double  Float  Boolen 
       注意:只有两两对应的才可以自动装箱与拆箱,否则需要强制转换(long)object;                   
    
二、迭代器:是遍历集合的一个非常好的方法 iterable(迭代)
概述及分类
[1].迭代器模式

把访问逻辑从不同类型的集合类中抽取出来,从而避免向外部暴露集合的内部结构。在java中它是一个对象,其目的是遍历并选中其中的每个元素,而使用者(客户端)无需知道里面的具体细节。

[2].Iterator
  • 概述

Collection集合元素的通用获取方式:在取出元素之前先判断集合中有没有元素。如果有,就把这个元素取出来,继续再判断,如果还有就再取出来,一直把集合中的所有元素全部取出来,这种取出元素的方式专业术语称为迭代。

java.util.Iterator:在Java中Iterator为一个接口,它只提供了迭代的基本规则。在JDK中它是这样定义的:对Collection进行迭代的迭代器。迭代器取代了Java Collection Framework中的Enumeration。

Collection中有一个抽象方法iterator方法,所有的Collection子类都实现了这个方法;返回一个Iterator对象

  • 定义:
package java.util;

public interface Iterator<E> {

    boolean hasNext();//判断是否存在下一个对象元素

    E next();//获取下一个元素

    void remove();//移除元素

}

  • 异常处理

在使用Iterator的时候禁止对所遍历的容器进行改变其大小结构的操作。例如: 在使用Iterator进行迭代时,如果对集合进行了add、remove操作就会出现ConcurrentModificationException异常。

在进行集合元素取出的时候,如果集合中没有元素了,还继续使用next()方法的话,将发生NoSuchElementException没有集合元素的错误

修改并发异常:在迭代集合中元素的过程中,集合的长度发生改变(进行了元素增加或者元素删除的操作), 增强for的底层原理也是迭代器,所以也需要避免这种操作;

解决以上异常的方法:使用ListIterator

  • 其他

任何集合都有迭代器。

任何集合类,都必须能以某种方式存取元素,否则这个集合容器就没有任何意义。

迭代器,也是一种模式(也叫迭代器模式)。迭代器要足够的“轻量”——创建迭代器的代价小。

[3].Iterable(1.5)
  • 概述

Java中还提供了一个Iterable接口,Iterable接口实现后的功能是‘返回’一个迭代器,我们常用的实现了该接口的子接口有:Collection、List、Set等。该接口的iterator()方法返回一个标准的Iterator实现。实现Iterable接口允许对象成为Foreach语句的目标。就可以通过foreach语句来遍历你的底层序列。

Iterable接口包含一个能产生Iterator对象的方法,并且Iterable被foreach用来在序列中移动。因此如果创建了实现Iterable接口的类,都可以将它用于foreach中。

  • 定义:
    Package java.lang; import java.util.Iterator; public interface Iterable<T> {    Iterator<T> iterator(); }

Iterable是Java 1.5的新特性, 主要是为了支持forEach语法, 使用容器的时候, 如果不关心容器的类型, 那么就需要使用迭代器来编写代码. 使代码能够重用.

使用方法很简单:

  List<String> strs = Arrays.asList("a", "b", "c"); for (String str: strs) {    out.println(str); }
  • 好处:代码减少,方便遍历

  • 弊端:没有索引,不能操作容器里的元素

增强for循环底层也是使用了迭代器获取的,只不过获取迭代器由jvm完成,不需要我们获取迭代器而已,所以在使用增强for循环变量元素的过程中不准使用集合对象对集合的元素个数进行修改;

[4].forEach()(1.8)

使用接收lambda表达式的forEach方法进行快速遍历.

List<String> strs = Arrays.asList("a", "b", "c"); // 使用Java 1.8的lambda表达式 strs.forEach(out::println);
[5].Spliterator迭代器

-概述

Spliterator是1.8新增的迭代器,属于并行迭代器,可以将迭代任务分割交由多个线程来进行。

Spliterator可以理解为Iterator的Split版本(但用途要丰富很多)。使用Iterator的时候,我们可以顺序地遍历容器中的元素,使用Spliterator的时候,我们可以将元素分割成多份,分别交于不于的线程去遍历,以提高效率。使用 Spliterator 每次可以处理某个元素集合中的一个元素 — 不是从 Spliterator 中获取元素,而是使用 tryAdvance() 或 forEachRemaining() 方法对元素应用操作。但Spliterator 还可以用于估计其中保存的元素数量,而且还可以像细胞分裂一样变为一分为二。这些新增加的能力让流并行处理代码可以很方便地将工作分布到多个可用线程上完成

[6].ListIterator
  • 概述

ListIterator是一个更强大的Iterator子类型,能用于各种List类访问,前面说过Iterator支持单向取数据,ListIterator可以双向移动,所以能指出迭代器当前位置的前一个和后一个索引,可以用set方法替换它访问过的最后一个元素。我们可以通过调用listIterator方法产生一个指向List开始处的ListIterator,并且还可以用过重载方法listIterator(n)来创建一个指定列表索引为n的元素的ListIterator。

ListIterator可以往前遍历,添加元素,设置元素

  • Iterator和ListIterator的区别:

两者都有next()和hasNext(),可以实现向后遍历,但是ListIterator有previous()和hasPrevious()方法,即可以实现向前遍历

ListIterator可以定位当前位置,nextIndex()和previous()可以实现

ListIterator有add()方法,可以向list集合中添加数据

都可以实现删除操作,但是ListIterator可以实现对对象的修改,set()可以实现,Iterator仅能遍历,不能修改

[7]Fail-Fast
  • 概述

类中的iterator()方法和listIterator()方法返回的iterators迭代器是fail-fast的:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。

  • 迭代器与枚举有两点不同:
  1. 迭代器在迭代期间可以从集合中移除元素。

  2. 方法名得到了改进,Enumeration的方法名称都比较长。
 

迭代器的好处:屏蔽了集合之间的不同,可以使用相同的方式取出

个人总结
  • 1、迭代器在遍历时不能进行增删改等操作。指针最开始在集合中第一个元素的前面

  • 2、代器下有两个方法:
    1、hasNext()判断是否有下一个元素,返回一个布尔值,多次使用指针不会移动
    2、Next():直接返回下一个元素,多次使用指针会移动

    注意:为避免输出集合中的最后一个元素后面的标点符号,需要利用集合中的size()方法

  用法:Map<Integer, String> map = new HashMap<Integer, String>();
		         map.put(1, "a");
		         map.put(2, "b");
		         map.put(3, "ab");
		         map.put(4, "ab");
		         map.put(4, "ab");
		Set<Integer> key = map.keySet();//将map中的键,转存到一个list或者set集合
		Iterator<Integer> it = key.iterator();//生成迭代器
		while(it.hasNext()) {
			int next = it.next();
			String s = map.get(next);
			System.out.println(s);
}
}回去看看for循环与while循环的实现及优缺点
三、集合类的概念
(1).集合类的作用

集合类也叫做容器类,和数组一样,用于存储数据,但数组类型单一,并且长度固定,限制性很大,而集合类可以动态增加长度。

集合存储的元素都是对象(引用地址),所以集合可以存储不同的数据类型,但如果是需要比较元素来排序的集合,则需要类型一致。

集合中提供了统一的增删改查方法,使用方便。

支持泛型,避免数据不一致和转换异常,还对常用的数据结构进行了封装。

所有的集合类的都在java.util包下。

(2)集合框架体系的组成(结构图请看上面)

集合框架体系是由Collection、Map(映射关系)和Iterator(迭代器)组成,各部分的作用如下所示。

[1]Collection体系中有三种集合:Set、List、Queue
 Set(集): 元素是无序的且不可重复。

 List(列表):元素是有序的且可重复。

 Queue(队列):封装了数据结构中的队列。
[2]Map体系
 Map用于保存具有映射关系的数据,即key-value(键值对)。Map集合的key是唯一的,不可重复,而value可以重复。所以一个value可以对应多个key。

 Map体系除了常用类之外,还有Properties(属性类)也属于Map体系。
[3]Iterator(迭代器)

请查看上面!

(3)Collection的由来

由于数组中存放对象,对对象操作起来不方便。java中有一类容器,专门用来存储对象。

集合可以存储多个元素,但我们对多个元素也有不同的需求

多个元素,不能有相同的

多个元素,能够按照某个规则排序

针对不同的需求:java就提供了很多集合类,多个集合类的数据结构不同。但是,结构不重要,重要 的是能够存储东西,能够判断,获取.

把集合共性的内容不断往上提取,最终形成集合的继承体系---->Collection

并且所有的Collection实现类都重写了toString()方法.

(4)集合和数组
  • 集合与数组的区别:

1.数组的长度固定的,而集合长度时可变的

2.数组只能储存同一类型的元素,而且能存基本数据类型和引用数据类型。集合可以存储不同类型的元素,只能存储引用数据类型

集合类和数组不一样,数组元素既可以是基本类型的值,也可以是对象(实际上保存的是对象的引用变量);而集合只能保存对象。

  • 数组和集合的主要区别包括以下几个方面:

一:数组声明了它容纳的元素的类型,而集合不声明。这是由于集合以object形式来存储它们的元素。

二:一个数组实例具有固定的大小,不能伸缩。集合则可根据需要动态改变大小。

三:数组是一种可读/可写数据结构没有办法创建一个只读数组。然而可以使用集合提供的ReadOnly方只读方式来使用集合。该方法将返回一个集合的只读版本。

  • 集合的作用:

如果一个类的内部有很多相同类型的属性,并且他们的作用与意义是一样的,比如说学生能选课学生类就有很多课程类型的属性,或者工厂类有很多机器类型的属性,我们用一个类似于容器的集合去盛装他们,这样在类的内部就变的井然有序---------这就是:

1、在类的内部,对数据进行组织的作用。
2、简单而快速的搜索查找其中的某一条元素
3、有的集合接口,提供了一系列排列有序的元素,并且可以在序列中间快速的插入或者删除有关元素。
4、有的集合接口在其内部提供了映射关系的结构,可以通过关键字(key)去快速查找对应的唯一对象,而这个关键可以是任意类型的。

(5)泛型与集合的区别

泛型听起来很高深的一个词,但实际上它的作用很简单,就是提高java程序的性能。

比如在计算机中经常用到一些数据结构,如队列,链表等,而其中的元素以前一般这么定义:object a=new object();

这样就带来一个严重的问题,用object来表示元素没有逻辑问题,但每次拆箱、封箱就占用了大量的计算机资源,导致程序性能低下,而这部分内容恰恰一般都是程序的核心部分,如果使用object,那么程序的表现就比较糟糕。

而使用泛型则很好的解决这个问题,本质就是在编译阶段就告诉编译器,数据结构中元素的种类,既然编译器知道了元素的种类,自然就避免了拆箱、封箱的操作,从而显著提高java程序的性能。

比如List就直接使用string对象作为List的元素,而避免使用object对象带来的封箱、拆箱操作,从而提高程序性能。

四、collection是list和set的父接口
概述
  Collection中重写了toString方法,所以打印集合时,直接写集合名即可
    1、list接口:有序而且可以存储重复的元素,是按照对象被放置容器的先后顺序来排列对象,也可以插入到指定位置
    2、set接口:无序而且一个不包含重复元素的集合,至少包含一个null值元素
Collection与Collections
Collection

Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements)。一些 Collection允许相同的元素而另一些不行。一些能排序而另一些不行。Java SDK不提供直接继承自Collection的类, Java SDK提供的类都是继承自Collection的“子接口”如List和Set。

所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的Collection,有一个Collection参数的构造函数用于创建一个新的 Collection,这个新的Collection与传入的Collection有相同的元素。后一个构造函数允许用户复制一个Collection。

如何遍历Collection中的每一个元素?不论Collection的实际类型如何,它都支持一个iterator()的方法,该方法返回一个迭代子,使用该迭代子即可逐一访问Collection中每一个元素。典型的用法如下:

    Iterator it = collection.iterator(); // 获得一个迭代子

    while(it.hasNext()) {

      Object obj = it.next(); // 得到下一个元素
      }

由Collection接口派生的两个接口是List和Set。

Collection返回的是Iterator迭代器接口,而List中又有它自己对应的实现–>ListIterator接口 Collection。标识所含元素的序列,这里面又包含多种集合类,比如List,Set,Queue;它们都有各自的特点,比如List是按顺序插入元素,Set是不重复元素集合,Queue则是典型的FIFO结构

  • Collection接口描述:

Collection接口常用的子接口有List 接口和Set接口: 

     List接口中常用的子类有:ArrayList类(数组列表)和LinkedList(链表)

    Set接口中常用的子类有:HashSet (哈希表)和LinkedHashSet(基于链表的哈希表)

Collection 层次结构 中的根接口。Collection 表示一组对象,这些对象也称为 collection 的元素。一些 collection 允许有重复的元素,而另一些则不允许。一些 collection 是有序的,而另一些则是无序的。JDK 不提供此接口的任何直接 实现:它提供更具体的子接口(如 Set和 List)实现。此接口通常用来传递 collection,并在需要最大普遍性的地方操作这些 collection。(面向接口的编程思想)

Collections
  • [1]排序操作

Collections提供以下方法对List进行排序操作

void reverse(List list):反转

void shuffle(List list),随机排序

void sort(List list),按自然排序的升序排序

void sort(List list, Comparator c);定制排序,由Comparator控制排序逻辑

void swap(List list, int i , int j),交换两个索引位置的元素

void rotate(List list, int distance),旋转。当distance为正数时,将list后distance个元素整体移到前面。当distance为负数时,将 list的前distance个元素整体移到后面。

  • [2]查找,替换操作

int binarySearch(List list, Object key), 对List进行二分查找,返回索引,注意List必须是有序的

int max(Collection coll),根据元素的自然顺序,返回最大的元素。 类比int min(Collection coll)

int max(Collection coll, Comparator c),根据定制排序,返回最大元素,排序规则由Comparatator类控制。类比int min(Collection coll, Comparator c)

void fill(List list, Object obj),用元素obj填充list中所有元素

int frequency(Collection c, Object o),统计元素出现次数

int indexOfSubList(List list, List target), 统计targe在list中第一次出现的索引,找不到则返回-1,类比int lastIndexOfSubList(List source, list target).

boolean replaceAll(List list, Object oldVal, Object newVal), 用新元素替换旧元素。

  • [3]同步控制

Collections中几乎对每个集合都定义了同步控制方法, 这些方法,来将集合包装成线程安全的集合

 SynchronizedList(List);

 SynchronizedSet(Set;

  SynchronizedMap(Map);
SynchronizedMap和ConcurrentHashMap 区别

Collections.synchronizedMap()和Hashtable一样,实现上在调用map所有方法时,都对整个map进行同步,而ConcurrentHashMap的实现却更加精细,它对map中的所有桶加了锁。所以,只要要有一个线程访问map,其他线程就无法进入map,而如果一个线程在访问ConcurrentHashMap某个桶时,其他线程,仍然可以对map执行某些操作。这样,ConcurrentHashMap在性能以及安全性方面,明显比Collections.synchronizedMap()更加有优势。同时,同步操作精确控制到桶,所以,即使在遍历map时,其他线程试图对map进行数据修改,也不会抛出ConcurrentModificationException。

ConcurrentHashMap从类的命名就能看出,它必然是个HashMap。而Collections.synchronizedMap()可以接收任意Map实例,实现Map的同步


线程安全,并且锁分离。ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的hashtable,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。

一、list接口下的实现类
  • 概述

List:有序(元素存入集合的顺序和取出的顺序一致),元素都有索引。元素可以重复。

List本身是Collection接口的子接口,具备了Collection的所有方法。

List的特有方法都有索引,这是该集合最大的特点。

List集合支持对元素的增、删、改、查。

List中存储的元素实现类排序,而且可以重复的存储相关元素。

次序是List最重要的特点:它保证维护元素特定的顺序。

List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。

和下面要提到的Set不同,List允许有相同的元素。

除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个 ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素,还能向前或向后遍历。

  • 优缺点

优点:操作读取操作效率高,基于数组实现的,可以为null值,可以允许重复元素,有序,异步。

缺点:由于它是由动态数组实现的,不适合频繁的对元素的插入和删除操作,因为每次插入和删除都需要移动数组中的元素。

  • 方法:
    get(下标):获得集合的元素
    set(int index, E element) 用指定元素替换列表中指定位置的元素(可选操作)。
1、Arraylist对象是长度可变的对象引用数组,类似于动态数组。

1、继承了AbstractList类并实现了list接口;随着元素的添加,元素列表会随着扩展;访问和遍历对象时,它有更好的性能,能够快速随机访问对象;对于删除和插入对象时操作速度很慢,因为它是基于数组实现的
2、ArraryList的方法有:add get iterator hasNext next set(下标,元素):更改集合的元素
3、遍历ArraryList: for循环、while循环、for-each(写起来简单但获取不到索引值)
注意:可以利用迭代器中的方法(获取不到索引值)
4、按照元素放进去的先后顺序存储访问

2、Linkedlist对象是长度可变的对象引用数(类比数据结构中的双向链表)删除插入非常快,访问非常慢

特点是:特别区分列表的头位置和尾位置的概念,提供了在头尾增、删和访问元素的方法,加入了特有方法有:addFirst() addLast() getFirst() getLast() removeFirst() removeLast()
Linkedlist:实现简单的先进后出类

3、vector

以前还能见到Vector和Stack,但Vector太过古老,被ArrayList取代,所以这里不讲;而Stack已经被ArrayDeque取代。

对于想在迭代器迭代过程中针对集合进行增删改的,可以通过返回ListIterator来操作。

Vector:底层的数据结构就是数组,线程同步的,Vector无论查询和增删都巨慢。

Vector:是按照原数组的2倍延长。

Vector是基于线程安全的,效率低 元素有放入顺序,元素可重复

Vector可以由我们自己来设置增长的大小,ArrayList没有提供相关的方法。

Vector相对ArrayList查询慢(线程安全的)

Vector相对LinkedList增删慢(数组结构)

Vector:底层结构是数组,线程是安全的,添加删除慢,查找快,(同ArrayList)

ArrayList 和Vector是采用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,都允许直接序号索引元素,但是插入数据要涉及到数组元素移动等内存操作,所以索引数据快,插入数据慢,Vector由于使用了synchronized方法(线程安全)所以性能上比ArrayList要差,LinkedList使用双向链表实现存储,按序号索引数据需要进行向前或向后遍历,但是插入数据时只需要记录本项的前后项即可,所以插入数度较快。

Vector 是矢量队列,它是JDK1.0版本添加的类。继承于AbstractList,实现了List, RandomAccess, Cloneable这些接口。

Vector 实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在Vector中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。

Vector 实现了Cloneable接口,即实现clone()函数。它能被克隆。

由Vector创建的Iterator,当一个Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出ConcurrentModificationException,因此必须捕获该异常。

二、Set接口下的实现类
既然不能存储重复元素,所以有equals()方法    
  • 特点

无序(存入和取出顺序有可能不一致),不可以存储重复元素。必须保证元素唯一性。

元素无放入顺序,元素不可重复(注意:元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的)

Set具有与Collection完全一样的接口,因此没有任何额外的功能,只是行为不同。这是继承与多态思想的典型应用:表现不同的行为。

Set不保存重复的元素(至于如何判断元素相同则较为负责)

存入Set的每个元素都必须是唯一的,因为Set不保存重复元素,加入Set的元素必须定义equals()方法以确保对象的唯一性。

Set 是基于对象的值来确定归属性的。

Set本身有去重功能是因为String内部重写了hashCode()和equals()方法,在add里实现了去重, Set集合是不允许重复元素的,但是集合是不知道我们对象的重复的判断依据的,默认情况下判断依据是判断两者是否为同一元素(euqals方法,依据是元素==元素),如果要依据我们自己的判断来判断元素是否重复,需要重写元素的equals方法(元素比较相等时调用)hashCode()的返回值是元素的哈希码,如果两个元素的哈希码相同,那么需要进行equals判断。【所以可以自定义返回值作为哈希码】 equals()返回true代表两元素相同,返回false代表不同。

set集合没有索引,只能用迭代器或增强for循环遍历

set的底层是map集合

Set最多有一个null元素

必须小心操作可变对象(Mutable Object)。如果一个Set中的可变元素改变了自身状态导致Object.equals(Object)=true将导致一些问题。

Set具有与Collection完全一样的接口,没有额外的任何功能。所以把Set就是Collection,只是行为不同(这就是多态);Set是基于对象的值来判断归属的,由于查询速度非常快速,HashSet使用了散列,HashSet维护的顺序与TreeSet或LinkedHashSet都不同,因为它们的数据结构都不同,元素存储方式自然也不同。TreeSet的数据结构是“红-黑树”,HashSet是散列函数,LinkedHashSet也用了散列函数;如果想要对结果进行排序,那么选择TreeSet代替HashSet是个不错的选择

HashSet
  • 1、HashSet对象由哈希表(实际上是一个 HashMap 实例)支持,默认有一个自然顺序。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用 null 元素。

      toArrary():将集合转换为数组
    
TreeSet
  • 2、TreeSet对象使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator 进行排序,具体取决于使用的构造方法。

       TreeSet(Comparator<? super E> comparator) 
                 构造一个新的空 TreeSet,它根据指定比较器进行排序
    

    1、存在Treeset中的对象,一般都要继承一个Comparable的接口重写compareTo方法用来排序

 compareTo(T o)比较此对象与指定对象的顺序。			
    			public int compareTo(User u) {		
    	              //int age = u.age;		
    	             int num = this.age-u.age;		
    	             return num==0?this.name.compareTo(u.name):num;
    	             }
    	             

比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。
2、Comparator接口(匿名内部类)
compare(T o1, T o2) 比较用来排序的两个参数。在初始化对象时用
比较用来排序的两个参数。根据第一个参数小于、等于或大于第二个参数分别返回负整数、零或正整数。

例子:
  TreeSet<User> set = new TreeSet<>(new Comparator<User>() { 
public int compare(User o1, User o2) {	
				int num = o1.getAge()-o2.getAge();				
				return num==0?o1.getName().compareTo(o2.getName()):num;
			}
		});
五、Map接口

1、统一采用键值对的方式存储,键不能重复,值可以重复
2、map中的泛型必须是引用型,可以很复杂
3、put(k,v) 添加
4、遍历
1、keyset():返回返回此映射中包含的键的 Set集合,get(k)得到键对应的值
2、entrySet(): 返回此映射中包含的映射关系的 Set 视图,即Set<Map.Entry<K,V>>;遍历set集合利用entry下的getKey() 返回与此项对应的键和getValue()
返回与此项对应的值。

  • 1、Hashmap 无序的,随机插入;(基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和
    null 键。(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable
    大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。)

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

     1、TreeMap(Comparator<? super K> comparator) 构造一个新的、空的树映射,该映射根据给定比较器进行排序。
    
六、泛型(genneracity):仅能用引用的数据类型。
1、定义:

泛型是指参数化类型,即将程序中的数据类型指定为一个参数,用<>定义形式类型参数,在程序执行前进行指明(泛型是将类型参数化,提高代码的复用性,用在类、接口、和方法中,集合中经常使用)
泛型在实例化时:EatFruit e1 = new EatFruit<>();

泛型在使用时有以下的规则:
  • 1、泛型也可以嵌套

  • 2、限制泛型可用类型:
    上界定界符:定上限之后会损失部分add方法,若集合是只读的可以用定上限的方法
    法一:在类的声明时,同时使用(T extends 类名)限制泛型的可用类型的范围必须是该类或者是其子类
    法二:在实例化时用,同时使用(?extends 类名)限制泛型的可用类型的范围必须是该类或者是其子类。例如:EatFruit<? extends Fruit> e = new EatFruit<>();
    下界定界符:在实例化时用<? super 类名>,限制泛型的可用类型的范围必须是该类或者是其父类
    例如:EatFruit<? super Apple> e1 = new EatFruit<>();

  • 3、泛型的类型参数只能是引用类型,但是类型参数可以有多个

  • 4、用泛型机制来创建一个数组

  • 5、在一个泛型类中可以使用另一个泛型的对象作为成员
    注意T必须是T

八、异常Exception

一、定义:

1、异常是程序运行时出现的错误,不是编译错误或语法错误;异常在java中代表一个错误的实体对象
2、异常分为可控式异常和执行时异常
可控式异常:可预测的,必须进行处理否则编译失败
执行时异常:运行时出现的,不可预测,不处理的话也不影响编译

二、几个关键字

1、try:有可发生异常的代码段,不能单独使用必须和catch或finally组合使用
2、catch:捕获异常,可以有多个,参数为什么类型的异常
3、finally:可写可不写,无论有没有异常,必须出现的代码段,一般都是关闭某些东西释放内存的代码
4、throw:手工自行(jvm认为没错,程序员认为有错的)抛出异常,程序员自己添加的异常 throw new一个异常对象
5、throws:若方法中有异常发生,但不想在方法中处理,而想要方法的调用者来处理,则在此方法的声明时使用关键字throws 异常1,异常2等(注意不要在main函数上使用)

三、异常处理的基本结构
try{
		
	}catch (Exception e) {
		// TODO: handle exception
	}finally{			
	}
四、异常对象的方法来自throwable

1、getMassage():获取异常信息(哪里发生异常),返回值为字符串类型
2、ptintStackTrace():输出错误堆栈中的错误

五、注意
 1、异常的层级结构,层级越高的放在最后,即先捕获子类再捕获父类
 2、自定义异常:自己写一个处理异常的类,该类继承exception,需要手工抛出才能执行

九、JDBC技术与数据库应用

开发技巧

1、 建立连接数据库的一个工具类(静态的),当连接数据库时直接调用即可
2、 model类包(数据表名)类:商品类别数据模型(实体类)javabean
即商品类的类名对应数据库数据表的表名;商品类的属性对应数据表中的列名
3、 dao类包下的(数据表名+DAO )类:商品类别数据访问对象
即一个商品类对应的基本方法,插入,删除,更新,查询等

一、JDBC驱动程序
一套jdbc驱动程序,可以连接多种数据库。每种数据库提供不同的驱动程序
二、ODBC的数据源配置
 导入驱动程序包(添加mysql-connect的基本类库):

1、 java项目下:项目上右键,选择属性,在java-bulid-path—ibraries下添加;或在新建项目时添加
2、 web项目下:将mysql-connect的基本类库,放到webcontent-webinf-lib目录下

三、JDBC API的常用接口和类
  JDBC是对ODBC API进行的一种面向对象的封装和重新设计,java应用程序通过JDBC API(java.sql)与数据库连接,而实际动作则是由JDBC驱动程序管理器(JDBC Driver Manager)通过JDBC驱动程序与数据库系统进行连接

注意:Connection接口、Statement接口、Resultset接口在使用完毕后都需要进行关闭释放资源(close())

  • 1、Connection接口:与特定数据库的连接,执行SQL语句并在连接的上下文中返回结果。
    1、getMetaData()方法获得数据库的有关描述信息,如表名,表的索引,数据库产品的名称和版本,数据库支持的操作
    2、createStatement():创建Statement接口的对象
    3、prepareStatement(sql):用来执行预编译的sql语句返回PreparedStatement对象
  • 2、Statement | PreparedStatement接口:用于执行静态SQL语句并返回其生成的结果的对象。
    1、默认情况下,每个Statement对象只能有一个ResultSet对象同时打开。 因此,如果一个ResultSet对象的读取与另一个对象的读取交错,则ResultSet对象必须由不同的Statement对象生成。 在所有执行方法Statement接口隐式关闭当前ResultSet声明的对象,如果一个开放的存在。
    2、Statement对象将由java垃圾收集程序自动关闭。而作为良好的编程习惯,应在不需要时调用close方法关闭
    3、常用方法
返回值方法描述
Booleanexecute(String sql)执行给定的SQL语句,这可能会返回多个结果;对于create,会返回一个boolean值,注意boolean值sql语句有没有返回值
ResultSetexecuteQuery(String sql)执行给定的SQL语句,该语句返回单个 ResultSet对象。
intexecuteUpdate(String sql)1、执行给定INSERT , UPDATE ,或 DELETE语句,返回值是一个整型,是受影响的行数或不返回任何内容; 2、执行SQL DDL(数据定义语言) 例如CREATE DROP等语句,返回值为零或不返回任何内容,

2、执行SQL DDL(数据定义语言) 例如CREATE DROP等语句,返回值为零或不返回任何内容,

  • PreparedStatement接口:Statement的子类 表示预编译的SQL语句的对象
    预编译的SQL语句:sql语句中一些不确定的变量用问号代替
    SQL语句已预编译并存储在PreparedStatement对象中。 然后可以使用该对象多次有效地执行此语句

常用方法

返回值方法描述
VoidsetString(int索引, String x)将指定的参数(即sql语句中的问号)设置为给定的值。
Void等一系列set方法…….
ResultSetexecuteQuery() 无参执行给定的SQL语句,该语句返回单个 ResultSet对象。
IntexecuteUpdate()无参与statement一样
  • 3、Resultset接口:表示数据库结果集的数据表,通常通过执行查询数据库的语句生成。
    ResultSet对象保持一个光标指向其当前的数据行。 最初,光标位于第一行之前。 next方法将光标移动到下一行,并且由于在ResultSet对象中没有更多行时返回false ,因此可以在while循环中使用循环来遍历结果集。默认的ResultSet对象不可更新,并且只有一个向前移动的光标。 因此,您只能从第一行到最后一行迭代一次。 可以生成可滚动和/或可更新的ResultSet对象。
返回值方法描述
ResultSetMetaDatagetMetaData()检索此 ResultSet对象的列的数量,类型和属性
booleannext()将光标从当前位置向前移动一行
DatagetDate(String column)将指定列的内容作为日期类型返回
stringgetString(String column)将指定列的内容作为字符串类型返回
一系列的get方法
  • 4、DriverManager类:用于管理加载数据库的驱动程序类。

    1、getConnection(String url, String user, String password) :尝试建立与给定数据库URL的连接。

  • 5、Class类:获取方法区的对象
    1、forName(String className) :返回与给定字符串名称的类或接口相关联的类对象

  • 6、ResultSetMetaData抽象类:用于获取有关ResultSet对象中列的类型和属性的信息的对象

     1、构造方法:通过ResultSet下的getMetaData()方法
     2、常用方法                      
    
返回值方法描述
intgetColumnCount()返回此 ResultSet对象中的列数
StringgetColumnName(int column)获取指定列的名称。
四、连接数据库:一般编写一个工具类
 1、使用JDBC连接数据库
  • 1、加载mysql数据库的驱动程序(查找方法区中的Driver类)

Class.forName(“com.mysql.jdbc.Driver”);
公司域名倒着写。项目名称jdbc下的Driver
String url=”jdbc:mysql://localhost:3306/mysqldb”;//mysqldb是具体数据库名
协议://数据库所在的服务器或主机地址:端口号/数据库的路径
localhost:3306指向了mysql的datadir目录
connection conn=DriverManager.getConnection(url,user,password);

  • 2、创建Statement对象执行sql语句返回结果

Statement stmt=conn.createStatement();//创建Statement对象
ResultSet rs=stmt.executeQuery(“sql语句”);//执行sql语句并返回结果

五、JDBC基本应用
1、数据库查询
  • a 获取列表信息

    通过ResultSetMetaData对象可以获得有关ResultSet中列的名称和类型的信息,
    假如res为结果集

  ResultSetMetaData rsmd=res.getMataData();
             rsmd.getColumnCount();
             rsmd.getColumnName();
  • b遍历访问结果集

ResultSet包含符合sql语句中条件的所有行,每一行称作一条记录,我们可以按行的顺序逐行访问结果集的内容,在结果集中通过一个游标来指示当前行,刚开始指向第一行之前的位置,可以使用ResultSet下的next方法将游标移到下一行,加上循环从而遍历结果集

  • c访问当前行的数据项

通过一套get()方法来访问当前行中的不同数据项。可以以多种形式获取ResultSet中的数据内容,这取决于每个列中存储数据的类型。可以按列序号或列名来标识要获取的数据项。注意,序列号从1开始,如果结果集中第二列列名为title,并将值存储为字符串,则下列任一代码将获取存储在该列中的值。
即 String s=rs.getString(“title”);
String s=rs.getString(2);

  • d创建可滚动的结果集

由Connection对象提供的不带参数的CreateStatement()方法创建的Statement对象执行sql语句所产生的结果集,只能向后移动记录指针。实际应用中,需要将指针进行前后移动,或移动到指定行,这时候要使用滚动结果集。

1、滚动记录集必须用如下方法创建Statement对象
createStatement(int resultSetType, int resultSetConcurrency)

创建一个 Statement对象,该对象将生成具有给定类型和并发性的 ResultSet对象。

resultSetType代表结果集的类型(执行效率由高到低)

1、	ResultSet.TYPE_FORWARD_ONLY  :结果集的游标只能向后移动
2、	ResultSet.TYPE_SCROLL_INSENSITIVE :结果集的游标可以前后后移动,但结果集不随着数据库内容的改变而改变
3、	ResultSet.TYPE_SCROLL_SENSITIVE:结果集的游标可以前后移动,而且结果集随着数据库内容的改变而改变

resultSetConcurrency:代表并发类型

1、	ResultSet.CONCUR_READ_ONLY:不能用结果集更新数据库表
2、	ResultSet.CONCUR_UPDATABLE:结果集会引起数据库表内容的改变
3、	游标的移动和检查ResultSet下的方法
    1、	afterLast():移到最后一条记录后面
    2、	beforeFirst():移到第一条记录前面
    3、	first():移到第一条记录
    4、	last():移到最后一条记录
    5、	previous():移到前一条记录处
    6、	next():移到下一条记录处
    7、	isFirst():判断游标是否在第一条记录
    8、	isLast():判断游标是否在最后一条记录
    9、	isBeforeFirst():判断游标是否在最后一条记录之前
    10、	isAfterFirst():判断游标是否在最后一条记录之后
    11、	getRow():返回当前游标所处的行号,行号从1开始编号,如果结果集没有行则返回空
    12、	absolute(row):将游标移动到指定行。如果row为负数,表示倒数行号。

十、多线程

1、基本概念

一个exe在内存中有一个单独的地址,程序一般对应一个进程
一个进程中如需同时运行多个子程序时,需要开启多个线程
当一个Java程序启动时,JVM会自动创建主线程,并在该线程中调用程序的main()方法
JVM还创建了其他线程,例如,与垃圾收集、对象终止和其它JVM内务处理任务相关的线程。
合理的线程使用可以帮助我们显著的提供程序运行效率

2、线程的实现

Java语言中实现线程的方式有两种,但都是通过start()方法启动线程实例

  • 第一种,继承线程类:Thread
继承Thread类:
 class Test extends Thread{
    public void run(){
         while(true){
	   System.out.println("o");
	   try {
	         sleep(1000);
	   } catch (InterruptedException e) {
	         e.printStackTrace();
	   }		
         }
    }
}
实例化:
Test td = new Test();
启动线程
td.start();

  • 第二种:实现Runnable接口
实现runable接口:
 class Test implements Runnable{
         public void run(){
             while(true){
	       System.out.println("o");
	       try {
	             Thread.sleep(1000);
	       } catch (InterruptedException e) {
	             e.printStackTrace();
	       }		
             }
        }
    }
    实例化并建立线程对象:
    Thread td = new Thread(new Test());
    运行线程:
    td.start();
Daemon线程

先来看个多线程的例子


    public class ParentTest  
    {  
      
        public static void main(String[] args)  
        {  
            System.out.println("parent thread begin ");  
              
            ChildThread t1 = new ChildThread("thread1");  
            ChildThread t2 = new ChildThread("thread2");  
            //t1.setDaemon(true);  
            //t2.setDaemon(true);  
              
            t1.start();  
            t2.start();  
      
            System.out.println("parent thread over ");  
        }  
    }  
    class ChildThread extends Thread  
    {  
        private String name = null;  
        public ChildThread(String name)  
        {  
            this.name = name;  
        }  
        @Override  
        public void run()  
        {  
            System.out.println(this.name + "--child thead begin");  
            try  
            {  
                Thread.sleep(500);  
            }  
            catch (InterruptedException e)  
            {  
                System.out.println(e);  
            }  
            System.out.println(this.name + "--child thead over");  
        }  
    }  
       
     执行结果如下:  
    parent thread begin  
    parent thread over  
    thread1--child thead begin  
    thread2--child thead begin  


程序在主程序运行结束后,子程序依然进行;
若在主线程中创建了子线程,如果希望在主线程结束时,子线程也结束,这时候就需要Daemon线程

一个Daemon线程是一个在后台执行服务的子线程。 如果所有的非Daemon的线程都结束了,则Daemon 线程就会自动终止。
例如:main方法是一个非Daemon线程,如果希望main方法结束后子线程跟着终止,要将它设为Daemon线程。
Daemon线程设置方法: Thread.setDaemon(true);

  • 第三种:实现Callable接口
    比较推荐实现Runnable接口的方式,原因如下:
(1)适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码,数
据有效的分离,较好地体现了面向对象的设计思想。 (可联想到模拟火车站卖票的例子) 

(2)可以避免由于Java的单继承特性带来的局限。我们经常碰到这样一种情况,即当我们要将已经
继承了某一个类的子类放入多线程中,由于一个类不能同时有两个父类,所以不能用继承Thread
类的方式,那么,这个类就只能采用实现Runnable接口的方式了。 (单继承多实现) 

(3)有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。当多个线程的执行代
码来自同一个类的实例时,即称它们共享相同的代码。多个线程操作相同的数据,与它们的代码
无关。当共享访问相同的对象时,即它们共享相同的数据。当线程被构造时,需要的代码和数据
通过一个对象作为构造函数实参传递进去,这个对象就是一个实现了Runnable接口的类的实例。


4、线程的生命周期
  • 生命的几个基本状态 在这里插入图片描述

  • 运行中的方式

 1、执行了start()之后,线程进入了Runnable状态,此时线程尚未真正开始执行
 2、必须等待排班器(Scheduler)的排班,决定运行的先后顺序

在这里插入图片描述
有几种状况会让线程进入Blocked状态

等待输入输出;
调用sleep()方法;
尝试取得对象锁定;
调用wait()方法;

Blocked线程如何恢复到Runnable状态?

输入输出完成;
调用interrupt();
取得对象锁定;
调用notify()或notifyAll();
5、线程的休眠与唤醒
Thread thread = new Thread(new Runnable() {
        public void run() {
                try {
                     Thread.sleep(99999);
                }catch(InterruptedException e) {
                     System.out.println("I'm interrupted!!");
                }
                System.out.println("Hello Thread");
        }
});
thread.start();
thread.interrupt(); 
6、线程的加入
  • 当线程使用join()加入至另一个线程时,另一个线程会等待这个被加入的线程工作完毕,然后再继续它的动作。
  • join()的意思表示将线程加入成为另一个线程的流程之一
Thread threadB = new Thread(new Runnable() {
        public void run() {}
});
threadB.start();
try {
       threadB.join(); //子线程threadB加入主线程main中
}catch(InterruptedException e) {
       e.printStackTrace();
}
7、线程的停止
  • 不建议使用stop()来停止一个线程的运行,而是采用向run方法传递一个Boolean型的信号量,以控制其结束与开始
public class SomeThread implements Runnable {
     private boolean flag = true;
     public void stopThread() {
         this.flag = false;
     }
     public void run() {
         while(isContinue) {
             // ... some statements
         }
     }
}

8、 线程的同步化 (关键字synchronized)
同步化的作用
  • 一个进程中的多个线程共享相同的内存地址空间,这就意味着它们可以访问相同的变量和对象,而且它们从同一堆中分配对象。
  • 如果没有同步,数据很容易就处于不一致状态。例如,如果一个线程正在更新两个相关值,而另一个线程正在读取这两个值,有可能在第一个线程只写了一个值,还没有写另一个值的时候,调度第二个线程运行,这样它就会看到一个旧值和一个新值。
  • 如果一个对象所持有的数据可以被多线程同时共享存取时,必须考虑到「数据同步」的问题。
对象锁(lock)

在这里插入图片描述

实现方式
  • 使用“synchronized”关键词为方法加同步锁
public class ThreadTest implements Runnable { 
     public synchronized void run() {
          for (int i = 0; i < 10; i++) {
                System.out.print(" " + i);
          }
     }
     public static void main(String[] args) {
          Runnable r = new ThreadTest();
          Thread t1 = new Thread(r);
          Thread t2 = new Thread(r);
          t1.start();
          t2.start();
      }
} 

  • 使用“synchronized”关键词为语句块加同步锁

public class ThreadTest implements Runnable {
      public void run() { 
          synchronized(this){ 
                for (int i = 0; i < 10; i++) {
                      System.out.print(" " + i);
                }
          }
      }
      public static void main(String[] args) {
          Runnable r = new ThreadTest();
          new Thread(r).start();
          new Thread(r).start();
      }
}

9、线程的等待与恢复
  • wait()、notify()与notifyAll()是由Object类别所提供的声明为“final”的方法。
  • 在同步化的方法或区块中呼叫wait()方法,当前的线程会被放入对象的等待池中,使线程暂停。
  • 当notify()方法被调用,JVM会通知等待池中的线程加入,回到锁定池的Blocked状态。
  • 被通知的线程是随机的,被通知的线程会与其它线程共同竞争对象的锁定。
  • 如果您呼叫notifyAll(),则所有在等待池中的线程都会被通知回到锁定池的Blocked状态。
    例子:
package com.phy.thread;


import java.util.Scanner;

/**
 * @author :xp
 * @date :Created in 2018/10/26 11:00
 * @description:
 */
class MT implements Runnable{
    public boolean flog = false;
    public  void run() {
        int i = 0 ;
        while(true){
            System.out.println("Hello World"+i);
            i++;
            if(flog){
                synchronized(this){
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class TestDaemon {
    public static void main(String[] args){
        MT mt = new MT();
        Thread t = new Thread(mt);
        t.start();
        Scanner sc = new Scanner(System.in);
        while(true){
            System.out.println("输入0暂停");
            int n = sc.nextInt();
            if(n==0) mt.flog = true;
            System.out.println("输入1继续");
            n = sc.nextInt();
            if(n==1){
                synchronized (mt) {
                    mt.flog = false;
                    mt.notify();
                }
            }
        }
    }

}



10、线程的优先级
  • 线程有其优先权,由1(Thread.MIN_PRIORITY)到10(Thread.MAX_PRIORITY)
  • 优先权越高,排班器越优先排入执行,如果优先权相同,则轮流执行(Round-robin方式)
  • 可以通过线程的setPriority()方法设置某个线程的优先级,这样,当一个资源空闲时,同时等待这个资源的两个线程里边优先级高的会先获得对象执行权
11、容器类的线程安全
  • Collections提供了一系列synchronized方法来传回一个同步化的容器对象,从而保证了容器类的线程安全。
  • 使用Iterator遍访对象时,必须实现同步化。
synchronized(list) {
    Iterator i = list.iterator();     while (i.hasNext()) {
        System.out.println(i.next());
    }
}
线程案例:写出输出顺序为男,女,男,女的线程
  • 方式一

//首先要定义一个User对象这里省略

package com.phy.thread;

import com.phy.entity.User;

/**
 * @author xp
 * @date :Created in 2018/10/26 14:52
 * @description:设计一个线程类,第一次输出小明男提问
 * 第二次输出小红女在提问
 */

//定义一个线程类
class RunnableExample implements Runnable {
    private boolean flag = true;
    User userB = new User("小明","男",1);
    User userG = new User("小红","女",1);

    public RunnableExample() {
    }
    @Override
    public synchronized void run() {
        while (true){
            if(flag) {
                System.out.println(userB.getName() + userB.getSex() + "正在问问题");
            }else {
                System.out.println(userG.getName() + userG.getSex() + "正在问问题");
            }
            flag = !flag;
            try {
                Thread.currentThread().sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
//测试函数
public class Test2 {
    public static void main(String args[]){
        Thread threadG = new Thread(new RunnableExample());
        threadG.start();
    }
}


  • 方式二

//首先要定义一个User对象这里省略

package com.phy.thread;

import com.phy.entity.User;

/**
 * @author :xp
 * @date :Created in 2019/3/26 14:52
 * @description:设计一个线程类,输出顺序为男,女,男,女
 * 第二次输出小红女在提问
 */


//定义一个线程类
class RunnableExample implements Runnable {
    private User user;
    boolean flag ;
    User userB = new User("小明","男",1);
    User userG = new User("小红","女",1);

    public RunnableExample(User user,boolean flag) {
        this.user = user;
        this.flag =flag;
    }

    @Override
    public  void run() {
        synchronized(user){
            while (true){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if( flag ){
                     System.out.println(userB.getName() + userB.getSex() + "正在问问题");
                }else {
                    System.out.println(userG.getName() + userG.getSex() + "正在问问题");
                }
                user.notifyAll();
                try {
                    user.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
//测试函数
public class Test2 {
    public static void main(String args[]){
        User c= new User();
        Thread threadB = new Thread(new RunnableExample(c,true));
        Thread threadG = new Thread(new RunnableExample(c,false));
        threadB.start();
        threadG.start();
    }
}


写两个线程,一个线程打印 1~52,另一个线程打印字母A-Z。打印顺序为12A34B56C……5152Z
  • 这是线程间的通信的一个很有代表性的例子。过程如下:
    1、创建两个线程实现Runnable接口重写run方法,一个用于打印数字,一个用于打印字母。
    2、创建一个测试类,在测试类中创建一个Object类的对象(作为两个线程的共享资源,以便实现线程间的通信),通过各类的构造方法传递过去。
    3、在两个类的run方法中都要用synchronized保证同步,即加锁。
    4、在数字类中用for循环每打印两个数字就唤醒其他线程,释放锁,进入阻塞状态。 在字母类中每打印一个字母就唤醒其他线程,释放锁,进入阻塞状态。
  • 在写这个程序的时候有几点要注意的地方:
    1、两个线程要使用同一个资源才需相互通信,所以在测试类中创建共享资源,并通过构造方法分别传到各线程类中。
    2、两个线程哪个先运行(执行start())哪个就先获得资源并执行
    3、在run方法体内写进程间的通信wait()和notifyall()时,一定要先写notifyall()再写wait()。
    原因:当你先写wait()时,本进程也进入休眠状态,再写notifyall()唤醒所有线程时本线程以及其他线程被一块唤醒,竞争同一个资源,就会造成死锁。 所以一定要先唤醒其他线程,再让本线程阻塞!
/**
 * @author :xp
 * @date :Created in 2018/10/26 16:12
 * @description:实现两个线程之间的通信
 */
public class ThreadExample {
    public static void main(String[] args) {
        Object obj = new Object();
        Number s = new Number(obj);
        Char z = new Char(obj);
        Thread th1 = new Thread(s);
        Thread th2 = new Thread(z);
        th1.start();//数字的线程先运行,数字先执行
        th2.start();
    }

}

//数字类实现runnable接口
class Number implements Runnable{
    private Object obj;

    public Number() {
    }
    public Number(Object obj) {
        this.obj = obj;
    }
    @Override
    public void run() {
        synchronized(obj){//给共享资源上锁
            for(int i = 1;i < 53;i++ ){
                System.out.println(i);
                if(i % 2 == 0){//保证输出两个数字
                    obj.notifyAll();//唤醒其他线程
                    try {
                        obj.wait();//等待并释放锁
                    } catch (InterruptedException e) {

                        e.printStackTrace();
                    }
                }
            }
        }

    }
}
//字母类
class Char implements Runnable{

    private Object obj;

    public Char() {
    }
    public Char(Object obj) {
        this.obj = obj;
    }

    @Override
    public void run() {
        synchronized(obj){
            for(int i = 0;i < 26;i++ ){
                System.out.println((char)(i+'A'));
                obj.notifyAll();//唤醒其他线程
                try {
                    obj.wait();//释放锁等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值