Java 基础笔记

类中5大成分

1、成员变量(描述类和对象的属性信息)
2、成员方法(描述类和对象的行为信息)
3、构造器(初始化一个类的对象,并返回引用)
4、代码块
5、内部类
注意:只要不是5大成分,放在类中,就会报错!

this关键字作用

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

面向对象三大特征

1、封装 (提高安全性、实现代码组件化)
2、继承
3、多态

static关键字

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

按有无static修饰,成员变量和方法可以分为:

  • 静态成员变量(类变量):有static修饰,属于类本身,与类一起只加载一次,直接用类名访问即可。(静态成员变量可以被共享访问!)
  • 实例成员变量:属于类的每个对象的,必须用类的对象来访问。
  • 静态方法:有static修饰,属于类本身,直接用类名访问即可。
  • 实例方法:属于类的每个对象的,必须用类的对象来访问。

注意:在同一个类中访问静态成员可以省略类名不写!

静态成员变量访问
  类名.静态成员变量;
  对象.静态成员变量。(不推荐)

实例成员变量的访问
  对象.实例成员变量;

静态方法访问格式
  类名.静态方法;
  对象.静态方法;(不推荐)

实例方法访问格式
  对象.实例方法;

继承

子类不能继承父类的构造器:子类有自己的构造器。(无争议的!)


(有争议的,但我认为~ )
子类可以继承父类的私有成员,只是不能直接访问而已。(可以暴力访问继承自父类的私有成员) 子类不能继承父类的静态成员。子类可以访问父类的静态成员,父类的静态成员只有一份可以被子类共享访问。共享并非继承。

子类有,选子类,没有找父类,父类没有就报错。

单继承
多层继承:一个类可以间接继承多个父类。
一个类可以有多个子类。

重写

方法重写规范:
1、加 @Override 注解
2、建议 “ 声明不变,重新实现 ”

子类重写方法名称与形参与父类被重写的方法一样。
子类重写方法返回值与父类一样或比父类返回值范围更小。
子类重写方法修饰符权限与父类被重写方法一样或者更大。
子类重写方法抛出异常与父类被重写方法一样或者范围更小。

构造器

子类的全部构造器,第一行默认会调用父类的无参构造器,再执行自己的构造器。
(由于子类构造器初始化子类对象数据的时候,必须先调用父类构造器初始化继承自父类的属性和行为)

在构造器中,第一行只能是 super() 或 this() ,他们只能放在第一行。

除了基本数据类型,就都是引用数据类型。
引用类型可以做方法的参数和返回值。

抽象类

拥有抽象方法的类,必须是抽象类。

抽象类的作用:为了被子类继承。

注意:
一个类继承了抽象类,必须重写完抽象类的全部抽象方法否则这个类也必须定义成抽象类

抽象类的特征

  • 抽象类得到了抽象方法的能力,失去了创建对象的能力。
  • 抽象类有构造器!提供给子类创建对象调用父类构造使用的。
  • 类有的成分,抽象类都具备。

抽象类存在的意义:
1、抽象类的核心意义就是为了被子类继承。(就是为了派生子类)
2、抽象类体现的是模版思想,部分实现,部分抽象。可以设计模版设计模式。

抽象类设计模版模式

什么事设计模式?
设计模式是 前人 或者 技术大牛 或者 软件行业 在生产实战中发现的优秀软件设计架构思想
我们可以直接用这些架构或者思想,设计出优秀、提高效率、提高软件可扩展性和可维护性的软件!

模版设计模式的作用?
优化代码架构、,提高代码的复用性,相同功能的重复代码无需反复书写。
可以做到部分实现,部分抽象,抽象的东西交给使用模版的人重写实现!

package com.luhang.Test;

/**
 *     抽象类设计模版模式:
 *     类似作文模板:
 *              标题:《我的爸爸》
 *         第一段固定:介绍下你的爸爸,有多好
 *              正文:抽象出来
 *              结尾:我的爸爸真棒,下辈子还要做他儿子
 */
public class test{
    public static void main(String[] args) {
        Student s=new Student();
        s.write();
        Teacher t=new Teacher();
        t.write();
    }
}

class Student extends Template{
    @Override
    public String writeMain() {
        return "\t\t我只子类 学生,重写了作文正文内容!";
    }
}
class Teacher extends Template{
    @Override
    public String writeMain() {
        return "\t\t我只子类 老师,我也借用了作文模版 !";
    }
}

// 设计一个模版类
abstract class Template{
    private String title="\t\t\t\t\t\t《我的爸爸》";
    private String one="\t\t介绍下你的爸爸,有多好。";
    private String last="\t\t我的爸爸真棒,下辈子还要做他儿子。";

    // 写作文功能
    public void write(){
        System.out.println(title);
        System.out.println(one);

        //正文部分,要交给模版子类来写!
        System.out.println(writeMain());

        System.out.println(last);
    }
    // 定义抽象方法
    public abstract String writeMain();
}

接口

什么是接口?
接口是更加彻底的抽象,在JDK1.8 之前接口中只能是抽象方法和常量。
接口体现的是规范思想,实现接口的子类必须重写完接口的全部抽象方法。

常量:值只有一个,且在程序运行的过程中不可修改!
常量修饰符public static final。常量名全部大写,用“_”连接。

接口中的抽象方法可以省略public abstract,默认会加上!
常量可以省略public static final,默认会加上。

实现接口的类,叫实现类。
类与接口是多实现关系。
一个类实现多个接口,必须重写全部接口中的全部抽象方法,否则这个类要定义成抽象类。

接口与接口的多继承关系:一个接口可以同时继承多个接口。

JDK 1.8 开始之后,接口新增了3个方法。
(JDK 1.8 之前,接口中只能是常量和抽象方法。)
1、默认方法
就是实例方法,但必须用default修饰,默认会加public修饰。只能用接口的实现类来调用。
2、静态方法
直接加static修饰,默认会加public修饰。只能用接口的类名称调用此静态方法。
3、私有方法
从JDK 1.9 开始支持。就是私有实例方法,用private修饰,只能在本接口中被访问。

实现多个接口的注意事项

  • 1、一个类,既继承一个父类,又实现若干个接口时,
    父类中的成员方法与接口中的默认方法重名,子类就近选择执行父类的成员方法
  • 2、一个类实现多个接口时,多个接口存在同名的默认方法,实现类必须重写这个方法。哪个接口的默认方法都不用,只用自己重写的那个方法。
  • 3、接口中,没有构造器,无法创建对象。

代码块

根据有无 static 分为:静态代码块实例代码块

静态代码块

必须用static修饰。属于类,会与类一起加载,而且自动触发执行一次
静态代码块,可以用于执行类方法之前进行静态资源的初始化操作。

// 格式:
static{
	
}

实例代码块

无 static 修饰。属于类的每个对象的,会与类的每个对象一起加载每次创建对象的时候,实例代码块就会触发一次
实例代码块,可以用于初始化实例资源
实例代码块的代码实际上是提取到每个构造器中去执行的。

// 格式:
{
	
}

final

final 修饰类:类不能被继承。
final 修饰方法:类不能被重写。
final 修饰变量:变量有且只能被赋值一次。

abstract 和 final 是互斥关系。

几种变量

1、成员变量

  • 静态成员变量:有 static 修饰,属于类,与类只加载一次。
  • 实例成员变量:无 static 修饰,属于对象,与对象只加载一次。

2、局部变量

  • 只能在 方法、构造器、代码块、for循环 中,用完作用范围就消失了。

final 修饰局部变量 :让值被固定或者说保护起来,执行过程中防止被修改。
final 修饰静态成员变量 :变量变成了常量。
final 修饰实例成员变量 :变量变成了常量,有且只能被赋值一次。(了解,用不到)

final 修饰静态成员变量可以在哪些地方赋值一次?
1、在定义的时候赋值一次。
2、在静态代码块中赋值一次。

final 修饰实例成员变量可以在哪些地方赋值一次?
1、在定义的时候赋值一次。
2、在实例代码块中赋值一次。
3、在每个构造器中赋值一次。

单例设计模式

什么是单例?
单例是一个类永远只存在一个对象,不能创建多个对象。

为什么要用单例?
开发中有很多类的对象我们只需要一个,例如虚拟机对象!任务管理器对象!
对象越多,越占内存。有时只需一个对象就可实现业务,可节约内存,提高性能!

如何实现单例?
单例的实现方法目前有2种:
1、饿汉单例设计模式
2、懒汉单例设计模式

饿汉单例设计模式

通过类获取单例对象时,对象已经提起做好了!
实现步骤:

  • 1、定义一个单例类
  • 2、把构造器私有
  • 3、定义一个静态成员变量用于存储对象!(饿汉单例在返回对象时,对象已经做好,所以这里要创建出来)
  • 4、定义一个方法,返回单例对象
// eg:
public class test {
    public static void main(String[] args) {
        SingleInstance s1=SingleInstance.getInstance();
        SingleInstance s2=SingleInstance.getInstance();
        System.out.println(s1==s2); // true
    }
}
class SingleInstance{
    private static SingleInstance instance=new SingleInstance();

    private SingleInstance(){

    }

    public static SingleInstance getInstance(){
        return instance;
    }
}

懒汉单例设计模式

通过类获取单例对象时,发现没有对象才会去创建一个对象。
实现步骤:

  • 1、定义一个单例类
  • 2、把构造器私有
  • 3、定义一个静态成员变量用于存储对象!(懒汉单例不能直接创建对象,必须在需要的时候才创建对象。)
  • 4、定义一个方法,返回单例对象,判断对象不存在才创建一次。
// eg:
public class test {
    public static void main(String[] args) {
        SingleInstance s1=SingleInstance.getInstance();
        SingleInstance s2=SingleInstance.getInstance();
        System.out.println(s1==s2); // true
    }
}
class SingleInstance{
    private static SingleInstance instance = null;

    private SingleInstance(){

    }

    public static SingleInstance getInstance(){
        if(instance == null){
            instance = new SingleInstance();
        }
        return instance;
    }
}

枚举类

枚举类的作用?
枚举是用于做信息标志信息分类

枚举类的特点
1、枚举类是final修饰的,不能被继承。
2、枚举类默认继承枚举类型:java.lang.Enum。
3、枚举类的第一行罗列的是枚举类的对象。而且是用常量存储的。
4、所有枚举类第一行写的都是常量名称,默认存储了枚举对象。
5、枚举类的构造器默认是私有的。
6、枚举类相当于多例设计模式。

// 枚举类格式:
修饰符 enum 枚举名称{
	实例1名称,实例2名称...;
}
// 枚举类第一行罗列的必须是枚举类的对象名称。

// eg:
public static void main(String[] args) {
    Sex s1=Sex.BOY;
    System.out.println(s1); // BOY
    Sex s2=Sex.GIRL;
    System.out.println(s2); // GIRL
    System.out.println(s2.ordinal()); //获取枚举对象的索引位置
}
enum Sex{
	BOY,GIRL;
}
// Sex反编译后源码如下:

Java 基础笔记_第1张图片

常量做信息标志和分类:虽然也挺好,但入参不受控制,入参太随性,不严谨。
枚举类做信息标志和信息分类:优雅!

// eg:枚举类用作信息标志和信息分类
public class test {
    public static void main(String[] args) {
        // 玛丽移动
        move(Orientation.UP);
    }
    public static void move(Orientation orientation){
        switch (orientation){
            case UP: //由于 switch 中已经知道了是枚举类型,所以无需 Orientation.UP 这样写
                System.out.println("向上");
                break;
            case DOWN:
                System.out.println("向下");
                break;
            case LEFT:
                System.out.println("向左");
                break;
            case RIGHT:
                System.out.println("向右");
                break;
        }
    }
}
enum Orientation{
    UP,DOWN,LEFT,RIGHT;
}

多态、instanceof

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

父类类型范围 > 子类类型范围

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

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

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

// eg:多态识别技巧
public class test {
    public static void main(String[] args) {
        Animal a1=new Cat();
        a1.run(); // 猫跑
        System.out.println(a1.name); //动物名称
        Animal a2=new Dog();
        a2.run(); // 狗跑
        System.out.println(a2.name); //动物名称

        go(a1);
        go(a2);
    }
    
    // 优势2具体例子
    public static void go(Animal animal){
        animal.run();
    }
}
class Animal{
    public String name="动物名称";
    public void run(){
        System.out.println("动物跑~");
    }
}
class Cat extends Animal{
    public String name="猫名称";
    @Override
    public void run() {
        System.out.println("猫跑~");
    }
}
class Dog extends Animal{
    public String name="狗名称";
    @Override
    public void run() {
        System.out.println("狗跑~");
    }
}

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


多态的劣势
多态形式下,不能调用子类特有的功能。因为编译看左边。
父类中没有子类独有的功能,所以代码在编译阶段直接报错!

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

java建议在强制类型转换之前先判断变量的真实类型,再强制类型转换!
变量 instanceof 类型:判断前面的变量是否是后面的类型或者其子类类型。

内部类

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

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

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

静态内部类

有 static 修饰,属于外部类本省,只会加载一次。

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

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

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

静态内部类的访问拓展
1、静态内部类中,是否可以直接访问外部类的静态成员?
可以,外部类的静态成员只有一份,可以被共享。
2、静态内部类中,是否可以直接访问外部类的实例成员?
不可以,外部类成员必须用外部类对象访问!

class Outter{
    // 静态内部类:有static 修饰,属于外部类本身,只会加载一次
    public static class Inner{
		// 特点和外部类完全一样,只是位置在别人里面而已
    }
}

实例内部类(成员内部类)

无 static 修饰,属于外部类的每个对象的,跟着对象一起加载的。

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

实例内部类访问格式外部类名称.内部类名称
实例内部类创建对象格式外部类名称.内部类名称 对象名称 = new 外部类构造器.new 内部类构造器;

拓展:
实例内部类中,是否可以直接访问外部类的静态成员?
可以,外部类的静态成员可以被共享访问。
实例内部类中,是否可以访问外部类的实例成员?
可以,实例内部类属于外部类对象,可以直接访问当前外部类对象的实例成员。

class Outter{
    public class Inner{
		// 只是不能定义静态成员、静态方法
    }
}

局部内部类(几乎不用)

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

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

public class test {
    public static void main(String[] args) {
        class A{
            public void test(){

            }
        }
        A a=new A();
        a.test();
    }
}

匿名内部类(重要)

就是一个没有名字的局部内部类。

匿名内部类目的:简化代码。

匿名内部类的格式:

new 类名|抽象类|接口(形参){ 
	方法重写。
}

匿名内部类的特点
1、匿名内部类是一个没有名字的内部类。
2、匿名内部类一旦写出来,就会立即创建一个匿名内部类的对象返回。
3、匿名内部类的对象的类型相当于是当前new的那个的类型的子类类型。

public class test {
    public static void main(String[] args) {
        Animal a1=new Animal() {
            @Override
            public void run() {
                System.out.println("猫跑的贼溜~");
            }
        };
        a1.run(); //猫跑的贼溜~
        a1.go(); //开始go~

        Animal a2=new Animal() {
            @Override
            public void run() {
                System.out.println("狗跑的贼快~");
            }
        };
        a2.run(); //狗跑的贼快~
        a2.go(); //开始go~
    }
}
abstract class Animal{
    public abstract void run();
    public void go(){
        System.out.println("开始go~");
    }
}

包 和 权限修饰符

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

定义包的格式package 包名;必须放在类的最上面。

包的命名规范
一般是公司域名的倒写+技术名称:
https://www.itheima.com => com.itheima.技术名称。
包名建议全部用英文,用. 连接,必须是合法标识符,不能用关键字。
不同包访问,到导入包。

权限修饰符
private -> 缺省 -> protected -> public
可以修饰成员变量、方法、构造器、内部类,不同修饰符修饰的成员能够被访问的权限将受到限制。
Java 基础笔记_第2张图片

Object类

Object类是java 的祖宗类。
一个类要么直接继承Object类,要么间接继承Object类。
Object类的方法是一切子类都可以直接使用的。

Object类的toString()方法

  • public String toString():
    默认返回当前对象在堆内存中的地址信息。
    默认地址信息格式:类的全限名@内存地址。
    直接输出对象名称,默认会自动调用toString()方法,所以输出对象toString()调用可省略。


开发中直接输出对象,默认输出对象的地址是毫无意义的。所以父类的toString()方法存在是为了被子类重写,以便返回对象的内容信息输出。

Object类的equals()方法

  • public boolean equals(Object o):
    默认是比较两个对象的地址是否相同。相同返回true。
    直接比较两个对象的地址是否相同完全可以用==替代equals
    所以equals存在的意义就是为了被子类重写,以便自己来制定比较规则。


字符串的equals()方法和Object类的equals()方法不一样!
字符串的equals()方法比较的是字符是否一样。

Objects类

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

Objects类的equals()、isNull()方法

  • public static boolean equals(Object a,Object b):
    比较两个对象。底层进行非空判断,从而避免空指针异常。更安全!
  • public static boolean isNull(Object obj):
    判断变量是否为null,为null返回true。此方法等同于==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以来 走过的总的毫秒数`。


记录时间的两种方式
1、日期对象
2、时间毫秒值

时间毫秒值的作用
可以用于时间的计算:例如代码的执行性能分析。

Date类的有参构造器

  • public Date(): 创建当前系统的此刻日期时间对象。
  • public Date(long time):把毫秒值转换成日期对象。eg:Date a=new Date(毫秒值);

DateFormat类

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

DateFormat是一个抽象类,不能直接使用,要找到它的子类: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 test {
    public static void main(String[] args) throws ParseException {
        Date d=new Date();
        // 格式化日期对象
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss EEE a");//也可 yyyy年MM月dd日 HH:mm:ss EEE a,保证字母格式正确就行
        System.out.println(sdf.format(d)); //2022-09-07 11:02:24 星期三 上午

        // 将字符串时间 转 日期对象
        String time="2022-09-07 11:02:24 星期三 上午";
        d=sdf.parse(time);//参数必须与被解析的时间格式完全一致,否则执行报错!
        System.out.println(d); //Wed Sep 07 11:02:24 CST 2022

        //格式化时间毫秒值,让时间往后走 1天15小时 30分29秒
        long m=d.getTime()+( 24L *60*60 + 15*60*60 + 30*60 + 29 ) * 1000; // 要加 L ,否则可能会数据溢出!
        System.out.println(m); //1662661973000
        System.out.println(sdf.format(m)); //2022-09-09 02:32:53 星期五 上午
    }
}

Calendar日历类

Calendar代表了系统此刻日期对应的日历对象。
Calendar是一个抽象类,不能直接创建对象。

Calendar日历类创建日历对象的语法
Calendar now=Calendar.getInstance();


Calendar的方法

  • public static Calendar getInstance():返回一个日历类实例对象。
  • public int get(int field):取日历的某个字段信息。
  • public void set(int field,int value):修改日历的某个字段信息。
  • public void add(int field,int amount):为某个字段增加/减少指定的值。
  • public final Date getTime():拿到此刻的日期对象。
  • public long getTimeInMillis():拿到此刻的时间毫秒数。
public class test {
    public static void main(String[] args) throws ParseException {
        Calendar c=Calendar.getInstance();
        System.out.println(c);//java.util.GregorianCalendar[time=1662523679129,YEAR=2022,MONTH=8,WEEK_OF_YEAR=37,WEEK_OF_MONTH=2,等...]
        System.out.println(c.get(Calendar.YEAR)); //2022

        c.set(Calendar.YEAR,2000);
        System.out.println(c.get(Calendar.YEAR)); //2000

        c.add(Calendar.YEAR,22);
        System.out.println(c.get(Calendar.YEAR)); //2022

        Date time = c.getTime();
        System.out.println(time); // Wed Sep 07 12:11:41 CST 2022
        
        long timeInMillis = c.getTimeInMillis();
        System.out.println(timeInMillis); //1662523901703
    }
}

Math类

Math类用于数学运算。
Math类中的方法全是静态方法,直接用类名调用即可。

方法

  • public static int abs(int a):获取参数a的绝对值。
  • public static double ceil(double a):向上取整。
  • public static double floor(double a):向下取整。
  • public staitc double pow(double a,double b):获取a的b次幂。
  • public static double round(double a):四舍五入取整。

System类

System代表当前系统。
静态方法
1、public static void exit(int status):终止JVM虚拟机,非0是异常终止。
2、public static long curruentTimeMillis():获取当前系统此刻时间毫秒值。
3、可以做数组的拷贝。(了解)
Java 基础笔记_第3张图片

Bigdecimal类

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

方法

  • public BigDecimal add(BigDecimal value):加法运算
  • public BigDecimal subtract(BigDecimal value):减法运算
  • public BigDecimal multiply(BigDecimal value):乘法运算
  • public BigDecimal divide(BigDecimal value):除法运算


BigDecimal只是解决精度问题的手段,double数据才是我们真正的目的!

public class test {
    public static void main(String[] args) throws ParseException {
        System.out.println(0.1+0.2); // 0.30000000000000004

        BigDecimal a=BigDecimal.valueOf(0.1);
        BigDecimal b=BigDecimal.valueOf(0.2);
        BigDecimal c=a.add(b);
        System.out.println(c); // 0.3

        // BigDecimal只是解决精度问题的手段,double数据才是我们真正的目的!
        Double rs=c.doubleValue();
    }
}

包装类

java认为,一切皆对象。引用数据类型就是对象。
8种基本数据类型不是对象,只是表示一种数据类型的形式。
Java为了一切皆对象的思想统一,把8种基本数据类型转换成对应的类,这个类称为基本数据类型的包装类。
Java 基础笔记_第4张图片
自动装箱:把基本数据类型的值或者变量赋值给包装类。
自动拆箱:把包装类的变量直接复赋值给基本数据类型。
Java 基础笔记_第5张图片
Java 基础笔记_第6张图片

java包装类的特殊功能
包装类作为类首先拥有了Object类的方法。
包装类作为引用数据类型的变量可以存储null值。

具体来看特殊功能主要有:
1、把基本数据类型的值转换成字符串类型的值。(没啥用)

  • 调用toString方法
  • 调用Integer.toString(基本数据类型的值) 得到字符串
  • 直接把基本数据类型+空字符串 就得到了字符串


2、把字符串类型的数值转换成对应的基本数据类型的值。(很有用)

  • Xxx.parseXxx("字符串类型的数值")
  • Xxx.valueOf("字符串类型的数值"):推荐使用!

正则表达式

正则表达式的作用:
是一些特殊字符组成的校验规则,可以校验信息的正确性,校验邮箱、手机号是否合法等。
Java 基础笔记_第7张图片

Java 基础笔记_第8张图片
Java 基础笔记_第9张图片

 System.out.println("a".matches("[abc]")); // true

正则表达式在方法中的应用:

  • public String[] split(String regex):按照正则表达式匹配的内容分割字符串,返回一个字符串数组。
  • public String replaceAll(String regex,String newStr):按照正则表达式匹配的内容进行替换。
// 了解
public static void main(String[] args) {
        String rs="电话 040-234234989,邮箱 [email protected] ,电风扇发送到";
        // 从上面内容中爬取 电话号码和邮箱
        //1、定义爬取规则
        String regex="\\w{1,}@\\w{2,10}"; // 等 ,规则省略
        // 2、编译正则表达式成为一个匹配规则对象
        Pattern pattern=Pattern.compile(regex);
        // 3、通过匹配规则对象得到一个匹配数据内容的 匹配对象
        Matcher matcher=pattern.matcher(rs);
        // 4、通过匹配器去内容中爬取x信息
        while (matcher.find()){
            System.out.println(matcher.group());
        }
    }

泛型

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

// 无泛型约束
 ArrayList a1=new ArrayList();
// 有泛型约束
 ArrayList<String> a1=new ArrayList<String>();
 ArrayList<String> a2=new ArrayList<>(); // jdk 1.7开始之后,可以省略后面的声明

泛型的好处:
泛型在编译阶段约束了操作的数据类型,从而不会出现类型转换异常。

自定义泛型类

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

泛型类格式:
修饰符 class 类名<泛型变量>{ }
泛型变量建议使用 E、T 、K、V

泛型类的核心思想:是把出现过泛型变量的地方全部替换成传输的真实数据类型。

// 模拟ArrayList自定义一个集合MyArrayList集合。
public class test {
    public static void main(String[] args) {
        MyArrayList<String> list=new MyArrayList<>();
        MyArrayList<String> list1=new MyArrayList<String>();

        list1.add("java");
        list1.add("bbb");
        // list1.add(1); // 会报错!因为泛型规定了操作的数据类型
        list1.remove("java");
        System.out.println(list1); //[bbb]
    }
}
class MyArrayList<E>{
    private ArrayList list=new ArrayList();
    public void add(E e){
        list.add(e);
    }
    public void remove(E e){
        list.remove(e);
    }
    @Override
    public String toString() {
        return list.toString();
    }
}

泛型方法和泛型类可以做通用技术架构。

泛型方法

定义了泛型的方法就是泛型方法。
泛型方法的定义格式:
修饰符 <泛型变量> 返回值类型 方法名称(形参列表){ }
注意:方法定义了什么泛型变量,后面就只能用什么泛型变量。

// 需求:给你任何一个类型的数组,都能返回它的内容。
public class test {
    public static void main(String[] args) {
        Integer[] nums={10,20,30};
        System.out.println(arrToString(nums)); // [10,20,30]

        String[] name={"张三","李四"};
        System.out.println(arrToString(name)); // [张三,李四]
    }
    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 class test {
    public static void main(String[] args) {
        Data studentData1=new StudentData();
        Data<Student> studentData2=new StudentData(); // 最好的写法
        StudentData studentData=new StudentData(); // 普遍写法
        studentData.add(new Student());
        studentData.delete(new Student());
        // 老师的差不多方式实现,略...
    }
}
// 泛型接口
interface Data<E>{
    void add(E e);
    void delete(E e);
    void update(E e);
    E query(int id);
}
// 操作学生数据
class StudentData implements Data<Student>{
    @Override
    public void add(Student student) {
    }
    @Override
    public void delete(Student student) {
    }
    @Override
    public void update(Student student) {
    }
    @Override
    public Student query(int id) {
        return null;
    }
}

class Teacher{}
class Student{}

泛型通配符

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

泛型的上下限:

  • ? extends Car:那么? 必须是Car 或者及其子类。(泛型的上限)
  • ? super Car : 那么? 必须是Car 或者及其父类。(泛型的下限,不常见)
// 需求:开发一个极品飞车的游戏,所有的汽车都能一起参与比赛。
// 注意:虽然BMW和BENZ都继承了Car,
// 但ArrayList 和 ArrayList 与 ArrayList没有关系!
// 泛型没有继承关系!!!!
public class test {
    public static void main(String[] args) {
        ArrayList<BMW> bmws=new ArrayList<>();
        bmws.add(new BMW());
        bmws.add(new BMW());
        run(bmws);

        ArrayList<BENZ> benzs=new ArrayList<>();
        benzs.add(new BENZ());
        benzs.add(new BENZ());
        run(benzs);

        ArrayList<Dog> dogs=new ArrayList<>();
        dogs.add(new Dog());
        //run(dogs); // 狗不是车,不能进来比赛,需定义 泛型的上下限。
    }
    // 定义一个方法,可以让很多汽车参加比赛
    public static void run(ArrayList<? extends Car> cars){
    
    }
}

class Car{

}
class BMW extends Car{

}
class BENZ extends Car{

}
class Dog{

}

集合

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

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

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

集合的特点:
Set系列集合:添加的元素是无序、不重复,无索引。

  • HashSet:添加的元素是无序、不重复,无索引
  • LinkedHashSet:添加的元素是有序、不重复,无索引
  • TreeSet:按照大小默认升序排序、不重复,无索引


List系列集合:添加的元素是有序、可重复,有索引。

  • ArrayList:添加的元素是有序、可重复,有索引
  • LinkedList:添加的元素是有序、可重复,有索引
  • Vector:是线程安全,速度慢,工作中很少使用

    Java 基础笔记_第10张图片

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():把集合中的元素,存储到数组中

变量名重构 快捷键:按shift+f6

public class test {
    public static void main(String[] args) {
        // 添加的元素是无序、不重复,无索引
        Collection<String> sets=new HashSet<>();
        // 添加元素,成功返回true
        System.out.println(sets.add("张三")); // true
        System.out.println(sets.add("张三")); // false
        System.out.println(sets.add("李四")); // true
        sets.add("王五");
        System.out.println(sets); // [张三, 李四, 王五]
        // 清空集合元素
//        sets.clear();
        System.out.println(sets);

        // 判断集合是否为空
        System.out.println(sets.isEmpty()); // false

        // 获取集合大小
        System.out.println(sets.size());

        // 判断集合中是否包含某个元素
        System.out.println(sets.contains("张三")); // true

        // 删除集合中某个元素
        sets.remove("王五");
        System.out.println(sets);

        // 把集合转换成数组
        Object[] arrs=sets.toArray();
        System.out.println("数组:"+ Arrays.toString(arrs)); // 数组:[张三, 李四]  ,效果等同于 输出sets
        //等同于
        String[] arr1=sets.toArray(String[]::new); // 以后了解,指定转换的数组类型
        System.out.println("数组:"+ Arrays.toString(arr1));
    }
}

遍历Collection集合的方式

迭代器

迭代器遍历集合。
方法

  • public Iterator iterator():获取集合对应的迭代器,用来遍历集合中的元素
  • E next():获取下一个元素值
  • boolean hasNext():判断是否有下一个元素,有返回true
public class test {
    public static void main(String[] args) {
       Collection<String> lists=new ArrayList<>();
       lists.add("张三");
       lists.add("李四");
       lists.add("王五");
       lists.add("赵六");
       System.out.println(lists); // [张三, 李四, 王五, 赵六]
        //                it在这,next再指向张三
       //1、得到集合的Iterator对象
        Iterator<String> it=lists.iterator();
        //2、遍历
        while (it.hasNext()){
            String ele=it.next();
            System.out.println(ele);
//          等同于  System.out.println(it.next());
        }
    }
}

foreach(增强for循环)

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

快捷生成方式:lists.for

缺点:foreach遍历无法知道遍历到了哪个元素,因为没有索引

public class test {
    public static void main(String[] args) {
       Collection<String> lists=new ArrayList<>();
       lists.add("张三");
       lists.add("李四");
       lists.add("王五");
       lists.add("赵六");
       System.out.println(lists); // [张三, 李四, 王五, 赵六]

        for(String list:lists){
            System.out.println(list);
        }

        int[] a=new int[]{1,2,3,4,5};
        for (int x:a) {
            System.out.println(x);
        }
    }
}

Lambda表达式(目前了解)

JDK 1.8开始之后的新技术,Lambda表达式。

public class test {
    public static void main(String[] args) {
       Collection<String> lists=new ArrayList<>();
       lists.add("张三");
       lists.add("李四");
       lists.add("王五");
       lists.add("赵六");
       
       lists.forEach(s->{
           System.out.println(s);
       });
       // 等价于
        lists.forEach(s->System.out.println(s));
        // 等价于
        lists.forEach(System.out::println);
    }
}

ArrayList

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):用指定元素替换集合中指定位置的元素,返回更新前的元素。


开发中ArrayList集合用的最多!

List遍历方式
1、for循环
2、迭代器
3、foreach
4、JDK 1.8新技术 Lambda表达式
(其他的集合只有3种,除去方式1 )

截取List集合元素:
List lastThreeCards = ALL_CARDS.subList( ALL_CARDS.size() - 3, ALL_CARDS.size() ) // 截取集合最后3个元素

LinkedList

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

可以用LinkedList实现队列

Set集合

(集合和泛型都只能支持引用数据类型)
Set系列集合元素结合去重复的流程:
1、对于有值特性的,Set集合可以直接判断进行去重。
2、对于引用数据类型的类对象,Set集合是按照如下流程进行是否重复的判断。
Set集合会让两两对象,先调用自己的hashCode()方法得到彼此的哈希值(所谓的内存地址)
然后比较两个对象的哈希值是否相同,如果不同则会直接认为两个对象不重复。
如果哈希值相同,会继续让两个对象进行equals比较内容是否相同,如果相同任务真的重复了,如果不相同认为不重复。
注意:如果希望Set集合认为两个对象只要内容一样就重复,必须重写对象的hashCode和equals方法。

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

JDK 1.8之前:哈希表 = 数组 + 链表 +(哈希算法)
JDK 1.8之后:哈希表 = 数组 + 链表 + 红黑树 +(哈希算法)
当链表长度超过阈值 (8) 时,将链表转换成红黑树,这样大大减少了查找时间。
Java 基础笔记_第11张图片

LinkedHashSet

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

总结:
若想元素可重复、有索引,查询要快,用ArrayList集合。(用的最多)
若想元素可重复、有索引,增删要快,用LinkedList集合。(适合查询元素比较少的情况,经常要收尾操作元素的情况)
若想增删改查都快,但元素不重复、无序、无索引,用HashSet集合。
若想增删改查快且有序,但元素不重复、无索引,用LinkedHashSet集合。

TreeSet

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

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

自定义引用数据类型的排序实现,自定义排序规则:
1、直接为对象的类实现比较器规则接口Comparable,重写比较方法。(拓展方式)
2、直接为集合设置比较器Comparator对象,重写比较方法。
注意:如果类和集合都带有比较规则,优先使用集合自带的比较规则。

//自定义排序规则1,案例: 
public class test {
    public static void main(String[] args) {
        Set<Employee> employees=new TreeSet<>();
        employees.add(new Employee("张三",7800.0,20));
        employees.add(new Employee("李四",8600.0,23));
        employees.add(new Employee("王五",7300.0,22));
        System.out.println(employees);
    }
}

public class Employee implements Comparable<Employee>{
    private String name;
    private double salary;
    private int age;
    public Employee(String name, double salary, int age) {
        this.name = name;
        this.salary = salary;
        this.age = age;
    }

    // this:比较者
    // o:被比较者
    // 需求:按年龄比较
    @Override
    public int compareTo(Employee o) {
        // 规则:java规则
        // 如果程序员认为 比较者大于被比较者,返回正数
        // 如果程序员认为 比较者小于被比较者,返回负数
        // 如果程序员认为 比较者等于被比较者,返回0
//        if(this.age>o.age){
//            return 1;
//        }else if(this.age
//            return -1;
//        }
//        return 0;

        //优雅写法
        return this.age-o.age; // 若要倒序就 o.age - this.age
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", salary=" + salary +
                ", age=" + age +
                '}';
    }
}

//自定义排序规则2,案例: 
public class test {
    public static void main(String[] args) {
    // 若类和集合都存在比较规则,默认使用集合自带的规则进行大小排序!
    // 集合自带比较对象如下:
        Set<Employee> employees=new TreeSet<>(new Comparator<Employee>() {
            @Override
            public int compare(Employee o1, Employee o2) {
                return o1.getAge()-o2.getAge();
            }
        });
        employees.add(new Employee("张三",7800.0,20));
        employees.add(new Employee("李四",8600.0,23));
        employees.add(new Employee("王五",7300.0,22));
        System.out.println(employees);

    }
}

public class Employee{
    private String name;
    private double salary;
    private int age;
    public Employee(String name, double salary, int age) {
        this.name = name;
        this.salary = salary;
        this.age = age;
    }

    // this:比较者
    // o:被比较者
    // 需求:按年龄比较
//    @Override
//    public int compareTo(Employee o) {
//        // 规则:java规则
//        // 如果程序员认为 比较者大于被比较者,返回正数
//        // 如果程序员认为 比较者小于被比较者,返回负数
//        // 如果程序员认为 比较者等于被比较者,返回0
        if(this.age>o.age){
            return 1;
        }else if(this.age
            return -1;
        }
        return 0;
//
//        //优雅写法
//        return this.age-o.age; // 若要倒序就 o.age - this.age
//    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", salary=" + salary +
                ", age=" + age +
                '}';
    }
}

常见的数据结构种类

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

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

java常见的数据结构有哪些?
数据结构常用的结构有:栈、队列、数组、链表、红黑树

队列(queue)
– 先进先出,后进后出
– 场景:各种排队。叫号系统
– 有很多集合可以实现队列。

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

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

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

红黑树
二叉树:binary tree 永远只有一个根节点,是每个节点不超过2个节点的树。
Java 基础笔记_第12张图片
查找二叉树 / 排序二叉树:小的左边,大的右边,但是可能树很高,性能变差。增删改查性能都很高!(可能出现瘸子现象)
Java 基础笔记_第13张图片

平衡二叉树:为避免“瘸子”的现象,减少树的高度,提高我们的搜索效率,又存在一种树的结构:“平衡二叉树”。
规则:它的左右两个子树的高度差绝对不超过1,并且左右两个子树都是一棵平衡二叉树。
(查找二叉树转平衡二叉树,通过不断地左旋,右旋得到)
Java 基础笔记_第14张图片

红黑树:就是平衡的查找二叉树,只是算法不一样,依然是树高对比实现旋转最终平衡。为了做排序和搜索会进行左旋和右旋实现平衡查找二叉树,让树的高度差不大于1红黑树(就是基于红黑规则实现了自平衡的排序二叉树):树尽量的保证到了很矮小,但是又排好序了,性能最高的树。


红黑树的特性:
1、每个节点或是红色,或是黑色。
2、根节点必须是黑色
3、每个叶节点(Nil)是黑色的;(如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点)
4、如果某个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)
5、对每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点
Java 基础笔记_第15张图片
在进行元素插入时,和之前一样;每次插入完毕后,使用黑色规则进行校验,如果不满足红黑规则,就需要通过变色,左旋和右旋来调整树,使其满足红黑规则;
红黑树增删改查性能都好!!综合性能好!

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

树性能从低到高排序:
树 --> 二叉树 --> 二叉排序树(二叉查找树) --> 平衡二叉树 --> 红黑树

Collections工具类的使用

java.utils.Collections:是集合工具类。
Collections不属于集合,只是用来操作集合的工具类!
Collections常用API:

  • public static boolean addAll(Collection c,T ... elements):给集合对象批量添加元素!
  • public static void shuffle(List list):打乱集合顺序
  • public static void sort(List list):将集合中元素按照默认规则排序。
  • public static void sort(List list,Comparator c):将集合中元素按照制定规则排序,自带比较器。


注意:如果类有比较规则,这里又有比较器,优先使用比较器。

public class test {
    public static void main(String[] args) {
        Collection<String> names=new ArrayList<>();
        // 参数一:被添加元素的集合, 参数二:可变参数,一堆元素
        Collections.addAll(names,"张三","李四","王五","赵六");
        System.out.println(names); // [张三, 李四, 王五, 赵六]

        // 打乱集合顺序
        // 注意:只能打乱有序的List集合
        List<String> newnames=new ArrayList<>();
        Collections.addAll(newnames,"张三","李四","王五","赵六");
        Collections.shuffle(newnames);
        System.out.println(newnames); // [王五, 张三, 李四, 赵六]

        // 给List集合升序排序
        List<Double> list=new ArrayList<>();
        Collections.addAll(list,66.0,33.0,99.0,73.0);
        Collections.sort(list); // 默认升序排序
        System.out.println(list); // [33.0, 66.0, 73.0, 99.0]

        // 将集合中元素按照制定规则排序,方式1:用类中的排序规则
        List<Employee> list1=new ArrayList<>();
        Employee e1=new Employee("张三",4000,22);
        Employee e2=new Employee("张三",3000,21);
        Employee e3=new Employee("张三",4346,42);
        Employee e4=new Employee("张三",5453,33);
        Collections.addAll(list1,e1,e2,e3,e4);
        Collections.sort(list1);
        System.out.println(list1);

        // 将集合中元素按照制定规则排序,方式2:自带比较器
        List<Employee> list2=new ArrayList<>();
        Collections.addAll(list2,e1,e2,e3,e4);
        Collections.sort(list2, new Comparator<Employee>() {
            @Override
            public int compare(Employee o1, Employee o2) {
                return o1.getAge()-o2.getAge();
            }
        });
        System.out.println(list2);
    }
}

public class Employee implements Comparable<Employee>{
    private String name;
    private double salary;
    private int age;
    public Employee(String name, double salary, int age) {
        this.name = name;
        this.salary = salary;
        this.age = age;
    }

    // this:比较者
    // o:被比较者
    // 需求:按年龄比较
    @Override
    public int compareTo(Employee o) {
        //优雅写法
        return this.age-o.age; // 若要倒序就 o.age - this.age
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", salary=" + salary +
                ", age=" + age +
                '}'+"\n";
    }
}

可变参数

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

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


可变参数在方法内部本质上就是一个数组。
可变参数的注意事项:
1、一个形参列表中可变参数只能有一个!
2、可变参数必须放在形参列表的最后面!

public class test {
    public static void main(String[] args) {
        sum(1);
        sum(1,1,2,3,4,5);
        sum(1,new int[]{1,2,3,4,5});
    }
    public static void sum(int a,int ...sums){
        System.out.println("元素:"+sums.length);
        System.out.println("元素内容"+Arrays.toString(sums));
    }
}

斗地主游戏:做牌、发牌、看牌

public class test {
    public static List<Card> ALL_CARDS = new ArrayList<>();
    static {
        String[] number={"3","4","5","6","7","8","9","10","J","Q","K","A","2"};
        String[] color={"♣","♠","◇","❤"};
        int index=0;
        for (String n : number) {
            for (String c : color) {
                Card card=new Card(n,c,index++);
                ALL_CARDS.add(card);
            }
        }
        Collections.addAll(ALL_CARDS,new Card("","",index++),new Card("","大王",index++));
        System.out.println(ALL_CARDS);
    }
    public static void main(String[] args) {
        // 打乱牌的顺序
        Collections.shuffle(ALL_CARDS);
        System.out.println(ALL_CARDS);
        //分牌
        ArrayList<Card> zhangSan=new ArrayList<>();
        ArrayList<Card> liSi=new ArrayList<>();
        ArrayList<Card> wangWu=new ArrayList<>();
        for (int i = 0; i < ALL_CARDS.size()-3; i++) {
            Card c=ALL_CARDS.get(i);
            if(i%3==0){
                zhangSan.add(c);
            }else if(i%3==1){
                liSi.add(c);
            }else if(i%3==2){
                wangWu.add(c);
            }
        }
        List<Card> lastThreedCard=ALL_CARDS.subList(ALL_CARDS.size()-3,ALL_CARDS.size());
        System.out.println("================ 看牌 ================");
        System.out.println("张三:"+zhangSan);
        System.out.println("李四:"+liSi);
        System.out.println("王五:"+wangWu);
        System.out.println("底牌:"+lastThreedCard);
        // 将牌排序

        sortCard(zhangSan);
        sortCard(liSi);
        sortCard(wangWu);

        System.out.println("================ 排序 ================");

        System.out.println("张三:"+zhangSan);
        System.out.println("李四:"+liSi);
        System.out.println("王五:"+wangWu);
        System.out.println("底牌:"+lastThreedCard);

    }

    private static void sortCard(ArrayList<Card> cards) {
        Collections.sort(cards, new Comparator<Card>() {
            @Override
            public int compare(Card o1, Card o2) {
                return o2.getIndex()-o1.getIndex();
            }
        });
    }
}

public class Card {
    private String number;
    private String color;
    private int index;

    public Card() {
    }

    public Card(String number, String color, int index) {
        this.number = number;
        this.color = color;
        this.index = index;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public int getIndex() {
        return index;
    }

    public void setIndex(int index) {
        this.index = index;
    }

    @Override
    public String toString() {
        return number+color;
    }
}

Map集合

Map集合是另一个集合体系。
Collection是单值集合体系。
Map集合是“键值对集合”,格式:key = value(键值对元素)

Map集合的完整格式:{key1=value1 , key2=value2 , key3=value3 … }
Map集合作用:
1、Map集合存储的信息更加具体丰富
2、Map集合很适合做购物车系统

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

Java 基础笔记_第16张图片
Map集合的特点:
1、Map集合的特点都是由键决定的
2、Map集合的键是无序、不重复,无索引的。
(Map集合后面重复的键对应的元素会覆盖前面的整个元素!)
3、Map集合的值无要求
4、Map集合的键值对都可以为null

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

(注意:Map和Set的底层是差不多的,只不过单值变成了键值对!)

Map集合常用API(重点)

  • publc V put(K key, V value):把指定的键与指定的值添加到Map集合中。
  • public V remove(Object key):把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的 键的值。
  • public V get(Object key):根据指定的键,在Map集合中获取对应的值。
  • public Set keySet:获取Map集合中所有的键,存储到Set集合中。
  • public Set> entrySet():获取到Map集合中所有的键值对对象的集合(Set集合)
  • public boolean containKey(Object key):判断该集合中是否有此键。
public class test {
    public static void main(String[] args) {
        Map<String,Integer> maps=new HashMap<>();
        maps.put("张三",192);
        maps.put("李四",122);
        maps.put("王五",142);
        maps.put("赵六",162);
        System.out.println(maps); //{李四=122, 张三=192, 王五=142, 赵六=162}

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

        // 判断集合是否为空
        System.out.println(maps.isEmpty()); //false

        //根据键获取对应的值
        System.out.println(maps.get("王五"));

        // 根据键删除整个元素
        maps.remove("王五");
        System.out.println(maps); //{李四=122, 张三=192, 赵六=162}

        // 判断是否包含某个键
        System.out.println(maps.containsKey("张三")); // true

        // 判断是否包含某个值
        System.out.println(maps.containsValue(162)); // true

        // 获取全部键的集合
        Set<String> keys=maps.keySet();
        for (String key : keys) {
            System.out.println(key);
        }

        // 获取全部值的集合
        Collection<Integer> values=maps.values();
        for (Integer value : values) {
            System.out.println(value);
        }

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

        // 合并其他Map集合(拓展)
        Map<String,Integer> maps2=new HashMap<>();
        maps2.put("程琪",1);
        maps2.put("赵琦",2);
        maps2.put("赵六",3);
        maps.putAll(maps2); // 把maps2数据全部加到maps中
        System.out.println(maps); //{程琪=1, 赵琦=2, 李四=122, 张三=192, 赵六=3}
    }
}

Map集合的遍历方式

1、“键找值”的方式遍历:先获取Map集合全部的键,再根据键找值。
2、“键值对”的方式遍历:难度较大
 “键值对”的方式遍历:更加面向对象的方式,代码复杂。
 "键值对"想把键值当成一个整体遍历,也就是用foreach遍历.
 但发现Map结合的键值对数据直接是没有类型的,foreach无法直接遍历Map集合。
 把Map结合通过代码Set> entrySet()转换成Set集合。
Set> entries = maps.entrySet();
在这里插入图片描述
3、JDK 1.8开始之后的新技术:Lambda表达式。(暂时了解)

Map<String,Integer> maps=new HashMap<>();
        maps.put("张三",192);
        maps.put("李四",122);
        maps.put("王五",142);
        maps.put("赵六",162);
        
// 方式一:键找值
        Set<String> keys=maps.keySet();
        for (String key : keys) {
            System.out.println(maps.get(key));
        }

// 方式二:键值对方式
Set<Map.Entry<String,Integer>> entries=maps.entrySet();
        for (Map.Entry<String, Integer> entry : entries) {
            System.out.println(entry.getKey());
            System.out.println(entry.getValue());
        }

// 方式三:Lambda表达式
 maps.forEach((k,v)->{
            System.out.println(k+","+v);
        });

LTS:Long Time Support,长久支持。

Map集合存储自定义类型

Map集合的键和值都可以存储自定义类型。
如果希望Map集合认为自定义类型的键对象重复了,必须重写对象的hashCode()和equals()方法

 public static void main(String[] args) {
        Map<Card,String> cards=new HashMap<>();
        Card c1=new Card("1","1",1);
        Card c2=new Card("2","2",2);
        Card c3=new Card("2","2",2);
        cards.put(c1,"1");
        cards.put(c2,"2");
        cards.put(c3,"3");
        System.out.println(cards); // {2,2,2=2, 2,2,2=3, 1,1,1=1}
                       // 重写了equals方法后,{2,2,2=3, 1,1,1=1}
    }

LinkedHashMap

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

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

public static void main(String[] args) {
        Map<String ,Integer> map=new LinkedHashMap<>();
        map.put("1",1);
        map.put("2",2);
        map.put("3",3);
        map.put("3",4);
        System.out.println(map); //{1=1, 2=2, 3=4}
    }

TreeMap

TreeMap集合按照键是可排序不重复的键值对集合。(默认升序)
TreeMap集合按照键排序的特点与TreeSet是完全一样的。

TreeMap集合和TreeSet集合都是排序不重复集合
TreeSet集合的底层是基于TreeMap,只是键没有附属值而已。
所以TreeMap集合指定大小规则有2种方式:和TreeSet差不多!
1、在类中重写比较方法。2、new一个比较器来比较。

// 比较小数的大小
Double.compare(p1,p2);
// p1大返回正数,p2大返回负数,相等返回0

Map集合练习题

输出一个字符串中每个字符出现的次数。

1、键盘输入字符串。
2、定义一个Map集合,键是每个字符,值是其出现的次数。
3、遍历字符串中的每个字符。
4、拿字符去Map集合中看是否存在字符,有+1,无则存入,并设该值为1.

public static void main(String[] args) {
        // 输出一个字符串中每个字符出现的次数
        System.out.println("请输入一个字符串:");
        Scanner scanner=new Scanner(System.in);
        String s=scanner.nextLine();
        Map<Character,Integer> map=new HashMap<>();
        for(int i=0;i<s.length();i++){
            char c=s.charAt(i);
            if(map.containsKey(c)){
                map.put(c,map.get(c)+1);
            }else {
                map.put(c,1);
            }
        }
        System.out.println(map);
    }

图书增删改查练习(集合)

public class test {
    // 图书 书架 ,图书增删改查
    // 书架用Map存放 图书为List
                public static final Map<String , List<Book>> books=new HashMap<>();
                public static final Scanner scanner=new Scanner(System.in);


                public static void main(String[] args) {
                // 进入提示界面
                inputCommand();

            }
            public static void inputCommand(){
                System.out.println("请选择操作:1、增加书籍;2、修改书籍;3、删除书籍;4、查询书籍");
                String s=scanner.nextLine();
                switch (s){
                    case "1":
                        System.out.println("增加书籍");
                        addBook();
                        break;
                    case "2":
                System.out.println("修改书籍");
                updateBook();
                break;
            case "3":
                System.out.println("删除书籍");
                deleteBook();
                break;
            case "4":
                System.out.println("查询书籍");
                queryBook();
                break;
            default:
                System.out.println("没有此操作!,请重新选择");
        }
        inputCommand();
    }

    private static void deleteBook() {
        queryBook();
        while (true){
            System.out.println("输入类型");
            String type=scanner.nextLine();
            if(books.containsKey(type)){
                System.out.println("输入要删除的书籍:");
                String name=scanner.nextLine();
                Book book = getBookByTypeAndName(type, name);
                if(book==null){
                    System.out.println("没有此书");
                }else {
                    List<Book> list = books.get(type);
                    list.remove(book);
                    return;
                }
            }else {
                System.out.println("没有此类型");
            }
        }
    }

    private static void updateBook() {
        queryBook();
        if(books.size()==0){
            System.out.println("目前没有书籍!无法修改!");
            return;
        }
        System.out.println("输入修改的类型:");
        String type=scanner.nextLine();
        while (true){
            if(books.containsKey(type)){
                System.out.println("输入要修改的书名:");
                String name=scanner.nextLine();

                if(getBookByTypeAndName(type, name)==null){
                    System.out.println("没有此书");
                }else{
                    for (Book book : books.get(type)) {
                        if(book.getName().equals(name)){
                            System.out.println("输入改后书名:");
                            String bookname=scanner.nextLine();
                            System.out.println("输入改后作者:");
                            String bookAuthor=scanner.nextLine();
                            System.out.println("输入改后价格:");
                            Double bookPrice=Double.parseDouble(scanner.nextLine());

                            book.setName(bookname);
                            book.setAhthor(bookAuthor);
                            book.setPrice(bookPrice);
                            queryBook();
                            return;
                        }
                    }
                }
            }else {
                System.out.println("没有这个类型,请重新输入:");
            }
        }
    }

    private static Book getBookByTypeAndName(String type, String name) {

        List<Book> list = books.get(type);
        for (Book book : list) {
            if(book.getName().equals(name)){
               return book;
            }
        }
        return null;
    }

    private static void queryBook() {
        if(books.size()==0){
            System.out.println("目前没有书籍!");
            return;
        }
        //  获取Set<(kv),(kv),(kv)>
        System.out.println("类型\t\t\t书名\t\t\t作者\t\t\t价格");
        Set<Map.Entry<String, List<Book>>> entries = books.entrySet();
        for (Map.Entry<String, List<Book>> entry : entries) {
                if(entry.getValue().size()==0){
                    System.out.println("此类型下无书!");
                    return;
                }
                // 迭代器
                Iterator<Book> iterator=entry.getValue().iterator();
                System.out.println(entry.getKey());
                while (iterator.hasNext()){
                    Book book = iterator.next();
                    System.out.println("\t\t\t"+book.getName()+"\t\t"+book.getAhthor()+"\t\t"+book.getPrice());
                }
        }
    }

    // Map>  
    private static void addBook() {
        System.out.println("请输入要增加书籍的类型:");
        String s = scanner.nextLine();
        List<Book> list;
        // 类型判断
        if(books.containsKey(s)){
            list=books.get(s);
        }else {
            list=new ArrayList<>();
            books.put(s,list);
        }
        System.out.println("请输入书名:");
        String name=scanner.nextLine();
        System.out.println("请输入作者:");
        String author=scanner.nextLine();
        System.out.println("请输入价格:");
        Double price=Double.parseDouble(scanner.nextLine());
        Book book=new Book(name,author,price);
        list.add(book);
        books.put(s,list);
        System.out.println("加入成功!");
    }
}

冒泡排序

冒泡排序作用:可以用于对数组或者集合的元素进行大小排序!

冒泡排序核心算法思想:
每次从数组的第一个位置开始两两比较,找出当前最大值冒出到当前最后面即可。
冒泡排序的实现核心:
1、确定总共需要冒几轮:数组的长度-1。
2、每轮两两比较几次:数组长度-i-1次。

public static void main(String[] args) {
        int[] arr=new int[] {55,22,99,88};
        for (int i = 0; i <arr.length-1 ; i++) {
            for (int j = 0; j <arr.length-i-1 ; j++) {
                if(arr[j]>arr[j+1]){
                    int temp=arr[j+1];
                    arr[j+1]=arr[j];
                    arr[j]=temp;
                }
            }
        }
        System.out.println(Arrays.toString(arr));
    }

选择排序

选择排序的思想:从当前位置开始找出后面的较小值与该位置交换。
(第一个为比较元素,将1和2比,小的放前面。再1和3比,小的放前面,以此类推。再第二个为比较元素,2和3,再2和4,在2和5…)
选择排序的实现思路:
1、控制选择几轮:数组的长度-1
2、控制每轮当前位置开始比较几次:数组长度-i-1.

public static void main(String[] args) {
        int arr[]={5,1,3,2};
        for (int i = 0; i <arr.length ; i++) {
            //  与后边的元素,一个个比较
            for (int j = i+1; j <arr.length ; j++) {
                if(arr[j]<arr[i]){
                    int temp=arr[i];
                    arr[i]=arr[j];
                    arr[j]=temp;
                }
            }
        }
        System.out.println(Arrays.toString(arr));
    }

二分查找

正常查找:从第一个元素开始遍历,一个个往后找,综合查询比较耗时。
二分查找:二分查找的前提,数组必须已经排好序。
每次先与中间的元素进行比较,如果大于往右边找,如果小于往左边找,如果等于就返回该元素的索引位置!如果没有该元素,返回-1。综合性能比较好。
实现方式:
1、定义开始索引、结束索引
2、取中间值,数大于中间值,start=中间索引+1
取中间值,数小于中间值,end=中间索引-1
3、循环条件:start<=end

public class test {
    public static void main(String[] args) {
        int arr[]={10,14,21,38,35,47,43,81,87,99};
        int i = binarySearch(arr, 87);
        System.out.println(i);
    }

    private static int binarySearch(int[] arr, int number) {
        // 二分查找
        int start=0;
        int end=arr.length-1;
        while (start<=end){
            int middleIndex=(start+end)/2;
            if(number>arr[middleIndex]){
                start=middleIndex+1;
            }else if(number<arr[middleIndex]){
                end=middleIndex-1;
            }else if(number==arr[middleIndex]){
                return middleIndex;
            }
        }
        return -1;
    }
}

异常

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

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

java会为常见的代码异常都设计一个类来代表。
异常的体系:
Java中异常继承的根类是:Throwable
Java 基础笔记_第17张图片
Error:错误的意思,无法通过处理的错误,一旦出现,程序员无能为力了,只能重启系统,优化项目。
比如内存崩溃,JVM本身崩溃,这个程序员无需理会。
Exception:才是异常类,它是开发中代码在编译或者执行的过程中可能出现的错误,它需提前吹了,以便程序更健壮!

Exception一场的分类:
1、编译时异常:继承自Exception的异常或者其子类,编译阶段就会报错,必须程序员处理,否则代码编译就不通过。
编译时异常的作用:担心程序员技术不行,在编译阶段就爆出错误,目的在于提醒程序员这里可能报错,请检查并注意不要出bug。

2、运行时异常:继承自RuntimeException的异常可以处理也可以不处理,编译阶段是不会出错的,但是运行阶段可能出现,建议提前处理!
Java 基础笔记_第18张图片

异常的默认处理机制:

1、默认会在出现异常的代码那里自动添加一个异常对象。例如ArithmeticException。
2、异常会从方法中出现的点这里抛出给调用者,调用者最终抛出给JVM虚拟机。
3、虚拟机接收到异常对象后,先在控制台直接输出异常栈信息数据。(因为是异常栈,所以最根本的错误,在最上边一行)
4、直接从当前执行的异常点干掉当前程序。
5、后续代码没有机会执行了,因为程序已经死亡。


默认的异常处理机制并不好,一旦真的出现异常,程序立即死亡。

编译时异常处理方式

方式一:
抛出异常格式:
方法 throws 异常1, 异常2,...{ }
建议抛出异常的方式:
方法 throws Exception { }
出现编译时异常的地方层层把异常抛出去给调用者,调用者最终抛出给JVM虚拟机。
JVM虚拟机输出异常信息,直接干掉程序,这种方式与默认方式是一样的。这种方式也不好!

方式二:
在出现异常的地方自己处理,谁出现谁处理。
捕获异常格式:
Java 基础笔记_第19张图片
Java 基础笔记_第20张图片
这种方式不是最好的,上层调用者不能直接知道底层的执行情况

方式三:
在出现异常的地方把异常一层层抛给调用者
最外层调用者集中捕获处理!(规范做法,理论上最好的方式)
Java 基础笔记_第21张图片

运行时异常处理机制

运行时异常编译阶段不报错,可处理可不处理,建议处理!
运行时异常会自动抛出,不用我们手动抛出。
运行时异常处理规范:直接在最外层处理即可(main函数中),底层会自动抛出,只要异常不给到JVM,程序就不会挂掉。
Java 基础笔记_第22张图片

finally关键字

用在捕获处理的异常格式中的,放在最后面。
finally的作用:可以在代码执行完毕后进行资源释放操作。
Java 基础笔记_第23张图片
不建议在finally中写return,会覆盖前面所有的return值!

什么是资源?
资源都是实现了Closeable接口的,都自带close()关闭方法!!

异常语法注意(了解)

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

自定义异常

自定义异常定义:
Java 基础笔记_第24张图片

自定义编译时异常:
Java 基础笔记_第25张图片
Java 基础笔记_第26张图片

自定义运行时异常:
Java 基础笔记_第27张图片
Java 基础笔记_第28张图片

多线程

什么是进程?
程序是静止的,运行中的程序就是进程。

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

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

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

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

多线程的创建

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

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

继承Thread类的优缺点:
优点:编码简单
缺点:线程类已经继承了Thread类无法计划处呢个其他类了,功能不能通过继承拓展(单继承的局限性)

public class test {
    // 启动后的 test 当成一个进程
    public static void main(String[] args) {
        // 创建一个线程对象
        Thread t=new MyThread();
        // 调用线程对象的start()方法启动线程,最终还是执行run()方法!
        t.start();
        for (int i = 0; i <100 ; i++) {
            System.out.println("子线程输出:"+i);
        }
    }
}

// 定义一个线程类继承 Thread 类
class MyThread extends Thread{
    @Override
    public void run() {
        // 线程执行的方法
        for (int i = 0; i <100 ; i++) {
            System.out.println("子线程输出:"+i);
        }
    }
}

线程的注意事项
1、线程的启动必须调用start()方法。否则当成普通类处理。

  • 如果线程直接调用run()方法,相当于变成了普通类的执行,此时将只有主线程在执行他们!
  • start()方法底层其实是给CPU注册当前线程,并且触发run()方法执行


2、建议线程先创建子线程,主线程的任务放在子线程的创建之后。否则主线程永远是先执行完!

线程常用API

  • public void setName(String name):给当前线程取名字。
  • public void getName():获取当前线程的名字。
    — 线程存在默认名称,子线程默认名称是:Thread-索引。
    — 主线程默认名称是:main
  • public static Thread currentThread():获取当前线程对象,这个代码在哪个线程中,就得到哪个线程对象。
public class test {

    public static void main(String[] args) {
        Thread t1=new MyThread();
        Thread t2=new MyThread();
        t1.setName("子线程1");
        t1.start();
        t2.setName("子线程2");
        t2.start();

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

// 定义一个线程类继承 Thread 类
class MyThread extends Thread{
    @Override
    public void run() {
        // 线程执行的方法
        for (int i = 0; i <100 ; i++) {
            System.out.println(Thread.currentThread().getName()+"输出:"+i);
        }
    }
}

线程休眠API

  • public static void sleep(long time):让当前线程休眠多少毫秒
      eg:Thread.sleep(1000); //让当前线程休眠1s

    通过Thread类的有参数构造器为当前线程对象取名字
  • public Thread()
  • public Thread(String name):创建线程对象并取名字
public class test {
    public static void main(String[] args) {
        Thread t1=new MyThread("子线程1");
        Thread t2=new MyThread("子线程2");
        t1.start();
        t2.start();
        for (int i = 0; i <100 ; i++) {
            System.out.println("main线程输出:"+i);
        }
    }
}

// 定义一个线程类继承 Thread 类
class MyThread extends Thread{
    public MyThread(String name) {
        super(name); // 调用父类的有参构造器初始化当前对象
    }

    @Override
    public void run() {
        // 线程执行的方法
        for (int i = 0; i <100 ; i++) {
            System.out.println(Thread.currentThread().getName()+"输出:"+i);
        }
    }
}

方式二:实现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接口,可以继续继承其他类,而且可以继续实现其他接口(避免了单继承的局限性)
  • 同一个线程任务对象可以被包装成多个线程对象
  • 适合多个相同的程序代码的线程去共享同一个资源(后面内容)
  • 实现解耦操作,线程任务代码可以被多个线程共享,线程任务代码和线程独立。
  • 线程池可以放入实现 RunnableCallable 线程任务对象(后面了解)
  • 不能直接得到线程执行的结果

注意:Thread类本身也是实现Runnable接口的

public class test {
    public static void main(String[] args) {
        // 创建线程任务对象
        Runnable target=new MyThread();
        // 把线程任务对象包装成线程对象
        Thread t=new Thread(target,"1号线程");
        t.start();
        for (int i = 0; i <100 ; i++) {
            System.out.println(Thread.currentThread().getName()+"线程输出"+i);
        }
    }
}

class MyThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <100 ; i++) {
            System.out.println(Thread.currentThread().getName()+"线程输出"+i);
        }
    }
}

方式二的匿名内部类写法:

public class test {
    public static void main(String[] args) {
        // 把线程任务对象包装成线程对象
        Runnable r=new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i <100 ; i++) {
                    System.out.println(Thread.currentThread().getName()+"线程输出"+i);
                }
            }
        };
        Thread t=new Thread(r,"1号线程");
        t.start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i <100 ; i++) {
                    System.out.println(Thread.currentThread().getName()+"线程输出"+i);
                }
            }
        },"2号线程").start();

        for (int i = 0; i <100 ; i++) {
            System.out.println(Thread.currentThread().getName()+"线程输出"+i);
        }
    }
}

方式三:实现Callable接口
1、定义一个线程任务类实现Callable接口,申明线程执行的结果类型。
2、重写线程任务类的call方法,这个方法可以直接返回执行的结果
3、创建一个Callable的线程任务对象
4、把Callable的线程任务对象包装成一个未来任务对象。
5、把未来任务对象包装成线程对象。
6、调用线程的 start() 方法启动线程。

优缺点:
优点:

  • 线程任务类只是实现了Runnable接口,可以继续继承其他类,而且可以继续实现其他接口(避免了单继承的局限性)
  • 同一个线程任务对象可以被包装成多个线程对象
  • 适合多个相同的程序代码的线程去共享同一个资源(后面内容)
  • 实现解耦操作,线程任务代码可以被多个线程共享,线程任务代码和线程独立。
  • 线程池可以放入实现Runnable或Callable线程任务对象(后面了解)
  • 能直接得到线程执行的结果

缺点:编码复杂

public class test {
    public static void main(String[] args){
        // 创建一个Callable线程任务对象
        Callable call=new MyCallable();
        // 未来任务对象其实就是一个Runnable对象
        // 未来任务对象可以在线程执行完毕之后去得到线程的执行结果
        FutureTask<String> task=new FutureTask<>(call);
        // 把未来任务对象包装成线程对象
        Thread t=new Thread(task);
        t.start();

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

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

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

线程安全问题

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

public class test {
    public static void main(String[] args){
        Account account=new Account(100000,"");
        Thread a1=new DrawThread(account,"小明");
        a1.start();
        Thread a2=new DrawThread(account,"小红");
        a2.start();
    }
}
class DrawThread extends Thread{
    // 定义一个成员变量接收账户对象
    private Account account;
    public DrawThread(Account account,String name){
        super(name);
        this.account=account;
    }
    @Override
    public void run() {
        account.drawMoney(100000d);
    }
}
class Account {
    private double money; // 余额
    private String cardId;

    public void drawMoney(Double money){
        // 开始判断取钱逻辑
        String name=Thread.currentThread().getName();
        if(this.money>=money){
            System.out.println(name+"取钱,钱够,吐出"+money);
            this.money-=money;
            System.out.println("余额="+this.money);
        }else {
            System.out.println(name+"取钱,钱不够");
        }
    }
    public Account(double money, String cardId) {
        this.money = money;
        this.cardId = cardId;
    }
}

线程同步

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

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

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

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

同步代码块

格式 :
synchronized( 锁对象 ){ //访问共享资源的核心代码 }
锁对象 :理论上可以是任意的 “唯一”对象 即可
原则上:锁对象建议 使用共享资源
— 在实例方法中建议用this作为锁对象。此时this正好是共享资源!必须代码高度面向对象。
— 在静态方法中建议用类名.class字节码作为锁对象。

public class test {
    public static void main(String[] args){
        Account account=new Account(100000,"");
        Thread a1=new DrawThread(account,"小明");
        a1.start();
        Thread a2=new DrawThread(account,"小红");
        a2.start();
    }
}
class DrawThread extends Thread{
    // 定义一个成员变量接收账户对象
    private Account account;
    public DrawThread(Account account,String name){
        super(name);
        this.account=account;
    }
    @Override
    public void run() {
        account.drawMoney(100000d);
    }
}
class Account {
    private double money; // 余额
    private String cardId;

    public void drawMoney(Double money){
        // 开始判断取钱逻辑
        String name=Thread.currentThread().getName();
        synchronized (this){
            if(this.money>=money){
                System.out.println(name+"取钱,钱够,吐出"+money);
                this.money-=money;
                System.out.println("余额="+this.money);
            }else {
                System.out.println(name+"取钱,钱不够");
            }
        }
    }
    public Account(double money, String cardId) {
        this.money = money;
        this.cardId = cardId;
    }
}

同步方法

用法:直接给方法加上一个修饰符 synchronized
原理:同步方法的原理和同步代码块的底层原理其实是完全一样的,只是同步方法把整个方法的代码都锁起来。
同步方法其实底层也是有锁对象的。

public class test {
    public static void main(String[] args){
        Account account=new Account(100000,"");
        Thread a1=new DrawThread(account,"小明");
        a1.start();
        Thread a2=new DrawThread(account,"小红");
        a2.start();
    }
}
class DrawThread extends Thread{
    // 定义一个成员变量接收账户对象
    private Account account;
    public DrawThread(Account account,String name){
        super(name);
        this.account=account;
    }
    @Override
    public void run() {
        account.drawMoney(100000d);
    }
}
class Account {
    private double money; // 余额
    private String cardId;

    public synchronized void drawMoney(Double money){
        // 开始判断取钱逻辑
        String name=Thread.currentThread().getName();
        if(this.money>=money){
            System.out.println(name+"取钱,钱够,吐出"+money);
            this.money-=money;
            System.out.println("余额="+this.money);
        }else {
            System.out.println(name+"取钱,钱不够");
        }
    }
    public Account(double money, String cardId) {
        this.money = money;
        this.cardId = cardId;
    }
}

lock显示锁

同步代码块/同步方法具有的功能Lock都有,除此之外更强大。
但线程安全,性能差

Lock锁也称同步锁,加锁与释放锁方法化了,如下:

  • public void lock():加同步锁
  • public void unlock():释放同步锁
public class test {
    public static void main(String[] args){
        Account account=new Account(100000,"");
        Thread a1=new DrawThread(account,"小明");
        a1.start();
        Thread a2=new DrawThread(account,"小红");
        a2.start();
    }
}
class DrawThread extends Thread{
    // 定义一个成员变量接收账户对象
    private Account account;
    public DrawThread(Account account,String name){
        super(name);
        this.account=account;
    }
    @Override
    public void run() {
        account.drawMoney(100000d);
    }
}
class Account {
    private double money; // 余额
    private String cardId;
    private final Lock lock=new ReentrantLock();

    public void drawMoney(Double money){
        // 开始判断取钱逻辑
        String name=Thread.currentThread().getName();
        lock.lock(); // 上锁
        try {
            if(this.money>=money){
                System.out.println(name+"取钱,钱够,吐出"+money);
                this.money-=money;
                System.out.println("余额="+this.money);
            }else {
                System.out.println(name+"取钱,钱不够");
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock(); // 解锁
        }
    }
    public Account(double money, String cardId) {
        this.money = money;
        this.cardId = cardId;
    }
}

线程通信

线程通信:多个线程因为在同一个进程中,所以相互通信比较容易。

注意:线程通信一定是多个线程在操作同一个资源才需要通信。线程通信必须先保证线程安全,否则毫无意义,代码也会报错!

核心方法

  • public void wait():让线程进入到等待状态,此方法必须锁对象调用。
  • public void notify():唤醒当前锁对象上等待状态的某个线程,此方法必须锁对象调用。
  • public void notifyAll():唤醒当前锁对象上等待状态的全部线程,此方法必须锁对象调用。
// 生产者与消费者问题举例:生产不能不能过剩,消费不能没有
public class test {
    public static void main(String[] args){
        Account account=new Account(100000,"0001");
        new DrawThread(account,"小明").start();
        new DrawThread(account,"小红").start();
        new SaveThread(account,"亲爹").start();
        new SaveThread(account,"干爹").start();
        new SaveThread(account,"岳父").start();
    }
}
class DrawThread extends Thread{
    // 定义一个成员变量接收账户对象
    private Account account;
    public DrawThread(Account account,String name){
        super(name);
        this.account=account;
    }
    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(3000);
                account.drawMoney(100000d);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class SaveThread extends Thread{
    private Account account;
    public SaveThread(Account account,String name){
        super(name);
        this.account=account;
    }
    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(3000);
                account.saveMoney(100000d);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}
class Account {
    private double money; // 余额
    private String cardId;
    private final Lock lock=new ReentrantLock();

    public synchronized void drawMoney(Double money){
        String name=Thread.currentThread().getName();
        try {
            if(this.money>=money){
                this.money-=money;
                System.out.println(name+"取钱,余额"+this.money);
                // 没钱了,先唤醒别人,再等待自己
                this.notifyAll();
                this.wait();
            }else {
                // 没钱了,先唤醒别人,再等待自己
                this.notifyAll();
                this.wait();

            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public Account(double money, String cardId) {
        this.money = money;
        this.cardId = cardId;
    }

    public synchronized void saveMoney(double v) {
        String name=Thread.currentThread().getName();
        try {
            if(this.money>0){
                // 有钱了,先唤醒别人,再等待自己
                this.notifyAll();
                this.wait();

            }else {
                this.money+=v;
                System.out.println(name+"存钱,余额="+this.money);
                // 有钱了,先唤醒别人,再等待自己
                this.notifyAll();
                this.wait();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

线程状态

Java 基础笔记_第29张图片

线程池

线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复的使用,省去了频繁创建和销毁线程对象的操作,无需反复创建线程而消耗过多资源。

使用线程池的好处:
1、降低资源消耗。(减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务)
2、提高响应速度。(不需要频繁的创建线程,如果有线程可以直接用,不会出现系统僵死)
3、提高线程的可管理性。(线程池可以约束系统最多只能有多少个线程,不会因为线程过多而死机)

线程的核心思想:线程复用,同一个线程可以被重复使用,来处理多个任务。


线程池子啊Java中的代表类:ExecutorService(接口)
Java在Executors类下提供了一个静态方法得到一个线程池的对象

  • public static ExecutorService newFixedThreadPool(int nThreads):创建一个线程池返回。


ExecutorService提交线程任务对象执行的方法

  • Future submit(Runnable task):提交一个Runnable的任务对象给线程池执行。


关闭线程池的方式
1、pools.shutdown();:等待任务执行完毕以后才会关闭线程
2、pools.shutdownNow();:立即关闭线程池,无论任务是否执行完毕!
线程池中的线程可以被复用,线程用完以后可以去继续执行其他任务。

// Runnable 做线程池的任务
public class test {
    public static void main(String[] args) {
        // 创建一个线程池,指定线程的固定数量是3
        ExecutorService pools= Executors.newFixedThreadPool(3);
        // 添加线程任务让线程池处理
        Runnable task=new MyRunnable();
        pools.submit(task); // 第1次提交任务,此时线程创建新线程,自动触发执行
        pools.submit(task); // 第2次提交任务,此时线程创建新线程,自动触发执行
        pools.submit(task); // 第3次提交任务,此时线程创建新线程,自动触发执行
        pools.submit(task); // 第4次提交任务,复用之前线程
        pools.shutdown(); // 等所有任务完成后,关闭线程池
    }
}

class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+"==>"+i);
        }
    }
}
//Callable做线程池的任务
public class test {
    public static void main(String[] args) {
        // 创建一个线程池,指定线程的固定数量是3
        ExecutorService pools= Executors.newFixedThreadPool(3);
        // 提交Callable的任务对象后,返回一个未来任务对象。
        Future<String> t1 = pools.submit(new MyCallable(100));
        Future<String> t2 = pools.submit(new MyCallable(200));
        Future<String> t3 = pools.submit(new MyCallable(300));
        Future<String> t4 = pools.submit(new MyCallable(400));

        // 获取线程执行的任务结果
        try {
            System.out.println(t1.get());
            System.out.println(t2.get());
            System.out.println(t3.get());
            System.out.println(t4.get());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class MyCallable implements Callable<String>{
    private int n;

    public MyCallable(int n){
        this.n=n;
    }

    @Override
    public String call() throws Exception {
        int sum=0;
        for (int i = 1; i <=n ; i++) {
            sum+=i;
        }
        return Thread.currentThread().getName()+"执行的结果是==>"+sum;
    }
}

死锁

死锁:是多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。
由于线程被无限期地阻塞,因此程序不可能正常终止。

Java 死锁产生的四个必要条件:

  • 互斥使用。当资源被一个线程使用(占用)时,别的线程不能使用。
  • 不可抢占。资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
  • 请求和保持。当资源请求者在请求其它资源的同时保持原有资源的的占有。
  • 循环等待。存在一个等待循环队列:p1要p2的资源,p2要p1的资源。这样形成了一个等待环路。

    当上述四个条件都成立时,形成死锁。只要破坏其中一个条件,就可以让死锁消失。
public class test {
   // 定义资源对象
   public static Object resources01=new Object();
   public static Object resources02=new Object();

   public static void main(String[] args) {
       new Thread(new Runnable() {
           @Override
           public void run() {
               synchronized (resources01){
                   System.out.println("线程1占用资源1,请求资源2");
                   try {
                       Thread.sleep(1000);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   synchronized (resources02){
                       System.out.println("线程1占用资源2");
                   }
               }
           }
       }).start();
       new Thread(new Runnable() {
           @Override
           public void run() {
               synchronized (resources02){
                   System.out.println("线程2占用资源2,请求资源1");
                   try {
                       Thread.sleep(1000);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   synchronized (resources01){
                       System.out.println("线程2占用资源1");
                   }
               }
           }
       }).start();
   }
}

volatile关键字

多个线程访问共享变量,会出现一个线程修改变量的值后,其他线程看不到变量最新值的情况。
并发编程下,多线程修改变量,会出现线程间变量的不可见性。
每个线程都有自己的工作内存,线程都是从主内存拷贝共享变量的副本值。
每个线程都是在自己的工作内存中从操作共享变量的。

JMM(Java Memory Model):java内存模型。描述了Java程序汇总各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这样的底层细节。
Java 基础笔记_第30张图片

// 线程间变量的不可见性实例
public class test {
    public static void main(String[] args) {
        ThreadDemo t=new ThreadDemo();
        t.start();
        while (true){
            if(t.isFlag()){
                System.out.println("主程序进入执行~");
            }
        }
    }
}
class ThreadDemo extends Thread{
    private boolean Flag=false;

    public boolean isFlag() {
        return Flag;
    }
    public void setFlag(boolean flag) {
        Flag = flag;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Flag=true;
    }
}

线程间变量不可见性常用解决方式:

  • 加锁。每次加锁会清空线程自己的工作内存,从新读取主内存最新值。
  • 对共享变量进行volatile关键字修饰。volatile修饰的变量可以在多线程并发修改下,实现线程间变量的可见性。一旦一个线程修改了volitale修饰的变量,另一个县城可以立即取到最新值。
// 方式1、加锁
public class test {
    public static void main(String[] args) {
        ThreadDemo t=new ThreadDemo();
        t.start();
        while (true){
            synchronized (ThreadDemo.class){
                if(t.isFlag()){
                    System.out.println("主程序进入执行~");
                }
            }
        }
    }
}
// 方式2、用volatile修饰
class ThreadDemo extends Thread{
    private volatile boolean Flag=false;

    public boolean isFlag() {
        return Flag;
    }
    public void setFlag(boolean flag) {
        Flag = flag;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Flag=true;
    }
}

volatile与synchronized说明

  • volatile只能修饰实例变量和类变量,而synchronized可以修饰方法,以及代码块。
  • volatile保证数据的可见性,但不保证原子性(多线程进行写操作,不保证线程安全);而synchronized是一种排他(互斥)的机制。

原子性

原子性:是指一批操作是一个整体,要么同时成功,要么同时失败,不能被干扰。
volatile只能保证线程间变量的可见性,但不能保证变量操作的原子性。

原子性操作的保证:

  • 加锁。性能较差。
  • 使用原子类。
    原子型Integer,可以实现原子更新操作:
    public AtomicInteger():初始化一个默认值为0的原子型Integer
    public AtomicInteger(int initialValue):初始化一个指定的原子型Integer
    int get():获取值
    int getAndIncrement():以原子方式将当前值加1,返回的是自增前的值。
    int incrementAndGet():以原子方式将当前值加1,返回的是自增后的值。
    int addAndGet(int data):以原子方式输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
    int getAndSet(int value):以原子方式设置为newValue的值,并返回旧值。
// 原子类示例
public class test {
    public static void main(String[] args) {
        Runnable target=new MyRunnable();
        for (int i = 1; i <=100 ; i++) {
            new Thread(target).start();
        }
    }
}
class MyRunnable implements Runnable{
    // 创建一个Integer更新的原子类,初始值是0
    private AtomicInteger atomicInteger=new AtomicInteger();
    @Override
    public void run() {
        for (int i = 1; i <= 100 ; i++) {
            System.out.println("count===>"+atomicInteger.incrementAndGet());
        }
    }
}

为什么使用原子类可以保证原子性操作,且性能好,而且线程安全呢?
因为底层基于CAS乐观锁机制,每次修改数据不会加锁,等到修改的时候判断有没有别人修改。

CAS (乐观锁) 与Synchronized (悲观锁) 的区别:

  • 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。(性能较差)
  • 乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但在更新时会判断一下再此期间别人有没有去更新这个数据。(综合性能较好)

并发包

并发包的来历:
在实际开发中如果不需要考虑线程安全问题,大家不需要做线程安全,因为如果做了反而性能不好!
但开发中有很多业务是需要考虑线程安全问题的,此时就必须考虑了。否则业务会出现问题。Java为很多业务场景提供了性能优异,且线程安全的并发包,程序员可以选择使用。

  • HashMap:线程不安全,性能好。
  • Hashtable:锁整个集合,线程安全,性能较差,被淘汰了。底层都是锁。
  • ConcurrentHashMap(并发包):线程安全,性能得到极大提升!分段式锁!只锁自己操作的元素位置。综合性能最好,建议并发下使用!

    子线程.join():主线程不与其竞争CPU,让子线程先跑完。
public class test {
    //  演示HashMap在高并发下的不安全性。
    // 运行结果:元素个数:963116
    public static Map<String,String> maps=new HashMap<>();

    public static void main(String[] args) {
        Runnable target=new MyRunnable();
        Thread t1=new Thread(target);
        Thread t2=new Thread(target);
        t1.start();
        t2.start();
        try {
            t1.join(); // 主线程不竞争CPU,让 t1 先跑完
            t2.join(); // 主线程不竞争CPU,让 t2 先跑完
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("元素个数:"+maps.size());
    }
}
class MyRunnable implements Runnable{

    @Override
    public void run() {
        for (int i = 1; i <= 500000 ; i++) {
            test.maps.put(Thread.currentThread().getName()+i,Thread.currentThread().getName()+i);
        }
    }
}

将上例改为线程安全,只需将 HashMap 改为HashTable。

public static Map<String,String> maps=new Hashtable<>();
// 运行结果:元素个数:1000000

将上例改为线程安全,只需将 HashMap 改为ConcurrentHashMap。

public static Map<String,String> maps=new ConcurrentHashMap<>();

CountDownLatch (并发包,了解):
CountDownLatch允许一个或多个现车个等待其他线程完成操作,再执行自己。

构造器:public CountDownLatch(int count):初始化唤醒需要的down几步。
方法:
public void await() throws InterrupaedException:让当前线程等待,必须down减到0才继续执行。
public void countDown():计数器进行减1 ( down 1 )

// ThreadA只能打印AC,ThreadB只能打印B,让ThreadA等待ThreadB,使结果打印 A B C
public class test {
    public static void main(String[] args) {
        CountDownLatch c=new CountDownLatch(1);
        new ThreadA(c).start();
        new ThreadB(c).start();
    }
}
class ThreadA extends Thread{
    CountDownLatch c;
    public ThreadA(CountDownLatch c) {
        this.c=c;
    }

    @Override
    public void run() {
        System.out.println("A");
        try {
            // 等待自己,让当前线程让出CPU
            c.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("C");
    }
}
class ThreadB extends Thread{
    CountDownLatch c;
    public ThreadB(CountDownLatch c) {
        this.c=c;
    }
    @Override
    public void run() {
        System.out.println("B");
        c.countDown(); // 让监督者中的计算器减1,被等待的线程就唤醒了!
    }
}

CylicBarrier (并发包):
某个线程任务必须等待其他线程执行完毕后,才能最终触发自己执行。
构造器:
public CyclicBarrier(int parties , Runnable barrierAction):用于在线程到达屏障parties的值时,优先执行barrierAction,方便处理更复杂的业务场景。
方法:
public int await():每个线程调用await()方法告诉CyclicBarrier我已经到达屏障,然后当前线程被回收。

// 5个员工开会,只有人全到了,会议才能开始
public class test {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier=new CyclicBarrier(5,new MeetingRunnable());
        // 创建5个线程
        for (int i = 0; i < 5; i++) {
            new EmployeeThread("员工"+i,cyclicBarrier).start();
        }
    }
}
class MeetingRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("会议开始,"+Thread.currentThread().getName()+"主持会议!");
    }
}
class EmployeeThread extends Thread{
    CyclicBarrier cyclicBarrier;
    public EmployeeThread(String name,CyclicBarrier cyclicBarrier){
        super(name);
        this.cyclicBarrier=cyclicBarrier;
    }
    public void run() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"进入会议");
        try {
            cyclicBarrier.await();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Semaphore(并发包):
Semaphore(发信号)的主要作用是控制线程并发占锁的数量。
synchronized可以起到“锁”的作用,但某个时间段内,只能有一个线程允许执行。
Semaphore可设置同时允许多个线程执行。

Semaphore的构造器:
public Semaphore(int permits):permits表示许可线程的数量
public Semaphore(int permits, boolean fair):fair表示公平性。
Semaphore的方法:
public void acquire() throws InterruptedException:表示许可
pubilc void release():表示释放许可

public class test {
    public static void main(String[] args) {
        Service service=new Service();
        for (int i = 0; i < 5; i++) {
            new MyThread(service).start();
        }
    }
}
class MyThread extends Thread{
    Service service;
    public MyThread(Service service) {
        this.service=service;
    }

    @Override
    public void run() {
        service.login();
    }
}
class Service{
    private Semaphore semaphore=new Semaphore(2);
    // 登录
    public void login(){
        try {
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName()+"进入时间:"+System.currentTimeMillis());
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName()+"登录成功!");
            System.out.println(Thread.currentThread().getName()+"离开时间"+System.currentTimeMillis());
            semaphore.release();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Exchanger(并发包):
Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。

两个线程通过exchange方法交换数据,如果第一个线程先执行exchange方法,它会一直等待第二个线程也执行exchange方法。当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。

Exchanger构造方法:public Exchanger()
Exchangerz重要方法:public V exchange(V x)

  • 需要两个线程
  • 需要一个交换对象负责交换两个线程执行的结果
public class test {
    public static void main(String[] args) {
        Exchanger<String> exchanger=new Exchanger<>();
        new Girl(exchanger).start();
        new Boy(exchanger).start();
    }
}
class Girl extends Thread{
    Exchanger<String> exchanger;
    public Girl(Exchanger<String> exchanger) {
        this.exchanger=exchanger;
    }

    @Override
    public void run() {
        try {
            System.out.println("女孩做好信物,");
            String rs = exchanger.exchange("");
            System.out.println("女孩收到信物:"+rs);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}class Boy extends Thread{
    Exchanger<String> exchanger;
    public Boy(Exchanger<String> exchanger) {
        this.exchanger=exchanger;
    }
    @Override
    public void run() {
        try {
            System.out.println("男孩做好信物,");
            String rs = exchanger.exchange("");
            System.out.println("男孩收到信物:"+rs);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

Lmabda表达式

JDK1.8 开始之后的新技术。
作用:简化匿名内部类的代码写法。

// Lambda表达式的格式:

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

Lambda表达式的使用前提:

  • Lambda表达式并不能简化所有匿名内部类的写法
  • Lambda表达式只能简化接口中只有一个抽象方法的匿名内部类形式(或叫 函数式接口的匿名内部类写法)。
    @FunctionalInterface:函数式接口注解。一旦某个接口加上了这个注解,这个接口只能有且仅有一个抽象方法。这个接口就可以被Lambda表达式简化
// Lambda表达式简化Runnable接口的匿名内部类
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("内容");
    }
}).start();
// 等价于
new Thread(() -> {
    System.out.println("内容");
}).start();

// Lambda表达式简化Comparator接口的匿名内部类
 List<Integer> list=new ArrayList<>();
 Collections.sort(list, new Comparator<Integer>() {
     @Override
     public int compare(Integer o1, Integer o2) {
         return o1-o2;
     }
 });
 // 等价于
 Collections.sort(list,(Integer o1, Integer o2)->{
     return o1-o2;
 });

Lambda表达式的省略写法:

  • Lambda表达式的方法体代码只有一行代码。可以省略大括号不写,但同时要省略分号
  • Lambda表达式的方法体只有一行代码。可以省略大括号不写。此时,若这行代码是return语句,必须省略return 不写。
  • 参数类型可以省略不写。
  • 如果只有一个参数参数类型可以省略同时() 也可以省略

方法引用

方法引用是为了进一步简化Lambda表达式的写法。

方法引用格式:类型或对象::引用的方法

方法引用有四种形式:

  • 静态方法的引用:类名::静态方法
    定义一个静态方法,把需要简化的代码放到一个静态方法中去。
    注意:被引用的方法参数列表要和函数式接口中的抽象方法的参数列表一致。
  • 实例方法的引用:对象::实例方法
    定义一个实例方法,把需要的代码放到实例方法中去。
    注意:被引用的方法的参数列表要和函数式接口中的抽象方法的参数列表一致。
  • 特定类型方法的引用:特定类型::方法
    特定类型:String,任何类型。
    注意:如果第一个参数列表中的第一个参数作为后面方法的调用者,并且其余参数作为后面方法的形参,那么就可以用特定类型方法引用了。
  • 构造器引用:类名::new
    注意:前后参数一致的情况下,又在创建对象就可以使用构造器引用
// 方法引用形式举例
public class test {
    public static void main(String[] args) {
        List<Student> list=new ArrayList<>();

        Collections.sort(list,(s1,s2)->{
            return s1.getAge()-s2.getAge();
        });
        Collections.sort(list,(s1,s2)->Student.compareByAge(s1,s2));
        // 以上方法,若前后参数是一样的,而且方法是静态方法,就可以使用静态方法引用
        // 静态方法引用
        Collections.sort(list,Student::compareByAge);

        list.forEach(s-> System.out.println(s));
        // 实例方法引用
        list.forEach(System.out::println);


        String[] strs=new String[]{"aa","cc","bb","AA","CC"};
        Arrays.sort(strs, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.compareToIgnoreCase(o2); // 按元素首字母比较(忽略大小写)
            }
        });
        // 特定类型的方法引用
        Arrays.sort(strs,String::compareToIgnoreCase);



        // 将toArray()方法返回值转换为我们需要的类型的数组
        Object[] obj = list.toArray();
        Student[] obj1= list.toArray(new IntFunction<Student[]>(){
            @Override
            public Student[] apply(int value) {
                return new Student[value];
            }
        });
        Student[] obj2= list.toArray(s -> new Student[s]);
        //构造器引用
        Student[] obj3= list.toArray(Student[]::new);
    }
}
class Student{
    private String name;
    private int age;
    public static int compareByAge(Student s1,Student s2){
        return s1.getAge()-s2.getAge();
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

Stream

Stream流:得益于Lambda所带来的函数式编程,用于解决已有集合/数组类库既有的弊端。
Stream认为集合和数组操作的API很不好用,所以采用了Steam流简化集合和数组的操作!

Stream流其实就是一根传送带,元素在上面可以被Stream流操作。

public class test {
    public static void main(String[] args) {
        List<String> list=new ArrayList<>();
        list.add("张王五");
        list.add("李四");
        list.add("张看看");
        list.add("代发");
        
        list.stream().filter(s -> s.startsWith("张")).filter(s->s.length()==3).forEach(System.out::println);
    }
}

获取Stream流的方法

public class test {
    public static void main(String[] args) {
        List<String> list=new ArrayList<>();
        list.add("张王五");
        list.add("李四");
        list.add("张看看");
        list.add("代发");
        list.stream().filter(s -> s.startsWith("张")).filter(s->s.length()==3).forEach(System.out::println);

// ---------------- Collection 集合获取流 --------------------------
        Collection<String> c=new ArrayList<>();
        Stream<String> ss = c.stream();

// ---------------- Map 集合获取流 ---------------------------------
        Map<String,Integer> map=new HashMap<>();
        // 获取键的Stream流
        Stream<String> keys = map.keySet().stream();
        // 获取值的Stream流
        Stream<Integer> vals = map.values().stream();
        // 获取键值对的Stream流
        Stream<Map.Entry<String, Integer>> kvs = map.entrySet().stream();

// ---------------- 数组 获取流 ------------------------------------
        String[] strs=new String[]{"Java","JavaSE","JavaEE"};
        Stream<String> stream = Arrays.stream(strs);
        
        // 此方法可通用
        Stream<String> strs1 = Stream.of(strs);
    }
}

Stream流的常用API
Java 基础笔记_第31张图片

// map举例
public class test {
    public static void main(String[] args) {
        List<String> list=new ArrayList<>();
        list.add("张王五");
        list.add("李四");
        list.add("张看看");
        list.add("代发");

        // 把原来的元素加工以后,重新放上去
        list.stream().map(s->"信息工程系的"+s).forEach(System.out::println);
        // 把名称都加工为学生对象,放上去
        list.stream().map(name->new Student(name)).forEach(System.out::println);
        list.stream().map(Student::new).forEach(System.out::println);
    }
}
class Student{
    String name;

    public Student(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }
}


// concat合并流举例
public class test {
    public static void main(String[] args) {
        List<String> list=new ArrayList<>();
        list.add("张王五");
        list.add("李四");
        list.add("张看看");
        list.add("代发");
        Stream<String> s1 = list.stream();
        Stream<Integer> s2 = Stream.of(10, 20, 30, 40);
        Stream<? extends Serializable> c = Stream.concat(s1, s2);
        // 或
        Stream<Object> obj=Stream.concat(s1,s2);

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

函数拼接与终结方法:Stream支持链式调用,而返回值不再为Stream接口的为终结方法,不再支持链式调用。如下图所示:
Java 基础笔记_第32张图片
收集Stream流:把Stream流的数据转回成集合。
Stream流是手段,集合才是目的。流只能使用一次。

public class test {
    public static void main(String[] args) {
        List<String> list=new ArrayList<>();
        list.add("张王五");
        list.add("李四");
        list.add("张三丰");
        list.add("张三丰");
        list.add("赵敏");
        // 把Stream流转换成set集合
        Stream<String> stream=list.stream().filter(s -> s.startsWith("张"));
        Set<String> set = stream.collect(Collectors.toSet());
        System.out.println(set);

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

        // 把Stream流转换成数组
        Stream<String> stream2=list.stream().filter(s -> s.startsWith("张"));
        Object[] arrs = stream2.toArray();
        // 或 借用构造引用 声明转换成的数组类型!
        //String[] arrsa = stream2.toArray(String[]::new);
        System.out.println(Arrays.toString(arrs));
    }
}

File

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

File类的创建文件对象的API:
包:java.io.File构造器:

  • public File(String pathname):根据路径获取文件对象
  • public File(String parent , String child):根据父路径和文件名称创建文件
  • public File(File parent, String child)

File类创建文件对象的格式:
File f=new File("绝对路径/相对路径")
File f =new File("文件对象/文件夹对象")
绝对路径:从磁盘的判读一路走到目的地的位置。依赖具体的环境。
相对路径:不带盘符的。默认直接到相对的工程目录下寻找文件。可跨平台。

文件路径分隔符:

  • 使用正斜杠:/
  • 使用反斜杠 :\\,反斜杠要转义
  • 使用分隔符API:File.separator

在这里插入图片描述
Java 基础笔记_第33张图片

File类的获取功能的API:
public String getAbsolutePath():返回此File的绝对路径名称字符串
public String getPath()":创建文件对象的时候使用的路径
public String getName():返回由此File表示的文件或目录的名称
public long length():返回此File表示的文件的长度(字节为单位)
public boolean exists():此File表示的是文件或目录是否实际存在
public boolean isDirectory():此File表示的是否为目录
public boolean ISfiLE():此File表示的是否为文件

public boolean createNewFile():只有在该名称的文件不存在时,创建一个新的空文件。(几乎不用,因为以后文件都是自动创建的)
public boolean delete():删除此File表示的文件或目录(只能删除空目录)
public boolean mkdir():创建此File表示的目录。(只能创建一级目录)
public boolean mkdirs():可以创建多级目录(建议使用)
Java 基础笔记_第34张图片
Java 基础笔记_第35张图片
File针对目录的遍历:
public String[] list():获取当前目录下所有的”一级文件名称,返回一个字符数组
public File[] listFiles():(常用)获取当前目录下所有的“一级文件对象”,返回一个文件对象数组。
Java 基础笔记_第36张图片
Java 基础笔记_第37张图片

递归

递归分为:直接递归、间接递归。
递归的三要素:

  • 递归的终结点。
  • 递归的公式
  • 递归的方向:必须走向终结点
// 非规律化递归举例
// 自己写的
public class test {
    public static void main(String[] args) {
        // 买酒 10元 4盖=》1   2空=》1    喝多少瓶,剩多少盖子,几个空瓶?  15瓶,3盖,1空瓶
        int drink=0,gai=0,kong=0,money=10;
        while (true){
            if(money>1){
                int num=money/2;
                money=money%2;
                drink+=num;
                gai+=num;
                kong+=num;
            }

            if(gai>3){
                int gainum=gai/4;
                gai=gai%4; // 换取 gainum 瓶
                gai+=gainum;
                drink+=gainum;
                kong+=gainum;
            }
            if(kong>1){
                int kongnum=kong/2;
                kong=kong%2; // 换取 kongnum 瓶
                gai+=kongnum;
                drink+=kongnum;
                kong+=kongnum;
            }
            if(kong<=1 && gai<=3 && money<=1){
                break;
            }
        }
        System.out.println("喝了"+drink+"瓶,盖="+gai+",空瓶"+kong);
    }
}

// 老师写的
public class test {
    public static int totalNum;
    public static int lastPinZiNum;
    public static int lastGaiZiNum;
    public static void main(String[] args) {
        // 功能,一定要写成方法!
        buyBeer(10);
        System.out.println("喝了"+totalNum+"瓶,剩:"+lastPinZiNum+"个瓶子,剩"+lastGaiZiNum+"个盖子");
    }

    // 定义一个方法,帮助用户买酒
    private static void buyBeer(int money) {
        int number=money/2;
        totalNum+=number;

        // 算出当前剩余瓶子和盖子数量,换成金额,再买
        lastPinZiNum+=number;
        lastGaiZiNum+=number;

        int totalmoney=0;
        // 换算瓶子
        totalmoney+=(lastPinZiNum/2)*2;
        lastPinZiNum=lastPinZiNum%2;
        // 换算盖子
        totalmoney+=(lastGaiZiNum/4)*2;
        lastGaiZiNum=lastGaiZiNum%4;

        if(totalmoney>=2){
            buyBeer(totalmoney);
        }
    }
}

// 某个目录下递归查找某个文件
public class test {
    public static void main(String[] args) {
        searchFIle(new File("/Users/temp/Downloads"),"R2ApplicationTests.java");
    }
    private static void searchFIle(File dir, String fileName) {
        if(dir.exists() && dir.isDirectory()){
            File[] files = dir.listFiles();
            if(files!=null && files.length>0){
                for (File file : files) {
                    if(file.isFile()){
                        if(file.getName().contains(fileName))
                            System.out.println("找到"+file.getAbsolutePath());
                    }else{
                        searchFIle(file.getAbsoluteFile(),fileName);
                    }
                }
            }
        }
    }
}

字符集/编码集

字符集:各个国家为自己国家的字符取的一套编码规则。计算机的底层只能存储二进制。
英文和数字在任何编码集中都是占1字节。
GBK编码中,1个中文字符一般占2字节。
UTF-8编码中,1个中文字符一般占3字节。
我们都应该用UTF-8编码!
编码前与编码后的编码集必须一致才不会乱码!!

IO流

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

IO流的分类:

  • 输入流:读取数据到内存。以内存为基准,把磁盘文件中的数据或者网络中的数据读入到内存中去的流称为输入流。
  • 输出流:写数据到文件,或写数据发送给别人。以内存为基准,把内存中的数据写出到磁盘文件或者网络介质中去的流称为输出流。

流的内容分为:

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

所以流大体分为四大类:
字节输入流、字节输出流、字符输入流、字符输出流
Java 基础笔记_第38张图片

FileInputStream文件字节输入流

作用:按照字节读取文件数据到内存。

构造器

  • public FileInputStream(File path):创建一个字节输入流管道与源文件对象接通。
  • public FileInputStream(String pathName):创建一个字节输入流管道与文件路径对接。

方法

  • public int read():每次读取一个字节返回!读取完毕会返回-1。
  • public int read(byte[] buffer):从字节输入流中读取字节到字节数组中去,返回读取的字节数量,没有字节可读返回-1。

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

// 字节输入流读取一个个字节 举例
/**
 * 一个个字节读取数据,性能也较差,所以禁止使用此方案!
 * 一个个字节读取英文和数字没有问题,但读取中文会出现乱码,因为在utf-8里中文字符占3个字节,中文会被截断!
 */
public class test {
    public static void main(String[] args) throws Exception {
        // 创建一个文件对象
        File file=new File("/Users/temp/Desktop/a.txt");
        // 创建一个字节输入流管道与文件相通
        InputStream is=new FileInputStream(file);
        // 每次读一个字节, 当无字节读取时,返回-1
//        int code=is.read();
//        System.out.println((char)code);
        int ch=0;
        while ((ch=is.read())!=-1){
            System.out.println((char) ch);
        }
    }
}


// 字节输入流读取一个个字节数组 举例
/**
 * 使用字节数组读取内容,效率可以。
 * 但使用字节数组读取文本内容输出,也无法避免中文读取输出乱码的问题!
 */

public class test {
    public static void main(String[] args) throws Exception {
        // 创建一个字节输入流管道与文件相通
        InputStream is=new FileInputStream("/Users/temp/Desktop/a.txt");
        // 定义一个字节数组读取数据, 3为桶的大小,一次装3字节。一般用1024!
//        byte[] buffer=new byte[3];
//        int len = is.read(buffer);
//        System.out.println("读取了字节数:"+len);
//        String rs=new String(buffer); // 将读取到的桶数组,转为字符串输出
//        System.out.println(rs);

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

// 字节流读取中文输出不乱码的解决方式 举例
/**
 * 定义一个字节数组与文件的大小刚刚好一样大,然后一桶水全部读取全部字节数据再输出!
 */
public class test {
    public static void main(String[] args) throws Exception {
        File file=new File("/Users/temp/Desktop/a.txt");
        // 创建一个字节输入流管道与文件相通
        InputStream is=new FileInputStream(file);
//        byte[] buffer=new byte[(int)file.length()];
//        is.read(buffer);
//        String rs=new String(buffer);
//        System.out.println(rs);
        // 以上 等价于   
        byte[] buffer=is.readAllBytes(); // jdk 1.9开始才有!
    }
}

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):写一个字节数组的一部分出去。参数1:字节数组,参数2:起始字节索引位置,参数3:写多少个字节数出去。

注意:管道用完要关闭!数据要生效需要刷新,关闭包含刷新,刷新后流可以继续使用,关闭后流无法继续使用!
字节输出流管道默认是覆盖数据管道。

// 字节流输出的使用:
public class test {
    public static void main(String[] args) throws Exception {
//        File file=new File("/Users/temp/Desktop/a.txt");
//        // 创建一个字节输出流管道与目标对象接通。
//        OutputStream os=new FileOutputStream(file);
        // 创建一个字节输出流管道与目标文件路径接通,默认是数据覆盖管道!
//        OutputStream os=new FileOutputStream("/Users/temp/Desktop/a.txt");
        // 追加数据管道!
        OutputStream os=new FileOutputStream("/Users/temp/Desktop/a.txt",true);
        // 写一个字节出去
        os.write(98);
        os.write('a');
        os.write("\r\n".getBytes()); // 换行

        // 写一个字节数组出去
        byte[] bytes=new byte[]{98,99,100,111,103};
        os.write(bytes);

        byte[] bytes1="java是最优美的语言".getBytes(); // 默认以当前代码编码UTF-8提取字节数组。
//        byte[] bytes1="java是最优美的语言".getBytes("GBK"); // 指定编码获取字节数组。
        os.write(bytes1);
        os.write("\r\n".getBytes()); // 换行

        // 写一个字节数组的一部分出去
        byte[] bytes2="java是最优美的语言\r\n".getBytes();
        os.write(bytes2,0,19);

//        os.flush(); // 立即刷新数据到文件中去,刷新后管道可以继续使用
        os.close(); // 关闭资源管道,关闭包含了刷新,关闭后管道不能使用了
    }
}

字节流做文件的复制

  • 字节是计算机中一切文件的组成,所以字节流适合做一切文件的复制
    复制是把源文件的全部字节一字不漏的转移到目标文件,只要文件前后的格式一样,绝对不会有问题。
public class test {
    public static void main(String[] args) {
        InputStream is=null;
        OutputStream os=null;
        try {
            // 创建一个字节输入流管道
            is=new FileInputStream("/Users/temp/Desktop/a.txt");
            // 创建一个字节输出流管道
            os=new FileOutputStream("/Users/temp/Desktop/b.txt");
            // 创建一个字节数组为桶
            byte[] buffer=new byte[1024]; // 1KB
            int len=0;
            while ((len=is.read(buffer)) != -1){
                os.write(buffer,0,len); // 读取多少就倒多少
            }
            System.out.println("复制完毕!");

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if(is!=null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(os!=null){
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

JDK 1.7开始之后,释放资源的新方式
什么是资源:
资源类是一定实现了Closeable接口,实现这个接口的类就是资源
有close()方法,try-with-resources在执行完毕后会自动调用它的close()关闭资源。

JDK 1.7开始释放资源的新方式:try-with-resources。如此就不需要写finally代码块了~

public class test {
    public static void main(String[] args) {
        try (
                // 创建一个字节输入流管道
                InputStream is=new FileInputStream("/Users/temp/Desktop/a.txt");
                // 创建一个字节输出流管道
                OutputStream os=new FileOutputStream("/Users/temp/Desktop/b.txt");
                ){
            // 创建一个字节数组为桶
            byte[] buffer=new byte[1024];
            int len=0;
            while ((len=is.read(buffer)) != -1){
                os.write(buffer,0,len); // 读取多少就倒多少
            }
            System.out.println("复制完毕!");

        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

FileReader文件字符输入流

作用:读取文本文件内容到内存中去。

构造器

  • public FileReader(File file):创建一个字符输入流与源文件对象接通。
  • public FileReader(String filePath):创建一个字符输入流与源文件路径接通。

    方法
  • public int read():读取一个字符的编号返回!读取完毕返回-1
  • pubic int read(char[] buffer):读取一个字符数组,读取多个字符就返回多少个数量。
// 字符输入流读取一个个字符
/**
 * 字符流一个个字符读取文本内容输出,可以解决中文读取输出乱码问题。
 * 字符流很适合操作文本内容。但一个个字符读取文本内容性能较差!!
 */
public class test {
    public static void main(String[] args) throws Exception {
//        File file=new File("/Users/temp/Desktop/a.txt");
//        Reader fr=new FileReader(file);
        Reader fr=new FileReader("/Users/temp/Desktop/a.txt");
        // 按每次读取一个字符的编号返回
        int code = fr.read();
        System.out.println((char)code);
        int ch;
        while ((ch=fr.read())!=-1){
            System.out.print((char)ch);
        }
    }
}

// 字符输入流读取一个个字符数组
/**
 * 字符流按照字符数组循环读取数据,可以解决中文读取输出乱码的问题,而且性能也好!
 */
public class test {
    public static void main(String[] args) throws Exception {
        Reader fr=new FileReader("/Users/temp/Desktop/a.txt");
        // 按照字符数组读取内容
        char[] buffer=new char[3];
        fr.read(buffer);
        String rs=new String(buffer);
        System.out.println(rs);
        int len=0;
        while ((len=fr.read(buffer))!=-1){
            System.out.print(new String(buffer,0,len));
        }
    }
}

FileWriter 字符输出流

作用:把内存的数据以字符写出到文件中去。

构造器

  • public FileWriter(File file):创建一个字符输出流管道通向目标文件对象。
  • public FileWriter(String filePath):创建一个字符输出流管道通向目标文件路径。
  • public FileWriter(File file,boolean append):创建一个追加数据的字符输出流管道通向文件路径。
  • public FileWriter(String filePath,boolean append):创建一个追加数据的字符输出流管道通向文件路径。


方法

  • public void write(int c):写一个字符出去。
  • public void write(String c):写一个字符串出去。
  • public void writer(char[] buffer):写一个字符数组出去。
  • public void writer(String c,int pos,int len):写字符串的一部分出去。


读写字符文件数据建议使用字符流。

public class test {
    public static void main(String[] args) throws Exception {
        Writer fw=new FileWriter("/Users/temp/Desktop/a.txt");
//        Writer fw=new FileWriter("/Users/temp/Desktop/a.txt",true); // 追加数据管道
        // 写一个字符出去
        fw.write('a'); // 字符a
        fw.write(98); // 字符b
        fw.write('晓'); // 字符晓
        fw.write("\r\n"); // 换行

        // 写一个字符串出去
        fw.write("Java是最优美的语言!");
        fw.write("\r\n");

        // 写一个字符数组出去
        fw.write("我爱中国".toCharArray());
        fw.write("\r\n");

        // 写字符串的一部分出去
        fw.write("Java是最优美的语言!",0,6);
        fw.write("\r\n");

        // 写字符数组的一部分出去
        fw.write("Java是最优美的语言!".toCharArray(),0,6);
        fw.write("\r\n");

        fw.close();
    }
}

缓冲流

缓冲流可以提高字节流和字符流的读写数据的性能。

缓冲流分为四类

  • BufferedInputStream:字节缓冲输入流。可以提高字节输入流读数据的性能。
  • BufferedOutputStream:字节缓冲输出流。可以提高字节输出流写数据的性能。
  • BufferedReader:字符缓冲输入流。可以提高字符输入流读数据的性能。
  • BufferedWriter:字符缓冲输出流。可以提高字符输出流写数据的性能。

Java 基础笔记_第39张图片

字节缓冲输入流:BufferedInputStream

构造器

  • public BufferedInputStream(InputStream in)


原理:缓冲字节输入流管道自带了一个8KB的缓冲池。每次可以直接借用操作系统的功能,最多提取8KB的数据到缓冲池中去,以后我们直接从缓冲池读取数据,所以性能较好。

public class test {
    public static void main(String[] args) throws Exception {
        InputStream is=new FileInputStream("/Users/temp/Desktop/a.txt");
        // 把低级字节输入流 包装成 高级的缓冲字节输入流。
        BufferedInputStream bis=new BufferedInputStream(is);
        byte[] buffer=new byte[3];
        int len=0;
        while ((len=bis.read(buffer))!=-1){
            String rs=new String(buffer,0,len);
            System.out.print(rs);
        }
    }
}

字节缓冲输出流:BufferedOutputStream

构造器

  • public BufferedoutputStream(OutputStream os)


原理:缓冲字节输出流自带了8KB缓冲池。数据就直接写入到缓冲池中去,性能提高了

public class test {
    public static void main(String[] args) throws Exception {
        OutputStream os=new FileOutputStream("/Users/temp/Desktop/a.txt");
        // 把低级字节输出流 包装成 高级的缓冲字节输出流。
        BufferedOutputStream bos=new BufferedOutputStream(os);
        // 写数据出去
        bos.write('a');
        bos.write(100);
        bos.write("我爱中国".getBytes());
        bos.close();
    }
}

字节缓冲流性能分析
低级的字节流按照一个个字节的形式复制文件。
(速度太慢了,直接淘汰!)

低级的字节流按照一个个字节数组的形式复制文件(读取较慢)

高级的缓冲字节流按照一个个字节的形式复制文件。(读取较慢)

高级的缓冲字节流按照一个个字节数组的形式复制文件。(速度极快,建议使用!)

// 用字节缓冲流 复制文件 举例
public class test {
    public static void main(String[] args) throws Exception {
        try (
                InputStream is=new FileInputStream("/Users/temp/Desktop/a.txt");
                BufferedInputStream bis=new BufferedInputStream(is);
                OutputStream os=new FileOutputStream("/Users/temp/Desktop/b.txt");
                BufferedOutputStream bos=new BufferedOutputStream(os);
                ){
            byte[] buffer=new byte[1024];
            int len=0;
            while ((len=bis.read(buffer))!=-1){
                bos.write(buffer,0,len);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
// 复制文件夹 举例
public class test {
    public static void main(String[] args) {
        copyDir(new File("/Users/temp/Downloads/project"),new File("/Users/temp/Downloads/ppp"));
    }

    private static void copyDir(File srcDir, File descDir) {
        // 判断是否存在源路径,是否是文件夹
        if(srcDir.exists() && srcDir.isDirectory()){
            // 创建复制的目标文件夹
            descDir.mkdirs();
            // 提取源文件夹的一级文件对象
            File[] files = srcDir.listFiles();
            // 判断是否存在一级文件对象
            if(files != null && files.length>0){
                // 遍历一级文件对象
                for (File file : files) {
                    // 判断是否是文件
                    if(file.isFile()){
                        // 复制文件
                        copyFile(file,new File(descDir,file.getName()));
                    }else{
                        // 是文件夹
                        copyDir(file,new File(descDir,file.getName()));
                    }
                }
            }
        }
    }

    // 复制文件
    private static void copyFile(File srcFile, File descFile) {
        try (
                InputStream is=new FileInputStream(srcFile);
                OutputStream os=new FileOutputStream(descFile);
                BufferedInputStream bis=new BufferedInputStream(is);
                BufferedOutputStream bos=new BufferedOutputStream(os);
                ){
            byte[] buffer=new byte[1024];
            int len=0;
            while ((len=bis.read(buffer))!=-1){
                bos.write(buffer,0,len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

字节缓冲输入流:BufferedReader

构造器

  • public BufferedReander(Reader reader)


原理:缓冲字符输入流默认会有一个8KB的字符缓冲池。可以提高读字符的性能。

缓冲字符输入流除了提高了字符输入流的读数据性能,还多了个按照行读取数据的功能(重点):

  • public String readLine():读取一行数据返回,读取完毕返回null
public class test {
    public static void main(String[] args) throws Exception {
        // 定义一个原始字符输入流读取源文件
        Reader fr=new FileReader("/Users/temp/Desktop/a.txt");
        // 把低级字符输入流管道 包装成 高级缓冲字符输入流管道
        BufferedReader br=new BufferedReader(fr);
//        char[] buffer=new char[1024];
//        int len;
//        while ((len=br.read(buffer))!=-1){
//            System.out.print(new String(buffer,0,len));
//        }
        // 等价于
        String line; // 定义一个字符串变量存吃每行数据
        // 使用循环读取数据(经典代码)
        while ((line=br.readLine())!=null){
            System.out.println(line);
        }
    }
}

字符缓冲输出流:BufferedWriter

构造器

  • public BufferedWriter(Writer writer)


原理:高级的字符缓冲输出流多了一个8KB的字符缓冲池,写数据性能极大提高了!

字符缓冲输出流除了提高字符输出流写数据的性能,还多个一个换行的特有功能:

  • public void newLine()新建一行
public class test {
    public static void main(String[] args) throws Exception {
        // 定义一个低级字符输出流 写数据出去
        Writer fw=new FileWriter("/Users/temp/Desktop/a.txt");
        // 把低级的字符输出流 包装成 高级的缓冲字符输出流
        BufferedWriter bf=new BufferedWriter(fw);
        // 写字符输出
        bf.write("我在学IO流");
        bf.write("\r\n"); // 换行
        bf.newLine(); // 换行
        bf.write("我在学IO流~~~");
        bf.close();
    }
}

缓冲流练习题:将《出师表》文章排序,排序规则如下:
Java 基础笔记_第40张图片
简化后:
在这里插入图片描述

字符流不同编码读取乱码问题
如果代码编码和读取的文件编码不一致。字符流读取的时候会乱码。

InputStreamReader 字符输入转换流

作用
可以把原始的字节流按照当前默认编码转换成字符输入流。解决因字符流不同的乱码问题
可以把原始的字节流按照指定编码转换成字符输入流。

构造器

  • public InputStreamReader(InputStream is):可以使用当前代码默认编码转换成字符流(几乎不用)
  • public InputStreamReader(InputStream is,String charset):可以指定编码把字节流转换成字符流。
public class test {
    public static void main(String[] args) throws Exception {
        // 代码:UTF-8编码  文件:GBK编码
        // 提取GBK的原始字节流
        InputStream is=new FileInputStream("/Users/temp/Desktop/a.txt");

        // 把原始字节输入流 通过转换流,转换成 字符输入转换流 InputStreamReader
//        Reader isr=new InputStreamReader(is); // 使用当前代码默认编码UTF-8转成字符流,几乎不用
        Reader isr=new InputStreamReader(is,"GBK"); // 指定编码把字节流转换成字符流

        // 包装成缓冲流
        BufferedReader br=new BufferedReader(isr);
        String line;
        while ((line=br.readLine())!=null){
            System.out.println(line);
        }
    }
}

OutputStreamWriter 字符输出转换流

作用:可以指定编码把字节输出流转换成字符输出流。从而实现指定写出去的字符编码!
可以指定写出去的字符编码

构造器

  • public OutputStreamWriter(OutputStream os):用当前默认编码UTF-8把字节输出流转为字符输出流
  • public OutputStreamWriter(OutputStream os,String charaset):指定编码把字节输出流转为字符输出流。
public class test {
    public static void main(String[] args) throws Exception {
        // 代码:UTF-8编码  文件:GBK编码
        OutputStream os=new FileOutputStream("/Users/temp/Desktop/a.txt");
        // 把字节输出流转换为字符输出流
//        Writer osw=new OutputStreamWriter(os); // 把字节输出流按照默认编码UTF-8转换成字符输出流
        Writer osw=new OutputStreamWriter(os,"GBK");  // 把字节输出流按照指定编码GBK转换成字符输出流
        osw.write("abc我是中国人!");
        osw.close();
    }
}

对象序列化、对象反序列化

对象序列化:把Java对象数据直接存储到文件中去。
( 对象 => 文件中 )
对象反序列化:把Java对象的文件数据恢复到Java对象中。
( 文件中 => 对象 )
Java 基础笔记_第41张图片


对象序列化流(对象字节输出流):ObjectOutputStream
作用:把内存中的Java对象数据保存到文件中去。

构造器

  • public ObjectOutputStream(OutputStream out)


序列化方法

  • public final void writeObject(Object obj)


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

// 对象序列化 举例
public class test {
    public static void main(String[] args) throws Exception {
        // 创建用户对象
        User user=new User("tsgz","123456","铁扇公主");
        // 创建低级字节输出流通向目标文件
        OutputStream os=new FileOutputStream("/Users/temp/Desktop/a.txt");
        // 把低级字节输出流 包装成 高级的对象字节输出流 ObjectOutputStream
        ObjectOutputStream oos=new ObjectOutputStream(os);
        // 通过对象字节输出流序列化对象
        oos.writeObject(user);
        // 是释放资源
        oos.close();
        System.out.println("序列化对象成功");
    }
}

public class User implements Serializable {
    private String loginName;
    private 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 String getLoginName() {
        return loginName;
    }

    public void setLoginName(String loginName) {
        this.loginName = loginName;
    }

    public String getPassWord() {
        return passWord;
    }

    public void setPassWord(String passWord) {
        this.passWord = passWord;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
    @Override
    public String toString() {
        return "User{" +
                "loginName='" + loginName + '\'' +
                ", passWord='" + passWord + '\'' +
                ", userName='" + userName + '\'' +
                '}';
    }
}

对象反序列化(对象字节输入流):ObjectInputStream
作用:读取序列化的对象文件恢复到Java对象中。

构造器

  • public ObjectInputStream(InputStream is)


方法

  • public final Object readObject()


设一个字段不参与序列化:用 transient修饰该成员变量。

序列化版本号

  • public static final long serialVersionUID = 1L;


注意:必须让序列化使用的版本号和反序列化使用的版本号一直才可以正常反序列化!否则报错!

public class test {
    public static void main(String[] args) throws Exception {
        // 创建低级字节输入流通向目标文件
        InputStream is=new FileInputStream("/Users/temp/Desktop/a.txt");
        // 把低级字节输入流 包装成 高级的对象字节输入流 ObjectInputStream
        ObjectInputStream ois=new ObjectInputStream(is);
        // 反序列化
        User user=(User) ois.readObject();
        ois.close();
        System.out.println(user);
        System.out.println("反序列化完成");
    }
}


public class User implements Serializable {
    // 加入序列版本号
    public 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 String getLoginName() {
        return loginName;
    }

    public void setLoginName(String loginName) {
        this.loginName = loginName;
    }

    public String getPassWord() {
        return passWord;
    }

    public void setPassWord(String passWord) {
        this.passWord = passWord;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
    @Override
    public String toString() {
        return "User{" +
                "loginName='" + loginName + '\'' +
                ", passWord='" + passWord + '\'' +
                ", userName='" + userName + '\'' +
                '}';
    }
}

打印流(PrintStream、PrintWriter)

作用:方便、高效的打印各种数据出去。

构造器

  • public PrintStream(OutputStream os)
  • public PrintStream(String filepath)


PrintStream不光可打印数据,还可写字节数据出去。
PrintWriter不光可打印数据,还可以写字符数据出去。

public class test {
    public static void main(String[] args) throws Exception {
//        OutputStream os=new FileOutputStream("/Users/temp/Desktop/a.txt");
//        PrintStream ps=new PrintStream(os);
        PrintStream ps=new PrintStream("/Users/temp/Desktop/a.txt");
        PrintWriter pw=new PrintWriter("/Users/temp/Desktop/a.txt");
        
        ps.println(97);
        ps.println(110);
        ps.println("我在学习");
        ps.println(false);
        ps.println('版');

        // 写字节数组出去
        ps.write("阿第三方".getBytes());
        ps.close();
    }
}

打印流改变树池的流向。重定向。
System 下的:

  • public static void setOut(PrintStream out):让系统的输出流向打印流。
public class test {
    public static void main(String[] args) throws Exception {
        PrintStream ps = new PrintStream("/Users/temp/Desktop/a.txt");
        System.setOut(ps);  // 让系统的输出流向打印流

        System.out.println("======== 1 =======");
        System.out.println("======== 2 =======");
        System.out.println("======== 3 =======");
        System.out.println("======== 4 =======");

        ps.close();
    }
}

Properties 属性集对象

其实就是一个Map集合。但一般不当集合使用,因为有HashMap。
核心作用

  • Properties代表的是一个属性文件,可以把键值对的数据存入到一个属性文件中去。
  • 属性文件:.properties的文件,里面都是 key=vlaue


方法

  • public Object setProperty(String key,String value):保存一对属性。
  • publc String getProperty(String key):使用此属性列表中指定的键搜索值。
  • public Set stringPropertyNames():所有键的名称的集合。
  • public void store(OutputStream out,String comments)保存数据到属性文件中去。
  • public void store(Writer fw,String comments)保存数据到属性文件中去。
  • public synchronized void load(InputStrem inStream):加载属性文件的数据到属性及对象中去。
  • public synchronized void load(Reader fr):加载属性文件的数据到属性集对象中去。
// Properties 保存数据到属性文件中 举例
public class test {
    public static void main(String[] args) throws Exception {
        // 创建一个属性集对象:Properties的对象
        Properties properties=new Properties();
        properties.setProperty("admin","123456");
        properties.setProperty("aaa","123456");

        // 把属性集对象的数据存入到属性文件中去
        OutputStream os=new FileOutputStream("/Users/temp/Desktop/a.txt");
        /**
         * 参数1:被保存数据的输出管道
         * 参数2:保存心得
         */
        properties.store(os,"这是我保存的感受");
        os.close();
    }
}

// Properties 读取 属性文件数据 举例
public class test {
    public static void main(String[] args) throws Exception {
        // 创建一个属性集对象
        Properties properties=new Properties();
        System.out.println(properties);
        properties.load(new FileInputStream("/Users/temp/Desktop/a.txt"));
        System.out.println(properties);
        System.out.println(properties.get("admin"));
        System.out.println(properties.get("aaa"));
    }
}

基本软件架构

  • C/S 架构(Client/Server):客户端和服务器架构。常见程序:QQ、迅雷、IDEA等软件。
  • B/S架构(Browser/Server):浏览器和服务器架构。常见浏览器:谷歌、火狐等。软件:京东、淘宝等。


两种架构各有优势,但是无论哪种架构,都离不开网络的支持。网络编程,就是在一定的协议下,实现两台计算机的通信的技术。

网络通信的三要素

  • 协议:计算机网络客户端与服务端通信必须事先约定和彼此遵守的通信规则。HTTP、FTP、TCP、UDP、SSH、SMTP。

  • IP地址:指互联网协议地址(Internet Protocol Address),简称IP。IP地址用来给一个网络中的计算机设备做唯一的编号。

    IPv4:4个字节,32位组成。192.168.70.70
    局域网、城域网、广域网(公网)

    IPv6:可以实现为所有设备分配IP,128位。

    ipconfig:查看本机的IP
    ping ip地址:检查本机与某个ip指定的机器是否联通,或者说检测对方是否在线。
    注意:特殊的IP地址:本机IP地址。127.0.0.1 == localhost

  • 端口
    端口号可以唯一标识设备中的进程。
    端口号:
    用两个字节表示的整数,它的取值范围是0~65535。
    0~1023之间的端口号用于一些知名的网络服务和应用。
    普通的应用程序需要使用1024以上的端口号。
    如果端口号被另一个服务或应用所占用,会导致当前程序启动失败。报端口被占用异常!

    利用协议+ip地址+端口号组合,就可以标识网络中的进程了。

Java 基础笔记_第42张图片
Java 基础笔记_第43张图片

InetAddress 类

InetAddress类的对象,代表一个IP地址对象。

成员方法

  • static InetAddress getLocalHost():获得本地主机IP地址对象。
  • static InetAddress getByName(String host):根据IP地址字符串或主机名获得对应的IP地址对象。
  • String getHostName():获得主机名。
  • String getHostAddress():获得IP地址字符串。
public class test {
    public static void main(String[] args) throws IOException {
        // 获取本机地址对象
        InetAddress ip=InetAddress.getLocalHost();
        System.out.println(ip.getHostName());
        System.out.println(ip.getHostAddress());

        // 获取域名ip对象
        InetAddress ip2=InetAddress.getByName("www.baidu.com");
        System.out.println(ip2.getHostName());
        System.out.println(ip2.getHostAddress());

        // 获取公网ip对象
        InetAddress ip3=InetAddress.getByName("182.61.200.6");
        System.out.println(ip3.getHostName());
        System.out.println(ip3.getHostAddress());

        // 判断是否能ping通
        System.out.println(ip2.isReachable(5000));  // 通返回true
    }
}

UDP通信(了解)

特点
面向无连接的协议
发送端只管发送,不确认对方是否能收到。
基于数据包进行数据传输。
发送数据的包的大小限制64kb以内。
因为面向无连接,速度快,但是不可靠,会丢数据!

UDP的使用场景:在线视频、网络语音电话。

UDP相关的类

  • DatagramPacket数据包对象。用来封装要发送或接收的数据。比如:集装箱。
  • DatagramSocket发送对象。用来发送或接收数据包。比如:码头


DatagramPacket类构造器

  • 发送端DatagramPacket(byte[] buf,int length,InetAddress address,int port):创建发送端数据包对象。
    参数1:要发送的内容。
    参数2:发送内容的长度,单位字节。
    参数3:接受端的IP地址对象。
    参数4:接收端的端口号

  • 接收端DatagramPacket(byte[] buf,int length):创建接收端的数据包对象。
    参数1:要发送的内容。
    参数2:发送内容的长度,单位字节。


DatagramPacket类常用方法

  • int getLength():获得实际接收到的字节个数。


DatagramSocket类构造方法

  • DatagramSocket():创建发送端的Socket对象,系统会随机分配一个端口号。
  • DatagramSocket(int port):创建接收端的Socket对象并指定端口号。


DatagramSocket类成员方法

  • void send(DatagramPacket dp):发送数据包。
  • void receive(DatagramPacket p):接收数据包
public class test {
    // 服务端
    public static void main(String[] args) throws Exception {
        System.out.println("============== 启动服务端 ===============");
        // 创建一个接收 客户端的数据包对象(集装箱)
        byte[] buf = new byte[1024*64];
        DatagramPacket dp=new DatagramPacket(buf,buf.length);
        // 创建一个接受的码头对象
        DatagramSocket socket=new DatagramSocket(6666);
        // 接收数据包
        socket.receive(dp);
        // 从集装箱中获取本次读取的数据量
        int len=dp.getLength();
        // 服务端还可获取发来信息的客户端的ip和端口
        System.out.println(dp.getAddress().getHostAddress());
        System.out.println(dp.getPort()); // 8888

        String rs=new String(buf,0,len);
        System.out.println(rs);
        socket.close();
    }
}
// 客户端
class client{
    public static void main(String[] args) throws Exception {
        System.out.println("============== 客户端 ===============");
        // 创建一个集装箱对象,用于封装需要发送的数据包
        byte[] buf = "我是要发送的数据".getBytes();
        DatagramPacket dp=new DatagramPacket(buf,buf.length, InetAddress.getLocalHost(),6666);

        // 创建一个码头对象。参数可以声明客户端端口,可以有可以没有,默认会给一个端口
        DatagramSocket socket=new DatagramSocket(8888);

        // 开始发送数据包对象
        socket.send(dp);
        socket.close();
    }
}

Socket 网络通信(TCP)

Socket类构造方法

  • Socket(String host,int port)
    根据ip地址字符串和端口号创建客户端Socket对象。
    注意:要执行该方法,就会立即连接指定的服务器程序,如果连接不成功,则会抛出异常。
    如果连接成功,则表明三次握手通过

Socket类常用方法

  • OutputStream getOutputStream():获得字符输出对象。
  • InputStream getInputStream():获得字节输入流对象


ServerSocket类构造器

  • public ServerSocket(int port)

ServerSocket类方法

  • public Socket accept():等待接收一个客户端的Socket管道连接请求,连接成功返回一个Socket对象。


注意:通信是很严格的,对方怎么发,就怎么收,对方发多少就收多少。
实现的面向连接的socket端到端的通信管道,一方如果出现问题,另一方会出现异常!

// 客户端发送一行数据,服务端收一行数据
public class test {
    // 服务端
    public static void main(String[] args) throws Exception {
//        注册端口
        ServerSocket ss=new ServerSocket(8888);
//        接收客户端的Socket管道连接
        Socket socket=ss.accept();
//        从socket通信管道中得到一个字节输入流
        InputStream is=socket.getInputStream();
//        把字节输入流转换成字符输入流
        Reader isr=new InputStreamReader(is);
//        把字符输入流包装成缓冲字符输入流
        BufferedReader br=new BufferedReader(isr);
//        按照行读取消息
        String msg;
        while ((msg=br.readLine())!=null){
            System.out.println("收到:"+msg);
        }
    }
}
// 客户端
class client{
    public static void main(String[] args) throws Exception {
//        客户端要求请求服务端的socket管道连接
        Socket socket=new Socket("172.xxx.xxx.xxx",8888);
//        从socket通信管道中得到一个字节输出流
        OutputStream os=socket.getOutputStream();
//        把低级的字节输出流包装成高级打印流
        PrintStream ps=new PrintStream(os);
//        开始发消息出去
        ps.println("我是客户端!");
        ps.flush();
        System.out.println("客户端发送消息完毕!");
    }
}
// 客户端反复发送数据,服务反复端收数据:目前服务端只能接收一个客户端的请求
public class test {
    // 服务端
    public static void main(String[] args) throws Exception {
        System.out.println("============= 服务端启动 ================");
//        注册端口
        ServerSocket ss=new ServerSocket(8888);
//        接收客户端的Socket管道连接
        Socket socket=ss.accept();
//        从socket通信管道中得到一个字节输入流
        InputStream is=socket.getInputStream();
//        把字节输入流转换成字符输入流
        Reader isr=new InputStreamReader(is);
//        把字符输入流包装成缓冲字符输入流
        BufferedReader br=new BufferedReader(isr);
//        按照行读取消息
        String msg;
        while ((msg=br.readLine())!=null){
            System.out.println("收到:"+msg);
        }
    }
}
// 客户端
class client{
    public static void main(String[] args) throws Exception {
        System.out.println("============= 服务端启动 ================");
//        客户端要求请求服务端的socket管道连接
        Socket socket=new Socket("172.xxx.xxx.xxx",8888);
//        从socket通信管道中得到一个字节输出流
        OutputStream os=socket.getOutputStream();
//        把低级的字节输出流包装成高级打印流
        PrintStream ps=new PrintStream(os);
//        开始发消息出去
        while (true){
            Scanner sc=new Scanner(System.in);
            System.out.println("请说:");
            ps.println(sc.nextLine());
            ps.flush();
        }
    }
}
// 一个服务端同时接收多个客户端消息
/**
 * 需要引入多线程
 * 每接收一个客户端的Socket通道,就为它分配一个独立线程来处理它的消息
 */
public class test {
    // 服务端
    public static void main(String[] args) throws Exception {
        System.out.println("============= 服务端启动 ================");
//        注册端口
        ServerSocket ss=new ServerSocket(8888);
        // 定义一个循环不断接收客户端的链接请求
        while (true){
            // 接收客户端的Socket管道连接
            Socket socket=ss.accept();
            // 每接收到一个客户端必须为这个客户端管道分配一个独立的线程来处理与之通信
            new ServerReaderThread(socket).start();
        }
    }
}
class ServerReaderThread extends Thread{
    private Socket socket;

    public ServerReaderThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            //从socket通信管道中得到一个字节输入流
            InputStream is=socket.getInputStream();
            // 把字节输入流转换成字符输入流
            Reader isr=new InputStreamReader(is);
            //  把字符输入流包装成缓冲字符输入流
            BufferedReader br=new BufferedReader(isr);
            // 按照行读取消息
            String msg;
            while ((msg=br.readLine())!=null){
                System.out.println(socket.getRemoteSocketAddress()+"说:"+msg);
            }

        }catch (Exception e){
            System.out.println(socket.getRemoteSocketAddress()+"下线了!");
        }
    }
}

// 客户端
class client{
    public static void main(String[] args) throws Exception {
        System.out.println("============= 服务端启动 ================");
//        客户端要求请求服务端的socket管道连接
        Socket socket=new Socket("172.xxx.xxx.xxx",8888);
//        从socket通信管道中得到一个字节输出流
        OutputStream os=socket.getOutputStream();
//        把低级的字节输出流包装成高级打印流
        PrintStream ps=new PrintStream(os);
//        开始发消息出去
        while (true){
            Scanner sc=new Scanner(System.in);
            System.out.println("请说:");
            ps.println(sc.nextLine());
            ps.flush();
        }
    }
}

/**
 * 之前的方式,会创建很多个线程,引起系统死机。不可行。
 * 解决:
 * 在服务端引入线程池,使用线程池来处理与客户端的消息通信!
 * 线程池限制线程数量
 */
// 一个服务端同时接收多个客户端消息
public class test {
    // 服务端
    public static void main(String[] args) throws Exception {
        System.out.println("============= 服务端启动 ================");
//        注册端口
        ServerSocket ss=new ServerSocket(8888);

        // 一个服务端只需要对应一个线程池
        HandleSocketThreadPool handleSocketThreadPool=new HandleSocketThreadPool(3,100);

        // 定义一个循环不断接收客户端的链接请求
        while (true){
            // 接收客户端的Socket管道连接
            Socket socket=ss.accept();
            System.out.println("有人上线了!");
            // 每接收到一个客户端必须为这个客户端管道分配一个独立的线程来处理与之通信
            handleSocketThreadPool.execute(new ReaderClientRunnable(socket));
        }
    }
}

class ReaderClientRunnable implements Runnable{
    private Socket socket;

    public ReaderClientRunnable(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            //从socket通信管道中得到一个字节输入流
            InputStream is=socket.getInputStream();
            // 把字节输入流转换成字符输入流
            Reader isr=new InputStreamReader(is);
            //  把字符输入流包装成缓冲字符输入流
            BufferedReader br=new BufferedReader(isr);
            // 按照行读取消息
            String msg;
            while ((msg=br.readLine())!=null){
                System.out.println(socket.getRemoteSocketAddress()+"说:"+msg);
            }

        }catch (Exception e){
            System.out.println(socket.getRemoteSocketAddress()+"下线了!");
        }
    }
}

// 线程池处理类
class HandleSocketThreadPool{
    // 线程池
    private ExecutorService executor;
    public HandleSocketThreadPool(int maxPoolSize,int queueSize){
        this.executor=new ThreadPoolExecutor(
                maxPoolSize, // 保存数量
                maxPoolSize, // 最大数量
                120, // 存活时间
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(queueSize) // 阻塞队列
        );
    }
    public void execute(Runnable task){
        this.executor.execute(task);
    }
}

// 客户端
class client{
    public static void main(String[] args) throws Exception {
        System.out.println("============= 服务端启动 ================");
//        客户端要求请求服务端的socket管道连接
        Socket socket=new Socket("172.xxx.xxx.xxx",8888);
//        从socket通信管道中得到一个字节输出流
        OutputStream os=socket.getOutputStream();
//        把低级的字节输出流包装成高级打印流
        PrintStream ps=new PrintStream(os);
//        开始发消息出去
        while (true){
            Scanner sc=new Scanner(System.in);
            System.out.println("请说:");
            ps.println(sc.nextLine());
            ps.flush();
        }
    }
}
/**
 * 图片上传:客户端上传图片至服务器
 */

// 服务端
public class test {
    public static final String DEST_FILE="/Users/temp/Desktop/";
    public static void main(String[] args) throws Exception {
        System.out.println("========= 服务端启动 ===========");
        // 注册端口
        ServerSocket ss=new ServerSocket(8888);
        while (true){
            Socket socket=ss.accept();
            System.out.println(socket.getRemoteSocketAddress()+"传来的图片;");
            // 为当前客户端管道分配线程处理通信
            new ServerReaderThread(socket).start();
        }
    }
}
class ServerReaderThread extends Thread{
    private Socket socket;

    public ServerReaderThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            // 接收当前客户端发来的图片资源
            InputStream is=socket.getInputStream();
            // 包装成高级的缓冲字节输入流
            BufferedInputStream bis=new BufferedInputStream(is);
            // 从bis管道中读取客户端发来的图片字节,写出到服务器仓库路径中去!
            BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream(test.DEST_FILE+ UUID.randomUUID().toString()+".png"));
            byte[] buffer=new byte[1024];
            int len;
            while ((len=bis.read(buffer))!=-1){
                bos.write(buffer,0,len);
            }
            bos.flush();
            bos.close();

            // 服务端响应接收成功的结果
            // 得到一个字节输出流,直接包装成打印流
            PrintStream ps=new PrintStream(socket.getOutputStream());
            ps.println("收到图片资源,感谢!");
            ps.flush();

            Thread.sleep(1000);

        } catch (Exception e) {
            System.out.println(socket.getRemoteSocketAddress()+"下线了");
        }
    }
}

// 客户端
class client{
    public static final String SRC_IMAGE="/Users/temp/Desktop/1.png";
    public static void main(String[] args) throws Exception {
        // 请求与服务端的Socket通信管道
        Socket socket=new Socket("172.xxx.xxx.xxx",8888);
        // 从Socket中得到一个字节输出流管道
        OutputStream os=socket.getOutputStream();
        // 把字节输出流包装成高级的缓冲字节输出流
        BufferedOutputStream bos=new BufferedOutputStream(os);
        // 读取本地上传的文件写出到bos管道:复制
        BufferedInputStream bis=new BufferedInputStream(new FileInputStream(SRC_IMAGE));
        // 定义一个缓冲字节输入流管道
        byte[] buffer=new byte[1024];
        int len;
        while ((len=bis.read(buffer))!=-1){
            bos.write(buffer,0,len);
        }
        bos.flush();
        bis.close();
        socket.shutdownOutput(); // 通知服务端 数据已经传输完毕!请不要再等我了
        System.out.println("传输图片数据成功!");

        // 接收服务端发回来的相应消息
        BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream()));
        System.out.println(br.readLine());
    }
}

BS 架构模拟

BS:浏览器-服务器

/**
 * 在浏览器中请求本程序,响应一个网页文字给浏览器显示
 */
public class test {
    public static void main(String[] args) throws Exception {
        // 注册端口
        ServerSocket ss=new ServerSocket(8080);
        // 创建一个循环接收客户端的请求
        while (true){
            Socket socket=ss.accept();
            // 交给一个独立线程来处理
            new ServerReaderThread(socket).start();
        }

    }
}
class ServerReaderThread extends Thread{
    Socket socket;

    public ServerReaderThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            // 响应消息数据给浏览器显示
            // 浏览器是基于 HTTP协议 通信的!
            // 响应格式必须满足HTTP数据格式的要求,浏览器才能够识别,否则响应消息浏览器不认识
            PrintStream ps=new PrintStream(socket.getOutputStream());
            ps.println("HTTP/1.1 200 OK");  // 响应 数据的响应头
            ps.println("Content-Type:text/html;charset=UTF-8"); // 响应 数据的类型。网页或者文本内容
            ps.println(); // 必须换行!因为格式要求就是这样
            // 以下开始响应真实的数据
            ps.println("我是真实要传输的数据");
            Thread.sleep(3000);
            ps.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

基本通信模型

基本通信模型概念介绍:

  • BIO:同步阻塞式通信。线程与客户端耦合,没有数据还要一直等。
  • 伪异步通信:引入了线程池。无需一个客户端一个线程,可实现1个线程复用来处理多个客户端!可避免系统死机,但高并发下性能很差。线程数量少,数据依然是阻塞式的,数据没有来线程还是要等待!
  • NIO:同步非阻塞IO。服务器实现模式为请求对应一个线程。
    即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接与I/O请求是才启动一个线程进行处理。
    相当于 1个人专门负责接收客户端;一个人负责轮询【c1,c2,c3,c4…】,发来了数据才会开启线程处理。
  • AIO:异步非阻塞IO。服务器实现模式为一个有效请求一个线程。
    客户端的I/O请求都是由操作系统先完成IO操作后再通知服务器应用来启动线程处理。


各种模型应用场景
BIO适用于连接数目比较小且固定的价格,该方式对服务器资源要求比较高,JDK 1.4以前的唯一选择。
NIO适用于连接数目多且连接比较短(轻操作)的架构,如聊天服务器,编程复杂。JDK 1.4开始支持。
AIO适用于连接数目多且连接比较长(重操作)的架构,如相册服务器。充分调用操作系统参与并发操作,编程复杂。JDK 1.7开始支持。

单元测试框架

什么是框架?
框架是前人或者一些牛逼的技术公司在实战或者研发中设计的一些优良的设计方案或者成型的 代码功能,作为一个完整的技术体系发行出来称为框架。
框架可以让程序员快速拥有一个强大的解决方案,可以快速的开发功能,提高效率,并且直接就有了很好的性能。


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

点击进入mvnrepository仓库下载jar包
使用junit,要导入以下两个jar包:
在这里插入图片描述
junit注解介绍
Java 基础笔记_第44张图片

反射

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

反射是指在对于任何一个类,在“运行的时候”都可以直接得到这个类的全部成分。

  • 在运行时,可以得到这个类的构造器对象。(Constructor)
  • 在运行时,可以得到这个类的成员变量对象。(Field)
  • 在运行时,可以得到这个类的成员方法对象。(Method)


反射的核心思想和关键就是得到:编译以后的class文件对象。
注意:反射是工作在运行时的技术,因为只有运行之后才会有class类对象。

获取Class类对象

获取类对象的方式

  • 类名.class
  • 通过类的对象.getClass()方法
  • Class.forName("类的全限名")
    public static Class forName(String className)


Class类下的方法

  • String getSimpleName():获取类名字符串:类名
  • String getName():获取类全名:包名+类名
  • T newInstance():创建Class对象关联类的对象,其实底层也是调用无参构造器,此方法已经被淘汰。
public class test {
    public static void main(String[] args) throws Exception {
        // 类名.class
        Class<?> c1 = Student.class;
        System.out.println(c1);

        // 对象.getClass
        Student student=new Student();
        Class<?> c2 = student.getClass();
        System.out.println(c2);

        // Class.forName("类的全限名")
        Class<?> c3=Class.forName("com.luhang.Test.Student");
        System.out.println(c3);

        System.out.println(c3.getSimpleName()); // 获取类名本身(简名)
        System.out.println(c3.getName()); // 类的全限名

        // Student s=(Student) c1.newInstance(); // 调用无参构造器得到对象,此方法已经淘汰
    }
}
class Student{

}

获取Class类的构造器对象

反射中,Class类型获取构造器的API:

  • Constructor getConstructor(Class... parameterTypes):根据参数匹配获取某个构造器,只能拿public修饰的构造器,几乎不用!
  • Constructor getDeclaredConstructor(Class... parameterTypes):根据参数匹配获取某个构造器,只要申明就可以定位,不关心权限修饰符,建议使用!
  • Constructor[] getConstructors():获取所有的构造器,只能拿public修饰的构造器,几乎不用!
  • Constructor[] getDeclaredConstructors():获取所有申明的构造器,只要你写我就能拿到,无所谓权限,建议使用!
public class test {
    // 获取全部构造器,只能获取public修饰的构造器
    @Test
    public void getConstructors(){
        // 得到class类对象
        Class c=Student.class;
        Constructor[] cons = c.getConstructors();
        for (Constructor con : cons) {
            System.out.println(con.getName()+"===>"+con.getParameterCount());
        }
    }

    // getDeclaredConstructors:获取全部构造器
    @Test
    public void getDeclaredConstructors(){
        Class c=Student.class;
        Constructor[] cons = c.getDeclaredConstructors();
        for (Constructor con : cons) {
            System.out.println(con.getName()+"===>"+con.getParameterCount());
        }
    }

    // getConstructor:获取某个构造器,只能获取public修饰的构造器
    @Test
    public void getConstructor() throws Exception {
        Class c=Student.class;
        // Constructor con = c.getConstructor(); // 报错!因为无参构造是私有的
        Constructor con = c.getConstructor(String.class, int.class);
        System.out.println(con.getName()+"===>"+con.getParameterCount());
    }

    // 获取某个构造器
    @Test
    public void getDeclaredConstructor() throws Exception {
        Class c=Student.class;
        Constructor con = c.getDeclaredConstructor();
        System.out.println(con.getName()+"===>"+con.getParameterCount());
    }
}
class Student{
    private String name;
    private int age;

    private Student() {
        System.out.println("无参构造器被执行");
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("有参构造器被执行");
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

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

Constructor的API:

  • T newInstance(Object... initargs):创建对象,注入构造器需要的数据。
  • void setAccessible(true):修改访问权限,true代表暴力攻破权限,false表示保留不可访问权限(暴力反射)
public class test {
    // 调用无参构造器得到一个类的返回
    @Test
    public void creatObj01() throws Exception {
        Class c=Student.class;
        // 定位无参构造器对象
        Constructor con = c.getDeclaredConstructor();
        // 暴力打开私有构造器的访问权限!
        con.setAccessible(true);
        // 通过无参构造器初始化对象返回
        Student swk = (Student) con.newInstance();
        System.out.println(swk);
    }
    @Test
    // 调用有参构造器得到一个类的返回
    public void creatObje02() throws Exception {
        Class c = Student.class;
        // 定位有参构造器定位对象
        Constructor con = c.getDeclaredConstructor(String.class,int.class);
        // 通过有参构造器初始化对象返回
        Student swk = (Student) con.newInstance("孙悟空",10000);
        System.out.println(swk);
    }
}
class Student{
    private String name;
    private int age;

    private Student() {
        System.out.println("无参构造器被执行");
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("有参构造器被执行");
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

获取Class类的成员变量

获取成员变量API

  • Field getField(String name):根据成员变量名获得对应Field对象,只能获取public修饰的
  • Field getDeclaredField(String name):根据成员变量名获得对应Field对象,只要申明了就可以得到。
  • Field[] getFields():获得所有成员变量对应的Field对象,只能获得public的。
  • Filed[] getDeclaredFields():获得所有的成员变量对应的Field对象,只要申明了就可以得到。
public class test {
    @Test
    public void getDeclaredFields() throws Exception {
        Class c=Student.class;
        // 获取所有的成员变量
        Field[] fields = c.getDeclaredFields();
        for (Field field : fields) {
            System.out.println(field.getName()+"===>"+field.getType());
        }
    }

    @Test
    public void getDeclaredField() throws Exception {
        Class c=Student.class;
        // 获取指定的成员变量
        Field field = c.getDeclaredField("age");
        System.out.println(field.getName()+"===>"+field.getType());
    }
}
class Student{
    private String name;
    private int age;
    private String color;
    public static String school;
    public static final String SCHOOL_1 = "学校";

    private Student() {
    }

    public Student(String name, int age, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", color='" + color + '\'' +
                '}';
    }
}

成员变量的赋值和取值

  • void set(Object obj,Object value):给对象注入某个成员变量数据。
  • Object get(Object obj):获取对象的成员变量的值。(取出来什么类型就转什么类型)
  • void setAccessible(true):暴力反射,设置为可以直接访问私有类型的属性。
  • Class getType():获取属性的类型,返回Class对象。
  • String getName():获取属性的名称。
public class test {
    @Test
    public void getDeclaredFields() throws Exception {
        Class c=Student.class;
        // 定位name成员变量
        Field name = c.getDeclaredField("name");
        // 为这个成员变量赋值
        Student student=new Student();
        name.setAccessible(true); // 暴力反射
        name.set(student,"张三");  // 参数1:被赋值的对象, 参数2:该成员变量的值
        System.out.println(student);

        // 获取成员变量的值
        String value = name.get(student)+"";
        System.out.println(value);
    }

    @Test
    public void getDeclaredField() throws Exception {
        Class c=Student.class;
        // 获取指定的成员变量
        Field field = c.getDeclaredField("age");
        System.out.println(field.getName()+"===>"+field.getType());
    }
}
class Student{
    private String name;
    private int age;
    private String color;
    public static String school;
    public static final String SCHOOL_1 = "学校";

    public Student() {
    }

    public Student(String name, int age, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", color='" + color + '\'' +
                '}';
    }
}

获取Class类的方法

反射获取类的Method方法独享:

  • Methon getMethod(String name,Class... args):根据方法名和参数类型获得对应的方法对象,只能获得public的
  • Method getDeclareMethod(String name,Class... args):根据方法名和参数类型获得对应的方法对象,包括private的。
  • Method[] getMethods():获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的。
  • Method[] getDeclaredMethods():获得类中的所有成员方法对象,返回数组,只获得本类申明的方法。
public class test {
    // 获取全部方法
    @Test
    public void getDeclaredMethods() throws Exception {
        Class c=Student.class;
        Method[] methods = c.getDeclaredMethods();
        for (Method method : methods) {
            System.out.println(method.getName()+"===>"+method.getParameterCount()+"===>"+method.getReturnType());
        }
    }
    // 获取某个方法对象
    @Test
    public void getDeclaredMethod() throws Exception {
        Class c=Student.class;
        // 获取run无参方法
        Method run = c.getDeclaredMethod("run");
        // 触发方法执行
        Student s=new Student();
        run.invoke(s);

        /**
         * 参数1:方法名称
         * 参数2:方法的参数个数和类型(可变参数!)
         */
        Method eat = c.getDeclaredMethod("eat",String.class);
        eat.setAccessible(true);
        eat.invoke(s,"饭"); // 参数1:被触发方法所在的对象, 参数2:方法需要的入参

    }
}
class Student{
    private String name;

    public Student() {
    }

    public Student(String name) {
        this.name = name;
    }
    public void run(){
        System.out.println("人跑的很快");
    }
    public void eat(){
        System.out.println("人在吃肉");
    }
    public void eat(String name){
        System.out.println("人在吃"+name);
    }
    public static void inAddr(){
        System.out.println("在吉山村");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

反射会破坏面向对象的封装性与泛型的约束性。

public class test {
    public static void main(String[] args) throws Exception {
        // 泛型只能工作在编译阶段,运行阶段泛型就消失了
        // 反射工作在运行时阶段
        List<Double> scores=new ArrayList<>();
        scores.add(98.4);
        scores.add(95.4);
        scores.add(93.4);
//        scores.add("张三") // 此时报错,因为泛型约束

        // 通过反射暴力注入一个其他类型的数据进去
        Class s=scores.getClass();
        Method add = s.getDeclaredMethod("add", Object.class);
        add.invoke(scores,"张三");

        System.out.println(scores); // [98.4, 95.4, 93.4, 张三]
    }
}

拓展:反射的作用
可以在运行时得到一个类的全部成分然后操作。
可以破坏封装性。也可以破坏泛型的约束性。
适合做Java高级框架,基本上主流框架都会基于反射设计一些通用技术功能。

写一个类似Mybatis框架:
给任何一个对象数据都可以直接解析字段并且把对应数据保存起来。

public class test {
    public static void main(String[] args) throws Exception {
        Student s=new Student(1,"小红",'女',"吉山村");
        Pig p=new Pig("佩奇",222.0,"粉色");
        Mybatis.save(s);
        Mybatis.save(p);
    }
}
class Mybatis{
    // 提供一个方法:可以保存一切对象数据的字段和具体值。
    public static void save(Object obj) throws Exception{
        try (
                PrintStream ps=new PrintStream(new FileOutputStream("/Users/temp/Desktop/a.txt",true));
                ){
            // 解析对象的每个字段和值存起来
            Class c = obj.getClass();
            Field[] fields = c.getDeclaredFields();
            ps.println("==========="+c.getSimpleName()+"==============");
            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();
        }
    }
}
class Student{
    private int id;
    private String name;
    private char sex;
    private String addr;

    public Student() {
    }

    public Student(int id, String name, char sex, String addr) {
        this.id = id;
        this.name = name;
        this.sex = sex;
        this.addr = addr;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public char getSex() {
        return sex;
    }

    public void setSex(char sex) {
        this.sex = sex;
    }

    public String getAddr() {
        return addr;
    }

    public void setAddr(String addr) {
        this.addr = addr;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", sex=" + sex +
                ", addr='" + addr + '\'' +
                '}';
    }
}
class Pig{
    private String name;
    private Double weight;
    private String color;

    public Pig() {
    }

    public Pig(String name, Double weight, String color) {
        this.name = name;
        this.weight = weight;
        this.color = color;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Double getWeight() {
        return weight;
    }

    public void setWeight(Double weight) {
        this.weight = weight;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public String toString() {
        return "Pig{" +
                "name='" + name + '\'' +
                ", weight=" + weight +
                ", color='" + color + '\'' +
                '}';
    }
}

注解

注解:用在类上、方法上、成员变量、构造器、. . . 上对成分进行编译约束,标记等作用。是JDK 1.5的新特性

直接相当于一种标记,是类的组成部分,可以给类携带一些额外的信息。
注解是给 编译器 和 JVM 看的,编译器或JVM可以根据注解来完成对应的功能。

注解的作用

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


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

@Book
@MyBook
public class test{
    @Book
    @MyBook
    public static void main(@MyBook String[] args) {

    }
}
// 自定义注解
@interface Book{

}
@interface MyBook{

}

注解的属性
格式

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


属性适用的数据类型
八种基本数据类型(byte、int、short、long、float、double、boolean、char)
String
Class
以上类型的数组形式都支持。
注意:注解可以有属性,属性必须带()。

@MyBook(name = "精通java基础",authors = {"张三","李四"},price = 99.0)
public class test{
    @MyBook(name = "精通MySql基础",authors = {"王五","李四"},price = 99.0,address = "广州")
    public static void main(String[] args) {

    }
}
// 自定义注解
@interface MyBook{
    String name();
    String[] authors();
    double price();
    String address() default "中国";
}

注解的特殊属性:value
value属性,如果只有一个value数学的情况下,使用value数学的时候可以省略value名称不写!!
但如果有多个属性,且多个属性没有默认值,那么value是不能省略的。

// 以下3种方式,效果一样
//@Book("/deteleBook.action")
//@Book(value = "/deteleBook.action")
@Book(value = "/deteleBook.action",age = 12)
public class test{

}

// 自定义注解
@interface Book{
    String value();
    int age() default 10;
}

元注解

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

元注解有两个

  • @Target:约束自定义注解可以标记的范围。若没有次注解标识,则自定义注解可在任意地方使用。
    可使用的值定义在ElementType枚举类中,常量值如下
    TYPE,类,接口
    FIELD,成员变量
    METHOD,成员方法
    PARAMENTER,方法参数
    CONSTRUCTOR,构造器
    LOCAL_VARIABLE,局部变量
  • @Rentention:申明注解的生命周期,约束自定义注解的存活范围。申明注解的作用范围。
    可使用的值定义在RetentionPolicy枚举类中,常用值如下
    SOURCE:注解只作用在源码阶段,生成的字节码文件中不存在。
    CLASS:注解作用在源码阶段,字节码文件阶段,运行阶段不存在。默认值。
    RUNTIME:注解作用在源码阶段,字节码文件阶段,运行阶段(开发常用)
public class test{

    private String name;
    @Book
    public static void main(String[] args) {
    }
    @Book
    public void run(){}
}

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

}

注解的解析

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

与注解解析相关的接口

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

解析注解数据的原理:

  • 注解在哪个成分上,就先拿哪个成分对象。在拿注解。
    【比如:注解作用在成员方法上,则要获得该成员方法对应的Method对象,再来拿上面的注解。
    比如:注解作用在类上,则要该类的Class对象,再拿上面的注解。
    比如:注解作用在成员变量上,则要获得该成员变量对应的Field对象,再来拿上面的注解。】
public class test{
    @Test
    public void ParseClass(){
        // 定位Class类对象
        Class c=BookStore.class;
        // 判断这个类上是否使用了某个注解
        if(c.isAnnotationPresent(Book.class)){
            // 获取这个注解对象
            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 {
        Class c=BookStore.class;
        Method run = c.getDeclaredMethod("run");
        if(run.isAnnotationPresent(Book.class)){
            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 = 200.0 ,authors = {"张三","李四"})
class BookStore{
    @Book(value = "java进阶",price =3200.0 ,authors = {"王五","李四"})
    public void run(){

    }
}

@Target({ElementType.METHOD,ElementType.TYPE})  // 申明只能注解方法和成员变量
@Retention(RetentionPolicy.RUNTIME) // 申明注解从写代码一直到运行还在,永远存活!
@interface Book{
    String value();
    double price() default 100;
    String[] authors();
}
// 注解模拟junit框架
public class test{
    @MyTest
    public void test01(){
        System.out.println("========== test01 ========");
    }
    public void test02(){
        System.out.println("========== test02 ========");
    }
    @MyTest
    public void test03(){
        System.out.println("========== test03 ========");
    }

    public static void main(String[] args) throws Exception {
        test t=new test();
        // 模拟测试类的启动按钮,实现有标记的方法就要触发执行
        // 得到类对象
        Class c=test.class;
        // 获取类对象的全部方法
        Method[] methods = c.getDeclaredMethods();
        // 遍历方法,有注解就触发执行
        for (Method method : methods) {
            if(method.isAnnotationPresent(MyTest.class)){
                // 触发此方法执行
                method.invoke(t);
            }
        }
    }
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyTest{

}

XML

XML:可扩展标记语言。

  • 用于数据交互,用于存储数据用于做系统配置文件
  • 区分大小写
  • 非常严谨,只要有错误,解析器就不能解析。
  • 可扩展的,所有的标签都是程序员自己创建出来的。
  • XML文件的后缀是**.xml**


XML由7种组成元素构成

  1. 声明(抬头)
  2. 元素(标签)
  3. 属性
  4. 注释
  5. 实体字符
  6. CDATA字符数据区
  7. 处理指令

xml 7种元素举例:






<person>

    <name id="1" desc="高富帅">王熙凤name>
    <sex>sex>
    <age>16age>

    <sql>

        select * from person where age < 18 && age >10;
    sql>

    <sql2>
        10;-
        ]]>
    sql2>
person>

css样式:

sex{
    color: red;
}
age{
    color: green;
}

XML的约束文件:DTD
作用:约束XML文件格式的规范性。
在实际开发中,自己无需编写DTD约束文档,只需根据DTD约束文档写出相应XML配置文档,然后会导入即可。
Java 基础笔记_第45张图片
导入方式:
Java 基础笔记_第46张图片
可以约束标签,但无法约束标签里的内容。

XML的约束文件:Schema
作用:Schema本身是一个XML文件,更严谨。可以验证数据类型。一个XML实例文档可以被多个xsd模版文档约束,要使用前缀来引用。

定义Schema模版文档:
Java 基础笔记_第47张图片
Schema实例文档:
Java 基础笔记_第48张图片

解析xml文件方式:

  1. DOM解析。
    优点:将整个XML文件加载到内存中,生成一颗DOM树,随意访问树上任何一个节点,可以修改和删除节点,程序开发比较方便。纯面向对象开发。
    缺点:占内存,若XML文件很大,可能出现内存溢出。

  2. SAX解析。
    优点:事件驱动型解析方式,读物一行就解析一行,释放内存。理论上可以解析任意大小的XML
    文件。
    缺点:使用过的元素就不能再访问了,不能修改元素,只能查找。
    Java 基础笔记_第49张图片

DOM树原理

XML DOM 和 HTML DOM一样,XML DOM 将整个XML文档加载到内存,生成一个DOM树,并获得一个Document对象,通过Document对象就可以对DOM进行操作。
Java 基础笔记_第50张图片

Java 基础笔记_第51张图片

Dom4J框架使用

注意:要导入dom4j框架包。

java提供了Class下的一个方法:

  • public InputStream getResourceAsStream(String path):用于加载文件成为一个字节输入流返回。


    Document文档:
  • Element getRootElement():获取根元素。


Element元素的API

  • String getName():获取元素的名称。
  • List elements():获取当前元素下的全部子元素(一级)
  • List elements(String name):获取当前元素下的指定名称的全部子元素(一级)。
  • Element element(String name):获取当前元素下的指定名称的某个子元素,默认取第一个(一级)。
  • List attributes():获取元素的全部属性对象。
  • Attribute attribute(String name):根据名称获取某个元素的属性对象。
  • String attributeValue(String var1):直接获取某个元素的某个属性名称的值。
  • String elementText(String name):可以直接获取当前元素的子元素的文本内容
  • String elementTextTrim(String name):去前后空格,直接获取当前元素的子元素的文本内容。
  • String getText():直接获取当前元素的文本内容。
  • String getTextTrim():去前后空格,直接获取当前元素的文本内容。


Attribute对象的API

  • String getName():获取属性名称。
  • String getValue():获取属性值。
// 获取Document对象和根元素
public class test{
    public static void main(String[] args) throws Exception {
        // 需求:分析books.xml文件成为一个Document文档树对象,得到根元素对象。
        // 创建一个dom4j的解析器对象:代表整个dom4j框架
        SAXReader saxReader=new SAXReader();

        // 通过解析器对象去加载xml文件数据,成为一个Document文档树对象。
        Document document=saxReader.read(new File("/Users/temp/Documents/IdeaProject/homework/xml/books.xml"));

        // 获取根元素对象
        Element root=document.getRootElement();
        System.out.println(root.getName());

        // 获取根元素下的全部子元素
        List<Element> sonElements=root.elements();
        for (Element sonElement : sonElements) {
            System.out.println(sonElement.getName());
        }

        System.out.println("------------------------");
        // 获取根元素下的全部book子元素
        List<Element> sonElement1=root.elements("book");
        for (Element element : sonElement1) {
            System.out.println(element.getName());
        }

        System.out.println("------------------------");
        // 获取根元素下的指定某个元素
        Element son=root.element("user");
        System.out.println(son.getName());
    }
}

books.xml:


<books>
    <book id="0001" desc="第一本书">
        <name>aaa书名name>
        <author>张三author>
        <sale>111.9sale>
    book>
    <book id="0002" >
        <name>bbb书名name>
        <author>李四author>
        <sale>133.9sale>
    book>
    <user>

    user>
books>
// 获取Document对象和根元素 及 属性对象
public class test{
    public static void main(String[] args) throws Exception {
        // 创建一个dom4j的解析器对象:代表整个dom4j框架
        SAXReader saxReader=new SAXReader();

        // 方式一:通过解析器对象去加载xml文件数据,成为一个Document文档树对象。
        Document document=saxReader.read(new File("/Users/temp/Documents/IdeaProject/homework/xml/books.xml"));

        // 获取根元素对象
        Element root=document.getRootElement();
        System.out.println(root.getName());

        // 获取根元素下的子元素
        Element boosEle=root.element("book");

        // 获取book元素的全部属性对象
        List<Attribute> attributes=boosEle.attributes();
        for (Attribute attribute : attributes) {
            System.out.println(attribute.getName()+"==>"+attribute.getValue());
        }

        // 获取Book元素的某个属性对象
        Attribute descAttr=boosEle.attribute("desc");
        System.out.println(descAttr.getName()+"==>"+descAttr.getValue());

        // 直接获取元素的属性值
        System.out.println(boosEle.attribute("id"));
        System.out.println(boosEle.attribute("desc"));
    }
}
// 获取元素的文本值
public class test{
    public static void main(String[] args) th

你可能感兴趣的:(Java,java)