1.JAVA SE前期准备

1.计算机语言(三类)

  • 1.机器语言: 二进制的01组成的编码
  • 2.汇编语言: 英文标识符组成的编码
  • 3.高级语言: 接近自然语言

2.Java技术平台

  • 1.JAVA SE(Java Platform Standard Edition ,Java标准版)
    • 1.JDK(Java SE开发工具集):包括JRE和命令行开发工具(编译,运行,调试Java程序所需要的基本工具)
    • 2.JRE(运行环境):提供Java虚拟机和运行Java应用程序所必需的类库
    • 3.JRE与JDK的区别
      • 1.如果只需要在某种操作系统下运行Java应用程序,则安装支持该操作系统的JRE即可
      • 2.如果不仅要运行Java应用程序,还要开发Java应用程序,就需要安装支持该操作系统的JDK
    • 4.JVM
      • 1.运行Java字节码的Java虚拟机,源码先通过编译器编译成二进制字节码文件,然后JVM把每条要执行的字节码交给Java解释器翻译成对应的机器码,最后由解释器执行
      • 2.JVM屏蔽了底层操作系统的差异,使Java程序只需生成在Java虚拟机上运行的字节码,就可在多种平台上运行,为跨平台提供了支持
    • 5.Java编译器:Java源文件(.java)编译成二进制字节码文件(.class),jdk安装包bin录下的javac.exeJava编译器
    • 6.Java解释器:JVM的一部分,Java解释器用来解释执行Java编译器编译后的.class文件;jdk安装包bin录下的java.exeJava解释器
    • 7.注意:
      • 1.通常一个平台上的二进制可执行文件不能在其他平台上工作,因为此可执行文件包含了对目标处理器的机器语言,而.class文件这种特殊的二进制文件可以运行在任何支持Java虚拟机的硬件平台和操作系统上
  • 2.JAVA EE (Java Platform Enterprise Edition,Java企业版)
  • 3.JAVA ME (Java Platform Micro Edition,Java微型版)

3.Java语言特点

  • 1.简单性

  • 2.面向对象

    • Java语言是一种完全面向对象的编程语言
  • 3.网络分布式计算

    • Java语言是面向网络的编程语言,具有基于网络协议的类库
  • 4.安全性

  • 5.平台无关性(可移植性)

  • 6.并发性(多线程)

4.Java开发工具

  • 1.基础开发工具
    • JDK(Java Development Kit): 编写Java程序时,可用文本编辑器编辑Java源码,然后运用JDK中的命令编译和运行程序
      javac 类名.java //编译
      java 包名.类名 //执行
      
  • 2.集成开发环境(IDE)
    • 1.Eclipse
    • 2.IntelliJ IDEA

5.JDK下载安装及环境配置

  • 1.官网下载jdk8.0及以上版本

  • 2.安装并设置安装路径

  • 3.配置系统环境变量

    此电脑->计算机->属性->环境变量->系统变量
    //用于指定jdk的安装目录,可以通过%%被引用
    新建:JAVA_HOME:jdk的安装路径
    //用于告诉计算机其他可执行文件的位置
    Path%JAVA_HOME%\bin
    
  • 4.扩展:windows中的两种环境变量:用户变量和系统变量

    1.环境变量不区分大小写
    2.系统用户变量对所有用户有效,用户变量只对当前用户有效
    3.用户变量与系统变量:名称是变量,值是里面的内容,通过变量存储想要的内容,方便调用
    4.系统变量和用户变量的PATH:告诉系统可执行文件.exe放在什么路径(可执行程序的路径要放在PATH里,不能新建一个变量,否则cmd会提示不是内部或外部命令,或者不是可执行程序)
    5.windows系统在执行用户命令时,若用户未给出文件的绝对路径,则首先在当前目录下寻找相应可执行文件,批处理文件等,若当前目录找不到对应文件名的程序,则在系统变量的Path的路径中,依次寻找对应的可执行程序文件(查找顺序是按照路径的录入顺序从左往右寻找的,最前面一条的优先级最高,如果找到程序就停止寻找,后面的路径不再执行)
    6.如果系统变量的Path路径找不到,再到用户变量的Path路径中寻找(如果系统变量和用户变量的Path中同时包含了同一个命令,则优先执行系统变量Path中的命令)
    7.每次新加了命令以后,要确定保存再重启CMD,否则命令不生效
    8.在CMD里要输出环境变量可以通过 echo %变量名%
    9.%JAVA_HOME%%的意思表示JAVA_HOME是定义过的一个环境变量,这里通过%%引用
    
    1.首先打开“运行”输入cmd,再输入“java -version”如果有内容说明你的jdk安装成功
    2.java命令可用说明你配置的classpath是正确的,因为java命令可以看作是Java解释器用来执行字节码(.class)文件,而classpath则是用来指定类库(存放.class文件)的位置
    3.classpath原配置代码:%Java_Home%\lib\dt.jar;%Java_Home%\lib\tools.jar
    4.JDK1.5之前,我们没有办法在当前目录下加载类文件(找不到 JDK目录下lib文件夹中的.jar文件),所以我们需要通过配置classpath在当前目录加载类文件
    5.JDK1.5之后,JRE能自动搜索目录下类文件,并且加载dt.jar和tool.jar的类。所以配置Java环境变量时不再需要配置classpath,只需要配置JAVA_HOME以及Path即可
    6.使用javac命令出现javac不是内部或外部命令,说明Path配置有问题,先检查下Path路径是否配置正确。如果确定没有问题的话,打开jdk的安装目录,在bin文件夹里查找下是否有javac.exe程序,如果有,说明你的path配置的有问题,如果没有,那说明你下载的jdk不是正规渠道下载的,建议去java官网上重新下载安装
    7.dt.jar是关于运行环境的类库,主要是用于swing的包,如果不使用可以不配置。
    8.tools.jar是工具类库,它在编译和运行一个类时被使用
    9.当我们配置classpath后,系统会根据我们所配置的classpath加载类
    10.例如:在我们使用javac命令编译程序时,系统加载tools.jar其实就封装了下面这样一条命令
    javac XXX.java
    java -classpath=%JAVA_HOME%\lib\tools.jar xx.xxx.Main XXX.java
    

6.Java工作原理:

  • 1.计算机高级语言类型主要有编译型解释型两种,java是两种类型的结合
  • 2.Java源文件名称的后缀为:.java
  • 3.通过编译使 .java源文件 生成后缀为 .class字节码文件
  • 4.再由JavaJVM 解释执行 .class文件

7.JVM

  • 1.JVM(Java Virtual Machine)是Java跨平台的保证
  • 2.所有的.class文件都在JVM上运行,不同的操作系统只需安装符合其类型的JVM,那么程序无论在哪个操作系统都可以正确执行
  • 3.具体JVM内容参考JVM文章

8.安装类库源代码

  • 1.Java类库源文件在JDK中以压缩文件src.zip的形式发布,将其解压缩后才能够访问源代码,src.zip中包含了所有公共类库的源代码
  • 2.步骤
    • 1.确保JDK已安装,并且jdk/bin目录在执行目录中
    • 2.进入JDK目录并建立一个子目录src
    • 3.在src目录下的cmd中执行解压缩命令:jar xvf . ./src.zip

9.通过命令行编译并运行Java程序:

  • 1.打开一个cmd窗口,进入到Java程序所在目录
  • 2.然后输入下列命令
    • 1.javac 程序名.java
    • 2.java 程序名
  • 3.带包编译
    • 1.javac -d . 源文件.java
    • 2.java 包名.类名

10.注意事项

  • 1.Java严格区分大小写

1.Java编程基础

1.Java的基本语法

  • 1.Java代码的基本格式
    修饰符 class 类名{
    	程序代码
    }:
    public class Test{
    	public static void main(String[] args){
    
    	}
    }
    
  • 语法说明
    • 1.Java程序代码都必须放在类中,类需要用class关键字定义,class前面可以有修饰符
    • 2.当类不是定义在基本java.lang包中时,要使用import将相应的包加载进来
    • 3.main函数:主函数是程序的入口,想要执行的代码必须写入主函数中,且写法是固定的(一个类中只能有一个主函数)
    • 4.中括号{}:划分代码块的边界
    • 5.Java程序中一句连续的字符串不能分开在两行中书写,可以使用加号+将连续的字符串分成两个字符串在不同行书写
    • 6.Java源文件后缀名为.java
  • 2.Java代码的基本规范
    • 1.Java中类名采用大驼峰命名法:所有单词的首字母大写
    • 2.变量名,函数名采用小驼峰命名法:从第二个单词开始首字母大写
    • 3.包名全小写,且只能放在当前文件有效代码的第一行

2.Java中的输入输出

  • 1.输出: 打印输出到标准输出流(控制台窗口)
    //输出内容单独一行,输出内容可以为空
    System.out.println();
    //输出内容不换行,输出内容不能为空
    System.out.print();
    
  • 2.输入: JDK5.0之后可以通过控制台读取输入
    • 1.需要利用Scanner类,其定义在java.util包中。
    • 2.首先构造一个Scanner对象,它附属于标准输入流(System.in)
      Scanner in = new Scanner(System.in);
      
    • 3.利用Scanner对象调用方法

3.Java中的注释

  • 1.Java中的注释有3种类型:
    • 1.单行注释
      //单行注释
      
    • 2.多行注释
      /*
      多行注释
      */
      
    • 3.文档注释
      /** 
       * 文档注释
       */
      例:
      /**
       * 这是类外部
       */
      package net.csdn.editor;
      public class Test{
      	/**
      	 * 这是方法外部,类内部
      	 */
      	public static void main(String[] args){
      		/**
      		 * 这是方法内部
      		 */
      		System.out.println("test");
      	}
      }
      //带包编译
      javac -d . Test.java
      //解释执行
      java net.csdn.editor.Test;
      //生成说明文档
      javadoc -d 文件名 源文件名.java
      //文件名是文档生成的目标路径,设为.表示在当前路径下生成
      javadoc -d . Test.java
      



  • 语法说明:
    • 1.-d表示带包,.表示在当前文件中生成,可使用其他文件名,如果文件不存在会自动生成
    • 2.注释是对程序的功能或代码的解释说明
    • 3.注释只在Java源文件中有效,不参与代码的编译和运行(编译程序时,编译器会忽略这些注释信息,不会将其编译到class字节码文件中去)
    • 4.文档注释用来对某一个函数或者某个类添加说明
    • 5.文档注释可以使用javadoc命令将文档注释提取出来生成帮助文档
    • 6.只有写在类外部和方法外部,类内部的文档注释才有作用

4.Java中的标识符

  • 1.Java中需要定义符号用来标记名称,这些符号被称为标识符
  • 2.标识符可以由字母,数字,下划线(_)和美元符号($)或人民币符号(¥)组成,但不能以数字开头,也不能是Java中的关键字或者保留字
  • 3.编码规范:
    • 1.包名所有字母一律小写
    • 2.类名,接口名采用大驼峰命名法
    • 3.常量名所有字母都大写,多个单词之间用下划线隔开
    • 4.变量名,方法名采用小驼峰命名法
    • 5.望名之意

5.Java中的关键字

  • 1.关键字是编程语言里事先定义好并赋予了特殊含义的单词
    abstractassertbooleanbreakbytecasecatchcharclassconst
    continuedefaultdodoubleelseenumextendsfinalfinallyfloat
    forgotoifimplementsimportimportinstanceofintinterfacelong
    newpackageprivateprotectedpublicreturnshortstaticstricttfpsuper
    switchsynchronizedthisthrowthrowstransienttryvoidvolatilewhile
  • 语法说明:
    • 1.所有的关键字都是小写
    • 2.程序中的标识符不能以关键字命名
    • 3.const和goto是保留关键字,虽然在Java中还没有任何意义,但是在程序中不能作为自定义的标识符
    • 4.true,false,null不属于关键字,它们是一个单独标识类型,不能直接使用

6.Java中的常量

  • 1.常量是程序中固定不变,不能改变的数据,Java中常量用final修饰
  • 2.常量一般采用大写字母,不同单词间用_隔开
  • 3.常量类型
    • 1.整型常量:整数类型的数据(包括二进制,八进制,十进制,十六进制4种表现形式)
      • 二进制:0b0B开头,并以数字01组成的数字序列
      • 八进制:0开头,并以数字0 ~ 7组成的数字序列
      • 十进制: 以数字0 ~ 9组成的数字序列
      • 十六进制:0x0X开头,并以0 ~ 9,A ~ F(包括09AF,字母不区分大小写)组成的数字序列
    • 2.浮点数常量
      • 1.浮点数常量分单精度浮点数双精度浮点数两种类型
      • 2.单精度浮点数必须以F或者f结尾
      • 3.双精度浮点数可以以D或d结尾
      • 4.使用浮点数时不加任何后缀,虚拟机会默认为double双精度浮点数
    • 3.字符常量
      • 1.字符常量表示一个字符,一个字符常量要用一对英文单引号''引起来,可以是英文字母,数字,标点符号以及由转义字符来表示的特殊字符
      • 2.Java采用的是Unicode字符集,Unicode字符以\u开头,空白字符在Unicode码表中对应的值为’\u0000’
    • 4.字符串常量
      • 1.字符串常量表示一串连续的字符,一个字符串常量要用一对英文双引号""引起来
      • 2.一个字符串可以包含一个字符或多个字符,也可以不包含任何字符
    • 5.布尔常量
      • 1.布尔常量的两个值truefalse,用于区分一个事物的真与假
    • 6.null常量
      • 1.null常量只有一个值null,表示对象的引用为空。

扩展

  • 1.Unicode字符集
    • 1.字符集是一张码表,它规定了文字与数字的一一对应关系,与计算的内部表示没有必然的联系
    • 2.Unicode编码使用4字节的数字编码
  • 2.进制转换
    • 1.十进制和二进制之间的转换
      • 十进制转换成二进制
        • 将要转换的数除以2,得到商和余数,将商继续除以2,直到商为0,最后将所有余数倒序排列,得到的数就是转换结果
      • 二进制转换成十进制
        • 从右到左用二进制位上的每个数去乘以2的相应次方(次方从0开始),然后把所有相乘后的结果相加,得到的数就是转换结果
    • 2.二进制和八进制的转换
      • 八进制转换为二进制
        • 将要转换的数除以2,得到商和余数,将商继续除以2,直到商为0,最后将所有余数倒序排列,得到的数就是转换结果
      • 二进制转换为八进制
        • 从右到左每三位为一组,如果最左边不够三位用0补齐,然后将每一组从左到右乘以2的相应次方(次方从0到2),最后把所有相乘后的结果相加,得到的数就是转换结果
    • 3.二进制和十六进制的转换
      • 十六进制转换为二进制
        • 将要转换的数除以2,得到商和余数,将商继续除以2,直到商为0,最后将所有余数倒序排列,得到的数就是转换结果
      • 二进制转换为十六进制
        • 从右到左每四位为一组,如果最左边不够四位用0补齐,然后将每一组从左到右乘以2的相应次方(次方从0到2),最后把所有相乘后的结果相加,得到的数就是转换结果

7.Java中的变量

  • 1.变量:是计算机内存中的一块存储空间,是存储数据的基本单元

  • 2.变量名:是标识变量(内存单元)的标识符

  • 3.变量值:是变量(内存单元)中存储的数据

  • 4.变量的创建方式(4种)

    • 1.先声明再赋值
      数据类型 变量名;
      变量名 =;
      例:int x; x=5;
      
    • 2.声明的同时直接赋值
      数据类型 变量名 =;
      例:int x = 5;
      
    • 3.同时声明多个变量,之后再一一赋值
      数据类型 变量名1,变量名2...;
      变量名1 =1;
      变量名2 =2;
      。。。
      例:int x,y,z; x = 5;y = 6;z = 7;
      
    • 4.同时声明多个变量并赋值
      数据类型 变量名1 =1,变量名2 =2 ... ;
      例:int x = 1,y =  2, z = 3; 
      
  • 5.变量的数据类型

    • 1.Java是一门强类型的编程语言,定义变量时必须声明变量的数据类型
    • 2.Java中变量的数据类型分为两种:基本数据类型(八种)和 引用数据类型
  • 1.整数类型变量

    类型名占用空间取值范围
    byte(字节型)1B/8位-27 ~ 27-1
    short(短整型)2B/16位-215 ~ 215-1
    int(整型)4B/32位-231 ~ 231-1
    long(长整型)8B/64位-263 ~ 263-1
  • 2.浮点数类型变量

    类型名占用空间取值范围
    float(单精度浮点数)4B/32位1.4E-45 ~ 3.4E+38 , -3.4E+38 ~ -1.4E-45
    double(双精度浮点数)8B/64位4.9E-324 ~ 1.7E+308 , -1.7E+308 ~ 4.9E-324
  • 3.字符类型变量

    类型名占用空间取值范围
    char2B/16位0~65535(十六位二进制的Unicode字符)
  • 语法说明:

    • 1.字符类型变量的创建方式
      //1.直接赋字符
      char 变量名 = '值';
      //2.赋0~65535的整数值
      char 变量名 = 整数值;
      //3.利用16进制Unicode编码赋值
      char 变量名 = 'Unicode编码值';
      
    • 2.第一种赋值方式,单引号内部只能放一个字符
    • 3.第二种赋值方式,整数范围不能超过取值范围
    • 4.Java中每个char类型的字符变量都会占用2个字节
    • 5.char类型的变量赋值时,需要用英文单引号''把字符括起来,也可以将char类型的变量赋值为0~65535范围内的整数,计算机会自动将这些整数转化为所对应的字符
  • 4.布尔类型变量

    类型名占用空间取值范围
    boolean如果boolean是单独使用:boolean占4个字节;如果boolean是以“boolean数组”的形式使用:boolean占1个字节true,false
  • 语法说明:

    • 1.boolean(布尔)类型有两个值:false和true,用来判定逻辑条件;这两个值不能与整数进行相互转换

8.Java中变量的作用域

  • 1.Java中的变量可以分为类变量,全局变量(成员变量)和局部变量
    • 1.类变量: 使用static修饰的成员变量
    • 2.成员变量:
      • 1.直接在类中声明的变量叫成员变量(又称全局变量)
      • 2.全局变量按调用方式可以分为实例属性(对象名.属性名)
      • 3.如果未对成员变量设置初始值,则系统会根据成员变量的类型自动分配初始值
      • 4.成员变量定义后,其作用域是其所在的整个类
      • 5.成员变量的定义没有先后顺序(定义在使用后也行),但最好将成员变量的定义集中在类的顶部
      class Test1{
      	private static byte a1;
      	private static short a2;
      	private static int a3;
      	private static long a4;
      	private static float a5;
      	private static double a6;
      	private static char a7;
      	private static boolean a8;
      	private static String a9;
      
       	public static void main(String[]args){
      		System.out.println(a1);
      		System.out.println(a2);
      		System.out.println(a3);
      		System.out.println(a4);
      		System.out.println(a5);
      		System.out.println(a6);
      		System.out.println(a7);
      		System.out.println(a8);
      		System.out.println(a9);
      	}
      }
      
    • 3.局部变量:
      • 1.局部变量包括形参,方法中定义的变量,代码块中定义的变量
      • 2.局部变量没有默认值,使用前必须显示初始化或赋值
      • 3.局部变量只能用final修饰符修饰

      • 4.局部变量的作用域范围从定义的位置开始到其所在语句块结束
      • 5.如果局部变量的变量名和全局变量的变量名相同,则在局部变量的作用范围内全局变量暂时失效,如果在局部变量的作用范围内想访问该成员变量,则必须使用关键字this类引用成员变量
    • 注意:基本数据类型的变量传递的是值,而引用数据类型的变量传递的是引用所指的地址(对值的修改不会影响本身的数据,而对堆地址中的数据进行修改,指向该地址的引用的数据值都会改变)
      public class Test01 {
      	public static void main(String[] args) {
      		//基本数据类型的变量
      		int a = 10;
      		//引用数据类型的变量
      		MyClass b = new MyClass();
      
      		Test01 t = new Test01();
      		
      		//输出原本的值	
      		System.out.println(a);
      		System.out.println(b.value);
      		
      		//调用指定方法
      		t.test(a,b);
      		
      		//输出调用方法后的值,其中基本数据类型的变量值没有发生变化,引用数据类型的变量值发生了变化
      		//基本数据类型变量依旧没有变化
      		System.out.println(a);
      		//引用数据类型的变量值发生了变量
      		System.out.println(b.value);
      	}
      	
      	//进入方法,首先给形参赋值:int a = 10;然后给MyClass b赋予上面引用指向的地址
      	public void test(int a,MyClass b) {
      		//局部变量a的值发生变量,但是没有影响到原本变量
      		a = 20;
      		//引用类型变量指向的对地址的数据值发生变量,会影响到指向该对地址的引用的数据值
      		b.value = 100;
      	}
      }
      
      class MyClass{
      	int value = 50;
      }
      

9.Java类型转换

  • 1.自动类型转换(隐式类型转换)
    • 1.指两种数据类型在转换的过程中不需要显式地进行声明
    • 2.自动类型转换必须同时满足两个条件:
      • 1.两种数据类型彼此兼容
      • 2.目标类型的取值范围大于源类型的取值范围
    • 3.数据类型大小排序:byte < char < short < int < long < float < double
      • 1.char类型默认转换为int类型的数据,如果将char转换为byte或short需要进行强转
      • 2.float类型转换为long类型需要强转,而long类型转换为float则不需要



  • 2.强制类型转换(显示类型转换)
    • 1.指两种数据类型之间的转换需要进行显式的声明
    • 2.当两种类型彼此不兼容,或者目标类型取值范围小于源类型时,自动类型转换无法进行,此时需要强制类型转换
      目标类型 变量 = (目标类型)
    • 3.强制类型转换可能会发生数据精度缺失
      class Test1{
       	public static void main(String[]args){
      		byte a;
      		int b = 299;
      		a = (byte)b;
      		System.out.println(b);
      		System.out.println(a);
      	}
      }
      /*
      原理:
      int类型4个字节,32位
      299转换为2进制为00000000 00000000 00000001 00101011
      byte类型占1个字节,8位
      将int类型的数据强转为byte类型
      会丢失前面多余24位,变为00101011即为43
      */
      
    • 4.Java中把浮点数类型强转为整数类型,会直接舍弃小数位,只保留整数位,且不会四舍五入
  • 3.自动类型提升
    • 1.只发生在数学运算期间
    • 2.当运算中有高精度类型的数值参与,则结果会自动提升为高精度类型
      class Test1{
       	public static void main(String[]args){
      		double a = 15.0;
      		float b = 14.0f;
      		float y = a+b+12+23;
      		System.out.println(y);
      	}
      }
      
    • 注意:
      • 1.任意数据类型加上双引号或者拼接双引号都会变成String字符串类型
      • 2.程序从上到下,从左到右运行
        class Test1{
         	public static void main(String[]args){
        		byte a = 13;
        		byte b = 14;
        		int x = 5;
        		int y = 6;
        		char m = 'a';
        		char n = 'A';
        		System.out.println(m+n+"A");
        		System.out.println("A"+m+n);
        		System.out.println(a+b+"A");
        		System.out.println("A"+x+y);
        	}
        }
        

10.Java中的运算符

1.算术运算符:

运算符运算范例结果
+正号+33
-负号-4-4
+5+510
-10-55
*3*412
/5/51
%取模(求余)7%52
++自增(前)a=2;b=++a;a=3,b=3
++自增(后)a=2;b=a++a=3,b=2
- -自减(前)a=2;b=–a;a=1,b=1
- -自减(后)a=2;b=a–;a=1,b=2
  • 语法说明:
    • 1.自增自减运算时,如果运算符++或- -放在操作数的前面则先进行自增或自减运行,再进行其他运算;如果运算符放在操作数的后面则是先进行其他运算再进行自增或自减运算
    • 2.除法运算时,当除数和被除数都为整数时,结果也是一个整数;如果除法运算有小数参与,得到的结果会是一个小数(相当于自动类型提升)
    • 3.取模运算时,运算结果取决于被模数(%左边的数)的符号,与模数(%右边的符号无关)

2.赋值运算符:

运算符运算范例结果
=赋值a=3;b=2;a=3;b=2;
+=加等于a=3;b=2;a+=b;a=5;b=2;
-=减等于a=3;b=2;a-=b;a=1;b=2;
*=乘等于a=3;b=2;a*=b;a=6;b=2;
/=除等于a=3;b=2;a/=b;a=1;b=2;
%=模等于a=3;b=2;a%=b;a=1;b=2;
  • 语法说明:
    • 1.变量赋值时,如果两种类型彼此不兼容,或者目标类型取值范围小于源类型时,需要进行强制类型转换
    • 2.但是如果使用+=,-=,*=,/=,%=运算符进行赋值时,强制类型转换会自动完成,程序不需要做任何显式地声明(虽然b+=a的结果为int类型,但是Java虚拟机会自动完成强制类型转换)

3.比较运算符

运算符运算范例结果
==相等于4 == 3false
!=不等于4 != 3true
<小于4<3false
>大于4>3true
<=小于等于4<=3false
>=大于等于4>=3true
  • 语法说明:
    • 1.基本数据类型的变量存放在虚拟机栈的局部变量表中的是值,==在比较基本数据类型时,比较的是变量值是否相同
    • 2.引用数据类型的变量存放在虚拟机栈的局部变量表中的是对象引用/堆地址,比较的是堆地址是否相同

4.逻辑运算符

运算符运算范例结果
&true&falsefalse
|truefalse
^异或true ^ true,true ^ falsefalse,true
!!truefalse
&&短路与true&&truetrue
||短路或true||falsetrue
  • 语法说明:
    • 1.使用&|进行运算时,不论左边为true或者为false,右边的表达式都会进行运算
    • 2.如果使用&&进行运算,当左边为false时,右边的表达式则不会运算;如果使用||进行运算,当左边为true时,右边的表达式不会运算
      package test.com.wd.test;
      
      public class Test {
      
      	public static void main(String[] args) {
      		// TODO Auto-generated method stub
      		int x=0;
      		int y=0;
      		int z=0;
      		boolean a,b;
      
      		a = x>0 & y++>1;
      		System.out.println(a);
      		System.out.println("y="+y);
      
      		System.out.println("----------------------------");
      
      		a = x>0 && y++>1;
      		System.out.println(a);
      		System.out.println("y="+y);
      
      		System.out.println("----------------------------");
      
      		b = x==0 | z++>1;
      		System.out.println(b);
      		System.out.println("z="+z);
      
      		System.out.println("----------------------------");
      
      		b = x==0 || z++>1;
      		System.out.println(b);
      		System.out.println("z="+z);
      	}
      }
      

5.三元运算符:

运算符运算范例结果
判断条件?表达式1:表达式2;类似if-else3>5?“对”:“错”
package test.com.wd.test;
	public class Test{
		public static void main(String[] args) {
			String str = 3>6?"对":"错";
			System.out.println(str);
		}
}

6.位逻辑运算符:

运算符运算范例结果
&按位与运算4&54
|按位或运算45
^按位异或运算4^51
~按位反运算~4-5
  • 位与运算符: 将数字转换为二进制,低位对齐,高位不足的补0;如果对应的二进制位同时为1,则结果为1,否则为0,最后将二进制转换为十进制
  • 位或运算符: 将数字转换为二进制,低位对齐,高位不足的补0;如果对应的二进制位有一个为1,则结果为1,如果对应的二进制都为0,结果才为0,最后将二进制转换为十进制
  • 位异或运算符: 将数字转换为二进制,低位对齐,高位不足的补0;如果对应的二进制位不同,则结果为1,如果对应的二进制相同,结果才为0,最后将二进制转换为十进制
  • 位取反运算符: 将数字转换为二进制,将二进制中的1改为0,将0改为1,最后将二进制转换为十进制

扩展:原码,反码,补码

  • 原码: 一个整数按照绝对值大小转换成的二进制数,称为原码
  • 反码: 将二进制数按位取反,所得的新二进制数称为原二进制数的反码
  • 补码: 反码加1称为补码
  • 计算机中负数以原码的补码形式表达

11.java中的选择结构语句

1.if语句

  • 基本语法:
    if(判断语句){
    	//代码块
    }
    

2.if-else语句

  • 基本语法:
    if(判断语句){
    	//代码块
    }else{
    	//代码块
    }
    

3.多重if-else语句

  • 基本语法:
    if(判断语句1){
    	//执行语句1
    }else if(判断语句2){
    	//执行语句2
    }
    ...
    else if(判断语句n){
    	//执行语句n
    }else{
    	//执行语句n+1;
    }
    
  • 语法说明:
    • 1.多重if-else语句是一个整体,只能执行其中一个分支语句,一旦进入一个,其他的分支则没用,比较顺序从上到下
    • 2.注意判断语句的使用方式,同样的数值,不同的使用方式,结果不同
    package test.com.wd.test;
    
    public class Test{
    	public static void main(String[] args) {
    		int key = 75;
    		if(key>100) {
    			System.out.println("A");
    		}else if(key>90) {
    			System.out.println("B");
    		}else if(key>80) {
    			System.out.println("C");
    		}else if(key>60) {
    			System.out.println("D");
    		}else {
    			System.out.println("E");
    		}
    		
    		System.out.println("------------");
    		if(key<100) {
    			System.out.println("A");
    		}else if(key<90) {
    			System.out.println("B");
    		}else if(key<80) {
    			System.out.println("C");
    		}else if(key<60) {
    			System.out.println("D");
    		}else {
    			System.out.println("E");
    		}
    	}
    }
    

4.switch条件语句

  • 基本语法:
    switch(表达式){
    	case 目标值1:
    		执行语句1;
    		break;
    	case 目标值2:
    		执行语句2;
    		break;
    	......
    	case 目标值n:
    		执行语句1;
    		break;
    	default:
    		执行语句n+1;
    		break;
    }
    
    package com.entity;
    
    import java.util.Random;
    
    public enum Color {
    
        RED("red color",0),
        GREEN("green color",1),
        BLUE("blue color",2),
       YELLOW("yellow color",3),
       BLACK("BLACK color",4);
    
      Color(String name,int id){
            name = name;
            id = id;
        }
    
        private String colorName;
        private int id;
    
        public String getColorName(){
            return colorName;
        }
    
        public int getId(){
            return id;
        }
    
        public static Color getColor(int max){
            Random random = new Random(System.currentTimeMillis());
            int num = random.nextInt(max);
            switch(num){
                case 0:
                    return Color.RED;
                case 1:
                    return Color.GREEN;
                case 2:
                    return Color.BLUE;
                case 3:
                    return Color.YELLOW;
                default:
                    return Color.BLACK;
            }
    
        }
    
    }
    
    package com.wd.test;
    
    import com.entity.Color;
    
    public class Test02 {
        public static void main(String[] args) {
            int length = Color.values().length;
            Color color = Color.getColor(length);
            switch (color){
                case RED:
                    System.out.println("select " + "RED");
                    break;
                case GREEN:
                    System.out.println("select " + "GREEN");
                    break;
                case BLUE:
                    System.out.println("select " + "BLUE");
                    break;
                case YELLOW:
                    System.out.println("select " + "YELLOW");
                    break;
                default:
                    System.out.println("select " + "BLACK!!!");
                    break;
            }
        }
    }
    
  • 语法说明:
    • 1.switch语句中,表达式和目标值都不支持null且不能不写,否则会出现编译错误;表达式和目标值的类型需要兼容,否则也会出现编译错误;目标值只能声明为常量表达式,不能声明为范围,比较结果,变量

    • 2.case语句的目标值不能重复,对于字符串类型也一样;注意:字符串可以包含Unicode转义字符,而case重复值的检查是在Java编译器对Java源代码进行相关的词法转换之后进行的;即:有些目标值在源代码中不同,但是经过词法转换后是一样,就会造成编译错误(例:“男"和”\u7537"会发生编译错误)

    • 3.JDK1.5之前,switch语句只支持byte,short,char,int四种数据类型
    • 4.JDK1.5及之后,switch语句支持了枚举类与byte,short,char,int的包装类(注意:这种说法不完全正确,之所以switch能够支持相应的包装类,是因为JDK1.5开始支持自动拆/装箱,编译器会自动调用相应的xxxValue()方法将其转换为int类型;之所以支持枚举类,是因为枚举类会调用ordinal()方法,该方法返回的是int类型)



    • 5.Java1.7及之后,switch语句支持使用String类型,实际底层调用的是String类型的hashCode()方法和String类型重写的equals方法(注意:该特性是在编译器层次上实现,在Java虚拟机和字节码层次上还是只支持在swicth语句中使用与整数类型兼容的类型,且在case子句对应的语句块中仍需要使用String的equals方法进行字符串比较,因为哈希函数在映射的时候可能存在冲突,结合equals方法可以解决冲突)
    • 6.switch中可以使用枚举替代String类型,可以提高可读性和可维护性
    • 7.case之后最好带上break(因为case比较的结果为布尔类型,如果有一个case结果为true,其余case不会再进行布尔判断),直接会从进入点开始一直往下执行,直到进入default后退出switch语句,无法起到分支的作用;且default类似于if-else分支中的else,如果上述case分支都没有结果则执行default中的内容,default也可以省略,建议加上,default后的break可加可不加,没有影响(此情况适用于default在switch最后,如果default位置在switch语句的其他位置,则最好加上break,否则也会从default往下一直执行到程序结束)
    • 8.如果switch-case结构中的多个case的执行语句相同,则可以考虑进行合并,提高效率
      @Test
          public void test02(){
              int score = 78;
              switch(score/10){
                  case 1:
                  case 2:
                  case 3:
                  case 4:
                  case 5:
                      System.out.println("不及格!");
                  case 6:
                  case 7:
                  case 8:
                  case 9:
                  case 10:
                      System.out.println("及格!");
              }
              //优化
              switch (score/60){
                  case 0:
                      System.out.println("不及格");
                  case 1:
                      System.out.println("及格");
              }
          }
      
    • 9.凡是可以使用switch-case的结构,都可以转换为if-else,反之不成立,因为switch对表达式和目标值有限制要求

5.三元运算符,if-else,switch-case的区别

  • 1.凡是可以使用三元运算符(三元运算符内部只能是表达式,所以不能代替if else,而if else能取代任何有三元的地方)和switch-case结构的都可以转换为if-else
  • 2.但是反之不一定成立,如果可以使用三元运算符或者switch-case则使用他两,因为效率更高

12 java中的循环语句

1.定义:

  • 在某些条件满足的情况下,反复执行特定代码的功能

2.循环语句分类

  • while循环语句
  • do…while循环语句
  • for循环语句

3.循环语句的四个组成部分

  • 初始化部分
  • 循环条件部分,必须是boolean类型
  • 循环体部分
  • 迭代部分

1.while循环语句

  • 基本语法:
    while(循环条件){
    	执行语句;
    	...
    }
    
  • 语法说明:
    • 1.while语句会反复地进行条件判断,只要条件成立,{}内的执行语句就会执行,直到条件不成立,while循环结束
    • 2.{}中执行语句被称作循环体,循环体是否执行取决于循环条件,当循环条件为true,循环体就会执行,循环体执行完毕时会继续判断循环条件,如条件仍为true则会继续执行,直到循环条件为false,整个循环过程才会结束
    • 3.while循环适用于循环次数不确定的情况下

2.do…while()循环语句

  • 基本语法:
    do{
    	执行语句;
    	...
    }while(循环条件);
    
  • 语法说明:
    • 1.关键字do后面{}中的执行语句是循环体
    • 2.do…while循环语句将循环条件放在了循环体的后面。即:循环体会无条件执行一次,然后在根据循环条件来决定是否继续执行
    • 3.while和do…while的区别:如果循环条件在循环语句开始时就不成立,那么while循环的循环体一次都不会执行,而do…while循环的循环体还是会执行一次

3.for循环语句

  • 基本语法:
    for(初始化部分;循环条件部分;迭代部分){
    	循环体部分
    }
    
  • 语法说明:
    • 1.for循环的执行顺序:初始化部分–>循环条件部分(满足条件)–>循环体部分–>迭代部分–>循环条件部分(满足条件)–>…–>(循环条件部分不满足)–>结束循环
    • 2.for循环的循环条件部分必须为boolean类型,所以只能有一个整体结果为boolean类型的循环条件,也可以没有,如果没有循环条件部分,即成为死循环
    • 3.for循环的其他部分都可以存在多个语句,对循环本身没有影响,多个语句之间用逗号分隔;而不同部分用分号分隔
    • 4.for循环()中的三部分内容都可以省略,但是必须通过;进行语法占位,此时为无限死循环
    • 5.for循环语句一般用在循环次数已知的情况下

4.嵌套循环

  • 基本语法:
    for(初始化部分;循环条件部分;迭代部分){
    	循环体部分;
    	...
    	for(初始化部分;循环条件部分;迭代部分){
    		循环体部分;
    		...
    	}
    	...
    }
    
    /**
     * @Author 依山观澜
     * @Date 2021/9/12 8:43
     * @Description 质数(素数):只能被1和它本身整除的自然数,1不是质数;即从2开始,到这个数-1结束为止,都不能被这个数整除
     * @Since version-1.0
     */
    
    public class Test01 {
    
        public static void main(String[] args){
            //标识i是否被j除尽,一旦除尽,即不是质数,修改其值
            boolean isFlag = true;
            //计数器:用于记录质数的个数
            int sum = 0;
    
            //获取当前时间距离1970-01-01 00:00:00的毫秒数
            long start = System.currentTimeMillis();
    
            //遍历100000以内的自然数
            for(int i=2; i<100000; i++){
                //让i除以j
                //优化二:两种优化方式:第一种j<i/2;第二种:Math.sqrt(i);
                //只对本身是质数有效
                //临界值:除一个数等于这个数本身,即开方
               for(int j=2; j<=Math.sqrt(i); j++){
                   if(i % j == 0){
                       isFlag = false;
                       break;//优化一:只对本身非质数的自然数有效
                   }
               }
               if(isFlag){
                   sum++;
               }
               //重置isFlag
               isFlag = true;
            }
            long end = System.currentTimeMillis();
    
            System.out.println("所花费的时间为:"+(end-start)+"毫秒");
            System.out.println("质数个数为:"+sum);
        }
    }
    
    方法二:利用带标签的continue实现
     label:for(int i=2; i<=100000; i++){
             for(int j=2; j<Math.sqrt(i); j++){
                 if(i%j == 0){
                     continue label;
                 }
             }
             sum++;
         }
    
  • 语法说明:
    • 1.嵌套循环是指在一个循环语句的循环体中再定义一个循环语句的语法结构
    • 2.一般两层循环的嵌套循环,外层循环用来控制行数,内层循环用来控制列数,外层循环执行一次,内层循环执行一遍;也可以嵌套多层
    • 3.while,do…while,for循环语句都可以进行嵌套,并且它们之间也可以相互嵌套

5.跳转语句

public static void main(String[] args) {
       label:for(int i=1; i<5; i++){
           for(int j=1; j<10; j++){
               if(j%4 == 0){
                   break label;
               }
               System.out.print(j+",");
           }
           System.out.println();
       }
}


1.break语句

  • 1.使用范围:
    • switch-case
    • 循环结构
  • 2.swtich-case中的作用:
    • 终止某个case并跳出switch语句
  • 3.循环中的作用:
    • 结束当前循环(如果是嵌套循环,只能结束当前层循环,如果想使用break语句跳出外层循环,则需要对外层循环添加标记)
  • 4.带标签的break语句
    • 结束指定标识的一层循环结构
  • 5.注意:
    • break关键字的(直接)后面不能声明执行语句,否则会编译错误(因为无法执行)
public static void main(String[] args) {
       label:for(int i=1; i<5; i++){
           for(int j=1; j<10; j++){
               if(j%4 == 0){
                   continue label;
               }
               System.out.print(j+",");
           }
           System.out.println();
       }
   }


2.continue语句

  • 1.使用范围:
    • 循环结构
  • 2.循环中的作用:
    • 终止本次循环,执行下一次循环(嵌套循环语句中,continue语句后面也可以通过使用标记的方式结束本次外层循环,用法与break语句相似)
  • 3.带标签的continue语句
    • 结束指定标识的一层循环结构的当次循环
  • 4.注意:
    • continue关键字的(直接)后面不能声明执行语句,否则会编译错误(因为无法执行)

12.java中的数组

1.数组的定义

  • 1.数组是内存中一块连续的存储空间,用来存储多个相同类型的数据
  • 2.数组是一组数据的集合,数组中的每个数据被称作元素。数组可以存放任意类型的元素,但同一个数组里存放的元素类型必须一致
  • 3.数组存储基本数据类型存储的是值,存储引用数据类型存储的是堆地址

2.数组的种类

  • 1.按照维度:一维数组,二维数组,多维数组
  • 2.按照元素的数据类型:基本数据类型元素的数组,引用数据类型元素的数组(即对象数组)

3.数组的相关概念

  • 1.数据类型
  • 2.数组名
  • 3.数组元素
  • 4.数组下标(索引,角标)
  • 5.数组长度

4.数组的特点

  • 1.数组是有序排列的
  • 2.数组的长度一旦确定,就不能修改
  • 3.可以直接通过下标(索引)的方式调用指定位置的元素
  • 4.数组本身是引用数据类型,而数组中的元素既可以是基本数据类型,也可以是引用数据类型
  • 5.创建数组对象会在内存中开辟一整块连续的空间,而数组名引用的是这块连续空间的首地址
  • 6.如果创建数组时没有显性赋值,数组会根据相应的元素类型自动赋予默认值

1.一维数组

1.声明和初始化

//1.声明
数据类型[] 数组名;
//2.1静态初始化:定义数组的同时为数组的每个元素赋值
数据类型[] 数组名 = new 数据类型[]{元素,元素。。。};
数据类型[] 数组名 = {元素,元素。。。};
//2.1动态初始化:定义数组时只指定数组的长度,由系统赋初值(或显式赋值)
数据类型[] 数组名 = new 数据类型[数组长度];
  • 说明:
    • 1.不管是动态初始化还是静态初始化,数组一旦完成初始化,其长度就确定了,因为内存中要分配指定的长度的内存空间
    • 2.数组的长度必须为整数
    • 3.通过动态初始化的方式创建数组时必须指定长度,否则无法分配空间,而通过静态初始化的方式创建数组可以不指明长度,长度由{}的元素个数决定,如果没有元素,则为空数组,即长度为0
    • 4.注意当使用数据类型[] 数组名 = new 数据类型[]{元素,元素。。。};不能再指定数组的长度,否则会报错,因为编译器会认为数组指定的元素个数与实际存储的元素个数有可能不一致,存在一定的安全隐患

2.访问数组元素

int[] arr = new int[]{5,6,7};
System.out.println(arr[2]);
  • 说明:
    • 1.可以通过数组下标的方式访问数组中的元素
    • 2.数组的下标都有一个范围,即0~数组长度-1;访问数组的元素时,下标不能超出这个范围,否则会报数组下标越界异常

3.数组的长度

属性:length
数组名.length

4.数组遍历

int[] arr = new int[]{5,6,7};
for(int i=0; i<arr.length; i++){
     System.out.println(arr[i]);
}
  • 说明:
    • 1.依次获取数组中每个元素的过程称为数组遍历

5.默认初始化值

  • 1.整型:默认初始化值0
  • 2.浮点型:默认初始化值0.0
  • 3.char: 默认初始化值0或’\u0000’,而非’0’, 效果像空格,但并不是空格,空格也有对应的asill值
  • 4.boolean:默认初始化值是 false
  • 5.引用类型:默认初始值是null

6.数组的内存解析

  • 1.栈中存放数组引用,该引用并非实际的数据,而是保存指向数组首元素的地址
  • 2.堆中存放的才是数组中的数据
  • 3.引用类型之间,相互赋值传递的是堆地址
  • 4.堆地址寻址公式:数组首元素地址+下标*数组数据类型对应的字节长度

7.数组扩容

  • 1.创建一个长度更大的数组,通常扩大两倍
  • 2.把原数组中的数据复制到新数组中(循环)
  • 3.把原数组的堆地址指向新数组的堆地址
  • 扩容方式(三种)
    //1.for循环扩容
    int[] arr = {1,2,3,4,5};
    int[] arr2 = new int[arr.length*2];
    for(int i=0; i<arr.length; i++){
        arr2[i] = arr[i];
    }
    arr = arr2;
    System.out.println(Arrays.toString(arr));
    //2.利用System.arraycopy扩容
    int[] arr = {1,2,3,4,5};
    int[] arr2 = new int[arr.length*2];
    System.arraycopy(arr,0,arr2,0,arr.length);
    arr = arr2;
    System.out.println(Arrays.toString(arr));
    //3.利用Arrays.copyOf扩容
    int[] arr = {1,2,3,4,5};
    arr = Arrays.copyOf(arr,arr.length*2);
    System.out.println(Arrays.toString(arr));
    

8.可变长参数数组

  • 1.方法中长度可变的参数,实际长度由调用者传入的实参个数决定
  • 2.一个方法中只能定义一个0-1个可变长参数,如果多于一个,则无法区分两个可变长参数的边界值
  • 3.如果参数列表中存在多个参数,可变长参数必须写在最后一位
    访问修饰符 返回值类型 函数名(数据类型...参数名){
    	方法体
    }
    

2.二维数组

1.二维数组的声明和初始化

  • 1.一个一维数组(高纬度)中的元素还是一个一维数组(低维度)
  • 2.创建方式:
    //1.先声明再指明数组长度
    数据类型[][] 数组名;
    数组名 = new 数据类型[高维度数组长度][低纬度数组长度];
    //2.声明的同时直接指明数组长度
    数据类型[][] 数组名 = new 数据类型[高维度数组长度][低纬度数组长度]
    //3.创建的同时直接赋值
    数据类型[][] 数组名 = {{1,2},{3,4},{5,6}...};
    //4.不规则创建
    数据类型[][] 数组名 = new 数据类型[高纬度数组长度][];
    数组名[高纬度数组下标] = new 数据类型[低纬度数组长度];
    
  • 说明:
    • 1.第一种方式元素个数=高纬度数组长度*低纬度数组长度
    • 2.第三种方式外层{}代表高纬度数组,内层{}代表低纬度数组;高维数组长度由内层{}个数决定,低维数组长度由内层{}中的元素个数决定
    • 3.第四种创建方式元素个数=所有低纬度数组长度之和

2.维数组的调用

  • 1.数组名[高维度下标]:返回高维下标对应的低维度数组
  • 2.数组名[高纬度下标][低纬度下标]:返回高纬度下标对应的低纬度数组中下标所对应的元素

3.二维数组的遍历

  • 1.结合双层嵌套循环
  • 2.外层循环循环高纬度数组
  • 3.内层循环循环低纬度数组
  • 4.在内层循环操作数组元素
  • 5.通过数组名.length获取高纬度数组长度
  • 6.通过数组名[高纬度数组下标].length获取低纬度数组长度
    for(int i=0; i<数组名.length; i++){
    	for(int j=0; j<数组名[i].length; j++){
    		循环体;
    	}
    }
    

2.面向对象

1.类和对象的定义

  • 1.:指对现实中对象的共性抽取,通常用来表示同一批具有相同特征和行为的对象
  • 2.对象:指类的实例化,其是计算机内存中的一块存储空间,用来存储现实中对象特性和行为的描述
  • 3.类和对象的关系
    • 1.类是对象的模板
    • 2.对象是类的实例

2.类

  • 1.属性:用来描述对象的特征
    局部变量成员变量(属性)
    位置形参或方法、代码块内部类的内部、方法的外部
    默认值没有
    作用范围从定义开始到定义的{}结束当前整个类
    命名冲突局部变量之间不能重名属性可以和局部重名,局部变量优先更高
  • 2.方法:用来描述对象行为
    方法修饰符 返回值类型 方法名(参数列表){
    	// 操作语句
    }
    

1.属性

  • 1.类的属性分为

    • 1.成员变量
      • 1.类变量/静态变量(static修饰)
      • 2.实例变量
    类变量实例变量
    修饰符static关键字
    访问方式类或类对象类的对象
    生存周期静态成员不依赖于类的特定实例,被类的所有实例共享;static 修饰的方法或者变量不需要依赖于对象来进行访问,只要这个类被加载,Java 虚拟机就可以根据类名找到它们与对象同存亡,被对象调用后才分配内存,调用结束时内存释放
    加载时机运行时Java 虚拟机只为静态变量分配一次内存,在加载类的过程中完成静态变量的内存分配每创建一个实例,Java 虚拟机就会为实例变量分配一次内存
    • 2.局部变量
      • 1.函数形参
      • 2.方法内定义
      • 3.代码块内定义

2.方法

  • 1.构造方法
  • 2.静态方法
  • 3.普通方法

1.方法重载

  • 1.规则
    • 1.方法名必须相同,参数列表必须不同(数据类型,个数,顺序)
    • 2.跟访问修饰符,返回值,异常没有关系
  • 2.本质
    • 1.重载就是同一个类中多个同名方法根据不同的传参来执行不同的逻辑处理

2.方法重写

  • 1.规则
    • 1.存在于继承关系
    • 2.方法名,参数列表,返回值类型必须与父类保持一致
    • 3.访问修饰符必须大于等于父类
    • 4.子类不允许抛出比父类更大的异常
    • 5.构造方法无法被重写
    • 6.子类可以继承并重写父类中的静态方法,但是多态情况下,子类的重写没有意义,因为父类引用执行的静态方法结果永远是父类内容
  • 2.本质
    • 1.重写是子类对父类的允许访问的方法的实现过程进行重新编写
    • 2.当子类对方法进行重写之后,子类对象调用方法执行的是重写之后的内容
区别点重载方法重写方法
发生范围同一个类子类中
参数列表必须修改一定不能修改
返回类型可修改一定不能修改
异常可修改可以减少或删除,一定不能抛出新的或者更广的异常
访问修饰符可修改一定不能做更严格的限制(可以降低限制)
发生阶段编译期运行期

3.this()方法

  • 1.用来调用当前类的构造方法
  • 2.只能写在构造方法有效代码第一行
  • 3.由this()实参列表决定调用哪个构造方法
  • 4.构造方法无法通过this()调用自身,这样会成为死循环调用,会造成内存溢出
  • 5.构造方法之间也不能相互循环调用,否则会死循环,会造成内存溢出

4.super()方法

  • 1.用来调用父类构造方法
  • 2.只能写在构造方法有效代码第一行
  • 3.由super()实参列表决定调用哪个父类构造方法
  • 4.this()super()不能同时出现
  • 5.如果子类构造方法没有显式调用父类构造,那么无参的super()默认存在于子类构造方法第一行
  • 6.当子类构造显式调用this()时,当前构造方法就只能使用默认的无参super()

5.构造方法

  • 1.构造方法是用来创建对象,并且同时给对象的属性赋值
  • 2.实例变量没有手动赋值的时候,系统会赋默认值
  • 3.访问修饰符只能使用public,且构造方法名和类名必须一致
  • 4.构造方法不需要指定返回值类型
  • 5.使用new关键字来调用构造方法
  • 6.当类中没有提供显式构造方法,默认提供一个无参数的构造方法
  • 7.当类中提供了显式构造方法,将不再默认提供无参数构造方法
  • 8.建议将无参数构造方法显示声明,这样一定不会出问题
  • 9.构造方法支持方法重载,不支持方法重写

6.静态方法和普通方法

  • 1.定义: 使用static修饰的方法称为静态方法
  • 2.加载时机:随着类加载而加载到内存中,非静态方法属于对象的具体实例,只有在类的对象创建时才会加载入内存中
  • 3.静态方法只能访问静态数据非静态方法既可以访问静态数据又可以访问非静态数据
  • 4.因为静态方法静态数据会随着类加载而被加载到内存中,而非静态方法非静态数据只有在对象创建时才会加载到内存中,如果静态方法调用了了非静态数据,则在静态方法加载时无法从内存中找到非静态数据,势必会出错,这种做法是Java虚拟机不允许的
  • 5.引用静态方法时,可以用类名.方法名或者对象名.方法名的形式
  • 7.静态方法中不能使用thissuper关键字

7.代码块和静态代码块

class 类名{
	static{
		// 静态初始代码块
	}
	{
		// 普通代码块
	}
}
  • 1.static修饰的代码块称为静态代码块
  • 2.静态代码块只有在类加载时才会执行,而且一个静态代码块没有执行完成时不会执行下一个静态代码块,一般将一些只需要进行一次的初始化操作都放在静态代码块中进行
  • 3.普通代码块是在创建对象的时执行,创建几个对象就执行多少次
  • 4.执行顺序静态代码块 > 普通代码块 > 构造函数
  • 5.静态代码块只能访问类中的静态成员变量
  • 6.普通代码块只能访问类中的成员变量
  • 7.位置:两者一般都写在属性之下,方法之上,静态代码块类似于一个方法,但它不可以存在于任何方法体中,静态代码块可以置于类中的任何地方,类中可以有多个静态初始化块
  • 8.作用:静态代码块一般是为了给静态成员变量赋值,代码块一般是为了给成员变量赋值
  • 9.如果类中包含多个静态代码块,则Java虚拟机将按它们在类中出现的顺序依次执行它们,每个静态代码块只会被执行一次
  • 10.静态代码块与静态方法一样,不能直接访问类的实例变量和实例方法,而需要通过类的实例对象来访问

8.方法返回值

  • 1.指获取到的某个方法体中的代码执行后产生的结果
  • 2.返回值的作用是接收结果,使得其可以用于其他的操作
  • 3.方法返回值可以是基本数据类型,也可以是引用数据类型

3.访问修饰符

修饰符本类同包不同包子类不同包非子类
public可以可以可以可以
protected可以可以可以
default可以可以
private可以
  • 1.决定内容的访问权限
  • 2.只有publicdefault可以修饰
  • 3.以上4个访问修饰符都可以修饰属性方法构造方法
  • 4.以上4个访问修饰符都不能修饰局部变量,只有final可以修饰局部变量
  • 5.jdk9之前default不允许显式声明,之后允许在接口中显式声明default

4.类加载机制

  • 1.参考JVM及GC文章中的类加载子系统

5.Java中只有值传递

  • 1.Java中只有值传递
  • 2.基本数据类型作为形参时传递的是值的拷贝,并不会改变原有的值
  • 3.引用数据类型作为形参时传递的是地址引用的拷贝,改变堆地址中的数据,原数据也会受到影响

3.对象

1.对象的创建方式

  • 1.new关键字
  • 2.反射Class类的newInstance使用类的public无参构造,因此必须有public无参构造才行
  • 3.Constructor.newInstancejava.lang.relect.Constructor有一个newInstance方法可以创建对象,通过这个newInstance方法调用有参数(不再必须是无参)的和私有的构造函数(不再必须是public
  • 4.Clone方法,必须先实现Cloneable接口并复写Objectclone方法,因为该方法是protected的,若不复写外部无法调用
  • 5.反序列化,当序列化和反序列化一个对象,JVM会创建一个单独的对象,在反序列化时,JVM创建对象并不会调用任何构造函数,类需要实现Serializable接口

  • 6.可参考:https://cloud.tencent/developer/article/1497720

2.对象的创建过程

  • 1.给属性分配空间,赋予默认值
  • 2.给属性赋予初始值
  • 3.利用构造给属性赋值

3.对象的内存结构

  • 1.对象引用保存在中,引用保存的是对象的地址(堆地址)

4.this关键字

  • 1.this代表当前对象
  • 2.this.属性名代表当前对象的属性
  • 3.位置:可以存在于构造方法普通方法内部
  • 4.当构造方法中实参和形参的变量名相同时,可以使用this关键字指定当前类的属性

5.super关键字

  • 1.super.属性名:用来指明父类属性
  • 2.super.方法名:用来指明调用父类方法

6.深拷贝和浅拷贝

  • 1.浅拷贝:对基本数据类型进行传递,对引用数据类型进行引用传递的拷贝
  • 2.深拷贝:对基本数据类型进行传递,对引用数据类型创建一个新的对象,并复制其内容

4.抽象类

  • 1.用abstract修饰的类
  • 2.抽象类不能实例化对象,设计理念是为了服务其他子类,所以规定不能实例化,可参考https://blog.csdn/qq_42859864/article/details/103394768
  • 3.其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样
  • 4.由于抽象类不能实例化对象,所以抽象类必须被继承才能被使用
  • 5.父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法
  • 6.Java 中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口
  • 7.privatestaticfinal不能与abstract连用,因为abstract需要被继承实现且无法被实例化

1.抽象方法

  • 1.用abstract修饰的方法
  • 2.抽象类中的抽象方法只是声明,不包含方法体
  • 3.抽象方法具体实现由它的子类实现
  • 4.抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类
  • 5.任何子类必须重写父类的抽象方法,或者声明自身为抽象类
  • 6.最终必须有子类实现该抽象方法,否则从最初的父类到最终的子类都不能用来实例化对象
  • 7.构造方法,静态方法(用 static 修饰的方法)不能声明为抽象方法

5.接口

interface 接口名{
}
class 类名 implements 接口名{

}
  • 1.使用关键字interface
  • 2.类是描述对象的属性和方法,接口则是包含类要实现的方法
  • 3.类通过实现接口的方式,从而来实现接口的抽象方法
  • 4.除非实现接口的类是抽象类,否则该类要定义接口中的所有方法
  • 5.接口无法被实例化,但是可以被实现,一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类
  • 6.接口中可以有属性,且属性都是公开静态常量(反编译后可以看到,所以需要赋初始值)
  • 7.接口中的方法都是公开抽象方法(反编译后可以看到,所以没有方法体)
  • 8.构造方法不能是抽象的,所以接口中不存在构造方法,抽象类中存在默认的构造方法
  • 9.因为接口中内容的修饰符固定,所以一般可以省略不写
  • 10.jdk8后,接口中可以存在默认方法
    default 返回值类型 方法名(参数列表){
    
    }
    
  • 11.为了能给接口拓展新功能,而又不必每个子类都要实现此方法,因此Java8新加了default关键字,被其修饰的方法可以不必由子类实现,并且由dafault修饰的方法在接口中有方法体,这打破了Java之前对接口方法的规范
  • 12.如果接口中的方法跟父类中的方法出现冲突,优先执行父类方法(类优先规则)
  • 13.如果实现类实现的多个接口中存在方法冲突,则实现类必须对该方法再次进行重写,执行自己重写后的内容
  • 14.接口中可以存在静态方法
    static 返回值类型 方法名(参数列表){
    
    }
    
  • 15.JDK9接口中可以存在私有方法
    private 返回值类型 方法名(参数列表){
    
    }
    

1.接口的分类

  • 1.常量式接口:接口中只定义了常量,未定义方法
  • 2.标记式接口:接口中未定义任何内容
  • 3.函数式接口:接口中只定义一个方法
  • 4.普通接口:接口中定义了多个方法

1.实现类

class 类名 extends 父类类名 implements 接口名{

}
  • 1.必须对接口中所有的方法提供方法实现
  • 2.如果实现类不想实现接口中的方法,则其自身只能成为抽象类
  • 3.一个实现类可以同时实现多个接口,一个接口也可以拥有多个实现类
  • 4.当实现类实现多个接口时,必须对所有接口中的所有方法都提供方法实现
  • 5.实现类在实现接口的同时也可以继承父类
  • 6.仍然可以使用多态创建实现类对象,但利用多态创建对象时,引用无法实现类独有内容
  • 7.多态中如果想要使用实现类独有内容,则需要进行强制类型转换,实现类之间不能进行强转

2.接口和抽象类的区别

抽象类接口
关键字abstract class /extendsinterface/implements
属性没有具体要求公开静态常量
方法可以存在非抽象的方法公开抽象方法
构造方法有构造方法没有构造方法
继承性单继承多继承

6.三大核心思想

1.封装

  • 1.保证了程序和数据都不受外部干扰且不被误用
  • 2.封装的目的在于保护信息,保护对象中的数据不被外界随意访问
  • 3.步骤:
    • 1.属性私有化
      private 数据类型 属性名;
      
    • 2.提供取值和赋值的方法(getter\setter方法),使外界通过调用方法的形式访问属性

2.继承

  • 1.指子类拥有父类的全部特征和行为,关键字extends
  • 2.Java 只支持单继承
  • 3.将子类中的共性进行抽取,生成父类,子类在继承关系下可以继承父类所有可被继承的内容
  • 4.继承的前提是建立在is-a关系上
    class 子类类名 extends 父类类名{
    }
    
  • 5.规则
    • 1.子类只能拥有一个父类(单继承),但是一个父类可以拥有多个子类
    • 2.父类的构造方法子类无法继承
    • 3.子类在继承父类内容的基础上可以拥有自己独有的内容
    • 4.父类无法访问子类独有内容
    • 5.子类在是子类的基础上还可以是其他类的父类
    • 6.子类可以继承所有直接父类或者间接父类的所有可被继承的内容
    • 7.父类的私有内容子类不可被继承
    • 8.父类封装:因为父类也是描述型的类,为了数据安全,也需要封装
    • 9.父类封装之后,子类无法直接继承父类私有属性,但是可以通过调用getter/setter方法的方式访问

1.对象创建过程(继承关系)

  • 1.给父/子类属性分配空间,给父/子类属性赋默认值
  • 2.给父类属性赋初始值
  • 3.利用父类构造给父类属性再次赋值
  • 4.给子类属性赋初始值
  • 5.利用子类构造给子类属性再次赋值
  • 6.先创建父类对象才能创建子类对象

3.多态

父类引用 引用名 = new 子类类名();
  • 1.父类引用可以指向多个不同的子类对象
  • 2.使用
    • 1.建立在继承关系之上
    • 2.实际创建的是子类对象
    • 3.方法执行优先执行子类中重写的内容
    • 4.父类引用无法使用子类独有的内容
  • 3.引用类型之间的相互转换
    • 1.自动类型转换
      • 1.子类类型赋值给父类类型
      父类类名 引用名 = new 子类类名();
      
    • 2.强制类型转换
      • 1.父类引用赋值给子类引用
      • 2.转换的子类类型必须跟父类引用指向的子类类型保持一致
      • 3.子类之间不能进行类型转换
      子类类名 引用名 = (子类类名)父类引用名;
      
  • 4.多态的使用场景
    • 1.数组:将数组数据类型声明为父类类型,则该数组中可以存放父类对象及其子类对象
    • 2.参数:将形参声明为父类类型,调用方法时则可以传入父类对象及其子类对象
    • 3.返回值:将返回值类型声明为父类类型,则该方法可以返回父类对象及其子类对象
  • 5.多态的好处
    • 1.减少代码冗余
    • 2.解耦合
    • 3.提升代码可重用性

1.instanceof关键字

  • 1.instanceofJava中的二元运算符,左边是对象,右边是类
  • 2.当对象是类及其子类所创建对象时,返回true;否则,返回false
  • 3.作用:判断引用是否与指定类类型兼容,兼容返回true,不兼容返回false
  • 4.语法
    引用名 instanceof 类名
    
  • 5.应用场景:通常用于类型强转之前判断是否兼容,否则不能强转

7.修饰符

1.static

  • 1.修饰属性
    • 1.类变量,该变量不独属于某个对象,被整个类所有的对象所共有,堆空间只有一个该变量(而不是每个对象都有一个该变量)
    • 2.静态属性可以直接通过类名.属性名的方式访问
    • 3.类名.属性名会直接去方法区查找静态变量
    • 4.static不能修饰局部变量,只能修饰成员变量
  • 2.修饰方法
    • 1.静态方法,静态方法中无法访问非静态的内容
    • 2.通过类名.方法名()的方式访问
    • 3.静态方法仍然可以通过对象名.方法名()的方式访问
    • 4.static无法修饰构造方法
    • 5.静态方法内部无法使用thissuper关键字
    • 6.静态内容可以直接从方法区中访问可能会导致类的实例对象还未创建也不会存在thissuper,有可能会产生冲突
  • 3.修饰代码块
    • 1.静态代码块
    • 2.随着类加载而加载,一般为静态变量赋初始值

2.final

  • 1.修饰属性
    • 1.常量,值不可改变
    • 2.常量不存在默认值,在创建时必须进行初始化
    • 3.final可以修饰局部变量(局部变量只能用final修饰),变成常量值不可修改
    • 4.final可以修饰引用,表现为堆地址不可修改
  • 2.修饰方法
    • 1.将方法变成最终的方法,即不可以被子类重写
  • 3.修饰类
    • 1.该类不可以被继承

3.native

  • 1.用来声明本地方法,该方法的实现由非 Java 语言实现
  • 2.一般用于java与外环境交互,或与操作系统交互
  • 3.native可以与所有其它的java标识符连用,但是abstract除外, 因为 native暗示这些方法是有代码实现的,只不过这些实现体是非java

8.内部类

  • 1.类的内部再次声明定义类
  • 2.作用:打破封装,又不破坏封装
  • 3.分类
    • 1.成员内部类
    • 2.静态内部类
    • 3.局部内部类
    • 4.匿名内部类

1.成员内部类

class 外部类名{
	class 内部类类名{

	}
}
  • 1.位置
    • 1.类的内部,方法的外部,与属性和方法平级
  • 2.使用
    • 1.成员内部类可以无条件访问外部类的所有成员属性成员方法(包括private成员和静态成员)
    • 2.成员内部类不能定义静态内容,但是可以访问外部类静态内容
  • 3.如果外部类属性,内部类属性,内部类局部变量出现重名
    • 1.外部类属性:外部类类名.this.属性名
    • 2.内部类属性:this.属性名
    • 3.局部变量:属性名
  • 4.内部类对象的创建需要依赖外部类对象
    外部类类名.内部类类名 内部类对象名 = 外部类对象名.new 内部类类名
    
  • 5.注意
    • 1.当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员,如果要访问外部类的同名成员,需要通过以下形式进行访问
      • 1.外部类.this.成员变量
      • 2.外部类.this.成员方法
    • 2.虽然成员内部类可以无条件地访问外部类的成员,但外部类想访问成员内部类的成员必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问

2.静态内部类

class 外部类类名{
	static class 内部类类名{
	
	}
}
  • 1.位置
    • 1.同成员内部类
  • 2.使用
    • 1.可以定义静态内容,但是无法访问外部类非静态内容
  • 3.如果外部类属性,内部类属性,内部类局部变量出现重名
    • 1.访问外部类静态属性:外部类类名.属性名
    • 2.访问内部类静态属性:外部类类名.内部类类名.属性名
  • 4.静态内部类对象创建要依赖于外部类类名,可直接通过外部类类名.内部类类名.静态内容的方式直接访问内部类静态内容

3.局部内部类

class 外部类类名{
	访问修饰符 返回值类型 方法名(参数列表){
		class n内部类类名{
		
		}
	}
}
  • 1.位置
    • 1.定义在外部类方法内部,跟局部变量平级
  • 2.使用
    • 1.作用范围:从定义行开始,到所在代码块结束
    • 2.无法定义静态内容
    • 3.可以访问外部类的局部变量,但是该局部变量必须是常量(jdk7之前必须用final显式修饰,jdk8之后只需要没有再次更改值)
    • 4.局部内部类对象只能在所属的外部类方法中创建

4.匿名内部类

接口名/父类类名 引用名 = new 接口名/父类类名(){
	
}
  • 1.位置
    • 1.利用多态创建接口实现类/子类对象时
  • 2.特点
    • 1.将类的声明,方法的定义,对象的创建合三为一
  • 3.使用
    • 1.必须继承一个父类或者实现一个接口
    • 2.一个匿名内部类只能创建一个对象,并且该对象只属于语法声明的父类或者接口引用
    • 3.一个引用只能拥有一个匿名内部类,一个匿名内部类也只能属性一个引用
    • 4.匿名内部类中只能拥有一个默认的无参构造,无法显式声明构造
    • 5.匿名内部类无法定义静态内容

5.lambda表达式

接口名 引用名 = (形参列表)->{//方法体实现部分}
  • 1.简化匿名内部类,为接口提供实现类对象
  • 2.使用
    • 1.jdk8之后只能作用于函数式接口
    • 2.小括号中的形参声明要严格跟函数式接口中的形参声明保持一致
    • 3.大括号中的内容是对接口方法提供的方法实现
  • 3.简化方式
    • 1.形参的数据类型可省略
    • 2.如果形参只有一个,小括号可省略,注意如果没有参数,()不可以省略
    • 3.如果方法体语句只有一条,大括号可省略
    • 4.如果方法体语句只有一条return返回语句,大括号和return都可省略,注意return{}必须同时省略

9.包装类

  • 1.基于八大基本数据类型
    byteshortintlongfloatdoublecharboolean
    ByteShortIntegerLongFloatDoubleCharacterBoolean
  • 2.基本数据类型跟包装类型转换
    • 1.基本数据类型转包装类
      • 1.利用构造方法
      • 2.利用valueOf方法
      包装类型 引用名 = new 包装数据类型(基本数据类型);
      包装类 引用名 = 包装类型.valueOf(基本数据类型);
      
    • 2.包装类转基本数据类型
      • 1.利用xxxValue()方法,xxx指要转的基本数据类型
      基本数据类型 变量名=包装类型.xxxValue();
      
    • 3.JDK5之后,官方提供了自动封箱及自动拆箱,基本数据类型跟包装类型可以直接相互赋值,会由编译器完成转换
      • 1.自动封箱:基转包
      • 2.自动拆箱:包转基
  • 3.基本数据类型和String转换
    • 1.基本数据类型转String
      • 1.字符串拼接
      • 2.valueOf()
      String 变量名 = 基本类型+"";
      String 变量名 = String.valueOf(基本类型);
      
    • 2.String转基本数据类型
      • 1.parseXxx()Xxx指要转向的基本数据类型
      // String中的数据必须是基本数据类型能接受的数据
      // 否则会报java.lang.NumberFormatException数据类型转换异常
      基本类型 变量名 = 对应包装类型.parseXxx(String类型)
      
  • 4.包装类型和String转换
    • 1.包装类型转String
      • 1.字符串拼接
      • 2.toString()
      string 变量名 = 包装类型+"";
      String 变量名=包装类型.toString();
      
    • 2.String转包装类型
      • 1.parseXxx()Xxx指要转向的基本数据类型
      // String中的数据必须是基本数据类型能接受的数据
      // 否则会报java.lang.NumberFormatException数据类型转换异常
      基本类型 变量名 = 对应包装类型.parseXxx(String类型)
      

10.常用类

1.Object

  • 1.所有类的父类
  • 2.如果一个类没有显式声明父类,那一定直接继承自Object
  • 3.Object中存放所有类都默认拥有的内容,子类可以根据自身需要选择是否对继承过来的方法进行重写
  • 4.常用方法
    • 1.getClass():返回当前引用的实际对象类型
    • 2.hashCode():获取当前对象的哈希码值
    • 3.equals(Object o):判断两个对象是否相同,默认比较的是内存地址
    • 4.toString():返回对象的详细信息,会在输出引用名时自动调用
    • 5.finalize():回收垃圾对象

1.finalize()

  • 1.finalize()方法用于垃圾回收,一般情况下不需要实现
  • 2.当对象被回收的时候需要释放一些资源;如socket链接,在对象初始化时创建,整个生命周期内有效,那么需要实现finalize方法关闭这个链接
  • 3.但是当调用finalize方法后,并不意味着GC会立即回收该对象,所以有可能真正调用的时候,对象又不需要回收了,然后到了真正要回收的时候,因为之前调用过一次,这次又不会调用了,产生问题,所以不推荐使用finalize方法
  • 4.垃圾回收机制:当内存已经满到不足以支持创建新的对象时,虚拟机会自动调用垃圾对象的finalize()方法对该垃圾对象进行销毁,从而释放内存空间
  • 5.垃圾对象的判断标准:当对象没有引用指向的时候,虚拟机则会该对象为垃圾对象
  • 6.手动进行垃圾回收:代码中手动调用System.gc()方法可以手动进行强制垃圾对象回收

2.String

3.Arrays

11.整型常量池

  • 1.Integer常量池是Java 5中引入的一个有助于节省内存、提高性能的特性
  • 2.Integer中有个静态内部类IntegerCache,里面有个常量cache[],即Integer常量池,常量池的大小为一个字节(-128~127),当用包装类直接使用区间内的数字时而不new对象时,会直接从常量池中取出,而不会额外在堆中开辟空间,节省内存空间
  • 3.所有整数类型的类都有类似的缓存机制
    • 1.ByteCache 用于缓存Byte对象
    • 2.ShortCache 用于缓存Short对象
    • 3.LongCache 用于缓存Long对象
  • 4.ByteShortLong的常量池范围默认都是: -128 到 127Byte的所有值都在常量池中,所以用生成的相同值对象都是相等的
  • 5.Character对象也有CharacterCache缓存 池,范围是 0127
  • 6.所有整型(Byte,Short,Long)的比较规律与Integer是一样的,但只有 Integer 可以通过参数改变范围,通过 JVM 的启动参数 -XX:AutoBoxCacheMax=size 修改


3.泛型

  • 1.Java 泛型(generics)是JDK 5中引入的一个特性,,泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型
  • 2.泛型的本质是参数化类型,即操作的数据类型被指定为一个参数

1.泛型方法

  • 1.该方法在调用时可以接收不同类型的参数,根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用
  • 2.规则
    • 1.所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前
    • 2.每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开,一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符
    • 3.类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符
    • 4.泛型方法体的声明和其他方法一样,类型参数只能代表引用类型,不能是基本数据类型
  • 3.java中泛型标记符
    • 1.E - Element (在集合中使用,因为集合中存放的是元素)
    • 2.T - Type(Java 类)
    • 3.K - Key(键)
    • 4.V - Value(值)
    • 5.N - Number(数值类型)
    • 6. - 表示不确定的 java 类型

2.泛型类/接口

  • 1.泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分
  • 2.泛型类的类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开
  • 3.一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符

3.泛型集合

  • 1.使用泛型集合时注意类型要保持一致,否则会出现类型转化异常

4.有界泛型

  • 1.有界的泛型:限制那些被允许传递到一个类型参数的类型种类范围
  • 2.其界限分为上界下界
    • 1.上界
      • 1.关键字extends
      • 2.比如一个操作数字的方法可能只希望接受Number或者Number子类的实例
      • 3.声明一个有界的类型参数,首先列出类型参数的名称,后跟extends关键字,最后紧跟它的上界

      • 4.//1处会出现错误,因为getUperNumber()方法中的参数已经限定了参数泛型上限为Number,所以泛型为String是不在这个范围之内
    • 2.下界
      • 1.类型通配符下界通过形如List<? super Number>来定义
      • 2.表示类型只能接受Number及其上层父类类型,如Object类型的实例

4.枚举(Enum)

package com.wd.entity;

public enum Season {
   SPRING,SUMMER,AUTUMN,WINTER
}
package com.wd.test;

import com.wd.entity.Season;

public class Test01 {
   public static void main(String[] args){
       test();
   }

   public static void test(){
       System.out.println("春天:" + Season.SPRING);
       System.out.println("夏天:" + Season.SUMMER.name());
       System.out.println("秋天:" + Season.AUTUMN.ordinal());
       System.out.println("冬天:" + Season.WINTER.ordinal());
   }
}






  • 1.枚举类型的关键字:enum,引入版本:JDK5
  • 2.枚举类本质上是一个class隐式继承于Enum抽象类(该继承关系不显示,可通过反编译显示),且不能继承其他类,因为Java类不支持多继承,但可以实现接口
  • 3.枚举类是final的,因此无法继承该类
  • 4.枚举类的构造函数只能是private,因为枚举的实现是多例模式,为了防止在类的外部就可以通过构造器来新建实例
  • 5.抽象Enum类有nameordinal函数,其中name方法的作用是取得名字,ordinal方法的作用的是返回每个枚举常量的索引,类似数组索引
  • 6.values() :返回枚举类中所有的值,valueOf():方法返回指定字符串值的枚举常量

  • 7.定义的每个枚举值都是该类的一个公共,静态,常量成员,且该成员的类型仍然是当前类

5.注解(Annotation)

  • 1.Java 注解,JDK5 引入的一种注释机制,同ClassInterface一样,注解也属于一种类型
  • 2.Java 注解的修饰符是@Interface,注解可以修饰Java中的方法变量参数
  • 3.不同于JavadocJava注解可以通过反射获取注解内容,同时编译期生成.class文件时注解可以被嵌入到字节码
  • 4.Java 虚拟机可以保留注解内容,在运行时可以获取到注解内容 , 同时支持自定义Java注解
  • 5.元数据:注解又称为元数据,即一种描述数据的数据
  • 6.元注解:作用在其他注解上的注解,即一种描述注解的注解,一般用于自定义注解
  • 7.Java SE提供 11 个内置注解,其中有5 个是基本注解,自于java.lang包;有 6个是元注解,来自于java.lang.annotation
  • 8.基本注解包括
    • 1.@Override
    • 2.@Deprecated
    • 3.@SuppressWarnings
    • 4.@SafeVarargs
    • 5.@FunctionalInterface
  • 9.元注解包括
    • 1.@Documented
    • 2.@Target
    • 3.@Retention
    • 4.@Inherited
    • 5.@Repeatable
    • 6.@Native
  • 10.注解常见作用
    • 1.生成帮助文档@see@param@return
    • 2.跟踪代码依赖性,实现替代配置文件功能Spring 2.5开始的基于注解配置,作用就是减少配置
    • 3.编译时进行格式检查@Override编译时检查是否重写父类方法,如果父类中不存在该方法,编译器便会报错,提示该方法没有重写父类中的方法

1.基本注解

1.@Override

  • 1.@Override只能修饰方法且只能用于方法重写
  • 2.@Override检查该方法是否是重写方法,如果发现其父类或引用的接口中并没有该方法时,会报编译错误

2. @Deprecated

  • 1.@Deprecated用来注解接口成员方法成员变量等,用于表示某个元素(类、方法等)已过时
  • 2.当其他程序使用已过时的元素时,编译器将会给出警告
  • 3.Java 9@Deprecated注解增加了2个属性
    • 1.forRemoval:该boolean类型的属性指定该API将来是否会被删除
    • 2.since:该String类型的属性指定该API从哪个版本被标记为过时
    class Test {
        // since属性指定从哪个版本开始被标记成过时,forRemoval指定该API将来会被删除
        @Deprecated(since = "9", forRemoval = true)
        public void print() {
            System.out.println("这里是C语言中文网Java教程!");
        }
    }
    public class DeprecatedTest {
        public static void main(String[] args) {
            // 下面使用info()方法时将会被编译器警告
            new Test().print();
        }
    }
    

3.@SuppressWarining

  • 1.@SuppressWarnings注解指示被该注解修饰的元素(以及该程素中的所有子元素)取消显示指定的编译器警告,且会作用于该元素的所有子元素
  • 2.通常情况下如果程序中使用没有泛型限制的集合将会引起编译器警告,为了避免这种编译器警告,可以使用 @SuppressWarnings 注解消除这些警告
  • 3.注解的使用方式有以下三种
    • 1.抑制单类型的警告:@SuppressWarnings("unchecked")
    • 2.抑制多类型的警告:@SuppressWarnings("unchecked","rawtypes")
    • 3.抑制所有类型的警告:@SuppressWarnings("all")
      关键字用途
      all抑制所有警告
      boxing抑制装箱、拆箱操作时候的警告
      cast抑制映射相关的警告
      dep-ann抑制启用注释的警告
      deprecation抑制过期方法警告
      fallthrough抑制在 switch 中缺失 breaks 的警告
      finally抑制 finally 模块没有返回的警告
      hiding抑制相对于隐藏变量的局部变量的警告
      incomplete-switch忽略不完整的 switch 语句
      nls忽略非 nls 格式的字符
      null忽略对 null 的操作
      rawtypes使用 generics 时忽略没有指定相应的类型
      restriction抑制禁止使用劝阻或禁止引用的警告
      serial忽略在 serializable 类中没有声明 serialVersionUID 变量
      static-access抑制不正确的静态访问方式警告
      synthetic-access抑制子类没有按最优方法访问内部类的警告
      unchecked抑制没有进行类型检查操作的警告
      unqualified-field-access抑制没有权限访问的域的警告
      unused抑制没被使用过的代码的警告

4.@SafeVarargs

  • 1.@SafeVarargs注解忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告
  • 2.@SafeVarargs注解不适用于非 static非 final声明的方法,对于未声明为 staticfinal的方法,如果要抑制unchecked警告,可使用@SuppressWarnings注解

5.@FunctionalInterface

  • 1.@FunctionalInterface注解用来指定某个接口必须是函数式接口
  • 2.@FunctionalInterface注解只能修饰接口,不能修饰其它程序元素
  • 3.函数式接口是为Java 8Lambda表达式准备的,Java 8允许使用 Lambda表达式创建函数式接口的实例,因此Java 8专门增加@FunctionalInterface注解
  • 4.@FunctionalInterface注解的作用只是告诉编译器检查这个接口,保证该接口只能包含一个抽象方法,否则就会编译出错

2.元注解

  • 1.元注解是负责对其它注解进行说明的注解,自定义注解时可以使用元注解
  • 2.Java 5定义了4个元注解
    • 1.@Documented
    • 2.@Target
    • 3.@Retention
    • 4.@Inherited
  • 3.Java 8增加2个元注解
    • 1.@Repeatable
    • 2.@Native
  • 4.这些注解都在java.lang.annotation包中

1.@Documented

  • 1.@Documented是一个标记注解,没有成员变量
  • 2.@Documented标记这些注解是否包含在用户文档中,注解修饰的注解类会被JavaDoc工具提取成文档
  • 3.默认情况下JavaDoc 不包括注解,如果声明注解时指定@Documented,就会被JavaDoc工具处理,注解类型信息会被包括在生成的帮助文档中

2.@Target

  • 1.@Target注解用来指定一个注解的使用范围,即被 @Target 修饰的注解可以用在什么地方
  • 2.@Target注解有一个成员变量value用来设置范围,valuejava.lang.annotation.ElementType枚举类型的数组
  • 3.下表为ElementType的枚举常量
    名称说明
    TYPE用于 类、接口(包括注解类型)或 enum 声明
    FIELD用于 字段声明(包括enum常量)
    METHOD用于 方法声明
    PARAMETER用于 正式参数声明
    CONSTRUCTOR用于 构造函数声明
    LOCAL_VARIABLE用于 局部变量声明
    ANNOTATION_TYPE用于 注释类型声明
    PACKAGE用于 包声明
    TYPE_PARAMETER用于 类型参数声明
    TYPE_USE用于 类型的使用


3.@Retention

  • 1.@Retention用于描述注解的生命周期,即该注解被保留的时间长短
  • 2.@Retention注解中的成员变量value用来设置保留策略,valuejava.lang.annotation.RetentionPolicy枚举类型
  • 3.下表为RetentionPolicy的枚举常量
    名称说明
    SOURCE源文件有效 ,编译期丢弃
    CLASS默认状态,字节码文件中有效,注释将由编译器记录在类文件中,运行时丢弃
    RUNTIME运行时有效,注释将由编译器记录在类文件中,并由VM在运行时保留,因此用RUNTIME修饰的注解可以反射式读取
  • 3.生命周期长短排序为:SOURCE < CLASS < RUNTIME
  • 4.如果需要运行时动态获取注解信息,只能用RUNTIME注解;如果需要编译时进行一些预处理操作,可选用CLASS注解;如果只是做一些检查性的操作,可选用SOURCE注解

4.@Inherited

  • 1.@Inherited是一个标记注解,用来指定该注解可以被子类继承
  • 2.@Inherited 即如果某个类使用了被@Inherited 修饰的注解,则其子类将自动具有该注解

5.@Repeatable

  • 1.注解是Java 8增加的,它允许在相同的程序元素中重复注解
  • 2.Java 8版本以前,同一个程序元素前最多只能有一个相同类型的注解,Java 8开始支持@Repeatable,标识某注解可以在同一个声明上使用多次

6.@Native

  • 1.@Native注解修饰成员变量,则表示这个变量可以被本地代码引用

3.自定义注解

  • 1.声明自定义注解使用@interface关键字,默认情况下,注解可以在程序的任何地方使用,通常用于修饰类、接口、方法和变量等
    // 定义一个简单的注解类型
    public @interface Test {
    }
    
  • 2.定义注解和定义类相似,注解前面的访问修饰符和类一样有两种
    • 1.公有访问权限(public
    • 2.默认访问权限(default,默认不写)
  • 3.不包含任何成员变量的注解称为标记注解,上面声明的Test注解以及基本注解中的 @Override 注解都属于标记注解
  • 4.根据需要注解中可以定义成员变量,成员变量以无形参的方法形式来声明,其方法名和返回值定义了该成员变量的名字和类型
  • 5.如果没有在声明时指定成员变量的默认值则在使用时需要手动赋值,否则会报错
    public @interface MyTag {
        // 定义带两个成员变量的注解
        // 注解中的成员变量以方法的形式来定义
        String name();
        int age();
    }
    
    public class Test {
        // 使用带成员变量的注解时,需要为成员变量赋值
        @MyTag(name="xx", age=6)
        public void info() {
            ...
        }
        ...
    }
    
  • 6.成员变量也可以有访问权限修饰符,但是只能有公有权限默认权限(不显式声明)
  • 7.注解中的成员变量也可以有默认值,可使用default关键字指定默认值
    public @interface MyTag {
        // 定义了两个成员变量的注解
        // 使用default为两个成员变量指定初始值
        String name() default "java";
        int age() default 1;
    }
    
  • 8.如果为注解的成员变量指定了默认值,那么使用该注解时可以不为这些成员变量赋值,而是直接使用默认值
    public class Test {
        // 使用带成员变量的注解
        // MyTag注释的成员变量有默认值,所以可以不为它的成员变量赋值
        @MyTag
        public void info() {
            ...
        }
        ...
    }
    
  • 9.可以在使用注解时为成员变量指定值,如果为注解的成员变量指定了值,则默认值不会起作用
  • 10.根据注解是否包含成员变量分为以下两类
    • 1.标记注解:没有定义成员变量的注解类型被称为标记注解,这种注解仅利用自身的存在与否来提供信息
    • 2.元数据注解:包含成员变量的注解,因为可以接受更多的元数据,所以也被称为元数据注解
  • 11.注解的使用基于反射,通过反射可以去获取注解中的信息


6.反射

1.反射机制

  • 反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息(例:成员变量,成员方法,构造器,注解等等。可查看Class类API),并能操作对象的属性及方法。
  • 加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息,通过这个对象得到类的结构,这个对象就像一面镜子,透过这个镜子看到类的结构,所有形象的称之为:反射
  • 1.获取想要获取对象的全类路径
  • 2.通过犬类路径加载类,返回Class类型的对象
  • 3.通过Class类型的对象获取到想要获取的对象实例
  • getClass获取对象的运行类型

2.Class类

  • Class也是类,因此也继承Object类
  • Class类对象不是new出来的,而是系统创建的,通过类加载器加载出来,通过loadClass
  • 对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
  • 4.每个类的实例都会记得自己是由哪个Class实例生产
  • 5.通过Class可以完整地得到一个类的完整结构,通过一系列API
  • 6.Class对象是存放在堆的
  • 7.类的字节码二进制数据,是放在方法区的,有的地方法称为类的元数据(包括 方法代码,变量名,方法名,访问权限等等)

获取Class类对象

  • 1.前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException
  • 应用场景:多用于配置文件,读取类全路径,加载类
  • 2.前提:若已知具体的类,通过类的class获取,该方式最为安全可靠,程序性能最高
  • 应用场景:多用于参数传递,比如通过反射得到对应构造器对象

3.类加载

4.反射获取类的结构信息

5.反射相关类

  • 1.java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象
  • 2.java.lang.reflect.Method:代表类的方法,Method对象表示某个类的方法
  • 3.java.lang.reflect.Field:代表类的成员变量,Field对象表示某个类的成员变量
  • 4.java.lang.reflect.Constructor:代表类的构造方法,Constructor对象表示构造器

6.反射调用性能优化

  • 反射调用优化-关闭访问检查
  • 1.Method和Field,Constructor对象都有setAccessible()方法
  • 2.setAccessible作用是启动和禁用访问安全检查的开关
  • 参数值为true表示反射的对象在使用时取消访问检查,提高反射的效率。参数为false则表示反射的对象执行访问检查

7.Class类常用方法

1.反射的定义

  • Java反射是指:在运行状态中,对于任意一个类,能够知道该类的所有属性和方法等信息,对于任意一个对象,能够调用该对象的任意方法和属性等信息,并且能改变它的属性
  • 反射机制是Java被视为准动态语言的一个关键性质(该机制允许程序在运行时通过反射取得任何一个已知名称的class的内部信息,包括:正在运行中的类的属性信息,正在运行中的类的方法信息,正在运行中的类的构造信息,正在运行中的类的访问修饰符,注解等等)

2.反射的作用

  • 反射机制允许程序在运行时取得任何一个已知名称的class的内部信息,包括包括其modifiers(修饰符),fields(属性),methods(方法)等,并可于运行时改变fields内容或调用methods
  • 首先需要获取Class类
  • 1.在运行时判断任意一个对象所属的类
  • 2.在运行时构造任意一个类的对象
  • 3.在运行时得到任意一个类所具有的成员变量和方法
  • 4.在运行时调用任意一个对象的成员变量和方法
  • 5.生产动态代理

2.反射的优缺点

  • 优点:反射机制允许程序在运行时取得任何一个已知名称的class的内部信息,包括包括其modifiers(修饰符),fields(属性),methods(方法)等,并可于运行时改变fields内容或调用methods。那么我们便可以更灵活的编写代码,代码可以在运行时装配,无需在组件之间进行源代码链接,降低代码的耦合度;还有动态代理的实现等等。
  • 缺点:反射使用不当会造成很高的资源消耗
  • 使用反射可以打破封装性,导致java对象的属性不安全(可以修改对象的私有属性)
  • 直接通过反射获取子类的对象是不能获取父类的属性值的,必须根据反射获得的子类 Class 对象在调用 getSuperclass() 方法获取父类对象,然后再通过父类对象去获取父类的属性值
  • 优点:可以动态的创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支持
  • 缺点:使用反射基本是解释执行,对执行速度有影响

3.反射的使用步骤

  • 1.使用反射需要首先获取
package dao;

import domains.Person;

import java.lang.reflect.Field;

public class TestPerson {
    public static void main(String[] args) throws NoSuchFieldException {
//        Class<Person> personClass = Person.class;

//        Person person = new Person();
//        Class<? extends Person> a = person.getClass();

        try {
            Class<?> a = Class.forName("domains.Person");
            Person o = (Person) a.newInstance();

            int age = o.getAge();
            String name = o.getName();

            System.out.println(age);
            System.out.println(name);

            System.out.println("--------------------");

            Field[] fields = a.getFields();
            for (Field field : fields) {
                System.out.println(field);
            }

            System.out.println("--------------------");

            Field[] declaredFields = a.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                System.out.println(declaredField);
            }

            System.out.println("--------------------");

            Field b = a.getDeclaredField("name");
            Object c = a.newInstance();
            b.setAccessible(true);
            b.set(c,"JVM");
            System.out.println(b.get(c));
            System.out.println(c.toString());

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }

    }
}

4.实例说明

  • 1.获得Class有三种方式:
    • 1.通过对象调用 getClass() 方法获取,通常应用在:传过来一个 Object类型的对象但不知道具体是什么类
    • 2.直接通过类名.class 的方式得到,该方法最为安全可靠,程序性能更高。同时说明任何一个类都有一个隐含的静态成员变量 class
    • 3.通过 Class 对象的 forName() 静态方法来获取(用的最多),但可能抛出ClassNotFoundException 异常
  • 2.一个类在 JVM 中只会有一个 Class 实例,即我们对上面获取的 c1,c2,c3进行 equals 比较,发现都是true
  • 3.通过 Class 类获取成员变量、成员方法、接口、超类、构造方法等(查阅 API 可以看到 Class 更多方法)
    • getName():获得类的完整名字。
    • getFields():获得类的public类型的属性。
    • getDeclaredFields():获得类的所有属性。包括private 声明的和继承类
    • getMethods():获得类的public类型的方法。
    • getDeclaredMethods():获得类的所有方法。包括private 声明的和继承类
    • getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型。
    • getConstructors():获得类的public类型的构造方法。
    • getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型。
    • newInstance():通过类的不带参数的构造方法创建这个类的一个对象。
  • 注意:setAccessible(true)启用和禁用访问安全检查的开关,值为 true,则表示反射的对象在使用时应该取消 java 语言的访问检查;反之不取消。如果不取消直接改变类的私有属性会报错

7.Java各版本特性

1.Lambda表达式

  • 定义:Lambda表达式是一个匿名函数,可以把Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)
  • 实质:语法糖,底层还是一个方法
    package domains;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    
    /**
     * 员工
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Getter
    public class Employee {
        private String name;
        private int age;
        private double salary;
    }
    
    package dao;
    
    public interface TestPredicate<T> {
        boolean test(T e);
    }
    
    package dao.daoImpl;
    
    import dao.TestPredicate;
    import domains.Employee;
    
    public class FilterEmployeeByAge implements TestPredicate<Employee> {
        @Override
        public boolean test(Employee employee) {
            return employee.getAge() > 24;
        }
    }
    
    package dao.daoImpl;
    
    import dao.TestPredicate;
    import domains.Employee;
    
    public class FilterEmployeeBySalary implements TestPredicate<Employee> {
        @Override
        public boolean test(Employee employee) {
            return employee.getSalary() > 8000;
        }
    }
    
    import dao.TestPredicate;
    import dao.daoImpl.FilterEmployeeByAge;
    import dao.daoImpl.FilterEmployeeBySalary;
    import domains.Employee;
    import org.junit.Test;
    import java.util.*;
    
    public class TestLambda {
    
        List<Employee> employees = Arrays.asList(
                new Employee("张三",8,100),
                new Employee("李四",12,500),
                new Employee("王五",18,3000),
                new Employee("赵六",24,8000),
                new Employee("田七",35,15000),
                new Employee("吴八",40,25000)
        );
    
        /**
         * 原始
         */
        @Test
        public void test1(){
            //比较器 - 匿名内部类
            Comparator<Integer> comparator = new Comparator<Integer>(){
                @Override
                public int compare(Integer o1, Integer o2) {
                    return Integer.compare(o1,o2);
                }
            };
            //注意:比较器的类型需要和TreeSet类型一致
            TreeSet<Integer> treeSet = new TreeSet<>(comparator);
        }
    
        /**
         * Lambda表达式化简
         */
        @Test
        public void test2(){
            //化简1
            Comparator<Integer> comparator = (x,y) -> Integer.compare(x,y);
            TreeSet<Integer> treeSet = new TreeSet<>(comparator);
            //化简2
            TreeSet<Integer> integerTreeSet = new TreeSet<>((x,y) -> Integer.compare(x,y));
        }
    
        /**
         * 需求1:获取员工年龄大于24岁的员工信息
         */
        @Test
        public void test3(){
            List<Employee> filterEmployees = filterAgeEmployees(employees);
            for (Employee filterEmployee : filterEmployees) {
                System.out.println(filterEmployee);
                /*  结果:
                 Employee(name=田七, age=35, salary=15000.0)
                 Employee(name=吴八, age=40, salary=25000.0)
                */
            }
        }
    
        /**
         * 需求1:获取员工年龄大于24岁的员工信息 优化1
         */
        @Test
        public void test4(){
            List<Employee> filterEmployees = filterEmployee(employees, new FilterEmployeeByAge());
            for (Employee filterEmployee : filterEmployees) {
                System.out.println(filterEmployee);
                /*  结果:
                    Employee(name=田七, age=35, salary=15000.0)
                    Employee(name=吴八, age=40, salary=25000.0)
                */
            }
        }
    
        /**
         * 需求2:获取员工工资大于8000的员工信息
         */
        @Test
        public void test5(){
            List<Employee> filterEmployees = filterSalaryEmployees(employees);
            for (Employee filterEmployee : filterEmployees) {
                System.out.println(filterEmployee);
                /*  结果:
                    Employee(name=田七, age=35, salary=15000.0)
                    Employee(name=吴八, age=40, salary=25000.0)
                */
            }
        }
    
        /**
         * 需求2:获取员工工资大于8000的员工信息 优化1
         */
        @Test
        public void test6(){
            List<Employee> filterEmployees = filterEmployee(employees,new FilterEmployeeBySalary());
            for (Employee filterEmployee : filterEmployees) {
                System.out.println(filterEmployee);
                /*  结果:
                    Employee(name=田七, age=35, salary=15000.0)
                    Employee(name=吴八, age=40, salary=25000.0)
                */
            }
        }
    
        /**
         * 根据年龄条件过滤员工信息
         * @param employees
         * @return
         */
        public List<Employee> filterAgeEmployees(List<Employee> employees){
            ArrayList<Employee> employeeArrayList = new ArrayList<>();
            for (Employee employee : employees) {
                if(employee.getAge() > 24){
                    employeeArrayList.add(employee);
                }
            }
            return employeeArrayList;
        }
    
        /**
         * 根据薪水条件过滤员工信息
         * @param employees
         * @return
         */
        public List<Employee> filterSalaryEmployees(List<Employee> employees){
            ArrayList<Employee> employeeArrayList = new ArrayList<>();
            for (Employee employee : employees) {
                if(employee.getSalary() > 8000){
                    employeeArrayList.add(employee);
                }
            }
            return employeeArrayList;
        }
    
        //优化方式一:策略设计模式. 优点:可以根据传入的策略执行不同的策略
        //注意:泛型类型需要填写,否则lambda会发生类型不匹配异常
        public List<Employee> filterEmployee(List<Employee> list, TestPredicate<Employee> predicate){
         ArrayList<Employee> employeeArrayList = new ArrayList<>();
            for (Employee employee : employees) {
                if(predicate.test(employee)){
                    employeeArrayList.add(employee);
                }
            }
            return employeeArrayList;
        }
    
        //优化方式二:匿名内部类
        @Test
        public void test7(){
            List<Employee> filterEmployees = filterEmployee(employees,new TestPredicate<Employee>(){
                @Override
                public boolean test(Employee employee) {
                    return employee.getSalary() < 5000;
                }
            });
            for (Employee filterEmployee : filterEmployees) {
                System.out.println(filterEmployee);
                /*  结果:
                    Employee(name=张三, age=8, salary=100.0)
                    Employee(name=李四, age=12, salary=500.0)
                    Employee(name=王五, age=18, salary=3000.0)
                */
            }
        }
        //优化方式三:Lambda表达式
        @Test
        public void test8(){
            List<Employee> filterEmployees = filterEmployee(employees,(e) -> e.getSalary() < 5000);
            filterEmployees.forEach(System.out::println);
        }
    
        //优化方式四:Stream API
        @Test
     public void test9(){
            employees.stream().filter((e) -> e.getSalary() < 5000).forEach(System.out::println);
        }
    
    }
    

1.Lambda基础语法

import org.junit.Test;

import java.util.Comparator;
import java.util.function.Consumer;

/**
 * Lambda表达式的基础语法:Java8中引入了一个新的操作符->,该操作符为箭>	头操作符或Lambda操作符
 * 箭头操作符将Lambda表达式拆分成两部分:
 * 左侧:Lambda表达式的参数列表
 * 右侧:Lambda表达式中所需执行的功能,即Lambda体
 *
 * 语法格式一:无参数,无返回值(小括号不可省略)
 *      () -> System.out.println("Hello Lambda");
 *
 * 语法格式二:一个参数,无返回值(小括号可省略,建议加上)
 *      x -> System.out.println(X);
 *
 * 语法格式三:两个及两个以上参数,并且Lambda体中有多条语句,小括号和大括号和return都不能省略
 *
 * 语法格式四:如果Lambda体中只有一条语句,return和大括号皆可省略
 *
 * 语法格式五:Lambda表达式的参数列表的数据类型可以省略不写,因为JVM编辑器通过上下文可以推断出数据类型,即类型推断
 *
 * 总结:
 *      1.可选类型声明:不需要声明参数类型,编译器可以通过上下文可以推断出数据类型
 *      2.可选参数圆括号:一个参数无需定义圆括号,但多个参数或无参需要定义圆括号
 *      3.可选的大括号:如果主题体只包含了一个语句,就不需要使用大括号,否则需要大括号
 *      4.可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,否则需要加return
 *
 * Lambda需要函数式接口的支持
 *      * 函数式接口:接口中只有一个抽象方法的接口
 *      * 通过注解@FuncationalInterface修饰可以检查是否为函数式接口
 *
 * 注意事项:
 *      1.lambda表达式只能引用标记了final的外层局部变量,即不能在Lambda内部修改定义在域外的局部变量,否则会编译错误(jdk7)
 *      2.lambda表达式的引用的外层局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义,jdk8)
 *      3.lambda达式当中不允许声明一个与局部变量同名的参数或者局部变量。
 */
public class TestLambda2 {
    
    @Test
    public void test1(){
        //传统匿名内部类
        Runnable runnable = new Runnable(){
            @Override
            public void run() {
                System.out.println("Hello World");
            }
        };
        runnable.run();
        //lambda表达式
        Runnable lambda = () -> System.out.println("Hello Lambda");
        lambda.run();
    }

    @Test
    public void test2(){
        Consumer<String> consumer = x -> System.out.println(x);
        consumer.accept("测试成功!");
    }

    @Test
    public void test3(){
        Comparator<Integer> comparator = (x, y) -> {
            System.out.println("测试成功!");
            return Integer.compare(x, y);
        };
    }

    @Test
    public void test4(){
        Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y);
    }
}

2.Lambda上下文推断的应用

//不能拆分开写,否则无法根据上下文推断出aaa,bbb,ccc的类型
String[] strs = {"aaa","bbb","ccc"}

//后面的泛型可以不写,可以通过前面的泛型推断出来
List<String> list = new ArrayList<>()//java8中show(new HashMap<>())可以不写泛型,通过上下文类型推断出结果
show(new HashMap<>());
public void show(Map<String, Integer> map){
	...
}

3.Java8内置的四大核心函数式接口

函数式接口参数类型返回类型用途
Consumer<T> 消费型接口Tvoid对类型为T的对象应用操作。包含方法:void accept(T t)
Supplier<T> 供给型接口T返回类型为T的对象。包含方法:T get()
Function<T, R> 函数型接口TR对类型为T的对象应用操作,并返回R类型对象的结果。包含方法:R apply(T t)
Predicate<T> 断言型接口Tboolean确定类型为T的对象是否满足某约束。包含方法:boolean test(T t)
import org.junit.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
 * Java8内置的四大核心函数式接口
 *
 * Consumer<T></消费型接口>
 *      void accept(T t);
 *
 * Supplier<T></供给型接口>
 *      T get();
 *
 * Function<T, R></函数型接口>
 *      R apply(T t);
 *
 * Predicate<T></断言型接口>
 *      boolean test(T t);
 */
public class TestLambda4 {

    //Consumer<T></消费型接口>
    @Test
    public void test1(){
        shopping(1000,(m) -> System.out.println("书包,消费" + m/4));
    }
    public void shopping(double money, Consumer<Double> consumer){
        consumer.accept(money);
    }

    //Supplier<T></供给型接口>
    @Test
    public void test2(){
        List<Integer> integers = getIntegers(10, () -> (int) (Math.random() * 100));
        for (Integer integer : integers) {
            System.out.println(integer);
        }
    }

    public List<Integer> getIntegers(int num, Supplier<Integer> supplier){
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < num; i++) {
            Integer n = supplier.get();
            list.add(n);
        }
        return list;
    }

    //Function<T, R></函数型接口>
    @Test
    public void test3(){
        String s = strHandler("三生三世:十里桃花", (str) -> str.replace(":", "+"));
        System.out.println(s);
    }

    public String strHandler(String str, Function<String,String> function){
        return function.apply(str);
    }

    //Predicate<T></断言型接口>
    @Test
    public void test4(){
        List<String> statement = Arrays.asList("三生三世,十里桃花","人生若只如初见","三生三世,情非得已");
        List<String> stringList = filterStr(statement, (str) -> str.contains("三生三世"));
        for (String s : stringList) {
            System.out.println(s);
        }
    }

    public List<String> filterStr(List<String> list, Predicate<String> predicate){
        List<String> stringList = new ArrayList<>();
        for (String s : list) {
            if(predicate.test(s)){
                stringList.add(s);
            }
        }
        return  stringList;
    }

}

其他内置接口

函数式接口参数类型返回类型用途
BiConsumer<T,U>T,Uvoid对类型为T,U的对象应用操作。包含方法:void accept(T t,U u)
BiFunction<T,U,R>T,UR对类型为T,U参数的对象应用操作,返回R类型的结果。包含方法:R apply(T t,U u)
BiPredicate<T,U>T,Uboolean确定类型为T,U的对象是否满足某约束。包含方法:boolean test(T t, U u)

2.方法引用,构造器引用及数组引用

import domains.Employee;
import org.junit.Test;

import java.util.Comparator;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * 方法引用:若Lambda体中的方法已经被其他类实现了,可以使用方法引用
 *
 * 三种法语格式:
 *      1.对象::实例方法名
 *      2.类::静态方法名
 *      3.类::实例方法名(需满足条件2)
 *
 *  注意:
 *      1.Lambda体中调用的方法需要与函数式接口的参数类型和返回值保持一致
 *      2.Lambda参数列表的第一个参数是实例方法的调用者,第二个参数是实例方法的参数时,可以使用语法格式三
 *
 *  构造器引用:若Lambda体中存在新建对象,可以使用构造器引用
 *
 *  法语格式:
 *      ClassName::new
 *
 *  注意:
 *      1.函数式接口的方法的参数列表匹配对应的实体类的构造器
 *
 *  数组引用:
 *
 *  语法格式:
 *      Type[]::new
 */
public class TestLambda5 {

    //1.对象::实例方法名
    @Test
    public void test1(){
        Consumer<String> consumer = (x) -> System.out.println(x);
        //等价于
        //注意:调用的方法需要与函数式接口接口的参数类型和返回值需要一致
        Consumer<String> consumer1 = System.out::println;

        consumer1.accept("200");
        shopping(1000, System.out::println);
    }
    public void shopping(double money, Consumer<Double> consumer){
        consumer.accept(money);
    }

    //2.类::静态方法名
    @Test
    public void test2(){
        Comparator<Integer> comparator = (x, y) -> Integer.compare(x,y);
        //等价于
        Comparator<Integer> comparator1 = Integer::compareTo;
    }

    //3.类::实例方法名
    @Test
    public void test3(){
        BiPredicate<String, String> biPredicate = (x, y) -> x.equals(x);
        BiPredicate<String, String> biPredicate1 = String::equals;
    }

    //构造器引用
    @Test
    public void test4(){
        Supplier<Employee> supplier = () -> new Employee();
        supplier.get();
        //等价于
        //函数式接口的方法的参数列表匹配对应的实体类的构造器
        Supplier<Employee> supplier1 = Employee::new;
        Employee employee = supplier1.get();
        System.out.println(employee);

//        Function<Integer, Employee> function = (x) -> new Employee(x);
        Function<Integer, Employee> function = Employee::new;
        Employee apply = function.apply(1);
        System.out.println(apply);
    }

    //数组引用
    @Test
    public void test5(){
        Function<Integer, String[]> function = (x) -> new String[x];
        String[] apply = function.apply(10);
        System.out.println(apply.length);
        //等价于
        Function<Integer, String[]> function1 = String[]::new;
        String[] apply1 = function1.apply(20);
        System.out.println(apply1.length);
    }
}

3.Stream

1.Stream的概念

  • Stream是Java8处理集合的关键抽象概念,它可以指定对集合进行的操作,可以执行非常复杂的查找,过滤,映射数据等操作
  • 流(Stream)是数据通道,用于操作数据源(集合,数组等)所生成的元素序列
  • 完整流包含以下结构:
    • 1.数据源(集合,数组等)
    • 2.通道(做一系列流水线式的中间操作)
    • 3.新流(终止操作产生)
  • 注意:
    • 1.Stream不会存储元素
    • 2.Stream不会改变数据源,而是返回一个持有结果的新Stream
    • 3.Stream操作是延迟执行的,即等到需要结果(终止操作)的时候才会被执行

2.Stream的操作三个步骤

  • 1.创建Stream
    • 通过一个数据源(如:集合,数组),获取一个流
  • 2.中间操作
    • Stream的一个中间操作链,对数据源的数据进行处理
  • 3.终止操作
    • Stream的一个终止操作,执行中间操作链,并产生结果

3.创建Stream的四种方式

import domains.Employee;
import org.junit.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.stream.Stream;

/**
 * Stream的三个操作步骤:
 *      1.创建Stream
 *      2.中间操作
 *      3.终止操作
 */
public class TestLambda6 {

    //创建Stream
    @Test
    public void test1(){
        //1.通过Collection系列集合可以获取的stream()串行流或parallelStream()
        List<String> list = new ArrayList<>();
        Stream<String> stream = list.stream();

        //2.通过Arrays中的静态方法Stream可以获取数组流
        Employee[] employees = new Employee[10];
        Stream<Employee> stream1 = Arrays.stream(employees);

        //3.通过Stream类中的静态方法of()
        Stream<String> stream3 = Stream.of("aa", "bb", "cc");

        //4.创建无限流
            //第一种无限流:迭代
            Stream<Integer> stream4 = Stream.iterate(0, (x) -> x + 2);
            //中间操作
            Stream<Integer> limit = stream4.limit(10);
            //终止操作
            limit.forEach(System.out::println);

            //第二种无限流:生成
            Stream.generate(() -> new Random().nextInt(100)).limit(10).forEach(System.out::println);
    }
}

4.中间操作

  • 延迟加载:中间操作默认不会执行任何操作,只有执行了终止操作,所有的中间操作才会执行,这个过程叫做惰性求值或者延迟加载(多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理,而在终止操作时一次性全部处理)
  • 内部迭代:迭代操作由Stream API完成
  • 外部迭代:手动完成

1.筛选与切片

List<Employee> employees = Arrays.asList(
            new Employee("张三",8,100),
            new Employee("李四",12,500),
            new Employee("王五",18,3000),
            new Employee("赵六",24,8000),
            new Employee("赵九",24,6000),
            new Employee("田七",35,15000),
            new Employee("吴八",40,25000),
            new Employee("吴八",40,25000)
    );

    @Test
    public void test2(){
        /**
         * 筛选与切片
         * Stream<T> filter(Predicate<? super T> predicate):接收一个断言型的Lambda表达式,从Stream流中排除满足条件的某些元素
         * Stream<T> limit(long maxSize):截断流,使其元素不超过指定数量;断路:如果发现了满足条件的数据就不再迭代
         * Stream<T> skip(long n):调过元素,返回一个跳过了前n个元素的流,若流中的元素不足n个,则返回一个空流(与limit(n)互补)
         * Stream<T> distinct():筛选,通过流所生成元素的hashCode()和equals()去除重复元素;注意需要重新写hashCode和equals()方法
         */
        //注意:没有终止操作,中间操作不会执行任何操作
        //外层迭代:手动完成
//        Iterator<Employee> iterator = employees.iterator();
//        while (iterator.hasNext()) {
//            System.out.println(iterator.next());
//        }
        //内层迭代:迭代操作由Stream API
        employees.stream().filter((e) -> e.getAge() > 24).forEach(System.out::println);
        employees.stream().filter((e) -> e.getAge() > 24).limit(1).forEach(System.out::println);
        employees.stream().filter((e) -> e.getAge() > 24).skip(1).forEach(System.out::println);
        employees.stream().filter((e) -> e.getAge() > 24).distinct().forEach(System.out::println);
    }

2.映射

@Test
    public void test3(){
        /**
         * 映射
         * map:接收一个函数型的Lambda表达式,该参数会被应用到每个元素上,并将其映射成一个新的元素,用于将元素转换成其他形式或者提取信息。(把流加入其中)
         * flatMap:接收一个函数型的Lambda表达式,将流中的每个元素都转换成另一个流,然后把所有流连接成一个流(把一个个流中的元素加入其中)
         */
        List<String> list = Arrays.asList("aaa","bbb","ccc");
        list.stream()
                .map((str) -> str.toUpperCase())
                .forEach(System.out::println);
     	employees.stream().map(Employee::getName).forEach(System.out::println);

        //map与flatMap的区别
        Stream<Stream<Character>> streamStream = list.stream().map(TestLambda6::filterCharacter);
        streamStream.forEach((stream) -> {
            stream.forEach(System.out::println);
        });

        Stream<Character> characterStream = list.stream().flatMap(TestLambda6::filterCharacter);
        characterStream.forEach(System.out::println);

        //类似list的add和addAll
        List<String> list1 = Arrays.asList("aaa", "bbb", "ccc");
        List list2 = new ArrayList<>();
        list2.add("111");
        list2.add("222");
        list2.add("333");
        list2.add(list1);
        /**
         * 结果
         * [111, 222, 333, [aaa, bbb, ccc]]
         */
        list2.addAll(list1);
        /**
         * 结果
         * [111, 222, 333, [aaa, bbb, ccc], aaa, bbb, ccc]
         */
        System.out.println(list2);
    }

    public static Stream<Character> filterCharacter(String str){
        List<Character> list = new ArrayList<>();
        for (Character c : str.toCharArray()) {
            list.add(c);
        }
        return list.stream();
    }

3.排序

@Test
    public void test4(){
        /**
         * 排序
         * sorted():自然排序(Comparable)
         * sorted(Comparator c):定制排序(Comparator)
         */
        List<String> list = Arrays.asList("ccc", "aaa", "bbb");
        list.stream()
                .sorted()
                .forEach(System.out::println);
        employees.stream().sorted((e1 ,e2) -> {
            if(e1.getAge() == e2.getAge()){
                return e1.getName().compareTo(e2.getName());
            }else {
                return -e1.getAge() - e2.getAge();
            }
        }).forEach(System.out::println);
    }

5.终止操作

  • 终止操作会从流的流水线生成结果

1.查找与匹配

@Test
    public void test5(){
        /**
         * 终止操作
         * 1.查找与匹配
         *      allMatch(Predicate<? super T> predicate):接收一个断言型的Lambda表达式,检查是否流中的所有元素都满足条件
         *      anyMatch(Predicate<? super T> predicate):接收一个断言型的Lambda表达式,检查流中是否至少有一个元素满足条件
         *      noneMatch(Predicate<? super T> predicate):接收一个断言型的Lambda表达式,检查是否流中的所有元素都不满足条件
         *      findFirst:返回第一个元素
         *      findAny:返回任意元素
         *      count:返回流中元素的总个数
         *      max:返回流中最大值
         *      min:返回流中最小值
         */

        boolean b = employees.stream().allMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
        System.out.println(b);

        boolean b1 = employees.stream().anyMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
        System.out.println(b1);

        boolean b2 = employees.stream().noneMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
        System.out.println(b2);

        Optional<Employee> first = employees.stream().findFirst();
        System.out.println(first.get());

        Optional<Employee> any = employees.parallelStream().findAny();
        System.out.println(any.get());

        long count = employees.stream().count();
        System.out.println(count);

        Optional<Employee> max = employees.stream().max(Comparator.comparingDouble(Employee::getSalary));
        System.out.println(max.get());

        Optional<Double> min = employees.stream().map(Employee::getSalary).min(Double::compareTo);
        System.out.println(min.get());
    }

2.归约

	@Test
    public void test6(){
        /**
         * 终止操作
         * 2.归约
         *     T reduce(T identity, BinaryOperator<T> accumulator):将流中元素反复结合起来(类似累加),得到一个值;identity:初始值,BinaryOperator<T>:二元操作
         */
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        Integer sum = list.stream().reduce(0, (x, y) -> x + y);
        System.out.println(sum);
    }

3.收集

 @Test
    public void test7(){
        /**
         * 终止操作
         * 2.收集
         *     collect:将流转换为其他形式,接收一个Collector接口的实现,用于给Stream中元素做汇总的方法
         *     Collector:接口中方法的实现决定了如何对流执行收集操作
         *     Collectors:实现类提供了很多静态方法,可以方便创建收集器实例
         *          1.mapping
         *          2.join()
         *          3.toList()
         *          4.toSet()
         *          5.toMap()
         *          6.toConcurrentMap()
         *          7.toCollection()
         *          8.counting()
         *          9.averagingDouble
         *          10.summarizingDouble
         *          11.maxBy()
         *          12.minBy()
         */

        //映射
        List<Person> list = new ArrayList<>();
        list.add(new Person("Ram", 30));
        list.add(new Person("Shyam", 20));
        list.add(new Person("Shiv", 20));
        list.add(new Person("Mahesh", 30));

        String nameByAge = list.stream().collect(Collectors.mapping(Person::getName, Collectors.joining(",", "[", "]")));
        System.out.println(nameByAge);
        nameByAge = list.stream().map(person -> person.getName()).collect(Collectors.joining(",", "[", "]"));
        System.out.println(nameByAge);

        Map<Integer, String> nameByAgeMap = list.stream().collect(
                Collectors.groupingBy(Person::getAge, Collectors.mapping(Person::getName, Collectors.joining(",", "[", "]"))));
        nameByAgeMap.forEach((k, v) -> System.out.println("Age:" + k + "  Persons: " + v));

        //连接符,前缀,后缀
        List<String> list1 = Arrays.asList("A","B","C","D");
        String result=  list1.stream().collect(Collectors.joining("-","[","]"));
        System.out.println(result);

        List<String> collect = employees.stream()
                .map(Employee::getName)
                .collect(Collectors.toList());
        collect.forEach(System.out::println);

        Set<String> collect1 = employees.stream()
                .map(Employee::getName)
                .collect(Collectors.toSet());
        collect1.forEach(System.out::println);

        //Duplicate key:使用toMap()的重载方法,如果已经存在则不再修改,直接使用上一个数据
        //(e1, e2) -> e1 这里使用的箭头函数,也就是说当出现了重复key的数据时,会回调这个方法,可以在这个方法里处理重复Key数据问题
        Map<String, Double> collect8 = employees.stream().collect(Collectors.toMap(Employee::getName, Employee::getSalary, (e1, e2) -> e1));
        for (Map.Entry<String, Double> stringDoubleEntry : collect8.entrySet()) {
            System.out.println(stringDoubleEntry.getKey() + ":" + stringDoubleEntry.getValue());
        }

        ConcurrentMap<String, Double> collect9 = employees.stream().collect(Collectors.toConcurrentMap(Employee::getName, Employee::getSalary, (e1, e2) -> e1));
        for (Map.Entry<String, Double> stringDoubleEntry : collect9.entrySet()) {
            System.out.println(stringDoubleEntry.getKey() + ":" + stringDoubleEntry.getValue());
        }

        HashSet<String> collect2 = employees.stream()
                .map(Employee::getName)
                .collect(Collectors.toCollection(HashSet::new));
        collect2.forEach(System.out::println);

        Long collect3 = employees.stream()
                .collect(Collectors.counting());
        System.out.println(collect3);

        Double collect4 = employees.stream()
                .collect(Collectors.averagingDouble(Employee::getSalary));
        System.out.println(collect4);
		
		//包含总数,总和,最小值,平均值,最大值
        DoubleSummaryStatistics collect5 = employees.stream()
                .collect(Collectors.summarizingDouble(Employee::getSalary));
        System.out.println(collect5);

        Optional<Employee> collect6 = employees.stream()
                .collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
        System.out.println(collect6.get());

        Optional<Employee> collect7 = employees.stream()
                .collect(Collectors.minBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
        System.out.println(collect7.get());
    }

4.分组

	@Test
    public void test8(){
        /**
         * 1.分组
         * 2.多级分组
         */
        Map<Employee.Status, List<Employee>> collect = employees.stream().collect(Collectors.groupingBy(Employee::getStatus));
        for (Map.Entry<Employee.Status, List<Employee>> statusListEntry : collect.entrySet()) {
            System.out.println(statusListEntry.getKey() + ":" + statusListEntry.getValue());
        }

        Map<Employee.Status, Map<String, List<Employee>>> collect1 = employees.stream().collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy((e) -> {
            if (e.getAge() <= 35) {
                return "青年";
            } else if (e.getAge() <= 50) {
                return "中年";
            } else {
                return "老年";
            }
        })));
        for (Map.Entry<Employee.Status, Map<String, List<Employee>>> statusMapEntry : collect1.entrySet()) {
            System.out.println(statusMapEntry.getKey() + ":" + statusMapEntry.getValue().entrySet());
        }
    }

5.分区

	@Test
    public void test9(){
        /**
         * 分区 true和false分区,满足条件的一区,不满足条件的一区
         */
        Map<Boolean, List<Employee>> collect = employees.stream().collect(Collectors.partitioningBy((e) -> e.getSalary() > 8000));
        for (Map.Entry<Boolean, List<Employee>> booleanListEntry : collect.entrySet()) {
            System.out.println(booleanListEntry.getKey() + ":" + booleanListEntry.getValue());
        }
    }

4.并行流与顺序流

  • 并行流是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流
  • Java8中将并行流进行了优化,可以更方便的对数据进行并行操作,Stream API可以声明性地通过parallel()与sequential()在并行流与顺序流之间进行切换

Fork/Join框架

  • 注意:需要继承RecursiveTask
  • 概念:在必要的情况下将一个大任务,进行拆分(fork)成若干个小任务,拆到不可再拆时,再将一个个的小任务运算的结果进行join汇总
  • Fork/Join框架与传统线程池的区别
    • 1.Fork/Join框架采用工作窃取模式(work-stealing)
    • 即:当执行新的任务时它可以将其拆分,分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中
    • 2.相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上,在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态;而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行,那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行,这种方式减少了线程的等待时间,提高了性能
    package domains;
    
    import java.util.concurrent.RecursiveTask;
    
    public class ForkJoinCalculate extends RecursiveTask<Long> {
    
        private static final long serialVersionUID = -8301484471666236858L;
    
        private long start; //开始值
        private long end;   //结束值
    
        private static final long THRESHOLD = 10000;    //拆分终止值:拆分到什么时候才结束
    
        public ForkJoinCalculate(long start, long end) {
            this.start = start;
            this.end = end;
        }
    
        @Override
        protected Long compute() {
            long length = end - start;
            //临界值:如果小于等于该值就不再拆分
            if(length <= THRESHOLD){
                long sum = 0;
                for (long i = start; i <= end; i++) {
                    sum += i;
                }
                return sum;
            }else {
                //拆分
                //一半一半的拆
                long middle = (start + end) / 2;
    
                ForkJoinCalculate left = new ForkJoinCalculate(start, middle);
                left.fork(); //拆分子任务,同时压入线程队列
    
                ForkJoinCalculate right = new ForkJoinCalculate(middle+1, end);
                right.fork();
    
                //合并
                return left.join() + right.join();
            }
    
        }
    }
    
    import domains.ForkJoinCalculate;
    import org.junit.Test;
    
    import java.time.Duration;
    import java.time.Instant;
    import java.util.concurrent.ForkJoinPool;
    import java.util.concurrent.ForkJoinTask;
    import java.util.stream.LongStream;
    
    public class TestForkJoin {
    
        @Test
        public void test1(){
            Instant start = Instant.now();
    
            ForkJoinPool pool = new ForkJoinPool();
            ForkJoinTask<Long> task = new ForkJoinCalculate(0,1000000000L);
            Long sum = pool.invoke(task);
            System.out.println("累加和为:" + sum);
    
            Instant end = Instant.now();
    
            System.out.println("消耗时间为:" + Duration.between(start, end).toMillis());
            /**
             *  CPU利用率:100%
             *  累加和为:500000000500000000
             *  消耗时间为:111
             */
        }
    
        @Test
        public void test2(){
            Instant start = Instant.now();
    
            long sum = 0L;
            for (int i = 0; i < 1000000000L; i++) {
                sum += i;
            }
            System.out.println("累加和为:" + sum);
    
            Instant end = Instant.now();
    
            System.out.println("消耗时间为:" + Duration.between(start, end).toMillis());
            /**
             *  CPU利用率:20%左右
             *  累加和为:499999999500000000
             * 消耗时间为:364
             */
        }
    
        @Test
        public void test3(){
            /**
             * java8并行流
             */
    
            Instant start = Instant.now();
    
            long sum = LongStream.rangeClosed(0, 1000000000L)
                    .parallel() //并行流
                    .reduce(0, Long::sum);
    
            System.out.println("累加和为:" + sum);
    
            Instant end = Instant.now();
    
            System.out.println("消耗时间为:" + Duration.between(start, end).toMillis());
            /**
             *  CPU利用率:100%左右
             *  累加和为:500000000500000000
             *  消耗时间为:136
             */
        }
    }
    

5.Optional类

  • java.util.Optional类是一个容器类,代表一个值存在或不存在
  • 原来用null表示一个值不存在,现在Optional可以更好的表达这个概念,并且可以避免空指针异常
    package domains;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Godness {
    
        private String name;
    }
    
    package domains;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Man {
    
        private Godness godness;
    }
    
    package domains;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.util.Optional;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class NewMan {
    
        //可能为空的属性用Optional包装
        //需要初始值,不能为null
        private Optional<Godness> godness = Optional.empty();
    }
    
    import domains.Employee;
    import domains.Godness;
    import domains.Man;
    import domains.NewMan;
    import org.junit.Test;
    
    import java.util.Arrays;
    import java.util.List;
    import java.util.Optional;
    
    public class TestOptional {
        @Test
        public void test1(){
            /**
             *  Optional.of(T t):创建一个Optional实例,不能传递null会报空指针异常,可以快速定位
             *  Optional.empty():创建一个空的Optional实例
             *  Optional.ofNullable(T t):若t不为null,创建Optional实例,否则创建空实例
             *  isPresent():判断是否包含值
             *  orElse(T t):如果调用对象包含值,返回该值,否则返回t
             *  orElseGet(Supplier s):如果调用对象包含值,返回该值,否则返回s获取的值;和orElse区别是可以通过供给型接口的实现类实现更多功能
             *  map(Function f):如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty()
             *  flatMap(Function mapper):与map类似,要求返回值必须是Optional
             *  filter:接收一个 Predicate 来对 Optional 中包含的值进行过滤,如果包含的值满足条件,那么还是返回这个 Optional;否则返回 Optional.empty
             */
            Optional<Employee> optional = Optional.of(new Employee());
            Employee employee = optional.get();
            System.out.println(employee);
    
    //        Optional<Object> empty = Optional.empty();
    //        System.out.println(empty.get());
    
    //        Optional<Object> o = Optional.ofNullable(null);
    //        System.out.println(o.get());
    
            Optional<Object> o = Optional.ofNullable(null);
            if(o.isPresent()){
                System.out.println(o.get());
            }
    
            Optional<Employee> objectOptional = Optional.ofNullable(null);
            System.out.println(objectOptional.orElse(new Employee(25)));
    
            System.out.println(objectOptional.orElseGet(() -> new Employee("李四",3,35.5, Employee.Status.BUSY)));
    
            Optional<Employee> employee1 = Optional.ofNullable(new Employee("李四",3,35.5, Employee.Status.BUSY));
            Optional<String> optional1 = employee1.map(Employee::getName);
            System.out.println(optional1.get());
    
            Optional<String> s = employee1.flatMap((e) -> Optional.of(e.getName()));
            System.out.println(s.get());
    
            List<Employee> employees = Arrays.asList(
                    new Employee("张三",8,100, Employee.Status.BUSY),
                    new Employee("李四",12,500, Employee.Status.BUSY),
                    new Employee("王五",18,3000, Employee.Status.FREE),
                    new Employee("赵六",24,8000, Employee.Status.FREE),
                    new Employee("赵九",24,6000, Employee.Status.FREE),
                 new Employee("田七",35,15000, Employee.Status.VOCATION),
                    new Employee("吴八",40,25000, Employee.Status.FREE)
            );
            Optional<Employee> optional2 = Optional.ofNullable(employees.get(5)).filter(e -> e.getAge() > 24);
            System.out.println(optional2.get());
        }
    
        @Test
        public void test2(){
            Man man = new Man();
    //        String godnessName = getGodnessName(man);
         String godnessName = getGodnessName2(Optional.ofNullable(null));
            System.out.println(godnessName);
        }
    
        public String getGodnessName(Man man){
            //空指针异常 return man.getGodness().getName();
    
            //传统if嵌套过深
            if(man != null){
                Godness godness = man.getGodness();
                if(godness != null){
                    return godness.getName();
                }
            }
            return "测试";
        }
    
        public String getGodnessName2(Optional<NewMan> man){
            //Optional
            return man.orElse(new NewMan())
                       .getGodness()
                       .orElse(new Godness("测试"))
                       .getName();
        }
    }
    

6.接口中的默认方法与静态方法

  • Java8中允许接口中包含具有具体实现的方法,该方法称为默认方法,默认方法使用default关键字修饰

接口默认方法的类优先原则

  • 若一个接口定义了一个默认方法,而另外一个父类或接口又定义了一个同名的方法时
    • 1.选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略
    • 2.接口冲突。如果一个父接口提供一个默认方法,而另一个父接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么实现类必须选择一个接口或者覆盖该方法来解决冲突
    package dao;
    
    public interface MyFun1 {
    
        default String getName(){
            return "myFun1";
        }
    
    	static void show(){
            System.out.println("接口中的静态方法");
        }
    }
    
    package dao;
    
    public interface MyFun2 {
    
        default String getName(){
            return "myFun2";
        }
    }
    
    package domains;
    
    public class MyClassFun {
    
        public String getName(){
            return "MyClassFun";
        }
    }
    
    package domains;
    
    import dao.MyFun1;
    import dao.MyFun2;
    
    //public class SubClass extends MyClassFun implements MyFun1 {
    //}
    
    public class SubClass implements MyFun1, >	MyFun2 {
        @Override
        public String getName() {
            return MyFun1.super.getName();
        }
    }
    
    import domains.SubClass;
    import org.junit.Test;
    
    public class TestDefault {
    
        @Test
        public void test1(){
            SubClass subClass = new SubClass();
            String name = subClass.getName();
            System.out.println(name);
    
    		MyFun1.show()
        }
    }
    

7.传统时间格式化的线程安全问题

  • java.util.Date() JDK1.0版本
  • java.util.Calender() JDK1.1版本
  • java.util.TimeZone()
  • java.text.SimpleDateFormat
  • 声明不规范,不安全

8.新时间与日期API

  • java.time包下
  • java.time.
  • java.time.format
  • java.time.temporal
  • java.time.zone
  • 不可变,会产生新的实例
  • 线程安全

9.重复注解与类型注解

常见面试题

1.JDK和JRE的区别

  • 1.JDK(Java SE开发工具集):包括JRE和命令行开发工具(编译,运行,调试Java程序所需要的基本工具)
  • 2.JRE(运行环境):提供Java虚拟机和运行Java应用程序所必需的类库
  • 3.JRE与JDK的区别
    • 1.如果只需要在某种操作系统下运行Java应用程序,则安装支持该操作系统的JRE即可
    • 2.如果不仅要运行Java应用程序,还要开发Java应用程序,就需要安装支持该操作系统的JDK

2.a+=b 和 a=a+b 的区别

  • 1.使用+=,-=,*=,/=,%=运算符进行赋值时,强制类型转换会自动完成,程序不需要做任何显式地声明
  • 2.当ab结果类型不一致时,a+=b会自动完成类型强制转换,而a=a+b会报错
  • 3.当ab结果类型一致时,两者没区别

3.==和equals的区别

  • 1.==
    • 1.基本数据类型的变量存放在虚拟机栈的局部变量表中的是值,==在比较基本数据类型时,比较的是变量值是否相同
    • 2.引用数据类型的变量存放在虚拟机栈的局部变量表中的是对象引用/堆空间的内存地址,比较的是堆地址是否相同
  • 2.equals
    • 1.equals用来比较的是两个对象的内容是否相等,由于所有的类都是继承自java.lang.Object类的,所以适用于所有对象,如果没有对该方法进行覆盖的话,调用的仍然是Object类中的方法,而Object中的equals方法返回的是==的判断

    • 2.Java中所有的类都是继承与Object基类,Object类中定义了一个equals方法,这个方法的初始行为是比较对象的内存地址,但在一些类库中已经重写了这个方法,所以不再是比较类在堆中的地址,(一般都是用来比较对象的成员变量值是否相同StringIntegerDate 等类)



4.equals和hashcode关系

  • 1.equals方法和hashCode方法是Object类中的两个基础方法,它们共同协作来判断两个对象是否相等
  • 2.如果两个对象相等,即equals()结果为true,那么二者的hashCode()也必须相同;但如果两个对象的hashCode相同,则二者不一定相同
  • 3.并不要求根据equals()不相等的两个对象,调用二者各自的hashCode()方法必须产生不同的结果,但是对于不同的对象产生不同的hashcode,可能会提高hashtable的性能
  • 4.当对比两个对象是否相等时,可以先使用hashCode进行比较,如果比较的结果是true,可以使用equals再次确认两个对象是否相等,如果比较的结果是true,那么这两个对象就是相等的,否则其他情况就认为两个对象不相等;这样大大的提升了对象比较的效率,这也是为什么Java设计使用hashCodeequals协同的方式,来确认两个对象是否相等的原因
  • 5.不能直接使用hashCode就确定两个对象是否相等,因为不同对象的 hashCode可能相同,但hashCode不同的对象一定不相等,所以使用 hashCode可以起到快速初次判断对象是否相等的作用

5.重写equals时为什么一定要重写hashCode

  • 1.重写equals方法
    • 1.Object类中的equals方法用于检测一个对象是否等于另外一个对象
    • 2.Object 类中,这个方法将判断两个对象是否具有相同的引用,如果两个对象具有相同的引用,则一定是相等的
    • 3.通过上述源码和equals的定义可以看出,大多数情况下equals的判断是没有什么意义的
    • 4.例:使用 Object 中的equals 比较两个自定义的对象是否相等完全没有意义(因为无论对象是否相等,结果都是 false),因此通常要判断两个对象是否相等,一定要重写equals方法,这就是要重写 equals 方法的原因
  • 2.重写hashCode方法
    • 1.hashCode是一个散列码,它是由对象推导出的一个整型值,并且这个值为任意整数,包括正数或负数
    • 2.散列码是没有规律的,如果 xy 是两个不同的对象,x.hashCode()y.hashCode()基本上不会相同;但如果 ab 相等,则a.hashCode()一定等于b.hashCode()
    • 3.相同的值hashCode一定相同
    • 4.不同的值hashCode也有可能相同
  • 3.一起重写
    • 1.Set集合是用来保存不同对象的,如果Set 集合中存储的是只重写了 equals方法的自定义对象时会出现异常

    • 2.如上图所示即使两个对象是相等的,Set集合却没有将二者进行去重与合并,这就是重写了equals方法,但没有重写hashCode方法的问题所在
  • 4.问题原因
    • 1.如果只重写了equals方法,那么默认情况下,Set进行去重操作时,会先判断两个对象的hashCode是否相同,此时因为没有重写hashCode方法,所以会直接执行Object中的hashCode方法,而 Object 中的hashCode方法对比的是两个不同引用地址的对象,所以结果是false,那么equals方法就不会执行,直接返回的结果就是 false:两个对象不是相等的,于是就在 Set 集合中插入了两个相同的对象。
    • 2.但如果在重写equals方法时,也重写了hashCode方法,那么执行判断时会先执行重写的hashCode方法,此时对比的是两个对象的所有属性的 hashCode是否相同,于是调用hashCode返回的结果就是true,再去调用 equals方法,发现两个对象确实是相等的,于是就返回true,因此Set集合不会存储两个一样的数据

6.String的底层实现

  • 1.String类的一个最大特性是不可修改性,其不可修改的原因是在String内部定义了一个常量char数组,因此每次对字符串的操作实际上都会另外分配一个新的常量数组空间
  • 2.Java打印String对象的地址:当使用System.out.println()方法打印String类型对象时,会输出String对象代表的字符串,并不会输出对象的地址
  • 3.一般被打印对象的形式为:全限定类名+@+十六进制数组成
  • 4.性能优化:对于直接相加字符串JVM在编译期便确定其值,如I+love+java的字符串相加,在编译期间便被优化成了Ilovejava;对于间接相加(即包含字符串引用或方法调用) 效率要比直接相加低,因为在编译期不会对引用变量或方法进行优化


  • 5.String str = new String("abc")创建了多少个对象
    • 1.从字节码看new只调用了一次,也就是说只创建了一个对象
    • 2.这段代码在运行期间确实只创建了一个对象,即在上创建了abc对象
    • 3.为什么一般说创建了2个对象:该段代码执行过程类的加载过程是有区别的,在类加载的过程中,确实在运行时常量池中创建了一个abc对象,而在代码执行过程中确实只创建了一个String对象,因此这个问题合理的解释是2个

7.String,StringBuilder,StringBuffer的区别

  • 1.String是常量字符数组,而StringBuilderStringBuffer是变量字符数组
  • 2.String没有默认初始化容量,而StringBuilderStringBuffer默认初始化容量为16
  • 3.StringBuilder线程不安全,StringBuffer线程安全,因为StringBuffer的大部分方法都使用synchronized关键字加锁


8.final、finalize和finally的区别

  • 1.final
    • 1.final是一个修饰符,其可以修饰方法属性
    • 2.final修饰的,不能作为父类被子类继承,因此一个类不能既被abstract声明,又被final声明
    • 3.final修饰的变量,称为常量,不能被修改,必须在声明时赋值
    • 4.final修饰的方法,不能被重写
  • 2.finally
    • 1.finally是异常处理时提供来执行最终结果的finally代码块
    • 2.不管有没有异常被抛出、捕获,finally代码块都会被执行
  • 3.finalize
    • 1.finalize是方法名,属于Object
    • 2.finalize()方法用于垃圾回收,一般情况下不需要实现
    • 3.当对象被回收的时候需要释放一些资源;如socket链接,在对象初始化时创建,整个生命周期内有效,那么需要实现finalize方法关闭这个链接
    • 4.但是当调用finalize方法后,并不意味着GC会立即回收该对象,所以有可能真正调用的时候,对象又不需要回收了,然后到了真正要回收的时候,因为之前调用过一次,这次又不会调用了,产生问题,所以不推荐使用finalize方法

9.volatile

  • 1.并发编程中通常会遇到以下三个问题
    • 1.原子性问题
      • 1.原子性:指一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行
    • 2.可见性问题
      • 1.可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值
    • 3.有序性问题
      • 1.有序性:指程序执行的顺序按照代码的先后顺序执行
      • 2.指令重排序:一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的
      • 3.指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性
  • 2.要想并发程序正确地执行,必须要保证原子性、可见性以及有序性;只要有一个没有被保证,就有可能会导致程序运行不正确
  • 3.Java解决以上三个问题
    • 1.原子性
      • 1.Java内存模型只保证了基本读取和赋值原子性操作,如果要实现更大范围操作的原子性,可以通过synchronizedLock来实现;
      • 2.因为synchronizedLock能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性
    • 2.可见性
      • 1.Java提供了volatile关键字来保证可见性,当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值
      • 2.而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性
      • 3.通过synchronizedLock也能够保证可见性,synchronizedLock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性
    • 3.有序性
      • 1.Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性
      • 2.Java里面,可以通过volatile关键字来保证一定的有序性
      • 3.也可以通过synchronizedLock来保证有序性,因为synchronizedLock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性
  • 4.volatile关键字的两层语义
    • 1.一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
      • 1.保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,新值对其他线程来说是立即可见的
      • 2.禁止进行指令重排序,但是只能对当前修饰的操作保证,其他操作无法保证,所以volatile能在一定程度上保证有序性
  • 5.volatile关键字无法保证对变量的任何操作都是原子性
  • 6.具体可参考:https://wwwblogs/ustc-anmin/p/11434769.html

更多推荐

JAVA SE