JAVA SE

3.面向对象

1.类和对象的定义

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

2.类

  • 1.属性:用来描述对象的特征
    局部变量成员变量(属性)
    位置形参或方法、代码块内部类的内部、方法的外部
    默认值没有
    作用范围从定义开始到定义的{}结束当前整个类
    命名冲突局部变量之间不能重名属性可以和局部重名,局部变量优先更高
  • 2.方法:用来描述对象行为
    方法修饰符 返回值类型 方法名(参数列表){
    	// 操作语句
    }
    
  • 3.规则:
    • 1.一个源文件中只能有一个public
    • 2.一个源文件可以有多个非public
    • 3.源文件的名称应该和public类的类名保持一致
    • 4.如果一个类定义在某个包中,那么package语句应该在源文件的首行
    • 5.如果源文件包含import语句,那么应该放在package 语句和类定义之间;如果没有package语句,那么import语句应该在源文件中最前面
    • 6.import语句和package语句对源文件中定义的所有类都有效;同一源文件中不能给不同的类不同的包声明

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.方法返回值可以是基本数据类型,也可以是引用数据类型

4.类加载机制

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

3.对象

1.对象的创建方式

  • 1.new关键字
    • 1.最常用的一种方式,通过 new 关键字调用类的有参或无参构造方法来创建对象
      Object obj = new Object()
      
  • 2.反射Class类的newInstance使用类的public无参构造,因此必须有public无参构造才行,内部调用还是ConstructornewInstance() 方法
    在这里插入图片描述
  • 3.Constructor.newInstancejava.lang.relect.Constructor有一个newInstance方法可以创建对象,通过这个newInstance方法调用有参数(不再必须是无参)的和私有的构造函数(不再必须是public
    在这里插入图片描述
  • 4.Clone方法,必须先实现Cloneable接口并复写Objectclone方法,因为该方法是protected的,若不复写外部无法调用
    在这里插入图片描述
  • 5.反序列化,当序列化和反序列化一个对象,JVM会创建一个单独的对象,在反序列化时,JVM创建对象并不会调用任何构造函数,类需要实现Serializable接口
    在这里插入图片描述
    在这里插入图片描述
  • 6.可参考:https://cloud.tencent.com/developer/article/1497720
  • 7.静态方法只能访问静态成员,不可以访问非静态成员;因为静态方法加载时,优先于对象存在,所以没有办法访问对象中的成员
  • 8.静态方法中不能使用thissuper关键字;因为this代表对象,而静态在时有可能没有对象,所以this无法使用
    在这里插入图片描述

2.对象的创建过程

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

3.对象的内存结构

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

4.this关键字

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

5.super关键字

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

6.深拷贝和浅拷贝

  • 1.浅拷贝:对基本数据类型进行传递,对引用数据类型进行引用传递的拷贝
  • 2.深拷贝:对基本数据类型进行传递,对引用数据类型创建一个新的对象,并复制其内容
1.浅拷贝
  • 1.Java 中数据类型可以分为两大类:基本类型引用类型
    • 1.基本类型byte、char、short、int、long、float、double、boolean
    • 2.引用类型:类、接口、数组、枚举等
  • 3.浅拷贝:创建一个新对象,然后将当前对象的非静态字段复制到该新对象,如果字段是值类型的,那么对该字段执行复制;如果该字段是引用类型的话,则复制引用但不复制引用的对象,因此原始对象及其副本引用同一个对象,当修改其中一个对象的属性,另一个也会跟着变化
1.实现浅拷贝
  • 1.实现浅拷贝需要满足以下条件
    • 1.实现Cloneable接口,否则会抛出 CloneNotSupportedException 异常
    • 2.重写clone()方法,并使用super.clone()来创建新的对象
  • 2.注意:Cloneable接口是个空接口,super.clone()调用的是Objectclone()方法,Object类中clone()是一个本地方法
    在这里插入图片描述
2.深拷贝
  • 深拷贝:创建一个新对象,然后将当前对象的非静态字段复制到该新对象,无论该字段是值类型的还是引用类型,都复制独立的一份,当修改其中一个对象的任何内容时,都不会影响另一个对象的内容
1.实现深拷贝
  • 1.深拷贝的原理就是要让原始对象克隆对象所具有的引用类型属性不指向同一块堆内存,实现方式如下如示
    • 1.让每个引用类型属性内部都重写clone() 方法
      • 1.引用类型不能实现深拷贝,那么可以将每个引用类型都拆分为基本类型,分别进行浅拷贝
      • 2.这种方式的缺点:有多少个引用类型,就要重写多少次,如果存在很多引用类型,那么代码量会很大,所以这种方法不合适
    package com.ys.test;
    
    public class Address implements Cloneable{
        private String provices;
        private String city;
        public void setAddress(String provices,String city){
            this.provices = provices;
            this.city = city;
        }
        @Override
        public String toString() {
            return "Address [provices=" + provices + ", city=" + city + "]";
        }
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    //Person.class 的 clone() 方法
    @Override
        protected Object clone() throws CloneNotSupportedException {
            Person p = (Person) super.clone();
            p.address = (Address) address.clone();
            return p;
        }
    
    • 2.利用序列化和反序列化
      • 1.序列化是将对象写到流中便于传输,反序列化则是把对象从流中读取出来
      • 2.写到流中的对象则是原始对象的一个拷贝,因为原始对象还存在JVM 中,所以可以利用对象的序列化产生克隆对象,然后通过反序列化获取这个对象
      • 3.因为序列化产生的是两个完全独立的对象,所有无论嵌套多少个引用类型,序列化都是能实现深拷贝的
    //深度拷贝
    public Object deepClone() throws Exception{
        // 序列化
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
    
        oos.writeObject(this);
    
        // 反序列化
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
    
        return ois.readObject();
    }
    

4.抽象类

在这里插入图片描述

  • 1.用abstract修饰的类
  • 2.抽象类不能实例化对象,设计理念是为了服务其他子类,所以规定不能实例化,可参考https://blog.csdn.net/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 返回值类型 方法名(参数列表){
    
    }
    
  • 16.Java接口可以继承多个接口
    • 1.Java不允许类多继承的主要原因:如果A同时继承BC,而BC同时有一个D方法,A无法决定继承哪一个
    • 2.接口不存在这样的问题,接口全都是抽象方法继承谁都无所谓,所以接口可以继承多个接口
    • 3.一个类如果实现了一个接口,则要实现该接口的所有方法,包括该接口继承的接口的所有方法
    • 4.因为接口的方法默认是public类型的,所以在实现时一定要用public来修饰,否则缩小了方法的使用范围

1.接口的分类

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

2.实现类

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

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

3.接口和抽象类的区别

抽象类接口
关键字abstract class /extendsinterface/implements
属性没有具体要求公开静态常量
方法可以存在非抽象的方法公开抽象方法
构造方法有构造方法没有构造方法
继承性单继承多继承
  • 1.接口:自上向下,不需要考虑子类,实现类需要去实现接口的内容
  • 2.抽象类:自下向上,是对子类共性的抽取

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.应用场景:通常用于类型强转之前判断是否兼容,否则不能强转

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

4.Date

整型和Date类之间并不存在直接的对应关系,只是你可以使用int型为分别表示年、月、日、时、分、秒,这样就在两者之间建立了一个对应关系,在作这种转换时,你可以使用Date类构造函数的三种形式:
Date(int year, int month, int date):以int型表示年、月、日
Date(int year, int month, int date, int hrs, int min):以int型表示年、月、日、时、分
Date(int year, int month, int date, int hrs, int min, int sec):以int型表示年、月、日、时、分、秒
在长整型和Date类之间有一个很有趣的对应关系,就是将一个时间表示为距离格林尼治标准时间1970年1月1日0时0分0秒的毫秒数。对于这种对应关系,Date类也有其相应的构造函数:Date(long date)。
获取Date类中的年、月、日、时、分、秒以及星期你可以使用Date类的getYear()、getMonth()、getDate()、getHours()、getMinutes()、getSeconds()、getDay()方法,你也可以将其理解为将Date类转换成int。
而Date类的getTime()方法可以得到我们前面所说的一个时间对应的长整型数,与包装类一样,Date类也有一个toString()方法可以将其转换为String类。
有时我们希望得到Date的特定格式,例如20020324,我们可以使用以下方法,首先在文件开始引入

import java.text.SimpleDateFormat;

java.util.Date date = new java.util.Date();
//如果希望得到YYYYMMDD的格式
SimpleDateFormat sy1=new SimpleDateFormat("yyyyMMdd");
String dateFormat=sy1.format(date);
//如果希望分开得到年,月,日
SimpleDateFormat sy=new SimpleDateFormat("yyyy");
SimpleDateFormat sm=new SimpleDateFormat("MM");
SimpleDateFormat sd=new SimpleDateFormat("dd");
String syear=sy.format(date);
String smon=sm.format(date);
String sday=sd.format(date);```

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 修改
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
Integer a = 1;
// int 类型在赋值到 Integer 类时,会自动封装,调用 Integer 的 valueOf(int i) 方法。
Integer a = Integer.valueOf(1);

public static Integer valueOf(int i) {
    assert IntegerCache.high >= 127;
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

当 i >= -128 && i <= 127 时,Integer.valueOf(i) 会将 i 存储在内部类 IntegerCachestatic final Integer cache[]里,这一字节的缓存内存地址是静态的,返回值即:

IntegerCache.cache[i + (-IntegerCache.low)]

因此:

Integer a = 1; 
Integer b = 1;
a 和 b 的引用都指向同一个对象,即 a == b。

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.注解的使用基于反射,通过反射可以去获取注解中的信息
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

1.实例

在这里插入图片描述

6.反射

1.反射的定义

  • 1.Java反射是指:在运行状态中,对于任意一个类,能够知道该类的所有属性和方法等信息,对于任意一个对象,能够调用该对象的任意方法和属性等信息,并且能改变它的属性

2.反射的作用

  • 1.反射机制是Java被视为准动态语言的一个关键性质(该机制允许程序在运行时通过反射取得任何一个已知名称的class的内部信息(运行时类的属性,方法,构造,访问修饰符,注解等),并可于运行时改变内容或调用方法

2.反射的优缺点

  • 1.优点
    • 1.反射机制允许程序在运行时取得任何一个已知名称的class的内部信息,包括包括其modifiers(修饰符),fields(属性),methods(方法)等,并可于运行时改变fields内容或调用methods
    • 2.可以动态的创建和使用对象(框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支持
  • 2.缺点
    • 1.使用反射基本是解释执行,对执行速度有影响
    • 2.使用反射可以打破封装性,导致java对象的属性不安全(可以修改对象的私有属性)

3.反射的使用步骤

  • 1.使用反射需要首先需要获取Class
  • 2.构造该类的实例对象
  • 3.通过实例对象获取到需要的成员变量和方法等信息或直接通过Class对象调用内部信息
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 说明:
    • 1.获得Class有三种方式
      • 1.通过Class对象的forName()静态方法,可能抛出ClassNotFoundException异常
      • 2.通过对象调用getClass()方法
      • 3.通过类名.class方式,该方法最为安全可靠,程序性能更高,同时说明任何一个类都有一个隐含的静态成员变量class
    • 2.一个类在JVM有且只有一个Class实例
    • 3.setAccessible(true)启用和禁用访问安全检查的开关,true表示反射的对象在使用时应该取消java语言的访问检查,反之不取消;如果不取消直接改变类的私有内容会报错
    • 4.反射只能获取当前类声明的方法,无法获取父类中声明的方法,直接通过反射获取子类的对象是不能获取父类的属性值的,必须根据反射获得的子类Class对象调用 getSuperclass()方法获取父类对象,然后再通过父类对象去获取父类的属性值

7.序列化和反序列化

1.序列化

1.序列化的定义

  • 1.序列化:指把内存中的Java对象通过某种方式存储到磁盘文件或传递给其他网络节点(网络上传输)
  • 2.即将数据结构或对象转换成二进制的过程
  • 3.如果想一次序列化多个对象可以将对象放到集合当中然后序列化集合,参与序列化的集合以及集合中的元素都需要实现java.io.Serializable接口

2.序列化的意义

  • 1.分布式系统:一般需要将Java对象通过网络在不同的节点间传输,此时需要将共享的对象数据转换为二进制形式,即序列化
  • 2.服务器钝化
    • 1.如果服务器发现某些对象很久不使用,那么服务器就会把这些内存中的对象以二进制的形式持久化在本地磁盘文件中(Java对象转换为二进制文件
    • 2.如果服务器发现某些对象需要使用时,先去内存中寻找,找不到再去磁盘文件中反序列化对象数据,将二进制数据恢复成 Java 对象,这样能节省服务器内存

3.序列化的步驟

  • 1.需要序列化的类必须实现序列化接口Java.lang.Serializable ,这是一个标志接口,没有任何抽象方法
  • 2.JVM底层会判断,如果当前对象是Serializable接口的实例才允许序列化,通过Java对象 instanceof Serializable判断
  • 3.Java中使用对象流来完成序列化反序列化,具体IO流操作可参考IO/NIO文章中的对象流实现序列化和反序列化

2.反序列化

1.反序列化的定义

  • 1.反序列化:指把磁盘文件中或网络节点上的对象数据,恢复成Java对象模型的过程
  • 2.即将在序列化过程中所生成的二进制转换成数据结构或对象的过程

3.序列化和反序列化的问题

1.transient问题

  • 1.某些数据不需要做序列化,只需字段面前加上transient
    private String name;//需要序列化
    transient private int age;//不需要序列化
    
  • 2.此时反序列化结果则显示name的具体值,而age因为没有序列化则无法传递数据,因此显示整型数据默认值0

2.序列化版本问题

  • 1.完成序列化操作后由于项目的升级或修改,可能会对序列化对象进行修改,那么进行反序列化可能会报错
    在这里插入图片描述
  • 2.只需要在类上增加一个 serialVersionUID字段,用来固定版本,无论怎么修改,只要版本都是一致的,就能进行反序列化
  • 3.注意
    • 1.Java虚拟机针对Serializable接口会自动生成一个序列化版本号
    • 2.这种自动生成的序列化版本号使代码一旦确定之后,就不能进行后续的修改
    • 3.因为只要修改必然会重新编译,此时会生成全新的序列化版本号,此时Java虚拟机会认为这是一个全新的类
    • 4.因此凡是类实现了Serializable接口,建议给该类手动提供一个固定不变的序列化版本号
    • 5.这样类即使修改了代码,但是版本号不变,Java虚拟机仍会认为是同一个类,可以正常进行反序列化
      private static final long serialVersionUID = 8656128222714547171L;
      

8.Java各版本特性

1.Java8

  • 1.Lambda表达式
  • 2.函数式接口
  • 3.方法引用
  • 4.默认方法
  • 5.Stream API
  • 6.Optional
  • 7.Date Time API
  • 8.Nashorn、JavaScript引擎

1.Lambda表达式

  • 1.定义Lambda表达式是对匿名函数简写形式Lambda允许把函数作为一个方法的参数,可以写出更简洁的代码
  • 2.实质语法糖,实质底层还是一个方法
    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){
	...
}

2.函数式接口

  • 1.定义: 只包含一个抽象方法的接口
  • 2.使用@FunctionalInterface注解,可以检查是否为函数式接口
1.四大核心函数式接口
函数式接口参数类型返回类型用途
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;
    }

}
2.其他函数式接口
函数式接口参数类型返回类型用途
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)

3.引用

  • 1.Lambda表达式的另一种写法
  • 2.当要传递给Lambda体的操作已经有实现则可以使用方法引用
1.方法引用
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参数列表的第一个参数是实例方法的调用者,第二个参数是实例方法的参数时,可以使用语法格式三
 *
 */
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;
    }
}
2.构造器引用
/**  
*	构造器引用:若Lambda体中存在新建对象,可以使用构造器引用
*  	法语格式:
*      ClassName::new
*  	注意:
*      1.函数式接口的方法的参数列表匹配对应的实体类的构造器
*/
	//构造器引用
	@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);
	}
3.数组引用
/*
*  	数组引用:
*	语法格式:
*   	Type[]::new
*/     
	//数组引用
	@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);
	}

5.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集合不会存储两个一样的数据
      在这里插入图片描述
      在这里插入图片描述
  • 5.具体细节可参考集合及其使用方式文章中的HashSet

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://www.cnblogs.com/ustc-anmin/p/11434769.html

10.BigDecimal

11.new关键字的作用

  • 1、为对象分配内存空间
  • 2、引起对象构造方法的调用
  • 3、为对象返回一个引用

在这里插入图片描述

值传递:在方法调用时,传递的是实际参数的值的副本。当参数变量被赋予新的值时,只会修改副本的值,不会影响原始值。Java 中的基本数据类型都采用值传递方式传递参数变量的值。

引用传递:在方法调用时,传递的是实际参数的引用(即内存地址)。当参数变量被赋予新的值时,会修改原始值的内容。Java 中的对象类型采用引用传递方式传递参数变量的值。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值