java常用知识

java知识进阶

基础知识回顾

定义类

定义类:
    格式:修饰符 class 类名{

    }
    注意:1.类名的首字母建议大写。满足驼峰模式。 StudentNameCode
         2.一个Java代码文件中可以定义多个类。但是按照规范还是建议一个Java文件定义一个类。
         3.一个Java代码文件中,只能有一个类是用public修饰的,
           而且public修饰的类名必须成为当前Java代码的文件名称。

类中的成分

类中的成分:有且仅有五大成分(五大金刚)
    修饰符 class 类名{
        // 1.成员变量(Field):  描述类或者对象的属性信息的。
        // 2.成员方法(Method): 描述类或者对象的行为信息的。
        // 3.构造器(Constructor): 初始化一个对象返回。
        // 4.代码块
        // 5.内部类
    }
    类中有且仅有这五种成分,否则代码报错!

构造器

构造器:
    格式:修饰符 类名(形参列表){

         }
    作用:初始化类的一个对象返回。
    构造器的分类:无参数构造器,有参数构造器。
    构造器的注意点:一个类默认自带一个无参数构造器,但是如果写了有参数构造器那么
        默认的无参数构造器就消失了,此时如果还需要用无参数构造器就需要自己从新写一个。
    构造器初始化对象的格式:
        类名 对象名称 = new 构造器;
        Student s = new Student();
    无参数构造器的作用:初始化一个类的对象(使用对象的默认值初始化)返回。
    有参数构造器的作用:初始化一个类的对象(可以在初始化对象的时候为对象赋值)返回。

面向对象三大特性

面向对象的三大特征:封装,继承,多态。
    是Java语言的风格。是我们在开发中必须遵循的,即使毫无意义,代码还是要按照这个风格写!!

    封装的作用:
        1.可以提高安全性。
        2.可以实现代码的组件化。

    封装的规范:
        1.建议成员变量都私有:用private修饰。
           private修饰的方法,成员变量,构造器等只能在本类被直接访问。
        2.提供成套的getter+setter方法暴露成员变量的取值和赋值。
           public修饰符,是公开的意义。
    小结:
        封装的核心思想:合理隐藏,合理暴露。
        封装已经成为Java代码的风格,即使代码毫无意义,还是要按照封装的规范写代码。
            成员变量私有,提供getter+setter方法。

static关键字

概述

目标:static关键字的概述。(重点)

引入:
    我们之前定义了很多成员变量(name , age , sex)
    其实我们只写了一份,但是发现每个对象都可以用,就说明
    Java中这些成员变量或者方法是存在所属性的。
    有些是属于对象的,有些是属于类本身的。

Java是通过成员变量是否有static修饰来区分是类的还是属于对象的。

static == 静态 == 修饰的成员(方法和成员变量)属于类本身的。

按照有无static修饰,成员变量和方法可以分为:
    成员变量:
        (1)静态成员变量(类变量):
                 有static修饰的成员变量称为静态成员变量也叫类变量,属于类本身的,
                 直接用类名访问即可。

        (2)实例成员变量
                无static修饰的成员变量称为实例成员变量,属于类的每个对象的,
                必须用类的对象来访问。

    成员方法:
        (1)静态方法
                有static修饰的成员方法称为静态方法也叫类方法,属于类本身的,
                直接用类名访问即可。

        (2)实例方法
                无static修饰的成员方法称为实例方法,
                属于类的每个对象的,必须用类的对象来访问。

小结:
    成员变量有2种
         -- 有static修饰的属于类叫静态成员变量,与类一起加载一次,直接用类名调用即可。
         -- 无static修饰的属于类的每个对象的叫实例成员变量,
            与类的对象一起加载,对象有多少个,实例成员变量就加载多少份。必须用类的对象调用。

    成员方法有2种:
         -- 有static修饰的属于类叫静态方法,直接用类名调用即可。
         -- 无static修饰的属于类的每个对象的叫实例方法,必须用类的对象调用。
          静态方法也可以被对象共享访问,但是不推荐,因为静态方法直接用类名访问即可。
拓展:成员变量和成员方法访问的拓展。(面试常考)

方法:实例方法,静态方法。
成员变量:实例成员变量,静态成员变量。

8种访问形式的问答:
     a.实例方法是否可以直接访问实例成员变量?可以的,因为它们都属于对象。
     b.实例方法是否可以直接访问静态成员变量?可以的,静态成员变量可以被共享访问。
     c.实例方法是否可以直接访问实例方法? 可以的,实例方法和实例方法都属于对象。
     d.实例方法是否可以直接访问静态方法?可以的,静态方法可以被共享访问!
     --------------------------------------------------------------------
     a.静态方法是否可以直接访问实例变量? 不可以的,实例变量必须用对象访问!!
     b.静态方法是否可以直接访问静态变量? 可以的,静态成员变量可以被共享访问。
     c.静态方法是否可以直接访问实例方法? 不可以的,实例方法必须用对象访问!!
     d.静态方法是否可以直接访问静态方法?可以的,静态方法可以被共享访问!!

成员变量和成员方法访问

拓展:成员变量和成员方法访问的拓展。(面试常考)

方法:实例方法,静态方法。
成员变量:实例成员变量,静态成员变量。

8种访问形式的问答:
     a.实例方法是否可以直接访问实例成员变量?可以的,因为它们都属于对象。
     b.实例方法是否可以直接访问静态成员变量?可以的,静态成员变量可以被共享访问。
     c.实例方法是否可以直接访问实例方法? 可以的,实例方法和实例方法都属于对象。
     d.实例方法是否可以直接访问静态方法?可以的,静态方法可以被共享访问!
     --------------------------------------------------------------------
     a.静态方法是否可以直接访问实例变量? 不可以的,实例变量必须用对象访问!!
     b.静态方法是否可以直接访问静态变量? 可以的,静态成员变量可以被共享访问。
     c.静态方法是否可以直接访问实例方法? 不可以的,实例方法必须用对象访问!!
     d.静态方法是否可以直接访问静态方法?可以的,静态方法可以被共享访问!!

继承

概述

目标:继承(extends)的概述。

面向对象的三大特征:封装,继承,多态。

继承是Java中一般到特殊的关系,是一种子类到父类的关系。
    例如:学生类继承了人类。  猫类继承了动物类。

被继承的类称为:父类/超类。
继承父类的类称为:子类。

继承的作用?
    “可以提高代码的复用”,相同代码可以定义在父类中。
    然后子类直接继承父类,就可以直接使用父类的这些代码了。
    (相同代码重复利用)

子类更强大:子类不仅得到了父类的功能,它还有自己的功能。

继承的特点:
    子类继承了一个父类,子类就可以直接得到父类的属性(成员变量)和行为(方法)了。

继承的格式:
    子类 extends 父类{

    }
小结:
     继承是子类到到父类的一种关系。
     子类继承了一个父类,子类就可以直接得到父类的属性和行为了。
     在Java中继承是 “is a” 的关系。Cat extends Animal:猫是一个动物。
     在Java中,子类是更强大的,子类不仅继承了父类的功能,自己还可以定义自己的功能。
     
        继承的优势可以把相同的代码定义在父类中,子类可以直接继承使用。
        这样就可以提高代码的复用性:相同代码只需要在父类中写一次就可以了。

子类不能继承父类的内容

    目标:子类不能继承父类的内容。

    引入:
        子类继承父类,子类就得到了父类的属性和行为。
        但是并非所有父类的属性和行为等子类都可以继承。
    子类不能继承父类的东西:
        子类不能继承父类的构造器:子类有自己的构造器。(没有争议的)
    有争议的观点(拓展):
        子类是否可以继承父类的私有成员(私有成员变量,私有成员方法)?
            -- 我认为子类是可以继承父类的私有成员的,只是不能直接访问而已。
            -- 以后可以暴力去访问继承自父类的私有成员~~~
        子类是否可以继承父类的静态成员?
            -- 我认为子类是不能继承父类的静态成员的,
            -- 子类只是可以访问父类的静态成员,父类的静态成员只有一份可以被子类共享访问。
                共享并非继承。

继承时成员变量的访问特定

 目标:继承后成员变量的访问特点。

    继承后成员变量的访问特点:就近原则
        -- 子类有找子类,子类没有找父类,父类没有就报错!

    如果一定要申明访问父类的成员变量可以使用:super.父类成员变量。
        -- super指父类引用。
    小结:
        子类访问成员变量的原则:就近原则。
        如果一定要访问父类的成员变量可以使用super关键字。

方法重写

    目标:方法重写。

    方法重写的概念:
        子类继承了父类,子类就得到了父类的某个方法
        但是子类觉得父类的这个方法不好用或者无法满足自己的需求
        子类重写一个与父类申明一样的方法来覆盖父类的该方法,子类的这个方法
        就进行了方法重写。

    方法重写的校验注解: @Override
        Java建议在重写的方法上面加上一个@Override注解。
        方法一旦加了这个注解,那就必须是成功重写父类的方法,否则报错!
        @Override优势:可读性好,安全,优雅!!

    方法重写的具体要求:
        1.子类重写方法的名称和形参列表必须与父类被重写方法一样。
        2.子类重写方法的返回值类型申明要么与父类一样,要么比父类方法返回值类型范围更小。
        3.子类重写方法的修饰符权限应该与父类被重写方法的修饰符权限相同或者更大。
        4.子类重写方法申明抛出的异常应该与父类被重写方法申明抛出的异常一样或者范围更小!

    方法重写的规范:
        1.加上@Override注解。
        2.建议“申明不变,重新实现”。

    小结:
        方法重写是子类重写一个与父类申明一样的方法覆盖父类的方法。
        方法重写建议加上@Override注解。
        方法重写的核心要求:方法名称形参列表必须与被重写方法一致!!
        建议“申明不变,重新实现”。
super:代表了父类引用。
super可以用在子类的实例方法中调用父类被重写的方法。
目标:静态方法和私有方法是否可以被重写(拓展语法)

可以吗?  都不可以.

继承后构造器特点

目标:继承后构造器的特点。

继承后子类构造器的特点:
    调用子类构造器的时候,子类的构造器一定会先调用父类的无参数构造器再执行自己的构造器。
为什么子类构造器一定要先调用父类无参数构造器:
    1.子类的全部构造器的第一行默认有一个super()调用父类无参数构造器。
    2.调用子类构造器初始化子类对象的时候,必须先调用父类构造器去初始化继承自父类的成员。
小结:
    子类构造器默认一定会先调用父类的无参数构造器再执行自己。
目标:super调用父类构造器。

特点:
    子类的全部构造器默认一定会调用父类的无参数构造器。

super(...):可以根据参数选择调用父类的某个构造器。

小结:
    可以在子类构造器中通过super(...)根据参数选择调用父类的构造器,以便调用
    父类构造器初始化继承自父类的数据。

this和super关键字使用总结

this关键字的作用:
        this代表了当前对象的引用。
        this关键字可以用在实例方法和构造器中。
        this用在方法中,谁调用这个方法,this就代表谁。
        this用在构造器,代表了构造器正在初始化的那个对象的引用。
        
总结与拓展:this和super关键字使用总结

 this关键字代表了当前对象的引用。
    this可以出现在方法,构造器中。
    this出现在方法中:哪个对象调用这个方法this就代表谁。
    this可以出现在构造器中:代表构造器正在初始化的那个对象。
    this可以区分变量是访问的成员变量还是局部变量。

this代表了当前对象的引用(继承中指代子类对象):
    this.子类成员变量。
    this.子类成员方法。
    this(...):可以根据参数匹配访问本类其他构造器。
super代表了父类对象的引用(继承中指代了父类对象空间)
    super.父类成员变量。
    super.父类的成员方法。
    super(...):可以根据参数匹配访问父类的构造器。

拓展:this(...)根据参数匹配访问本类其他构造器。

注意:
    this(...)借用本类其他构造器。
    super(...)调用父类的构造器。
    this(...)和super(...)必须放在构造器的第一行,否则报错!
    所以this(...)和super(...)不能同时出现在构造器中!!!

Java是不是最好的语言?
     只能找到合适的语言,而不能找到最好的语言。
     Python
     Go语言
     C语言
     Java WEB
     微软的技术。

继承的特点

目标:继承的特点;

1.单继承:一个类只能继承一个直接父类。
    为什么Java是单继承的?
        答:反证法,假如Java可以多继承,请看如下代码:
        class A{
            public void test(){
                System.out.println("A");
            }
        }
        class B{
            public void test(){
                System.out.println("B");
             }
        }
        class C extends A , B {
            public static void main(String[] args){
                C c = new C();
                c.test(); // 出现了类的二义性!所以Java不能多继承!!
            }
        }

2.多层继承:一个类可以间接继承多个父类。(家谱)
3.一个类可以有多个子类。
4.一个类要么默认继承了Object类,要么间接继承了Object类,Object类是Java中的祖宗类!!

应用类型

目标:引用类型作为方法参数和返回值。

引用类型作为Java的数据类型,自然可以作为方法的参数类型和返回值类型。
除了基本数据类型都是引用数据类型了。

关注语法即可!!

小结:
    引用类型作为数据类型可以在一切可以使用类型的地方使用!!
     引用类型也可以定义成员变量,该成员变量有时候称为复合类型的变量

抽象类

概述

目标:抽象类的入门概述。

引入:
    父类知道子类一定要完成某个功能,但是每个子类实现的情况都不一样
    而且子类都会用自己的功能了,父类的该功能就可以定义成抽象的方法。
    拥有抽象方法的类必须定义成抽象类。
什么是抽象方法?
    没有方法体,只有方法签名,必须用abstract修饰的方法就是抽象方法。
什么是抽象类?
    拥有抽象方法的类必须定义成抽象类。
    抽象类必须用abstract关键字修饰。
小结:
    抽象方法:没有方法体,只有方法签名,必须用abstract修饰的方法就是抽象方法。
    抽象类:拥有抽象方法的类必须定义成抽象类,必须用abstract修饰。
    
    
    // 抽象类:拥有了抽象方法的类必须定义成抽象类。抽象类必须加上abstract修饰。
abstract class Animal{
    // 抽象方法:没有方法体,只有方法签名,必须加上abstract修饰。
    public abstract void run();
}

作用

目标:抽象类的使用。

抽象类是为了被继承。

总结:
    一个类继承了抽象类,必须重写完抽象类的全部抽象方法,否则这个类必须定义成抽象类。
    因为拥有抽象方法的类必须定义成抽象类。

意义

目标:抽象类的意义。

抽象类存在的意义有两点:
    (1)被继承,抽象类就是为了被子类继承,否则抽象类将毫无意义。(核心意义)
    (2)抽象类体现的是"模板思想":部分实现,部分抽象。(拓展)
        -- 可以使用抽象类设计一个模板模式。

特征

目标:抽象类的特征研究和深入。

抽象类的特征:有得有失。
    有得:抽象类拥有了得到抽象方法的能力。
    失去:抽象类失去了创建对象的能力。

面试题:抽象类是否有构造器,是否可以创建对象,为什么?
     答:抽象类作为类一定有构造器,而且必须有构造器。
     提供给子类继承后调用父类构造器使用的。

     抽象类虽然有构造器,但是抽象类绝对不能创建对象。
     抽象类中可能存在抽象方法,抽象方法不能执行。
     抽象在学术上本身意味着不能实例化。

小结:
    抽象类不能创建对象。
    抽象类除了不能创建对象之外,类的其他成分它都具备!
    抽象类中也可以没有抽象方法!!

设计模板模式

目标:使用抽象类设计一个模板模式。

设计模式:是前人(技术大牛,或者一些技术协会,或者一些大型知名的IT公司)
         已经研发好或者设计好或者在实战开发中发现的的优秀软件设计思想,开源出去
         后来者可以直接使用就能够得到很好的软件模式。

设计模式的目的:得到优秀的软件架构,从而提升代码的可重用性,扩展性,维护性,可读性。

模板模式是一种设计模式思想:
模板模式的作用:部分实现,部分抽象,可以极大的简化功能代码,提高开发效率

写一个模板模式的案例:作文模板。
    作文模板:
    标题和第一段 以及最后一段是固定的,
    正文部分交给使用模板的人自己来实现。
小结:
    抽象类是部分实现,部分抽象的含义,所以可以设计模板模式。
    好处:模板可以确定的模板自己实现,模板不能确定的定义成抽象方法交给使用模板的人重写。
    可以设计出优秀的设计模式,提升开发效率,提高代码的重用性!
    
    
class Teacher extends Template{
    @Override
    public String writeMain() {
        return "\t\t我爸就是好,有多好,做他儿子才能懂~~~";
    }
}

class Student extends Template{
    @Override
    public String writeMain() {
        return "\t\t我爸爸很牛,我爸爸是马云,就是爽,很有钱~~~~";
    }
}
// 1.写一个模板类:代表了作文模板。
abstract class Template{
    private String title = "\t\t\t\t\t\t《我的爸爸》";
    private String one = "\t\t我的爸爸很牛逼,到底有多牛呢,请看如下说明:";
    private String last = "\t\t以上就是我的爸爸,简直太好了,下辈子还要做他儿子!";

    // 2.提供一个写作文方法
    public void write(){
        System.out.println(title);
        System.out.println(one);
        // 正文:正文部分模板是不知道怎么写的!应该把正文部分定义成抽象方法
        // 交给使用模板的子类重写!
        System.out.println(writeMain());
        System.out.println(last);
    }

    // 正文部分定义成抽象方法,交给子类重写!!
    public abstract String writeMain();
}

注意事项和总结

目标:抽象类的注意事项和总结

1. 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
    理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。

2. 抽象类一定有而且是必须有构造器,是供子类创建对象时,初始化父类成员使用的。
    理解:子类的构造器中,有默认的super(),需要访问父类构造器。

3. 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

4. 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则子类也必须定义成抽象类。

5. 抽象类存在的意义是为了被子类继承,抽象类体现的是模板思想。
   理解:抽象类中已经实现的是模板中确定的成员,
   抽象类不确定如何实现的定义成抽象方法,交给具体的子类去实现。

接口

概述

目标:接口的概述和定义等。(以理解和记住语法为主)

什么是接口?
     接口是更加彻底的抽象,接口中全部是抽象方法和常量,没有其他成分。(JDK 1.8之前)

接口有啥用?
     接口体现的是规范思想,实现接口的类必须重写完接口中全部的抽象方法。
     规范 == 约束。
     接口称为被实现,实现接口的类称为实现类。

定义接口的格式:
     修饰符 interface 接口名称{

     }
     interface:定义接口的关键字。

接口中的成分研究(JDK 1.8之前):
     1.抽象方法
          a.接口中的抽象方法默认会加上public abstract修饰,所以可以省略不写。
     2.常量
       常量:是指有public static final修饰的成员变量,有且仅能被赋值一次,值不能改变。
       常量的名称规范上要求全部大写,多个单词下划线连接。
       常量修饰的public static final 可以省略不写,默认会加上。
小结:
     定义接口使用的关键字:interface
     接口中的成分在JDK 1.8之前只能有:常量和抽象方法。
     在接口中常量的修饰符:public static final 可以省略不写,默认会加上。
     在接口中抽象方法的修饰符:public abstract 可以省略不写,默认会加上。

基本实现

目标:接口的基本实现。

接口是用来被类实现的。

引入:
    类与类是继承关系:一个类只能直接继承一个父类。
    类与接口是实现关系:一个类可以实现多个接口。
    实现接口的类称为“实现类”。

    子类   继承   父类
    实现类 实现   接口

实现类实现接口的格式:
    修饰符 class 实现类名称 implements 接口1,接口2,接口3,....{

    }
    implements:实现的含义。
    接口是可以被多实现的:一个类可以实现多个接口。

小结:
    接口是用类被实现的,实现接口的类称为实现类。
    实现接口的关键字是:implements。
    接口是可以被类多实现的。
    注意:一个类实现接口必须重写完接口中全部抽象方法,否则这个类必须定义成抽象类!!
 目标:接口的多实现介绍。

 实现类实现接口的格式:
     修饰符 class 实现类名称 implements 接口1,接口2,接口3,....{

     }

 类与类是单继承。
 类与接口是多实现。

小结:
    一个类可以实现多个接口。
    一个类如果实现了多个接口,必须重写完全部接口中的全部抽象方法
    否则这个类必须定义抽象类。
目标:接口与接口的多继承。

引入:
    类与类是单继承关系:一个类只能继承一个直接父类。
    类与接口是多实现关系:一个类可以实现多个接口。
    接口与接口是多继承关系:一个接口可以继承多个接口。

jdk8之后接口的新增方法

目标:JDK 1.8开始之后接口新增的三种方法(理解语法,属于Java自己的技术)

引入:
    JDK 1.8之前接口中只能是抽象方法,常量。
    JDK 1.8开始之后接口不再纯洁了。
    JDK 1.8开始之后接口新增了如下三种方法。

a.默认方法(就是之前写的普通实例方法)
    -- 必须用default修饰,默认会public修饰
    -- 必须用接口的实现类的对象来调用。

b.静态方法
    -- 默认会public修饰
    -- 注意:接口的静态方法必须用接口的类名本身来调用。

c.私有方法(就是私有的实例方法): JDK 1.9才开始有的。
    -- 只能在本类中被其他的默认方法或者私有方法访问。

实现多个接口的使用注意实现

拓展:实现多个接口的使用注意实现。(非常偏的语法,理解和了解即可)

1.如果实现了多个接口,多个接口中存在同名的静态方法并不会冲突,
    原因是只能通过各自接口名访问静态方法。

2.当一个类,既继承一个父类,又实现若干个接口时,(重点)
    父类中的成员方法与接口中的默认方法重名,子类就近选择执行父类的成员方法。

3.当一个类实现多个接口时,多个接口中存在同名的默认方法。
    实现类必须重写这个方法。

4.接口中,没有构造器,不能创建对象。(重点)
    接口是更彻底的抽象,连构造器都没有,自然不能创建对象!!

代码块

静态代码块

目标:代码块-静态代码块。

代码块是类的成分之一:
    成员变量,方法,构造器,代码块,内部类。
代码块按照有无static修饰分为:
    1.静态代码块。
    2.实例代码块。
静态代码块的格式:
    static {

    }
静态代码块特点:
    -- 必须有static修饰。
    -- 会与类一起优先加载,且自动触发执行一次。
静态代码块作用:
    -- 可以在执行类的方法等操作之前先在静态代码块中进行静态资源的初始化操作。

小结:
    静态代码块有static修饰,与类一起加载,自动触发执行一次。
    静态代码块的作用:可以用于在静态代码块中进行静态资源的初始化操作。
    
    
    public class CodeDemo01 {
    public static String schoolName ;
    public static ArrayList<String> lists = new ArrayList<>();

    // 静态代码块,属于类,与类一起加载一次!
    static {
        System.out.println("静态代码块被触发执行~~~~~~~");
        // 在静态代码块中进行静态资源的初始化操作
        schoolName = "黑马";
        lists.add("3");
        lists.add("4");
        lists.add("5");
    }
    
    public static void main(String[] args) {
        System.out.println(schoolName);
        System.out.println(lists);
    }
}

实例代码块

目标:代码块-实例代码块。

实例代码块的格式:
     {

     }

实例代码块的特点:
    -- 无static修饰。
    -- 会与类的对象一起加载,每次创建类的对象的时候,
        实例代码块都会被加载且自动触发执行一次。
    -- 实例代码块的代码在底层实际上是提取到每个构造器中去执行的!

实例代码块的作用:
    -- 实例代码块可以在创建对象之前进行实例资源的初始化操作。

小结:
    实例代码块无static修饰,属于对象,与对象一起加载执行。
    实例代码块的代码在底层实际上是提取到每个构造器中去执行的!
    实例代码块可以在创建对象之前进行实例资源的初始化操作。
    
    
    public class CodeDemo02 {
    private String name;
    private ArrayList<String> lists = new ArrayList<>();
    // 实例代码块!属于对象!与对象一起加载!
    {
        name = "小手";
        lists.add("东");
        lists.add("南");
        lists.add("西");
        lists.add("北");
        System.out.println("实例代码块被触发执行一次~~~~~~~~");
    }

    public CodeDemo02(){

    }
    public CodeDemo02(String name){

    }

    public static void main(String[] args) {
        CodeDemo02 c = new CodeDemo02();
        System.out.println(c.name);
        System.out.println(c.lists);
        new CodeDemo02();
        new CodeDemo02();
    }
}

final关键字

目标:final关键字。

final是最终的含义。
final用于修饰:类,方法,变量。

1.final修饰类,类不能被继承了。
2.final可以修饰方法,方法就不能被重写了。
3.final修饰变量总规则:变量有且仅能被赋值一次。

拓展:final和abstract的关系?
      互斥关系,不能同时修饰类或者同时修饰方法!!
目标:final修饰变量-局部变量。

final修饰变量的总规则:有且仅能被赋值一次。

变量有几种?
    成员变量
        -- 静态成员变量:有static修饰,属于类,只加载一份。
        -- 实例成员变量:无static修饰,属于每个对象,与对象一起加载。
    局部变量
        -- 只能方法中,构造器中,代码块中,for循环中,用完作用范围就消失了。
final修饰局部变量:
        -- 让值被固定或者说保护起来,执行的过程中防止被修改。
目标:final修饰静态成员变量。final修饰变量的总规则:有且仅能被赋值一次。final修饰静态成员变量,变量变成了常量。常量:有public static final修饰,名称字母全部大写,多个单词用下划线连接。拓展:    final修饰静态成员变量可以在哪些地方赋值一次:    1.定义的时候赋值一次。    2.可以在静态代码块中赋值一次。            public class FinalDemo03 {    // 常量:有public static final修饰,名称字母全部大写,多个单词用下划线连接。    public static final String SCHOOL_NAME = "黑马" ;    public static final String SCHOOL_NAME1  ;    static{        SCHOOL_NAME1 = "黑马1";        //SCHOOL_NAME1 = "黑马2"; // 报错,第二次赋值!    }}
     目标:final修饰实例成员变量。(了解。用不到)

     final修饰变量的总规则:有且仅能被赋值一次。
     拓展:
        final修饰实例成员变量可以在哪些地方赋值1次:
            1.定义的时候赋值一次。
            2.可以在实例代码块中赋值一次。
            3.可以在每个构造器中赋值一次。

单例模式

目标:面试必考(单例模式)。

单例模式的含义: 单例模式,是一种常用的软件设计模式。通过单例模式可以保证系统中,
       应用该模式的这个类永远只有一个实例。即一个类永远只有一个对象实例。

单例的应用场景:在实际开发中,有很多业务对象永远只需要一个,无论启动多少次
我们只需要一个对象,例如任务管理对象,只需要一个对象。节约内存和性能。
因为对象越多内存占用越大,极有可能出现内存溢出!

实现单例模式两种方式:
       1.饿汉单例设计模式
            在用类获取对象的时候,对象已经提前创建好了。
            设计步骤:
            a.定义一个类,把构造器私有。
            b.定义一个静态变量存储一个对象。
            c.提供一个返回单例对象的方法。

       2.懒汉单例设计模式
            在真正需要该对象的时候,才去创建一个对象(延迟加载对象)。
            设计步骤:
            a.定义一个类,把构造器私有。
            b.定义一个静态变量存储一个对象。
            c.提供一个返回单例对象的方法。
// 饿汉单例设计模式
class Singleton01{
    //  b.定义一个静态变量存储一个对象( 在用类获取对象的时候,对象已经提前为你创建好了。)
    private static final Singleton01 INSTANCE = new Singleton01();
    //  a.定义一个类,把构造器私有。
    private Singleton01(){
    }
    // c.提供一个返回单例对象的方法。
    public static Singleton01 getInstance(){
        return INSTANCE;
    }
}

public class SingleInstanceDemo01 {
    public static void main(String[] args) {
        Singleton01 s1 = Singleton01.getInstance();
        Singleton01 s2 = Singleton01.getInstance();
        System.out.println(s1 == s2);
    }
}
// 懒汉单例设计模式
class Singleton02{
    //  b.定义一个静态变量存储一个对象(这里不能创建对象,需要的时候才创建,这里只是一个变量用于存储对象!)
    public static Singleton02  instance ;

    //   a.定义一个类,把构造器私有。
    private Singleton02(){

    }
    //  c.提供一个返回单例对象的方法。
    public static Singleton02 getInstance(){
        if(instance == null){
            // 第一次来拿单例对象!需要创建一次对象,以后直接返回!!
            instance = new Singleton02();
        }
        return instance;
    }
}

public class SingleInstanceDemo02 {
    public static void main(String[] args) {
        Singleton02 s1 = Singleton02.getInstance();
        Singleton02 s2 = Singleton02.getInstance();
        System.out.println(s1 == s2);
    }
}

枚举

枚举概述

目标:枚举的概述和作用。

枚举是Java中的一种特殊类型。
枚举的作用:是为了做信息的标志和信息的分类。

定义枚举的格式:
    修饰符 enum 枚举名称{
        第一行都是罗列枚举实例的名称。
    }
枚举类的编译以后源代码:
     public final class Season extends java.lang.Enum<Season> {
         public static final Season SPRING = new Season();
         public static final Season SUMMER = new Season();
         public static final Season AUTUMN = new Season();
         public static final Season WINTER = new Season();

         public static Season[] values();
         public static Season valueOf(java.lang.String);
     }
枚举的特点:
     1.枚举类是用final修饰的,枚举类不能被继承!
     2.枚举类默认继承了java.lang.Enum枚举类。
     3.枚举类的第一行都是常量,存储都是枚举类的对象。
     4.枚举类的第一行必须是罗列枚举类的实例名称。
     所以:枚举类相当于是多例设计模式。
小结:
     枚举类的特点:
     1.枚举类是用final修饰的,枚举类不能被继承!
     2.枚举类默认继承了java.lang.Enum枚举类。
     3.枚举类的第一行都是常量,存储都是枚举类的对象。
     4.枚举类的第一行必须是罗列枚举类的实例名称。
     所以:枚举类相当于是多例设计模式。
// 枚举
enum Season {
    SPRING , SUMMER , AUTUMN , WINTER;
}

枚举作用

目标:枚举的作用:是为了做信息的标志和信息的分类。

常量做信息分类和信息标志:
    虽然可以实现可读性,但是入参不受限制!!!

Java建议做信息标志和信息分类应该使用枚举实现:最优雅的方式。
    可以实现可读性,而且入参受限制,不能乱输入!!!

小结:
    枚举的作用:是为了做信息的标志和信息的分类。
    
    
enum Oritation{
    UP , DOWN , LEFT , RIGHT ;
}
public class EnumDemo02 {
    public static void main(String[] args) {
        move(Oritation.RIGHT); // 方法入参只能输入枚举的4个类型!
    }

    // 提供一个方法控制玛丽的方向。
    // 上下左右
    public static void move(Oritation o) {
        switch (o) {
            case UP:
                System.out.println("让🐎往👆蹦~~~~");
                break;
            case DOWN:
                System.out.println("让🐎往👇蹦~~~~");
                break;
            case LEFT:
                System.out.println("让🐎往👈蹦~~~~");
                break;
            case RIGHT:
                System.out.println("让🐎往👉蹦~~~~");
                break;
        }
    }
}   

枚举的api

枚举类.values()获取当前类的全部枚举实例

枚举类对象.ordinal()获取枚举对象的索引

public class EnumDemo03 {
    public static void main(String[] args) {
        Season03 s = Season03.SPRING;
        System.out.println(s); // SPRING
        System.out.println("---------------");
        // 获取当前类的全部枚举实例 : public static Season[] values()
        Season03[] ss = Season03.values();
        for(int i = 0 ; i < ss.length ; i++ ){
            Season03 s1 = ss[i];
            System.out.println(s1);
        }

        // 获取枚举对象的索引: ordinal()
        Season03 s2 = Season03.AUTUMN;
        System.out.println(s2.ordinal()); // 2
    }
}

enum Season03{
    SPRING, SUMMER , AUTUMN, WINTER ;
}

enum Sex03{
    BOY , GIRL;
}

多态

概述

目标:多态的入门概述。

面向对象的三大特征:封装,继承,多态。

多态的形式:
    父类类型 对象名称 = new 子类构造器;
    接口    对象名称 = new 实现类构造器;

    父类类型的范围 > 子类类型范围的。

多态的概念:
    同一个类型的对象,执行同一个行为,在不同的状态下会表现出不同的行为特征。

多态的识别技巧:
    对于方法的调用:编译看左边,运行看右边。
    对于变量的调用:编译看左边,运行看左边。

多态的使用前提:
    (1)  必须存在继承或者实现关系。
    (2)  必须存在父类类型的变量引用子类类型的对象。
    (3)  需要存在方法重写。

小结:
    记住多态的形式,识别,概念等语法即可!

优势

目标:多态的优劣势。

优势:
    1.在多态形式下,右边对象可以实现组件化切换,业务功能也随之改变,
       便于扩展和维护。可以实现类与类之间的解耦。
    2.实际开发的过程中,父类类型作为方法形式参数,传递子类对象给方法,
        可以传入一切子类对象进行方法的调用,更能体现出多态的扩展性与便利。

劣势:
    1.多态形式下,不能直接调用子类特有的功能。编译看左边!! 左边
    父类中没有子类独有的功能,所以代码在编译阶段就直接报错了!
小结:
     记住以上语法!

引用数据类型的自动类型转换

目标:引用数据类型的自动类型转换。

在基础班学过了基本数据类型的转换。
    1.小范围类型的变量或者值可以直接赋值给大范围类型的变量。
    2.大范围类型的变量或者值必须强制类型转换给小范围类型的变量。


引用数据类型转换的思想是一样的:
    父类类型的范围 > 子类类型的范围。
    Animal          Cat

引用数据类型的自动类型转换语法:
    1.子类类型的对象或者变量可以自动类型转换赋值给父类类型的变量。

小结:
    记住语法!
    引用类型的自动类型转换并不能解决多态的劣势。

引用类型强制类型转换

目标:引用类型强制类型转换

引用类型强制类型转换的语法:
    1.父类类型的变量或者对象必须强制类型转换成子类类型的变量,否则报错!

强制类型转换的格式:
    类型 变量名称 = (类型)(对象或者变量)

注意:有继承/实现关系的两个类型就可以进行强制类型转换,编译阶段一定不报错!
     但是运行阶段可能出现:类型转换异常 ClassCastException

Java建议在进行强制类型转换之前先判断变量的真实类型,再强制类型转换!
    变量 instanceof 类型: 判断前面的变量是否是后面的类型或者其子类类型才会返回true,

小结:
    有继承/实现关系的两个类型就可以进行强制类型转换,编译阶段一定不报错!
    但是运行阶段可能出现:类型转换异常 ClassCastException
    Java建议在进行强制类型转换之前先判断变量的真实类型,再强制类型转换!
        变量 instanceof 类型: 判断前面的变量是否是后面的类型或者其子类类型才会返回true,
        
        
public class PolymorphicDemo {
    public static void main(String[] args) {
        Animal a = new Wolf();
        a.run();
        // a.catchSheep(); // 报错!

        // 1.把动物类型的变量a 转换成 真实的狼类型
        Wolf w = (Wolf) a;
        w.catchSheep();

        // 2.多态下类型转换异常问题研究(重点)
        Animal a1 = new Cat();
        //Wolf w1 = (Wolf) a1; // 编译阶段没有报错!在运行阶段出现ClassCastException类型转换成!

        if(a1 instanceof Cat){
            Cat c1 = (Cat) a1;
            c1.catchMouse();
        }else if(a1 instanceof Wolf){
            Wolf w1 = (Wolf) a1;
            w1.catchSheep();
        }
    }
}

class Wolf extends Animal{
    @Override
    public void run(){
        System.out.println("狼跑的飞快~~~");
    }

    public void catchSheep(){
        System.out.println("🐺抓🐏");
    }
}

class Cat extends Animal{
    @Override
    public void run(){
        System.out.println("猫跑的贼快~~~");
    }

    public void catchMouse(){
        System.out.println("🐱抓🐀~~");
    }
}

class Animal{
    public void run(){
        System.out.println("动物可以跑~~");
    }
}        

内部类

概述和分类

目标:内部类的概述和分类。

内部类是类的五大成分之一:成员变量,方法,构造器,代码块,内部类。

什么是内部类?
    定义在一个类里面的类就是内部类。

内部类有什么用?
    可以提供更好的封装性, 内部类有更多权限修饰符 , 封装性有更多的控制。
    可以体现出组件的思想。

内部类的分类:
    (1)静态内部类。
    (2)实例内部类。(成员内部类)
    (3)局部内部类。
    (4)匿名内部类。(重点)

小结:
    匿名内部类是我们的重点。

静态内部类

目标:静态内部类的研究(了解语法即可)

什么是静态内部类?
    有static修饰,属于外部类本身,会加载一次。

静态内部类中的成分研究:
    类有的成分它都有,静态内部类属于外部类本身,只会加载一次
    所以它的特点与外部类是完全一样的,只是位置在别人里面而已。

    外部类=宿主
    内部类=寄生

静态内部类的访问格式:
    外部类名称.内部类名称

静态内部类创建对象的格式:
    外部类名称.内部类名称 对象名称 = new 外部类名称.内部类构造器;

静态内部类的访问拓展:
    静态内部类中是否可以直接访问外部类的静态成员?可以的,外部类的静态成员只有一份,可以被共享!
    静态内部类中是否可以直接访问外部类的实例成员?不可以的,外部类的是成员必须用外部类对象访问!!
小结:
     静态内部类属于外部类本身,只会加载一次
     所以它的特点与外部类是完全一样的,只是位置在别人里面而已。
     
     对象的创建
       // 外部类名称.内部类名称 对象名称 = new 外部类名称.内部类构造器
        Outter.Inner in = new Outter.Inner();

实例内部类

    目标:内部类_实例内部类(成员内部类)(了解语法为主)

    什么是实例内部类:
        无static修饰的内部类,属于外部类的每个对象的,跟着对象一起加载的。

    实例内部类的成分特点:
        实例内部类中不能定义静态成员,其他都可以定义。
        可以定义常量。

    实例内部类的访问格式:
        外部类名称.内部类名称。

    创建对象的格式:
        外部类名称.内部类名称 对象名称 = new 外部类构造器.new 内部构造器;

    拓展:
        实例内部类中是否可以直接访问外部类的静态成员?可以的,外部类的静态成员可以被共享访问!
        实例内部类中是否可以访问外部类的实例成员?可以的,实例内部类属于外部类对象,可以直接访问当前外部类对象的实例成员!
    小结:
        实例内部类属于外部类对象,需要用外部类对象一起加载,
        实例内部类可以访问外部类的全部成员!
        
        对象的创建
          // 实例内部类属于外部类对象。实例内部类的宿主是外部类对象!!
        Outter.Inner in = new Outter().new Inner();

局部内部类

目标:局部内部类。(几乎不用)

定义在方法中,在构造器中,代码块中,for循环中定义的内部类
就是局部内部类。

局部内部类中的成分特点:
    只能定义实例成员,不能定义静态成员
    可以定义常量的。
小结:
    局部内部类没啥用。
    
    
public class InnerClass {

    static {
        abstract class A{

        }
    }

    public static void main(String[] args) {
        class A{
            private String name;

            public void test(){
            }
            public String getName() {
                return name;
            }

            public void setName(String name) {
                this.name = name;
            }
        }
        A a = new A();
        a.test();
    }

    public static void test(){
       class Animal{

       }

       class Cat extends Animal{

       }
    }
}
    

匿名内部类

目标:匿名内部类的概述、

什么是匿名内部类?
    就是一个没有名字的局部内部类。
    匿名内部类目的是为了:简化代码,也是开发中常用的形式。

匿名内部类的格式:
    new 类名|抽象类|接口(形参){
        方法重写。
    }
 匿名内部类的特点:
     1.匿名内部类是一个没有名字的内部类。
     2.匿名内部类一旦写出来,就会立即创建一个匿名内部类的对象返回。
     3.匿名内部类的对象的类型相当于是当前new的那个的类型的子类类型。
小结:
     1.匿名内部类是一个没有名字的内部类。
     2.匿名内部类一旦写出来,就会立即创建一个匿名内部类的对象返回。
     3.匿名内部类的对象的类型相当于是当前new的那个的类型的子类类型。
     
     
public class Anonymity02 {
    public static void main(String[] args) {
        Swim bozai = new Swim() {
            @Override
            public void swimming() {
                System.out.println("老师🏊‍的贼溜~~~~");
            }
        };
        go(bozai);

        Swim boniu = new Swim() {
            @Override
            public void swimming() {
                System.out.println("波妞学生快乐的狗爬式~~~");
            }
        };
        go(boniu);

        go(new Swim() {
            @Override
            public void swimming() {
                System.out.println("波妞学生快乐的狗爬式~~~");
            }
        });
    }

    // 提供一个方法让全部角色进入比赛
    public static void go(Swim s){
        System.out.println("开始。。。。");
        s.swimming();
        System.out.println("结束。。。。");
    }
}

interface Swim{
    void swimming();
}     

包:
    分门别类的管理各种不同的技术。
    企业的代码必须用包区分。便于管理技术,扩展技术,阅读技术。

定义包的格式:package 包名; 必须放在类名的最上面。
   一般工具已经帮我们做好了。

包名的命名规范:
    一般是公司域名的倒写+技术名称:
    http://www.itheima.com => com.itheima.技术名称
    包名建议全部用英文,多个单词用”.“连接,必须是合法标识符,不能用关键字

注意:
   相同包下的类可以直接访问。
   不同包下的类必须导包,才可以使用!
      导包格式:import 包名.类名;

权限修饰符

目标:权限修饰符。

权限修饰符:有四种(private -> 缺省 -> protected - > public )
可以修饰成员变量,修饰方法,修饰构造器,内部类,不同修饰符修饰的成员能够被访问的权限将受到限制!

四种修饰符的访问权限范围:
                    private    缺省       protected   public
    本类中           √           √          √           √
    本包下其他类中    X           √          √           √
    其他包下的类中    X           X          X           √
    其他包下的子类中  X           X          √           √

Object类

概述

包:java.lang.Object
Object类是Java中的祖宗类。
一个类要么默认继承了Object类,要么间接继承了Object类。
Object类的方法是一切子类都可以直接使用的,所以我们要学习Object类的方法。

toString方法

(1)public String toString():
        -- 默认是返回当前对象在堆内存中的地址信息:com.itheima._12Object类的详细使用.Student@735b478
        -- 默认的地址信息格式:类的全限名@内存地址
        -- 直接输出对象名称,默认会调用toString()方法,所以直接输出对象可以省略toString()不写。
        -- 实际开发中直接输出对象,输出对象的地址其实是没有意义的。
           所以toString方法存在的意义是为了被子类重写。
           以便能够返回对象的数据内容输出。因为实际开发中我们
            输出对象更多的时候是希望看到对象的数据内容信息。

        小结:开发中如果希望输出对象看到对象的内容,
             只需要重写toString()方法即可。
             所以toString方法存在的意义是为了被子类重写。

equals方法

(2)public boolean equals(Object o):
        -- 默认是比较两个对象的地址是否相同。相同返回true,反之。
        -- 直接比较两个对象的地址是否相同完全可以用“==”替代equals。
           所以equals存在的意义是为了被子类重写,以便程序员可以
            自己来定制比较规则。
        -- 需求:只要两个对象的内容一样,我们就认为他们是相等的。
    小结:
         equals存在的意义是为了被子类重写,以便程序员可以
         自己来定制比较规则。

Objects类的使用

Objects类与Object还是继承关系。
Objects类是从JDK 1.7开始之后才有的。

Objects的方法:
    1.public static boolean equals(Object a, Object b)
        -- 比较两个对象的。
        -- 底层进行非空判断,从而可以避免空指针异常。更安全!!推荐使用!!
         public static boolean equals(Object a, Object b) {
                 return a == b || a != null && a.equals(b);
         }
    2.public static boolean isNull(Object obj)
        -- 判断变量是否为null ,为null返回true ,反之!
        
        
public class ObjectsDemo {
    public static void main(String[] args) {
        Student s1 = null;
        Student s2 = new Student();
        System.out.println(Objects.equals(s1 , s2)); // 可以避免空指针异常。更安全!!
        // System.out.println(s1.equals(s2)); // 空指针异常

        // 询问s1是否为null 为null返回true ,反之!
        System.out.println(Objects.isNull(s1));
        System.out.println(s1 == null); // 可以直接用==判断也可以!
    }
}
        

日期类的使用

Date类的使用

Java是面向对象的,会用一个类代表一个事物。
Date类在Java中代表的是系统当前此刻日期时间对象。

Date类:
    包:java.util.Date。
    构造器:
        -- public Date():创建当前系统的此刻日期时间对象。
        -- public Date(long time):
    方法:
         -- public long getTime():返回自 1970 年 1 月 1 日 00:00:00 GMT 以来走过的总的毫秒数。

    时间记录的两种方式:
         a.Date日期对象。
         b.时间毫秒值:从1970-01-01 00:00:00开始走到此刻的总的毫秒值。 1s = 1000ms

    小结:
        Date可以代表系统当前此刻日期时间对象。
        时间记录的两种方式:
             Date日期对象。
             时间毫秒值:从1970-01-01 00:00:00开始走到此刻的总的毫秒值。 1s = 1000ms
      
      
public class DateDemo01 {
    public static void main(String[] args) {
        // a.创建一个日期对象代表了系统此刻日期时间对象
        Date d = new Date();
        System.out.println(d);

        // b.拿当前日期对象的时间毫秒值
        long time = d.getTime();
        System.out.println(time);
    }
}             
拓展:时间毫秒值的作用。时间毫秒值可以用于做时间的计算:例如代码的执行性能分析。public class DateDemo02 {    public static void main(String[] args) {        // 1.拿到此刻日期时间对象的毫秒值        long startTime = new Date().getTime();        for(int i = 1; i < 1000000 ; i++ ){            System.out.println("输出:"+i);        }        // 2.拿到此刻日期时间对象的毫秒值        long endTime = new Date().getTime();        System.out.println( (endTime - startTime) / 1000.0 +"s");    }}
目标:Date类的有参数构造器的使用。

构造器:
     -- public Date():创建当前系统的此刻日期时间对象。
     -- public Date(long time):把时间毫秒值转换成日期对象。
流程:
    Date日期对象 -> getTime() -> 时间毫秒值
    时间毫秒值 -> new Date(时间毫秒值) -> Date日期对象
小结:
    public Date(long time):把时间毫秒值转换成日期对象。
    
    
public class DateDemo03 {
    public static void main(String[] args) {
        // 需求:问121s以后的时间是多少。

        // 1.拿到此刻日期对象
        Date d = new Date();
        System.out.println(d);

        // 2.拿到此刻日期对象的时间毫秒值 往后走 121 s
        long time = d.getTime() + 121*1000;

        // 3.把时间毫秒值转换成日期对象。
        Date d1 = new Date(time);
        System.out.println(d1);
    }
}    

SimpleDateFormat类

简单日期格式化类的使用

目标:DateFormat简单日期格式化类的使用。

引入:
    我们之前得到的Date日期对象或者时间毫秒值的时间形式
    开发中并不喜欢,不符合有些时间格式的需求。

DateFormat作用:
    1.可以把“日期对象”或者“时间毫秒值”格式化成我们喜欢的时间形式。(格式化时间)
    2.可以把字符串的时间形式解析成日期对象。(解析字符串时间)

DateFormat是一个抽象类,不能直接使用,要找它的子类:SimpleDateFormat
我们需要用的是简单日期格式化类:SimpleDateFormat

SimpleDateFormat简单日期格式化类:
    包:java.text.SimpleDateFormat
    构造器:public SimpleDateFormat(String pattern):
           指定时间的格式创建简单日期格式化对象。
    方法:
        -- public String format(Date date):可以把日期对象格式化成我们喜欢的时间形式,返回的是字符串!
        -- public String format(Object time):可以把时间毫秒值格式化成我们喜欢的时间形式,返回的是字符串!
        -- public Date parse(String date) throws ParseException:把字符串的时间解析成日期对象
      

时间格式化成字符串

public class SimpleDateFormatDemo01 {
    public static void main(String[] args) {
        // 需求:把此刻日期对象格式化成我们喜欢的形式。
        // 1.得到此刻日期对象
        Date d = new Date();
        System.out.println(d);

        // 2.创建一个简单日期格式化对象负责格式化日期对象
        // 注意:参数是之间的格式。
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss EEE a");

        // 3.开始调用方法格式化时间得到格式化的字符串时间形式
        String rs = sdf.format(d);
        System.out.println(rs);
    }
}

格式化时间毫秒值

简单日期格式化类SimpleDateFormat可以直接格式化时间毫秒值
     public String format(Object time)
     

public class SimpleDateFormatDemo02 {
    public static void main(String[] args) {
        // 1.问121s后的时间是多少。格式化输出。
        // a.得到此刻日期对象
        Date date = new Date();
        System.out.println(date);

        // b.得到当前时间的时间毫秒值
        long time = date.getTime();
        time += 121 * 1000;

        // c.格式化时间毫秒值
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss EEE a");
        System.out.println(sdf.format(time));
    }
}

解析字符串时间成为日期对象

目标:简单日期格式化类SimpleDateFormat解析字符串时间成为日期对象。

    开发中经常会收到字符串的时间,需要转成Date日期对象。
    "2018-10-11 10:10:22"

    简单日期格式化对象不仅可以把日期对象和时间毫秒值格式化成我们喜欢的字符串时间形式
    它还可以把 把字符串的时间解析成日期对象
         -- public Date parse(String date) throws ParseException:把字符串的时间解析成日期对象
         
         
public class SimpleDateFormatDemo03 {
    public static void main(String[] args) throws ParseException {
        // 面试题:请问 “2019-11-04 09:30:30” 往后 1天15小时,30分29s后的时间是多少
        // a.定义一个字符串时间
        String date = "2019-11-04 09:30:30";

        // b.把字符串的时间解析成Date日期对象 。(重点)
        // 1.创建一个简单日期格式化对象负责解析字符串的时间成为日期对象
        // 注意:参数必须与被解析的时间的格式完全一致,否则执行报错!!
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        // 2.开始解析成日期对象
        Date newDate = sdf.parse(date);

        // c.得到日期对象的时间毫秒值 + 往后走 1天15小时,30分29s
        long time = newDate.getTime() + (24L *60*60 + 15*60*60 + 30*60 + 29) * 1000;

        // d.把时间毫秒值格式化成喜欢的字符串的时间形式!
        System.out.println(sdf.format(time));
    }
}         

日历类Calendar的使用

目标:日历类Calendar的使用。

Calendar代表了系统此刻日期对应的日历对象。
Calendar是一个抽象类,不能直接创建对象。
Calendar日历类创建日历对象的语法:
    Calendar rightNow = Calendar.getInstance();
Calendar的方法:
    1.public static Calendar getInstance(): 返回一个日历类的对象。
    2.public int get(int field):取日期中的某个字段信息。
    3.public void set(int field,int value):修改日历的某个字段信息。
    4.public void add(int field,int amount):为某个字段增加/减少指定的值
    5.public final Date getTime(): 拿到此刻日期对象。
    6.public long getTimeInMillis(): 拿到此刻时间毫秒值
    
    
public class CalendarDemo01 {
    public static void main(String[] args) {
        // 1.通过调用日历类的静态方法getInstance得到一个当前此刻日期对象对应的日历对象。
        Calendar rightNow = Calendar.getInstance();
        System.out.println(rightNow);

        // 2.获取年:
        int year = rightNow.get(Calendar.YEAR);
        System.out.println(year);

        int mm = rightNow.get(Calendar.MONTH) + 1;
        System.out.println(mm);

        // 3.一年中的第几天: 308
        int days = rightNow.get(Calendar.DAY_OF_YEAR);
        System.out.println(days);

        // 4.修改日历的信息
        //rightNow.set(Calendar.YEAR , 2099);
        //System.out.println(rightNow.get(Calendar.YEAR));

        // 5.日历可以得到此刻日期对象。
        Date d = rightNow.getTime();
        System.out.println(d);

        // 6.此刻时间毫秒值
        long time = rightNow.getTimeInMillis();
        System.out.println(time);

        // 7.请问701天  15小时后是哪个日期
        // 让日历的一年中的第几天往后走 701天!
        rightNow.add(Calendar.DAY_OF_YEAR , 701);
        rightNow.add(Calendar.HOUR , 15);
        long time1 = rightNow.getTimeInMillis();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss EEE a");
        System.out.println(sdf.format(time1));
    }
}    

Math类的使用

目标:Math类的使用。

Math用于做数学运算。
Math类中的方法全部是静态方法,直接用类名调用即可。
方法:
      方法名                                        说明                
      public static int abs(int a)                  获取参数a的绝对值:
      public static double ceil(double a)           向上取整
      public static double floor(double a)          向下取整
      public static double pow(double a, double b)  获取a的b次幂        
      public static long round(double a)            四舍五入取整

System系统类的使用

目标:System系统类的使用。
System代表当前系统。
静态方法:
    1.public static void exit(int status):终止JVM虚拟机,非0是异常终止。
    2.public static long currentTimeMillis():获取当前系统此刻时间毫秒值。
    3.可以做数组的拷贝。
      arraycopy(Object var0, int var1, Object var2, int var3, int var4);
     * 参数一:原数组
     * 参数二:从原数组的哪个位置开始赋值。
     * 参数三:目标数组
     * 参数四:赋值到目标数组的哪个位置
     * 参数五:赋值几个。
     
     
public class SystemDemo {
    public static void main(String[] args) {
        System.out.println("程序开始。。。");

         // 1.终止当前虚拟机
        //System.exit(0); // 0代表正常终止!!

        // 2.得到系统当前时间毫秒值
        long time = System.currentTimeMillis();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        System.out.println(sdf.format(time));

        // 3.可以做数组的拷贝(了解)
        int[] arrs1 = new int[]{10 ,20 ,30 ,40 ,50 ,60 ,70};
        int[] arrs2 = new int[6]; // [ 0 , 0 , 0 , 0 , 0 , 0]
        // arrs2 = [0 , 30 , 40 , 50 , 0 , 0 ]
        /**
         arraycopy(Object src,int  srcPos ,Object dest, int destPos,  int length)
         参数一:原数组
         参数二:从哪个索引位置开始赋值
         参数三:目标数组
         参数四:目标数组的开始索引:
         参数五:复制几个
         */
        System.arraycopy(arrs1,2, arrs2 , 1 , 3);
        System.out.println(Arrays.toString(arrs2));
        System.out.println("程序结束。。。");
    }
}     

BigDecimal大数据类的使用

目标:BigDecimal大数据类。

引入:
    浮点型运算的时候直接+  * / 可能会出现数据失真(精度问题)。
    BigDecimal可以解决浮点型运算数据失真的问题。

BigDicimal类:
    包:java.math.
    创建对象的方式(最好的方式:)
          public static BigDecimal valueOf(double val) :包装浮点数成为大数据对象。
    方法声明
          public BigDecimal add(BigDecimal value)       加法运算
          public BigDecimal subtract(BigDecimal value)  减法运算 
          public BigDecimal multiply(BigDecimal value)  乘法运算 
          public BigDecimal divide(BigDecimal value)    除法运算
          public double doubleValue():把BigDecimal转换成double类型。
          
          
public class BigDecimalDemo {
    public static void main(String[] args) {
        // 浮点型运算的时候直接+  * / 可能会出现数据失真(精度问题)。
        System.out.println(0.1 + 0.2);
        System.out.println(0.09 + 0.01);
        System.out.println(1.0  - 0.32);
        System.out.println(1.015 * 100);
        System.out.println(1.301 / 100);

        System.out.println("-------------------------");
        double a = 0.1 ;
        double b = 0.2 ;
        double c = a + b ;
        System.out.println(c);

        // 1.把浮点数转换成大数据对象运算
        BigDecimal a1 = BigDecimal.valueOf(a);
        BigDecimal b1 = BigDecimal.valueOf(b);
        //BigDecimal c1 = a1.add(b1);  // 加法
        BigDecimal c1 = a1.divide(b1); // 除法
        System.out.println(c1);

        // 结果可能需要继续使用!!!
        // BigDecimal只是解决精度问题的手段,double数据才是我们的目的!!
        double rs = c1.doubleValue();
        System.out.println(rs);
    }
}          

包装类

概述

目标:包装类。

引入:
    Java认为一切皆对象。引用数据类型就是对象了。
    但是在Java中8基本数据类型不是对象,只是表示一种数据的类型形式,这8种数据类型显得很突兀。
    Java为了一切皆对象的思想统一,把8种基本数据类型转换成对应的类,这个类称为
    基本数据类型的包装类。

基本数据类型                    包装类(引用数据类型)
     byte                      Byte
     short                     Short
     int                       Integer(特殊)
     long                      Long

     float                     Float
     double                    Double
     char                      Character(特殊)
     boolean                   Boolean

自动装箱:可以直接把基本数据类型的值或者变量赋值给包装类。
自动拆箱:可以把包装类的变量直接赋值给基本数据类型。

小结:
     自动装箱:可以直接把基本数据类型的值或者变量赋值给包装类。
     自动拆箱:可以把包装类的变量直接赋值给基本数据类型。

和字符串的相互转化

目标:包装类的特殊功能。

Java为包装类做了一些特殊功能,以便程序员使用。
包装类作为类首先拥有了Object类的方法。
包装类作为引用类型的变量可以存储null值。

具体来看特殊功能主要有:
    1.可以把基本数据类型的值转换成字符串类型的值。(没啥用)
        -- 调用toString()方法。
        -- 调用Integer.toString(基本数据类型的值)得到字符串。
        -- 直接把基本数据类型+空字符串就得到了字符串。

    2.把字符串类型的数值转换成对应的基本数据类型的值。(真的很有用)
        -- Xxx.parseXxx("字符串类型的数值")
        -- Xxx.valueOf("字符串类型的数值"):推荐使用!
小结:
    包装类可以把字符串类型的数值转换成对应的基本数据类型的值(真的很有用)
    
    
public class PackageClass02 {
    public static void main(String[] args) {
        // 1.把基本数据类型的值转成字符串
        Integer it = 100 ;
        // a.调用toString()方法。
        String itStr = it.toString();
        System.out.println(itStr+1);
        // b.调用Integer.toString(基本数据类型的值)得到字符串。
        String itStr1 = Integer.toString(it);
        System.out.println(itStr1+1);
        // c.直接把基本数据类型+空字符串就得到了字符串。
        String itStr2 = it+"";
        System.out.println(itStr2+1);

        // 2.把字符串类型的数值转换成对应的基本数据类型的值。(真的很有用)
        String numStr = "23";
        //int numInt = Integer.parseInt(numStr);
        int numInt = Integer.valueOf(numStr);
        System.out.println(numInt+1);

        String doubleStr = "99.9";
        //double doubleDb = Double.parseDouble(doubleStr);
        double doubleDb = Double.valueOf(doubleStr);
        System.out.println(doubleDb+0.1);
    }
}    

正则表达式

概述

目标:正则表达式的演示。

正则表达式的作用:
    是一些特殊字符组成的校验规则,可以校验信息的正确性,校验邮箱是否合法
    电话号码,金额等。

需求:演示不用正则表达式和用正则表达式校验QQ号码。

小结:
    正则表达式很适合做校验,代码简单,优雅!
    
 public static void main(String[] args) {
        System.out.println(checkQQ("42422332"));
        System.out.println(chechQQRegex("42422d332"));
    }
    // 用正则表达式
    public static boolean chechQQRegex(String qq){
        return qq!=null && qq.matches("\\d{4,}");
    }    

基本语法

目标:深入学习正则表达式的写法。

字符类
     [abc] a、b 或 c(简单类)
     [^abc] 任何字符,除了 a、b 或 c(否定)
     [a-zA-Z] a 到 z 或 A 到 Z,两头的字母包括在内(范围)
     [a-d[m-p]] a 到 d 或 m 到 p:[a-dm-p](并集)
     [a-z&&[def23]] d、e 或 f(交集)
     [a-z&&[^bc]] a 到 z,除了 b 和 c:[ad-z](减去)
     [a-z&&[^m-p]] a 到 z,而非 m 到 p:[a-lq-z](减去)

 预定义字符类
     . 任何字符
     \d 数字:[0-9]
     \D 非数字: [^0-9]
     \s 空白字符:[ \t\n\x0B\f\r]
     \S 非空白字符:[^\s]
     \w 单词字符:[a-zA-Z_0-9]
     \W 非单词字符:[^\w]

以上正则匹配只能校验单个字符。

Greedy 数量词
     X? X,一次或一次也没有
     X* X,零次或多次
     X+ X,一次或多次
     X{n} X,恰好 n 次
     X{n,} X,至少 n 次
     X{n,m} X,至少 n 次,但是不超过 m 次
     
     
public class RegexDemo02 {
    public static void main(String[] args) {
        //public boolean matches(String regex):判断是否与正则表达式匹配,匹配返回true
        // 只能是 a  b  c
        System.out.println("a".matches("[abc]")); // true
        System.out.println("z".matches("[abc]")); // false

        // 不能出现a  b  c
        System.out.println("a".matches("[^abc]")); // false
        System.out.println("z".matches("[^abc]")); // true

        System.out.println("a".matches("\\d")); // false
        System.out.println("3".matches("\\d")); // true
        System.out.println("333".matches("\\d")); // false
        System.out.println("z".matches("\\w")); // true
        System.out.println("2".matches("\\w")); // true
        System.out.println("21".matches("\\w")); // false
        System.out.println("你".matches("\\w")); // false
        //  以上正则匹配只能校验单个字符。

        // 校验密码
        // 必须是数字 字母 下划线 至少 6位
        System.out.println("ssds3c".matches("\\w{6,}")); // true
        System.out.println("ssdsc".matches("\\w{6,}")); // false
        System.out.println("ssdsda232s你c".matches("\\w{6,}")); // false

        // 验证。必须是数字和字符  必须是4位
        System.out.println("dsd2".matches("[a-zA-Z0-9]{4}")); // true
        System.out.println("A3dy".matches("[a-zA-Z0-9]{4}")); // true
        System.out.println("A3dy2".matches("[a-zA-Z0-9]{4}")); // false

    }
}     

在方法中的引用

目标:正则表达式在方法中的应用。
    public String[] split(String regex):
        -- 按照正则表达式匹配的内容进行分割字符串,反回一个字符串数组。
    public String replaceAll(String regex,String newStr)
        -- 按照正则表达式匹配的内容进行替换
        
public class RegexDemo04 {
    public static void main(String[] args) {
        // 1.split的基础用法
        String names = "贾乃亮,王宝强,陈羽凡";
        // 以“,”分割成字符串数组
        String[] nameArrs = names.split(",");
        for(int i = 0 ; i < nameArrs.length ; i++ ){
            String name = nameArrs[i];
            System.out.println(name);
        }

        System.out.println("----------------------");
        // 2.split集合正则表达式做分割
        String names1 = "贾乃亮lv434fda324王宝强87632fad2342423陈羽凡";
        // 以匹配正则表达式的内容为分割点分割成字符串数组
        String[] nameArrs1 = names1.split("\\w+");
        for(int i = 0 ; i < nameArrs1.length ; i++ ){
            String name = nameArrs1[i];
            System.out.println(name);
        }
        System.out.println("----------------------");
        // 3. public String replaceAll(String regex,String newStr)
        String names2 = "贾乃亮lv434fda324王宝强87632fad2342423陈羽凡";
        // 使用正则表达式定位出内容,替换成/
        System.out.println(names2.replaceAll("\\w+" , "/"));

        String names3 = "贾乃亮,王宝强,羽凡";
        System.out.println(names3.replaceAll(",","-"));
    }
}        

泛型

概述

目标:泛型的概述。

什么是泛型?
    泛型就是一个标签:<数据类型>
    泛型可以在编译阶段约束只能操作某种数据类型。
注意:JDK 1.7开始之后,泛型后面的申明可以省略不写!!
     泛型和集合都只能支持引用数据类型,不支持基本数据类型。

小结:
    泛型可以在编译阶段约束只能操作某种数据类型。
    泛型和集合都只能支持引用数据类型,不支持基本数据类型。
    JDK 1.7开始之后,泛型后面的申明可以省略不写!!

好处

目标:泛型的好处。
泛型在编译阶段约束了操作的数据类型,从而不会出现类型转换异常。
体现的是Java的严谨性和规范性,数据类型,经常需要进行统一!

自定义泛型类

目标:自定义泛型类。

引入:
    我们之前用的泛型都是别人写好的,接下来我们来自定义泛型类使用。

泛型类的概念:
    使用了泛型定义的类就是泛型类。

泛型类的格式:
    修饰符 class 类名<泛型变量>{

    }
    泛型变量建议使用 E , T , K , V

需求:模拟ArrayList集合自定义一个集合MyArrayList集合。
泛型类的核心思想:是把出现泛型变量的地方全部替换成传输的真实数据类型。

小结:
    自定义泛型的核心思想:是把出现泛型变量的地方全部替换成传输的真实数据类型。
    
public class GenericDemo {
    public static void main(String[] args) {
        MyArrayList<String> lists = new MyArrayList<String>();

        MyArrayList<String> lists1 = new MyArrayList<>();
        lists1.add("java");
        lists1.add("mysql");
        lists1.remove("java");
        System.out.println(lists1);
    }
}

class MyArrayList<E>{

    private ArrayList lists = new ArrayList();

    public void add(E e){
        lists.add(e);
    }

    public void remove(E e){
        lists.remove(e);
    }
    @Override
    public String toString() {
        return lists.toString();
    }
}    

自定义泛型方法

目标:自定义泛型方法。

什么是泛型方法?
    定义了泛型的方法就是泛型方法。
泛型方法的定义格式:
    修饰符 <泛型变量> 返回值类型 方法名称(形参列表){

    }
    注意:方法定义了是什么泛型变量,后面就只能用什么泛型变量。
    泛型类的核心思想:是把出现泛型变量的地方全部替换成传输的真实数据类型。

需求:给你任何一个类型的数组,都能返回它的内容。

小结:
    泛型方法和泛型类可以做通用技术架构。
    
    
public class GenericDemo {
    public static void main(String[] args) {
        Integer[] nums = {10 , 20 , 30 , 40 , 50};
        String rs1  = arrToString(nums);
        System.out.println(rs1);

        String[] names = {"贾乃亮","王宝绿","陈羽凡"};
        String rs2  = arrToString(names);
        System.out.println(rs2);
    }

    public static <T> String arrToString(T[] nums){
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        if(nums!=null && nums.length > 0){
            for(int i = 0 ; i < nums.length ; i++ ){
                T ele = nums[i];
                sb.append(i == nums.length-1 ? ele : ele+", ");
            }
        }
        sb.append("]");
        return sb.toString();
    }
}    

泛型接口

目标:泛型接口。

什么是泛型接口?
    使用了泛型定义的接口就是泛型接口。
泛型接口的格式:
    修饰符 interface 接口名称<泛型变量>{

    }

需求:教务系统,提供一个接口可约束一定要完成数据(学生,老师)的增删改查操作

小结:
    泛型接口的核心思想,在实现接口的时候传入真实的数据类型
    这样重写的方法就是对该数据类型进行操作!
    
// 泛型接口
public interface Data<E> {
    void add(E stu);
    void delete(E stu);
    void update(E stu);
    E query(int id);
}


// 操作学生数据
public class StudentData implements Data<Student> {
    @Override
    public void add(Student stu) {
        System.out.println("添加学生!");
    }

    @Override
    public void delete(Student stu) {
        System.out.println("删除学生!");
    }

    @Override
    public void update(Student stu) {

    }
    @Override
    public Student query(int id) {
        return null;
    }
}


public class TeacherData implements Data<Teacher> {
    @Override
    public void add(Teacher stu) {
        System.out.println("添加老师!");
    }

    @Override
    public void delete(Teacher stu) {
        System.out.println("删除老师!");
    }

    @Override
    public void update(Teacher stu) {

    }
    @Override
    public Teacher query(int id) {
        return null;
    }
}

泛型的通配符

目标:泛型通配符。

需求:开发一个极品飞车的游戏,所有的汽车都能一起参与比赛。

注意:
    虽然BMW和BENZ都继承了Car
    但是ArrayList<BMW>和ArrayList<BENZ>与ArrayList<Car>没有关系的!泛型没有继承关系!

通配符:?
    ?可以用在使用泛型的时候代表一切类型。
    E , T , K , V是在定义泛型的时候使用代表一切类型。

泛型的上下限:
    ? extends Car : 那么?必须是Car或者其子类。(泛型的上限)
    ? super  Car :那么?必须是Car或者其父类。(泛型的下限。不是很常见)
小结:
    通配符:?可以用在使用泛型的时候代表一切类型。
    ? extends Car : 那么?必须是Car或者其子类。(泛型的上限)

集合

Collection集合

collection集合概述

目标:Collection集合概述。

什么是集合?
    集合是一个大小可变的容器。
    容器中的每个数据称为一个元素。数据==元素。
    集合的特点是:类型可以不确定,大小不固定。集合有很多种,不同的集合特点和使用场景不同。
    数组:类型和长度一旦定义出来就都固定了。

集合有啥用?
    在开发中,很多时候元素的个数是不确定的。
    而且经常要进行元素的增删该查操作,集合都是非常合适的。
    开发中集合用的更多!!

Java中集合的代表是:Collection.
Collection集合是Java中集合的祖宗类。
学习Collection集合的功能,那么一切集合都可以用这些功能!!

Collection集合的体系:
                        Collection<E>(接口)
                  /                                \
             Set<E>(接口)                            List<E>(接口)
            /               \                       /                \
         HashSet<E>(实现类)  TreeSet<>(实现类)     ArrayList<E>(实现类)  LinekdList<>(实现类)
         /
     LinkedHashSet<>(实现类)

集合的特点:
      Set系列集合:添加的元素是无序,不重复,无索引的。
            -- HashSet:添加的元素是无序,不重复,无索引的。
            -- LinkedHashSet:添加的元素是有序,不重复,无索引的。
            -- TreeSet:不重复,无索引,按照大小默认升序排序!!
      List系列集合:添加的元素是有序,可重复,有索引。
            -- ArrayList:添加的元素是有序,可重复,有索引。
            -- LinekdList:添加的元素是有序,可重复,有索引。
             -- Vector 是线程安全的,速度慢,工作中很少使用。
小结:
    Collection是集合的祖宗类,Collection集合的功能是一切集合都可以直接使用的。

collection集合的常用api

目标:Collection集合的常用API.

Collection是集合的祖宗类,它的功能是全部集合都可以继承使用的,所以要学习它。
Collection API如下:
     - public boolean add(E e):  把给定的对象添加到当前集合中 。
     - public void clear() :清空集合中所有的元素。
     - public boolean remove(E e): 把给定的对象在当前集合中删除。
     - public boolean contains(Object obj): 判断当前集合中是否包含给定的对象。
     - public boolean isEmpty(): 判断当前集合是否为空。
     - public int size(): 返回集合中元素的个数。
     - public Object[] toArray(): 把集合中的元素,存储到数组中

collection集合的遍历方式

概述
目标:Collection集合的遍历方式。

什么是遍历? 为什么开发中要遍历?
遍历就是一个一个的把容器中的元素访问一遍。
开发中经常要统计元素的总和,找最值,找出某个数据然后干掉等等业务都需要遍历。

Collection集合的遍历方式是全部集合都可以直接使用的,所以我们学习它。
Collection集合的遍历方式有三种:
    (1)迭代器。
    (2)foreach(增强for循环)。
    (3)JDK 1.8开始之后的新技术Lambda表达式。
迭代器遍历
a.迭代器遍历集合。
    -- 方法:
        public Iterator iterator(): 获取集合对应的迭代器,用来遍历集合中的元素的
        E next():获取下一个元素值!
        boolean hasNext():判断是否有下一个元素,有返回true ,反之。
    --流程:
        1.先获取当前集合的迭代器
            Iterator<String> it = lists.iterator();
        2.定义一个while循环,问一次取一次。
          通过it.hasNext()询问是否有下一个元素,有就通过
          it.next()取出下一个元素。
          
          
public class CollectionDemo01 {
    public static void main(String[] args) {
        Collection<String> lists = new ArrayList<>();
        lists.add("赵敏");
        lists.add("小昭");
        lists.add("殷素素");
        
        // 2.使用while循环遍历。
        while(it.hasNext()){
            String ele = it.next();
            System.out.println(ele);
        }

    }
}          
foreach循环遍历
b.foreach(增强for循环)遍历集合。
    foreach是一种遍历形式,可以遍历集合或者数组。
    foreach遍历集合实际上是迭代器遍历的简化写法。
    foreach遍历的关键是记住格式:
       for(被遍历集合或者数组中元素的类型 变量名称 : 被遍历集合或者数组){

       }
   小结:
       foreach遍历集合或者数组很方便。
       缺点:foreach遍历无法知道遍历到了哪个元素了,因为没有索引。
       
public class CollectionDemo02 {
    public static void main(String[] args) {
        Collection<String> lists = new ArrayList<>();
        lists.add("赵敏");
        lists.add("小昭");
        lists.add("殷素素");
        lists.add("周芷若");
      
        for (String ele : lists) {
            System.out.println(ele);
        }

        int[] ages = new int[]{17 , 18 , 38 , 21};
        for (int age : ages) {
            System.out.println(age);
        }
    }
}       
lambda表达式遍历
lists.forEach(s ->  System.out.println(s));
lists.forEach(System.out::println);

常见的数据结构

目标:常见的数据结构种类。

集合是基于数据结构做出来的,不同的集合底层会采用不同的数据结构。
不同的数据结构,功能和作用是不一样的。

什么是数据结构?
    数据结构指的是数据以什么方式组织在一起。
    不同的数据结构,增删查的性能是不一样的。
    不同的集合底层会采用不同的数据结构,我们要知道集合的底层是基于哪种数据结构存储和操作数据的。
        这样才能知道具体场景用哪种集合。

Java常见的数据结构有哪些?
数据存储的常用结构有:栈、队列、数组、链表和红黑树
    a.队列(queue)
       -- 先进先出,后进后出。
       -- 场景:各种排队。叫号系统。
       -- 有很多集合可以实现队列。

    b.栈(stack)
       -- 后进先出,先进后出
       -- 压栈 == 入栈
       -- 弹栈 == 出栈
       -- 场景:手枪的弹夹。


    c.数组
       -- 数组是内存中的连续存储区域。
       -- 分成若干等分的小区域(每个区域大小是一样的)
       -- 元素存在索引
       -- 特点:查询元素快(根据索引快速计算出元素的地址,然后立即去定位)
               增删元素慢(创建新数组,迁移元素)

    d.链表
       -- 元素不是内存中的连续区域存储。
       -- 元素是游离存储的。每个元素会记录下个元素的地址。
       -- 特点:查询元素慢
               增删元素快(针对于首尾元素,速度极快,一般是双链表)

    e.红黑树
         二叉树:binary tree 永远只有一个根节点,是每个结点不超过2个节点的树(tree) 。
         查找二叉树,排序二叉树:小的左边,大的右边,但是可能树很高,性能变差。
         为了做排序和搜索会进行左旋和右旋实现平衡查找二叉树,让树的高度差不大于1
         红黑树(就是基于红黑规则实现了自平衡的排序二叉树):
            树尽量的保证到了很矮小,但是又排好序了,性能最高的树。

         红黑树的增删查改性能都好!!!

         这些结构,其实Java早就通过代码实现了,我们要知道有这些结构即可!

List系列集合的使用

概述

   List集合继承了Collection集合的全部功能,同时因为List系列集合有索引,
   因为List集合多了索引,所以多了很多按照索引操作元素的功能:
   ArrayList实现类集合底层基于数组存储数据的,查询快,增删慢!
       - public void add(int index, E element): 将指定的元素,添加到该集合中的指定位置上。
       - public E get(int index):返回集合中指定位置的元素。
       - public E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素。
       - public E set(int index, E element):用指定元素替换集合中指定位置的元素,返回更新前的元素值。
  小结:
      List系列集合有序,可重复,有索引的。
      ArrayList实现类集合底层基于数组存储数据的,查询快,增删慢!!
      开发中ArrayList集合用的最多!!
      
public class ListDemo01 {
    public static void main(String[] args) {
        // 1.创建一个ArrayList集合对象:这是一行经典代码!
        // List:有序,可重复,有索引的。
        List<String> lists = new ArrayList<>();
        lists.add("java1");
        lists.add("java1");
        lists.add("java2");
        lists.add("java2");
        System.out.println(lists);

        // 2.在某个索引位置插入元素。
        lists.add(2,"MySQL");
        System.out.println(lists);

        // 3.根据索引删除元素,返回被删除元素
        System.out.println(lists.remove(2));
        System.out.println(lists);

        // 4.根据索引获取元素
        System.out.println(lists.get(2)); // java2

        // 5.修改索引位置处的元素
        lists.set(3,"Mybatis");
        System.out.println(lists);
    }
}      

List系列集合的遍历

拓展:List系列集合的遍历方式有:4种。

List系列集合多了索引,所以多了一种按照索引遍历集合的for循环。

List遍历方式:
    (1)for循环。
    (2)迭代器。
    (3)foreach。
    (4)JDK 1.8新技术。
    
  /** (1)for循环。 */
        for(int i = 0 ; i < lists.size() ; i++ ) {
            String ele = lists.get(i);
            System.out.println(ele);
        }    

LinkedList常用API

 LinkedList也是List的实现类:底层是基于链表的,增删比较快,查询慢!!
 LinkedList是支持双链表,定位前后的元素是非常快的,增删首尾的元素也是最快的
 所以LinkedList除了拥有List集合的全部功能还多了很多操作首尾元素的特殊功能:
     - public void addFirst(E e):将指定元素插入此列表的开头。
     - public void addLast(E e):将指定元素添加到此列表的结尾。
     - public E getFirst():返回此列表的第一个元素。
     - public E getLast():返回此列表的最后一个元素。
     - public E removeFirst():移除并返回此列表的第一个元素。
     - public E removeLast():移除并返回此列表的最后一个元素。
     - public E pop():从此列表所表示的堆栈处弹出一个元素。
     - public void push(E e):将元素推入此列表所表示的堆栈。

小结:
     LinkedList是支持双链表,定位前后的元素是非常快的,增删首尾的元素也是最快的。
     所以提供了很多操作首尾元素的特殊API可以做栈和队列的实现。

     如果查询多而增删少用ArrayList集合。(用的最多的)
     如果查询少而增删首尾较多用LinkedList集合。
     
     
public class ListDemo03 {
    public static void main(String[] args) {
        // 1.用LinkedList做一个队列:先进先出,后进后出。
        LinkedList<String> queue = new LinkedList<>();
        // 入队
        queue.addLast("1号");
        queue.addLast("2号");
        queue.addLast("3号");
        queue.addLast("4号");
        System.out.println(queue); // [1号, 2号, 3号, 4号]
        // 出队
        System.out.println(queue.removeFirst());
        System.out.println(queue.removeFirst());
        System.out.println(queue);

        // 做一个栈
        LinkedList<String> stack = new LinkedList<>();
        // 压栈
        stack.push("第1颗子弹");
        stack.push("第2颗子弹");
        stack.push("第3颗子弹");
        stack.push("第4颗子弹");
        System.out.println(stack); // [第4颗子弹, 第3颗子弹, 第2颗子弹, 第1颗子弹]
        // 弹栈
        System.out.println(stack.pop());
        System.out.println(stack.pop());
        System.out.println(stack);


    }
}     

set系列集合

set集合无序的根本原因

目标:Set系列集合元素无序的根本原因。(面试必考)

Set系列集合添加元素无序的根本原因是因为底层采用了哈希表存储元素。

JDK 1.8之前:哈希表 = 数组 + 链表  + (哈希算法)
JDK 1.8之后:哈希表 = 数组 + 链表 + 红黑树  + (哈希算法)
    当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。

小结:
    Set系列集合是基于哈希表存储数据的
    它的增删改查的性能都很好!!但是它是无序不重复的!如果不在意当然可以使用!
    
    
public class HashSetDemo03 {
    public static void main(String[] args) {
        Set<String> sets = new HashSet<>(); // 一行经典代码!!
        sets.add("Java");
        sets.add("Java");
        sets.add("Mybatis");
        sets.add("MySQL");
        sets.add("MySQL");
        sets.add("Spring");
        // [Java, MySQL, Spring, Mybatis]
        System.out.println(sets);
    }
}    

应用数据类型去重流程

目标:Set系列集合元素去重复的流程。

集合和泛型都只能支持引用数据类型。

1.对于有值特性的,Set集合可以直接判断进行去重复。
2.对于引用数据类型的类对象,Set集合是按照如下流程进行是否重复的判断。
    Set集合会让两两对象,先调用自己的hashCode()方法得到彼此的哈希值(所谓的内存地址)
    然后比较两个对象的哈希值是否相同,如果不相同则直接认为两个对象不重复。
    如果哈希值相同,会继续让两个对象进行equals比较内容是否相同,如果相同认为真的重复了
    如果不相同认为不重复。

            Set集合会先让对象调用hashCode()方法获取两个对象的哈希值比较  哈希值,相当于是内存地址
               /                     \
            false                    true
            /                          \
        不重复                        继续让两个对象进行equals比较
                                       /          \
                                     false        true
                                      /             \
                                    不重复          重复了

    需求:只要对象内容一样,就希望集合认为它们重复了。重写hashCode和equals方法。

小结:
    如果希望Set集合认为两个对象只要内容一样就重复了,必须重写对象的hashCode和equals方法。

linkedHashSet

目标:LinkedHashSet

是HashSet的子类,元素是“有序” 不重复,无索引.

LinkedHashSet底层依然是使用哈希表存储元素的,
但是每个元素都额外带一个链来维护添加顺序!!
不光增删查快,还有序。缺点是多了一个存储顺序的链会占内存空间!!而且不允许重复,无索引。

TreeSet

目标:TreeSet集合。

TreeSet: 不重复,无索引,按照大小默认升序排序!!
TreeSet集合称为排序不重复集合,可以对元素进行默认的升序排序。

TreeSet集合自自排序的方式:
    1.有值特性的元素直接可以升序排序。(浮点型,整型)
    2.字符串类型的元素会按照首字符的编号排序。
    3.对于自定义的引用数据类型,TreeSet默认无法排序,执行的时候直接报错,因为人家不知道排序规则。

自定义的引用数据类型的排序实现:
    对于自定义的引用数据类型,TreeSet默认无法排序
    所以我们需要定制排序的大小规则,程序员定义大小规则的方案有2种:
    a.直接为对象的类实现比较器规则接口Comparable,重写比较方法(拓展方式)
         // 如果程序员认为比较者大于被比较者 返回正数!
         // 如果程序员认为比较者小于被比较者 返回负数!
         // 如果程序员认为比较者等于被比较者 返回0!

    b.直接为集合设置比较器Comparator对象,重写比较方法
         // 如果程序员认为比较者大于被比较者 返回正数!
         // 如果程序员认为比较者小于被比较者 返回负数!
         // 如果程序员认为比较者等于被比较者 返回0!
    注意:如果类和集合都带有比较规则,优先使用集合自带的比较规则。

小结:
    TreeSet集合对自定义引用数据类型排序,默认无法进行。
    但是有两种方式可以让程序员定义大小规则:
         a.直接为对象的类实现比较器规则接口Comparable,重写比较方法(拓展方式)
         b.直接为集合设置比较器Comparator对象,重写比较方法
    注意:如果类和集合都带有比较规则,优先使用集合自带的比较规则。

TreeSet集合对自定义引用数据类型排序代码

1.直接为对象的类实现比较器规则接口Comparable,重写比较方法public class Employee implements Comparable<Employee>{    private String name;    private double salary;    private int age;    // 重写了比较方法。    // e1.compareTo(o)    // 比较者:this    // 被比较者:o    // 需求:按照年龄比较    @Override    public int compareTo(Employee o) {        // 规则:Java规则        // 如果程序员认为比较者大于被比较者 返回正数!        // 如果程序员认为比较者小于被比较者 返回负数!        // 如果程序员认为比较者等于被比较者 返回0!        return this.age - o.age;    }}12.直接为集合设置比较器Comparator对象,重写比较方法public class TreeSetDemo05 {    public static void main(String[] args) {        // TreeSet : 排序不重复集合。        Set<Double> scores = new TreeSet<>();        scores.add(100.0);        scores.add(99.9);        scores.add(69.5);        scores.add(0.1);        scores.add(89.3);        System.out.println(scores);        // 字符串按照首字符的编号进行排序。        Set<String> names = new TreeSet<>();        names.add("Jack");        names.add("rose");        names.add("Dlei");        names.add("about");        names.add("曹雪芹");        names.add("bozai");        names.add("caocao");        names.add("angel");        System.out.println(names);        // public TreeSet(Comparator<? super E> comparator)        // 集合自带比较器对象        // 如果类和集合都存在大小规则,默认使用集合自带的规则进行大小排序!!        Set<Employee> employees1 = new TreeSet<>(new Comparator<Employee>() {            @Override            public int compare(Employee o1, Employee o2) {                // o1比较者   o2被比较者                // 如果程序员认为比较者大于被比较者 返回正数!                // 如果程序员认为比较者小于被比较者 返回负数!                // 如果程序员认为比较者等于被比较者 返回0!                return o1.getAge() - o2.getAge();            }        });        employees1.add(new Employee("播仔",6500.0,21));        employees1.add(new Employee("播妞",7500.0,19));        employees1.add(new Employee("乔治",4500.0,23));        System.out.println(employees1);    }}

总结:
如果希望元素可以重复,又有索引,查询要快用ArrayList集合。(用的最多)
如果希望元素可以重复,又有索引,增删要快要用LinkedList集合。(适合查询元素比较少的情况,经常要首尾操作元素的情况)
如果希望增删改查都很快,但是元素不重复以及无序无索引,那么用HashSet集合。
如果希望增删改查都很快且有序,但是元素不重复以及无索引,那么用LinkedHashSet集合

Collections工具类的使用

常用api

目标:Collections工具类的使用。

java.utils.Collections:是集合工具类
Collections并不属于集合,是用来操作集合的工具类。
Collections有几个常用的API:
     - public static <T> boolean addAll(Collection<? super T> c, T... elements)
         给集合对象批量添加元素!
     - public static void shuffle(List<?> list) :打乱集合顺序。
     - public static <T> void sort(List<T> list):将集合中元素按照默认规则排序。
     - public static <T> void sort(List<T> list,Comparator<? super T> ):将集合中元素按照指定规则排序。
public class CollectionsDemo01 {
    public static void main(String[] args) {
        // 1.给集合批量添加元素
        Collection<String> names = new ArrayList<>();
        /**
         * 参数一:被添加元素的集合
         * 参数二:可变参数,一批元素
         */
        Collections.addAll(names,"曹操","贾乃亮","王宝强","陈羽凡");
        System.out.println(names);

        // 2.打乱集合的顺序:public static void shuffle(List<?> list)
        // 注意:只能打乱有序的List集合。
        List<String> newnames = new ArrayList<>();
        Collections.addAll(newnames,"曹操","贾乃亮","王宝强","陈羽凡");
        Collections.shuffle(newnames); // 打乱顺序
        System.out.println(newnames);

        // 3.public static <T> void sort(List<T> list):给List集合升序排序。
        List<Double> scores = new ArrayList<>();
        Collections.addAll(scores, 98.5, 66.5 , 59.5 , 66.5 , 99.5 );
        Collections.sort(scores); // 默认升序排序!
        System.out.println(scores);
    }
}

排序

目标:引用数据类型的排序。

字符串按照首字符的编号升序排序!

自定义类型的比较方法API:
     - public static <T> void sort(List<T> list):
           将集合中元素按照默认规则排序。
           对于自定义的引用类型的排序人家根本不知道怎么排,直接报错!
           如果希望自定义的引用类型排序不报错,可以给类提供比较规则:Comparable。

     - public static <T> void sort(List<T> list,Comparator<? super T> c):
            将集合中元素按照指定规则排序,自带比较器
            注意:如果类有比较规则,而这里有比较器,优先使用比较器。

代码实例

排序,按照类实现的比较规则进行排序
public class Orange implements Comparable {
    private String name;
    private double weight;
    private String price;
    
    @Override
    public int compareTo(Object o) {
        Orange o2 = (Orange) o;
        // 集合会自动送入两个句子对象来比较!!
        // 认为o1 > o2返回正整数。
        // 认为o1 = o2返回0。
        // 认为o1 < o2返回负整数。
        if(this.weight > o2.weight) return 1;
        if(this.weight < o2.weight) return -1;
        return 0;
    }
}

将集合中元素按照指定规则排序,自带比较器
public class CollectionsDemo02 {
    public static void main(String[] args) {
        // 自定义类型如何排序!
        List<Orange> oranges = new ArrayList<>();
        Orange o1 = new Orange("红橘子",654.0 ,"贼便宜~");
        Orange o2 = new Orange("黄橘子",454.0 ,"贼便宜~");
        Orange o3 = new Orange("黄橘子",454.0 ,"贼便宜~");
        Orange o4 = new Orange("青橘子",456.0 ,"贼便宜~");
        Collections.addAll(oranges,o1,o2,o3,o4);
        Collections.sort(oranges); // 排序,按照类实现的比较规则进行排序!!
        System.out.println(oranges);


        List<Orange> oranges1 = new ArrayList<>();
        Orange o11 = new Orange("红橘子",654.0 ,"贼便宜~");
        Orange o22 = new Orange("黄橘子",454.0 ,"贼便宜~");
        Orange o33 = new Orange("黄橘子",454.0 ,"贼便宜~");
        Orange o44 = new Orange("青橘子",456.0 ,"贼便宜~");
        Collections.addAll(oranges1,o11,o22,o33,o44);
        // 优先用方法自带的比较器对象Comparator而不会用类的比较规则!!
        Collections.sort(oranges1, new Comparator<Orange>() {
            @Override
            public int compare(Orange o1, Orange o2) {
                if(o1.getWeight() > o2.getWeight()) return -1;
                if(o1.getWeight() < o2.getWeight()) return 1;
                return 0;
            }
        });
        System.out.println(oranges1);
    }
}

可变参数

目标:可变参数。

可变参数用在形参中可以接收多个数据。
可变参数的格式:数据类型... 参数名称

可变参数的作用:
     传输参数非常灵活,方便。
     可以不传输参数。
     可以传输一个参数。
     可以传输多个参数。
     可以传输一个数组。

可变参数在方法内部本质上就是一个数组。
可变参数的注意事项:
        1.一个形参列表中可变参数只能有一个!!
        2.可变参数必须放在形参列表的最后面!!
小结:
     可变参数的作用:传输参数非常灵活,方便。
     可变参数的注意事项:
     1.一个形参列表中可变参数只能有一个!!
     2.可变参数必须放在形参列表的最后面!!

代码实例

public class MethodDemo {
    public static void main(String[] args) {
        sum(); // 可以不传输参数。
        sum(10); // 可以传输一个参数。
        sum(10,20,30); // 可以传输多个参数。
        sum(new int[]{10,30,50,70,90}); // 可以传输一个数组。
    }

    public static void sum(int...nums){
        // 可变参数在方法内部本质上就是一个数组。
        System.out.println("元素个数:"+nums.length);
        System.out.println("元素内容:"+ Arrays.toString(nums));
        System.out.println("--------------------------");
    }
}

Map集合

概述

目标:Map集合概述。

Map集合是另一个集合体系。
Collection是单值集合体系。

Map集合是一种双列集合,每个元素包含两个值。
Map集合的每个元素的格式:key=value(键值对元素)。
Map集合也被称为“键值对集合”。

Map集合的完整格式:{key1=value1 , key2=value2 , key3=value3 , ...}

Map集合有啥用?
   1.Map集合存储的信息更加的具体丰富。
        Collection: ["苍老师","日本","女","动作演员",23,"广州"]
        Map : {name="苍老师" , jiaxiang=小日本 , sex="女" , age = 23 , addr=广州}

   2.Map集合很适合做购物车这样的系统。
        Map:  {娃娃=30 , huawei=1 , iphonex=1}

注意:集合和泛型都只能支持引用数据类型,集合完全可以称为是对象容器,存储都是对象。

Map集合的体系:
        Map<K , V>(接口,Map集合的祖宗类)
       /                      \
      TreeMap<K , V>           HashMap<K , V>(实现类,经典的,用的最多)
                                 \
                                  LinkedHashMap<K, V>(实现类)

Map集合的特点:
    1.Map集合的特点都是由键决定的。
    2.Map集合的键是无序,不重复的,无索引的。
        Map集合后面重复的键对应的元素会覆盖前面的整个元素!
    3.Map集合的值无要求。
    4.Map集合的键值对都可以为null。

   HashMap:元素按照键是无序,不重复,无索引,值不做要求。
   LinkedHashMap:元素按照键是有序,不重复,无索引,值不做要求。

常用api

目标:Map集合的常用API(重点中的重点)
 - public V put(K key, V value):  把指定的键与指定的值添加到Map集合中。
 - public V remove(Object key): 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。
 - public V get(Object key) 根据指定的键,在Map集合中获取对应的值。
 - public Set<K> keySet(): 获取Map集合中所有的键,存储到Set集合中。
 - public Set<Map.Entry<K,V>> entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合)。
 - public boolean containKey(Object key):判断该集合中是否有此键。
public class MapDemo {
    public static void main(String[] args) {
        Map<String , Integer> maps = new HashMap<>();
        // 1.添加元素: 无序,不重复,无索引。
        maps.put("iphoneX",10);
        maps.put("娃娃",30);
        maps.put("iphoneX",100);//  Map集合后面重复的键对应的元素会覆盖前面重复的整个元素!
        maps.put("huawei",1000);
        maps.put("生活用品",10);
        maps.put("手表",10);
        // {huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30}
        System.out.println(maps);

        // 2.清空集合
        //maps.clear();
        //System.out.println(maps);

        // 3.判断集合是否为空,为空返回true ,反之!
        System.out.println(maps.isEmpty());

        // 4.根据键获取对应值。
        //Integer value = maps.get("娃娃");
        //System.out.println(value);
        System.out.println(maps.get("娃娃"));

        // 5.根据键删除整个元素。(删除键会返回键的值)
        maps.remove("iphoneX");
        System.out.println(maps);

        // 6.判断是否包含某个键 ,包含返回true ,反之
        System.out.println(maps.containsKey("手表")); // true
        System.out.println(maps.containsKey(10)); // false

        // 7.判断是否包含某个值。
        System.out.println(maps.containsValue(1000)); // true
        System.out.println(maps.containsValue(10)); // true
        System.out.println(maps.containsValue("30")); // false 包含的是整数30不是字符串。

        // 8.获取全部键的集合:public Set<K> keySet()
        // Map集合的键是无序不重复的,所以返回的是一个Set集合。
        Set<String> keys = maps.keySet();
        for (String key : keys) {
            System.out.println(key);
        }

        // 9.获取全部值的集合:Collection<V> values();
        // Map集合的值是不做要求的,可能重复,所以值要用Collection集合接收!
        Collection<Integer> values = maps.values();
        for (Integer value : values) {
            System.out.println(value);
        }

        // 10.集合的大小
        System.out.println(maps.size());

        // 11.合并其他Map集合。(拓展)
        Map<String,Integer> maps2 = new HashMap<>();
        maps2.put("xiaoMi" , 1);
        maps2.put("🔨手机" , 10);
        maps2.put("手表" , 10000);
        maps.putAll(maps2); // 把Map集合maps2的数据全部倒入到maps集合中去
        System.out.println(maps);
    }
}

遍历(三种)

概述
目标:Map集合的遍历方式。

Map集合的遍历方式有:3种。
    (1)“键找值”的方式遍历:先获取Map集合全部的键,再根据遍历键找值。
    (2)“键值对”的方式遍历:难度较大。
    (3)JDK 1.8开始之后的新技术:Lambda表达式。(暂时了解)

a.“键找值”的方式遍历Map集合。
    1.先获取Map集合的全部键的Set集合。
    2.遍历键的Set集合,然后通过键找值。
小结:
    代码简单,需要记住!
    
 b.“键值对”的方式遍历:
        1.把Map集合转换成一个Set集合:Set<Map.Entry<K, V>> entrySet();
        2.此时键值对元素的类型就确定了,类型是键值对实体类型:Map.Entry<K, V>
        3.接下来就可以用foreach遍历这个Set集合,类型用Map.Entry<K, V>    
“键找值”的方式遍历
public class MapDemo01 {
    public static void main(String[] args) {
        Map<String , Integer> maps = new HashMap<>();
        // 1.添加元素: 无序,不重复,无索引。
        maps.put("娃娃",30);
        maps.put("iphoneX",100);//  Map集合后面重复的键对应的元素会覆盖前面重复的整个元素!
        maps.put("huawei",1000);
        maps.put("生活用品",10);
        maps.put("手表",10);
        System.out.println(maps);
        // maps = {huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30}

        // a.键找值方式遍历
        // 获取当前Map集合的全部键的集合 。
        Set<String> keys = maps.keySet();
        System.out.println(keys);
        // b.通过遍历键然后通过键取对应的值
        for (String key : keys) {
            // 过键取对应的值
            Integer value = maps.get(key);
            System.out.println(key + "=" + value);
        }
    }
}
“键值对”的方式遍历
public class MapDemo02 {
    public static void main(String[] args) {
        Map<String , Integer> maps = new HashMap<>();
        // 1.添加元素: 无序,不重复,无索引。
        maps.put("娃娃",30);
        maps.put("iphoneX",100);//  Map集合后面重复的键对应的元素会覆盖前面重复的整个元素!
        maps.put("huawei",1000);
        maps.put("生活用品",10);
        maps.put("手表",10);
        System.out.println(maps);
        Set<Map.Entry<String,Integer>> entries = maps.entrySet();
        for (Map.Entry<String, Integer> entry : entries) {
             String key = entry.getKey();
             Integer value = entry.getValue();
             System.out.println(key + "=>" + value);
        }
    }
}
Lambda表达式遍历
public class MapDemo03 {
    public static void main(String[] args) {
        Map<String , Integer> maps = new HashMap<>();
        // 1.添加元素: 无序,不重复,无索引。
        maps.put("娃娃",30);
        maps.put("iphoneX",100);//  Map集合后面重复的键对应的元素会覆盖前面重复的整个元素!
        maps.put("huawei",1000);
        maps.put("生活用品",10);
        maps.put("手表",10);
        System.out.println(maps);

        maps.forEach((k , v) -> {
            System.out.println(k+"==>"+v);
        });
    }
}

map集合存储自定义类型

目标:Map集合存储自定义类型;

Map集合的键和值都可以存储自定义类型。

小结:
    Map集合的键和值都可以存储自定义类型。
    如果希望Map集合认为自定义类型的键对象重复了,必须重写对象的hashCode()和equals()方法
public class Orange {
    private String name;
    private double weight;
    private String price;
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Orange orange = (Orange) o;
        return Double.compare(orange.weight, weight) == 0 &&
                Objects.equals(name, orange.name) &&
                Objects.equals(price, orange.price);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, weight, price);
    }
}

public class MapDemo01 {
    public static void main(String[] args) {
        Map<Orange,String> maps = new HashMap<>();
        Orange o1 = new Orange("黄橘子",20.3 , "贼便宜!");
        Orange o2 = new Orange("黑橘子",30.3 , "坏了");
        Orange o3 = new Orange("青橘子",34.3 , "9.9包邮");
        Orange o4 = new Orange("黄橘子",20.3 , "贼便宜!");

        maps.put(o1 , "江西\n");
        maps.put(o2 , "赣州\n");
        maps.put(o3 , "广州\n");
        maps.put(o4 , "广西\n");

        System.out.println(maps);
    }
}

LinkdedHashMap

目标:LinkedHashMap的特点介绍。

LinkedHashMap是HashMap的子类。
    -- 添加的元素按照键有序,不重复的。
HashSet集合相当于是HashMap集合的键都不带值。
LinkedHashSet集合相当于是LinkedHashMap集合的键都不带值。

底层原理完全一样,都是基于哈希表按照键存储数据的,
只是HashMap或者LinkedHashMap的键都多一个附属值。


小结:
    HashMap集合是无序不重复的键值对集合。
    LinkedHashMap集合是有序不重复的键值对集合。
    他们都是基于哈希表存储数据,增删改查都很好。
public class LinkedHashMapDemo {
    public static void main(String[] args) {
        Map<String , Integer> maps = new LinkedHashMap<>();
        maps.put("iphoneX",10);
        maps.put("娃娃",30);
        maps.put("iphoneX",100); // 依然是保留前面的位置,只是替换其值!
        maps.put("huawei",1000);
        maps.put("生活用品",10);
        maps.put("手表",10);
        System.out.println(maps);
    }
}

TreeMap

目标:TreeMap集合的特点和使用。

TreeMap集合按照键是可排序不重复的键值对集合。(默认升序)
TreeMap集合按照键排序的特点与TreeSet是完全一样的。
小结:
    TreeMap集合和TreeSet集合都是排序不重复集合
    TreeSet集合的底层是基于TreeMap,只是键没有附属值而已。
    所以TreeMap集合指定大小规则有2种方式:
         a.直接为对象的类实现比较器规则接口Comparable,重写比较方法(拓展方式)
         b.直接为集合设置比较器Comparator对象,重写比较方法
public class Pig implements Comparable{
    private String name;
    private double price ;
    private double weight;

    public Pig() {
    }

    public Pig(String name, double price, double weight) {
        this.name = name;
        this.price = price;
        this.weight = weight;
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Pig pig = (Pig) o;
        return Double.compare(pig.price, price) == 0 &&
                Double.compare(pig.weight, weight) == 0 &&
                Objects.equals(name, pig.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, price, weight);
    }


    // 比较者: this
    // 被比较者: o
    // 需求:按照价格排序!
    @Override
    public int compareTo(Object o) {
        // 浮点型的大小比较建议使用Java自己的API:
        // public static int compare(double d1, double d2)
        return  -Double.compare(this.price , ((Pig)o).price);
    }
}


public class TreeMapDemo {
    public static void main(String[] args) {
        Map<Integer,String> maps = new TreeMap<>();
        maps.put(1000000,"张三");
        maps.put(1000000,"张三1");
        maps.put(10000,"李四");
        maps.put(10,"王五");
        maps.put(24244,"张麻子");
        System.out.println(maps);

        Map<Pig,String> pigs = new TreeMap<>();
        pigs.put(new Pig("🐖佩奇",99.5 , 500.0), "荷兰");
        pigs.put(new Pig("🐖乔治",99.4 , 605.0), "澳大利亚");
        pigs.put(new Pig("🐗野猪",199.4 , 305.0), "山上");
        pigs.put(new Pig("🐗野猪",199.4 , 305.0), "山上2");
        System.out.println(pigs);


        //  public TreeMap(Comparator<? super K> comparator)
        Map<Pig,String> pigs1 = new TreeMap<>(new Comparator<Pig>() {
            @Override
            public int compare(Pig p1, Pig p2) {
                return Double.compare(p1.getWeight() , p2.getWeight());
            }
        });
        pigs1.put(new Pig("🐖佩奇",99.5 , 500.0), "荷兰");
        pigs1.put(new Pig("🐖乔治",99.4 , 605.0), "澳大利亚");
        pigs1.put(new Pig("🐗野猪",199.4 , 305.0), "山上");
        pigs1.put(new Pig("🐗野猪",199.4 , 305.0), "山上2");
        System.out.println(pigs1);
    }
}

异常

概念和体系

目标:异常的概念和体系。

什么是异常?
     异常是程序在"编译"或者"执行"的过程中可能出现的问题。
     异常是应该尽量提前避免的。
     异常可能也是无法做到绝对避免的,异常可能有太多情况了,开发中只能提前干预!!
     异常一旦出现了,如果没有提前处理,程序就会退出JVM虚拟机而终止,开发中异常是需要提前处理的。

     研究异常并且避免异常,然后提前处理异常,体现的是程序的安全, 健壮性!!!

     Java会为常见的代码异常都设计一个类来代表。

异常的体系:
     Java中异常继承的根类是:Throwable。

         Throwable(根类,不是异常类)
      /              \
    Error           Exception(异常,需要研究和处理)
                    /            \
                   编译时异常     RuntimeException(运行时异常)


    Error : 错误的意思,严重错误Error,无法通过处理的错误,一旦出现,程序员无能为力了,
        只能重启系统,优化项目。
        比如内存奔溃,JVM本身的奔溃。这个程序员无需理会。

    Exception:才是异常类,它才是开发中代码在编译或者执行的过程中可能出现的错误,
        它是需要提前处理的。以便程序更健壮!

Exception异常的分类:
     1.编译时异常:继承自Exception的异常或者其子类,编译阶段就会报错,
            必须程序员处理的。否则代码编译就不能通过!!

     2.运行时异常: 继承自RuntimeException的异常或者其子类,编译阶段是不会出错的,它是在
            运行时阶段可能出现,运行时异常可以处理也可以不处理,编译阶段是不会出错的,
            但是运行阶段可能出现,还是建议提前处理!!
小结:
    异常是程序在编译或者运行的过程中可能出现的错误!!
    异常分为2类:编译时异常,运行时异常。
        -- 编译时异常:继承了Exception,编译阶段就报错,必须处理,否则代码不通过。
        -- 运行时异常:继承了RuntimeException,编译阶段不会报错,运行时才可能出现。
    异常一旦真的出现,程序会终止,所以要研究异常,避免异常,处理异常,程序更健壮!!

常见的运行时异常

拓展: 常见的运行时异常。(面试题)
     运行时异常的概念:
         继承自RuntimeException的异常或者其子类,
         编译阶段是不会出错的,它是在运行时阶段可能出现的错误,
         运行时异常编译阶段可以处理也可以不处理,代码编译都能通过!!

         1.数组索引越界异常: ArrayIndexOutOfBoundsException。
         2.空指针异常 : NullPointerException。
           直接输出没有问题。但是调用空指针的变量的功能就会报错!!
         3.类型转换异常:ClassCastException。
         4.迭代器遍历没有此元素异常:NoSuchElementException。
         5.数学操作异常:ArithmeticException。
         6.数字转换异常: NumberFormatException。

小结:
    运行时异常继承了RuntimeException ,编译阶段不报错,运行时才可能会出现错误!
public class ExceptionDemo {
    public static void main(String[] args) {
        System.out.println("程序开始。。。。。。");
        /** 1.数组索引越界异常: ArrayIndexOutOfBoundsException。*/
        int[] arrs = {10 ,20 ,30};
        System.out.println(arrs[2]);
        // System.out.println(arrs[3]); // 此处出现了数组索引越界异常。代码在此处直接执行死亡!

        /** 2.空指针异常 : NullPointerException。直接输出没有问题。但是调用空指针的变量的功能就会报错!! */
        String name = null ;
        System.out.println(name); // 直接输出没有问题
        // System.out.println(name.length());  // 此处出现了空指针异常。代码在此处直接执行死亡!

        /** 3.类型转换异常:ClassCastException。 */
        Object o = "齐天大圣";
        //Integer s = (Integer) o;  // 此处出现了类型转换异常。代码在此处直接执行死亡!


        /** 5.数学操作异常:ArithmeticException。 */
        // int c = 10 / 0 ; // 此处出现了数学操作异常。代码在此处直接执行死亡!


        /** 6.数字转换异常: NumberFormatException。 */
        String num = "23aa";
        Integer it = Integer.valueOf(num); // 此处出现了数字转换异常。代码在此处直接执行死亡!
        System.out.println(it+1);

        System.out.println("程序结束。。。。。。");
    }
}

编译异常

目标:常见的编译时异常认识。

编译时异常:继承自Exception的异常或者其子类,没有继承RuntimeException
           "编译时异常是编译阶段就会报错",
           必须程序员编译阶段就处理的。否则代码编译就报错!!

编译时异常的作用是什么:
        是担心程序员的技术不行,在编译阶段就爆出一个错误, 目的在于提醒!
        提醒程序员这里很可能出错,请检查并注意不要出bug。

        编译时异常是可遇不可求。遇到了就遇到了呗。
小结:
    编译时异常是编译阶段就会报错的,继承了Exception,编译时
    异常是可遇不可求。遇到了就遇到了呗。

    编译时异常编译阶段必须处理,否则代码编译不通过!!
public class ExceptionDemo {
    public static void main(String[] args) throws ParseException {
        String date = "2015-01-12 10:23:21";
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date d = sdf.parse(date);
        System.out.println(d);
    }
}

异常处理过程

     目标:异常的产生默认的处理过程解析。(自动处理的过程!)
    (1)默认会在出现异常的代码那里自动的创建一个异常对象:ArithmeticException。
    (2)异常会从方法中出现的点这里抛出给调用者,调用者最终抛出给JVM虚拟机。
    (3)虚拟机接收到异常对象后,先在控制台直接输出异常栈信息数据。
    (4)直接从当前执行的异常点干掉当前程序。
    (5)后续代码没有机会执行了,因为程序已经死亡。

 小结:
     异常一旦出现,会自动创建异常对象,最终抛出给虚拟机,虚拟机
     只要收到异常,就直接输出异常信息,干掉程序!!

     默认的异常处理机制并不好,一旦真的出现异常,程序立即死亡!
public class ExceptionDemo {
    public static void main(String[] args) {
        System.out.println("程序开始。。。。。。。。。。");
        chu( 10 ,0 );
        System.out.println("程序结束。。。。。。。。。。");
    }

    public static void chu(int a , int b){
        System.out.println(a);
        System.out.println(b);
        int c = a / b ;// 出现了运行时异常,自动创建异常对象:ArithmeticException
        System.out.println("结果是:"+c);
    }
}

编译异常的处理方式

直接抛出

目标:编译时异常的处理方式一。

编译时异常:编译阶段就会报错,一定需要程序员处理的,否则代码无法通过!!

抛出异常格式:
    方法 throws 异常1 ,  异常2 , ..{

    }
    建议抛出异常的方式:代表可以抛出一切异常,
    方法 throws Exception{

    }

方式一:
    在出现编译时异常的地方层层把异常抛出去给调用者,调用者最终抛出给JVM虚拟机。
    JVM虚拟机输出异常信息,直接干掉程序,这种方式与默认方式是一样的。
    虽然可以解决代码编译时的错误,但是一旦运行时真的出现异常,程序还是会立即死亡!
    这种方式并不好!

小结:
    编译时异常编译阶段就报错,必须程序员处理。
    方式一:出现异常的地方层层抛出,谁都不处理,最终抛出给虚拟机。
    这种方式虽然可以解决编译时异常,但是如果异常真的出现了,程序会直接死亡,所以这种方式并不好!
public static void main(String[] args) throws Exception {
    System.out.println("程序开始。。。。");
    parseDate("2013-03-23 10:19:23");
    System.out.println("程序结束。。。。。");
}

// Exception是异常最高类型可以抛出一切异常!
public static void parseDate(String time) throws Exception{
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    Date d = sdf.parse(time);
    System.out.println(d);

    InputStream is = new FileInputStream("D:/meinv.jpg");
}

出现异常的地方自己捕获

目标:编译时异常的处理方式二。

方式二:在出现异常的地方自己处理,谁出现谁处理。

自己捕获异常和处理异常的格式:捕获处理
     try{
        // 监视可能出现异常的代码!
     }catch(异常类型1 变量){
        // 处理异常
     }catch(异常类型2 变量){
        // 处理异常
     }...

监视捕获处理异常企业级写法:
     try{
         // 可能出现异常的代码!
     }catch (Exception e){
        e.printStackTrace(); // 直接打印异常栈信息
     }
     Exception可以捕获处理一切异常类型!

小结:
    第二种方式,可以处理异常,并且出现异常后代码也不会死亡。
    这种方案还是可以的。
    但是从理论上来说,这种方式不是最好的,上层调用者不能直接知道底层的执行情况!
public static void main(String[] args) {
    System.out.println("程序开始。。。。");
    parseDate("2013-03-23 10:19:23");
    System.out.println("程序结束。。。。。");
}

public static void parseDate(String time)  {
    try{
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date d = sdf.parse(time);
        System.out.println(d);

        InputStream is = new FileInputStream("D:/meinv.png");
    } catch (Exception e) {
        e.printStackTrace(); // 打印异常栈信息
    }
}

抛出给最外层调用者,由最外层调用者处理

目标:编译时异常的处理方式三。

方式三: 在出现异常的地方把异常一层一层的抛出给最外层调用者,
        最外层调用者集中捕获处理!!(规范做法)

小结:
    编译时异常的处理方式三:底层出现的异常抛出给最外层调用者集中捕获处理。
    这种方案最外层调用者可以知道底层执行的情况,同时程序在出现异常后也不会立即死亡,这是
    理论上最好的方案。

    虽然异常有三种处理方式,但是开发中只要能解决你的问题,每种方式都又可能用到!!
public class ExceptionDemo03 {

    public static void main(String[] args) {
        System.out.println("程序开始。。。。");
        try {
            parseDate("2013-03-23 10:19:23");
            System.out.println("功能成功执行!!");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("功能执行失败!!");
        }
        System.out.println("程序结束。。。。。");
    }

    // 可以拦截所以异常!
    public static void parseDate(String time) throws Exception {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date d = sdf.parse(time);
        System.out.println(d);

        InputStream is = new FileInputStream("D:/meinv.png");
    }
}

运行时异常的处理机制

    目标:运行时异常的处理机制。

    运行时异常在编译阶段是不会报错,在运行阶段才会出错。
    运行时异常在编译阶段不处理也不会报错,但是运行时如果出错了程序还是会死亡
    所以运行时异常也建议要处理。

    运行时异常是自动往外抛出的,不需要我们手工抛出。

    运行时异常的处理规范:直接在最外层捕获处理即可,底层会自动抛出!!

    小结:
         运行时异常编译阶段不报错,可以处理也可以不处理,建议处理!!
         运行时异常可以自动抛出,不需要我们手工抛出。
         运行时异常的处理规范:直接在最外层捕获处理即可,底层会自动抛出!!
public class ExceptionDemo {
    public static void main(String[] args) {
        System.out.println("程序开始。。。。");
        try{
            chu(10 , 0);
            System.out.println("操作成功!");
        }catch (Exception e){
            e.printStackTrace();
            System.out.println("操作失败!");
        }
        System.out.println("程序结束。。。。");
    }

    public static void chu(int a , int b)  {
        System.out.println( a / b );
    }
}

finally

目标:finally关键字

用在捕获处理的异常格式中的,放在最后面。
    try{
        // 可能出现异常的代码!
    }catch(Exception e){
        e.printStackTrace();
    }finally{
        // 无论代码是出现异常还是正常执行,最终一定要执行这里的代码!!
    }
    try: 1次。
    catch:0-N次  (如果有finally那么catch可以没有!!)
    finally: 0-1次

finally的作用: 可以在代码执行完毕以后进行资源的释放操作。
什么是资源?资源都是实现了Closeable接口的,都自带close()关闭方法!!
public class FinallyDemo {
    public static void main(String[] args) {
        //chu();
        System.out.println(chu1());
    }

    public static int chu1(){
        try{
            int a = 10 / 2 ;
            return a ;
        }catch (Exception e){
            e.printStackTrace();
            return -1;
        }finally {
            System.out.println("=====finally被执行");
            return 111; // 不建议在finally中写return,会覆盖前面所有的return值!
        }
    }

    public static void chu(){
        InputStream is = null;
        try{
            //System.out.println(10/0);
            is = new FileInputStream("D:/cang.png");
            System.out.println(10 / 0 );

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            System.out.println("==finally被执行===");
            // 回收资源。用于在代码执行完毕以后进行资源的回收操作!
            try {
                if(is!=null)is.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

异常的语法注意

目标:异常的语法注意:(了解)
    - 运行时异常被抛出可以不处理。可以自动抛出,编译时异常必须处理.按照规范都应该处理!
    - 重写方法申明抛出的异常,应该与父类被重写方法申明抛出的异常一样或者范围更小
    - 方法默认都可以自动抛出运行时异常!  throws RuntimeException可以省略不写!!
    - 当多异常处理时,捕获处理,前边的异常类不能是后边异常类的父类。
    - 在try/catch后可以追加finally代码块,其中的代码一定会被执行,通常用于资源回收操作。

自定义异常

目标:自定义异常(了解)

引入:Java已经为开发中可能出现的异常都设计了一个类来代表.
    但是实际开发中,异常可能有无数种情况,Java无法为
    这个世界上所有的异常都定义一个代表类。
    假如一个企业如果想为自己认为的某种业务问题定义成一个异常
    就需要自己来自定义异常类.

需求:认为年龄小于0岁,大于200岁就是一个异常。

自定义异常:
    自定义编译时异常.
        a.定义一个异常类继承Exception.
        b.重写构造器。
        c.在出现异常的地方用throw new 自定义对象抛出!
        编译时异常是编译阶段就报错,提醒更加强烈,一定需要处理!!

    自定义运行时异常.
        a.定义一个异常类继承RuntimeException.
        b.重写构造器。
        c.在出现异常的地方用throw new 自定义对象抛出!
        提醒不强烈,编译阶段不报错!!运行时才可能出现!!
小结:
    自定义异常是程序员自己定义的异常
    继承Exception/RuntimeException,重写构造器。
    在出现异常的地方用throw new 自定义异常对象抛出!

自定义编译时异常类

自定义编译时异常类:
    1.继承Exception。
    2.重写构造器。
    
public class ItheimaAgeIllegalException extends Exception {
    public ItheimaAgeIllegalException() {
    }

    public ItheimaAgeIllegalException(String message) {
        super(message);
    }

    public ItheimaAgeIllegalException(String message, Throwable cause) {
        super(message, cause);
    }

    public ItheimaAgeIllegalException(Throwable cause) {
        super(cause);
    }

    public ItheimaAgeIllegalException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}    

自定义运行时异常类

自定义运行时异常类:
    1.继承RuntimeException
    2.重写构造器。
    
public class ItheimaAgeIllegalRuntimeException extends RuntimeException {
    public ItheimaAgeIllegalRuntimeException() {
    }

    public ItheimaAgeIllegalRuntimeException(String message) {
        super(message);
    }

    public ItheimaAgeIllegalRuntimeException(String message, Throwable cause) {
        super(message, cause);
    }

    public ItheimaAgeIllegalRuntimeException(Throwable cause) {
        super(cause);
    }

    public ItheimaAgeIllegalRuntimeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}    

异常的作用

拓展:异常的作用
    1.可以处理代码问题,防止程序出现异常后的死亡。
    2.提高了程序的健壮性和安全性。

线程

概述

目标:多线程的概述。(并发编程)

什么是进程?
     程序是静止的,运行中的程序就是进程。
     进程的三个特征:
     1.动态性 : 进程是运行中的程序,要动态的占用内存,CPU和网络等资源。
     2.独立性 : 进程与进程之间是相互独立的,彼此有自己的独立内存区域。
     3.并发性 : 假如CPU是单核,同一个时刻其实内存中只有一个进程在被执行。
                CPU会分时轮询切换依次为每个进程服务,因为切换的速度非常
                快,给我们的感觉这些进程在同时执行,这就是并发性。

    并行:同一个时刻同时有多个在执行。

什么是线程?
     线程是属于进程的。一个进程可以包含多个线程,这就是多线程。
     线程是进程中的一个独立执行单元。
     线程创建开销相对于进程来说比较小。
     线程也支持“并发性”。

线程的作用:
     可以提高程序的效率,线程也支持并发性,可以有更多机会得到CPU。
     多线程可以解决很多业务模型。
     大型高并发技术的核心技术。
     设计到多线程的开发可能都比较难理解。

线程创建的三种方式

概述

多线程是很有用的,我们在进程中创建线程的方式有三种:
    (1)直接定义一个类继承线程类Thread,重写run()方法,创建线程对象
        调用线程对象的start()方法启动线程。
    (2)定义一个线程任务类实现Runnable接口,重写run()方法,创建线程任务对象,把
        线程任务对象包装成线程对象, 调用线程对象的start()方法启动线程。
    (3)实现Callable接口(拓展)。

继承Thread类(一)

a.继承Thread类的方式
    -- 1.定义一个线程类继承Thread类。
    -- 2.重写run()方法
    -- 3.创建一个新的线程对象。
    -- 4.调用线程对象的start()方法启动线程。

    继承Thread类的优缺点:
        优点:编码简单。
        缺点:线程类已经继承了Thread类无法继承其他类了,功能不能通过继承拓展(单继承的局限性)
小结:
    线程类是继承了Thread的类。
    启动线程必须调用start()方法。
    多线程是并发抢占CPU执行,所以在执行的过程中会出现并发随机性。
public class ThreadDemo {
    // 启动这个类,这个类就是进程,它自带一个主线程,
    // 是main方法,main就是一个主线程的执行!!
    public static void main(String[] args) {


        // 3.创建一个新的线程对象。
        Thread t = new MyThread();
        // 4. 调用线程对象的start()方法启动线程。最终还是执行run()方法!
        // 如果线程直接调用run()方法,相当于变成了普通类的执行,此时将只有主线程在执行他们!此时其实还是只有一个线程就是主线程执行
        // t.run();
        // start()方法底层其实是给CPU注册当前线程,并且触发run()方法执行
        t.start();


        for(int i = 0 ; i < 10 ; i++ ) {
            System.out.println("main线程输出:"+i);
        }
    }
}

// 1.定义一个线程类继承Thread。线程类并不是线程对象,用来创建线程对象的。
class MyThread extends Thread{
    // 2.重写run()方法
    @Override
    public void run() {
        for(int i = 0 ; i < 10 ; i++ ) {
            System.out.println("子线程输出:"+i);
        }
    }
}

实现Runnable接口(二)

目标:线程的创建方式二。

b.实现Runnable接口的方式。
     -- 1.创建一个线程任务类实现Runnable接口。
     -- 2.重写run()方法
     -- 3.创建一个线程任务对象。
     -- 4.把线程任务对象包装成线程对象
     -- 5.调用线程对象的start()方法启动线程。
Thread的构造器:
     -- public Thread(){}
     -- public Thread(String name){}
     -- public Thread(Runnable target){}
     -- public Thread(Runnable target,String name){}
实现Runnable接口创建线程的优缺点:
     缺点:代码复杂一点。
     优点:
       -- 线程任务类只是实现了Runnable接口,可以继续继承其他类,而且可以继续实现其他接口(避免了单继承的局限性)
       -- 同一个线程任务对象可以被包装成多个线程对象
       -- 适合多个多个线程去共享同一个资源(后面内容)
       -- 实现解耦操作,线程任务代码可以被多个线程共享,线程任务代码和线程独立。
       -- 线程池可以放入实现Runable或Callable线程任务对象。(后面了解)
          注意:其实Thread类本身也是实现了Runnable接口的。
       -- 不能直接得到线程执行的结果!
public class ThreadDemo {    public static void main(String[] args) {        // 3.创建一个线程任务对象(注意:线程任务对象不是线程对象,只是执行线程的任务的)        Runnable target = new MyRunnable();        // 4.把线程任务对象包装成线程对象.且可以指定线程名称        // Thread t = new Thread(target);        Thread t = new Thread(target,"1号线程");        // 5.调用线程对象的start()方法启动线程        t.start();        Thread t2 = new Thread(target);        // 调用线程对象的start()方法启动线程        t2.start();        for(int i = 0 ; i < 10 ; i++ ){            System.out.println(Thread.currentThread().getName()+"==>"+i);        }    }}// 1.创建一个线程任务类实现Runnable接口。class MyRunnable implements Runnable{    // 2.重写run()方法    @Override    public void run() {        for(int i = 0 ; i < 10 ; i++ ){            System.out.println(Thread.currentThread().getName()+"==>"+i);        }    }}

匿名内部类的写法-简化写法

public class ThreadDemo02 {
    public static void main(String[] args) {
        // 创建一个线程任务对象(注意:线程任务对象不是线程对象,只是执行线程的任务的)
        Runnable target = new Runnable() {
            @Override
            public void run() {
                for(int i = 0 ; i < 10 ; i++ ){
                    System.out.println(Thread.currentThread().getName()+"==>"+i);
                }
            }
        };
        // 把线程任务对象包装成线程对象.且可以指定线程名称
        Thread t = new Thread(target);
        // 调用线程对象的start()方法启动线程
        t.start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i = 0 ; i < 10 ; i++ ){
                    System.out.println(Thread.currentThread().getName()+"==>"+i);
                }
            }
        }).start();

        for(int i = 0 ; i < 10 ; i++ ){
            System.out.println(Thread.currentThread().getName()+"==>"+i);
        }
    }
}

实现Callable接口(三)

拓展:线程的创建方式三。(拓展)

 c.线程的创建方式三: 实现Callable接口。
     -- 1,定义一个线程任务类实现Callable接口 , 申明线程执行的结果类型。
     -- 2,重写线程任务类的call方法,这个方法可以直接返回执行的结果。
     -- 3,创建一个Callable的线程任务对象。
     -- 4,把Callable的线程任务对象包装成一个未来任务对象。
     -- 5.把未来任务对象包装成线程对象。
     -- 6.调用线程的start()方法启动线程
 优缺点:
     优点:全是优点。
        -- 线程任务类只是实现了Callable接口,可以继续继承其他类,而且可以继续实现其他接口(避免了单继承的局限性)
        -- 同一个线程任务对象可以被包装成多个线程对象
        -- 适合多个多个线程去共享同一个资源(后面内容)
        -- 实现解耦操作,线程任务代码可以被多个线程共享,线程任务代码和线程独立。
        -- 线程池可以放入实现Runable或Callable线程任务对象。(后面了解)
        -- 能直接得到线程执行的结果!
     缺点:编码复杂。
public class ThreadDemo {
    public static void main(String[] args) {
        // 3.创建一个Callable的线程任务对象
        Callable call = new MyCallable();
        // 4.把Callable任务对象包装成一个未来任务对象
        //      -- public FutureTask(Callable<V> callable)
        // 未来任务对象是啥,有啥用?
        //      -- 未来任务对象其实就是一个Runnable对象:这样就可以被包装成线程对象!
        //      -- 未来任务对象可以在线程执行完毕之后去得到线程执行的结果。
        FutureTask<String> task = new FutureTask<>(call);
        // 5.把未来任务对象包装成线程对象
        Thread t = new Thread(task);
        // 6.启动线程对象
        t.start();

        for(int i = 1 ; i <= 10 ; i++ ){
            System.out.println(Thread.currentThread().getName()+" => " + i);
        }

        // 在最后去获取线程执行的结果,如果线程没有结果,让出CPU等线程执行完再来取结果
        try {
            String rs = task.get(); // 获取call方法返回的结果(正常/异常结果)
            System.out.println(rs);
        }  catch (Exception e) {
            e.printStackTrace();
        }

    }
}

// 1.创建一个线程任务类实现Callable接口,申明线程返回的结果类型
class MyCallable implements Callable<String>{
    // 2.重写线程任务类的call方法!
    @Override
    public String call() throws Exception {
        // 需求:计算1-10的和返回
        int sum = 0 ;
        for(int i = 1 ; i <= 10 ; i++ ){
            System.out.println(Thread.currentThread().getName()+" => " + i);
            sum+=i;
        }
        return Thread.currentThread().getName()+"执行的结果是:"+sum;
    }
}

线程的使用注意事项

目标:线程的注意事项。

1.线程的启动必须调用start()方法。否则当成普通类处理。
    -- 如果线程直接调用run()方法,相当于变成了普通类的执行,此时将只有主线程在执行他们!
    -- start()方法底层其实是给CPU注册当前线程,并且触发run()方法执行
2.建议线程先创建子线程,主线程的任务放在之后。否则主线程永远是先执行完!

线程的常用API

目标:线程的常用API.

Thread类的API:
    1.public void setName(String name):给当前线程取名字。
    2.public void getName():获取当前线程的名字。
        -- 线程存在默认名称,子线程的默认名称是:Thread-索引。
        -- 主线程的默认名称就是:main
    3.public static Thread currentThread()
        -- 获取当前线程对象,这个代码在哪个线程中,就得到哪个线程对象。
        
        目标:线程休眠API.
    public static void sleep(long time): 让当前线程休眠多少毫秒再继续执行。
    
    目标:通过Thread类的有参数构造器为当前线程对象取名字。
        -- public Thread()
        -- public Thread(String name):创建线程对象并取名字。
public class ThreadDemo {
    // 启动后的ThreadDemo当成一个进程。
    // main方法是由主线程执行的,理解成main方法就是一个主线程
    public static void main(String[] args) {
        // 创建一个线程对象
        Thread t1 = new MyThread();
        t1.setName("1号线程");
        t1.start();
        //System.out.println(t1.getName()); // 获取线程名称

        Thread t2 = new MyThread();
        t2.setName("2号线程");
        t2.start();
        //System.out.println(t2.getName());  // 获取线程名称

        // 主线程的名称如何获取呢?
        // 这个代码在哪个线程中,就得到哪个线程对象。
        Thread m = Thread.currentThread();
        m.setName("最强线程main");
        //System.out.println(m.getName()); // 获取线程名称

        for(int i = 0 ; i < 10 ; i++ ){
            System.out.println(m.getName()+"==>"+i);
        }
    }
}

// 1.定义一个线程类继承Thread类。
class MyThread extends Thread{
    // 2.重写run()方法
    @Override
    public void run() {
        // 线程的执行方法。
        for(int i = 0 ; i < 10 ; i++ ){
            System.out.println(Thread.currentThread().getName()+"==>"+i);
        }
    }
}

线程休眠

public class ThreadDemo02 {
    public static void main(String[] args) {
        for(int i = 0 ; i < 10 ; i++ ) {
            System.out.println(i);
            try {
                // 项目经理让我加上这行代码
                // 如果用户交钱了,我就去掉。
                Thread.sleep(1000); // 让当前线程休眠1s.
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

实现线程安全的三种方式

概述

目标:线程安全问题。

线程安全问题:多个线程同时操作同一个共享资源的时候可能会出现线程安全问题。

模拟出取款问题的案例:
    注意:用高度面向对象的思想设计。
    分析:
        (1)提供一个账户类Account.java作为创建共享资源账户对象的类。
        (2)定义一个线程类来用于创建2个线程分别代表小明和小红来取钱。
小结:
    多个线程同时操作同一个共享资源的时候可能会出现线程安全问题。
目标:线程同步_同步代码块

线程同步的作用:就是为了解决线程安全问题的方案。

线程同步解决线程安全问题的核心思想:
        让多个线程实现先后依次访问共享资源,这样就解决了安全问题。

线程同步的做法:加锁
        是把共享资源进行上锁,每次只能一个线程进入访问完毕以后,其他线程才能进来。

线程同步的方式有三种:
    (1)同步代码块。
    (2)同步方法。
    (3)lock显示锁。

同步代码块(方式一)

a.同步代码块。
     作用:把出现线程安全问题的核心代码给上锁,每次只能一个线程进入
          执行完毕以后自动解锁,其他线程才可以进来执行。

     格式:
          synchronized(锁对象){
                // 访问共享资源的核心代码
          }
          锁对象:理论上可以是任意的“唯一”对象即可。
          原则上:锁对象建议使用共享资源。
                -- 在实例方法中建议用this作为锁对象。此时this正好是共享资源!必须代码高度面向对象
                -- 在静态方法中建议用类名.class字节码作为锁对象。
public void drawMoney(double money) {
    // 1.先拿到是谁来取钱:取当前线程对象的名称
    String name = Thread.currentThread().getName();
    // 小明、小红
    // this == 当前共享账户acc
    // 2.判断余额是否足够
    synchronized (this){
        if(this.money >= money){
            // 3.余额足够
            System.out.println(name+"来取钱,吐出:"+money);
            // 4.更新余额
            this.money -= money;
            // 5.输出结果
            System.out.println(name+"来取钱"+money+"成功,取钱后剩余:"+this.money);
        }else{
            // 6.余额不足
            System.out.println(name+"来取钱,余额不足,剩余"+this.money);
        }
    }
}

同步方法(方式二)

b.同步方法
    作用:把出现线程安全问题的核心方法给锁起来,
         每次只能一个线程进入访问,其他线程必须在方法外面等待。
    用法:直接给方法加上一个修饰符 synchronized.
    原理:  同步方法的原理和同步代码块的底层原理其实是完全一样的,只是
          同步方法是把整个方法的代码都锁起来的。
          同步方法其实底层也是有锁对象的:
              如果方法是实例方法:同步方法默认用this作为的锁对象。
              如果方法是静态方法:同步方法默认用类名.class作为的锁对象。
public synchronized void drawMoney(double money) {
    // 1.先拿到是谁来取钱:取当前线程对象的名称
    String name = Thread.currentThread().getName();
    // 2.判断余额是否足够
    if(this.money >= money){
        // 3.余额足够
        System.out.println(name+"来取钱,吐出:"+money);
        // 4.更新余额
        this.money -= money;
        // 5.输出结果
        System.out.println(name+"来取钱"+money+"成功,取钱后剩余:"+this.money);
    }else{
        // 6.余额不足
        System.out.println(name+"来取钱,余额不足,剩余"+this.money);
    }
}

lock显示锁(方式三)

c.lock显示锁。
     java.util.concurrent.locks.Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,
     同步代码块/同步方法具有的功能Lock都有,除此之外更强大

     Lock锁也称同步锁,加锁与释放锁方法化了,如下:
          - `public void lock() `:加同步锁。
          - `public void unlock()`:释放同步锁。

总结:
     线程安全,性能差。
     线程不安全性能好。假如开发中不会存在多线程安全问题,建议使用线程不安全的设计类。
public Account(double money, String cardId) {
    this.money = money;
    this.cardId = cardId;
}
// 创建一把锁对象:因为账户对象对于小明小红是唯一的,所以这里的锁对象对于小明小红也是唯一的
private final Lock lock = new ReentrantLock();

// 小明、小红执行到此
public void drawMoney(double money) {
    // 1.先拿到是谁来取钱:取当前线程对象的名称
    String name = Thread.currentThread().getName();
    lock.lock(); // 上锁~!
    try{
        if(this.money >= money){
            // 3.余额足够
            System.out.println(name+"来取钱,吐出:"+money);
            // 4.更新余额
            this.money -= money;
            // 5.输出结果
            System.out.println(name+"来取钱"+money+"成功,取钱后剩余:"+this.money);
        }else{
            // 6.余额不足
            System.out.println(name+"来取钱,余额不足,剩余"+this.money);
        }
    }catch (Exception e){
        e.printStackTrace();
    }finally {
        lock.unlock(); // 解锁~!
    }

}

Lambda表达式

概述

目标:Lambda表达式的概述。

什么是Lambda表达式?
    Lambda表达式是JDK1.8开始之后的新技术,是一种代码的新语法。
    是一种特殊写法,
    作用:“核心目的是为了简化匿名内部类的代码写法”。

Lambda表达式的格式:
   (匿名内部类被重写方法的形参列表) -> {
      被重写方法的方法体代码。
   }

   -> 就是一个新语法,没有实际含义,但是不能省略!

Lambda表达式的使用前提:
    (1)Lambda表达式并不能简化所有匿名内部类的写法。
    (2)Lambda表达式只能简化接口中只有一个抽象方法的匿名内部类形式。

Lambda表达式只能简化函数式接口的匿名内部类写法:
    a.首先必须是接口。
    b.接口中只能有一个抽象方法。
小结:
   Lambda表达式只能简化接口中只有一个抽象方法的匿名内部类写法。
   接口中只有一个抽象方法的接口称为函数式接口。
   Lambda只能简化函数式接口的匿名内部类写法。

函数式接口

@FunctionalInterface函数式接口注解:
   一旦某个接口加上了这个注解,这个接口只能有且仅有一个抽象方法。
   这个接口就可以被Lambda表达式简化。
public class LambdaDemo {
    public static void main(String[] args) {
         Thread t = new Thread(new Runnable() {
             @Override
             public void run() {
                System.out.println(Thread.currentThread().getName()+":执行~~~");
             }
         });
         t.start();

        Thread t1 = new Thread(() -> {
                System.out.println(Thread.currentThread().getName()+":执行~~~");
        });
        t1.start();

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName()+":执行~~~");
        }).start();

        new Thread(() -> System.out.println(Thread.currentThread().getName()+":执行~~~")).start();
    }
}

匿名内部类写法

Lambda表达式的省略写法(进一步在Lambda表达式的基础上继续简化)
   (1)如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写,同时要省略分号!
   (2)如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写。
       此时,如果这行代码是return语句,必须省略return不写,同时也必须省略";"不写
   (3)参数类型可以省略不写。
   (4)如果只有一个参数,参数类型可以省略,同时()也可以省略。
Lambda简化Comparator接口匿名内部类写法;
 Collections.sort(lists ,( t1,  t2) -> t1.getAge() - t2.getAge());
public class LambdaDemo01 {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("胡伟光");
        names.add("甘挺");
        names.add("洪磊");

        names.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });

        names.forEach((String s) -> {
                System.out.println(s);
        });

        names.forEach((s) -> {
            System.out.println(s);
        });

        names.forEach(s -> {
            System.out.println(s);
        });

        names.forEach(s -> System.out.println(s) );

        names.forEach(System.out::println);
    }
}

方法引用

概述

目标:方法引用的概述。(了解)

方法引用:
        方法引用是为了进一步简化Lambda表达式的写法。
        方法引用的格式:类型或者对象::引用的方法。
关键语法是:“::”

小结:
   方法引用可以进一步简化Lambda表达式的写法。
   关键语法是:“::”
public class MethodDemo01 {
    public static void main(String[] args) {
        List<String> lists = new ArrayList<>();
        lists.add("java1");
        lists.add("java2");
        lists.add("java3");

        lists.forEach( s -> System.out.println(s));
        // 方法引用!
        lists.forEach(System.out::println);
    }
}

方法引用的方式(四种)

概述

目标:方法引用有四种形式:
     1.静态方法的引用。
     2.实例方法的引用。
     3.特定类型方法的引用。
     4.构造器引用。

静态方法引用(一)

1.静态方法的引用。
   引用格式:
      类名::静态方法。
   简化步骤:
      a.定义一个静态方法,把需要简化的代码放到一个静态方法中去。
   静态方法引用的注意事项
      ” 重要:被引用的方法的参数列表要和函数式接口中的抽象方法的参数列表一致。“
小结:
     静态方法引用的格式: 类名::静态方法。
     重要:被引用的方法的参数列表要和函数式接口中的抽象方法的参数列表一致,才可以引用简化!
public static int compareByAge(Student o1 , Student o2){
    return  o1.getAge() - o2.getAge();
}

public class MethodDemo01 {
    public static void main(String[] args) {
        List<Student> lists = new ArrayList<>();
        Student s1 = new Student("李铭",18,'男');
        Student s2 = new Student("冯龙",23,'男');
        Student s3 = new Student("王乐乐",21,'男');
        Collections.addAll(lists , s1 , s2 , s3);

        System.out.println(lists);

        Collections.sort(lists, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return o1.getAge() - o2.getAge();
            }
        });

        // Lambda表达式简化参数二:匿名内部类的Comparator写法!
        Collections.sort(lists, (Student o1, Student o2) -> {
            return o1.getAge() - o2.getAge();
        });

        Collections.sort(lists, (Student o1, Student o2) -> o1.getAge() - o2.getAge());

        Collections.sort(lists, ( o1, o2) -> o1.getAge() - o2.getAge());

        // 使用静态方法进行简化!
        Collections.sort(lists, ( o1, o2) -> Student.compareByAge(o1 , o2));
        // 如果前后参数是一样的,而且方法是静态方法,既可以使用静态方法引用
        Collections.sort(lists, Student::compareByAge);

        System.out.println(lists);
    }
}

实例方法的引用(二)

2.实例方法的引用
     格式: 对象::实例方法。
     简化步骤:
         a.定义一个实例方法,把需要的代码放到实例方法中去。
     实例方法引用的注意事项
        ” 重要:被引用的方法的参数列表要和函数式接口中的抽象方法的参数列表一致。“
public class MethodDemo01 {
    public static void main(String[] args) {
        List<String> lists = new ArrayList<>();
        lists.add("java1");
        lists.add("java2");
        lists.add("java3");

        // 对象是 System.out = new PrintStream();
        // 实例方法:println()
        // 前后参数正好都是一个
        lists.forEach(s -> System.out.println(s));
        lists.forEach(System.out::println);
    }
}

特定类型方法的引用(三)

3.特定类型方法的引用。
     特定类型:String ,任何类型。
     格式:特定类型::方法。
     注意:
        如果第一个参数列表中的形参中的第一个参数作为了后面的方法的调用者,
        并且其余参数作为后面方法的形参,那么就可以用特定类型方法引用了。

构造器引用(四)

4.构造器引用。
   格式是:类名::new。
   注意点:前后参数一致的情况下,又在创建对象就可以使用构造器引用
   s -> new Student(s) => Student::new
方法引用是可遇不可求,能用则用,不能用就不要用!
 List<String> lists = new ArrayList<>();
        lists.add("java1");
        lists.add("java2");
// 集合默认只能转成Object类型的数组。
        Object[] objs = lists.toArray();
        System.out.println("Object类型的数组:"+ Arrays.toString(objs));
        
// 我们想指定转换成字符串类型的数组!!
// 最新的写法可以结合构造器引用实现 。
   String[] strs1 = lists.toArray(s -> new String[s] );
        String[] strs2 = lists.toArray(String[]::new);
        System.out.println("String类型的数组:"+ Arrays.toString(strs2));

集合转换成特定类型的数组实例

public class ConstructorDemo01 {
    public static void main(String[] args) {
        List<String> lists = new ArrayList<>();
        lists.add("java1");
        lists.add("java2");
        lists.add("java3");

        // 集合默认只能转成Object类型的数组。
        Object[] objs = lists.toArray();
        System.out.println("Object类型的数组:"+ Arrays.toString(objs));

        // 我们想指定转换成字符串类型的数组!!
        // 最新的写法可以结合构造器引用实现 。
        // default <T> T[] toArray(IntFunction<T[]> generator)
        String[] strs = lists.toArray(new IntFunction<String[]>() {
            @Override
            public String[] apply(int value) {
                return new String[value];
            }
        });

        String[] strs1 = lists.toArray(s -> new String[s] );

        String[] strs2 = lists.toArray(String[]::new);

        System.out.println("String类型的数组:"+ Arrays.toString(strs2));
    }
}

Stream流

概述

目标: 演示Stream流的强大。

什么是Stream流?
    在Java 8中,得益于Lambda所带来的函数式编程,
    引入了一个全新的Stream流概念 ,用于解决已有集合/数组类库有的弊端。

Stream流能解决什么问题?
    可以解决已有集合类库或者数组API的弊端。
    Stream认为集合和数组操作的API很不好用,所以采用了Stream流简化集合和数组的操作!!

小结:
    Stream流是用来简化集合类库或者数组API的弊端。
    Stream流其实就一根传送带,元素在上面可以被Stream流操作。

获取

目标:Stream流的获取

Stream流式思想的核心:
            是先得到集合或者数组的Stream流(就是一根传送带)
            然后就用这个Stream流操作集合或者数组的元素。
            然后用Stream流简化替代集合操作的API.

集合获取流的API:
    (1) default Stream<E> stream();

小结:
    集合获取Stream流用: stream();
    数组:Arrays.stream(数组)   /  Stream.of(数组);
public class StreamDemo01 {
    public static void main(String[] args) {
        /** --------------------Collection集合获取流-------------------------------   */
        // Collection集合如何获取Stream流。
        Collection<String> c = new ArrayList<>();
        Stream<String> ss = c.stream();

        /** --------------------Map集合获取流-------------------------------   */
        Map<String, Integer> map = new HashMap<>();
        // 先获取键的Stream流。
        Stream<String> keyss = map.keySet().stream();
        // 在获取值的Stream流
        Stream<Integer> valuess = map.values().stream();
        // 获取键值对的Stream流(key=value: Map.Entry<String,Integer>)
        Stream<Map.Entry<String,Integer>> keyAndValues = map.entrySet().stream();

        /** ---------------------数组获取流------------------------------   */
        // 数组也有Stream流。
        String[] arrs = new String[]{"Java", "JavaEE" ,"Spring Boot"};
        Stream<String> arrsSS1 = Arrays.stream(arrs);
        Stream<String> arrsSS2 = Stream.of(arrs);
    }
}

常用API

目标:Stream流的常用API
forEach : 逐一处理(遍历)
count:统计个数
   -- long count();
filter : 过滤元素
   -- Stream<T> filter(Predicate<? super T> predicate)
limit : 取前几个元素
skip : 跳过前几个
map : 加工方法
concat : 合并流。
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
// 需求:从集合中筛选出所有姓张的人出来。然后再找出姓名长度是3的人。
list.stream().filter(s -> s.startsWith("张")).filter( s -> s.length()== 3 )
        .forEach(System.out::println); 
// 统计数量
        long count = list.stream().filter( s -> s.length() == 3 )
                .filter( s -> s.startsWith("张")).count();
        System.out.println(count);
        // 取前2个
        list.stream().filter(s -> s.length() == 3).limit(2)
                .forEach( System.out::println);
        // 跳过前2个
        list.stream().filter(s -> s.length() == 3).skip(2)
                .forEach( System.out::println);  、
                
        // 需求:把名称都加上“黑马的:+xxx”
        list.stream().map(a -> "黑马的:"+a).forEach(System.out::println);

        // 需求:把名称都加工厂学生对象放上去!!
        // list.stream().map(name -> new Student(name)).forEach(System.out::println);
        list.stream().map(Student::new).forEach(System.out::println); 
        
        (合并)
         // 数组流
        Stream<Integer> s1 = Stream.of(10, 20 ,30 ,40);
        // 集合流
        Stream<String> s2 = list.stream();
        // 合并流
        Stream<Object> s3 = Stream.concat(s1,s2);
        s3.forEach(System.out::println);
目标:终结与非终结方法。

终结方法:一旦Stream调用了终结方法,流的操作就全部终结了,不能继续使用,
    只能创建新的Stream操作。
    终结方法: foreach , count。

非终结方法:每次调用完成以后返回一个新的流对象,
    可以继续使用,支持链式编程!

收集stream流(把流转换成集合)

引入:
    Stream的作用是:把集合转换成一根传送带,借用Stream流的强大功能进行的操作。
    但是实际开发中数据最终的形式还是应该是集合,最终Stream流操作完毕以后还是要转换成集合。
    这就是收集Stream流。

收集Stream流的含义:就是把Stream流的数据转回到集合中去。

Stream流:手段。
集合:才是目的。

小结:
    收集Stream流的含义:就是把Stream流的数据转回到集合中去。

熟练函数式编程,如Lambda表达式简化函数式接口的匿名内部类形式,
以及四种方法引用简化Lambda表达式,以及流式编程思想如Stream简化集合或者数组的操作。

示例

public class StreamDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");
        list.add("张三丰");

        Stream<String> zhangLists = list.stream().filter(s -> s.startsWith("张"));
        // 把stream流转换成Set集合。
        Set<String> sets = zhangLists.collect(Collectors.toSet());
        System.out.println(sets);

        // 把stream流转换成List集合。
        Stream<String> zhangLists1 = list.stream().filter(s -> s.startsWith("张"));
        List<String> lists= zhangLists1.collect(Collectors.toList());
        System.out.println(lists);

        // 把stream流转换成数组。
        Stream<String> zhangLists2 = list.stream().filter(s -> s.startsWith("张"));
        Object[] arrs = zhangLists2.toArray();
        // 可以借用构造器引用申明转换成的数组类型!!!
        String[] arrs1 = zhangLists2.toArray(String[]::new);
    }
}

File

概述

目标:File类的概述和API

File类:代表操作系统的文件对象。
File类:是用来操作操作系统的文件对象的,删除文件,获取文件信息,创建文件(文件夹)...
广义来说操作系统认为文件包含(文件和文件夹)

File类的创建文件对象的API:
     包:java.io.File
    (1)构造器:
        -- public File(String pathname):根据路径获取文件对象
        -- public File(String parent , String child):根据父路径和文件名称获取文件对象!
        -- public File(File parent , String child)

File类创建文件对象的格式:
    a.File f = new File("绝对路径/相对路径");
        绝对路径:从磁盘的的盘符一路走到目的位置的路径。
            -- 绝对路径依赖具体的环境,一旦脱离环境,代码可能出错!!
            -- 一般是定位某个操作系统中的某个文件对象。
        相对路径:不带盘符的。(重点)
            -- 默认是直接相对到工程目录下寻找文件的。
            -- 相对路径只能用于寻找工程下的文件。
            -- 能用相对路径就应该尽量使用,可以跨平台!

    b.File f = new File("文件对象/文件夹对象");
        广义来说:文件是包含文件和文件夹的。
小结:
    创建文件对象可以用绝对路径也可以用相对路径。
    相对路径只能用于寻找工程下的文件。
    文件对象可以表示文件也可以表示文件夹!
public class FileDemo01 {
    public static void main(String[] args) {
        // 1.创建文件对象:使用绝对路径
        // 文件路径分隔符:
        //      -- a.使用正斜杠: /
        //      -- b.使用反斜杠: \\
        //      -- c.使用分隔符API:File.separator
        //File f1 = new File("D:/itcast/图片资源/beautiful.jpg");
        //File f1 = new File("D:"+File.separator+"itcast"+File.separator+"图片资源"+File.separator+"beautiful.jpg");
        File f1 = new File("D:\\itcast\\图片资源\\beautiful.jpg");
        System.out.println(f1.length()); // 获取文件的大小,字节大小

        // 2.创建文件对象:使用相对路径
        File f2 = new File("Day09Demo/src/dlei01.txt");
        System.out.println(f2.length());

        // 3.创建文件对象:代表文件夹。
        File f3 = new File("D:\\itcast\\图片资源");
        System.out.println(f3.exists());// 判断路径是否存在!!
    }
}

File获取功能的API

目标:File类的获取功能的API
- public String getAbsolutePath()  :返回此File的绝对路径名字符串。
- public String getPath()  : 获取创建文件对象的时候用的路径
- public String getName()  : 返回由此File表示的文件或目录的名称。
- public long length()  :    返回由此File表示的文件的长度。
public class FileDemo {
    public static void main(String[] args) {
        // 1.绝对路径创建一个文件对象
        File f1 = new File("D:/itcast/图片资源/meinv.jpg");
        // a.获取它的绝对路径。
        System.out.println(f1.getAbsolutePath());
        // b.获取文件定义的时候使用的路径。
        System.out.println(f1.getPath());
        // c.获取文件的名称:带后缀。
        System.out.println(f1.getName());
        // d.获取文件的大小:字节个数。
        System.out.println(f1.length());

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

        // 2.相对路径
        File f2 = new File("Day09Demo/src/dlei01.txt");
        // a.获取它的绝对路径。
        System.out.println(f2.getAbsolutePath());
        // b.获取文件定义的时候使用的路径。
        System.out.println(f2.getPath());
        // c.获取文件的名称:带后缀。
        System.out.println(f2.getName());
        // d.获取文件的大小:字节个数。
        System.out.println(f2.length());
    }
}

File判断功能的方法

目标:File类的判断功能的方法
- public boolean exists() :此File表示的文件或目录是否实际存在。
- public boolean isDirectory():此File表示的是否为目录。
- public boolean isFile() :此File表示的是否为文件
public class FileDemo {
    public static void main(String[] args) {
        // 1.文件对象。
        File f1 = new File("D:\\itcast\\图片资源\\meinv.jpg");
        // a.判断文件路径是否存在
        System.out.println(f1.exists()); // true
        // b.判断文件对象是否是文件,是文件返回true ,反之
        System.out.println(f1.isFile()); // true
        // c.判断文件对象是否是文件夹,是文件夹返回true ,反之
        System.out.println(f1.isDirectory()); // false
    }
}

File的创建和删除

目标:File类的创建和删除的方法
- public boolean createNewFile() :当且仅当具有该名称的文件尚不存在时,
         创建一个新的空文件。 (几乎不用的,因为以后文件都是自动创建的!)
- public boolean delete() :删除由此File表示的文件或目录。 (只能删除空目录)
- public boolean mkdir() :创建由此File表示的目录。(只能创建一级目录)
- public boolean mkdirs() :可以创建多级目录(建议使用的)
public class FileDemo {
    public static void main(String[] args) throws IOException {
        File f = new File("Day09Demo/src/dlei02.txt");
        // a.创建新文件,创建成功返回true ,反之
        System.out.println(f.createNewFile());

        // b.删除文件或者空文件夹
        System.out.println(f.delete());
        // 不能删除非空文件夹,只能删除空文件夹
        File f1 = new File("D:/itcast/aaaaa");
        System.out.println(f1.delete());

        // c.创建一级目录
        File f2 = new File("D:/itcast/bbbb");
        System.out.println(f2.mkdir());

        // d.创建多级目录
        File f3 = new File("D:/itcast/e/a/d/ds/fas/fas/fas/fas/fas/fas");
        System.out.println(f3.mkdirs());
    }
}

File目录的遍历

目标:File针对目录的遍历
- public String[] list():
        获取当前目录下所有的"一级文件名称"到一个字符串数组中去返回。
- public File[] listFiles()(常用):
        获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回(重点)
public class FileDemo {
    public static void main(String[] args) {
        File dir = new File("D:\\itcast");
        // a.获取当前目录对象下的全部一级文件名称到一个字符串数组返回。
        String[] names = dir.list();
        for (String name : names) {
            System.out.println(name);
        }
        // b.获取当前目录对象下的全部一级文件对象到一个File类型的数组返回。
        File[] files = dir.listFiles();
        for (File file : files) {
            System.out.println(file.getAbsolutePath());
        }

        // ---------拓展------------
        File f1 = new File("D:\\itcast\\图片资源\\beautiful.jpg");
        long time = f1.lastModified(); // 最后修改时间!
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println(sdf.format(time));
    }
}

字符集

目标:字符集/编码集

字符集:各个国家为自己国家的字符取的一套编号规则。
计算机的底层是不能直接存储字符的。
计算机的底层只能存储二进制。010101
二进制就是可以转成10进制的。10进制就是整数编号。
101 = 1*2^0 + 0*2^1 + 1*2^2 = 5

结论:计算机的底层可以存储编号。

1B = 8b   计算机中的最小单位是字节B.

美国人:
    8个开关一组就可以编码字符。 1个字节。
    2^8 = 256
    一个字节存储一个字符完全够用了。

    a  97
    b  98

    A  65
    B  66

    0  48
    1  49
    这套编码是ASCII编码。

    英文和数字在底层存储的时候都是采用1个字节存储的。

中国人:
    中国人的字符很多:9万左右字符。
    中国人一般采用2个字节编码一个中文字符。
    这套编码叫:GBK编码。
    它也必须兼容ASCII编码表。
    ....

美国人:
    我来收集全球所有的字符,统一编号。这套编码叫 Unicode编码(万国码)
    UTF-8就是变种形式。
    UTF-8一个中文一般占3个字节。
    它也必须兼容ASCII编码表。

小结:
    英文和数字在任何编码集中都是一样的,都占1个字节。
    GBK编码中,1个中文字符一般占2个字节。
    UTF-8编码中,1个中文字符一般占3个字节。
    技术人员都应该使用UTF-8编码!

    编码前与编码后的编码集必须一致才不会乱码!!

    GBK   我爱你[oo oo oo]     GBK     不会乱码!
    GBK   我爱你[oo oo oo]     UTF-8   会乱码!

    英文和数字在任何编码集中可以通用,不会乱码!!

IO流

概述和分类

目标:IO流读写数据。

IO输入输出流:输入/输出流。
    Input:输入。
    Output:输出。

引入:
    File类只能操作文件对象本身,不能读写文件对象的内容。
    读写数据内容,应该使用IO流。

IO流是一个水流模型:IO理解成水管,把数据理解成水流。

IO流的分类:
    按照流的方向分为:输入流,输出流。
       (1)输出流:以内存为基准,把内存中的数据写出到磁盘文件或者网络介质中去的流称为输出流。
               输出流的作用:写数据到文件,或者写数据发送给别人。

       (2)输入流:以内存为基准,把磁盘文件中的数据或者网络中的数据读入到内存中去的流称为输入流。
               输入流的作用:读取数据到内存。

    按照流的内容分为: 字节流,字符流。
       (1)字节流:流中的数据的最小单位是一个一个的字节,这个流就是字节流。
       (2)字符流:流中的数据的最小单位是一个一个的字符,这个流就是字符流。(针对于文本内容)


所以流大体分为四大类:
    字节输入流:以内存为基准,把磁盘文件中的数据或者网络中的数据以一个一个的字节的形式读入到内存中去的流称为字节输入流。
    字节输出流:以内存为基准,把内存中的数据以一个一个的字节写出到磁盘文件或者网络介质中去的流称为字节输出流。
    字符输入流:以内存为基准,把磁盘文件中的数据或者网络中的数据以一个一个的字符的形式读入到内存中去的流称为字符输入流。
    字符输出流:以内存为基准,把内存中的数据以一个一个的字符写出到磁盘文件或者网络介质中去的流称为字符输出流。
小结:
    IO流是读写传输数据的,IO流有很多种,每种流有自己的功能特点。

IO流的体系

IO流的体系:
        字节流                                   字符流
字节输入流           字节输出流               字符输入流      字符输出流
InputStream         OutputStream           Reader         Writer     (抽象类)
FileInputStream     FileOutputStream       FileReader     FileWriter (子类实现类)

字节流

字节输入流

目标:字节输入流的使用-按照字节数组读取。

IO流的体系:
        字节流                                   字符流
字节输入流           字节输出流               字符输入流       字符输出流
InputStream         OutputStream           Reader         Writer     (抽象类)
FileInputStream     FileOutputStream       FileReader     FileWriter (实现类)

a.FileInputStream文件字节输入流。
    -- 作用:以内存为基准,把磁盘文件中的数据按照字节的形式读入到内存中的流。
            简单来说,就是按照字节读取文件数据到内存。
    -- 构造器:
       1.public FileInputStream(File path):创建一个字节输入流管道与源文件对象接通。
       2.public FileInputStream(String pathName):创建一个字节输入流管道与文件路径对接。
    -- 方法:
       1.public int read():每次读取一个字节返回!读取完毕会返回-1。
       2.public int read(byte[] buffer):从字节输入流中读取字节到字节数组中去,
            返回读取的字节数量,没有字节可读返回-1。
    小结:
      public int read(byte[] buffer):从字节输入流中读取字节到字节数组中去,
            返回读取的字节数量,没有字节可读返回-1。
       使用字节数组读取内容,效率可以。
       但是使用字节数组读取文本内容输出,也无法避免中文读取输出乱码的问题。

字节读取

// 1.创建文件对象定位dlei01.txtFile file = new File("Day09Demo/src/dlei01.txt");// 2.创建一个字节输入流管道与源文件接通InputStream is = new FileInputStream(file); int ch = 0 ;  while((ch = is.read())!= -1){            System.out.print((char) ch);        }        

数组读取

InputStream is = new FileInputStream("Day09Demo/src/dlei02.txt");
  byte[] buffer = new byte[3];
        int len ; // 存储每次读取的字节数。
        while((len = is.read(buffer)) != -1){
            // 读取了多少就倒出多少!
            String rs = new String(buffer , 0 , len);
            System.out.print(rs);
        }

字节输出流

目标:字节输出流的使用。
a.FileOutputStream文件字节输出流
    -- 作用:以内存为基准,把内存中的数据,按照字节的形式写出到磁盘文件中去。
             简单来说,把内存数据按照字节写出到磁盘文件中去。
    -- 构造器:
        public FileOutputStream(File file):创建一个字节输出流管道通向目标文件对象。
        public FileOutputStream(String file):创建一个字节输出流管道通向目标文件路径。
        public FileOutputStream(File file , boolean append):创建一个追加数据的字节输出流管道通向目标文件对象。
        public FileOutputStream(String file , boolean append):创建一个追加数据的字节输出流管道通向目标文件路径。
    -- 方法:
       public void write(int a):写一个字节出去 。
       public void write(byte[] buffer):写一个字节数组出去。
       public void write(byte[] buffer , int pos , int len):写一个字节数组的一部分出去。
                    参数一,字节数组;参数二:起始字节索引位置,参数三:写多少个字节数出去。
小结:
    字节输出流只能写字节出去。
    字节输出流默认是覆盖数据管道。
    换行用: os.write("\r\n".getBytes());
    关闭和刷新:刷新流可以继续使用,关闭包含刷新数据但是流就不能使用了!
    
       FileOutputStream字节输出流每次启动写数据的时候都会先清空之前的全部数据,从新写入。
    小结:
        覆盖数据管道: OutputStream os = new FileOutputStream("Day09Demo/out05");
        追加数据的管道:OutputStream os = new FileOutputStream("Day09Demo/out05" , true);
                 参数二代表了此管道是追加数据的管道,不会覆盖之前的数据!

字节流做文件复制

目标:字节流做文件复制。

字节流复制的思想:
    字节是计算机中一切文件的组成,所以
    字节流适合做一切文件的复制。
    复制是把源文件的全部字节一字不漏的转移到目标文件,只要文件前后的格式一样,绝对不会有问题。

需求:
    原文件:D:\itcast\图片资源\meinv.jpg
    目标文件:D:\itcast\meimei.jpg
分析步骤:
    (1)创建一个字节输入流管道与源文件接通。
    (2)创建一个字节输出流与目标文件接通。
    (3)创建一个字节数组作为桶
    (4)从字节输入流管道中读取数据,写出到字节输出流管道即可。
    (5)关闭资源!
public class CopyDemo01 {
    public static void main(String[] args) {
        InputStream is = null ;
        OutputStream os = null ;
        try{
            /** (1)创建一个字节输入流管道与源文件接通。 */
            is = new FileInputStream("D:\\itcast\\图片资源\\meinv.jpg");
            /** (2)创建一个字节输出流与目标文件接通。*/
            os = new FileOutputStream("D:\\itcast\\meimei.jpg");
            /** (3)创建一个字节数组作为桶*/
            byte[] buffer = new byte[1024];
            /** (4)从字节输入流管道中读取数据,写出到字节输出流管道即可。*/
            int len = 0;
            while((len = is.read(buffer)) != -1){
                // 读取多少就倒出多少
                os.write(buffer, 0 , len);
            }
            System.out.println("复制完成!");
        }catch (Exception e){
            e.printStackTrace();
        } finally {
            /**(5)关闭资源! */
            try{
                if(os!=null)os.close();
                if(is!=null)is.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

资源释放新方式

目标:JDK 1.7开始之后释放资源的新方式

try-with-resources:
     try(
            // 这里只能放置资源对象,用完会自动调用close()关闭
     ){

     }catch(Exception e){
          e.printStackTrace();
     }
什么是资源?
     资源类一定是实现了Closeable接口,实现这个接口的类就是资源
     有close()方法,try-with-resources会自动调用它的close()关闭资源。
public class CopyDemo02 {
    public static void main(String[] args) {
        try(
                /** (1)创建一个字节输入流管道与源文件接通。 */
                InputStream is  = new FileInputStream("D:\\itcast\\图片资源\\meinv.jpg");
                /** (2)创建一个字节输出流与目标文件接通。*/
                OutputStream os = new FileOutputStream("D:\\itcast\\meimei.jpg");
                /** (5)关闭资源!是自动进行的 */
        ){
            /** (3)创建一个字节数组作为桶*/
            byte[] buffer = new byte[1024];
            /** (4)从字节输入流管道中读取数据,写出到字节输出流管道即可。*/
            int len = 0;
            while((len = is.read(buffer)) != -1){
                // 读取多少就倒出多少
                os.write(buffer, 0 , len);
            }
            System.out.println("复制完成!");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

字符流

概述

 IO流的体系:
            字节流                                   字符流
     字节输入流           字节输出流               字符输入流       字符输出流
     InputStream         OutputStream            Reader         Writer     (抽象类)
     FileInputStream     FileOutputStream        FileReader     FileWriter (实现类)
拓展:解决字节输入流读取中文内容输出乱码的问题。

引入:
    一个一个字节读取中文输出
    一个一个字节数组读取中文输出均无法避免乱码。
如何实现读取可以避免乱码呢?
    1.定义一个字节数组与文件的大小刚刚一样大,然后一桶水读取全部字节数据再输出!
小结:
    定义一个字节数组与文件的大小刚刚一样大,然后一桶水读取全部字节数据再输出!
    可以避免中文读取输出乱码,但是如果读取的文件过大,会出现内存溢出!!

    字节流并不适合读取文本文件内容输出,读写文件内容建议使用字符流。

字符输入流

目标:字符输入流的使用。

IO流的体系:
       字节流                                   字符流
字节输入流           字节输出流               字符输入流       字符输出流
InputStream         OutputStream            Reader         Writer     (抽象类)
FileInputStream     FileOutputStream        FileReader     FileWriter (实现类)

c.FileReader:文件字符输入流。
    -- 作用:以内存为基准,把磁盘文件的数据以字符的形式读入到内存。
       简单来说,读取文本文件内容到内存中去。
    -- 构造器:
       public FileReader(File file):创建一个字符输入流与源文件对象接通。
       public FileReader(String filePath):创建一个字符输入流与源文件路径接通。
    -- 方法:
       public int read(): 读取一个字符的编号返回! 读取完毕返回-1
       public int read(char[] buffer):读取一个字符数组,读取多少个字符就返回多少个数量,读取完毕返回-1
小结:
   字符流一个一个字符的读取文本内容输出,可以解决中文读取输出乱码的问题。
   字符流很适合操作文本文件内容。
   但是:一个一个字符的读取文本内容性能较差!!

单个字符读取

  		Reader fr = new FileReader("Day10Demo/src/dlei01.txt"); 		int ch ;        while ((ch = fr.read()) != -1){            System.out.print((char)ch);        }

字符数组读取

// 1.创建一个字符输入流管道与源文件接通
Reader fr = new FileReader("Day10Demo/src/dlei02.txt");
// 2.按照字符数组读取内容
// a.按照字符数组读取数据使用循环
          char[] buffer = new char[1024]; // 1K
          // b.定义一个整数记录每次桶读取的字符数据量。
          int len;
          while((len = fr.read(buffer)) != -1 ) {
              // 读取多少倒出多少字符
              System.out.print(new String(buffer, 0 , len));
          }

字符输出流

目标:字符输出流的使用。

IO流的体系:
       字节流                                   字符流
字节输入流           字节输出流               字符输入流       字符输出流
InputStream         OutputStream           Reader         Writer     (抽象类)
FileInputStream     FileOutputStream       FileReader     FileWriter (实现类)

d.FileWriter文件字符输出流的使用。
   -- 作用:以内存为基准,把内存中的数据按照字符的形式写出到磁盘文件中去。
       简单来说,就是把内存的数据以字符写出到文件中去。
   -- 构造器:
      public FileWriter(File file):创建一个字符输出流管道通向目标文件对象。
      public FileWriter(String filePath):创建一个字符输出流管道通向目标文件路径。
      public FileWriter(File file,boolean append):创建一个追加数据的字符输出流管道通向目标文件对象。
      public FileWriter(String filePath,boolean append):创建一个追加数据的字符输出流管道通向目标文件路径。
   -- 方法:
        a.public void write(int c):写一个字符出去
        b.public void write(String c)写一个字符串出去:
        c.public void write(char[] buffer):写一个字符数组出去
        d.public void write(String c ,int pos ,int len):写字符串的一部分出去
        e.public void write(char[] buffer ,int pos ,int len):写字符数组的一部分出去
小结:
   字符输出流可以写字符数据出去,总共有5个方法写字符。
   覆盖管道:
        Writer fw = new FileWriter("Day10Demo/src/dlei03.txt"); // 覆盖数据管道
   追加数据管道:
        Writer fw = new FileWriter("Day10Demo/src/dlei03.txt",true); // 追加数据管道
   换行:
        fw.write("\r\n"); // 换行
   读写字符文件数据建议使用字符流。
public class FileWriterDemo03 {
    public static void main(String[] args) throws Exception {
        // 1.创建一个字符输出流管道通向目标文件路径
        //Writer fw = new FileWriter("Day10Demo/src/dlei03.txt"); // 覆盖数据管道
        Writer fw = new FileWriter("Day10Demo/src/dlei03.txt",true); // 追加数据管道

        // 2.写一个字符出去:public void write(int c):写一个字符出去
        fw.write(97);   // 字符a
        fw.write('b');  // 字符b
        fw.write('磊'); // 字符磊,此时没有任何问题。
        fw.write("\r\n"); // 换行

        // 3.写一个字符串出去:public void write(String c)写一个字符串出去:
        fw.write("Java是最优美的语言!");
        fw.write("我们在黑马学习它!");
        fw.write("\r\n"); // 换行

        // 4.写一个字符数组出去:public void write(char[] buffer):写一个字符数组出去
        fw.write("我爱中国".toCharArray());
        fw.write("\r\n"); // 换行

        // 5.写字符串的一部分出去: public void write(String c ,int pos ,int len):写字符串的一部分出去
        fw.write("Java是最优美的语言!",0,9);
        fw.write("\r\n"); // 换行

        // 6.写字符数组的一部分出去:public void write(char[] buffer ,int pos ,int len):写字符数组的一部分出去
        fw.write("我爱中国".toCharArray(),0 ,2);
        fw.write("\r\n"); // 换行

        fw.close();
    }
}

缓存流

概述

目标:缓冲流的概述和分类。

       字节流                                             字符流
字节输入流               字节输出流                 字符输入流         字符输出流
InputStream             OutputStream             Reader            Writer   (抽象类)
FileInputStream         FileOutputStream         FileReader        FileWriter(实现类,低级流,原始流)
BufferedInputStream     BufferedOutputStream     BufferedReader    BufferedWriter(实现类,缓冲流)

什么是缓冲流:缓冲流可以提高字节流和字符流的读写数据的性能。
缓冲流分为四类:
    (1)BufferedInputStream:字节缓冲输入流,可以提高字节输入流读数据的性能。
    (2)BufferedOutStream:  字节缓冲输出流,可以提高字节输出流写数据的性能。
    (3)BufferedReader:  字符缓冲输入流,可以提高字符输入流读数据的性能。
    (4)BufferedWriter:  字符缓冲输出流,可以提高字符输出流写数据的性能。

字节缓存输入流

目标:字节缓冲输入流的使用。

a.字节缓冲输入流:BufferedInputStream
       -- 作用:可以把低级的字节输入流包装成一个高级的缓冲字节输入流管道,
               从而提高字节输入流读数据的性能。
       -- 构造器: public BufferedInputStream(InputStream in)
       -- 原理:缓冲字节输入流管道自带了一个8KB的缓冲池,每次可以直接借用操作系统的功能最多提取8KB
           的数据到缓冲池中去,以后我们直接从缓冲池读取数据,所以性能较好!
小结:
    字节缓冲输入流:BufferedInputStream
    可以把低级的字节输入流包装成一个高级的缓冲字节输入流管道,从而提高字节输入流读数据的性能。
    功能几乎无变化。
public class BufferedInputStreamDemo01 {
    public static void main(String[] args) throws Exception {
        // 1.定义一个低级的字节输入流与源文件接通
        InputStream is = new FileInputStream("Day10Demo/src/dlei04.txt");

        // 3.把低级的字节输入流包装成一个高级的缓冲字节输入流。
        BufferedInputStream bis = new BufferedInputStream(is);

        // 2.定义一个字节数组按照循环读取。
        byte[] buffer = new byte[3];
        int len ;
        while((len = is.read(buffer)) != -1){
            String rs = new String(buffer, 0 , len);
            System.out.print(rs);
        }
    }
}

字节缓存输出流

目标:字节缓冲输出流的使用。
字节缓冲输出流:BufferedOutputStream
    -- 作用:可以把低级的字节输出流包装成一个高级的缓冲字节输出流,从而提高写数据的性能。
    -- 构造器:public BufferedOutputStream(OutputStream os)
    -- 原理:缓冲字节输出流自带了8KB缓冲池,数据就直接写入到缓冲池中去,性能极高了!
小结:
       字节缓冲输出流可以把低级的字节输出流包装成一个高级的缓冲字节输出流,从而提高写数据的性能。
       功能几乎不变。
// 1.写一个原始的字节输出流
OutputStream os = new FileOutputStream("Day10Demo/src/dlei05.txt");
// 3.把低级的字节输出流包装成一个高级的缓冲字节输出流
BufferedOutputStream bos =  new BufferedOutputStream(os);
// 2.写数据出去
bos.write('a');
bos.write(100);
bos.write('b');
bos.write("我爱中国".getBytes());
bos.close();

字符缓存输入流

目标:字符缓冲输入流的使用。

           字节流                                     字符流
字节输入流               字节输出流              字符输入流         字符输出流
InputStream             OutputStream          Reader            Writer   (抽象类)
FileInputStream         FileOutputStream      FileReader        FileWriter(实现类)
BufferedInputStream     BufferedOutputStream  BufferedReader    BufferedWriter(实现类,缓冲流)

字符缓冲输入流:BufferedReader
     -- 作用:字符缓冲输入流可以把字符输入流包装成一个高级的缓冲字符输入流,
              可以提高字符输入流读数据的性能。
     -- 构造器:public BufferedReader(Reader reader):
     -- 原理:缓冲字符输入流默认会有一个8K的字符缓冲池,可以提高读字符的性能。
     -- 缓冲字符输入流除了提高了字符输入流的读数据性能,
        缓冲字符输入流还多了一个按照行读取数据的功能(重点):
            public String readLine(): 读取一行数据返回,读取完毕返回null;
    小结:
       字符缓冲输入流可以把字符输入流包装成一个高级的缓冲字符输入流,
       可以提高字符输入流读数据的性能。
       除此之外多了一个按照行读取数据的功能:
           public String readLine(): 读取一行数据返回,读取完毕返回null;
// 1.定义一个原始的字符输入流读取源文件
Reader fr = new FileReader("Day10Demo/src/dlei06.txt");

// 3.把低级的字符输入流管道包装成一个高级的缓冲字符输入流管道
BufferedReader br = new BufferedReader(fr);
// 定义一个字符串变量存储每行数据
String line;
// 使用一个循环读取数据(经典代码)
while((line = br.readLine())!=null){
    System.out.println(line);
}
 br.close();

字符缓存输出流

目标:字符缓冲输出流的使用。
字符缓冲输出流:BufferedWriter
       -- 作用:把字符输出流包装成一个高级的缓冲字符输出流,提高写字符数据的性能。
       -- 构造器:public BufferedWriter(Writer writer):
       -- 原理:高级的字符缓冲输出流多了一个8k的字符缓冲池,写数据性能极大提高了!
       -- 字符缓冲输出流除了提高字符输出流写数据的性能,还多了一个换行的特有功能:
            public void newLine():新建一行。
小结:
   缓冲字符输出流可以把低级的字符输出流进行包装。提高了写字符的性能。
   多了一个换行的功能:public void newLine():新建一行。
// 1.定义一个低级的字符输出流写数据出去
Writer fw = new FileWriter("Day10Demo/src/dlei07.txt",true);

// 3.把低级的字符输出流包装成高级的缓冲字符输出流
BufferedWriter bw = new BufferedWriter(fw);

// 2.写字符输出
bw.write("我在黑马学IO流~~~~");
bw.newLine(); // 换行
bw.write("我在黑马学IO流~~~~");
bw.newLine();// 换行

bw.close();

字符流不同编码读取乱码的问题

目标:字符流不同编码读取乱码的问题。
引入:
    我们之前用的代码编码和文件编码都是UTF-8编码,字符流读取没有出现乱码!!
    字符流读取:
        代码编码            文件编码         中文情况。
        UTF-8              UTF-8           不乱码!
        GBK                GBK             不乱码!
        UTF-8              GBK             乱码!
小结:
    如果代码编码和读取的文件编码一致。字符流读取的时候不会乱码。
    如果代码编码和读取的文件编码不一致。字符流读取的时候会乱码。
// 1.定义一个原始的字符输入流读取源文件
//  代码UTF-8  文件UTF-8 不会出现乱码!
// Reader fr = new FileReader("Day10Demo/src/dlei06.txt");
//  代码UTF-8  文件GBK   会出现乱码!
Reader fr = new FileReader("D:\\itcast\\网络编程公开课\\Netty.txt");
// 2.把低级的字符输入流管道包装成一个高级的缓冲字符输入流管道
BufferedReader br = new BufferedReader(fr);
// 3.定义一个字符串变量存储每行数据
String line;
// 使用一个循环读取数据(经典代码)
while((line = br.readLine())!=null){
    System.out.println(line);
}

转换流

字符输入转换流

目标:字符输入转换流InputStreamReader的使用。

        字节流                                     字符流
字节输入流               字节输出流              字符输入流            字符输出流
InputStream             OutputStream          Reader               Writer   (抽象类)
FileInputStream         FileOutputStream      FileReader           FileWriter(实现类)
BufferedInputStream     BufferedOutputStream  BufferedReader       BufferedWriter(实现类,缓冲流)
                                              InputStreamReader    OutputStreamWriter
字符输入转换流InputStreamReader:
     -- 作用:可以解决字符流读取不同编码乱码的问题。
             可以把原始的字节流按照当前默认的代码编码转换成字符输入流。
             也可以把原始的字节流按照指定编码转换成字符输入流

     -- 构造器:
           public InputStreamReader(InputStream is):可以使用当前代码默认编码转换成字符流,几乎不用!
           public InputStreamReader(InputStream is,String charset):可以指定编码把字节流转换成字符流
小结:
    字符输入转换流可以把字节输入流按照默认编码转换成字符输入流。
       -- Reader isr = new InputStreamReader(is); // 使用当前代码默认编码UTF-8转换成字符流,几乎不用!
    字符输入转换流也可以指定编码把字节输入流转换成字符输入流。
       -- Reader isr = new InputStreamReader(is,"GBK"); // 指定编码把字节流转换成字符流
    字符输入转换流可以解决不同编码读取乱码的问题!
// 代码:UTF-8    文件:GBK(ab我爱你: o o [oo] [oo] [oo])
// 1.提取GBK文件的原始字节流
InputStream is = new FileInputStream("D:\\itcast\\网络编程公开课\\Netty.txt");
// 2.把原始字节输入流通过转换流,转换成 字符输入转换流InputStreamReader
//Reader isr = new InputStreamReader(is); // 使用当前代码默认编码UTF-8转换成字符流,几乎不用!
Reader isr = new InputStreamReader(is,"GBK"); // 指定编码把字节流转换成字符流
// 3.包装成缓冲流
BufferedReader br = new BufferedReader(isr);
// 4.定义一个字符串变量存储每行数据
String line;
// 使用一个循环读取数据(经典代码)
while((line = br.readLine())!=null){
    System.out.println(line);
}

字符输出转换流

目标:字符输出转换OutputStreamWriter流的使用。                             
字符输出转换流:OutputStreamWriter
      -- 作用:可以指定编码把字节输出流转换成字符输出流。
              可以指定写出去的字符的编码。
      -- 构造器:
           public OutputStreamWriter(OutputStream os) :   用当前默认编码UTF-8把字节输出流转换成字符输出流
           public OutputStreamWriter(OutputStream os , String charset):指定编码把字节输出流转换成字符输出流
小结:
   字符输出转换流可以指定编码把字节输出流转换成字符输出流。
   从而实现指定写出去的字符编码!
// 1.写一个字节输出流通向文件
OutputStream os = new FileOutputStream("Day10Demo/src/dlei07.txt");

// 2.把字节输出流转换成字符输出流。
// Writer fw = new OutputStreamWriter(os); // .把字节输出流按照默认编码UTF-8转换成字符输出流。
Writer fw = new OutputStreamWriter(os,"GBK"); // .  把字节输出流按照指定编码GBK转换成字符输出流。
fw.write("abc我是中国人");
fw.close();

打印流

目标:打印流PrintStream / PrintWriter.

打印流的作用:
    1.可以方便,快速的写数据出去。
    2.可以实现打印啥出去,就是啥出去。
打印流的构造器:
    public PrintStream(OutputStream os):
    public PrintStream(String filepath):

小结:
     打印流可以方便,且高效的打印各种数据。
     PrintStream不光可以打印数据,还可以写"字节数据"出去。
     PrintWriter不光可以打印数据,还可以写"字符数据"出去。

对象序列化和反序列化

概述

目标:对象序列化技术。

对象序列化:就是把Java对象数据直接存储到文件中去。           对象 => 文件中
对象反序列化:就是把Java对象的文件数据恢复到Java对象中。     文件中 => 对象

              字节流                                    字符流
字节输入流               字节输出流              字符输入流          字符输出流
InputStream             OutputStream          Reader             Writer   (抽象类)
FileInputStream         FileOutputStream      FileReader         FileWriter(实现类)
BufferedInputStream     BufferedOutputStream  BufferedReader     BufferedWriter(实现类,缓冲流)
                                              InputStreamReader  OutputStreamWriter
ObjectInputStream       ObjectOutputStream

对象序列化技术

 对象序列化流(对象字节输出流):ObjectOutputStream
     -- 作用:把内存中的Java对象数据保存到文件中去。
     -- 构造器:   public ObjectOutputStream(OutputStream out)
     -- 序列化方法:public final void writeObject(Object obj)

 注意:对象如果想参与序列化,对象必须实现序列化接口 implements Serializable ,否则序列化失败!

小结:
     注意:对象如果想参与序列化,对象必须实现序列化接口 implements Serializable ,否则序列化失败!
     对象序列化使用的流是对象字节输出流:ObjectOutputStream
public class User implements Serializable {
    // 加入序列版本号
    private static final long serialVersionUID = 1L;

    private String loginName;
    // transient修饰的成员变量,将不参与序列化!
    private transient String passWord;
    private String userName;

    public User() {
    }

    public User(String loginName, String passWord, String userName) {
        this.loginName = loginName;
        this.passWord = passWord;
        this.userName = userName;
    }
}


public class SerializeDemo01 {
    public static void main(String[] args) throws Exception {
        // 1.创建User用户对象
        User user = new User("tsgz","003197","铁扇公主");
        // 2.创建低级的字节输出流通向目标文件
        OutputStream os = new FileOutputStream("Day10Demo/src/obj.dat");
        // 3.把低级的字节输出流包装成高级的对象字节输出流ObjectOutputStream
        ObjectOutputStream oos = new ObjectOutputStream(os);
        // 4.通过对象字节输出流序列化对象:
        oos.writeObject(user);
        // 6.释放资源
        oos.close();
        System.out.println("序列化对象成功~~~~");
    }
}

对象反序列化

目标:对象序反列化技术。
对象反序列化(对象字节输入流):ObjectInputStream
       -- 作用:读取序列化的对象文件恢复到Java对象中。
       -- 构造器:public ObjectInputStream(InputStream is)
       -- 方法:public final Object readObject()

如果一个字段不想参数序列化:
        transient修饰该成员变量,它将不参与序列化!
序列化版本号:
    // 加入序列版本号
    private static final long serialVersionUID = 2L;
    必须序列化使用的版本号和反序列化使用的版本号一致才可以正常反序列化!否则报错!
小结:
   对象反序列化可以把对象序列化的文件数据恢复成Java对象。
   对象反序列化使用的流是:ObjectInputStream.
public class SerializeDemo02 {
    public static void main(String[] args) throws Exception {
        // 1.定义一个低级的字节输入流通向源文件
        InputStream is = new FileInputStream("Day10Demo/src/obj.dat");
        // 2.把字节输入流包装成高的对象字节输入流
        ObjectInputStream ois = new ObjectInputStream(is);
        // 3.反序列化
        User user = (User) ois.readObject();
        System.out.println(user);
        System.out.println("反序列化完成!");
    }
}

Properties属性集对象

目标:Properties的概述和使用(框架底层使用,了解这个技术即可)。(保存数据到属性文件)

Properties:属性集对象。
     其实就是一个Map集合。也就是一个键值对集合。但是我们一般不会当集合使用,
     因为有HashMap。
Properties核心作用:
     Properties代表的是一个属性文件,可以把键值对的数据存入到一个属性文件中去。
     属性文件:后缀是.properties结尾的文件,里面的内容都是 key=value。

大家在后期学的很多大型框架技术中,属性文件都是很重要的系统配置文件。
    users.properties
            admin=123456
            dlei=dlei

 需求:使用Properties对象生成一个属性文件,里面存入用户名和密码信息。

 Properties的方法:
 -- public Object setProperty(String key, String value) : 保存一对属性。
 -- public String getProperty(String key) :使用此属性列表中指定的键搜索属性值
 -- public Set<String> stringPropertyNames() :所有键的名称的集合
 -- public void store(OutputStream out, String comments):保存数据到属性文件中去
 -- public void store(Writer fw, String comments):保存数据到属性文件中去

小结:
    属性集对象Properties实际上是一个Map集合,可以实现把键值对数据保存到
    属性文件中去!!
// a.创建一个属性集对象:Properties的对象。
Properties properties = new Properties();
properties.setProperty("admin" , "123456");
properties.setProperty("dlei" , "101333");
System.out.println(properties);

// b.把属性集对象的数据存入到属性文件中去(重点)
OutputStream os = new FileOutputStream("Day10Demo/src/users.properties");
/**
 * 参数一:被保存数据的输出管道
 * 参数二:保存心得。就是对象保存的数据进行解释说明!
 */
properties.store(os , "i am very happy!!我快乐的保存了用户数据!");

反射

概述

目标:反射的概念。

反射,注解,代理,泛型是Java的高级技术,是以后框架的底层原理必须使用到的技术。

反射:是Java独有的技术。是Java技术显著的特点。

反射是指对于任何一个类,在"运行的时候"都可以直接得到这个类全部成分。
    在运行时,可以直接得到这个类的构造器对象。(Constructor)
    在运行时,可以直接得到这个类的成员变量对象。(Field)
    在运行时,可以直接得到这个类的成员方法对象。(Method)

反射的核心思想和关键就是得到:编译以后的class文件对象。

反射提供了一个Class类型,就是可以得到编译以后的class类对象。
    HelloWorld.java -> javac -> HelloWorld.class

    Class c = HelloWorld.class;


注意:反射是工作在运行时的技术,因为只有运行之后才会有class类对象。

小结:
    反射的核心思想和关键就是得到:编译以后的class文件对象。
    反射是在运行时获取类的字节码文件对象:然后可以解析类中的全部成分。

获取Class类对象

引入:
    反射是通过先得到编译以后的Class类对象:字节码文件。
    然后才可以得到类中的全部成分,进行一些功能设计。

反射为一个类的全部成分都设计了一个类型来代表这个对象:
    Class : 字节码文件的类型
    Constructor : 构造器的类型
    Field : 成员变量的类型
    Method : 方法的类型

反射技术的第一步永远是先得到Class类对象:有三种方式获取
    (1) 类名.class
    (2) 通过类的对象.getClass()方法
    (3) Class.forName("类的全限名")
            -- public static Class<?> forName(String className)

Class类下的方法:
     String getSimpleName(); 获得类名字符串:类名
     String getName();  获得类全名:包名+类名
     T newInstance() ;  创建Class对象关联类的对象,其实底层也是调用无参数构造器,已经被淘汰。
小结:
    Class类对象的获取有三种方式:
        1.类名.class。
        2.通过类的对象.getClass()方法。
        3.Class.forName("类的全限名")。
    Class类的方法:
        String getSimpleName(); 获得类名字符串:类名
        String getName();  获得类全名:包名+类名
// 反射的第一步永远是先得到类的Class文件对象: 字节码文件。
// 1.类名.class
Class c1 = Student.class;
System.out.println(c1);

// 2.对象.getClass()
Student swk = new Student();
Class c2 = swk.getClass();
System.out.println(c2);

// 3.Class.forName("类的全限名")
// 直接去加载该类的class文件。
Class c3 = Class.forName("com.itheima._03反射_获取Class类对象.Student");
System.out.println(c3);

System.out.println(c1.getSimpleName()); // 获取类名本身(简名)
System.out.println(c1.getName()); // 获取类的全限名
// Student s1 = (Student) c1.newInstance(); // 调用无参数构造器得到对象,被淘汰了!

Constructor构造器对象

反射获取Constructor构造器对象

目标:反射_获取Constructor构造器对象.

反射的第一步是先得到Class类对象。(Class文件)

反射中Class类型获取构造器提供了很多的API:
     1. Constructor getConstructor(Class... parameterTypes)
        根据参数匹配获取某个构造器,只能拿public修饰的构造器,几乎不用!
     2. Constructor getDeclaredConstructor(Class... parameterTypes)
        根据参数匹配获取某个构造器,只要申明就可以定位,不关心权限修饰符,建议使用!
     3. Constructor[] getConstructors()
        获取所有的构造器,只能拿public修饰的构造器。几乎不用!!太弱了!
     4. Constructor[] getDeclaredConstructors()
        获取所有申明的构造器,只要你写我就能拿到,无所谓权限。建议使用!!
小结:
    获取类的全部构造器对象: Constructor[] getDeclaredConstructors()
        -- 获取所有申明的构造器,只要你写我就能拿到,无所谓权限。建议使用!!
    获取类的某个构造器对象:Constructor getDeclaredConstructor(Class... parameterTypes)
        -- 根据参数匹配获取某个构造器,只要申明就可以定位,不关心权限修饰符,建议使用!
// 获取全部的构造器:只要你敢写,这里就能拿到,无所谓权限是否可及。
@Test
public void getDeclaredConstructors(){
    // a.反射第一步先得到Class类对象
    Class c = Student.class ;
    // b.getDeclaredConstructors():定位全部构造器,只要申明了就可以拿到
    Constructor[] cons = c.getDeclaredConstructors();
    // c.遍历这些构造器
    for (Constructor con : cons) {
        System.out.println(con.getName()+"===>"+con.getParameterCount());
    }
}

反射_获取Constructor构造器然后通过这个构造器初始化对象

反射获取Class中的构造器对象Constructor作用:
        也是初始化并得到类的一个对象返回。

Constructor的API:
     1. T newInstance(Object... initargs)
            创建对象,注入构造器需要的数据。
     2. void setAccessible(true)
            修改访问权限,true代表暴力攻破权限,false表示保留不可访问权限(暴力反射)
小结:
    可以通过定位类的构造器对象。
    如果构造器对象没有访问权限可以通过:void setAccessible(true)打开权限
    构造器可以通过T newInstance(Object... initargs)调用自己,传入参数!
// 1.调用无参数构造器得到一个类的对象返回。
@Test
public void createObj01() throws Exception {
    // a.反射第一步是先得到Class类对象
    Class c = Student.class ;
    // b.定位无参数构造器对象
    Constructor constructor = c.getDeclaredConstructor();
    // c.暴力打开私有构造器的访问权限
    constructor.setAccessible(true);
    // d.通过无参数构造器初始化对象返回
    Student swk = (Student) constructor.newInstance(); // 最终还是调用无参数构造器的!
    System.out.println(swk);
}

// 2.调用有参数构造器得到一个类的对象返回。
@Test
public void createObj02() throws Exception {
    // a.反射第一步是先得到Class类对象
    Class c = Student.class ;
    // b.定位有参数构造器对象
    Constructor constructor = c.getDeclaredConstructor(String.class , int.class);
    // c.通过无参数构造器初始化对象返回
    Student swk = (Student) constructor.newInstance("孙悟空",10000); // 最终还是调用有参数构造器的!
    System.out.println(swk);
}

成员变量对象

反射_获取Field成员变量对象

反射的第一步是先得到Class类对象。

1、Field getField(String name);
       根据成员变量名获得对应Field对象,只能获得public修饰
2.Field getDeclaredField(String name);
       根据成员变量名获得对应Field对象,只要申明了就可以得到
3.Field[] getFields();
       获得所有的成员变量对应的Field对象,只能获得public的
4.Field[] getDeclaredFields();
       获得所有的成员变量对应的Field对象,只要申明了就可以得到
小结:
   获取全部成员变量:getDeclaredFields
   获取某个成员变量:getDeclaredField
/**
 * 1.获取全部的成员变量。
 */
@Test
public void getDeclaredFields(){
    // a.先获取class类对象
    Class c = Dog.class;
    // b.获取全部申明的成员变量对象
    Field[] fields = c.getDeclaredFields();
    for (Field field : fields) {
        System.out.println(field.getName()+"===>"+field.getType());
    }
}

/**
    2.获取某个成员变量对象
 */
@Test
public void getDeclaredField() throws Exception {
    // a.先获取class类对象
    Class c = Dog.class;
    // b.定位某个成员变量对象 :根据名称定位!!
    Field ageF = c.getDeclaredField("age");
    System.out.println(ageF.getName()+"--->"+ageF.getType());
}

给反射获取的成员变量取值和赋值

Field的方法:给成员变量赋值和取值
    void set(Object obj, Object value):给对象注入某个成员变量数据
    Object get(Object obj):获取对象的成员变量的值。
    void setAccessible(true);暴力反射,设置为可以直接访问私有类型的属性。
    Class getType(); 获取属性的类型,返回Class对象。
    String getName(); 获取属性的名称。
// a.反射的第一步获取Class类对象
Class c = Dog.class ;
// b.定位name成员变量
Field nameF = c.getDeclaredField("name");
// c.为这个成员变量赋值!
Dog taiDi = new Dog();
nameF.setAccessible(true); // 暴力反射!
/**
 * 参数一:被赋值的对象。
 * 参数二:该成员变量的值。
 */
nameF.set(taiDi , "勇敢的泰迪");
System.out.println(taiDi);

// d.获取成员变量的值
String value = nameF.get(taiDi)+"";
System.out.println(value);

反射-Method方法对象

目标:反射——获取Method方法对象

反射获取类的Method方法对象:
     1、Method getMethod(String name,Class...args);
         根据方法名和参数类型获得对应的方法对象,只能获得public的

     2、Method getDeclaredMethod(String name,Class...args);
         根据方法名和参数类型获得对应的方法对象,包括private的

     3、Method[] getMethods();
         获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的

     4、Method[] getDeclaredMethods();
        获得类中的所有成员方法对象,返回数组,只获得本类申明的方法。

Method的方法执行:
    Object invoke(Object obj, Object... args)
      参数一:触发的是哪个对象的方法执行。
      参数二: args:调用方法时传递的实际参数
/**
 * 1.获得类中的所有成员方法对象
 */
@Test
public void getDeclaredMethods(){
    // a.先获取class类对象
    Class c = Dog.class ;
    // b.获取全部申明的方法!
    Method[] methods = c.getDeclaredMethods();
    // c.遍历这些方法
    for (Method method : methods) {
        System.out.println(method.getName()+"====>"
                + method.getParameterCount()+"===>" + method.getReturnType());
    }

}
/**
 * 2. 获取某个方法对象
 */
@Test
public void getDeclardMethod() throws Exception {
    // a.先获取class类对象
    Class c = Dog.class;
    // b.定位它的某个方法
    Method run = c.getDeclaredMethod("run");
    // c.触发方法执行!
    Dog jinMao = new Dog();
    Object rs = run.invoke(jinMao); // 触发jinMao对象的run()方法执行!
    System.out.println(rs);// 如果方法没有返回值,结果是null

    /**
     * 参数一:方法名称
     * 参数二:方法的参数个数和类型(可变参数!)
     */
    Method eat = c.getDeclaredMethod("eat",String.class);
    eat.setAccessible(true); // 暴力反射!
    /**
     * 参数一:被触发方法所在的对象
     * 参数二:方法需要的入参值
     */
    Object rs1 = eat.invoke(jinMao,"肉");
    System.out.println(rs1);// 如果方法没有返回值,结果是null
}

暴力攻击集合泛型

拓展
   1.反射可以破坏面向对象的封装性(暴力反射)。
   2.同时可以破坏泛型的约束性。
public class ReflectDemo {
    public static void main(String[] args) throws Exception {
        // 泛型只能工作在编译阶段,运行阶段泛型就消失了,
        // 反射工作在运行时阶段。
        List<Double> scores = new ArrayList<>();
        scores.add(99.3);
        scores.add(199.3);
        scores.add(89.5);

        // 拓展:通过反射暴力的注入一个其他类型的数据进去。
        // a.先得到集合对象的Class文件对象
        Class c = scores.getClass();
        // b.从ArrayList的Class对象中定位add方法
        Method add = c.getDeclaredMethod("add", Object.class);
        // c.触发scores集合对象中的add执行(运行阶段,泛型不能约束了)
        add.invoke(scores,"波仔");

        System.out.println(scores);
    }
}

反射的作用

// 提供一个方法:可以保存一切对象数据的字段和具体值
可以在运行时得到一个类的全部成分然后操作。
     可以破坏封装性。
     也可以破坏泛型的约束性。

     更重要的用途是适合:做Java高级框架,基本上主流框架都会基于反射设计一些通用技术功能。

     Mybatis框架:
         你给任何一个对象数据我都可以直接帮你解析字段并且把对应数据保存起来。
         Student (注册,把信息字段都存储起来)
         Teacher (注册,把信息字段都存储起来)
         Manager (注册,把信息字段都存储起来)

         我现在用反射技术开发一个框架实现:
         任何对象只要给我,我就可以把信息和字段都解析并存储起来。
     小结:
        反射适合做通用技术框架的底层实现,在框架的底层源码中我们经常看到反射的影子!!
public static void save(Object obj) {
    try(
            PrintStream ps =
                    new PrintStream(new FileOutputStream("Day12Demo/src/datas.txt",true));
      ){
        // 解析对象的字段和每个字段的值存储起来!
        // obj = Student/Pig/Teacher....
        // 1.先得到对象的Class文件对象
        Class c = obj.getClass();
        ps.println("========="+c.getSimpleName()+"===========");
        // 2.定位它的全部成员变量
        Field[] fields = c.getDeclaredFields();
        // 3.遍历这些字段并且取值
        for (Field field : fields) {
            // 字段名称
            String name = field.getName();
            // 字段的值
            field.setAccessible(true); // 暴力反射!
            String value = field.get(obj)+"";
            ps.println(name+"="+value);
        }
    }catch (Exception e){
        e.printStackTrace();
    }

}

注解

概述

目标:注解的概念。

注解:
       用在类上,方法上,成员变量,构造器,...上对成分进行编译约束,标记等操作的。
       注解是JDK1.5的新特性。
       注解相当一种标记,是类的组成部分,可以给类携带一些额外的信息。
       注解是给编译器或JVM看的,编译器或JVM可以根据注解来完成对应的功能。

注解作用:
       1.标记。
       2.方法重写约束 @Override
       3.函数式接口约束。 @FunctionalInterface.
       4.现今最牛逼的框架技术多半都是在使用注解和反射。都是属于框架的底层基础技术。

我们之前用的注解都是别人写好的,今天我们自己来定义一下注解。

自定义注解

目标:我们之前都是用别人写好的注解,今天我们自己来做注解。

自定义注解的格式:
    修饰符 @interface 注解名{
         // 注解属性
    }

 小结:
       自定义注解用@interface关键字。
       使用注解的格式:@注解名称。
       注解默认可以标记很多地方。
@Book
@MyTest
public class MyBook {
    @Book
    @MyTest
    private MyBook(){

    }
    @Book
    @MyTest
    public static void main(@MyTest String[] args) {
        @MyTest
        @Book
        int age = 12;
    }
}

@interface Book{
}

@interface MyTest{

}

注解的属性

目标:注解的属性:

属性的格式
       - 格式1:数据类型 属性名();
       - 格式2:数据类型 属性名() default 默认值;

属性适用的数据类型:
       八种数据数据类型(int,short,long,double,byte
        ,char,boolean,float)
       String,Class
       以上类型的数组形式都支持
       
目标:注解的特殊属性名称:value
        value属性,如果只有一个value属性的情况下,
        使用value属性的时候可以省略value名称不写!!

        但是如果有多个属性,且多个属性没有默认值,那么value是不能省略的。       

小结:
   注解可以有属性,属性名必须带()
   在用注解的时候,属性必须赋值,除非这个属性有默认值!!
@MyBook(name="《精通Java基础》",authors = {"播仔","Dlei","播妞"} , price = 99.9 )
public class AnnotationDemo01 {
    @MyBook(name="《精通MySQL数据库入门到删库跑路》",authors = {"小白","小黑"} ,
            price = 19.9 , address = "北京")
    public static void main(String[] args) {

    }
}

// 自定义一个注解
@interface MyBook{
    String name();
    String[] authors(); // 数组
    double price();
    String address() default "广州";
}

元注解

元注解是sun公司提供的。
元注解是用在自定义注解上的注解。
元注解是用来注解自定义注解的。

元注解有两个:
    @Target:约束自定义注解只能在哪些地方使用,
        -- 但是默认的注解可以在类,方法,构造器,成员变量,... 使用。

    @Retention:申明注解的生命周期
        -- 申明注解的作用范围:编译时,运行时。

@Target
     * 作用:用来标识注解使用的位置,如果没有使用该注解标识,则自定义的注解可以使用在任意位置。
     * 可使用的值定义在ElementType枚举类中,常用值如下
        TYPE,类,接口
        FIELD, 成员变量
        METHOD, 成员方法
        PARAMETER, 方法参数
        CONSTRUCTOR, 构造器
        LOCAL_VARIABLE, 局部变量

@Retention
    作用:用来标识注解的生命周期(有效存活范围)
     * 可使用的值定义在RetentionPolicy枚举类中,常用值如下
     * SOURCE:注解只作用在源码阶段,生成的字节码文件中不存在
     * CLASS:注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值.
     * RUNTIME:注解作用在源码阶段,字节码文件阶段,运行阶段(开发常用)
小结:
   @Target约束自定义注解可以标记的范围。
   @Retention用来约束自定义注解的存活范围。
public class AnnotationDemo01{
    // @MyTest
    private String name;

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

    @MyTest
    public void testRun(){

    }
}

//@Target({ElementType.METHOD , ElementType.FIELD}) // 申明只能注解方法和成员变量!
@Target(ElementType.METHOD ) // 申明只能注解方法
@Retention(RetentionPolicy.RUNTIME) // 申明注解从写代码一直到运行还在,永远存活!!
@interface MyTest{
}

注解的解析

我们会使用注解注释一个类的成分,那么就设计到要解析出这些注解的数据。
开发中经常要知道一个类的成分上面到底有哪些注解,注解有哪些属性数据,这都需要进行注解的解析。

与注解解析相关的接口
     1. Annotation: 注解类型,该类是所有注解的父类。注解都是一个Annotation的对象
     2. AnnotatedElement:该接口定义了与注解解析相关的方法
       所有的类成分Class, Method , Field , Constructor:都实现了AnnotatedElement接口
       他们都拥有解析注解的能力:
        a.Annotation[] getDeclaredAnnotations()
           获得当前对象上使用的所有注解,返回注解数组。
        b.T getDeclaredAnnotation(Class<T> annotationClass)
            根据注解类型获得对应注解对象
        c.boolean isAnnotationPresent(Class<Annotation> annotationClass)
           判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false

解析注解数据的原理
    * 注解在哪个成分上,我们就先拿哪个成分对象。
    * 比如注解作用成员方法,则要获得该成员方法对应的Method对象,再来拿上面的注解
    * 比如注解作用在类上,则要该类的Class对象,再来拿上面的注解
    * 比如注解作用在成员变量上,则要获得该成员变量对应的Field对象,再来拿上面的注解

需求:(了解即可)
    1. 定义注解Book,要求如下:
        - 包含属性:String value()   书名
        - 包含属性:double price()  价格,默认值为 100
        - 包含属性:String[] authors() 多位作者
        - 限制注解使用的位置:类和成员方法上
        - 指定注解的有效范围:RUNTIME
    2. 定义BookStore类,在类和成员方法上使用Book注解
    3. 定义AnnotationDemo01测试类获取Book注解上的数据
public class AnnotationDemo01 {
    @Test
    public void parseClass(){
        // 1.定位Class类对象
        Class c = BookStore.class ;
        // 2.判断这个类上是否使用了某个注解
        if(c.isAnnotationPresent(Book.class)){
            // 3.获取这个注解对象
            Book book = (Book) c.getDeclaredAnnotation(Book.class);
            System.out.println(book.value());
            System.out.println(book.price());
            System.out.println(Arrays.toString(book.authors()));
        }
    }

    @Test
    public void parseMethod() throws Exception {
        // 1.定位Class类对象
        Class c = BookStore.class ;
        // 2.定位方法对象
        Method run = c.getDeclaredMethod("run");
        // 3.判断这个方法上是否使用了某个注解
        if(run.isAnnotationPresent(Book.class)){
            // 3.获取这个注解对象
            Book book = (Book) run.getDeclaredAnnotation(Book.class);
            System.out.println(book.value());
            System.out.println(book.price());
            System.out.println(Arrays.toString(book.authors()));
        }
    }
}

@Book(value = "《Java基础到精通》" , price = 99.5 , authors = {"波仔","波妞"})
class BookStore{
    @Book(value = "《Mybatis持久层框架》" , price = 199.5 , authors = {"dlei","播客"})
    public void run(){
    }
}

@Target({ElementType.TYPE,ElementType.METHOD}) // 类和成员方法上使用
@Retention(RetentionPolicy.RUNTIME) // 注解永久存活
@interface Book{
    String value();
    double price() default 100;
    String[] authors();
}

单元测试

目标:单元测试的概念和操作步骤。

单元测试是指程序员写的测试代码给自己的类中的方法进行预期正确性的验证。
单元测试一旦写好了这些测试代码,就可以一直使用,可以实现一定程度上的自动化测试。

单元测试一般要使用框架进行。
什么是框架?
       -- 框架是前人或者一些牛逼的技术公司在实战或者研发中设计的一些优良的设计方案
           或者成型的 代码功能,作为一个完整的技术体系发行出来称为框架。

       -- 框架可以让程序员快速拥有一个强大的解决方案,可以快速的开发功能,提高效率
          并且直接就有了很好的性能。

单元测试的经典框架:Junit.

Junit是什么
    *  Junit是Java语言编写的第三方单元测试框架
    *  Junit框架的方案可以帮助我们方便且快速的测试我们的代码的正确性。

单元测试概念
    * 单元:在Java中,一个类就是一个单元
    * 单元测试:程序猿用Junit编写的一小段代码,
                 用来对某个类中的某个方法进行功能测试或业务逻辑测试。

Junit单元测试框架的作用
    * 用来对类中的方法功能进行有目的的测试,以保证程序的正确性和稳定性。
    * 能够独立的测试某个方法或者所有方法的预期正确性。

Junit框架的使用步骤:
   (1) 下载这个框架。(别人设计好的技术体系)
       框架一般是jar包的形式,jar包里面都是class文件。(Java工程的最终形式)
       class文件就是我们调用的核心代码。

       -- 现在不需要大家去官网下载,因为很多知名框架其实IDEA工具早就整合好了,
          程序员可以直接使用。

       Junit已经被IDEA下载好了,可以直接导入到项目使用的。

   (2)直接用Junit测试代码即可
       a.先模拟业务代码
       b.写测试类
               测试类的命名规范:以Test开头,以业务类类名结尾,使用驼峰命名法
               业务名称是:UserService
               测试这个业务类的测试类:TestUserService/UserServiceTest
       c.在测试类中写测试方法
                测试方法的命名规则:以test开头,以业务方法名结尾
                比如被测试业务方法名为:login,那么测试方法名就应该叫:testLogin

       d.测试方法注意事项
                必须是public修饰的,没有返回值,没有参数
                必须使注解@Test修饰

    (3)如何运行测试方法
            * 选中方法名 --> 右键 --> Run '测试方法名'  运行选中的测试方法
            * 选中测试类类名 --> 右键 --> Run '测试类类名'  运行测试类中所有测试方法
            * 选中模块名 --> 右键 --> Run 'All Tests'  运行模块中的所有测试类的所有测试方法


    如何查看测试结果
        * 绿色:表示测试通过
        * 红色:表示测试失败,有问题

    Junit常用注解(Junit 4.xxxx版本)
        * @Test 测试方法!
        * @Before:用来修饰实例方法,该方法会在每一个测试方法执行之前执行一次。
        * @After:用来修饰实例方法,该方法会在每一个测试方法执行之后执行一次。
        * @BeforeClass:用来静态修饰方法,该方法会在所有测试方法之前只执行一次。
        * @AfterClass:用来静态修饰方法,该方法会在所有测试方法之后只执行一次。

       开始执行的方法:初始化资源。
       执行完之后的方法:释放资源。

    Junit常用注解(Junit5.xxxx版本)
        * @Test 测试方法!
        * @BeforeEach:用来修饰实例方法,该方法会在每一个测试方法执行之前执行一次。
        * @AfterEach:用来修饰实例方法,该方法会在每一个测试方法执行之后执行一次。
        * @BeforeAll:用来静态修饰方法,该方法会在所有测试方法之前只执行一次。
        * @AfterAll:用来静态修饰方法,该方法会在所有测试方法之后只执行一次。
posted @ 2021-06-21 12:44  zhiYong'blog  阅读(88)  评论(0)    收藏  举报